* pl_exec.c - Executor for the PL/pgSQL
* procedural language
*
- * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.251 2009/11/09 00:26:55 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.261 2010/07/06 19:19:01 momjian Exp $
*
*-------------------------------------------------------------------------
*/
static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
Portal portal, bool prefetch_ok);
static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
- PLpgSQL_expr *expr);
+ PLpgSQL_expr *expr);
static void plpgsql_param_fetch(ParamListInfo params, int paramid);
static void exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
List *params);
static void free_params_data(PreparedParamsData *ppd);
static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
- PLpgSQL_expr *query, List *params);
+ PLpgSQL_expr *dynquery, List *params,
+ const char *portalname, int cursorOptions);
/* ----------
/*
* 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')",
+ * 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]);
{
PLpgSQL_var *curvar;
char *curname = NULL;
+ const char *portalname;
PLpgSQL_expr *query;
ParamListInfo paramLI;
Portal portal;
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Set up ParamListInfo (note this is only carrying a hook function,
- * not any actual data values, at this point)
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
paramLI = setup_param_list(estate, query);
if (portal == NULL)
elog(ERROR, "could not open cursor: %s",
SPI_result_code_string(SPI_result));
+ portalname = portal->name;
/* don't need paramlist any more */
if (paramLI)
{
TupleDesc tupdesc;
int natts;
- MemoryContext oldcxt;
HeapTuple tuple = NULL;
bool free_tuple = false;
tupdesc->attrs[0]->atttypmod,
isNull);
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull);
- MemoryContextSwitchTo(oldcxt);
}
break;
tupdesc->attrs[0]->atttypmod,
isNull);
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull);
- MemoryContextSwitchTo(oldcxt);
exec_eval_cleanup(estate);
}
if (HeapTupleIsValid(tuple))
{
- oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
tuplestore_puttuple(estate->tuple_store, tuple);
- MemoryContextSwitchTo(oldcxt);
if (free_tuple)
heap_freetuple(tuple);
/* RETURN QUERY EXECUTE */
Assert(stmt->dynquery != NULL);
portal = exec_dynquery_with_params(estate, stmt->dynquery,
- stmt->params);
+ stmt->params, NULL, 0);
}
tupmap = convert_tuples_by_position(portal->tupDesc,
estate->rettupdesc,
- gettext_noop("structure of query does not match function result type"));
+ gettext_noop("structure of query does not match function result type"));
while (true)
{
- MemoryContext old_cxt;
int i;
SPI_cursor_fetch(portal, true, 50);
if (SPI_processed == 0)
break;
- old_cxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
for (i = 0; i < SPI_processed; i++)
{
HeapTuple tuple = SPI_tuptable->vals[i];
heap_freetuple(tuple);
processed++;
}
- MemoryContextSwitchTo(old_cxt);
SPI_freetuptable(SPI_tuptable);
}
{
ReturnSetInfo *rsi = estate->rsi;
MemoryContext oldcxt;
+ ResourceOwner oldowner;
/*
* Check caller can handle a set result in the way we want
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
- estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
-
+ /*
+ * Switch to the right memory context and resource owner for storing the
+ * tuplestore for return set. If we're within a subtransaction opened for
+ * an exception-block, for example, we must still create the tuplestore in
+ * the resource owner that was active when this function was entered, and
+ * not in the subtransaction resource owner.
+ */
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+ oldowner = CurrentResourceOwner;
+ CurrentResourceOwner = estate->tuple_store_owner;
+
estate->tuple_store =
tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
false, work_mem);
+
+ CurrentResourceOwner = oldowner;
MemoryContextSwitchTo(oldcxt);
estate->rettupdesc = rsi->expectedDesc;
if (stmt->message)
{
- StringInfoData ds;
+ StringInfoData ds;
ListCell *current_param;
char *cp;
estate->exitlabel = NULL;
estate->tuple_store = NULL;
- estate->tuple_store_cxt = NULL;
+ if (rsi)
+ {
+ estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+ estate->tuple_store_owner = CurrentResourceOwner;
+ }
+ else
+ {
+ estate->tuple_store_cxt = NULL;
+ estate->tuple_store_owner = NULL;
+ }
estate->rsi = rsi;
estate->found_varno = func->found_varno;
SPIPlanPtr plan;
/*
- * The grammar can't conveniently set expr->func while building the
- * parse tree, so make sure it's set before parser hooks need 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.
*/
expr->func = estate->func;
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function,
- * not any actual data values, at this point)
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
paramLI = setup_param_list(estate, expr);
if (*ptr == 'S' || *ptr == 's')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("EXECUTE of SELECT ... INTO is not implemented")));
+ errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+ errhint("You might want to use EXECUTE ... INTO instead.")));
break;
}
Portal portal;
int rc;
- portal = exec_dynquery_with_params(estate, stmt->query, stmt->params);
+ portal = exec_dynquery_with_params(estate, stmt->query, stmt->params,
+ NULL, 0);
/*
* Execute the loop
PLpgSQL_expr *query;
Portal portal;
ParamListInfo paramLI;
- bool isnull;
/* ----------
* Get the cursor variable and if it has an assigned name, check
* This is an OPEN refcursor FOR EXECUTE ...
* ----------
*/
- Datum queryD;
- Oid restype;
- char *querystr;
- SPIPlanPtr curplan;
-
- /* ----------
- * We evaluate the string expression after the
- * EXECUTE keyword. It's result is the querystring we have
- * to execute.
- * ----------
- */
- queryD = exec_eval_expr(estate, stmt->dynquery, &isnull, &restype);
- if (isnull)
- ereport(ERROR,
- (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("query string argument of EXECUTE is null")));
-
- /* Get the C-String representation */
- querystr = convert_value_to_string(queryD, restype);
-
- exec_eval_cleanup(estate);
-
- /* ----------
- * Now we prepare a query plan for it and open a cursor
- * ----------
- */
- curplan = SPI_prepare_cursor(querystr, 0, NULL, stmt->cursor_options);
- if (curplan == NULL)
- elog(ERROR, "SPI_prepare_cursor failed for \"%s\": %s",
- querystr, SPI_result_code_string(SPI_result));
- portal = SPI_cursor_open(curname, curplan, NULL, NULL,
- estate->readonly_func);
- if (portal == NULL)
- elog(ERROR, "could not open cursor for query \"%s\": %s",
- querystr, SPI_result_code_string(SPI_result));
- pfree(querystr);
- SPI_freeplan(curplan);
+ portal = exec_dynquery_with_params(estate,
+ stmt->dynquery,
+ stmt->params,
+ curname,
+ stmt->cursor_options);
/*
* If cursor variable was NULL, store the generated portal name in it
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function,
- * not any actual data values, at this point)
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
paramLI = setup_param_list(estate, query);
*/
PLpgSQL_row *row = (PLpgSQL_row *) target;
- /* Source must be of RECORD or composite type */
- if (!type_is_rowtype(valtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("cannot assign non-composite value to a row variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the row */
TupleDesc tupdesc;
HeapTupleData tmptup;
- /* Else source is a tuple Datum, safe to do this: */
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a row variable")));
+ /* Source is a tuple Datum, so safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
*/
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
- /* Source must be of RECORD or composite type */
- if (!type_is_rowtype(valtype))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("cannot assign non-composite value to a record variable")));
if (*isNull)
{
/* If source is null, just assign nulls to the record */
TupleDesc tupdesc;
HeapTupleData tmptup;
- /* Else source is a tuple Datum, safe to do this: */
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a record variable")));
+
+ /* Source is a tuple Datum, so safe to do this: */
td = DatumGetHeapTupleHeader(value);
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype);
- typeid = InvalidOid; /* keep compiler quiet */
+ typeid = InvalidOid; /* keep compiler quiet */
break;
}
errmsg("query \"%s\" did not return data", expr->query)));
/*
- * If there are no rows selected, the result is NULL.
+ * Check that the expression returns exactly one column...
+ */
+ if (estate->eval_tuptable->tupdesc->natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("query \"%s\" returned %d column",
+ "query \"%s\" returned %d columns",
+ estate->eval_tuptable->tupdesc->natts,
+ expr->query,
+ estate->eval_tuptable->tupdesc->natts)));
+
+ /*
+ * ... and get the column's datatype.
+ */
+ *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
+
+ /*
+ * If there are no rows selected, the result is a NULL of that type.
*/
if (estate->eval_processed == 0)
{
}
/*
- * Check that the expression returned one single Datum
+ * Check that the expression returned no more than one row.
*/
- if (estate->eval_processed > 1)
+ if (estate->eval_processed != 1)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("query \"%s\" returned more than one row",
expr->query)));
- if (estate->eval_tuptable->tupdesc->natts != 1)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg_plural("query \"%s\" returned %d column",
- "query \"%s\" returned %d columns",
- estate->eval_tuptable->tupdesc->natts,
- expr->query,
- estate->eval_tuptable->tupdesc->natts)));
/*
- * Return the result and its type
+ * Return the single result Datum.
*/
- *rettype = SPI_gettypeid(estate->eval_tuptable->tupdesc, 1);
return SPI_getbinval(estate->eval_tuptable->vals[0],
estate->eval_tuptable->tupdesc, 1, isNull);
}
exec_prepare_plan(estate, expr, 0);
/*
- * Set up ParamListInfo (note this is only carrying a hook function,
- * not any actual data values, at this point)
+ * Set up ParamListInfo (note this is only carrying a hook function, not
+ * any actual data values, at this point)
*/
paramLI = setup_param_list(estate, expr);
else
elog(ERROR, "unsupported target");
+ /*
+ * Make sure the portal doesn't get closed by the user statements we
+ * execute.
+ */
+ PinPortal(portal);
+
/*
* Fetch the initial tuple(s). If prefetching is allowed then we grab a
* few more rows to avoid multiple trips through executor startup
*/
SPI_freetuptable(tuptab);
+ UnpinPortal(portal);
+
/*
* Set the FOUND variable to indicate the result of executing the loop
* (namely, whether we looped one or more times). This must be set last so
}
/*
- * 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.
+ * 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.
*
* 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
+ * 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;
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.
+ * 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));
+ (estate->ndatums - 1) *sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
/*
* 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.
+ * 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.
+ * 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;
}
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.
+ * 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;
{
value = (Datum) 0;
isnull = true;
+
+ /*
+ * InvalidOid is OK because exec_assign_value doesn't care
+ * about the type of a source NULL
+ */
valtype = InvalidOid;
}
* Open portal for dynamic query
*/
static Portal
-exec_dynquery_with_params(PLpgSQL_execstate *estate, PLpgSQL_expr *dynquery,
- List *params)
+exec_dynquery_with_params(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *dynquery,
+ List *params,
+ const char *portalname,
+ int cursorOptions)
{
Portal portal;
Datum query;
PreparedParamsData *ppd;
ppd = exec_eval_using_params(estate, params);
- portal = SPI_cursor_open_with_args(NULL,
+ portal = SPI_cursor_open_with_args(portalname,
querystr,
ppd->nargs, ppd->types,
ppd->values, ppd->nulls,
- estate->readonly_func, 0);
+ estate->readonly_func,
+ cursorOptions);
free_params_data(ppd);
}
else
{
- portal = SPI_cursor_open_with_args(NULL,
+ portal = SPI_cursor_open_with_args(portalname,
querystr,
0, NULL,
NULL, NULL,
- estate->readonly_func, 0);
+ estate->readonly_func,
+ cursorOptions);
}
if (portal == NULL)