/* *Weusetwosession-widehashtablesforcachingcastinformation. * *cast_expr_hashentries(oftypeplpgsql_CastExprHashEntry)holdcompiled *expressiontreesforcasts.Thesesurviveforthelifeofthesessionand *aresharedacrossallPL/pgSQLfunctionsandDOblocks.Atsomepointit *mightbeworthinvalidatingthemafterpg_castchanges,butforthemoment *wedon'tbother. * *Thereisaseparatehashtableshared_cast_hash(withentriesoftype *plpgsql_CastHashEntry)containingevaluationstatetreesforthese *expressions,whicharemanagedinthesamewayassimpleexpressions *(i.e.,weassumecastexpressionsarealwayssimple). * *Aswithsimpleexpressions,DOblocksdon'tusetheshared_cast_hashtable *butmusthavetheirownevaluationstatetrees.Thisisn'tideal,butwe *don'twanttodealwithmultiplesimple_eval_estateswithinaDOblock.
*/ typedefstruct/* lookup key for cast info */
{ /* NB: we assume this struct contains no padding bytes */
Oid srctype; /* source type for cast */
Oid dsttype; /* destination type for cast */
int32 srctypmod; /* source typmod for cast */
int32 dsttypmod; /* destination typmod for cast */
} plpgsql_CastHashKey;
typedefstruct/* cast_expr_hash table entry */
{
plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
Expr *cast_expr; /* cast expression, or NULL if no-op cast */
CachedExpression *cast_cexpr; /* cached expression backing the above */
} plpgsql_CastExprHashEntry;
typedefstruct/* cast_hash table entry */
{
plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
plpgsql_CastExprHashEntry *cast_centry; /* link to matching expr entry */ /* ExprState is valid only when cast_lxid matches current LXID */
ExprState *cast_exprstate; /* expression's eval tree */ bool cast_in_use; /* true while we're executing eval tree */
LocalTransactionId cast_lxid;
} plpgsql_CastHashEntry;
/* State struct for count_param_references */ typedefstruct count_param_references_context
{ int paramid; int count;
Param *last_param;
} count_param_references_context;
/* *Storetheactualcallargumentvaluesintotheappropriatevariables
*/
estate.err_text = gettext_noop("while storing call arguments into local variables"); for (i = 0; i < func->fn_nargs; i++)
{ int n = func->fn_argvarnos[i];
assign_simple_var(&estate, var,
fcinfo->args[i].value,
fcinfo->args[i].isnull, false);
/* *Ifit'savarlenatype,checktoseeifwereceiveda *R/Wexpanded-objectpointer.Ifso,wecancommandeer *theobjectratherthanhavingtocopyit.Ifpasseda *R/Oexpandedpointer,justkeepitasthevalueofthe *variableforthemoment.(WecanchangeittoR/Wif *thevariablegetsmodified,butthatmayverywell *neverhappen.) * *Also,forceanyflatarrayvaluetobestoredin *expandedforminourlocalvariable,inhopesof *improvingefficiencyofusesofthevariable.(Thisis *ahack,really:whyonlyarrays?Needmorethought *aboutwhichcasesarelikelytowin.Seealso *typisarray-specificheuristicinexec_assign_value.)
*/ if (!var->isnull && var->datatype->typlen == -1)
{ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
{ /* take ownership of R/W object */
assign_simple_var(&estate, var,
TransferExpandedObject(var->value,
estate.datum_context), false, true);
} elseif (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value)))
{ /* R/O pointer, keep it as-is until assigned to */
} elseif (var->datatype->typisarray)
{ /* flat array, so force to expanded form */
assign_simple_var(&estate, var,
expand_array(var->value,
estate.datum_context,
NULL), false, true);
}
}
} break;
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[n];
if (!fcinfo->args[i].isnull)
{ /* Assign row value from composite datum */
exec_move_row_from_datum(&estate,
(PLpgSQL_variable *) rec,
fcinfo->args[i].value);
} else
{ /* If arg is null, set variable to null */
exec_move_row(&estate, (PLpgSQL_variable *) rec,
NULL, NULL);
} /* clean up after exec_move_row() */
exec_eval_cleanup(&estate);
} break;
default: /* Anything else should not be an argument variable */
elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
}
}
estate.err_text = gettext_noop("during function entry");
/* *Lettheinstrumentationpluginpeekatthisfunction
*/ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
((*plpgsql_plugin_ptr)->func_beg) (&estate, func);
/* *Nowcallthetoplevelblockofstatements
*/
estate.err_text = NULL;
rc = exec_toplevel_block(&estate, func->action); if (rc != PLPGSQL_RC_RETURN)
{
estate.err_text = NULL;
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of function without RETURN")));
}
/* *Wegotareturnvalue-processit
*/
estate.err_text = gettext_noop("while casting return value to function's return type");
fcinfo->isnull = estate.retisnull;
if (estate.retisset)
{
ReturnSetInfo *rsi = estate.rsi;
/* Check caller can handle a set result */ if (!rsi || !IsA(rsi, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsi->allowedModes & SFRM_Materialize))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialize mode required, but it is not allowed in this context")));
rsi->returnMode = SFRM_Materialize;
/* If we produced any tuples, send back the result */ if (estate.tuple_store)
{
MemoryContext oldcxt;
rsi->setResult = estate.tuple_store;
oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt);
rsi->setDesc = CreateTupleDescCopy(estate.tuple_store_desc);
MemoryContextSwitchTo(oldcxt);
}
estate.retval = (Datum) 0;
fcinfo->isnull = true;
} elseif (!estate.retisnull)
{ /* *Castresultvaluetofunction'sdeclaredresulttype,andcopyit *outtotheupperexecutormemorycontext.Wemusttreattuple *resultsspeciallyinordertodealwithcaseslikerowtypes *involvingdroppedcolumns.
*/ if (estate.retistuple)
{ /* Don't need coercion if rowtype is known to match */ if (func->fn_rettype == estate.rettype &&
func->fn_rettype != RECORDOID)
{ /* *Copythetupleresultintoupperexecutormemorycontext. *However,ifwehaveaR/Wexpandeddatum,wecanjust *transferitsownershipouttotheuppercontext.
*/
estate.retval = SPI_datumTransfer(estate.retval, false,
-1);
} else
{ /* *Needtolookuptheexpectedresulttype.XXXwouldbe *bettertocachethetupdescinsteadofrepeating *get_call_result_type(),buttheonlyeasyplacetosaveit *isinthePLpgSQL_functionstruct,andthat'stoo *long-lived:compositetypescouldchangeduringthe *existenceofaPLpgSQL_function.
*/
Oid resultTypeId;
TupleDesc tupdesc;
switch (get_call_result_type(fcinfo, &resultTypeId, &tupdesc))
{ case TYPEFUNC_COMPOSITE: /* got the expected result rowtype, now coerce it */
coerce_function_result_tuple(&estate, tupdesc); break; case TYPEFUNC_COMPOSITE_DOMAIN: /* got the expected result rowtype, now coerce it */
coerce_function_result_tuple(&estate, tupdesc); /* and check domain constraints */ /* XXX allowing caching here would be good, too */
domain_check(estate.retval, false, resultTypeId,
NULL, NULL); break; case TYPEFUNC_RECORD:
/* *FailedtodetermineactualtypeofRECORD.We *couldraiseanerrorhere,butwhatthismeansin *practiceisthatthecallerisexpectinganyold *genericrowtype,sowedon'treallyneedtobe *restrictive.Passbackthegeneratedresultas-is.
*/
estate.retval = SPI_datumTransfer(estate.retval, false,
-1); break; default: /* shouldn't get here if retistuple is true ... */
elog(ERROR, "return type must be a row type"); break;
}
}
} else
{ /* Scalar case: use exec_cast_value */
estate.retval = exec_cast_value(&estate,
estate.retval,
&fcinfo->isnull,
estate.rettype,
-1,
func->fn_rettype,
-1);
estate.err_text = gettext_noop("during function exit");
/* *Lettheinstrumentationpluginpeekatthisfunction
*/ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
((*plpgsql_plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate); /* stmt_mcontext will be destroyed when function's main context is */
/* We assume exec_stmt_return verified that result is composite */
Assert(type_is_rowtype(estate->rettype));
/* We can special-case expanded records for speed */ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(estate->retval)))
{
ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(estate->retval);
/* check rowtype compatibility */
tupmap = convert_tuples_by_position(retdesc,
tupdesc,
gettext_noop("returned record type does not match expected record type"));
/* it might need conversion */ if (tupmap)
{
rettup = expanded_record_get_tuple(erh);
Assert(rettup);
rettup = execute_attr_map_tuple(rettup, tupmap);
/* *Copytupletoupperexecutormemory,asatupleDatum.Make *sureitislabeledwiththecaller-suppliedtupletype.
*/
estate->retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc)); /* no need to free map, we're about to return anyway */
} elseif (!(tupdesc->tdtypeid == erh->er_decltypeid ||
(tupdesc->tdtypeid == RECORDOID &&
!ExpandedRecordIsDomain(erh))))
{ /* *Theexpandedrecordhastherightphysicaltupdesc,butthe *wrongtypeID.(Typically,theexpandedrecordisRECORDOID *butthefunctionisdeclaredtoreturnanamedcompositetype. *Asinexec_move_row_from_datum,wedon'tallowreturninga *composite-domainrecordfromafunctiondeclaredtoreturn *RECORD.)Sowemustflattentherecordtoatupledatumand *overwriteitstypefieldswiththerightthing.spi.cdoesn't *provideanyeasywaytodealwiththiscase,soweendup *duplicatingthegutsofdatumCopy():-(
*/
Size resultsize;
HeapTupleHeader tuphdr;
/* check rowtype compatibility */
tupmap = convert_tuples_by_position(retdesc,
tupdesc,
gettext_noop("returned record type does not match expected record type"));
/* it might need conversion */ if (tupmap)
rettup = execute_attr_map_tuple(rettup, tupmap);
/* We assume exec_stmt_return verified that result is composite */
Assert(type_is_rowtype(estate.rettype));
/* We can special-case expanded records for speed */ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(estate.retval)))
{
ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(estate.retval);
if (retdesc != RelationGetDescr(trigdata->tg_relation))
{ /* check rowtype compatibility */
tupmap = convert_tuples_by_position(retdesc,
RelationGetDescr(trigdata->tg_relation),
gettext_noop("returned row structure does not match the structure of the triggering table")); /* it might need conversion */ if (tupmap)
rettup = execute_attr_map_tuple(rettup, tupmap); /* no need to free map, we're about to return anyway */
}
/* *Copytupletoupperexecutormemory.Butifuserjustdid *"returnnew"or"returnold"withoutchanginganything,there's *noneedtocopy;wecanreturntheoriginaltuple(whichwill *saveafewcyclesintrigger.caswellashere).
*/ if (rettup != trigdata->tg_newtuple &&
rettup != trigdata->tg_trigtuple)
rettup = SPI_copytuple(rettup);
} else
{ /* Convert composite datum to a HeapTuple and TupleDesc */
HeapTupleData tmptup;
/* check rowtype compatibility */
tupmap = convert_tuples_by_position(retdesc,
RelationGetDescr(trigdata->tg_relation),
gettext_noop("returned row structure does not match the structure of the triggering table")); /* it might need conversion */ if (tupmap)
rettup = execute_attr_map_tuple(rettup, tupmap);
ReleaseTupleDesc(retdesc); /* no need to free map, we're about to return anyway */
/* *Lettheinstrumentationpluginpeekatthisfunction
*/ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
((*plpgsql_plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate); /* stmt_mcontext will be destroyed when function's main context is */
/* *Lettheinstrumentationpluginpeekatthisfunction
*/ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
((*plpgsql_plugin_ptr)->func_beg) (&estate, func);
/* *Nowcallthetoplevelblockofstatements
*/
estate.err_text = NULL;
rc = exec_toplevel_block(&estate, func->action); if (rc != PLPGSQL_RC_RETURN)
{
estate.err_text = NULL;
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of trigger procedure without RETURN")));
}
estate.err_text = gettext_noop("during function exit");
/* *Lettheinstrumentationpluginpeekatthisfunction
*/ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
((*plpgsql_plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate); /* stmt_mcontext will be destroyed when function's main context is */
/* Fill datum-pointer array, copying datums into workspace as needed */
indatums = func->datums;
outdatums = estate->datums; for (i = 0; i < ndatums; i++)
{
PLpgSQL_datum *indatum = indatums[i];
PLpgSQL_datum *outdatum;
/* This must agree with plpgsql_finish_datums on what is copiable */ switch (indatum->dtype)
{ case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_PROMISE:
outdatum = (PLpgSQL_datum *) ws_next;
memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
ws_next += MAXALIGN(sizeof(PLpgSQL_var)); break;
switch (var->promise)
{ case PLPGSQL_PROMISE_TG_NAME: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function");
assign_simple_var(estate, var,
DirectFunctionCall1(namein,
CStringGetDatum(estate->trigdata->tg_trigger->tgname)), false, true); break;
case PLPGSQL_PROMISE_TG_WHEN: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function"); if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event))
assign_text_var(estate, var, "BEFORE"); elseif (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event))
assign_text_var(estate, var, "AFTER"); elseif (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event))
assign_text_var(estate, var, "INSTEAD OF"); else
elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF"); break;
case PLPGSQL_PROMISE_TG_LEVEL: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function"); if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event))
assign_text_var(estate, var, "ROW"); elseif (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event))
assign_text_var(estate, var, "STATEMENT"); else
elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT"); break;
case PLPGSQL_PROMISE_TG_OP: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function"); if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event))
assign_text_var(estate, var, "INSERT"); elseif (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event))
assign_text_var(estate, var, "UPDATE"); elseif (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event))
assign_text_var(estate, var, "DELETE"); elseif (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event))
assign_text_var(estate, var, "TRUNCATE"); else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE"); break;
case PLPGSQL_PROMISE_TG_RELID: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function");
assign_simple_var(estate, var,
ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id), false, false); break;
case PLPGSQL_PROMISE_TG_TABLE_NAME: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function");
assign_simple_var(estate, var,
DirectFunctionCall1(namein,
CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))), false, true); break;
case PLPGSQL_PROMISE_TG_TABLE_SCHEMA: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function");
assign_simple_var(estate, var,
DirectFunctionCall1(namein,
CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))), false, true); break;
case PLPGSQL_PROMISE_TG_NARGS: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function");
assign_simple_var(estate, var,
Int16GetDatum(estate->trigdata->tg_trigger->tgnargs), false, false); break;
case PLPGSQL_PROMISE_TG_ARGV: if (estate->trigdata == NULL)
elog(ERROR, "trigger promise is not in a trigger function"); if (estate->trigdata->tg_trigger->tgnargs > 0)
{ /* *Forhistoricalreasons,tg_argv[]subscriptsstartatzero *notone.Sowecan'tuseconstruct_array().
*/ int nelems = estate->trigdata->tg_trigger->tgnargs;
Datum *elems; int dims[1]; int lbs[1]; int i;
elems = palloc(sizeof(Datum) * nelems); for (i = 0; i < nelems; i++)
elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]);
dims[0] = nelems;
lbs[0] = 0;
case PLPGSQL_PROMISE_TG_EVENT: if (estate->evtrigdata == NULL)
elog(ERROR, "event trigger promise is not in an event trigger function");
assign_text_var(estate, var, estate->evtrigdata->event); break;
case PLPGSQL_PROMISE_TG_TAG: if (estate->evtrigdata == NULL)
elog(ERROR, "event trigger promise is not in an event trigger function");
assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag)); break;
/* *Pushdownthecurrentstmt_mcontextsothatcalledstatementswon'tuseit. *Thisisneededbystatementsthathavestatement-lifespandataandneedto *preserveitacrosssomeinnerstatements.Thecallershouldeventuallydo *pop_stmt_mcontext().
*/ staticvoid
push_stmt_mcontext(PLpgSQL_execstate *estate)
{ /* Should have done get_stmt_mcontext() first */
Assert(estate->stmt_mcontext != NULL); /* Assert we've not messed up the stack linkage */
Assert(MemoryContextGetParent(estate->stmt_mcontext) == estate->stmt_mcontext_parent); /* Push it down to become the parent of any nested stmt mcontext */
estate->stmt_mcontext_parent = estate->stmt_mcontext; /* And make it not available for use directly */
estate->stmt_mcontext = NULL;
}
/* *Undopush_stmt_mcontext().Weassumethisisdonejustbeforeorafter *resettingthecaller'sstmt_mcontext;sincethatactionwillalsodelete *anychildcontexts,there'snoneedtoexplicitlydeletewhatevercontext *mightcurrentlybeestate->stmt_mcontext.
*/ staticvoid
pop_stmt_mcontext(PLpgSQL_execstate *estate)
{ /* We need only pop the stack */
estate->stmt_mcontext = estate->stmt_mcontext_parent;
estate->stmt_mcontext_parent = MemoryContextGetParent(estate->stmt_mcontext);
}
/* Let the plugin know that we are about to execute this statement */ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
((*plpgsql_plugin_ptr)->stmt_beg) (estate, (PLpgSQL_stmt *) block);
CHECK_FOR_INTERRUPTS();
rc = exec_stmt_block(estate, block);
/* Let the plugin know that we have finished executing this statement */ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
((*plpgsql_plugin_ptr)->stmt_end) (estate, (PLpgSQL_stmt *) block);
/* *Freeanyoldvalue,incasere-enteringblock,and *initializetoNULL
*/
assign_simple_var(estate, var, (Datum) 0, true, false);
if (var->default_val == NULL)
{ /* *Ifneeded,givethedatatypeachancetoreject *NULLs,byassigningaNULLtothevariable.We *claimthevalueisoftypeUNKNOWN,notthevar's *datatype,elsecoercionwillbeskipped.
*/ if (var->datatype->typtype == TYPTYPE_DOMAIN)
exec_assign_value(estate,
(PLpgSQL_datum *) var,
(Datum) 0, true,
UNKNOWNOID,
-1);
/* parser should have rejected NOT NULL */
Assert(!var->notnull);
} else
{
exec_assign_expr(estate, (PLpgSQL_datum *) var,
var->default_val);
}
} break;
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
/* Let the plugin know that we are about to execute this statement */ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
((*plpgsql_plugin_ptr)->stmt_beg) (estate, stmt);
case PLPGSQL_STMT_ASSIGN:
rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt); break;
case PLPGSQL_STMT_PERFORM:
rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt); break;
case PLPGSQL_STMT_CALL:
rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt); break;
case PLPGSQL_STMT_GETDIAG:
rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt); break;
case PLPGSQL_STMT_IF:
rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt); break;
case PLPGSQL_STMT_CASE:
rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt); break;
case PLPGSQL_STMT_LOOP:
rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt); break;
case PLPGSQL_STMT_WHILE:
rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt); break;
case PLPGSQL_STMT_FORI:
rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt); break;
case PLPGSQL_STMT_FORS:
rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt); break;
case PLPGSQL_STMT_FORC:
rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt); break;
case PLPGSQL_STMT_FOREACH_A:
rc = exec_stmt_foreach_a(estate, (PLpgSQL_stmt_foreach_a *) stmt); break;
case PLPGSQL_STMT_EXIT:
rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt); break;
case PLPGSQL_STMT_RETURN:
rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt); break;
case PLPGSQL_STMT_RETURN_NEXT:
rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt); break;
case PLPGSQL_STMT_RETURN_QUERY:
rc = exec_stmt_return_query(estate, (PLpgSQL_stmt_return_query *) stmt); break;
case PLPGSQL_STMT_RAISE:
rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt); break;
case PLPGSQL_STMT_ASSERT:
rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt); break;
case PLPGSQL_STMT_EXECSQL:
rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt); break;
case PLPGSQL_STMT_DYNEXECUTE:
rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt); break;
case PLPGSQL_STMT_DYNFORS:
rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt); break;
case PLPGSQL_STMT_OPEN:
rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt); break;
case PLPGSQL_STMT_FETCH:
rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt); break;
case PLPGSQL_STMT_CLOSE:
rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt); break;
case PLPGSQL_STMT_COMMIT:
rc = exec_stmt_commit(estate, (PLpgSQL_stmt_commit *) stmt); break;
case PLPGSQL_STMT_ROLLBACK:
rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt); break;
default: /* point err_stmt to parent, since this one seems corrupt */
estate->err_stmt = save_estmt;
elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
rc = -1; /* keep compiler quiet */
}
/* Let the plugin know that we have finished executing this statement */ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
((*plpgsql_plugin_ptr)->stmt_end) (estate, stmt);
if (rc != PLPGSQL_RC_OK)
{
estate->err_stmt = save_estmt; return rc;
}
} /* end of loop over statements */
if (!stmt->is_call)
elog(ERROR, "DO statement returned a row");
exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
} elseif (SPI_processed > 1)
elog(ERROR, "procedure call returned more than one row");
/* *WeconstructaDTYPE_ROWdatumrepresentingtheplpgsqlvariables *associatedwiththeprocedure'soutputarguments.Thenwecanuse *exec_move_row()todotheassignments.
*/ static PLpgSQL_variable *
make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
CachedPlan *cplan;
PlannedStmt *pstmt;
CallStmt *stmt;
FuncExpr *funcexpr;
HeapTuple func_tuple;
Oid *argtypes; char **argnames; char *argmodes; int numargs;
MemoryContext oldcontext;
PLpgSQL_row *row; int nfields; int i;
/* Use eval_mcontext for any cruft accumulated here */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
/* *GettheparsedCallStmt,andlookupthecalledprocedure.Weuse *SPI_plan_get_cached_plantocovertheedgecasewhereexpr->planis *alreadystaleandneedstobeupdated.
*/
cplan = SPI_plan_get_cached_plan(expr->plan); if (cplan == NULL || list_length(cplan->stmt_list) != 1)
elog(ERROR, "query for CALL statement is not a CallStmt");
pstmt = linitial_node(PlannedStmt, cplan->stmt_list);
stmt = (CallStmt *) pstmt->utilityStmt; if (stmt == NULL || !IsA(stmt, CallStmt))
elog(ERROR, "query for CALL statement is not a CallStmt");
funcexpr = stmt->funcexpr;
func_tuple = SearchSysCache1(PROCOID,
ObjectIdGetDatum(funcexpr->funcid)); if (!HeapTupleIsValid(func_tuple))
elog(ERROR, "cache lookup failed for function %u",
funcexpr->funcid);
/* *Examineprocedure'sargumentlist.Eachoutputargpositionshouldbe *anunadornedplpgsqlvariable(Datum),whichwecaninsertintotherow *Datum.
*/
nfields = 0; for (i = 0; i < numargs; i++)
{ if (argmodes &&
(argmodes[i] == PROARGMODE_INOUT ||
argmodes[i] == PROARGMODE_OUT))
{
Node *n = list_nth(stmt->outargs, nfields);
if (IsA(n, Param))
{
Param *param = (Param *) n; int dno;
/* paramid is offset by 1 (see make_datum_param()) */
dno = param->paramid - 1; /* must check assignability now, because grammar can't */
exec_check_assignable(estate, dno);
row->varnos[nfields++] = dno;
} else
{ /* report error using parameter name, if available */ if (argnames && argnames[i] && argnames[i][0])
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
argnames[i]))); else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
i + 1)));
}
}
}
/* now we can assign to the variable */
exec_assign_value(estate,
(PLpgSQL_datum *) t_var,
t_val,
isnull,
t_typoid,
t_typmod);
exec_eval_cleanup(estate);
}
/* Now search for a successful WHEN clause */
foreach(l, stmt->case_when_list)
{
PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l); bool value;
value = exec_eval_boolean(estate, cwt->expr, &isnull);
exec_eval_cleanup(estate); if (!isnull && value)
{ /* Found it */
/* We can now discard any value we had for the temp variable */ if (t_var != NULL)
assign_simple_var(estate, t_var, (Datum) 0, true, false);
/* Evaluate the statement(s), and we're done */ return exec_stmts(estate, cwt->stmts);
}
}
/* We can now discard any value we had for the temp variable */ if (t_var != NULL)
assign_simple_var(estate, t_var, (Datum) 0, true, false);
/* SQL2003 mandates this error if there was no ELSE clause */ if (!stmt->have_else)
ereport(ERROR,
(errcode(ERRCODE_CASE_NOT_FOUND),
errmsg("case not found"),
errhint("CASE statement is missing ELSE part.")));
/* Evaluate the ELSE statements, and we're done */ return exec_stmts(estate, stmt->else_stmts);
}
value = exec_eval_boolean(estate, stmt->cond, &isnull);
exec_eval_cleanup(estate);
if (isnull || !value) break;
rc = exec_stmts(estate, stmt->body);
LOOP_RC_PROCESSING(stmt->label, break);
}
return rc;
}
/* ---------- *exec_stmt_foriIterateanintegervariable *fromalowertoanuppervalue *incrementingordecrementingbytheBYvalue *----------
*/ staticint
exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
{
PLpgSQL_var *var;
Datum value; bool isnull;
Oid valtype;
int32 valtypmod;
int32 loop_value;
int32 end_value;
int32 step_value; bool found = false; int rc = PLPGSQL_RC_OK;
var = (PLpgSQL_var *) (estate->datums[stmt->var->dno]);
/* *Getthevalueofthelowerbound
*/
value = exec_eval_expr(estate, stmt->lower,
&isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, &isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("lower bound of FOR loop cannot be null")));
loop_value = DatumGetInt32(value);
exec_eval_cleanup(estate);
/* *Getthevalueoftheupperbound
*/
value = exec_eval_expr(estate, stmt->upper,
&isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, &isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("upper bound of FOR loop cannot be null")));
end_value = DatumGetInt32(value);
exec_eval_cleanup(estate);
/* *Getthestepvalue
*/ if (stmt->step)
{
value = exec_eval_expr(estate, stmt->step,
&isnull, &valtype, &valtypmod);
value = exec_cast_value(estate, value, &isnull,
valtype, valtypmod,
var->datatype->typoid,
var->datatype->atttypmod); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("BY value of FOR loop cannot be null")));
step_value = DatumGetInt32(value);
exec_eval_cleanup(estate); if (step_value <= 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("BY value of FOR loop must be greater than zero")));
} else
step_value = 1;
/* *Nowdotheloop
*/ for (;;)
{ /* *Checkagainstupperbound
*/ if (stmt->reverse)
{ if (loop_value < end_value) break;
} else
{ if (loop_value > end_value) break;
}
found = true; /* looped at least once */
/* *Assigncurrentvaluetoloopvar
*/
assign_simple_var(estate, var, Int32GetDatum(loop_value), false, false);
/* We only need stmt_mcontext to hold the cursor name string */
stmt_mcontext = get_stmt_mcontext(estate);
oldcontext = MemoryContextSwitchTo(stmt_mcontext);
curname = TextDatumGetCString(curvar->value);
MemoryContextSwitchTo(oldcontext);
if (SPI_cursor_find(curname) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_CURSOR),
errmsg("cursor \"%s\" already in use", curname)));
}
if (curname == NULL)
assign_simple_var(estate, curvar, (Datum) 0, true, false);
return rc;
}
/* ---------- *exec_stmt_foreach_aLoopoverelementsorslicesofanarray * *Whenloopingoverelements,theloopvariableisthesametypethatthe *arraystores(eg:integer),whenloopingthroughslices,theloopvariable *isanarrayofsizeanddimensionstomatchthesizeoftheslice. *----------
*/ staticint
exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
{
ArrayType *arr;
Oid arrtype;
int32 arrtypmod;
PLpgSQL_datum *loop_var;
Oid loop_var_elem_type; bool found = false; int rc = PLPGSQL_RC_OK;
MemoryContext stmt_mcontext;
MemoryContext oldcontext;
ArrayIterator array_iterator;
Oid iterator_result_type;
int32 iterator_result_typmod;
Datum value; bool isnull;
/* get the value of the array expression */
value = exec_eval_expr(estate, stmt->expr, &isnull, &arrtype, &arrtypmod); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("FOREACH expression must not be null")));
/* check the type of the expression - must be an array */ if (!OidIsValid(get_element_type(arrtype)))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("FOREACH expression must yield an array, not type %s",
format_type_be(arrtype))));
/* Clean up any leftover temporary memory */
exec_eval_cleanup(estate);
/* Slice dimension must be less than or equal to array dimension */ if (stmt->slice < 0 || stmt->slice > ARR_NDIM(arr))
ereport(ERROR,
(errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
errmsg("slice dimension (%d) is out of the valid range 0..%d",
stmt->slice, ARR_NDIM(arr))));
/* Set up the loop variable and see if it is of an array type */
loop_var = estate->datums[stmt->varno]; if (loop_var->dtype == PLPGSQL_DTYPE_REC ||
loop_var->dtype == PLPGSQL_DTYPE_ROW)
{ /* *Record/rowvariableiscertainlynotofarraytype,andmightnot *beinitializedatallyet,sodon'ttrytogetitstype
*/
loop_var_elem_type = InvalidOid;
} else
loop_var_elem_type = get_element_type(plpgsql_exec_get_datum_type(estate,
loop_var));
/* *Sanity-checktheloopvariabletype.Wedon'ttryveryhardhere,and *shouldnotbetoopickysinceit'spossiblethatexec_assign_valuecan *coercevaluesofdifferenttypes.Butitseemsworthwhiletocomplain *ifthearray-nessoftheloopvariableisnotright.
*/ if (stmt->slice > 0 && loop_var_elem_type == InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("FOREACH ... SLICE loop variable must be of an array type"))); if (stmt->slice == 0 && loop_var_elem_type != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("FOREACH loop variable must not be of an array type")));
/* Create an iterator to step through the array */
array_iterator = array_create_iterator(arr, stmt->slice, NULL);
/* Identify iterator result type */ if (stmt->slice > 0)
{ /* When slicing, nominal type of result is same as array type */
iterator_result_type = arrtype;
iterator_result_typmod = arrtypmod;
} else
{ /* Without slicing, results are individual array elements */
iterator_result_type = ARR_ELEMTYPE(arr);
iterator_result_typmod = arrtypmod;
}
/* Iterate over the array elements or slices */ while (array_iterate(array_iterator, &value, &isnull))
{
found = true; /* looped at least once */
/* exec_assign_value and exec_stmts must run in the main context */
MemoryContextSwitchTo(oldcontext);
/* Assign current element/slice to the loop variable */
exec_assign_value(estate, loop_var, value, isnull,
iterator_result_type, iterator_result_typmod);
/* In slice case, value is temporary; must free it to avoid leakage */ if (stmt->slice > 0)
pfree(DatumGetPointer(value));
switch (retvar->dtype)
{ case PLPGSQL_DTYPE_PROMISE: /* fulfill promise if needed, then handle like regular var */
plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
/* FALL THRU */
case PLPGSQL_DTYPE_VAR:
{
PLpgSQL_var *var = (PLpgSQL_var *) retvar;
switch (retvar->dtype)
{ case PLPGSQL_DTYPE_PROMISE: /* fulfill promise if needed, then handle like regular var */
plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
/* If rec is null, try to convert it to a row of nulls */ if (rec->erh == NULL)
instantiate_empty_record_variable(estate, rec); if (ExpandedRecordIsEmpty(rec->erh))
deconstruct_expanded_record(rec->erh);
/* Use eval_mcontext for tuple conversion work */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
rec_tupdesc = expanded_record_get_tupdesc(rec->erh);
tupmap = convert_tuples_by_position(rec_tupdesc,
tupdesc,
gettext_noop("wrong record type supplied in RETURN NEXT"));
tuple = expanded_record_get_tuple(rec->erh); if (tupmap)
tuple = execute_attr_map_tuple(tuple, tupmap);
tuplestore_puttuple(estate->tuple_store, tuple);
MemoryContextSwitchTo(oldcontext);
} break;
case PLPGSQL_DTYPE_ROW:
{
PLpgSQL_row *row = (PLpgSQL_row *) retvar;
/* We get here if there are multiple OUT parameters */
/* Use eval_mcontext for tuple conversion work */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tuple = make_tuple_from_row(estate, row, tupdesc); if (tuple == NULL) /* should not happen */
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong record type supplied in RETURN NEXT")));
tuplestore_puttuple(estate->tuple_store, tuple);
MemoryContextSwitchTo(oldcontext);
} break;
default:
elog(ERROR, "unrecognized dtype: %d", retvar->dtype); break;
}
} elseif (stmt->expr)
{
Datum retval; bool isNull;
Oid rettype;
int32 rettypmod;
if (estate->retistuple)
{ /* Expression should be of RECORD or composite type */ if (!isNull)
{
HeapTupleData tmptup;
TupleDesc retvaldesc;
TupleConversionMap *tupmap;
if (!type_is_rowtype(rettype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("cannot return non-composite value from function returning composite type")));
/* Use eval_mcontext for tuple conversion work */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
retvaldesc = deconstruct_composite_datum(retval, &tmptup);
tuple = &tmptup;
tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
gettext_noop("returned record type does not match expected record type")); if (tupmap)
tuple = execute_attr_map_tuple(tuple, tupmap);
tuplestore_puttuple(estate->tuple_store, tuple);
ReleaseTupleDesc(retvaldesc);
MemoryContextSwitchTo(oldcontext);
} else
{ /* Composite NULL --- store a row of nulls */
Datum *nulldatums; bool *nullflags;
/* Simple scalar result */ if (natts != 1)
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("wrong result type supplied in RETURN NEXT")));
/* coerce type if needed */
retval = exec_cast_value(estate,
retval,
&isNull,
rettype,
rettypmod,
attr->atttypid,
attr->atttypmod);
tuplestore_putvalues(estate->tuple_store, tupdesc,
&retval, &isNull);
}
} else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("RETURN NEXT must have a parameter")));
}
if (!estate->retisset)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("cannot use RETURN QUERY in a non-SETOF function")));
if (estate->tuple_store == NULL)
exec_init_tuple_store(estate); /* There might be some tuples in the tuplestore already */
tcount = tuplestore_tuple_count(estate->tuple_store);
/* *SetupDestReceivertotransferresultsdirectlytotuplestore, *convertingrowtypeifnecessary.DestReceiverlivesinmcontext.
*/
oldcontext = MemoryContextSwitchTo(stmt_mcontext);
treceiver = CreateDestReceiver(DestTuplestore);
SetTuplestoreDestReceiverParams(treceiver,
estate->tuple_store,
estate->tuple_store_cxt, false,
estate->tuple_store_desc,
gettext_noop("structure of query does not match function result type"));
MemoryContextSwitchTo(oldcontext);
/* *Checkcallercanhandleasetresultinthewaywewant
*/ if (!rsi || !IsA(rsi, ReturnSetInfo))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("set-valued function called in context that cannot accept a set")));
if (!(rsi->allowedModes & SFRM_Materialize) ||
rsi->expectedDesc == NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialize mode required, but it is not allowed in this context")));
/* RAISE with no parameters: re-throw current exception */ if (stmt->condname == NULL && stmt->message == NULL &&
stmt->options == NIL)
{ if (estate->cur_error != NULL)
ReThrowError(estate->cur_error); /* oops, we're not inside a handler */
ereport(ERROR,
(errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
errmsg("RAISE without parameters cannot be used outside an exception handler")));
}
/* We'll need to accumulate the various strings in stmt_mcontext */
stmt_mcontext = get_stmt_mcontext(estate);
/* do nothing when asserts are not enabled */ if (!plpgsql_check_asserts) return PLPGSQL_RC_OK;
value = exec_eval_boolean(estate, stmt->cond, &isnull);
exec_eval_cleanup(estate);
if (isnull || !value)
{ char *message = NULL;
if (stmt->message != NULL)
{
Datum val;
Oid typeid;
int32 typmod;
val = exec_eval_expr(estate, stmt->message,
&isnull, &typeid, &typmod); if (!isnull)
message = convert_value_to_string(estate, val, typeid); /* we mustn't do exec_eval_cleanup here */
}
case SPI_OK_INSERT: case SPI_OK_UPDATE: case SPI_OK_DELETE: case SPI_OK_MERGE: case SPI_OK_INSERT_RETURNING: case SPI_OK_UPDATE_RETURNING: case SPI_OK_DELETE_RETURNING: case SPI_OK_MERGE_RETURNING:
Assert(stmt->mod_stmt);
exec_set_found(estate, (SPI_processed != 0)); break;
case SPI_OK_SELINTO: case SPI_OK_UTILITY:
Assert(!stmt->mod_stmt); break;
/* Some SPI errors deserve specific error messages */ case SPI_ERROR_COPY:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot COPY to/from client in PL/pgSQL"))); break;
case SPI_ERROR_TRANSACTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported transaction command in PL/pgSQL"))); break;
/* All variants should save result info for GET DIAGNOSTICS */
estate->eval_processed = SPI_processed;
/* Process INTO if present */ if (stmt->into)
{
SPITupleTable *tuptab = SPI_tuptable;
uint64 n = SPI_processed;
PLpgSQL_variable *target;
/* If the statement did not return a tuple table, complain */ if (tuptab == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO used with a command that cannot return data")));
ereport(errlevel,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("query returned more than one row"),
errdetail ? errdetail_internal("parameters: %s", errdetail) : 0,
errhint("Make sure the query returns a single row, or use LIMIT 1.")));
} /* Put the first result row into the target */
exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
}
/* Clean up */
exec_eval_cleanup(estate);
SPI_freetuptable(SPI_tuptable);
} else
{ /* If the statement returned a tuple table, complain */ if (SPI_tuptable != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("query has no destination for result data"),
(rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
}
switch (exec_res)
{ case SPI_OK_SELECT: case SPI_OK_INSERT: case SPI_OK_UPDATE: case SPI_OK_DELETE: case SPI_OK_MERGE: case SPI_OK_INSERT_RETURNING: case SPI_OK_UPDATE_RETURNING: case SPI_OK_DELETE_RETURNING: case SPI_OK_MERGE_RETURNING: case SPI_OK_UTILITY: case SPI_OK_REWRITTEN: break;
/* *WewanttodisallowSELECTINTOfornow,becauseitsbehavior *isnotconsistentwithSELECTINTOinanormalplpgsqlcontext. *(WeneedtoreimplementEXECUTEtoparsethestringasa *plpgsqlcommand,notjustfeedittoSPI_execute.)Thisisnot *afunctionallimitationbecauseCREATETABLEASisallowed.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE of SELECT ... INTO is not implemented"),
errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."))); break;
/* Some SPI errors deserve specific error messages */ case SPI_ERROR_COPY:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot COPY to/from client in PL/pgSQL"))); break;
case SPI_ERROR_TRANSACTION:
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("EXECUTE of transaction commands is not implemented"))); break;
/* Save result info for GET DIAGNOSTICS */
estate->eval_processed = SPI_processed;
/* Process INTO if present */ if (stmt->into)
{
SPITupleTable *tuptab = SPI_tuptable;
uint64 n = SPI_processed;
PLpgSQL_variable *target;
/* If the statement did not return a tuple table, complain */ if (tuptab == NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("INTO used with a command that cannot return data")));
/* *IfSELECT...INTOspecifiedSTRICT,andthequerydidn'tfind *exactlyonerow,throwanerror.IfSTRICTwasnotspecified,then *allowthequerytofindanynumberofrows.
*/ if (n == 0)
{ if (stmt->strict)
{ char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_preparedparamsdata(estate, paramLI); else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_NO_DATA_FOUND),
errmsg("query returned no rows"),
errdetail ? errdetail_internal("parameters: %s", errdetail) : 0));
} /* set the target to NULL(s) */
exec_move_row(estate, target, NULL, tuptab->tupdesc);
} else
{ if (n > 1 && stmt->strict)
{ char *errdetail;
if (estate->func->print_strict_params)
errdetail = format_preparedparamsdata(estate, paramLI); else
errdetail = NULL;
ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_ROWS),
errmsg("query returned more than one row"),
errdetail ? errdetail_internal("parameters: %s", errdetail) : 0));
}
/* Put the first result row into the target */
exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
} /* clean up after exec_move_row() */
exec_eval_cleanup(estate);
} else
{ /* *Itmightbeagoodideatoraiseanerrorifthequeryreturned *tuplesthatarebeingignored,buthistoricallywehavenotdone *that.
*/
}
/* Release any result from SPI_execute, as well as transient data */
SPI_freetuptable(SPI_tuptable);
MemoryContextReset(stmt_mcontext);
/* We only need stmt_mcontext to hold the cursor name string */
stmt_mcontext = get_stmt_mcontext(estate);
oldcontext = MemoryContextSwitchTo(stmt_mcontext);
curname = TextDatumGetCString(curvar->value);
MemoryContextSwitchTo(oldcontext);
if (SPI_cursor_find(curname) != NULL)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_CURSOR),
errmsg("cursor \"%s\" already in use", curname)));
}
/* ---------- *Gettheportalofthecursorbyname *----------
*/
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]); if (curvar->isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
/* Use eval_mcontext for short-lived string */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
curname = TextDatumGetCString(curvar->value);
MemoryContextSwitchTo(oldcontext);
portal = SPI_cursor_find(curname); if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
/* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */ if (stmt->expr)
{ bool isnull;
/* XXX should be doing this in LONG not INT width */
how_many = exec_eval_integer(estate, stmt->expr, &isnull);
if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("relative or absolute cursor position is null")));
/* ---------- *Gettheportalofthecursorbyname *----------
*/
curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]); if (curvar->isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("cursor variable \"%s\" is null", curvar->refname)));
/* Use eval_mcontext for short-lived string */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
curname = TextDatumGetCString(curvar->value);
MemoryContextSwitchTo(oldcontext);
portal = SPI_cursor_find(curname); if (portal == NULL)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_CURSOR),
errmsg("cursor \"%s\" does not exist", curname)));
/* Use eval_mcontext for short-lived text value */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); if (str != NULL)
value = cstring_to_text(str); else
value = cstring_to_text("");
MemoryContextSwitchTo(oldcontext);
if (isNull && var->notnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
var->refname)));
/* *Iftypeisby-reference,copythenewvalue(whichis *probablyintheeval_mcontext)intotheprocedure'smain *memorycontext.Butifit'saread/writereferencetoan *expandedobject,nophysicalcopyneedstohappen;atmost *weneedtoreparenttheobject'smemorycontext. * *Ifit'sanarray,weforcethevaluetobestoredinR/W *expandedform.Thiswinsifthefunctionlaterdoes,say, *alotofarraysubscriptingoperationsonthevariable,and *otherwisemightlose.Wemightneedtouseadifferent *heuristic,butit'stoosoontotell.Also,arethere *caseswhereit'dbeusefultoforcenon-arrayvaluesinto *expandedform?
*/ if (!var->datatype->typbyval && !isNull)
{ if (var->datatype->typisarray &&
!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(newvalue)))
{ /* array and not already R/W, so apply expand_array */
newvalue = expand_array(newvalue,
estate->datum_context,
NULL);
} else
{ /* else transfer value if R/W, else just datumCopy */
newvalue = datumTransfer(newvalue, false,
var->datatype->typlen);
}
}
if (isNull)
{ /* If source is null, just assign nulls to the row */
exec_move_row(estate, (PLpgSQL_variable *) row,
NULL, NULL);
} else
{ /* 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")));
exec_move_row_from_datum(estate, (PLpgSQL_variable *) row,
value);
} break;
}
if (isNull)
{ if (rec->notnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
rec->refname)));
/* Set variable to a simple NULL */
exec_move_row(estate, (PLpgSQL_variable *) rec,
NULL, NULL);
} else
{ /* 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")));
exec_move_row_from_datum(estate, (PLpgSQL_variable *) rec,
value);
} break;
}
/* *Lookupthefield'spropertiesifwehavenotalready,or *ifthetupledescriptorIDchangedsincelasttime.
*/ if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id))
{ if (!expanded_record_lookup_field(erh,
recfield->fieldname,
&recfield->finfo))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
recfield->rectupledescid = erh->er_tupdesc_id;
}
/* We don't support assignments to system columns. */ if (recfield->finfo.fnumber <= 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot assign to system column \"%s\"",
recfield->fieldname)));
/* Cast the new value to the right type, if needed. */
value = exec_cast_value(estate,
value,
&isNull,
valtype,
valtypmod,
recfield->finfo.ftypeid,
recfield->finfo.ftypmod);
/* And assign it. */
expanded_record_set_field(erh, recfield->finfo.fnumber,
value, isNull, !estate->atomic); break;
}
switch (datum->dtype)
{ case PLPGSQL_DTYPE_PROMISE: /* fulfill promise if needed, then handle like regular var */
plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum);
/* FALL THRU */
case PLPGSQL_DTYPE_VAR:
{
PLpgSQL_var *var = (PLpgSQL_var *) datum;
/* We get here if there are multiple OUT parameters */ 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);
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
tup = make_tuple_from_row(estate, row, row->rowtupdesc); if (tup == NULL) /* should not happen */
elog(ERROR, "row not compatible with its own tupdesc");
*typeid = row->rowtupdesc->tdtypeid;
*typetypmod = row->rowtupdesc->tdtypmod;
*value = HeapTupleGetDatum(tup);
*isnull = false;
MemoryContextSwitchTo(oldcontext); break;
}
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
if (rec->erh == NULL)
{ /* Treat uninstantiated record as a simple NULL */
*value = (Datum) 0;
*isnull = true; /* Report variable's declared type */
*typeid = rec->rectypeid;
*typetypmod = -1;
} else
{ if (ExpandedRecordIsEmpty(rec->erh))
{ /* Empty record is also a NULL */
*value = (Datum) 0;
*isnull = true;
} else
{
*value = ExpandedRecordGetDatum(rec->erh);
*isnull = false;
} if (rec->rectypeid != RECORDOID)
{ /* Report variable's declared type, if not RECORD */
*typeid = rec->rectypeid;
*typetypmod = -1;
} else
{ /* Report record's actual type if declared RECORD */
*typeid = rec->erh->er_typeid;
*typetypmod = rec->erh->er_typmod;
}
} break;
}
case PLPGSQL_DTYPE_REC:
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
if (rec->erh == NULL || rec->rectypeid != RECORDOID)
{ /* Report variable's declared type */
*typeId = rec->rectypeid;
*typMod = -1;
} else
{ /* Report record's actual type if declared RECORD */
*typeId = rec->erh->er_typeid; /* do NOT return the mutable typmod of a RECORD variable */
*typMod = -1;
} /* composite types are never collatable */
*collation = InvalidOid; break;
}
/* *Checkthattheexpressionreturnednomorethanonerow.
*/ if (estate->eval_processed != 1)
ereport(ERROR,
(errcode(ERRCODE_CARDINALITY_VIOLATION),
errmsg("query returned more than one row"),
errcontext("query: %s", expr->query)));
/* *Ifaportalwasrequested,putthequeryandparamlistintotheportal
*/ if (portalP != NULL)
{
*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));
exec_eval_cleanup(estate); return SPI_OK_CURSOR;
}
/* *Executethequery
*/
rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
estate->readonly_func, maxtuples); if (rc != SPI_OK_SELECT)
{ /* *SELECTINTOdeservesaspecialerrormessage,because"queryisnot *aSELECT"isnotveryhelpfulinthatcase.
*/ if (rc == SPI_OK_SELINTO)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("query is SELECT INTO, but it should be plain SELECT"),
errcontext("query: %s", expr->query))); else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("query is not a SELECT"),
errcontext("query: %s", expr->query)));
}
/* Save query results for eventual cleanup */
Assert(estate->eval_tuptable == NULL);
estate->eval_tuptable = SPI_tuptable;
estate->eval_processed = SPI_processed;
/* *Ifthequerydidn'treturnanyrows,setthetargettoNULLandfall *throughwithfound=false.
*/ if (n == 0)
{
exec_move_row(estate, var, NULL, tuptab->tupdesc);
exec_eval_cleanup(estate);
} else
found = true; /* processed at least one tuple */
/* *Nowdotheloop
*/ while (n > 0)
{
uint64 i;
for (i = 0; i < n; i++)
{ /* *Assignthetupletothetarget.Here,becauseweknowthatall *loopiterationsshouldbeassigningthesametupdesc,wecan *optimizeawayrepeatedcreationsofexpandedrecordswith *identicaltupdescs.Testingforchangesofer_tupdesc_idis *reliableeveniftheloopbodycontainsassignmentsthat *replacethetarget'svalueentirely,becauseit'sassignedfrom *aprocess-globalcounter.Thecasewherethetupdescsdon't *matchcouldpossiblybehandledmoreefficientlythanthis *codingdoes,butit'snotclearextraeffortisworthwhile.
*/ if (var->dtype == PLPGSQL_DTYPE_REC)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) var;
if (rec->erh &&
rec->erh->er_tupdesc_id == previous_id &&
tupdescs_match)
{ /* Only need to assign a new tuple value */
expanded_record_set_tuple(rec->erh, tuptab->vals[i], true, !estate->atomic);
} else
{ /* *Firsttimethrough,orvar'stupdescchangedinloop, *orwehavetodoitthehardwaybecausetypecoercion *isneeded.
*/
exec_move_row(estate, var,
tuptab->vals[i], tuptab->tupdesc);
/* Do the replanning work in the eval_mcontext */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);
MemoryContextSwitchTo(oldcontext);
/* *WeonlyneedaParamListInfoiftheexpressionhasparameters.
*/ if (!bms_is_empty(expr->paramnos))
{ /* Use the common ParamListInfo */
paramLI = estate->paramLI;
/* Return "no such parameter" if not ok */ if (!ok)
{
prm->value = (Datum) 0;
prm->isnull = true;
prm->pflags = 0;
prm->ptype = InvalidOid; return prm;
}
/* OK, evaluate the value and store into the return struct */
exec_eval_datum(estate, datum,
&prm->ptype, &prmtypmod,
&prm->value, &prm->isnull); /* We can always mark params as "const" for executor's purposes */
prm->pflags = PARAM_FLAG_CONST;
/* *Wemighthavealreadyfiguredthisoutwhileevaluatingsomeother *Paramreferencingthesamevariable,socheckexpr_rwoptfirst.
*/ if (expr->expr_rwopt == PLPGSQL_RWOPT_UNKNOWN)
exec_check_rw_parameter(expr, op->d.cparam.paramid);
/* *Updatethecallbackpointertomatchwhatwedecidedtodo,sothat *thisfunctionwillnotbecalledagain.Thenpassoffthis *executiontothenewly-selectedfunction.
*/ switch (expr->expr_rwopt)
{ case PLPGSQL_RWOPT_UNKNOWN:
Assert(false); break; case PLPGSQL_RWOPT_NOPE: /* Force the value to read-only in all future executions */
op->d.cparam.paramfunc = plpgsql_param_eval_var_ro;
plpgsql_param_eval_var_ro(state, op, econtext); break; case PLPGSQL_RWOPT_TRANSFER: /* There can be only one matching Param in this case */
Assert(param == expr->expr_rw_param); /* When the value is read/write, transfer to exec context */
op->d.cparam.paramfunc = plpgsql_param_eval_var_transfer;
plpgsql_param_eval_var_transfer(state, op, econtext); break; case PLPGSQL_RWOPT_INPLACE: if (param == expr->expr_rw_param)
{ /* When the value is read/write, deliver it as-is */
op->d.cparam.paramfunc = plpgsql_param_eval_var;
plpgsql_param_eval_var(state, op, econtext);
} else
{ /* Not the optimizable reference, so force to read-only */
op->d.cparam.paramfunc = plpgsql_param_eval_var_ro;
plpgsql_param_eval_var_ro(state, op, econtext);
} break;
} return;
}
/* *Lookupthefield'spropertiesifwehavenotalready,orifthetuple *descriptorIDchangedsincelasttime.
*/ if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id))
{ if (!expanded_record_lookup_field(erh,
recfield->fieldname,
&recfield->finfo))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("record \"%s\" has no field \"%s\"",
rec->refname, recfield->fieldname)));
recfield->rectupledescid = erh->er_tupdesc_id;
}
/* OK to fetch the field value. */
*op->resvalue = expanded_record_get_field(erh,
recfield->finfo.fnumber,
op->resnull);
/* safety check -- needed for, eg, record fields */ if (unlikely(recfield->finfo.ftypeid != op->d.cparam.paramtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
op->d.cparam.paramid,
format_type_be(recfield->finfo.ftypeid),
format_type_be(op->d.cparam.paramtype))));
}
/* safety check -- needed for, eg, record fields */ if (unlikely(datumtype != op->d.cparam.paramtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
op->d.cparam.paramid,
format_type_be(datumtype),
format_type_be(op->d.cparam.paramtype))));
}
/* safety check -- needed for, eg, record fields */ if (unlikely(datumtype != op->d.cparam.paramtype))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
op->d.cparam.paramid,
format_type_be(datumtype),
format_type_be(op->d.cparam.paramtype))));
/* force the value to read-only */
*op->resvalue = MakeExpandedObjectReadOnly(*op->resvalue,
*op->resnull,
-1);
}
/* *Iftherowtypesmatch,orifwehavenotupleanyway,wecan *completetheassignmentwithoutfield-by-fieldprocessing. * *Thetestshereareorderedmoreorlessinorderofcheapness.We *caneasilydetectitwillworkifthetargetisdeclaredRECORDor *hasthesametypeidasthesource.Butwhenassigningfromaquery *result,it'scommontohaveasourcetupdescthat'slabeledRECORD *butisactuallyphysicallycompatiblewithanamed-composite-type *target,soit'sworthspendingextracyclestocheckforthat.
*/ if (rec->rectypeid == RECORDOID ||
rec->rectypeid == tupdesc->tdtypeid ||
!HeapTupleIsValid(tup) ||
compatible_tupdescs(tupdesc, expanded_record_get_tupdesc(newerh)))
{ if (!HeapTupleIsValid(tup))
{ /* No data, so force the record into all-nulls state */
deconstruct_expanded_record(newerh);
} else
{ /* No coercion is needed, so just assign the row value */
expanded_record_set_tuple(newerh, tup, true, !estate->atomic);
}
/* Complete the assignment */
assign_record_var(estate, rec, newerh);
return;
}
}
/* *Otherwise,deconstructthetupleanddofield-by-fieldassignment, *usingexec_move_row_from_fields.
*/ if (tupdesc && HeapTupleIsValid(tup))
{ int td_natts = tupdesc->natts;
Datum *values; bool *nulls;
Datum values_local[64]; bool nulls_local[64];
if (rec->rectypeid == RECORDOID) return; /* it's RECORD, so nothing to do */
Assert(typ != NULL); if (typ->tcache &&
typ->tcache->tupDesc_identifier == typ->tupdesc_id)
{ /* *Although*typisknownup-to-date,it'spossiblethatrectypeid *isn't,because*recisclonedduringeachfunctionstartupfroma *copythatwedon'thaveagoodwaytoupdate.Hence,forciblyfix *rectypeidbeforereturning.
*/
rec->rectypeid = typ->typoid; return;
}
/* *typcacheentryhassufferedinvalidation,sore-look-upthetypename *ifpossible,andthenrecheckthetypeOID.Ifwedon'thavea *TypeName,thenwejusthavetosoldieronwiththeOIDwe'vegot.
*/ if (typ->origtypname != NULL)
{ /* this bit should match parse_datatype() in pl_gram.y */
typenameTypeIdAndMod(NULL, typ->origtypname,
&typ->typoid,
&typ->atttypmod);
}
/* this bit should match build_datatype() in pl_comp.c */
typentry = lookup_type_cache(typ->typoid,
TYPECACHE_TUPDESC |
TYPECACHE_DOMAIN_BASE_INFO); if (typentry->typtype == TYPTYPE_DOMAIN)
typentry = lookup_type_cache(typentry->domainBaseType,
TYPECACHE_TUPDESC); if (typentry->tupDesc == NULL)
{ /* *Ifwegethere,usertriedtoreplaceacompositetypewitha *non-compositeone.We'renotgonnasupportthat.
*/
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is not composite",
format_type_be(typ->typoid))));
}
/* Walk over destination columns */
anum = 0; for (fnum = 0; fnum < vtd_natts; fnum++)
{
Form_pg_attribute attr = TupleDescAttr(var_tupdesc, fnum);
Datum value; bool isnull;
Oid valtype;
int32 valtypmod;
if (attr->attisdropped)
{ /* expanded_record_set_fields should ignore this column */ continue; /* skip dropped column in record */
}
while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++; /* skip dropped column in tuple */
if (anum < td_natts)
{
value = values[anum];
isnull = nulls[anum];
valtype = TupleDescAttr(tupdesc, anum)->atttypid;
valtypmod = TupleDescAttr(tupdesc, anum)->atttypmod;
anum++;
} else
{ /* no source for destination column */
value = (Datum) 0;
isnull = true;
valtype = UNKNOWNOID;
valtypmod = -1;
/* When source value is missing */ if (strict_multiassignment_level)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment does not match"), /* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.", "strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" : "extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
/* Cast the new value to the right type, if needed. */
newvalues[fnum] = exec_cast_value(estate,
value,
&isnull,
valtype,
valtypmod,
attr->atttypid,
attr->atttypmod);
newnulls[fnum] = isnull;
}
/* *Whenstrict_multiassignmentextracheckisactive,thenensure *therearenounassignedsourceattributes.
*/ if (strict_multiassignment_level && anum < td_natts)
{ /* skip dropped columns in the source descriptor */ while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++;
if (anum < td_natts)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment does not match"), /* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.", "strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" : "extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
values = newvalues;
nulls = newnulls;
}
/* Insert the coerced field values into the new expanded record */
expanded_record_set_fields(newerh, values, nulls, !estate->atomic);
/* Complete the assignment */
assign_record_var(estate, rec, newerh);
return;
}
/* newerh should not have been passed in non-RECORD cases */
Assert(newerh == NULL);
anum = 0; for (fnum = 0; fnum < row->nfields; fnum++)
{
PLpgSQL_var *var;
Datum value; bool isnull;
Oid valtype;
int32 valtypmod;
var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++; /* skip dropped column in tuple */
if (anum < td_natts)
{
value = values[anum];
isnull = nulls[anum];
valtype = TupleDescAttr(tupdesc, anum)->atttypid;
valtypmod = TupleDescAttr(tupdesc, anum)->atttypmod;
anum++;
} else
{ /* no source for destination column */
value = (Datum) 0;
isnull = true;
valtype = UNKNOWNOID;
valtypmod = -1;
if (strict_multiassignment_level)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment does not match"), /* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.", "strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" : "extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
exec_assign_value(estate, (PLpgSQL_datum *) var,
value, isnull, valtype, valtypmod);
}
/* *Whenstrict_multiassignmentextracheckisactive,ensurethereare *nounassignedsourceattributes.
*/ if (strict_multiassignment_level && anum < td_natts)
{ while (anum < td_natts &&
TupleDescAttr(tupdesc, anum)->attisdropped)
anum++; /* skip dropped column in tuple */
if (anum < td_natts)
ereport(strict_multiassignment_level,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("number of source and target fields in assignment does not match"), /* translator: %s represents a name of an extra check */
errdetail("%s check of %s is active.", "strict_multi_assignment",
strict_multiassignment_level == ERROR ? "extra_errors" : "extra_warnings"),
errhint("Make sure the query returns the exact list of columns.")));
}
/* Possibly we could allow src_tupdesc to have extra columns? */ if (dst_tupdesc->natts != src_tupdesc->natts) returnfalse;
for (i = 0; i < dst_tupdesc->natts; i++)
{
Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i);
Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i);
if (dattr->attisdropped != sattr->attisdropped) returnfalse; if (!dattr->attisdropped)
{ /* Normal columns must match by type and typmod */ if (dattr->atttypid != sattr->atttypid ||
(dattr->atttypmod >= 0 &&
dattr->atttypmod != sattr->atttypmod)) returnfalse;
} else
{ /* Dropped columns are OK as long as length/alignment match */ if (dattr->attlen != sattr->attlen ||
dattr->attalign != sattr->attalign) returnfalse;
}
} returntrue;
}
/* *Needtospecial-caseemptysourcerecord,elsecodebelowwould *leaknewerh.
*/ if (ExpandedRecordIsEmpty(erh))
{ /* Set newerh to a row of NULLs */
deconstruct_expanded_record(newerh);
assign_record_var(estate, rec, newerh); return;
}
} /* end of record-target-only cases */
/* Ensure that any detoasted data winds up in the eval_mcontext */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate)); /* Get tuple body (note this could involve detoasting) */
td = DatumGetHeapTupleHeader(value);
MemoryContextSwitchTo(oldcontext);
/* Build a temporary HeapTuple control structure */
tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup.t_self));
tmptup.t_tableOid = InvalidOid;
tmptup.t_data = td;
/* Now, if the target is record not row, maybe we can optimize ... */ if (target->dtype == PLPGSQL_DTYPE_REC)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
/* If declared type is RECORD, we can't instantiate */ if (rec->rectypeid == RECORDOID)
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 rec->rectypeid is up-to-date before using it */
revalidate_rectypeid(rec);
/* OK, do it */
rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1,
estate->datum_context);
}
/* Look for existing entry */
cast_key.srctype = srctype;
cast_key.dsttype = dsttype;
cast_key.srctypmod = srctypmod;
cast_key.dsttypmod = dsttypmod;
cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
&cast_key,
HASH_ENTER, &found); if (!found) /* initialize if new entry */
{ /* We need a second lookup to see if a cast_expr_hash entry exists */
expr_entry = (plpgsql_CastExprHashEntry *) hash_search(cast_expr_hash,
&cast_key,
HASH_ENTER,
&found); if (!found) /* initialize if new expr entry */
expr_entry->cast_cexpr = NULL;
cast_entry->cast_centry = expr_entry;
cast_entry->cast_exprstate = NULL;
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = InvalidLocalTransactionId;
} else
{ /* Use always-valid link to avoid a second hash lookup */
expr_entry = cast_entry->cast_centry;
}
/* Extract the single PlannedStmt */
Assert(list_length(cplan->stmt_list) == 1);
stmt = linitial_node(PlannedStmt, cplan->stmt_list);
Assert(stmt->commandType == CMD_SELECT);
/* *Ordinarily,theplannodeshouldbeasimpleResult.However,if *debug_parallel_queryison,theplannermight'vestuckaGathernode *atopthat;and/orifthisplanisforascrollablecursor,theplanner *might'vestuckaMaterialnodeatopit.Thesimplestwaytodealwith *thisistolookthroughtheGatherand/orMaterialnodes.Theupper *node'stlistwouldnormallycontainaVarreferencingthechildnode's *output...butsetrefs.cmightalsohavecopiedaConstas-is.
*/
plan = stmt->planTree; for (;;)
{ /* Extract the single tlist expression */
Assert(list_length(plan->targetlist) == 1);
tle_expr = linitial_node(TargetEntry, plan->targetlist)->expr;
if (IsA(plan, Result))
{
Assert(plan->lefttree == NULL &&
plan->righttree == NULL &&
plan->initPlan == NULL &&
plan->qual == NULL &&
((Result *) plan)->resconstantqual == NULL); break;
} elseif (IsA(plan, Gather) || IsA(plan, Material))
{
Assert(plan->lefttree != NULL &&
plan->righttree == NULL &&
plan->initPlan == NULL &&
plan->qual == NULL); /* If setrefs.c copied up a Const, no need to look further */ if (IsA(tle_expr, Const)) break; /* Otherwise, it better be an outer Var */
Assert(IsA(tle_expr, Var));
Assert(((Var *) tle_expr)->varno == OUTER_VAR); /* Descend to the child node */
plan = plan->lefttree;
} else
elog(ERROR, "unexpected plan node type: %d",
(int) nodeTag(plan));
}
/* *Savethesimpleexpression,andinitializestateto"notvalidin *currenttransaction".
*/
expr->expr_simple_expr = tle_expr;
expr->expr_simple_state = NULL;
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = InvalidLocalTransactionId; /* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle_expr);
expr->expr_simple_typmod = exprTypmod((Node *) tle_expr); /* We also want to remember if it is immutable or not */
expr->expr_simple_mutable = contain_mutable_functions((Node *) tle_expr);
}
/* See how many references there are, and find one of them */
context.paramid = paramid;
context.count = 0;
context.last_param = NULL;
(void) count_param_references((Node *) sexpr, &context);
/* If we're here, the expr must contain some reference to the var */
Assert(context.count > 0);
/* If exactly one reference, success! */ if (context.count == 1)
{
expr->expr_rwopt = PLPGSQL_RWOPT_TRANSFER;
expr->expr_rw_param = context.last_param; return;
}
}
Assert(dno >= 0 && dno < estate->ndatums);
datum = estate->datums[dno]; switch (datum->dtype)
{ case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_PROMISE: case PLPGSQL_DTYPE_REC: if (((PLpgSQL_variable *) datum)->isconst)
ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
errmsg("variable \"%s\" is declared CONSTANT",
((PLpgSQL_variable *) datum)->refname))); break; case PLPGSQL_DTYPE_ROW: /* always assignable; member vars were checked at compile time */ break; case PLPGSQL_DTYPE_RECFIELD: /* assignable if parent record is */
exec_check_assignable(estate,
((PLpgSQL_recfield *) datum)->recparentno); break; default:
elog(ERROR, "unrecognized dtype: %d", datum->dtype); break;
}
}
/* *Dothedetoastingintheeval_mcontexttoavoidlong-termleakage *ofwhatevermemorytoastfetchingmightleak.Thenwehavetocopy *thedetoasteddatumtothefunction'smaincontext,whichisa *pain,butthere'slittlechoice.
*/
oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
MemoryContextSwitchTo(oldcxt); /* Now's a good time to not leak the input value if it's freeable */ if (freeable)
pfree(DatumGetPointer(newvalue)); /* Once we copy the value, it's definitely freeable */
newvalue = datumCopy(detoasted, false, -1);
freeable = true; /* Can't clean up eval_mcontext here, but it'll happen before long */
}
/* Free the old value if needed */ if (var->freeval)
{ if (DatumIsReadWriteExpandedObject(var->value,
var->isnull,
var->datatype->typlen))
DeleteExpandedObject(var->value); else
pfree(DatumGetPointer(var->value));
} /* Assign new value to datum */
var->value = newvalue;
var->isnull = isnull;
var->freeval = freeable;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.601Bemerkung:
(vorverarbeitet am 2026-08-07)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.