Add support for invoking parser callback hooks via SPI and in cached plans.

As proof of concept, modify plpgsql to use the hooks.  plpgsql is still
inserting $n symbols textually, but the "back end" of the parsing process now
goes through the ParamRef hook instead of using a fixed parameter-type array,
and then execution only fetches actually-referenced parameters, using a hook
added to ParamListInfo.

Although there's a lot left to be done in plpgsql, this already cures the
"if (TG_OP = 'INSERT' and NEW.foo ...)"  problem, as illustrated by the
changed regression test.
This commit is contained in:
Tom Lane 2009-11-04 22:26:08 +00:00
parent 48912acc08
commit 9bedd128d6
23 changed files with 1110 additions and 533 deletions

View File

@ -1,4 +1,4 @@
<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.65 2009/08/05 19:31:50 alvherre Exp $ -->
<!-- $PostgreSQL: pgsql/doc/src/sgml/spi.sgml,v 1.66 2009/11/04 22:26:04 tgl Exp $ -->
<chapter id="spi">
<title>Server Programming Interface</title>
@ -861,7 +861,7 @@ SPIPlanPtr SPI_prepare(const char * <parameter>command</parameter>, int <paramet
<para>
<function>SPI_prepare</function> creates and returns an execution
plan for the specified command but doesn't execute the command.
plan for the specified command, but doesn't execute the command.
This function should only be called from a connected procedure.
</para>
@ -990,7 +990,7 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
of the planner's <quote>cursor options</> parameter. This is a bitmask
having the values shown in <filename>nodes/parsenodes.h</filename>
for the <structfield>options</> field of <structname>DeclareCursorStmt</>.
<function>SPI_prepare</function> always takes these options as zero.
<function>SPI_prepare</function> always takes the cursor options as zero.
</para>
</refsect1>
@ -1061,6 +1061,94 @@ SPIPlanPtr SPI_prepare_cursor(const char * <parameter>command</parameter>, int <
<!-- *********************************************** -->
<refentry id="spi-spi-prepare-params">
<refmeta>
<refentrytitle>SPI_prepare_params</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_prepare_params</refname>
<refpurpose>prepare a plan for a command, without executing it yet</refpurpose>
</refnamediv>
<indexterm><primary>SPI_prepare_params</primary></indexterm>
<refsynopsisdiv>
<synopsis>
SPIPlanPtr SPI_prepare_params(const char * <parameter>command</parameter>,
ParserSetupHook <parameter>parserSetup</parameter>,
void * <parameter>parserSetupArg</parameter>,
int <parameter>cursorOptions</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_prepare_params</function> creates and returns an execution
plan for the specified command, but doesn't execute the command.
This function is equivalent to <function>SPI_prepare_cursor</function>,
with the addition that the caller can specify parser hook functions
to control the parsing of external parameter references.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>command</parameter></literal></term>
<listitem>
<para>
command string
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ParserSetupHook <parameter>parserSetup</parameter></literal></term>
<listitem>
<para>
Parser hook setup function
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>void * <parameter>parserSetupArg</parameter></literal></term>
<listitem>
<para>
passthrough argument for <parameter>parserSetup</parameter>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>int <parameter>cursorOptions</parameter></literal></term>
<listitem>
<para>
integer bitmask of cursor options; zero produces default behavior
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
<function>SPI_prepare_params</function> has the same return conventions as
<function>SPI_prepare</function>.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-getargcount">
<refmeta>
<refentrytitle>SPI_getargcount</refentrytitle>
@ -1386,14 +1474,100 @@ int SPI_execute_plan(SPIPlanPtr <parameter>plan</parameter>, Datum * <parameter>
<function>SPI_execute</function> if successful.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-execute-plan-with-paramlist">
<refmeta>
<refentrytitle>SPI_execute_plan_with_paramlist</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_execute_plan_with_paramlist</refname>
<refpurpose>execute a plan prepared by <function>SPI_prepare</function></refpurpose>
</refnamediv>
<indexterm><primary>SPI_execute_plan_with_paramlist</primary></indexterm>
<refsynopsisdiv>
<synopsis>
int SPI_execute_plan_with_paramlist(SPIPlanPtr <parameter>plan</parameter>,
ParamListInfo <parameter>params</parameter>,
bool <parameter>read_only</parameter>,
long <parameter>count</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Notes</title>
<title>Description</title>
<para>
If one of the objects (a table, function, etc.) referenced by the
prepared plan is dropped during the session then the result of
<function>SPI_execute_plan</function> for this plan will be unpredictable.
<function>SPI_execute_plan_with_paramlist</function> executes a plan
prepared by <function>SPI_prepare</function>.
This function is equivalent to <function>SPI_execute_plan</function>
except that information about the parameter values to be passed to the
query is presented differently. The <literal>ParamListInfo</>
representation can be convenient for passing down values that are
already available in that format. It also supports use of dynamic
parameter sets via hook functions specified in <literal>ParamListInfo</>.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para>
<literal>true</> for read-only execution
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>long <parameter>count</parameter></literal></term>
<listitem>
<para>
maximum number of rows to process or return
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
The return value is the same as for <function>SPI_execute_plan</function>.
</para>
<para>
<varname>SPI_processed</varname> and
<varname>SPI_tuptable</varname> are set as in
<function>SPI_execute_plan</function> if successful.
</para>
</refsect1>
</refentry>
@ -1543,7 +1717,7 @@ Portal SPI_cursor_open(const char * <parameter>name</parameter>, SPIPlanPtr <par
</para>
<para>
The passed-in data will be copied into the cursor's portal, so it
The passed-in parameter data will be copied into the cursor's portal, so it
can be freed while the cursor still exists.
</para>
</refsect1>
@ -1667,7 +1841,7 @@ Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
</para>
<para>
The passed-in data will be copied into the cursor's portal, so it
The passed-in parameter data will be copied into the cursor's portal, so it
can be freed while the cursor still exists.
</para>
</refsect1>
@ -1770,6 +1944,104 @@ Portal SPI_cursor_open_with_args(const char *<parameter>name</parameter>,
<!-- *********************************************** -->
<refentry id="spi-spi-cursor-open-with-paramlist">
<refmeta>
<refentrytitle>SPI_cursor_open_with_paramlist</refentrytitle>
<manvolnum>3</manvolnum>
</refmeta>
<refnamediv>
<refname>SPI_cursor_open_with_paramlist</refname>
<refpurpose>set up a cursor using parameters</refpurpose>
</refnamediv>
<indexterm><primary>SPI_cursor_open_with_paramlist</primary></indexterm>
<refsynopsisdiv>
<synopsis>
Portal SPI_cursor_open_with_paramlist(const char *<parameter>name</parameter>,
SPIPlanPtr <parameter>plan</parameter>,
ParamListInfo <parameter>params</parameter>,
bool <parameter>read_only</parameter>)
</synopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
<function>SPI_cursor_open_with_paramlist</function> sets up a cursor
(internally, a portal) that will execute a plan prepared by
<function>SPI_prepare</function>.
This function is equivalent to <function>SPI_cursor_open</function>
except that information about the parameter values to be passed to the
query is presented differently. The <literal>ParamListInfo</>
representation can be convenient for passing down values that are
already available in that format. It also supports use of dynamic
parameter sets via hook functions specified in <literal>ParamListInfo</>.
</para>
<para>
The passed-in parameter data will be copied into the cursor's portal, so it
can be freed while the cursor still exists.
</para>
</refsect1>
<refsect1>
<title>Arguments</title>
<variablelist>
<varlistentry>
<term><literal>const char * <parameter>name</parameter></literal></term>
<listitem>
<para>
name for portal, or <symbol>NULL</symbol> to let the system
select a name
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SPIPlanPtr <parameter>plan</parameter></literal></term>
<listitem>
<para>
execution plan (returned by <function>SPI_prepare</function>)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>ParamListInfo <parameter>params</parameter></literal></term>
<listitem>
<para>
data structure containing parameter types and values; NULL if none
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bool <parameter>read_only</parameter></literal></term>
<listitem>
<para>
<literal>true</> for read-only execution
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Return Value</title>
<para>
Pointer to portal containing the cursor. Note there is no error
return convention; any error will be reported via <function>elog</>.
</para>
</refsect1>
</refentry>
<!-- *********************************************** -->
<refentry id="spi-spi-cursor-find">
<refmeta>
<refentrytitle>SPI_cursor_find</refentrytitle>

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.192 2009/10/12 18:10:41 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.193 2009/11/04 22:26:04 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -107,8 +107,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
ParamListInfo params, DestReceiver *dest)
{
ExplainState es;
Oid *param_types;
int num_params;
TupOutputState *tstate;
List *rewritten;
ListCell *lc;
@ -150,9 +148,6 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
opt->defname)));
}
/* Convert parameter type data to the form parser wants */
getParamListTypes(params, &param_types, &num_params);
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
@ -163,8 +158,10 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
* executed repeatedly. (See also the same hack in DECLARE CURSOR and
* PREPARE.) XXX FIXME someday.
*/
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
queryString, param_types, num_params);
rewritten = pg_analyze_and_rewrite_params((Node *) copyObject(stmt->query),
queryString,
(ParserSetupHook) setupParserWithParamList,
params);
/* emit opening boilerplate */
ExplainBeginOutput(&es);

View File

@ -10,7 +10,7 @@
* Copyright (c) 2002-2009, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.99 2009/08/10 05:46:50 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.100 2009/11/04 22:26:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -379,6 +379,11 @@ EvaluateParams(PreparedStatement *pstmt, List *params,
paramLI = (ParamListInfo)
palloc(sizeof(ParamListInfoData) +
(num_params - 1) *sizeof(ParamExternData));
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = num_params;
i = 0;

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.12 2009/10/26 02:26:29 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.13 2009/11/04 22:26:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -217,9 +217,21 @@ fetch_param_value(ExprContext *econtext, int paramId)
{
ParamExternData *prm = &paramInfo->params[paramId - 1];
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
(*paramInfo->paramFetch) (paramInfo, paramId);
if (OidIsValid(prm->ptype) && !prm->isnull)
{
Assert(prm->ptype == REFCURSOROID);
/* safety check in case hook did something unexpected */
if (prm->ptype != REFCURSOROID)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
paramId,
format_type_be(prm->ptype),
format_type_be(REFCURSOROID))));
/* We know that refcursor uses text's I/O routines */
return TextDatumGetCString(prm->value);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.253 2009/10/26 02:26:29 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.254 2009/11/04 22:26:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -882,9 +882,21 @@ ExecEvalParam(ExprState *exprstate, ExprContext *econtext,
{
ParamExternData *prm = &paramInfo->params[thisParamId - 1];
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL)
(*paramInfo->paramFetch) (paramInfo, thisParamId);
if (OidIsValid(prm->ptype))
{
Assert(prm->ptype == expression->paramtype);
/* safety check in case hook did something unexpected */
if (prm->ptype != expression->paramtype)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
thisParamId,
format_type_be(prm->ptype),
format_type_be(expression->paramtype))));
*isNull = prm->isnull;
return prm->value;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.135 2009/06/11 17:25:38 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.136 2009/11/04 22:26:05 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -526,6 +526,11 @@ postquel_sub_params(SQLFunctionCachePtr fcache,
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
(nargs - 1) *sizeof(ParamExternData));
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs;
fcache->paramLI = paramLI;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.210 2009/10/10 01:43:47 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.211 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -45,8 +45,7 @@ static int _SPI_connected = -1;
static int _SPI_curid = -1;
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Datum *Values, const char *Nulls,
bool read_only, int pflags);
ParamListInfo paramLI, bool read_only);
static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan,
ParamListInfo boundParams);
@ -407,6 +406,28 @@ SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount)
return SPI_execute_plan(plan, Values, Nulls, false, tcount);
}
/* Execute a previously prepared plan */
int
SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params,
bool read_only, long tcount)
{
int res;
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0)
return SPI_ERROR_ARGUMENT;
res = _SPI_begin_call(true);
if (res < 0)
return res;
res = _SPI_execute_plan(plan, params,
InvalidSnapshot, InvalidSnapshot,
read_only, true, tcount);
_SPI_end_call(true);
return res;
}
/*
* SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow
* the caller to specify exactly which snapshots to use, which will be
@ -483,6 +504,8 @@ SPI_execute_with_args(const char *src,
plan.cursor_options = 0;
plan.nargs = nargs;
plan.argtypes = argtypes;
plan.parserSetup = NULL;
plan.parserSetupArg = NULL;
paramLI = _SPI_convert_params(nargs, argtypes,
Values, Nulls,
@ -528,6 +551,45 @@ SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
plan.cursor_options = cursorOptions;
plan.nargs = nargs;
plan.argtypes = argtypes;
plan.parserSetup = NULL;
plan.parserSetupArg = NULL;
_SPI_prepare_plan(src, &plan, NULL);
/* copy plan to procedure context */
result = _SPI_copy_plan(&plan, _SPI_current->procCxt);
_SPI_end_call(true);
return result;
}
SPIPlanPtr
SPI_prepare_params(const char *src,
ParserSetupHook parserSetup,
void *parserSetupArg,
int cursorOptions)
{
_SPI_plan plan;
SPIPlanPtr result;
if (src == NULL)
{
SPI_result = SPI_ERROR_ARGUMENT;
return NULL;
}
SPI_result = _SPI_begin_call(true);
if (SPI_result < 0)
return NULL;
memset(&plan, 0, sizeof(_SPI_plan));
plan.magic = _SPI_PLAN_MAGIC;
plan.cursor_options = cursorOptions;
plan.nargs = 0;
plan.argtypes = NULL;
plan.parserSetup = parserSetup;
plan.parserSetupArg = parserSetupArg;
_SPI_prepare_plan(src, &plan, NULL);
@ -954,8 +1016,21 @@ SPI_cursor_open(const char *name, SPIPlanPtr plan,
Datum *Values, const char *Nulls,
bool read_only)
{
return SPI_cursor_open_internal(name, plan, Values, Nulls,
read_only, 0);
Portal portal;
ParamListInfo paramLI;
/* build transient ParamListInfo in caller's context */
paramLI = _SPI_convert_params(plan->nargs, plan->argtypes,
Values, Nulls,
0);
portal = SPI_cursor_open_internal(name, plan, paramLI, read_only);
/* done with the transient ParamListInfo */
if (paramLI)
pfree(paramLI);
return portal;
}
@ -992,7 +1067,10 @@ SPI_cursor_open_with_args(const char *name,
plan.cursor_options = cursorOptions;
plan.nargs = nargs;
plan.argtypes = argtypes;
plan.parserSetup = NULL;
plan.parserSetupArg = NULL;
/* build transient ParamListInfo in executor context */
paramLI = _SPI_convert_params(nargs, argtypes,
Values, Nulls,
PARAM_FLAG_CONST);
@ -1007,8 +1085,7 @@ SPI_cursor_open_with_args(const char *name,
/* SPI_cursor_open_internal must be called in procedure memory context */
_SPI_procmem();
result = SPI_cursor_open_internal(name, &plan, Values, Nulls,
read_only, PARAM_FLAG_CONST);
result = SPI_cursor_open_internal(name, &plan, paramLI, read_only);
/* And clean up */
_SPI_curid++;
@ -1018,25 +1095,36 @@ SPI_cursor_open_with_args(const char *name,
}
/*
* SPI_cursor_open_with_paramlist()
*
* Same as SPI_cursor_open except that parameters (if any) are passed
* as a ParamListInfo, which supports dynamic parameter set determination
*/
Portal
SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan,
ParamListInfo params, bool read_only)
{
return SPI_cursor_open_internal(name, plan, params, read_only);
}
/*
* SPI_cursor_open_internal()
*
* Common code for SPI_cursor_open and SPI_cursor_open_with_args
* Common code for SPI_cursor_open variants
*/
static Portal
SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
Datum *Values, const char *Nulls,
bool read_only, int pflags)
ParamListInfo paramLI, bool read_only)
{
CachedPlanSource *plansource;
CachedPlan *cplan;
List *stmt_list;
char *query_string;
ParamListInfo paramLI;
Snapshot snapshot;
MemoryContext oldcontext;
Portal portal;
int k;
/*
* Check that the plan is something the Portal code will special-case as
@ -1082,54 +1170,15 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
portal = CreatePortal(name, false, false);
}
/*
* Prepare to copy stuff into the portal's memory context. We do all this
* copying first, because it could possibly fail (out-of-memory) and we
* don't want a failure to occur between RevalidateCachedPlan and
* PortalDefineQuery; that would result in leaking our plancache refcount.
*/
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
/* Copy the plan's query string into the portal */
query_string = pstrdup(plansource->query_string);
/* If the plan has parameters, copy them into the portal */
if (plan->nargs > 0)
{
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
(plan->nargs - 1) *sizeof(ParamExternData));
paramLI->numParams = plan->nargs;
for (k = 0; k < plan->nargs; k++)
{
ParamExternData *prm = &paramLI->params[k];
prm->ptype = plan->argtypes[k];
prm->pflags = pflags;
prm->isnull = (Nulls && Nulls[k] == 'n');
if (prm->isnull)
{
/* nulls just copy */
prm->value = Values[k];
}
else
{
/* pass-by-ref values must be copied into portal context */
int16 paramTypLen;
bool paramTypByVal;
get_typlenbyval(prm->ptype, &paramTypLen, &paramTypByVal);
prm->value = datumCopy(Values[k],
paramTypByVal, paramTypLen);
}
}
}
else
paramLI = NULL;
MemoryContextSwitchTo(oldcontext);
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
plansource->query_string);
/*
* Note: we mustn't have any failure occur between RevalidateCachedPlan
* and PortalDefineQuery; that would result in leaking our plancache
* refcount.
*/
if (plan->saved)
{
/* Replan if needed, and increment plan refcount for portal */
@ -1220,6 +1269,19 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
snapshot = GetTransactionSnapshot();
}
/*
* If the plan has parameters, copy them into the portal. Note that
* this must be done after revalidating the plan, because in dynamic
* parameter cases the set of parameters could have changed during
* re-parsing.
*/
if (paramLI)
{
oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
paramLI = copyParamList(paramLI);
MemoryContextSwitchTo(oldcontext);
}
/*
* Start portal execution.
*/
@ -1588,11 +1650,12 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self)
/*
* Parse and plan a querystring.
*
* At entry, plan->argtypes, plan->nargs, and plan->cursor_options must be
* valid. If boundParams isn't NULL then it represents parameter values
* that are made available to the planner (as either estimates or hard values
* depending on their PARAM_FLAG_CONST marking). The boundParams had better
* match the param types embedded in the plan!
* At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup
* and plan->parserSetupArg) must be valid, as must plan->cursor_options.
* If boundParams isn't NULL then it represents parameter values that are made
* available to the planner (as either estimates or hard values depending on
* their PARAM_FLAG_CONST marking). The boundParams had better match the
* param type information embedded in the plan!
*
* Results are stored into *plan (specifically, plan->plancache_list).
* Note however that the result trees are all in CurrentMemoryContext
@ -1605,8 +1668,6 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
List *plancache_list;
ListCell *list_item;
ErrorContextCallback spierrcontext;
Oid *argtypes = plan->argtypes;
int nargs = plan->nargs;
int cursor_options = plan->cursor_options;
/*
@ -1623,8 +1684,8 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
raw_parsetree_list = pg_parse_query(src);
/*
* Do parse analysis and rule rewrite for each raw parsetree, then cons up
* a phony plancache entry for each one.
* Do parse analysis, rule rewrite, and planning for each raw parsetree,
* then cons up a phony plancache entry for each one.
*/
plancache_list = NIL;
@ -1635,9 +1696,27 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
CachedPlanSource *plansource;
CachedPlan *cplan;
/* Need a copyObject here to keep parser from modifying raw tree */
stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
src, argtypes, nargs);
/*
* Parameter datatypes are driven by parserSetup hook if provided,
* otherwise we use the fixed parameter list.
*/
if (plan->parserSetup != NULL)
{
Assert(plan->nargs == 0);
/* Need a copyObject here to keep parser from modifying raw tree */
stmt_list = pg_analyze_and_rewrite_params(copyObject(parsetree),
src,
plan->parserSetup,
plan->parserSetupArg);
}
else
{
/* Need a copyObject here to keep parser from modifying raw tree */
stmt_list = pg_analyze_and_rewrite(copyObject(parsetree),
src,
plan->argtypes,
plan->nargs);
}
stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams);
plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource));
@ -1647,8 +1726,10 @@ _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams)
/* cast-away-const here is a bit ugly, but there's no reason to copy */
plansource->query_string = (char *) src;
plansource->commandTag = CreateCommandTag(parsetree);
plansource->param_types = argtypes;
plansource->num_params = nargs;
plansource->param_types = plan->argtypes;
plansource->num_params = plan->nargs;
plansource->parserSetup = plan->parserSetup;
plansource->parserSetupArg = plan->parserSetupArg;
plansource->fully_planned = true;
plansource->fixed_result = false;
/* no need to set search_path, generation or saved_xmin */
@ -1921,7 +2002,7 @@ fail:
}
/*
* Convert query parameters to form wanted by planner and executor
* Convert arrays of query parameters to form wanted by planner and executor
*/
static ParamListInfo
_SPI_convert_params(int nargs, Oid *argtypes,
@ -1937,6 +2018,11 @@ _SPI_convert_params(int nargs, Oid *argtypes,
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
(nargs - 1) *sizeof(ParamExternData));
/* we have static list of params, so no hooks needed */
paramLI->paramFetch = NULL;
paramLI->paramFetchArg = NULL;
paramLI->parserSetup = NULL;
paramLI->parserSetupArg = NULL;
paramLI->numParams = nargs;
for (i = 0; i < nargs; i++)
@ -2222,6 +2308,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
}
else
newplan->argtypes = NULL;
newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg;
foreach(lc, plan->plancache_list)
{
@ -2241,6 +2329,8 @@ _SPI_copy_plan(SPIPlanPtr plan, MemoryContext parentcxt)
newsource->commandTag = plansource->commandTag;
newsource->param_types = newplan->argtypes;
newsource->num_params = newplan->nargs;
newsource->parserSetup = newplan->parserSetup;
newsource->parserSetupArg = newplan->parserSetupArg;
newsource->fully_planned = plansource->fully_planned;
newsource->fixed_result = plansource->fixed_result;
/* no need to worry about seach_path, generation or saved_xmin */
@ -2298,6 +2388,8 @@ _SPI_save_plan(SPIPlanPtr plan)
}
else
newplan->argtypes = NULL;
newplan->parserSetup = plan->parserSetup;
newplan->parserSetupArg = plan->parserSetupArg;
foreach(lc, plan->plancache_list)
{
@ -2317,6 +2409,10 @@ _SPI_save_plan(SPIPlanPtr plan)
cplan->stmt_list,
true,
false);
if (newplan->parserSetup != NULL)
CachedPlanSetParserHook(newsource,
newplan->parserSetup,
newplan->parserSetupArg);
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
}

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.11 2009/01/01 17:23:43 momjian Exp $
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.12 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,6 +16,7 @@
#include "postgres.h"
#include "nodes/params.h"
#include "parser/parse_param.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
@ -24,6 +25,11 @@
* Copy a ParamListInfo structure.
*
* The result is allocated in CurrentMemoryContext.
*
* Note: the intent of this function is to make a static, self-contained
* set of parameter values. If dynamic parameter hooks are present, we
* intentionally do not copy them into the result. Rather, we forcibly
* instantiate all available parameter values and copy the datum values.
*/
ParamListInfo
copyParamList(ParamListInfo from)
@ -40,54 +46,76 @@ copyParamList(ParamListInfo from)
(from->numParams - 1) *sizeof(ParamExternData);
retval = (ParamListInfo) palloc(size);
memcpy(retval, from, size);
retval->paramFetch = NULL;
retval->paramFetchArg = NULL;
retval->parserSetup = NULL;
retval->parserSetupArg = NULL;
retval->numParams = from->numParams;
/*
* Flat-copy is not good enough for pass-by-ref data values, so make a
* pass over the array to copy those.
*/
for (i = 0; i < retval->numParams; i++)
for (i = 0; i < from->numParams; i++)
{
ParamExternData *prm = &retval->params[i];
ParamExternData *oprm = &from->params[i];
ParamExternData *nprm = &retval->params[i];
int16 typLen;
bool typByVal;
if (prm->isnull || !OidIsValid(prm->ptype))
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(oprm->ptype) && from->paramFetch != NULL)
(*from->paramFetch) (from, i+1);
/* flat-copy the parameter info */
*nprm = *oprm;
/* need datumCopy in case it's a pass-by-reference datatype */
if (nprm->isnull || !OidIsValid(nprm->ptype))
continue;
get_typlenbyval(prm->ptype, &typLen, &typByVal);
prm->value = datumCopy(prm->value, typByVal, typLen);
get_typlenbyval(nprm->ptype, &typLen, &typByVal);
nprm->value = datumCopy(nprm->value, typByVal, typLen);
}
return retval;
}
/*
* Extract an array of parameter type OIDs from a ParamListInfo.
* Set up the parser to treat the given list of run-time parameters
* as available external parameters during parsing of a new query.
*
* The result is allocated in CurrentMemoryContext.
* Note that the parser doesn't actually care about the *values* of the given
* parameters, only about their *types*. Also, the code that originally
* provided the ParamListInfo may have provided a setupHook, which should
* override applying parse_fixed_parameters().
*/
void
getParamListTypes(ParamListInfo params,
Oid **param_types, int *num_params)
setupParserWithParamList(struct ParseState *pstate,
ParamListInfo params)
{
Oid *ptypes;
int i;
if (params == NULL) /* no params, nothing to do */
return;
if (params == NULL || params->numParams <= 0)
/* If there is a parserSetup hook, it gets to do this */
if (params->parserSetup != NULL)
{
*param_types = NULL;
*num_params = 0;
(*params->parserSetup) (pstate, params->parserSetupArg);
return;
}
ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
*param_types = ptypes;
*num_params = params->numParams;
for (i = 0; i < params->numParams; i++)
/* Else, treat any available parameters as being of fixed type */
if (params->numParams > 0)
{
ParamExternData *prm = &params->params[i];
Oid *ptypes;
int i;
ptypes[i] = prm->ptype;
ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
for (i = 0; i < params->numParams; i++)
{
ParamExternData *prm = &params->params[i];
/* give hook a chance in case parameter is dynamic */
if (!OidIsValid(prm->ptype) && params->paramFetch != NULL)
(*params->paramFetch) (params, i+1);
ptypes[i] = prm->ptype;
}
parse_fixed_parameters(pstate, ptypes, params->numParams);
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.574 2009/10/08 22:34:57 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.575 2009/11/04 22:26:06 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -627,6 +627,52 @@ pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
return querytree_list;
}
/*
* Do parse analysis and rewriting. This is the same as pg_analyze_and_rewrite
* except that external-parameter resolution is determined by parser callback
* hooks instead of a fixed list of parameter datatypes.
*/
List *
pg_analyze_and_rewrite_params(Node *parsetree,
const char *query_string,
ParserSetupHook parserSetup,
void *parserSetupArg)
{
ParseState *pstate;
Query *query;
List *querytree_list;
Assert(query_string != NULL); /* required as of 8.4 */
TRACE_POSTGRESQL_QUERY_REWRITE_START(query_string);
/*
* (1) Perform parse analysis.
*/
if (log_parser_stats)
ResetUsage();
pstate = make_parsestate(NULL);
pstate->p_sourcetext = query_string;
(*parserSetup) (pstate, parserSetupArg);
query = transformStmt(pstate, parsetree);
free_parsestate(pstate);
if (log_parser_stats)
ShowUsage("PARSE ANALYSIS STATISTICS");
/*
* (2) Rewrite the queries, as necessary
*/
querytree_list = pg_rewrite_query(query);
TRACE_POSTGRESQL_QUERY_REWRITE_DONE(query_string);
return querytree_list;
}
/*
* Perform rewriting of a query produced by parse analysis.
*
@ -1536,6 +1582,11 @@ exec_bind_message(StringInfo input_message)
/* sizeof(ParamListInfoData) includes the first array element */
params = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
(numParams - 1) *sizeof(ParamExternData));
/* we have static list of params, so no hooks needed */
params->paramFetch = NULL;
params->paramFetchArg = NULL;
params->parserSetup = NULL;
params->parserSetupArg = NULL;
params->numParams = numParams;
for (paramno = 0; paramno < numParams; paramno++)

View File

@ -35,7 +35,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.30 2009/10/26 02:26:41 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.31 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -99,8 +99,8 @@ InitPlanCache(void)
* raw_parse_tree: output of raw_parser()
* query_string: original query text (as of PG 8.4, must not be NULL)
* commandTag: compile-time-constant tag for query, or NULL if empty query
* param_types: array of parameter type OIDs, or NULL if none
* num_params: number of parameters
* param_types: array of fixed parameter type OIDs, or NULL if none
* num_params: number of fixed parameters
* cursor_options: options bitmask that was/will be passed to planner
* stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
* fully_planned: are we caching planner or rewriter output?
@ -156,6 +156,9 @@ CreateCachedPlan(Node *raw_parse_tree,
else
plansource->param_types = NULL;
plansource->num_params = num_params;
/* these can be set later with CachedPlanSetParserHook: */
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
plansource->cursor_options = cursor_options;
plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
@ -240,6 +243,9 @@ FastCreateCachedPlan(Node *raw_parse_tree,
plansource->commandTag = commandTag; /* no copying needed */
plansource->param_types = param_types;
plansource->num_params = num_params;
/* these can be set later with CachedPlanSetParserHook: */
plansource->parserSetup = NULL;
plansource->parserSetupArg = NULL;
plansource->cursor_options = cursor_options;
plansource->fully_planned = fully_planned;
plansource->fixed_result = fixed_result;
@ -274,6 +280,27 @@ FastCreateCachedPlan(Node *raw_parse_tree,
return plansource;
}
/*
* CachedPlanSetParserHook: set up to use parser callback hooks
*
* Use this when a caller wants to manage parameter information via parser
* callbacks rather than a fixed parameter-types list. Beware that the
* information pointed to by parserSetupArg must be valid for as long as
* the cached plan might be replanned!
*/
void
CachedPlanSetParserHook(CachedPlanSource *plansource,
ParserSetupHook parserSetup,
void *parserSetupArg)
{
/* Must not have specified a fixed parameter-types list */
Assert(plansource->param_types == NULL);
Assert(plansource->num_params == 0);
/* OK, save hook info */
plansource->parserSetup = parserSetup;
plansource->parserSetupArg = parserSetupArg;
}
/*
* StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
*
@ -466,6 +493,7 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
if (!plan)
{
bool snapshot_set = false;
Node *rawtree;
List *slist;
TupleDesc resultDesc;
@ -491,14 +519,19 @@ RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
/*
* Run parse analysis and rule rewriting. The parser tends to
* scribble on its input, so we must copy the raw parse tree to
* prevent corruption of the cache. Note that we do not use
* parse_analyze_varparams(), assuming that the caller never wants the
* parameter types to change from the original values.
* prevent corruption of the cache.
*/
slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
plansource->query_string,
plansource->param_types,
plansource->num_params);
rawtree = copyObject(plansource->raw_parse_tree);
if (plansource->parserSetup != NULL)
slist = pg_analyze_and_rewrite_params(rawtree,
plansource->query_string,
plansource->parserSetup,
plansource->parserSetupArg);
else
slist = pg_analyze_and_rewrite(rawtree,
plansource->query_string,
plansource->param_types,
plansource->num_params);
if (plansource->fully_planned)
{

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.72 2009/06/11 14:49:11 momjian Exp $
* $PostgreSQL: pgsql/src/include/executor/spi.h,v 1.73 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -73,6 +73,9 @@ extern void SPI_restore_connection(void);
extern int SPI_execute(const char *src, bool read_only, long tcount);
extern int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls,
bool read_only, long tcount);
extern int SPI_execute_plan_with_paramlist(SPIPlanPtr plan,
ParamListInfo params,
bool read_only, long tcount);
extern int SPI_exec(const char *src, long tcount);
extern int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls,
long tcount);
@ -88,6 +91,10 @@ extern int SPI_execute_with_args(const char *src,
extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes);
extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes,
int cursorOptions);
extern SPIPlanPtr SPI_prepare_params(const char *src,
ParserSetupHook parserSetup,
void *parserSetupArg,
int cursorOptions);
extern SPIPlanPtr SPI_saveplan(SPIPlanPtr plan);
extern int SPI_freeplan(SPIPlanPtr plan);
@ -122,6 +129,8 @@ extern Portal SPI_cursor_open_with_args(const char *name,
int nargs, Oid *argtypes,
Datum *Values, const char *Nulls,
bool read_only, int cursorOptions);
extern Portal SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan,
ParamListInfo params, bool read_only);
extern Portal SPI_cursor_find(const char *name);
extern void SPI_cursor_fetch(Portal portal, bool forward, long count);
extern void SPI_cursor_move(Portal portal, bool forward, long count);

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.32 2009/01/01 17:23:59 momjian Exp $
* $PostgreSQL: pgsql/src/include/executor/spi_priv.h,v 1.33 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -68,6 +68,8 @@ typedef struct _SPI_plan
int cursor_options; /* Cursor options used for planning */
int nargs; /* number of plan arguments */
Oid *argtypes; /* Argument types (NULL if nargs is 0) */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
} _SPI_plan;
#endif /* SPI_PRIV_H */

View File

@ -7,13 +7,16 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.38 2009/01/01 17:24:00 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.39 2009/11/04 22:26:06 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#ifndef PARAMS_H
#define PARAMS_H
/* To avoid including a pile of parser headers, reference ParseState thus: */
struct ParseState;
/* ----------------
* ParamListInfo
@ -26,10 +29,20 @@
* Although parameter numbers are normally consecutive, we allow
* ptype == InvalidOid to signal an unused array entry.
*
* pflags is a flags field. Currently the only used bit is:
* PARAM_FLAG_CONST signals the planner that it may treat this parameter
* as a constant (i.e., generate a plan that works only for this value
* of the parameter).
*
* There are two hook functions that can be associated with a ParamListInfo
* array to support dynamic parameter handling. First, if paramFetch
* isn't null and the executor requires a value for an invalid parameter
* (one with ptype == InvalidOid), the paramFetch hook is called to give
* it a chance to fill in the parameter value. Second, a parserSetup
* hook can be supplied to re-instantiate the original parsing hooks if
* a query needs to be re-parsed/planned (as a substitute for supposing
* that the current ptype values represent a fixed set of parameter types).
* Although the data structure is really an array, not a list, we keep
* the old typedef name to avoid unnecessary code changes.
* ----------------
@ -45,14 +58,22 @@ typedef struct ParamExternData
Oid ptype; /* parameter's datatype, or 0 */
} ParamExternData;
typedef struct ParamListInfoData *ParamListInfo;
typedef void (*ParamFetchHook) (ParamListInfo params, int paramid);
typedef void (*ParserSetupHook) (struct ParseState *pstate, void *arg);
typedef struct ParamListInfoData
{
ParamFetchHook paramFetch; /* parameter fetch hook */
void *paramFetchArg;
ParserSetupHook parserSetup; /* parser setup hook */
void *parserSetupArg;
int numParams; /* number of ParamExternDatas following */
ParamExternData params[1]; /* VARIABLE LENGTH ARRAY */
} ParamListInfoData;
typedef ParamListInfoData *ParamListInfo;
/* ----------------
* ParamExecData
@ -82,7 +103,7 @@ typedef struct ParamExecData
/* Functions found in src/backend/nodes/params.c */
extern ParamListInfo copyParamList(ParamListInfo from);
extern void getParamListTypes(ParamListInfo params,
Oid **param_types, int *num_params);
extern void setupParserWithParamList(struct ParseState *pstate,
ParamListInfo params);
#endif /* PARAMS_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.100 2009/09/01 00:09:42 tgl Exp $
* $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.101 2009/11/04 22:26:07 tgl Exp $
*
* OLD COMMENTS
* This file was created so that other c files could get the two
@ -49,6 +49,10 @@ extern List *pg_parse_and_rewrite(const char *query_string,
extern List *pg_parse_query(const char *query_string);
extern List *pg_analyze_and_rewrite(Node *parsetree, const char *query_string,
Oid *paramTypes, int numParams);
extern List *pg_analyze_and_rewrite_params(Node *parsetree,
const char *query_string,
ParserSetupHook parserSetup,
void *parserSetupArg);
extern PlannedStmt *pg_plan_query(Query *querytree, int cursorOptions,
ParamListInfo boundParams);
extern List *pg_plan_queries(List *querytrees, int cursorOptions,

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.15 2009/01/01 17:24:02 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.16 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,6 +16,7 @@
#define PLANCACHE_H
#include "access/tupdesc.h"
#include "nodes/params.h"
/*
* CachedPlanSource represents the portion of a cached plan that persists
@ -50,6 +51,8 @@ typedef struct CachedPlanSource
const char *commandTag; /* command tag (a constant!), or NULL */
Oid *param_types; /* array of parameter type OIDs, or NULL */
int num_params; /* length of param_types array */
ParserSetupHook parserSetup; /* alternative parameter spec method */
void *parserSetupArg;
int cursor_options; /* cursor options used for planning */
bool fully_planned; /* do we cache planner or rewriter output? */
bool fixed_result; /* disallow change in result tupdesc? */
@ -105,6 +108,9 @@ extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree,
bool fully_planned,
bool fixed_result,
MemoryContext context);
extern void CachedPlanSetParserHook(CachedPlanSource *plansource,
ParserSetupHook parserSetup,
void *parserSetupArg);
extern void DropCachedPlan(CachedPlanSource *plansource);
extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
bool useResOwner);

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.128 2009/09/29 20:05:29 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/gram.y,v 1.129 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -109,7 +109,7 @@ static List *read_raise_options(void);
} loop_body;
List *list;
PLpgSQL_type *dtype;
PLpgSQL_datum *scalar; /* a VAR, RECFIELD, or TRIGARG */
PLpgSQL_datum *scalar; /* a VAR or RECFIELD */
PLpgSQL_variable *variable; /* a VAR, REC, or ROW */
PLpgSQL_var *var;
PLpgSQL_row *row;
@ -236,7 +236,7 @@ static List *read_raise_options(void);
*/
%token T_STRING
%token T_NUMBER
%token T_SCALAR /* a VAR, RECFIELD, or TRIGARG */
%token T_SCALAR /* a VAR or RECFIELD */
%token T_ROW
%token T_RECORD
%token T_DTYPE
@ -1903,44 +1903,6 @@ lno :
%%
#define MAX_EXPR_PARAMS 1024
/*
* determine the expression parameter position to use for a plpgsql datum
*
* It is important that any given plpgsql datum map to just one parameter.
* We used to be sloppy and assign a separate parameter for each occurrence
* of a datum reference, but that fails for situations such as "select DATUM
* from ... group by DATUM".
*
* The params[] array must be of size MAX_EXPR_PARAMS.
*/
static int
assign_expr_param(int dno, int *params, int *nparams)
{
int i;
/* already have an instance of this dno? */
for (i = 0; i < *nparams; i++)
{
if (params[i] == dno)
return i+1;
}
/* check for array overflow */
if (*nparams >= MAX_EXPR_PARAMS)
{
plpgsql_error_lineno = plpgsql_scanner_lineno();
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many variables specified in SQL statement")));
}
/* add new parameter dno to array */
params[*nparams] = dno;
(*nparams)++;
return *nparams;
}
/* Convenience routine to read an expression with one possible terminator */
PLpgSQL_expr *
plpgsql_read_expression(int until, const char *expected)
@ -1993,8 +1955,7 @@ read_sql_construct(int until,
int lno;
StringInfoData ds;
int parenlevel = 0;
int nparams = 0;
int params[MAX_EXPR_PARAMS];
Bitmapset *paramnos = NULL;
char buf[32];
PLpgSQL_expr *expr;
@ -2047,24 +2008,21 @@ read_sql_construct(int until,
switch (tok)
{
case T_SCALAR:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.scalar->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.scalar->dno);
break;
case T_ROW:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.row->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.row->dno);
break;
case T_RECORD:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.rec->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.rec->dno);
break;
default:
@ -2076,13 +2034,11 @@ read_sql_construct(int until,
if (endtoken)
*endtoken = tok;
expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
expr = palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->nparams = nparams;
while(nparams-- > 0)
expr->params[nparams] = params[nparams];
expr->paramnos = paramnos;
pfree(ds.data);
if (valid_sql)
@ -2162,8 +2118,7 @@ static PLpgSQL_stmt *
make_execsql_stmt(const char *sqlstart, int lineno)
{
StringInfoData ds;
int nparams = 0;
int params[MAX_EXPR_PARAMS];
Bitmapset *paramnos = NULL;
char buf[32];
PLpgSQL_stmt_execsql *execsql;
PLpgSQL_expr *expr;
@ -2214,24 +2169,21 @@ make_execsql_stmt(const char *sqlstart, int lineno)
switch (tok)
{
case T_SCALAR:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.scalar->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.scalar->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.scalar->dno);
break;
case T_ROW:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.row->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.row->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.row->dno);
break;
case T_RECORD:
snprintf(buf, sizeof(buf), " $%d ",
assign_expr_param(yylval.rec->dno,
params, &nparams));
snprintf(buf, sizeof(buf), " $%d ", yylval.rec->dno + 1);
appendStringInfoString(&ds, buf);
paramnos = bms_add_member(paramnos, yylval.rec->dno);
break;
default:
@ -2240,13 +2192,11 @@ make_execsql_stmt(const char *sqlstart, int lineno)
}
}
expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
expr = palloc0(sizeof(PLpgSQL_expr));
expr->dtype = PLPGSQL_DTYPE_EXPR;
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->nparams = nparams;
while(nparams-- > 0)
expr->params[nparams] = params[nparams];
expr->paramnos = paramnos;
pfree(ds.data);
check_sql_expr(expr->query);
@ -2600,9 +2550,6 @@ check_assignable(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_ARRAYELEM:
/* always assignable? */
break;
case PLPGSQL_DTYPE_TRIGARG:
yyerror("cannot assign to tg_argv");
break;
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
break;
@ -3095,24 +3042,10 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
{
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
PLpgSQL_expr *expr = cwt->expr;
int nparams = expr->nparams;
PLpgSQL_expr *new_expr;
StringInfoData ds;
/* Must add the CASE variable as an extra param to expression */
if (nparams >= MAX_EXPR_PARAMS)
{
plpgsql_error_lineno = cwt->lineno;
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("too many variables specified in SQL statement")));
}
new_expr = palloc(sizeof(PLpgSQL_expr) + sizeof(int) * (nparams + 1) - sizeof(int));
memcpy(new_expr, expr,
sizeof(PLpgSQL_expr) + sizeof(int) * nparams - sizeof(int));
new_expr->nparams = nparams + 1;
new_expr->params[nparams] = t_varno;
expr->paramnos = bms_add_member(expr->paramnos, t_varno);
/* copy expression query without SELECT keyword (expr->query + 7) */
Assert(strncmp(expr->query, "SELECT ", 7) == 0);
@ -3120,17 +3053,14 @@ make_case(int lineno, PLpgSQL_expr *t_expr,
/* And do the string hacking */
initStringInfo(&ds);
appendStringInfo(&ds, "SELECT $%d IN(%s)",
nparams + 1,
expr->query + 7);
appendStringInfo(&ds, "SELECT $%d IN (%s)",
t_varno + 1,
expr->query + 7);
new_expr->query = pstrdup(ds.data);
pfree(expr->query);
expr->query = pstrdup(ds.data);
pfree(ds.data);
pfree(expr->query);
pfree(expr);
cwt->expr = new_expr;
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.139 2009/09/22 23:43:42 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.140 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -624,20 +624,24 @@ do_compile(FunctionCallInfo fcinfo,
true);
function->tg_table_name_varno = var->dno;
/* add variable tg_table_schema */
/* add the variable tg_table_schema */
var = plpgsql_build_variable("tg_table_schema", 0,
plpgsql_build_datatype(NAMEOID, -1),
true);
function->tg_table_schema_varno = var->dno;
/* Add the variable tg_nargs */
var = plpgsql_build_variable("tg_nargs", 0,
plpgsql_build_datatype(INT4OID, -1),
true);
function->tg_nargs_varno = var->dno;
/* Add the variable tg_argv */
var = plpgsql_build_variable("tg_argv", 0,
plpgsql_build_datatype(TEXTARRAYOID, -1),
true);
function->tg_argv_varno = var->dno;
break;
default:
@ -931,34 +935,6 @@ plpgsql_parse_word(const char *word)
/* Do case conversion and word separation */
plpgsql_convert_ident(word, cp, 1);
/*
* Recognize tg_argv when compiling triggers (XXX this sucks, it should be
* a regular variable in the namestack)
*/
if (plpgsql_curr_compile->fn_is_trigger)
{
if (strcmp(cp[0], "tg_argv") == 0)
{
bool save_spacescanned = plpgsql_SpaceScanned;
PLpgSQL_trigarg *trigarg;
trigarg = palloc0(sizeof(PLpgSQL_trigarg));
trigarg->dtype = PLPGSQL_DTYPE_TRIGARG;
if (plpgsql_yylex() != '[')
plpgsql_yyerror("expected \"[\"");
trigarg->argnum = plpgsql_read_expression(']', "]");
plpgsql_adddatum((PLpgSQL_datum *) trigarg);
plpgsql_yylval.scalar = (PLpgSQL_datum *) trigarg;
plpgsql_SpaceScanned = save_spacescanned;
pfree(cp[0]);
return T_SCALAR;
}
}
/*
* Do a lookup on the compiler's namestack
*/

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.248 2009/08/06 20:44:31 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.249 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -26,6 +26,7 @@
#include "lib/stringinfo.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "parser/parse_node.h"
#include "parser/scansup.h"
#include "storage/proc.h"
#include "tcop/tcopprot.h"
@ -154,10 +155,11 @@ static void exec_assign_value(PLpgSQL_execstate *estate,
Datum value, Oid valtype, bool *isNull);
static void exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull);
static Oid exec_get_datum_type(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum);
static int exec_eval_integer(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
bool *isNull);
@ -172,8 +174,11 @@ static int exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
Portal portal, bool prefetch_ok);
static void eval_expr_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, Datum **p_values, char **p_nulls);
static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr);
static void plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr);
static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
static void plpgsql_param_fetch(ParamListInfo params, int paramid);
static void exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
PLpgSQL_row *row,
@ -514,12 +519,20 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
/*
* Put the OLD and NEW tuples into record variables
*
* We make the tupdescs available in both records even though only one
* may have a value. This allows parsing of record references to succeed
* in functions that are used for multiple trigger types. For example,
* we might have a test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')",
* which should parse regardless of the current trigger type.
*/
rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
rec_new->freetup = false;
rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_new->freetupdesc = false;
rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
rec_old->freetup = false;
rec_old->tupdesc = trigdata->tg_relation->rd_att;
rec_old->freetupdesc = false;
if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
@ -528,30 +541,22 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
* Per-statement triggers don't use OLD/NEW variables
*/
rec_new->tup = NULL;
rec_new->tupdesc = NULL;
rec_old->tup = NULL;
rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_trigtuple;
rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = NULL;
rec_old->tupdesc = NULL;
}
else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
rec_new->tup = trigdata->tg_newtuple;
rec_new->tupdesc = trigdata->tg_relation->rd_att;
rec_old->tup = trigdata->tg_trigtuple;
rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
rec_new->tup = NULL;
rec_new->tupdesc = NULL;
rec_old->tup = trigdata->tg_trigtuple;
rec_old->tupdesc = trigdata->tg_relation->rd_att;
}
else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
@ -631,19 +636,36 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
var->isnull = false;
var->freeval = false;
/*
* Store the trigger argument values into the special execution state
* variables
*/
estate.err_text = gettext_noop("while storing call arguments into local variables");
estate.trig_nargs = trigdata->tg_trigger->tgnargs;
if (estate.trig_nargs == 0)
estate.trig_argv = NULL;
var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
if (trigdata->tg_trigger->tgnargs > 0)
{
/*
* For historical reasons, tg_argv[] subscripts start at zero not one.
* So we can't use construct_array().
*/
int nelems = trigdata->tg_trigger->tgnargs;
Datum *elems;
int dims[1];
int lbs[1];
elems = palloc(sizeof(Datum) * nelems);
for (i = 0; i < nelems; i++)
elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
dims[0] = nelems;
lbs[0] = 0;
var->value = PointerGetDatum(construct_md_array(elems, NULL,
1, dims, lbs,
TEXTOID,
-1, false, 'i'));
var->isnull = false;
var->freeval = true;
}
else
{
estate.trig_argv = palloc(sizeof(Datum) * estate.trig_nargs);
for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
estate.trig_argv[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
var->value = (Datum) 0;
var->isnull = true;
var->freeval = false;
}
estate.err_text = gettext_noop("during function entry");
@ -756,10 +778,6 @@ plpgsql_exec_error_callback(void *arg)
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
/* safety check, shouldn't happen */
if (estate->err_func == NULL)
return;
/* if we are doing RAISE, don't report its location */
if (estate->err_text == raise_skip_msg)
return;
@ -784,7 +802,7 @@ plpgsql_exec_error_callback(void *arg)
* local variable initialization"
*/
errcontext("PL/pgSQL function \"%s\" line %d %s",
estate->err_func->fn_name,
estate->func->fn_name,
estate->err_stmt->lineno,
_(estate->err_text));
}
@ -795,7 +813,7 @@ plpgsql_exec_error_callback(void *arg)
* arguments into local variables"
*/
errcontext("PL/pgSQL function \"%s\" %s",
estate->err_func->fn_name,
estate->func->fn_name,
_(estate->err_text));
}
}
@ -803,13 +821,13 @@ plpgsql_exec_error_callback(void *arg)
{
/* translator: last %s is a plpgsql statement type name */
errcontext("PL/pgSQL function \"%s\" line %d at %s",
estate->err_func->fn_name,
estate->func->fn_name,
estate->err_stmt->lineno,
plpgsql_stmt_typename(estate->err_stmt));
}
else
errcontext("PL/pgSQL function \"%s\"",
estate->err_func->fn_name);
estate->func->fn_name);
}
@ -856,7 +874,6 @@ copy_plpgsql_datum(PLpgSQL_datum *datum)
case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_RECFIELD:
case PLPGSQL_DTYPE_ARRAYELEM:
case PLPGSQL_DTYPE_TRIGARG:
/*
* These datum records are read-only at runtime, so no need to
@ -977,10 +994,13 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
if (rec->freetup)
{
heap_freetuple(rec->tup);
FreeTupleDesc(rec->tupdesc);
rec->freetup = false;
}
if (rec->freetupdesc)
{
FreeTupleDesc(rec->tupdesc);
rec->freetupdesc = false;
}
rec->tup = NULL;
rec->tupdesc = NULL;
}
@ -1885,10 +1905,9 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
PLpgSQL_var *curvar;
char *curname = NULL;
PLpgSQL_expr *query;
ParamListInfo paramLI;
Portal portal;
int rc;
Datum *values;
char *nulls;
/* ----------
* Get the cursor variable and if it has an assigned name, check
@ -1954,19 +1973,25 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
* Now build up the values and nulls arguments for SPI_execute_plan()
* Set up ParamListInfo (note this is only carrying a hook function,
* not any actual data values, at this point)
*/
eval_expr_params(estate, query, &values, &nulls);
paramLI = setup_param_list(estate, query);
/*
* Open the cursor
* Open the cursor (the paramlist will get copied into the portal)
*/
portal = SPI_cursor_open(curname, query->plan, values, nulls,
estate->readonly_func);
portal = SPI_cursor_open_with_paramlist(curname, query->plan,
paramLI,
estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
/* don't need paramlist any more */
if (paramLI)
pfree(paramLI);
/*
* If cursor variable was NULL, store the generated portal name in it
*/
@ -1992,8 +2017,6 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
curvar->isnull = true;
}
pfree(values);
pfree(nulls);
if (curname)
pfree(curname);
@ -2599,6 +2622,11 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
PLpgSQL_function *func,
ReturnSetInfo *rsi)
{
/* this link will be restored at exit from plpgsql_call_handler */
func->cur_estate = estate;
estate->func = func;
estate->retval = (Datum) 0;
estate->retisnull = true;
estate->rettype = InvalidOid;
@ -2616,9 +2644,6 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->tuple_store_cxt = NULL;
estate->rsi = rsi;
estate->trig_nargs = 0;
estate->trig_argv = NULL;
estate->found_varno = func->found_varno;
estate->ndatums = func->ndatums;
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
@ -2627,11 +2652,14 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
estate->eval_econtext = NULL;
estate->cur_expr = NULL;
estate->err_func = func;
estate->err_stmt = NULL;
estate->err_text = NULL;
estate->plugin_info = NULL;
/*
* Create an EState and ExprContext for evaluation of simple expressions.
*/
@ -2682,30 +2710,20 @@ static void
exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions)
{
int i;
SPIPlanPtr plan;
Oid *argtypes;
/*
* We need a temporary argtypes array to load with data. (The finished
* plan structure will contain a copy of it.)
* The grammar can't conveniently set expr->func while building the
* parse tree, so make sure it's set before parser hooks need it.
*/
argtypes = (Oid *) palloc(expr->nparams * sizeof(Oid));
for (i = 0; i < expr->nparams; i++)
{
Datum paramval;
bool paramisnull;
exec_eval_datum(estate, estate->datums[expr->params[i]],
InvalidOid,
&argtypes[i], &paramval, &paramisnull);
}
expr->func = estate->func;
/*
* Generate and save the plan
*/
plan = SPI_prepare_cursor(expr->query, expr->nparams, argtypes,
plan = SPI_prepare_params(expr->query,
(ParserSetupHook) plpgsql_parser_setup,
(void *) expr,
cursorOptions);
if (plan == NULL)
{
@ -2722,17 +2740,13 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
errmsg("cannot begin/end transactions in PL/pgSQL"),
errhint("Use a BEGIN block with an EXCEPTION clause instead.")));
default:
elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
elog(ERROR, "SPI_prepare_params failed for \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
}
}
expr->plan = SPI_saveplan(plan);
SPI_freeplan(plan);
plan = expr->plan;
expr->plan_argtypes = plan->argtypes;
exec_simple_check_plan(expr);
pfree(argtypes);
}
@ -2744,8 +2758,7 @@ static int
exec_stmt_execsql(PLpgSQL_execstate *estate,
PLpgSQL_stmt_execsql *stmt)
{
Datum *values;
char *nulls;
ParamListInfo paramLI;
long tcount;
int rc;
PLpgSQL_expr *expr = stmt->sqlstmt;
@ -2782,9 +2795,10 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
}
/*
* Now build up the values and nulls arguments for SPI_execute_plan()
* Set up ParamListInfo (note this is only carrying a hook function,
* not any actual data values, at this point)
*/
eval_expr_params(estate, expr, &values, &nulls);
paramLI = setup_param_list(estate, expr);
/*
* If we have INTO, then we only need one row back ... but if we have INTO
@ -2810,8 +2824,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
/*
* Execute the plan
*/
rc = SPI_execute_plan(expr->plan, values, nulls,
estate->readonly_func, tcount);
rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
estate->readonly_func, tcount);
/*
* Check for error, and set FOUND if appropriate (for historical reasons
@ -2852,7 +2866,7 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
break;
default:
elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
}
@ -2919,8 +2933,8 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
(rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
}
pfree(values);
pfree(nulls);
if (paramLI)
pfree(paramLI);
return PLPGSQL_RC_OK;
}
@ -3142,8 +3156,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
char *curname = NULL;
PLpgSQL_expr *query;
Portal portal;
Datum *values;
char *nulls;
ParamListInfo paramLI;
bool isnull;
/* ----------
@ -3280,15 +3293,17 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
}
/*
* Now build up the values and nulls arguments for SPI_execute_plan()
* Set up ParamListInfo (note this is only carrying a hook function,
* not any actual data values, at this point)
*/
eval_expr_params(estate, query, &values, &nulls);
paramLI = setup_param_list(estate, query);
/*
* Open the cursor
*/
portal = SPI_cursor_open(curname, query->plan, values, nulls,
estate->readonly_func);
portal = SPI_cursor_open_with_paramlist(curname, query->plan,
paramLI,
estate->readonly_func);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
@ -3299,10 +3314,10 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
if (curname == NULL)
assign_text_var(curvar, portal->name);
pfree(values);
pfree(nulls);
if (curname)
pfree(curname);
if (paramLI)
pfree(paramLI);
return PLPGSQL_RC_OK;
}
@ -3755,7 +3770,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
/* Fetch current value of array datum */
exec_eval_datum(estate, target, InvalidOid,
exec_eval_datum(estate, target,
&arraytypeid, &oldarraydatum, &oldarrayisnull);
arrayelemtypeid = get_element_type(arraytypeid);
@ -3860,8 +3875,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
*
* The type oid, value in Datum format, and null flag are returned.
*
* If expectedtypeid isn't InvalidOid, it is checked against the actual type.
*
* At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
*
* NOTE: caller must not modify the returned value, since it points right
@ -3872,7 +3885,6 @@ exec_assign_value(PLpgSQL_execstate *estate,
static void
exec_eval_datum(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum,
Oid expectedtypeid,
Oid *typeid,
Datum *value,
bool *isnull)
@ -3888,11 +3900,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = var->datatype->typoid;
*value = var->value;
*isnull = var->isnull;
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s\" does not match that when preparing the plan",
var->refname)));
break;
}
@ -3913,11 +3920,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = row->rowtupdesc->tdtypeid;
*value = HeapTupleGetDatum(tup);
*isnull = false;
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s\" does not match that when preparing the plan",
row->refname)));
break;
}
@ -3950,11 +3952,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
*typeid = rec->tupdesc->tdtypeid;
*value = HeapTupleGetDatum(&worktup);
*isnull = false;
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s\" does not match that when preparing the plan",
rec->refname)));
break;
}
@ -3979,36 +3976,6 @@ exec_eval_datum(PLpgSQL_execstate *estate,
rec->refname, recfield->fieldname)));
*typeid = SPI_gettypeid(rec->tupdesc, fno);
*value = SPI_getbinval(rec->tup, rec->tupdesc, fno, isnull);
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of \"%s.%s\" does not match that when preparing the plan",
rec->refname, recfield->fieldname)));
break;
}
case PLPGSQL_DTYPE_TRIGARG:
{
PLpgSQL_trigarg *trigarg = (PLpgSQL_trigarg *) datum;
int tgargno;
*typeid = TEXTOID;
tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
{
*value = (Datum) 0;
*isnull = true;
}
else
{
*value = estate->trig_argv[tgargno];
*isnull = false;
}
if (expectedtypeid != InvalidOid && expectedtypeid != *typeid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of tg_argv[%d] does not match that when preparing the plan",
tgargno)));
break;
}
@ -4017,6 +3984,90 @@ exec_eval_datum(PLpgSQL_execstate *estate,
}
}
/*
* exec_get_datum_type Get datatype of a PLpgSQL_datum
*
* This is the same logic as in exec_eval_datum, except that it can handle
* some cases where exec_eval_datum has to fail; specifically, we may have
* a tupdesc but no row value for a record variable. (This currently can
* happen only for a trigger's NEW/OLD records.)
*/
static Oid
exec_get_datum_type(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum)
{
Oid typeid;
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
{
PLpgSQL_var *var = (PLpgSQL_var *) datum;
typeid = var->datatype->typoid;
break;
}
case PLPGSQL_DTYPE_ROW:
{
PLpgSQL_row *row = (PLpgSQL_row *) datum;
if (!row->rowtupdesc) /* should not happen */
elog(ERROR, "row variable has no tupdesc");
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(row->rowtupdesc);
typeid = row->rowtupdesc->tdtypeid;
break;
}
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
if (rec->tupdesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
/* Make sure we have a valid type/typmod setting */
BlessTupleDesc(rec->tupdesc);
typeid = rec->tupdesc->tdtypeid;
break;
}
case PLPGSQL_DTYPE_RECFIELD:
{
PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
PLpgSQL_rec *rec;
int fno;
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
if (rec->tupdesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("record \"%s\" is not assigned yet",
rec->refname),
errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
if (fno == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
typeid = SPI_gettypeid(rec->tupdesc, fno);
break;
}
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
typeid = InvalidOid; /* keep compiler quiet */
break;
}
return typeid;
}
/* ----------
* exec_eval_integer Evaluate an expression, coerce result to int4
*
@ -4145,8 +4196,7 @@ static int
exec_run_select(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
{
Datum *values;
char *nulls;
ParamListInfo paramLI;
int rc;
/*
@ -4156,30 +4206,32 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr, 0);
/*
* Now build up the values and nulls arguments for SPI_execute_plan()
* Set up ParamListInfo (note this is only carrying a hook function,
* not any actual data values, at this point)
*/
eval_expr_params(estate, expr, &values, &nulls);
paramLI = setup_param_list(estate, expr);
/*
* If a portal was requested, put the query into the portal
*/
if (portalP != NULL)
{
*portalP = SPI_cursor_open(NULL, expr->plan, values, nulls,
estate->readonly_func);
*portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
paramLI,
estate->readonly_func);
if (*portalP == NULL)
elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
expr->query, SPI_result_code_string(SPI_result));
pfree(values);
pfree(nulls);
if (paramLI)
pfree(paramLI);
return SPI_OK_CURSOR;
}
/*
* Execute the query
*/
rc = SPI_execute_plan(expr->plan, values, nulls,
estate->readonly_func, maxtuples);
rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
estate->readonly_func, maxtuples);
if (rc != SPI_OK_SELECT)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@ -4191,8 +4243,8 @@ exec_run_select(PLpgSQL_execstate *estate,
estate->eval_processed = SPI_processed;
estate->eval_lastoid = SPI_lastoid;
pfree(values);
pfree(nulls);
if (paramLI)
pfree(paramLI);
return rc;
}
@ -4378,7 +4430,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
int i;
PLpgSQL_expr *save_cur_expr;
MemoryContext oldcontext;
/*
@ -4425,42 +4477,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
expr->expr_simple_lxid = curlxid;
}
/*
* Param list can live in econtext's temporary memory context.
*
* XXX think about avoiding repeated palloc's for param lists? Beware
* however that this routine is re-entrant: exec_eval_datum() can call it
* back for subscript evaluation, and so there can be a need to have more
* than one active param list.
*/
if (expr->nparams > 0)
{
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo)
MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
sizeof(ParamListInfoData) +
(expr->nparams - 1) *sizeof(ParamExternData));
paramLI->numParams = expr->nparams;
for (i = 0; i < expr->nparams; i++)
{
ParamExternData *prm = &paramLI->params[i];
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
prm->pflags = 0;
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
&prm->ptype,
&prm->value, &prm->isnull);
}
}
else
paramLI = NULL;
/*
* Now we can safely make the econtext point to the param list.
*/
econtext->ecxt_param_list_info = paramLI;
/*
* We have to do some of the things SPI_execute_plan would do, in
* particular advance the snapshot if we are in a non-read-only function.
@ -4476,6 +4492,22 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
PushActiveSnapshot(GetTransactionSnapshot());
}
/*
* Create the param list in econtext's temporary memory context.
* We won't need to free it explicitly, since it will go away at the
* next reset of that context.
*
* XXX think about avoiding repeated palloc's for param lists? It should
* be possible --- this routine isn't re-entrant anymore.
*
* Just for paranoia's sake, save and restore the prior value of
* estate->cur_expr, which setup_param_list() sets.
*/
save_cur_expr = estate->cur_expr;
paramLI = setup_param_list(estate, expr);
econtext->ecxt_param_list_info = paramLI;
/*
* Finally we can call the executor to evaluate the expression
*/
@ -4483,11 +4515,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
econtext,
isNull,
NULL);
MemoryContextSwitchTo(oldcontext);
/* Assorted cleanup */
estate->cur_expr = save_cur_expr;
if (!estate->readonly_func)
PopActiveSnapshot();
MemoryContextSwitchTo(oldcontext);
SPI_pop();
/*
@ -4503,32 +4539,136 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Build up the values and nulls arguments for SPI_execute_plan()
* Create a ParamListInfo to pass to SPI
*
* The ParamListInfo array is initially all zeroes, in particular the
* ptype values are all InvalidOid. This causes the executor to call the
* paramFetch hook each time it wants a value. We thus evaluate only the
* parameters actually demanded.
*
* The result is a locally palloc'd array that should be pfree'd after use;
* but note it can be NULL.
*/
static ParamListInfo
setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
ParamListInfo paramLI;
/*
* Could we re-use these arrays instead of palloc'ing a new one each
* time? However, we'd have to zero the array each time anyway,
* since new values might have been assigned to the variables.
*/
if (estate->ndatums > 0)
{
/* sizeof(ParamListInfoData) includes the first array element */
paramLI = (ParamListInfo)
palloc0(sizeof(ParamListInfoData) +
(estate->ndatums - 1) * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
paramLI->parserSetupArg = (void *) expr;
paramLI->numParams = estate->ndatums;
/*
* Set up link to active expr where the hook functions can find it.
* Callers must save and restore cur_expr if there is any chance
* that they are interrupting an active use of parameters.
*/
estate->cur_expr = expr;
/*
* Also make sure this is set before parser hooks need it. There
* is no need to save and restore, since the value is always correct
* once set.
*/
expr->func = estate->func;
}
else
paramLI = NULL;
return paramLI;
}
/*
* plpgsql_parser_setup set up parser hooks for dynamic parameters
*/
static void
eval_expr_params(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, Datum **p_values, char **p_nulls)
plpgsql_parser_setup(ParseState *pstate, PLpgSQL_expr *expr)
{
Datum *values;
char *nulls;
int i;
pstate->p_ref_hook_state = (void *) expr;
pstate->p_paramref_hook = plpgsql_param_ref;
/* no need to use p_coerce_param_hook */
}
*p_values = values = (Datum *) palloc(expr->nparams * sizeof(Datum));
*p_nulls = nulls = (char *) palloc(expr->nparams * sizeof(char));
/*
* plpgsql_param_ref parser callback for ParamRefs ($n symbols)
*/
static Node *
plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
{
int paramno = pref->number;
PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
PLpgSQL_execstate *estate;
Param *param;
for (i = 0; i < expr->nparams; i++)
{
PLpgSQL_datum *datum = estate->datums[expr->params[i]];
Oid paramtypeid;
bool paramisnull;
/* Let's just check parameter number is in range */
if (!bms_is_member(paramno-1, expr->paramnos))
return NULL;
exec_eval_datum(estate, datum, expr->plan_argtypes[i],
&paramtypeid, &values[i], &paramisnull);
if (paramisnull)
nulls[i] = 'n';
else
nulls[i] = ' ';
}
/*
* We use the function's current estate to resolve parameter data types.
* This is really pretty bogus because there is no provision for updating
* plans when those types change ...
*/
estate = expr->func->cur_estate;
Assert(paramno <= estate->ndatums);
param = makeNode(Param);
param->paramkind = PARAM_EXTERN;
param->paramid = paramno;
param->paramtype = exec_get_datum_type(estate,
estate->datums[paramno-1]);
param->paramtypmod = -1;
param->location = pref->location;
return (Node *) param;
}
/*
* plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
*/
static void
plpgsql_param_fetch(ParamListInfo params, int paramid)
{
int dno;
PLpgSQL_execstate *estate;
PLpgSQL_expr *expr;
PLpgSQL_datum *datum;
ParamExternData *prm;
/* paramid's are 1-based, but dnos are 0-based */
dno = paramid - 1;
Assert(dno >= 0 && dno < params->numParams);
/* fetch back the hook data */
estate = (PLpgSQL_execstate *) params->paramFetchArg;
expr = estate->cur_expr;
Assert(params->numParams == estate->ndatums);
/*
* Do nothing if asked for a value that's not supposed to be used by
* this SQL expression. This avoids unwanted evaluations when functions
* such as copyParamList try to materialize all the values.
*/
if (!bms_is_member(dno, expr->paramnos))
return;
/* OK, evaluate the value and store into the appropriate paramlist slot */
datum = estate->datums[dno];
prm = &params->params[dno];
exec_eval_datum(estate, datum,
&prm->ptype, &prm->value, &prm->isnull);
}
@ -4710,7 +4850,7 @@ make_tuple_from_row(PLpgSQL_execstate *estate,
elog(ERROR, "dropped rowtype entry for non-dropped column");
exec_eval_datum(estate, estate->datums[row->varnos[i]],
InvalidOid, &fieldtypeid, &dvalues[i], &nulls[i]);
&fieldtypeid, &dvalues[i], &nulls[i]);
if (fieldtypeid != tupdesc->attrs[i]->atttypid)
return NULL;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.81 2009/09/29 20:05:29 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_funcs.c,v 1.82 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1148,21 +1148,7 @@ dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
static void
dump_expr(PLpgSQL_expr *expr)
{
int i;
printf("'%s", expr->query);
if (expr->nparams > 0)
{
printf(" {");
for (i = 0; i < expr->nparams; i++)
{
if (i > 0)
printf(", ");
printf("$%d=%d", i + 1, expr->params[i]);
}
printf("}");
}
printf("'");
printf("'%s'", expr->query);
}
void
@ -1240,11 +1226,6 @@ plpgsql_dumptree(PLpgSQL_function *func)
dump_expr(((PLpgSQL_arrayelem *) d)->subscript);
printf("\n");
break;
case PLPGSQL_DTYPE_TRIGARG:
printf("TRIGARG ");
dump_expr(((PLpgSQL_trigarg *) d)->argnum);
printf("\n");
break;
default:
printf("??? unknown data type %d\n", d->dtype);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.46 2009/09/22 23:43:42 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.47 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -68,6 +68,7 @@ Datum
plpgsql_call_handler(PG_FUNCTION_ARGS)
{
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
Datum retval;
int rc;
@ -80,6 +81,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
/* Find or compile the function */
func = plpgsql_compile(fcinfo, false);
/* Must save and restore prior value of cur_estate */
save_cur_estate = func->cur_estate;
/* Mark the function as busy, so it can't be deleted from under us */
func->use_count++;
@ -97,14 +101,17 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
}
PG_CATCH();
{
/* Decrement use-count and propagate error */
/* Decrement use-count, restore cur_estate, and propagate error */
func->use_count--;
func->cur_estate = save_cur_estate;
PG_RE_THROW();
}
PG_END_TRY();
func->use_count--;
func->cur_estate = save_cur_estate;
/*
* Disconnect from SPI manager
*/

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.117 2009/09/29 20:05:29 tgl Exp $
* $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.118 2009/11/04 22:26:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -22,6 +22,7 @@
#include "fmgr.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "nodes/bitmapset.h"
#include "utils/tuplestore.h"
/**********************************************************************
@ -58,8 +59,7 @@ enum
PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_EXPR,
PLPGSQL_DTYPE_TRIGARG
PLPGSQL_DTYPE_EXPR
};
/* ----------
@ -162,8 +162,7 @@ typedef struct
/*
* PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
* PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, PLpgSQL_arrayelem, and
* PLpgSQL_trigarg
* PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem
*/
typedef struct
{ /* Generic datum array item */
@ -189,7 +188,11 @@ typedef struct PLpgSQL_expr
int dno;
char *query;
SPIPlanPtr plan;
Oid *plan_argtypes;
Bitmapset *paramnos; /* all dnos referenced by this query */
/* function containing this expr (not set until we first parse query) */
struct PLpgSQL_function *func;
/* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */
int expr_simple_generation; /* plancache generation we checked */
@ -202,10 +205,6 @@ typedef struct PLpgSQL_expr
*/
ExprState *expr_simple_state;
LocalTransactionId expr_simple_lxid;
/* params to pass to expr */
int nparams;
int params[1]; /* VARIABLE SIZE ARRAY ... must be last */
} PLpgSQL_expr;
@ -284,14 +283,6 @@ typedef struct
} PLpgSQL_arrayelem;
typedef struct
{ /* Positional argument to trigger */
int dtype;
int dno;
PLpgSQL_expr *argnum;
} PLpgSQL_trigarg;
typedef struct
{ /* Item in the compilers namestack */
int itemtype;
@ -670,17 +661,22 @@ typedef struct PLpgSQL_function
int tg_table_name_varno;
int tg_table_schema_varno;
int tg_nargs_varno;
int tg_argv_varno;
int ndatums;
PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action;
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
} PLpgSQL_function;
typedef struct
typedef struct PLpgSQL_execstate
{ /* Runtime execution data */
PLpgSQL_function *func; /* function being executed */
Datum retval;
bool retisnull;
Oid rettype; /* type of current retval */
@ -699,9 +695,6 @@ typedef struct
MemoryContext tuple_store_cxt;
ReturnSetInfo *rsi;
int trig_nargs;
Datum *trig_argv;
int found_varno;
int ndatums;
PLpgSQL_datum **datums;
@ -711,11 +704,12 @@ typedef struct
uint32 eval_processed;
Oid eval_lastoid;
ExprContext *eval_econtext; /* for executing simple expressions */
PLpgSQL_expr *cur_expr; /* current query/expr being evaluated */
/* status information for error context reporting */
PLpgSQL_function *err_func; /* current func */
PLpgSQL_stmt *err_stmt; /* current stmt */
const char *err_text; /* additional state info */
void *plugin_info; /* reserved for use by optional plugin */
} PLpgSQL_execstate;

View File

@ -285,11 +285,9 @@ begin
if new.slotno < 1 or new.slotno > hubrec.nslots then
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' then
if new.hubname != old.hubname then
if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
sname := ''HS.'' || trim(new.hubname);

View File

@ -347,11 +347,9 @@ begin
if new.slotno < 1 or new.slotno > hubrec.nslots then
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' then
if new.hubname != old.hubname then
if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
if tg_op = ''UPDATE'' and new.hubname != old.hubname then
if count(*) > 0 from Hub where name = old.hubname then
raise exception ''no manual manipulation of HSlot'';
end if;
end if;
sname := ''HS.'' || trim(new.hubname);