mirror of
https://github.com/postgres/postgres.git
synced 2025-12-01 12:18:01 +03:00
Implement function-local GUC parameter settings, as per recent discussion.
There are still some loose ends: I didn't do anything about the SET FROM CURRENT idea yet, and it's not real clear whether we are happy with the interaction of SET LOCAL with function-local settings. The documentation is a bit spartan, too.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
#
|
||||
#
|
||||
# IDENTIFICATION
|
||||
# $PostgreSQL: pgsql/src/backend/utils/Gen_fmgrtab.sh,v 1.36 2007/06/05 21:31:06 tgl Exp $
|
||||
# $PostgreSQL: pgsql/src/backend/utils/Gen_fmgrtab.sh,v 1.37 2007/09/03 00:39:17 tgl Exp $
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
@@ -134,12 +134,12 @@ cat > "$$-$OIDSFILE" <<FuNkYfMgRsTuFf
|
||||
*/
|
||||
FuNkYfMgRsTuFf
|
||||
|
||||
# Note assumption here that prosrc == $(NF-2).
|
||||
# Note assumption here that prosrc == $(NF-3).
|
||||
|
||||
tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' < $SORTEDFILE | \
|
||||
$AWK '
|
||||
BEGIN { OFS = ""; }
|
||||
{ if (seenit[$(NF-2)]++ == 0) print "#define F_", $(NF-2), " ", $1; }' >> "$$-$OIDSFILE"
|
||||
{ if (seenit[$(NF-3)]++ == 0) print "#define F_", $(NF-3), " ", $1; }' >> "$$-$OIDSFILE"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
cleanup
|
||||
@@ -184,9 +184,9 @@ cat > "$$-$TABLEFILE" <<FuNkYfMgRtAbStUfF
|
||||
|
||||
FuNkYfMgRtAbStUfF
|
||||
|
||||
# Note assumption here that prosrc == $(NF-2).
|
||||
# Note assumption here that prosrc == $(NF-3).
|
||||
|
||||
$AWK '{ print "extern Datum", $(NF-2), "(PG_FUNCTION_ARGS);"; }' $SORTEDFILE >> "$$-$TABLEFILE"
|
||||
$AWK '{ print "extern Datum", $(NF-3), "(PG_FUNCTION_ARGS);"; }' $SORTEDFILE >> "$$-$TABLEFILE"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
cleanup
|
||||
@@ -204,7 +204,7 @@ FuNkYfMgRtAbStUfF
|
||||
# may seem tedious, but avoid the temptation to write a quick x?y:z
|
||||
# conditional expression instead. Not all awks have conditional expressions.
|
||||
#
|
||||
# Note assumptions here that prosrc == $(NF-2), pronargs == $13,
|
||||
# Note assumptions here that prosrc == $(NF-3), pronargs == $13,
|
||||
# proisstrict == $10, proretset == $11
|
||||
|
||||
$AWK 'BEGIN {
|
||||
@@ -212,7 +212,7 @@ $AWK 'BEGIN {
|
||||
Bool["f"] = "false"
|
||||
}
|
||||
{ printf (" { %d, \"%s\", %d, %s, %s, %s },\n"), \
|
||||
$1, $(NF-2), $13, Bool[$10], Bool[$11], $(NF-2)
|
||||
$1, $(NF-3), $13, Bool[$10], Bool[$11], $(NF-3)
|
||||
}' $SORTEDFILE >> "$$-$TABLEFILE"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.108 2007/07/31 15:49:49 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.109 2007/09/03 00:39:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "access/tuptoaster.h"
|
||||
#include "catalog/pg_language.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
@@ -23,9 +24,11 @@
|
||||
#include "parser/parse_expr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgrtab.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
|
||||
/*
|
||||
* Declaration for old-style function pointer type. This is now used only
|
||||
* in fmgr_oldstyle() and is no longer exported.
|
||||
@@ -212,7 +215,13 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
|
||||
finfo->fn_strict = procedureStruct->proisstrict;
|
||||
finfo->fn_retset = procedureStruct->proretset;
|
||||
|
||||
if (procedureStruct->prosecdef && !ignore_security)
|
||||
/*
|
||||
* If it has prosecdef set, or non-null proconfig, use
|
||||
* fmgr_security_definer call handler.
|
||||
*/
|
||||
if (!ignore_security &&
|
||||
(procedureStruct->prosecdef ||
|
||||
!heap_attisnull(procedureTuple, Anum_pg_proc_proconfig)))
|
||||
{
|
||||
finfo->fn_addr = fmgr_security_definer;
|
||||
finfo->fn_oid = functionId;
|
||||
@@ -826,34 +835,45 @@ fmgr_oldstyle(PG_FUNCTION_ARGS)
|
||||
|
||||
|
||||
/*
|
||||
* Support for security definer functions
|
||||
* Support for security-definer and proconfig-using functions. We support
|
||||
* both of these features using the same call handler, because they are
|
||||
* often used together and it would be inefficient (as well as notationally
|
||||
* messy) to have two levels of call handler involved.
|
||||
*/
|
||||
|
||||
struct fmgr_security_definer_cache
|
||||
{
|
||||
FmgrInfo flinfo;
|
||||
Oid userid;
|
||||
FmgrInfo flinfo; /* lookup info for target function */
|
||||
Oid userid; /* userid to set, or InvalidOid */
|
||||
ArrayType *proconfig; /* GUC values to set, or NULL */
|
||||
};
|
||||
|
||||
/*
|
||||
* Function handler for security definer functions. We extract the
|
||||
* OID of the actual function and do a fmgr lookup again. Then we
|
||||
* look up the owner of the function and cache both the fmgr info and
|
||||
* the owner ID. During the call we temporarily replace the flinfo
|
||||
* with the cached/looked-up one, while keeping the outer fcinfo
|
||||
* (which contains all the actual arguments, etc.) intact.
|
||||
* Function handler for security-definer/proconfig functions. We extract the
|
||||
* OID of the actual function and do a fmgr lookup again. Then we fetch the
|
||||
* pg_proc row and copy the owner ID and proconfig fields. (All this info
|
||||
* is cached for the duration of the current query.) To execute a call,
|
||||
* we temporarily replace the flinfo with the cached/looked-up one, while
|
||||
* keeping the outer fcinfo (which contains all the actual arguments, etc.)
|
||||
* intact. This is not re-entrant, but then the fcinfo itself can't be used
|
||||
* re-entrantly anyway.
|
||||
*/
|
||||
static Datum
|
||||
fmgr_security_definer(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Datum result;
|
||||
FmgrInfo *save_flinfo;
|
||||
struct fmgr_security_definer_cache *volatile fcache;
|
||||
FmgrInfo *save_flinfo;
|
||||
Oid save_userid;
|
||||
HeapTuple tuple;
|
||||
volatile int save_nestlevel;
|
||||
|
||||
if (!fcinfo->flinfo->fn_extra)
|
||||
{
|
||||
HeapTuple tuple;
|
||||
Form_pg_proc procedureStruct;
|
||||
Datum datum;
|
||||
bool isnull;
|
||||
MemoryContext oldcxt;
|
||||
|
||||
fcache = MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt,
|
||||
sizeof(*fcache));
|
||||
|
||||
@@ -867,7 +887,20 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
|
||||
if (!HeapTupleIsValid(tuple))
|
||||
elog(ERROR, "cache lookup failed for function %u",
|
||||
fcinfo->flinfo->fn_oid);
|
||||
fcache->userid = ((Form_pg_proc) GETSTRUCT(tuple))->proowner;
|
||||
procedureStruct = (Form_pg_proc) GETSTRUCT(tuple);
|
||||
|
||||
if (procedureStruct->prosecdef)
|
||||
fcache->userid = procedureStruct->proowner;
|
||||
|
||||
datum = SysCacheGetAttr(PROCOID, tuple, Anum_pg_proc_proconfig,
|
||||
&isnull);
|
||||
if (!isnull)
|
||||
{
|
||||
oldcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
|
||||
fcache->proconfig = DatumGetArrayTypePCopy(datum);
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
|
||||
fcinfo->flinfo->fn_extra = fcache;
|
||||
@@ -876,25 +909,47 @@ fmgr_security_definer(PG_FUNCTION_ARGS)
|
||||
fcache = fcinfo->flinfo->fn_extra;
|
||||
|
||||
save_flinfo = fcinfo->flinfo;
|
||||
/* GetUserId is cheap enough that no harm in a wasted call */
|
||||
save_userid = GetUserId();
|
||||
if (fcache->proconfig) /* Need a new GUC nesting level */
|
||||
save_nestlevel = NewGUCNestLevel();
|
||||
else
|
||||
save_nestlevel = 0; /* keep compiler quiet */
|
||||
|
||||
PG_TRY();
|
||||
{
|
||||
fcinfo->flinfo = &fcache->flinfo;
|
||||
SetUserId(fcache->userid);
|
||||
|
||||
if (OidIsValid(fcache->userid))
|
||||
SetUserId(fcache->userid);
|
||||
|
||||
if (fcache->proconfig)
|
||||
{
|
||||
/* The options are processed as if by SET LOCAL var = val */
|
||||
ProcessGUCArray(fcache->proconfig,
|
||||
(superuser() ? PGC_SUSET : PGC_USERSET),
|
||||
PGC_S_SESSION,
|
||||
true);
|
||||
}
|
||||
|
||||
result = FunctionCallInvoke(fcinfo);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
fcinfo->flinfo = save_flinfo;
|
||||
SetUserId(save_userid);
|
||||
if (fcache->proconfig)
|
||||
AtEOXact_GUC(false, save_nestlevel);
|
||||
if (OidIsValid(fcache->userid))
|
||||
SetUserId(save_userid);
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
fcinfo->flinfo = save_flinfo;
|
||||
SetUserId(save_userid);
|
||||
if (fcache->proconfig)
|
||||
AtEOXact_GUC(true, save_nestlevel);
|
||||
if (OidIsValid(fcache->userid))
|
||||
SetUserId(save_userid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.162 2007/02/15 23:23:23 alvherre Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.163 2007/09/03 00:39:18 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@@ -448,7 +448,12 @@ InitializeSessionUserId(const char *rolename)
|
||||
{
|
||||
ArrayType *a = DatumGetArrayTypeP(datum);
|
||||
|
||||
ProcessGUCArray(a, PGC_S_USER);
|
||||
/*
|
||||
* We process all the options at SUSET level. We assume that the
|
||||
* right to insert an option into pg_authid was checked when it was
|
||||
* inserted.
|
||||
*/
|
||||
ProcessGUCArray(a, PGC_SUSET, PGC_S_USER, false);
|
||||
}
|
||||
|
||||
ReleaseSysCache(roleTup);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.176 2007/05/27 05:37:49 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.177 2007/09/03 00:39:18 tgl Exp $
|
||||
*
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
@@ -250,7 +250,12 @@ CheckMyDatabase(const char *name, bool am_superuser)
|
||||
{
|
||||
ArrayType *a = DatumGetArrayTypeP(datum);
|
||||
|
||||
ProcessGUCArray(a, PGC_S_DATABASE);
|
||||
/*
|
||||
* We process all the options at SUSET level. We assume that the
|
||||
* right to insert an option into pg_database was checked when it
|
||||
* was inserted.
|
||||
*/
|
||||
ProcessGUCArray(a, PGC_SUSET, PGC_S_DATABASE, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* Written by Peter Eisentraut <peter_e@gmx.net>.
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.414 2007/08/21 01:11:19 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.415 2007/09/03 00:39:19 tgl Exp $
|
||||
*
|
||||
*--------------------------------------------------------------------
|
||||
*/
|
||||
@@ -2495,6 +2495,8 @@ static bool guc_dirty; /* TRUE if need to do commit/abort work */
|
||||
|
||||
static bool reporting_enabled; /* TRUE to enable GUC_REPORT */
|
||||
|
||||
static int GUCNestLevel = 0; /* 1 when in main transaction */
|
||||
|
||||
|
||||
static int guc_var_compare(const void *a, const void *b);
|
||||
static int guc_name_compare(const char *namea, const char *nameb);
|
||||
@@ -3388,17 +3390,16 @@ ResetAllOptions(void)
|
||||
static void
|
||||
push_old_value(struct config_generic * gconf)
|
||||
{
|
||||
int my_level = GetCurrentTransactionNestLevel();
|
||||
GucStack *stack;
|
||||
|
||||
/* If we're not inside a transaction, do nothing */
|
||||
if (my_level == 0)
|
||||
if (GUCNestLevel == 0)
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
/* Done if we already pushed it at this nesting depth */
|
||||
if (gconf->stack && gconf->stack->nest_level >= my_level)
|
||||
if (gconf->stack && gconf->stack->nest_level >= GUCNestLevel)
|
||||
return;
|
||||
|
||||
/*
|
||||
@@ -3457,20 +3458,53 @@ push_old_value(struct config_generic * gconf)
|
||||
}
|
||||
|
||||
/*
|
||||
* Do GUC processing at transaction or subtransaction commit or abort.
|
||||
* Do GUC processing at main transaction start.
|
||||
*/
|
||||
void
|
||||
AtEOXact_GUC(bool isCommit, bool isSubXact)
|
||||
AtStart_GUC(void)
|
||||
{
|
||||
/*
|
||||
* The nest level should be 0 between transactions; if it isn't,
|
||||
* somebody didn't call AtEOXact_GUC, or called it with the wrong
|
||||
* nestLevel. We throw a warning but make no other effort to clean up.
|
||||
*/
|
||||
if (GUCNestLevel != 0)
|
||||
elog(WARNING, "GUC nest level = %d at transaction start",
|
||||
GUCNestLevel);
|
||||
GUCNestLevel = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enter a new nesting level for GUC values. This is called at subtransaction
|
||||
* start and when entering a function that has proconfig settings. NOTE that
|
||||
* we must not risk error here, else subtransaction start will be unhappy.
|
||||
*/
|
||||
int
|
||||
NewGUCNestLevel(void)
|
||||
{
|
||||
return ++GUCNestLevel;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do GUC processing at transaction or subtransaction commit or abort, or
|
||||
* when exiting a function that has proconfig settings. (The name is thus
|
||||
* a bit of a misnomer; perhaps it should be ExitGUCNestLevel or some such.)
|
||||
* During abort, we discard all GUC settings that were applied at nesting
|
||||
* levels >= nestLevel. nestLevel == 1 corresponds to the main transaction.
|
||||
*/
|
||||
void
|
||||
AtEOXact_GUC(bool isCommit, int nestLevel)
|
||||
{
|
||||
int my_level;
|
||||
int i;
|
||||
|
||||
Assert(nestLevel > 0 && nestLevel <= GUCNestLevel);
|
||||
|
||||
/* Quick exit if nothing's changed in this transaction */
|
||||
if (!guc_dirty)
|
||||
{
|
||||
GUCNestLevel = nestLevel - 1;
|
||||
return;
|
||||
|
||||
my_level = GetCurrentTransactionNestLevel();
|
||||
Assert(isSubXact ? (my_level > 1) : (my_level == 1));
|
||||
}
|
||||
|
||||
for (i = 0; i < num_guc_variables; i++)
|
||||
{
|
||||
@@ -3491,9 +3525,9 @@ AtEOXact_GUC(bool isCommit, bool isSubXact)
|
||||
/* Assert that we stacked old value before changing it */
|
||||
Assert(stack != NULL && (my_status & GUC_HAVE_STACK));
|
||||
/* However, the last change may have been at an outer xact level */
|
||||
if (stack->nest_level < my_level)
|
||||
if (stack->nest_level < nestLevel)
|
||||
continue;
|
||||
Assert(stack->nest_level == my_level);
|
||||
Assert(stack->nest_level == nestLevel);
|
||||
|
||||
/*
|
||||
* We will pop the stack entry. Start by restoring outer xact status
|
||||
@@ -3677,7 +3711,7 @@ AtEOXact_GUC(bool isCommit, bool isSubXact)
|
||||
set_string_field(conf, &stack->tentative_val.stringval,
|
||||
NULL);
|
||||
/* Don't store tentative value separately after commit */
|
||||
if (!isSubXact)
|
||||
if (nestLevel == 1)
|
||||
set_string_field(conf, &conf->tentative_val, NULL);
|
||||
break;
|
||||
}
|
||||
@@ -3691,7 +3725,7 @@ AtEOXact_GUC(bool isCommit, bool isSubXact)
|
||||
* If we're now out of all xact levels, forget TENTATIVE status bit;
|
||||
* there's nothing tentative about the value anymore.
|
||||
*/
|
||||
if (!isSubXact)
|
||||
if (nestLevel == 1)
|
||||
{
|
||||
Assert(gconf->stack == NULL);
|
||||
gconf->status = 0;
|
||||
@@ -3708,8 +3742,11 @@ AtEOXact_GUC(bool isCommit, bool isSubXact)
|
||||
* that all outer transaction levels will have stacked values to deal
|
||||
* with.)
|
||||
*/
|
||||
if (!isSubXact)
|
||||
if (nestLevel == 1)
|
||||
guc_dirty = false;
|
||||
|
||||
/* Update nesting level */
|
||||
GUCNestLevel = nestLevel - 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -6078,11 +6115,14 @@ ParseLongOption(const char *string, char **name, char **value)
|
||||
|
||||
|
||||
/*
|
||||
* Handle options fetched from pg_database.datconfig or pg_authid.rolconfig.
|
||||
* Handle options fetched from pg_database.datconfig, pg_authid.rolconfig,
|
||||
* pg_proc.proconfig, etc. Caller must specify proper context/source/local.
|
||||
*
|
||||
* The array parameter must be an array of TEXT (it must not be NULL).
|
||||
*/
|
||||
void
|
||||
ProcessGUCArray(ArrayType *array, GucSource source)
|
||||
ProcessGUCArray(ArrayType *array,
|
||||
GucContext context, GucSource source, bool isLocal)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -6090,7 +6130,6 @@ ProcessGUCArray(ArrayType *array, GucSource source)
|
||||
Assert(ARR_ELEMTYPE(array) == TEXTOID);
|
||||
Assert(ARR_NDIM(array) == 1);
|
||||
Assert(ARR_LBOUND(array)[0] == 1);
|
||||
Assert(source == PGC_S_DATABASE || source == PGC_S_USER);
|
||||
|
||||
for (i = 1; i <= ARR_DIMS(array)[0]; i++)
|
||||
{
|
||||
@@ -6117,17 +6156,13 @@ ProcessGUCArray(ArrayType *array, GucSource source)
|
||||
{
|
||||
ereport(WARNING,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("could not parse setting for parameter \"%s\"", name)));
|
||||
errmsg("could not parse setting for parameter \"%s\"",
|
||||
name)));
|
||||
free(name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* We process all these options at SUSET level. We assume that the
|
||||
* right to insert an option into pg_database or pg_authid was checked
|
||||
* when it was inserted.
|
||||
*/
|
||||
SetConfigOption(name, value, PGC_SUSET, source);
|
||||
(void) set_config_option(name, value, context, source, isLocal, true);
|
||||
|
||||
free(name);
|
||||
if (value)
|
||||
|
||||
Reference in New Issue
Block a user