* pl_exec.c - Executor for the PL/pgSQL
* procedural language
*
- * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, 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.218 2008/08/29 13:02:33 petere Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.242 2009/06/04 18:33:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
#include "parser/scansup.h"
+#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/snapmgr.h"
* creates its own "eval_econtext" ExprContext within this estate for
* per-evaluation workspace. eval_econtext is freed at normal function exit,
* and the EState is freed at transaction end (in case of error, we assume
- * that the abort mechanisms clean it all up). In order to be sure
- * ExprContext callbacks are handled properly, each subtransaction has to have
- * its own such EState; hence we need a stack. We use a simple counter to
- * distinguish different instantiations of the EState, so that we can tell
- * whether we have a current copy of a prepared expression.
+ * that the abort mechanisms clean it all up). Furthermore, any exception
+ * block within a function has to have its own eval_econtext separate from
+ * the containing function's, so that we can clean up ExprContext callbacks
+ * properly at subtransaction exit. We maintain a stack that tracks the
+ * individual econtexts so that we can clean up correctly at subxact exit.
*
* This arrangement is a bit tedious to maintain, but it's worth the trouble
* so that we don't have to re-prepare simple expressions on each trip through
* a function. (We assume the case to optimize is many repetitions of a
* function within a transaction.)
*/
-typedef struct SimpleEstateStackEntry
+typedef struct SimpleEcontextStackEntry
{
- EState *xact_eval_estate; /* EState for current xact level */
- long int xact_estate_simple_id; /* ID for xact_eval_estate */
- SubTransactionId xact_subxid; /* ID for current subxact */
- struct SimpleEstateStackEntry *next; /* next stack entry up */
-} SimpleEstateStackEntry;
+ ExprContext *stack_econtext; /* a stacked econtext */
+ SubTransactionId xact_subxid; /* ID for current subxact */
+ struct SimpleEcontextStackEntry *next; /* next stack entry up */
+} SimpleEcontextStackEntry;
-static SimpleEstateStackEntry *simple_estate_stack = NULL;
-static long int simple_estate_id_counter = 0;
+static EState *simple_eval_estate = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
/************************************************************
* Local function forward declarations
Oid reqtype, int32 reqtypmod,
bool isnull);
static void exec_init_tuple_store(PLpgSQL_execstate *estate);
-static bool compatible_tupdesc(TupleDesc td1, TupleDesc td2);
+static void validate_tupdesc_compat(TupleDesc expected, TupleDesc returned,
+ const char *msg);
static void exec_set_found(PLpgSQL_execstate *estate, bool state);
static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
+static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
static void free_var(PLpgSQL_var *var);
static void assign_text_var(PLpgSQL_var *var, const char *str);
static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
{
case TYPEFUNC_COMPOSITE:
/* got the expected result rowtype, now check it */
- if (estate.rettupdesc == NULL ||
- !compatible_tupdesc(estate.rettupdesc, tupdesc))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("returned record type does not match expected record type")));
+ validate_tupdesc_compat(tupdesc, estate.rettupdesc,
+ "returned record type does not match expected record type");
break;
case TYPEFUNC_RECORD:
((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
- FreeExprContext(estate.eval_econtext);
- estate.eval_econtext = NULL;
+ plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
/*
rettup = NULL;
else
{
- if (!compatible_tupdesc(estate.rettupdesc,
- trigdata->tg_relation->rd_att))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("returned tuple structure does not match table of trigger event")));
+ validate_tupdesc_compat(trigdata->tg_relation->rd_att,
+ estate.rettupdesc,
+ "returned row structure does not match the structure of the triggering table");
/* Copy tuple to upper executor memory */
rettup = SPI_copytuple((HeapTuple) DatumGetPointer(estate.retval));
}
((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
- FreeExprContext(estate.eval_econtext);
- estate.eval_econtext = NULL;
+ plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
/*
*/
if (estate->err_stmt != NULL)
{
- /*
- * translator: last %s is a phrase such as "during statement block
- * local variable initialization"
- */
+ /* translator: last %s is a phrase such as "during statement block local variable initialization" */
errcontext("PL/pgSQL function \"%s\" line %d %s",
estate->err_func->fn_name,
estate->err_stmt->lineno,
- gettext(estate->err_text));
+ _(estate->err_text));
}
else
{
- /*
- * translator: last %s is a phrase such as "while storing call
- * arguments into local variables"
- */
+ /* translator: last %s is a phrase such as "while storing call arguments into local variables" */
errcontext("PL/pgSQL function \"%s\" %s",
estate->err_func->fn_name,
- gettext(estate->err_text));
+ _(estate->err_text));
}
}
else if (estate->err_stmt != NULL)
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
ExprContext *old_eval_econtext = estate->eval_econtext;
- EState *old_eval_estate = estate->eval_estate;
- long int old_eval_estate_simple_id = estate->eval_estate_simple_id;
estate->err_text = gettext_noop("during statement block entry");
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
- /* Revert to outer eval_econtext */
+ /*
+ * Revert to outer eval_econtext. (The inner one was automatically
+ * cleaned up during subxact exit.)
+ */
estate->eval_econtext = old_eval_econtext;
- estate->eval_estate = old_eval_estate;
- estate->eval_estate_simple_id = old_eval_estate_simple_id;
/*
* AtEOSubXact_SPI() should not have popped any SPI context, but
/* Revert to outer eval_econtext */
estate->eval_econtext = old_eval_econtext;
- estate->eval_estate = old_eval_estate;
- estate->eval_estate_simple_id = old_eval_estate_simple_id;
/*
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it
return rc;
case PLPGSQL_RC_EXIT:
+ /*
+ * This is intentionally different from the handling of RC_EXIT
+ * for loops: to match a block, we require a match by label.
+ */
if (estate->exitlabel == NULL)
- return PLPGSQL_RC_OK;
+ return PLPGSQL_RC_EXIT;
if (block->label == NULL)
return PLPGSQL_RC_EXIT;
- if (strcmp(block->label, estate->exitlabel))
+ if (strcmp(block->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
return PLPGSQL_RC_OK;
if (stmt->label == NULL)
return PLPGSQL_RC_EXIT;
- if (strcmp(stmt->label, estate->exitlabel))
+ if (strcmp(stmt->label, estate->exitlabel) != 0)
return PLPGSQL_RC_EXIT;
estate->exitlabel = NULL;
return PLPGSQL_RC_OK;
bool found = false;
int rc = PLPGSQL_RC_OK;
- var = (PLpgSQL_var *) (estate->datums[stmt->var->varno]);
+ var = (PLpgSQL_var *) (estate->datums[stmt->var->dno]);
/*
* Get the value of the lower bound
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("lower bound of FOR loop cannot be NULL")));
+ errmsg("lower bound of FOR loop cannot be null")));
loop_value = DatumGetInt32(value);
exec_eval_cleanup(estate);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("upper bound of FOR loop cannot be NULL")));
+ errmsg("upper bound of FOR loop cannot be null")));
end_value = DatumGetInt32(value);
exec_eval_cleanup(estate);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("BY value of FOR loop cannot be NULL")));
+ errmsg("BY value of FOR loop cannot be null")));
step_value = DatumGetInt32(value);
exec_eval_cleanup(estate);
if (step_value <= 0)
(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.")));
- if (!compatible_tupdesc(tupdesc, rec->tupdesc))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("wrong record type supplied in RETURN NEXT")));
+ errdetail("The tuple structure of a not-yet-assigned"
+ " record is indeterminate.")));
+ validate_tupdesc_compat(tupdesc, rec->tupdesc,
+ "wrong record type supplied in RETURN NEXT");
tuple = rec->tup;
}
break;
PLpgSQL_stmt_return_query *stmt)
{
Portal portal;
+ uint32 processed = 0;
if (!estate->retisset)
ereport(ERROR,
stmt->params);
}
- if (!compatible_tupdesc(estate->rettupdesc, portal->tupDesc))
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("structure of query does not match function result type")));
+ validate_tupdesc_compat(estate->rettupdesc, portal->tupDesc,
+ "structure of query does not match function result type");
while (true)
{
HeapTuple tuple = SPI_tuptable->vals[i];
tuplestore_puttuple(estate->tuple_store, tuple);
+ processed++;
}
MemoryContextSwitchTo(old_cxt);
SPI_freetuptable(SPI_tuptable);
SPI_cursor_close(portal);
+ estate->eval_processed = processed;
+ exec_set_found(estate, processed != 0);
+
return PLPGSQL_RC_OK;
}
estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
- estate->tuple_store = tuplestore_begin_heap(true, false, work_mem);
+ estate->tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
MemoryContextSwitchTo(oldcxt);
estate->rettupdesc = rsi->expectedDesc;
if (optionisnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("RAISE statement option cannot be NULL")));
+ errmsg("RAISE statement option cannot be null")));
extval = convert_value_to_string(optionvalue, optiontypeid);
ereport(stmt->elog_level,
(err_code ? errcode(err_code) : 0,
errmsg_internal("%s", err_message),
- (err_detail != NULL) ? errdetail(err_detail) : 0,
- (err_hint != NULL) ? errhint(err_hint) : 0));
+ (err_detail != NULL) ? errdetail("%s", err_detail) : 0,
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0));
estate->err_text = NULL; /* un-suppress... */
Assert(!stmt->mod_stmt);
break;
+ case SPI_OK_REWRITTEN:
+ Assert(!stmt->mod_stmt);
+ /*
+ * The command was rewritten into another kind of command. It's
+ * not clear what FOUND would mean in that case (and SPI doesn't
+ * return the row count either), so just set it to false.
+ */
+ exec_set_found(estate, false);
+ break;
+
default:
elog(ERROR, "SPI_execute_plan failed executing query \"%s\": %s",
expr->query, SPI_result_code_string(rc));
/* Determine if we assign to a record or a row */
if (stmt->rec != NULL)
- rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+ rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
else if (stmt->row != NULL)
- row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+ row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
else
elog(ERROR, "unsupported target");
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("cannot EXECUTE a null querystring")));
+ errmsg("query string argument of EXECUTE is null")));
/* Get the C-String representation */
querystr = convert_value_to_string(query, restype);
case SPI_OK_UPDATE_RETURNING:
case SPI_OK_DELETE_RETURNING:
case SPI_OK_UTILITY:
+ case SPI_OK_REWRITTEN:
break;
case 0:
if (*ptr == 'S' || *ptr == 's')
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("EXECUTE of SELECT ... INTO is not implemented yet")));
+ errmsg("EXECUTE of SELECT ... INTO is not implemented")));
break;
}
/* Determine if we assign to a record or a row */
if (stmt->rec != NULL)
- rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+ rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
else if (stmt->row != NULL)
- row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+ row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
else
elog(ERROR, "unsupported target");
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("cannot EXECUTE a null querystring")));
+ errmsg("query string argument of EXECUTE is null")));
/* Get the C-String representation */
querystr = convert_value_to_string(queryD, restype);
SPITupleTable *tuptab;
Portal portal;
char *curname;
- int n;
+ uint32 n;
/* ----------
* Get the portal of the cursor by name
if (curvar->isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
+ errmsg("cursor variable \"%s\" is null", curvar->refname)));
curname = TextDatumGetCString(curvar->value);
portal = SPI_cursor_find(curname);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("relative or absolute cursor position is NULL")));
+ errmsg("relative or absolute cursor position is null")));
exec_eval_cleanup(estate);
}
* ----------
*/
if (stmt->rec != NULL)
- rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+ rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
else if (stmt->row != NULL)
- row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+ row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
else
elog(ERROR, "unsupported target");
n = SPI_processed;
/* ----------
- * Set the target and the global FOUND variable appropriately.
+ * Set the target appropriately.
* ----------
*/
if (n == 0)
- {
exec_move_row(estate, rec, row, NULL, tuptab->tupdesc);
- exec_set_found(estate, false);
- }
else
- {
exec_move_row(estate, rec, row, tuptab->vals[0], tuptab->tupdesc);
- exec_set_found(estate, true);
- }
SPI_freetuptable(tuptab);
}
/* Move the cursor */
SPI_scroll_cursor_move(portal, stmt->direction, how_many);
n = SPI_processed;
-
- /* Set the global FOUND variable appropriately. */
- exec_set_found(estate, n != 0);
}
+ /* Set the ROW_COUNT and the global FOUND variable appropriately. */
+ estate->eval_processed = n;
+ exec_set_found(estate, n != 0);
+
return PLPGSQL_RC_OK;
}
if (curvar->isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("cursor variable \"%s\" is NULL", curvar->refname)));
+ errmsg("cursor variable \"%s\" is null", curvar->refname)));
curname = TextDatumGetCString(curvar->value);
portal = SPI_cursor_find(curname);
if (*isNull && var->notnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("NULL cannot be assigned to variable \"%s\" declared NOT NULL",
+ errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
var->refname)));
/*
int fno;
HeapTuple newtup;
int natts;
- int i;
Datum *values;
- char *nulls;
+ bool *nulls;
+ bool *replaces;
void *mustfree;
bool attisnull;
Oid atttype;
/*
* Get the number of the records field to change and the
- * number of attributes in the tuple.
+ * number of attributes in the tuple. Note: disallow
+ * system column names because the code below won't cope.
*/
fno = SPI_fnumber(rec->tupdesc, recfield->fieldname);
- if (fno == SPI_ERROR_NOATTRIBUTE)
+ if (fno <= 0)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
natts = rec->tupdesc->natts;
/*
- * Set up values/datums arrays for heap_formtuple. For all
+ * Set up values/control arrays for heap_modify_tuple. For all
* the attributes except the one we want to replace, use the
* value that's in the old tuple.
*/
values = palloc(sizeof(Datum) * natts);
- nulls = palloc(natts);
+ nulls = palloc(sizeof(bool) * natts);
+ replaces = palloc(sizeof(bool) * natts);
- for (i = 0; i < natts; i++)
- {
- if (i == fno)
- continue;
- values[i] = SPI_getbinval(rec->tup, rec->tupdesc,
- i + 1, &attisnull);
- if (attisnull)
- nulls[i] = 'n';
- else
- nulls[i] = ' ';
- }
+ memset(replaces, false, sizeof(bool) * natts);
+ replaces[fno] = true;
/*
* Now insert the new value, being careful to cast it to the
atttype,
atttypmod,
attisnull);
- if (attisnull)
- nulls[fno] = 'n';
- else
- nulls[fno] = ' ';
+ nulls[fno] = attisnull;
/*
* Avoid leaking the result of exec_simple_cast_value, if it
mustfree = NULL;
/*
- * Now call heap_formtuple() to create a new tuple that
+ * Now call heap_modify_tuple() to create a new tuple that
* replaces the old one in the record.
*/
- newtup = heap_formtuple(rec->tupdesc, values, nulls);
+ newtup = heap_modify_tuple(rec->tup, rec->tupdesc,
+ values, nulls, replaces);
if (rec->freetup)
heap_freetuple(rec->tup);
pfree(values);
pfree(nulls);
+ pfree(replaces);
if (mustfree)
pfree(mustfree);
if (nsubscripts >= MAXDIM)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("number of array dimensions exceeds the maximum allowed, %d",
- MAXDIM)));
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ nsubscripts, MAXDIM)));
subscripts[nsubscripts++] = arrayelem->subscript;
target = estate->datums[arrayelem->arrayparentno];
} while (target->dtype == PLPGSQL_DTYPE_ARRAYELEM);
if (subisnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("array subscript in assignment must not be NULL")));
+ errmsg("array subscript in assignment must not be null")));
}
/* Coerce source value to match array element type. */
if (estate->eval_tuptable->tupdesc->natts != 1)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("query \"%s\" returned %d columns", expr->query,
- estate->eval_tuptable->tupdesc->natts)));
+ 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
* Determine if we assign to a record or a row
*/
if (stmt->rec != NULL)
- rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->recno]);
+ rec = (PLpgSQL_rec *) (estate->datums[stmt->rec->dno]);
else if (stmt->row != NULL)
- row = (PLpgSQL_row *) (estate->datums[stmt->row->rowno]);
+ row = (PLpgSQL_row *) (estate->datums[stmt->row->dno]);
else
elog(ERROR, "unsupported target");
Oid *rettype)
{
ExprContext *econtext = estate->eval_econtext;
+ LocalTransactionId curlxid = MyProc->lxid;
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
/*
* Prepare the expression for execution, if it's not been done already in
- * the current eval_estate. (This will be forced to happen if we called
+ * the current transaction. (This will be forced to happen if we called
* exec_simple_check_plan above.)
*/
- if (expr->expr_simple_id != estate->eval_estate_simple_id)
+ if (expr->expr_simple_lxid != curlxid)
{
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
- estate->eval_estate);
- expr->expr_simple_id = estate->eval_estate_simple_id;
+ simple_eval_estate);
+ expr->expr_simple_lxid = curlxid;
}
/*
if (rec != NULL)
{
/*
- * copy input first, just in case it is pointing at variable's value
+ * Copy input first, just in case it is pointing at variable's value
*/
if (HeapTupleIsValid(tup))
tup = heap_copytuple(tup);
+ else if (tupdesc)
+ {
+ /* If we have a tupdesc but no data, form an all-nulls tuple */
+ bool *nulls;
+
+ nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
+ memset(nulls, true, tupdesc->natts * sizeof(bool));
+
+ tup = heap_form_tuple(tupdesc, NULL, nulls);
+
+ pfree(nulls);
+ }
+
if (tupdesc)
tupdesc = CreateTupleDescCopy(tupdesc);
+ /* Free the old value ... */
if (rec->freetup)
{
heap_freetuple(rec->tup);
rec->freetupdesc = false;
}
+ /* ... and install the new */
if (HeapTupleIsValid(tup))
{
rec->tup = tup;
rec->freetup = true;
}
- else if (tupdesc)
- {
- /* If we have a tupdesc but no data, form an all-nulls tuple */
- char *nulls;
-
- nulls = (char *) palloc(tupdesc->natts * sizeof(char));
- memset(nulls, 'n', tupdesc->natts * sizeof(char));
-
- rec->tup = heap_formtuple(tupdesc, NULL, nulls);
- rec->freetup = true;
-
- pfree(nulls);
- }
else
rec->tup = NULL;
* attributes of the tuple to the variables the row points to.
*
* NOTE: this code used to demand row->nfields ==
- * HeapTupleHeaderGetNatts(tup->t_data, but that's wrong. The tuple might
+ * HeapTupleHeaderGetNatts(tup->t_data), but that's wrong. The tuple might
* have more fields than we expected if it's from an inheritance-child
* table of the current table, or it might have fewer if the table has had
* columns added by ALTER TABLE. Ignore extra columns and assume NULL for
*/
if (row != NULL)
{
+ int td_natts = tupdesc ? tupdesc->natts : 0;
int t_natts;
int fnum;
int anum;
var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
- while (anum < t_natts && tupdesc->attrs[anum]->attisdropped)
+ while (anum < td_natts && tupdesc->attrs[anum]->attisdropped)
anum++; /* skip dropped column in tuple */
- if (anum < t_natts)
+ if (anum < td_natts)
{
- value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+ if (anum < t_natts)
+ value = SPI_getbinval(tup, tupdesc, anum + 1, &isnull);
+ else
+ {
+ value = (Datum) 0;
+ isnull = true;
+ }
valtype = SPI_gettypeid(tupdesc, anum + 1);
anum++;
}
static char *
convert_value_to_string(Datum value, Oid valtype)
{
- char *str;
Oid typoutput;
bool typIsVarlena;
getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
-
- /*
- * We do SPI_push to allow the datatype output function to use SPI.
- * However we do not mess around with CommandCounterIncrement or advancing
- * the snapshot, which means that a stable output function would not see
- * updates made so far by our own function. The use-case for such
- * scenarios seems too narrow to justify the cycles that would be
- * expended.
- */
- SPI_push();
-
- str = OidOutputFunctionCall(typoutput, value);
-
- SPI_pop();
-
- return str;
+ return OidOutputFunctionCall(typoutput, value);
}
/* ----------
char *extval;
extval = convert_value_to_string(value, valtype);
-
- /* Allow input function to use SPI ... see notes above */
- SPI_push();
-
value = InputFunctionCall(reqinput, extval,
reqtypioparam, reqtypmod);
-
- SPI_pop();
-
pfree(extval);
}
else
{
- SPI_push();
-
value = InputFunctionCall(reqinput, NULL,
reqtypioparam, reqtypmod);
-
- SPI_pop();
}
}
Oid reqtype, int32 reqtypmod,
bool isnull)
{
- if (!isnull)
+ if (valtype != reqtype || reqtypmod != -1)
{
- if (valtype != reqtype || reqtypmod != -1)
- {
- Oid typinput;
- Oid typioparam;
- FmgrInfo finfo_input;
+ Oid typinput;
+ Oid typioparam;
+ FmgrInfo finfo_input;
- getTypeInputInfo(reqtype, &typinput, &typioparam);
+ getTypeInputInfo(reqtype, &typinput, &typioparam);
- fmgr_info(typinput, &finfo_input);
+ fmgr_info(typinput, &finfo_input);
- value = exec_cast_value(value,
- valtype,
- reqtype,
- &finfo_input,
- typioparam,
- reqtypmod,
- isnull);
- }
+ value = exec_cast_value(value,
+ valtype,
+ reqtype,
+ &finfo_input,
+ typioparam,
+ reqtypmod,
+ isnull);
}
return value;
*/
expr->expr_simple_expr = tle->expr;
expr->expr_simple_state = NULL;
- expr->expr_simple_id = -1;
+ expr->expr_simple_lxid = InvalidLocalTransactionId;
/* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle->expr);
}
/*
- * Check two tupledescs have matching number and types of attributes
+ * Validates compatibility of supplied TupleDesc pair by checking number and type
+ * of attributes.
*/
-static bool
-compatible_tupdesc(TupleDesc td1, TupleDesc td2)
+static void
+validate_tupdesc_compat(TupleDesc expected, TupleDesc returned, const char *msg)
{
- int i;
+ int i;
+ const char *dropped_column_type = gettext_noop("N/A (dropped column)");
- if (td1->natts != td2->natts)
- return false;
+ if (!expected || !returned)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("%s", _(msg))));
- for (i = 0; i < td1->natts; i++)
- {
- if (td1->attrs[i]->atttypid != td2->attrs[i]->atttypid)
- return false;
- }
+ if (expected->natts != returned->natts)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("%s", _(msg)),
+ errdetail("Number of returned columns (%d) does not match "
+ "expected column count (%d).",
+ returned->natts, expected->natts)));
- return true;
+ for (i = 0; i < expected->natts; i++)
+ if (expected->attrs[i]->atttypid != returned->attrs[i]->atttypid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("%s", _(msg)),
+ errdetail("Returned type %s does not match expected type "
+ "%s in column \"%s\".",
+ OidIsValid(returned->attrs[i]->atttypid) ?
+ format_type_be(returned->attrs[i]->atttypid) :
+ _(dropped_column_type),
+ OidIsValid(expected->attrs[i]->atttypid) ?
+ format_type_be(expected->attrs[i]->atttypid) :
+ _(dropped_column_type),
+ NameStr(expected->attrs[i]->attname))));
}
/* ----------
/*
* plpgsql_create_econtext --- create an eval_econtext for the current function
*
- * We may need to create a new eval_estate too, if there's not one already
- * for the current (sub) transaction. The EState will be cleaned up at
- * (sub) transaction end.
+ * We may need to create a new simple_eval_estate too, if there's not one
+ * already for the current transaction. The EState will be cleaned up at
+ * transaction end.
*/
static void
plpgsql_create_econtext(PLpgSQL_execstate *estate)
{
- SubTransactionId my_subxid = GetCurrentSubTransactionId();
- SimpleEstateStackEntry *entry = simple_estate_stack;
+ SimpleEcontextStackEntry *entry;
- /* Create new EState if not one for current subxact */
- if (entry == NULL ||
- entry->xact_subxid != my_subxid)
+ /*
+ * Create an EState for evaluation of simple expressions, if there's not
+ * one already in the current transaction. The EState is made a child of
+ * TopTransactionContext so it will have the right lifespan.
+ */
+ if (simple_eval_estate == NULL)
{
MemoryContext oldcontext;
- /* Stack entries are kept in TopTransactionContext for simplicity */
- entry = (SimpleEstateStackEntry *)
- MemoryContextAlloc(TopTransactionContext,
- sizeof(SimpleEstateStackEntry));
-
- /* But each EState should be a child of its CurTransactionContext */
- oldcontext = MemoryContextSwitchTo(CurTransactionContext);
- entry->xact_eval_estate = CreateExecutorState();
+ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+ simple_eval_estate = CreateExecutorState();
MemoryContextSwitchTo(oldcontext);
+ }
- /* Assign a reasonably-unique ID to this EState */
- entry->xact_estate_simple_id = simple_estate_id_counter++;
- entry->xact_subxid = my_subxid;
+ /*
+ * Create a child econtext for the current function.
+ */
+ estate->eval_econtext = CreateExprContext(simple_eval_estate);
- entry->next = simple_estate_stack;
- simple_estate_stack = entry;
- }
+ /*
+ * Make a stack entry so we can clean up the econtext at subxact end.
+ * Stack entries are kept in TopTransactionContext for simplicity.
+ */
+ entry = (SimpleEcontextStackEntry *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(SimpleEcontextStackEntry));
+
+ entry->stack_econtext = estate->eval_econtext;
+ entry->xact_subxid = GetCurrentSubTransactionId();
+
+ entry->next = simple_econtext_stack;
+ simple_econtext_stack = entry;
+}
+
+/*
+ * plpgsql_destroy_econtext --- destroy function's econtext
+ *
+ * We check that it matches the top stack entry, and destroy the stack
+ * entry along with the context.
+ */
+static void
+plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
+{
+ SimpleEcontextStackEntry *next;
+
+ Assert(simple_econtext_stack != NULL);
+ Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
- /* Link plpgsql estate to it */
- estate->eval_estate = entry->xact_eval_estate;
- estate->eval_estate_simple_id = entry->xact_estate_simple_id;
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
- /* And create a child econtext for the current function */
- estate->eval_econtext = CreateExprContext(estate->eval_estate);
+ FreeExprContext(estate->eval_econtext);
+ estate->eval_econtext = NULL;
}
/*
* If we are doing a clean transaction shutdown, free the EState (so that
* any remaining resources will be released correctly). In an abort, we
* expect the regular abort recovery procedures to release everything of
- * interest. We don't need to free the individual stack entries since
- * TopTransactionContext is about to go away anyway.
- *
- * Note: if plpgsql_subxact_cb is doing its job, there should be at most
- * one stack entry, but we may as well code this as a loop.
+ * interest.
*/
if (event != XACT_EVENT_ABORT)
{
- while (simple_estate_stack != NULL)
- {
- FreeExecutorState(simple_estate_stack->xact_eval_estate);
- simple_estate_stack = simple_estate_stack->next;
- }
+ /* Shouldn't be any econtext stack entries left at commit */
+ Assert(simple_econtext_stack == NULL);
+
+ if (simple_eval_estate)
+ FreeExecutorState(simple_eval_estate);
+ simple_eval_estate = NULL;
}
else
- simple_estate_stack = NULL;
+ {
+ simple_econtext_stack = NULL;
+ simple_eval_estate = NULL;
+ }
}
/*
* plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
*
- * If a simple-expression EState was created in the current subtransaction,
- * it has to be cleaned up.
+ * Make sure any simple-expression econtexts created in the current
+ * subtransaction get cleaned up. We have to do this explicitly because
+ * no other code knows which child econtexts of simple_eval_estate belong
+ * to which level of subxact.
*/
void
plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
if (event == SUBXACT_EVENT_START_SUB)
return;
- if (simple_estate_stack != NULL &&
- simple_estate_stack->xact_subxid == mySubid)
+ while (simple_econtext_stack != NULL &&
+ simple_econtext_stack->xact_subxid == mySubid)
{
- SimpleEstateStackEntry *next;
+ SimpleEcontextStackEntry *next;
- if (event == SUBXACT_EVENT_COMMIT_SUB)
- FreeExecutorState(simple_estate_stack->xact_eval_estate);
- next = simple_estate_stack->next;
- pfree(simple_estate_stack);
- simple_estate_stack = next;
+ FreeExprContext(simple_econtext_stack->stack_econtext);
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
}
}
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
- errmsg("cannot EXECUTE a null querystring")));
+ errmsg("query string argument of EXECUTE is null")));
/* Get the C-String representation */
querystr = convert_value_to_string(query, restype);