/* *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;
java.lang.StringIndexOutOfBoundsException: Index 71 out of bounds for length 71 if(*plpgsql_plugin_ptr&&(*plpgsql_plugin_ptr)->stmt_beg) ((*plpgsql_plugin_ptr)->stmt_beg)(estate,stmt);
casePLPGSQL_STMT_IF: java.lang.StringIndexOutOfBoundsException: Range [22, 21) out of bounds for length 56 break;
java.lang.StringIndexOutOfBoundsException: Range [35, 34) out of bounds for length 35 ifjava.lang.StringIndexOutOfBoundsException: Range [30, 28) out of bounds for length 39 ;
casejava.lang.StringIndexOutOfBoundsException: Range [26, 25) out of bounds for length 26 rc=exec_stmt_loop(estate,(PLpgSQL_stmt_loopjava.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0 break;
: ; break;
R){ rc break;
casePLPGSQL_STMT_FORSfor(0<;i+{ *) java.lang.StringIndexOutOfBoundsException: Index 10 out of bounds for length 10
casejava.lang.StringIndexOutOfBoundsException: Range [24, 16) out of bounds for length 35 rc=exec_stmt_forc(estate,(PLpgSQL_stmt_forc*)stmt); break;
casePLPGSQL_STMT_FOREACH_A:
java.lang.StringIndexOutOfBoundsException: Index 9 out of bounds for length 9
*thata/11serverintermediatejava.lang.StringIndexOutOfBoundsException: Range [73, 72) out of bounds for length 97 rc=exec_stmt_return_query(,(intermediatecodes break;
casePLPGSQL_STMT_OPEN: rc=exec_stmt_open(estate,(PLpgSQL_stmt_open*)stmt); java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 10
case rc=exec_stmt_fetch(estate,(PLpgSQL_stmt_fetch*)stmt); break;
default:
/* point err_stmt to parent, since this one seems corrupt */
estate->err_stmt = save_estmt;
, stmt->cmd_type;
rc = -1; /* keep compiler quiet */track(lient
}
/* Let the plugin know that we have finished executing this statement */
( &&(plpgsql_plugin_ptr-stmt_endjava.lang.StringIndexOutOfBoundsException: Index 61 out of bounds for length 61
, stmt)
if (rc != PLPGSQL_RC_OK)
{
estate->err_stmt = save_estmt; return java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
}
} /* 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);
/*
* Release last group of tuples (if any)
*/
SPI_freetuptable(tuptab);
UnpinPortal(portal);
/*
* Set the FOUND variable to indicate the result of executing the loop
* (namely, whether we looped one or more times). This must be set last so
* that it does not interfere with the value of the FOUND variable inside
* the loop processing itself.
*/
exec_set_found(estate, found);
return rc;
}
/* ----------
* exec_eval_simple_expr - Evaluate a simple expression returning
* a Datum by directly calling ExecEvalExpr().
*
* If successful, store results into *result, *isNull, *rettype, *rettypmod
* and return true. If the expression cannot be handled by simple evaluation,
* return false.
*
* Because we only store one execution tree for a simple expression, we
* can't handle recursion cases. So, if we see the tree is already busy
* with an evaluation in the current xact, we just return false and let the
* caller run the expression the hard way. (Other alternatives such as
* creating a new tree for a recursive call either introduce memory leaks,
* or add enough bookkeeping to be doubtful wins anyway.) Another case that
* is covered by the expr_simple_in_use test is where a previous execution
* of the tree was aborted by an error: the tree may contain bogus state
* so we dare not re-use it.
*
* It is possible that we'd need to replan a simple expression; for example,
* someone might redefine a SQL function that had been inlined into the simple
* expression. That cannot cause a simple expression to become non-simple (or
* vice versa), but we do have to handle replacing the expression tree.
*
* Note: if pass-by-reference, the result is in the eval_mcontext.
* It will be freed when exec_eval_cleanup is done.
* ----------
*/
static bool
exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
Datum *result,
bool *isNull,
Oid *rettype,
int32 *rettypmod)
{
ExprContext *econtext = estate->eval_econtext;
LocalTransactionId curlxid = MyProc->vxid.lxid;
ParamListInfo paramLI;
void *save_setup_arg;
bool need_snapshot;
MemoryContext oldcontext;
/*
* Forget it if expression wasn't simple before.
*/
if (expr->expr_simple_expr == NULL)
return false;
/*
* If expression is in use in current xact, don't touch it.
*/
if (unlikely(expr->expr_simple_in_use) &&
expr->expr_simple_lxid == curlxid)
return false;
/*
* Ensure that there's a portal-level snapshot, in case this simple
* expression is the first thing evaluated after a COMMIT or ROLLBACK.
* We'd have to do this anyway before executing the expression, so we
* might as well do it now to ensure that any possible replanning doesn't
* need to take a new snapshot.
*/
EnsurePortalSnapshotExists();
/*
* Check to see if the cached plan has been invalidated. If not, and this
* is the first use in the current transaction, save a plan refcount in
* the simple-expression resowner.
*/
if (likely(CachedPlanIsSimplyValid(expr->expr_simple_plansource,
expr->expr_simple_plan,
(expr->expr_simple_plan_lxid != curlxid ?
estate->simple_eval_resowner : NULL))))
{
/*
* It's still good, so just remember that we have a refcount on the
* plan in the current transaction. (If we already had one, this
* assignment is a no-op.)
*/
expr->expr_simple_plan_lxid = curlxid;
}
else
{
/* Need to replan */
CachedPlan *cplan;
/*
* If we have a valid refcount on some previous version of the plan,
* release it, so we don't leak plans intra-transaction.
*/
if (expr->expr_simple_plan_lxid == curlxid)
ReleaseCachedPlan(expr->expr_simple_plan,
estate->simple_eval_resowner);
/*
* Reset to "not simple" to leave sane state (with no dangling
* pointers) in case we fail while replanning. We'll need to
* re-determine simplicity and R/W optimizability anyway, since those
* could change with the new plan. expr_simple_plansource can be left
* alone however, as that cannot move.
*/
expr->expr_simple_expr = NULL;
expr->expr_rwopt = PLPGSQL_RWOPT_UNKNOWN;
expr->expr_rw_param = NULL;
expr->expr_simple_plan = NULL;
expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
/* Do the replanning work in the eval_mcontext */
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);
MemoryContextSwitchTo(oldcontext);
/*
* We can't get a failure here, because the number of
* CachedPlanSources in the SPI plan can't change from what
* exec_simple_check_plan saw; it's a property of the raw parsetree
* generated from the query text.
*/
Assert(cplan != NULL);
/*
* Recheck exec_is_simple_query, which could now report false in
* edge-case scenarios such as a non-SRF having been replaced with a
* SRF. Also recheck CachedPlanAllowsSimpleValidityCheck, just to be
* sure. If either test fails, cope by declaring the plan to be
* non-simple. On success, we'll acquire a refcount on the new plan,
* stored in simple_eval_resowner.
*/
if (exec_is_simple_query(expr) &&
CachedPlanAllowsSimpleValidityCheck(expr->expr_simple_plansource,
cplan,
estate->simple_eval_resowner))
{
/* Remember that we have the refcount */
expr->expr_simple_plan = cplan;
expr->expr_simple_plan_lxid = curlxid;
}
else
{
/* Release SPI_plan_get_cached_plan's refcount */
ReleaseCachedPlan(cplan, CurrentResourceOwner);
return false;
}
/*
* SPI_plan_get_cached_plan acquired a plan refcount stored in the
* active resowner. We don't need that anymore, so release it.
*/
ReleaseCachedPlan(cplan, CurrentResourceOwner);
/* Extract desired scalar expression from cached plan */
exec_save_simple_expr(expr, cplan);
}
/*
* Pass back previously-determined result type.
*/
*rettype = expr->expr_simple_type;
*rettypmod = expr->expr_simple_typmod;
/*
* Set up ParamListInfo to pass to executor. For safety, save and restore
* estate->paramLI->parserSetupArg around our use of the param list.
*/
paramLI = estate->paramLI;
save_setup_arg = paramLI->parserSetupArg;
/*
* We can skip using setup_param_list() in favor of just doing this
* unconditionally, because there's no need for the optimization of
* possibly setting ecxt_param_list_info to NULL; we've already forced use
* of a generic plan.
*/
paramLI->parserSetupArg = expr;
econtext->ecxt_param_list_info = paramLI;
/*
* Prepare the expression for execution, if it's not been done already in
* the current transaction. (This will be forced to happen if we called
* exec_save_simple_expr above.)
*/
if (unlikely(expr->expr_simple_lxid != curlxid))
{
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
expr->expr_simple_state =
ExecInitExprWithParams(expr->expr_simple_expr,
econtext->ecxt_param_list_info);
expr->expr_simple_in_use = false;
expr->expr_simple_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
}
/*
* We have to do some of the things SPI_execute_plan would do, in
* particular push a new snapshot so that stable functions within the
* expression can see updates made so far by our own function. However,
* we can skip doing that (and just invoke the expression with the same
* snapshot passed to our function) in some cases, which is useful because
* it's quite expensive relative to the cost of a simple expression. We
* can skip it if the expression contains no stable or volatile functions;
* immutable functions shouldn't need to see our updates. Also, if this
* is a read-only function, we haven't made any updates so again it's okay
* to skip.
*/
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
need_snapshot = (expr->expr_simple_mutable && !estate->readonly_func);
if (need_snapshot)
{
CommandCounterIncrement();
PushActiveSnapshot(GetTransactionSnapshot());
}
/*
* Mark expression as busy for the duration of the ExecEvalExpr call.
*/
expr->expr_simple_in_use = true;
/*
* Finally we can call the executor to evaluate the expression
*/
*result = ExecEvalExpr(expr->expr_simple_state,
econtext,
isNull);
/*
* Create a ParamListInfo to pass to SPI
*
* We use a single ParamListInfo struct for all SPI calls made to evaluate
* PLpgSQL_exprs in this estate. It contains no per-param data, just hook
* functions, so it's effectively read-only for SPI.
*
* An exception from pure read-only-ness is that the parserSetupArg points
* to the specific PLpgSQL_expr being evaluated. This is not an issue for
* statement-level callers, but lower-level callers must save and restore
* estate->paramLI->parserSetupArg just in case there's an active evaluation
* at an outer call level. (A plausible alternative design would be to
* create a ParamListInfo struct for each PLpgSQL_expr, but for the moment
* that seems like a waste of memory.)
*/
static ParamListInfo
setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
ParamListInfo paramLI;
/*
* We must have created the SPIPlan already (hence, query text has been
* parsed/analyzed at least once); else we cannot rely on expr->paramnos.
*/
Assert(expr->plan != NULL);
/*
* We only need a ParamListInfo if the expression has parameters.
*/
if (!bms_is_empty(expr->paramnos))
{
/* Use the common ParamListInfo */
paramLI = estate->paramLI;
/*
* Set up link to active expr where the hook functions can find it.
* Callers must save and restore parserSetupArg if there is any chance
* that they are interrupting an active use of parameters.
*/
paramLI->parserSetupArg = expr;
}
else
{
/*
* Expression requires no parameters. Be sure we represent this case
* as a NULL ParamListInfo, so that plancache.c knows there is no
* point in a custom plan.
*/
paramLI = NULL;
}
return paramLI;
}
/*
* plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
*
* We always use the caller's workspace to construct the returned struct.
*
* Note: this is no longer used during query execution. It is used during
* planning (with speculative == true) and when the ParamListInfo we supply
* to the executor is copied into a cursor portal or transferred to a
* parallel child process.
*/
static ParamExternData *
plpgsql_param_fetch(ParamListInfo params,
int paramid, bool speculative,
ParamExternData *prm)
{
int dno;
PLpgSQL_execstate *estate;
PLpgSQL_expr *expr;
PLpgSQL_datum *datum;
bool ok = true;
int32 prmtypmod;
/* paramid's are 1-based, but dnos are 0-based */
dno = paramid - 1;
Assert(dno >= 0 && dno < params->numParams);
/* fetch back the hook data */
estate = (PLpgSQL_execstate *) params->paramFetchArg;
expr = (PLpgSQL_expr *) params->parserSetupArg;
Assert(params->numParams == estate->ndatums);
/* now we can access the target datum */
datum = estate->datums[dno];
/*
* Since copyParamList() or SerializeParamList() will try to materialize
* every single parameter slot, it's important to return a dummy param
* when asked for a datum that's not supposed to be used by this SQL
* expression. Otherwise we risk failures in exec_eval_datum(), or
* copying a lot more data than necessary.
*/
if (!bms_is_member(dno, expr->paramnos))
ok = false;
/*
* If the access is speculative, we prefer to return no data rather than
* to fail in exec_eval_datum(). Check the likely failure cases.
*/
else if (speculative)
{
switch (datum->dtype)
{
case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
/* always safe */
break;
case PLPGSQL_DTYPE_ROW:
/* should be safe in all interesting cases */
break;
/*
* If record variable is NULL, don't risk anything.
*/
if (rec->erh == NULL)
ok = false;
/*
* Look up the field's properties if we have not already,
* or if the tuple descriptor ID changed since last time.
*/
else if (unlikely(recfield->rectupledescid != rec->erh->er_tupdesc_id))
{
if (expanded_record_lookup_field(rec->erh,
recfield->fieldname,
&recfield->finfo))
recfield->rectupledescid = rec->erh->er_tupdesc_id;
else
ok = false;
}
break;
}
default:
ok = false;
break;
}
}
/* 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;
/*
* If it's a read/write expanded datum, convert reference to read-only.
* (There's little point in trying to optimize read/write parameters,
* given the cases in which this function is used.)
*/
if (datum->dtype == PLPGSQL_DTYPE_VAR)
prm->value = MakeExpandedObjectReadOnly(prm->value,
prm->isnull,
((PLpgSQL_var *) datum)->datatype->typlen);
else if (datum->dtype == PLPGSQL_DTYPE_REC)
prm->value = MakeExpandedObjectReadOnly(prm->value,
prm->isnull,
-1);
/*
* Select appropriate eval function.
*
* First, if this Param references the same varlena-type DTYPE_VAR datum
* that is the target of the assignment containing this simple expression,
* then it's possible we will be able to optimize handling of R/W expanded
* datums. We don't want to do the work needed to determine that unless
* we actually see a R/W expanded datum at runtime, so install a checking
* function that will figure that out when needed.
*
* Otherwise, it seems worth special-casing DTYPE_VAR and DTYPE_RECFIELD
* for performance. Also, we can determine in advance whether
* MakeExpandedObjectReadOnly() will be required. Currently, only
* VAR/PROMISE and REC datums could contain read/write expanded objects.
*/
if (datum->dtype == PLPGSQL_DTYPE_VAR)
{
bool isvarlena = (((PLpgSQL_var *) datum)->datatype->typlen == -1);
/*
* Note: it's tempting to use paramarg to store the estate pointer and
* thereby save an indirection or two in the eval functions. But that
* doesn't work because the compiled expression might be used with
* different estates for the same PL/pgSQL function. Instead, store
* pointers to the PLpgSQL_expr as well as this specific Param, to support
* plpgsql_param_eval_var_check().
*/
scratch.d.cparam.paramarg = expr;
scratch.d.cparam.paramarg2 = param;
scratch.d.cparam.paramid = param->paramid;
scratch.d.cparam.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch);
}
/*
* plpgsql_param_eval_var_check evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we may need to determine the applicability of a read/write optimization,
* but we've not done that yet. The work to determine applicability will
* be done at most once (per construction of the PL/pgSQL function's cache
* entry) when we first see that the target variable's old value is a R/W
* expanded object. If we never do see that, nothing is lost: the amount
* of work done by this function in that case is just about the same as
* what would be done by plpgsql_param_eval_var_ro, which is what we'd
* have used otherwise.
*/
static void
plpgsql_param_eval_var_check(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/*
* If the variable's current value is a R/W expanded object, it's time to
* decide whether/how to optimize the assignment.
*/
if (!var->isnull &&
VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
{
PLpgSQL_expr *expr = (PLpgSQL_expr *) op->d.cparam.paramarg;
Param *param = (Param *) op->d.cparam.paramarg2;
/*
* We might have already figured this out while evaluating some other
* Param referencing the same variable, so check expr_rwopt first.
*/
if (expr->expr_rwopt == PLPGSQL_RWOPT_UNKNOWN)
exec_check_rw_parameter(expr, op->d.cparam.paramid);
/*
* Update the callback pointer to match what we decided to do, so that
* this function will not be called again. Then pass off this
* execution to the newly-selected function.
*/
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;
}
/*
* Otherwise, continue to postpone that decision, and execute an inlined
* version of exec_eval_datum(). Although this value could potentially
* need MakeExpandedObjectReadOnly, we know it doesn't right now.
*/
*op->resvalue = var->value;
*op->resnull = var->isnull;
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/*
* plpgsql_param_eval_var_transfer evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we have determined that a read/write expanded value can be handed off
* into execution of the expression (and then possibly returned to our
* function's ownership afterwards). We have to test though, because the
* variable might not contain a read/write expanded value during this
* execution.
*/
static void
plpgsql_param_eval_var_transfer(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/*
* If the variable's current value is a R/W expanded object, transfer its
* ownership into the expression execution context, then drop our own
* reference to the value by setting the variable to NULL. That'll be
* overwritten (perhaps with this same object) when control comes back
* from the expression.
*/
if (!var->isnull &&
VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
{
*op->resvalue = TransferExpandedObject(var->value,
get_eval_mcontext(estate));
*op->resnull = false;
var->value = (Datum) 0;
var->isnull = true;
var->freeval = false;
}
else
{
/*
* Otherwise we can pass the variable's value directly; we now know
* that MakeExpandedObjectReadOnly isn't needed.
*/
*op->resvalue = var->value;
*op->resnull = var->isnull;
}
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/*
* plpgsql_param_eval_var evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we do not need to invoke MakeExpandedObjectReadOnly.
*/
static void
plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/* inlined version of exec_eval_datum() */
*op->resvalue = var->value;
*op->resnull = var->isnull;
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/*
* plpgsql_param_eval_var_ro evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we need to invoke MakeExpandedObjectReadOnly.
*/
static void
plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/*
* Inlined version of exec_eval_datum() ... and while we're at it, force
* expanded datums to read-only.
*/
*op->resvalue = MakeExpandedObjectReadOnly(var->value,
var->isnull,
-1);
*op->resnull = var->isnull;
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/*
* plpgsql_param_eval_recfield evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_RECFIELD variables, for which
* we never need to invoke MakeExpandedObjectReadOnly.
*/
static void
plpgsql_param_eval_recfield(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_recfield *recfield;
PLpgSQL_rec *rec;
ExpandedRecordHeader *erh;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
recfield = (PLpgSQL_recfield *) estate->datums[dno];
Assert(recfield->dtype == PLPGSQL_DTYPE_RECFIELD);
/* inline the relevant part of exec_eval_datum */
rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
erh = rec->erh;
/*
* If record variable is NULL, instantiate it if it has a named composite
* type, else complain. (This won't change the logical state of the
* record: it's still NULL.)
*/
if (erh == NULL)
{
instantiate_empty_record_variable(estate, rec);
erh = rec->erh;
}
/*
* Look up the field's properties if we have not already, or if the tuple
* descriptor ID changed since last time.
*/
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))));
}
/*
* plpgsql_param_eval_generic evaluation of EEOP_PARAM_CALLBACK step
*
* This handles all variable types, but assumes we do not need to invoke
* MakeExpandedObjectReadOnly.
*/
static void
plpgsql_param_eval_generic(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_datum *datum;
Oid datumtype;
int32 datumtypmod;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
datum = estate->datums[dno];
/* 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))));
}
/*
* plpgsql_param_eval_generic_ro evaluation of EEOP_PARAM_CALLBACK step
*
* This handles all variable types, but assumes we need to invoke
* MakeExpandedObjectReadOnly (hence, variable must be of a varlena type).
*/
static void
plpgsql_param_eval_generic_ro(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_datum *datum;
Oid datumtype;
int32 datumtypmod;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
datum = estate->datums[dno];
/* 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);
}
/*
* exec_move_row Move one tuple's values into a record or row
*
* tup and tupdesc may both be NULL if we're just assigning an indeterminate
* composite NULL to the target. Alternatively, can have tup be NULL and
* tupdesc not NULL, in which case we assign a row of NULLs to the target.
*
* Since this uses the mcontext for workspace, caller should eventually call
* exec_eval_cleanup to prevent long-term memory leaks.
*/
static void
exec_move_row(PLpgSQL_execstate *estate,
PLpgSQL_variable *target,
HeapTuple tup, TupleDesc tupdesc)
{
ExpandedRecordHeader *newerh = NULL;
/*
* If target is RECORD, we may be able to avoid field-by-field processing.
*/
if (target->dtype == PLPGSQL_DTYPE_REC)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
/*
* If we have no source tupdesc, just set the record variable to NULL.
* (If we have a source tupdesc but not a tuple, we'll set the
* variable to a row of nulls, instead. This is odd perhaps, but
* backwards compatible.)
*/
if (tupdesc == NULL)
{
if (rec->datatype &&
rec->datatype->typtype == TYPTYPE_DOMAIN)
{
/*
* If it's a composite domain, NULL might not be a legal
* value, so we instead need to make an empty expanded record
* and ensure that domain type checking gets done. If there
* is already an expanded record, piggyback on its lookups.
*/
newerh = make_expanded_record_for_rec(estate, rec,
NULL, rec->erh);
expanded_record_set_tuple(newerh, NULL, false, false);
assign_record_var(estate, rec, newerh);
}
else
{
/* Just clear it to NULL */
if (rec->erh)
DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
rec->erh = NULL;
}
return;
}
/*
* Build a new expanded record with appropriate tupdesc.
*/
newerh = make_expanded_record_for_rec(estate, rec, tupdesc, NULL);
/*
* If the rowtypes match, or if we have no tuple anyway, we can
* complete the assignment without field-by-field processing.
*
* The tests here are ordered more or less in order of cheapness. We
* can easily detect it will work if the target is declared RECORD or
* has the same typeid as the source. But when assigning from a query
* result, it's common to have a source tupdesc that's labeled RECORD
* but is actually physically compatible with a named-composite-type
* target, so it's worth spending extra cycles to check for that.
*/
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, deconstruct the tuple and do field-by-field assignment,
* using exec_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];
/*
* Need workspace arrays. If td_natts is small enough, use local
* arrays to save doing a palloc. Even if it's not small, we can
* allocate both the Datum and isnull arrays in one palloc chunk.
*/
if (td_natts <= lengthof(values_local))
{
values = values_local;
nulls = nulls_local;
}
else
{
char *chunk;
/*
* Verify that a PLpgSQL_rec's rectypeid is up-to-date.
*/
static void
revalidate_rectypeid(PLpgSQL_rec *rec)
{
PLpgSQL_type *typ = rec->datatype;
TypeCacheEntry *typentry;
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 *typ is known up-to-date, it's possible that rectypeid
* isn't, because *rec is cloned during each function startup from a
* copy that we don't have a good way to update. Hence, forcibly fix
* rectypeid before returning.
*/
rec->rectypeid = typ->typoid;
return;
}
/*
* typcache entry has suffered invalidation, so re-look-up the type name
* if possible, and then recheck the type OID. If we don't have a
* TypeName, then we just have to soldier on with the OID we've got.
*/
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)
{
/*
* If we get here, user tried to replace a composite type with a
* non-composite one. We're not gonna support that.
*/
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("type %s is not composite",
format_type_be(typ->typoid))));
}
/*
* Update tcache and tupdesc_id. Since we don't support changing to a
* non-composite type, none of the rest of *typ needs to change.
*/
typ->tcache = typentry;
typ->tupdesc_id = typentry->tupDesc_identifier;
/*
* Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.)
*/
rec->rectypeid = typ->typoid;
}
/*
* Build an expanded record object suitable for assignment to "rec".
*
* Caller must supply either a source tuple descriptor or a source expanded
* record (not both). If the record variable has declared type RECORD,
* it'll adopt the source's rowtype. Even if it doesn't, we may be able to
* piggyback on a source expanded record to save a typcache lookup.
*
* Caller must fill the object with data, then do assign_record_var().
*
* The new record is initially put into the mcontext, so it will be cleaned up
* if we fail before reaching assign_record_var().
*/
static ExpandedRecordHeader *
make_expanded_record_for_rec(PLpgSQL_execstate *estate,
PLpgSQL_rec *rec,
TupleDesc srctupdesc,
ExpandedRecordHeader *srcerh)
{
ExpandedRecordHeader *newerh;
MemoryContext mcontext = get_eval_mcontext(estate);
if (rec->rectypeid != RECORDOID)
{
/*
* Make sure rec->rectypeid is up-to-date before using it.
*/
revalidate_rectypeid(rec);
/*
* New record must be of desired type, but maybe srcerh has already
* done all the same lookups.
*/
if (srcerh && rec->rectypeid == srcerh->er_decltypeid)
newerh = make_expanded_record_from_exprecord(srcerh,
mcontext);
else
newerh = make_expanded_record_from_typeid(rec->rectypeid, -1,
mcontext);
}
else
{
/*
* We'll adopt the input tupdesc. We can still use
* make_expanded_record_from_exprecord, if srcerh isn't a composite
* domain. (If it is, we effectively adopt its base type.)
*/
if (srcerh && !ExpandedRecordIsDomain(srcerh))
newerh = make_expanded_record_from_exprecord(srcerh,
mcontext);
else
{
if (!srctupdesc)
srctupdesc = expanded_record_get_tupdesc(srcerh);
newerh = make_expanded_record_from_tupdesc(srctupdesc,
mcontext);
}
}
return newerh;
}
/*
* exec_move_row_from_fields Move arrays of field values into a record or row
*
* When assigning to a record, the caller must have already created a suitable
* new expanded record object, newerh. Pass NULL when assigning to a row.
*
* tupdesc describes the input row, which might have different column
* types and/or different dropped-column positions than the target.
* values/nulls/tupdesc can all be NULL if we just want to assign nulls to
* all fields of the record or row.
*
* Since this uses the mcontext for workspace, caller should eventually call
* exec_eval_cleanup to prevent long-term memory leaks.
*/
static void
exec_move_row_from_fields(PLpgSQL_execstate *estate,
PLpgSQL_variable *target,
ExpandedRecordHeader *newerh,
Datum *values, bool *nulls,
TupleDesc tupdesc)
{
int td_natts = tupdesc ? tupdesc->natts : 0;
int fnum;
int anum;
int strict_multiassignment_level = 0;
/*
* The extra check strict strict_multi_assignment can be active, only when
* input tupdesc is specified.
*/
if (tupdesc != NULL)
{
if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
strict_multiassignment_level = ERROR;
else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
strict_multiassignment_level = WARNING;
}
/* Handle RECORD-target case */
if (target->dtype == PLPGSQL_DTYPE_REC)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
TupleDesc var_tupdesc;
Datum newvalues_local[64];
bool newnulls_local[64];
Assert(newerh != NULL); /* caller must have built new object */
/*
* Coerce field values if needed. This might involve dealing with
* different sets of dropped columns and/or coercing individual column
* types. That's sort of a pain, but historically plpgsql has allowed
* it, so we preserve the behavior. However, it's worth a quick check
* to see if the tupdescs are identical. (Since expandedrecord.c
* prefers to use refcounted tupdescs from the typcache, expanded
* records with the same rowtype will have pointer-equal tupdescs.)
*/
if (var_tupdesc != tupdesc)
{
int vtd_natts = var_tupdesc->natts;
Datum *newvalues;
bool *newnulls;
/*
* Need workspace arrays. If vtd_natts is small enough, use local
* arrays to save doing a palloc. Even if it's not small, we can
* allocate both the Datum and isnull arrays in one palloc chunk.
*/
if (vtd_natts <= lengthof(newvalues_local))
{
newvalues = newvalues_local;
newnulls = newnulls_local;
}
else
{
char *chunk;
/* 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;
}
/*
* When strict_multiassignment extra check is active, then ensure
* there are no unassigned source attributes.
*/
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);
/*
* For a row, we assign the individual field values to the variables the
* row points to.
*
* NOTE: both this code and the record code above silently ignore extra
* columns in the source and assume NULL for missing columns. This is
* pretty dubious but it's the historical behavior.
*
* If we have no input data at all, we'll assign NULL to all columns of
* the row variable.
*/
if (target->dtype == PLPGSQL_DTYPE_ROW)
{
PLpgSQL_row *row = (PLpgSQL_row *) target;
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);
}
/*
* When strict_multiassignment extra check is active, ensure there are
* no unassigned source attributes.
*/
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.")));
}
/*
* compatible_tupdescs: detect whether two tupdescs are physically compatible
*
* TRUE indicates that a tuple satisfying src_tupdesc can be used directly as
* a value for a composite variable using dst_tupdesc.
*/
static bool
compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc)
{
int i;
/* Possibly we could allow src_tupdesc to have extra columns? */
if (dst_tupdesc->natts != src_tupdesc->natts)
return false;
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)
return false;
if (!dattr->attisdropped)
{
/* Normal columns must match by type and typmod */
if (dattr->atttypid != sattr->atttypid ||
(dattr->atttypmod >= 0 &&
dattr->atttypmod != sattr->atttypmod))
return false;
}
else
{
/* Dropped columns are OK as long as length/alignment match */
if (dattr->attlen != sattr->attlen ||
dattr->attalign != sattr->attalign)
return false;
}
}
return true;
}
/* ----------
* make_tuple_from_row Make a tuple from the values of a row object
*
* A NULL return indicates rowtype mismatch; caller must raise suitable error
*
* The result tuple is freshly palloc'd in caller's context. Some junk
* may be left behind in eval_mcontext, too.
* ----------
*/
static HeapTuple
make_tuple_from_row(PLpgSQL_execstate *estate,
PLpgSQL_row *row,
TupleDesc tupdesc)
{
int natts = tupdesc->natts;
HeapTuple tuple;
Datum *dvalues;
bool *nulls;
int i;
for (i = 0; i < natts; i++)
{
Oid fieldtypeid;
int32 fieldtypmod;
if (TupleDescAttr(tupdesc, i)->attisdropped)
{
nulls[i] = true; /* leave the column as null */
continue;
}
exec_eval_datum(estate, estate->datums[row->varnos[i]],
&fieldtypeid, &fieldtypmod,
&dvalues[i], &nulls[i]);
if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
return NULL;
/* XXX should we insist on typmod match, too? */
}
tuple = heap_form_tuple(tupdesc, dvalues, nulls);
return tuple;
}
/*
* deconstruct_composite_datum extract tuple+tupdesc from composite Datum
*
* The caller must supply a HeapTupleData variable, in which we set up a
* tuple header pointing to the composite datum's body. To make the tuple
* value outlive that variable, caller would need to apply heap_copytuple...
* but current callers only need a short-lived tuple value anyway.
*
* Returns a pointer to the TupleDesc of the datum's rowtype.
* Caller is responsible for calling ReleaseTupleDesc when done with it.
*
* Note: it's caller's responsibility to be sure value is of composite type.
* Also, best to call this in a short-lived context, as it might leak memory.
*/
static TupleDesc
deconstruct_composite_datum(Datum value, HeapTupleData *tmptup)
{
HeapTupleHeader td;
Oid tupType;
int32 tupTypmod;
/* Get tuple body (note this could involve detoasting) */
td = DatumGetHeapTupleHeader(value);
/* Build a temporary HeapTuple control structure */
tmptup->t_len = HeapTupleHeaderGetDatumLength(td);
ItemPointerSetInvalid(&(tmptup->t_self));
tmptup->t_tableOid = InvalidOid;
tmptup->t_data = td;
/* Extract rowtype info and find a tupdesc */
tupType = HeapTupleHeaderGetTypeId(td);
tupTypmod = HeapTupleHeaderGetTypMod(td);
return lookup_rowtype_tupdesc(tupType, tupTypmod);
}
/*
* exec_move_row_from_datum Move a composite Datum into a record or row
*
* This is equivalent to deconstruct_composite_datum() followed by
* exec_move_row(), but we can optimize things if the Datum is an
* expanded-record reference.
*
* Note: it's caller's responsibility to be sure value is of composite type.
*/
static void
exec_move_row_from_datum(PLpgSQL_execstate *estate,
PLpgSQL_variable *target,
Datum value)
{
/* Check to see if source is an expanded record */
if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value)))
{
ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(value);
ExpandedRecordHeader *newerh = NULL;
Assert(erh->er_magic == ER_MAGIC);
/* These cases apply if the target is record not row... */
if (target->dtype == PLPGSQL_DTYPE_REC)
{
PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
/*
* If it's the same record already stored in the variable, do
* nothing. This would happen only in silly cases like "r := r",
* but we need some check to avoid possibly freeing the variable's
* live value below. Note that this applies even if what we have
* is a R/O pointer.
*/
if (erh == rec->erh)
return;
/*
* Make sure rec->rectypeid is up-to-date before using it.
*/
revalidate_rectypeid(rec);
/*
* If we have a R/W pointer, we're allowed to just commandeer
* ownership of the expanded record. If it's of the right type to
* put into the record variable, do that. (Note we don't accept
* an expanded record of a composite-domain type as a RECORD
* value. We'll treat it as the base composite type instead;
* compare logic in make_expanded_record_for_rec.)
*/
if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(value)) &&
(rec->rectypeid == erh->er_decltypeid ||
(rec->rectypeid == RECORDOID &&
!ExpandedRecordIsDomain(erh))))
{
assign_record_var(estate, rec, erh);
return;
}
/*
* If we already have an expanded record object in the target
* variable, and the source record contains a valid tuple
* representation with the right rowtype, then we can skip making
* a new expanded record and just assign the tuple with
* expanded_record_set_tuple. (We can't do the equivalent if we
* have to do field-by-field assignment, since that wouldn't be
* atomic if there's an error.) We consider that there's a
* rowtype match only if it's the same named composite type or
* same registered rowtype; checking for matches of anonymous
* rowtypes would be more expensive than this is worth.
*/
if (rec->erh &&
(erh->flags & ER_FLAG_FVALUE_VALID) &&
erh->er_typeid == rec->erh->er_typeid &&
(erh->er_typeid != RECORDOID ||
(erh->er_typmod == rec->erh->er_typmod &&
erh->er_typmod >= 0)))
{
expanded_record_set_tuple(rec->erh, erh->fvalue,
true, !estate->atomic);
return;
}
/*
* Otherwise we're gonna need a new expanded record object. Make
* it here in hopes of piggybacking on the source object's
* previous typcache lookup.
*/
newerh = make_expanded_record_for_rec(estate, rec, NULL, erh);
/*
* If the expanded record contains a valid tuple representation,
* and we don't need rowtype conversion, then just copying the
* tuple is probably faster than field-by-field processing. (This
* isn't duplicative of the previous check, since here we will
* catch the case where the record variable was previously empty.)
*/
if ((erh->flags & ER_FLAG_FVALUE_VALID) &&
(rec->rectypeid == RECORDOID ||
rec->rectypeid == erh->er_typeid))
{
expanded_record_set_tuple(newerh, erh->fvalue,
true, !estate->atomic);
assign_record_var(estate, rec, newerh);
return;
}
/*
* Need to special-case empty source record, else code below would
* leak newerh.
*/
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 */
/*
* If the source expanded record is empty, we should treat that like a
* NULL tuple value. (We're unlikely to see such a case, but we must
* check this; deconstruct_expanded_record would cause a change of
* logical state, which is not OK.)
*/
if (ExpandedRecordIsEmpty(erh))
{
exec_move_row(estate, target, NULL,
expanded_record_get_tupdesc(erh));
return;
}
/*
* Otherwise, ensure that the source record is deconstructed, and
* assign from its field values.
*/
deconstruct_expanded_record(erh);
exec_move_row_from_fields(estate, target, newerh,
erh->dvalues, erh->dnulls,
expanded_record_get_tupdesc(erh));
}
else
{
/*
* Nope, we've got a plain composite Datum. Deconstruct it; but we
* don't use deconstruct_composite_datum(), because we may be able to
* skip calling lookup_rowtype_tupdesc().
*/
HeapTupleHeader td;
HeapTupleData tmptup;
Oid tupType;
int32 tupTypmod;
TupleDesc tupdesc;
MemoryContext oldcontext;
/* 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 we already have an expanded record object in the target
* variable, and the source datum has a matching rowtype, then we
* can skip making a new expanded record and just assign the tuple
* with expanded_record_set_tuple. We consider that there's a
* rowtype match only if it's the same named composite type or
* same registered rowtype. (Checking to reject an anonymous
* rowtype here should be redundant, but let's be safe.)
*/
if (rec->erh &&
tupType == rec->erh->er_typeid &&
(tupType != RECORDOID ||
(tupTypmod == rec->erh->er_typmod &&
tupTypmod >= 0)))
{
expanded_record_set_tuple(rec->erh, &tmptup,
true, !estate->atomic);
return;
}
/*
* If the source datum has a rowtype compatible with the target
* variable, just build a new expanded record and assign the tuple
* into it. Using make_expanded_record_from_typeid() here saves
* one typcache lookup compared to the code below.
*/
if (rec->rectypeid == RECORDOID || rec->rectypeid == tupType)
{
ExpandedRecordHeader *newerh;
MemoryContext mcontext = get_eval_mcontext(estate);
/*
* Otherwise, we're going to need conversion, so fall through to
* do it the hard way.
*/
}
/*
* ROW target, or unoptimizable RECORD target, so we have to expend a
* lookup to obtain the source datum's tupdesc.
*/
tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
/* Do the move */
exec_move_row(estate, target, &tmptup, tupdesc);
/*
* If we have not created an expanded record to hold the record variable's
* value, do so. The expanded record will be "empty", so this does not
* change the logical state of the record variable: it's still NULL.
* However, now we'll have a tupdesc with which we can e.g. look up fields.
*/
static void
instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec)
{
Assert(rec->erh == NULL); /* else caller error */
/* 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);
}
/* ----------
* convert_value_to_string Convert a non-null Datum to C string
*
* Note: the result is in the estate's eval_mcontext, and will be cleared
* by the next exec_eval_cleanup() call. The invoked output function might
* leave additional cruft there as well, so just pfree'ing the result string
* would not be enough to avoid memory leaks if we did not do it like this.
* In most usages the Datum being passed in is also in that context (if
* pass-by-reference) and so an exec_eval_cleanup() call is needed anyway.
*
* Note: not caching the conversion function lookup is bad for performance.
* However, this function isn't currently used in any places where an extra
* catalog lookup or two seems like a big deal.
* ----------
*/
static char *
convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
{
char *result;
MemoryContext oldcontext;
Oid typoutput;
bool typIsVarlena;
/* ----------
* exec_cast_value Cast a value if required
*
* Note that *isnull is an input and also an output parameter. While it's
* unlikely that a cast operation would produce null from non-null or vice
* versa, that could happen in principle.
*
* Note: the estate's eval_mcontext is used for temporary storage, and may
* also contain the result Datum if we have to do a conversion to a pass-
* by-reference data type. Be sure to do an exec_eval_cleanup() call when
* done with the result.
* ----------
*/
static inline Datum
exec_cast_value(PLpgSQL_execstate *estate,
Datum value, bool *isnull,
Oid valtype, int32 valtypmod,
Oid reqtype, int32 reqtypmod)
{
/*
* If the type of the given value isn't what's requested, convert it.
*/
if (valtype != reqtype ||
(valtypmod != reqtypmod && reqtypmod != -1))
{
/* We keep the slow path out-of-line. */
value = do_cast_value(estate, value, isnull, valtype, valtypmod,
reqtype, reqtypmod);
}
return value;
}
/* ----------
* do_cast_value Slow path for exec_cast_value.
* ----------
*/
static Datum
do_cast_value(PLpgSQL_execstate *estate,
Datum value, bool *isnull,
Oid valtype, int32 valtypmod,
Oid reqtype, int32 reqtypmod)
{
plpgsql_CastHashEntry *cast_entry;
value = ExecEvalExpr(cast_entry->cast_exprstate, econtext,
isnull);
cast_entry->cast_in_use = false;
MemoryContextSwitchTo(oldcontext);
}
return value;
}
/* ----------
* get_cast_hashentry Look up how to perform a type cast
*
* Returns a plpgsql_CastHashEntry if an expression has to be evaluated,
* or NULL if the cast is a mere no-op relabeling. If there's work to be
* done, the cast_exprstate field contains an expression evaluation tree
* based on a CaseTestExpr input, and the cast_in_use field should be set
* true while executing it.
* ----------
*/
static plpgsql_CastHashEntry *
get_cast_hashentry(PLpgSQL_execstate *estate,
Oid srctype, int32 srctypmod,
Oid dsttype, int32 dsttypmod)
{
plpgsql_CastHashKey cast_key;
plpgsql_CastHashEntry *cast_entry;
plpgsql_CastExprHashEntry *expr_entry;
bool found;
LocalTransactionId curlxid;
MemoryContext oldcontext;
/* 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;
}
if (expr_entry->cast_cexpr == NULL ||
!expr_entry->cast_cexpr->is_valid)
{
/*
* We've not looked up this coercion before, or we have but the cached
* expression has been invalidated.
*/
Node *cast_expr;
CachedExpression *cast_cexpr;
CaseTestExpr *placeholder;
/*
* Drop old cached expression if there is one.
*/
if (expr_entry->cast_cexpr)
{
FreeCachedExpression(expr_entry->cast_cexpr);
expr_entry->cast_cexpr = NULL;
}
/*
* Since we could easily fail (no such coercion), construct a
* temporary coercion expression tree in the short-lived
* eval_mcontext, then if successful save it as a CachedExpression.
*/
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
/*
* We use a CaseTestExpr as the base of the coercion tree, since it's
* very cheap to insert the source value for that.
*/
placeholder = makeNode(CaseTestExpr);
placeholder->typeId = srctype;
placeholder->typeMod = srctypmod;
placeholder->collation = get_typcollation(srctype);
/*
* Apply coercion. We use the special coercion context
* COERCION_PLPGSQL to match plpgsql's historical behavior, namely
* that any cast not available at ASSIGNMENT level will be implemented
* as an I/O coercion. (It's somewhat dubious that we prefer I/O
* coercion over cast pathways that exist at EXPLICIT level. Changing
* that would cause assorted minor behavioral differences though, and
* a user who wants the explicit-cast behavior can always write an
* explicit cast.)
*
* If source type is UNKNOWN, coerce_to_target_type will fail (it only
* expects to see that for Const input nodes), so don't call it; we'll
* apply CoerceViaIO instead. Likewise, it doesn't currently work for
* coercing RECORD to some other type, so skip for that too.
*/
if (srctype == UNKNOWNOID || srctype == RECORDOID)
cast_expr = NULL;
else
cast_expr = coerce_to_target_type(NULL,
(Node *) placeholder, srctype,
dsttype, dsttypmod,
COERCION_PLPGSQL,
COERCE_IMPLICIT_CAST,
-1);
/*
* If there's no cast path according to the parser, fall back to using
* an I/O coercion; this is semantically dubious but matches plpgsql's
* historical behavior. We would need something of the sort for
* UNKNOWN literals in any case. (This is probably now only reachable
* in the case where srctype is UNKNOWN/RECORD.)
*/
if (cast_expr == NULL)
{
CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
/* Note: we don't bother labeling the expression tree with collation */
/* Plan the expression and build a CachedExpression */
cast_cexpr = GetCachedExpression(cast_expr);
cast_expr = cast_cexpr->expr;
/* Detect whether we have a no-op (RelabelType) coercion */
if (IsA(cast_expr, RelabelType) &&
((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
cast_expr = NULL;
/* Now we can fill in the expression hashtable entry. */
expr_entry->cast_cexpr = cast_cexpr;
expr_entry->cast_expr = (Expr *) cast_expr;
/* Be sure to reset the exprstate hashtable entry, too. */
cast_entry->cast_exprstate = NULL;
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = InvalidLocalTransactionId;
MemoryContextSwitchTo(oldcontext);
}
/* Done if we have determined that this is a no-op cast. */
if (expr_entry->cast_expr == NULL)
return NULL;
/*
* Prepare the expression for execution, if it's not been done already in
* the current transaction; also, if it's marked busy in the current
* transaction, abandon that expression tree and build a new one, so as to
* avoid potential problems with recursive cast expressions and failed
* executions. (We will leak some memory intra-transaction if that
* happens a lot, but we don't expect it to.) It's okay to update the
* hash table with the new tree because all plpgsql functions within a
* given transaction share the same simple_eval_estate. (Well, regular
* functions do; DO blocks have private simple_eval_estates, and private
* cast hash tables to go with them.)
*/
curlxid = MyProc->vxid.lxid;
if (cast_entry->cast_lxid != curlxid || cast_entry->cast_in_use)
{
oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
cast_entry->cast_exprstate = ExecInitExpr(expr_entry->cast_expr, NULL);
cast_entry->cast_in_use = false;
cast_entry->cast_lxid = curlxid;
MemoryContextSwitchTo(oldcontext);
}
return cast_entry;
}
/* ----------
* exec_simple_check_plan - Check if a plan is simple enough to
* be evaluated by ExecEvalExpr() instead
* of SPI.
*
* Note: the refcount manipulations in this function assume that expr->plan
* is a "saved" SPI plan. That's a bit annoying from the caller's standpoint,
* but it's otherwise difficult to avoid leaking the plan on failure.
* ----------
*/
static void
exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
{
List *plansources;
CachedPlanSource *plansource;
CachedPlan *cplan;
MemoryContext oldcontext;
/*
* Initialize to "not simple", and reset R/W optimizability.
*/
expr->expr_simple_expr = NULL;
expr->expr_rwopt = PLPGSQL_RWOPT_UNKNOWN;
expr->expr_rw_param = NULL;
/*
* Check the analyzed-and-rewritten form of the query to see if we will be
* able to treat it as a simple expression. Since this function is only
* called immediately after creating the CachedPlanSource, we need not
* worry about the query being stale.
*/
if (!exec_is_simple_query(expr))
return;
/* exec_is_simple_query verified that there's just one CachedPlanSource */
plansources = SPI_plan_get_plan_sources(expr->plan);
plansource = (CachedPlanSource *) linitial(plansources);
/*
* Get the generic plan for the query. If replanning is needed, do that
* work in the eval_mcontext. (Note that replanning could throw an error,
* in which case the expr is left marked "not simple", which is fine.)
*/
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);
MemoryContextSwitchTo(oldcontext);
/* Can't fail, because we checked for a single CachedPlanSource above */
Assert(cplan != NULL);
/*
* Verify that plancache.c thinks the plan is simple enough to use
* CachedPlanIsSimplyValid. Given the restrictions above, it's unlikely
* that this could fail, but if it does, just treat plan as not simple. On
* success, save a refcount on the plan in the simple-expression resowner.
*/
if (CachedPlanAllowsSimpleValidityCheck(plansource, cplan,
estate->simple_eval_resowner))
{
/* Remember that we have the refcount */
expr->expr_simple_plansource = plansource;
expr->expr_simple_plan = cplan;
expr->expr_simple_plan_lxid = MyProc->vxid.lxid;
/* Share the remaining work with the replan code path */
exec_save_simple_expr(expr, cplan);
}
/*
* Release the plan refcount obtained by SPI_plan_get_cached_plan. (This
* refcount is held by the wrong resowner, so we can't just repurpose it.)
*/
ReleaseCachedPlan(cplan, CurrentResourceOwner);
}
/*
* exec_is_simple_query - precheck a query tree to see if it might be simple
*
* Check the analyzed-and-rewritten form of a query to see if we will be
* able to treat it as a simple expression. It is caller's responsibility
* that the CachedPlanSource be up-to-date.
*/
static bool
exec_is_simple_query(PLpgSQL_expr *expr)
{
List *plansources;
CachedPlanSource *plansource;
Query *query;
/*
* We can only test queries that resulted in exactly one CachedPlanSource.
*/
plansources = SPI_plan_get_plan_sources(expr->plan);
if (list_length(plansources) != 1)
return false;
plansource = (CachedPlanSource *) linitial(plansources);
/*
* 1. There must be one single querytree.
*/
if (list_length(plansource->query_list) != 1)
return false;
query = (Query *) linitial(plansource->query_list);
/*
* 2. It must be a plain SELECT query without any input tables.
*/
if (!IsA(query, Query))
return false;
if (query->commandType != CMD_SELECT)
return false;
if (query->rtable != NIL)
return false;
/*
* 3. Can't have any subplans, aggregates, qual clauses either. (These
* tests should generally match what inline_function() checks before
* inlining a SQL function; otherwise, inlining could change our
* conclusion about whether an expression is simple, which we don't want.)
*/
if (query->hasAggs ||
query->hasWindowFuncs ||
query->hasTargetSRFs ||
query->hasSubLinks ||
query->cteList ||
query->jointree->fromlist ||
query->jointree->quals ||
query->groupClause ||
query->groupingSets ||
query->havingQual ||
query->windowClause ||
query->distinctClause ||
query->sortClause ||
query->limitOffset ||
query->limitCount ||
query->setOperations)
return false;
/*
* 4. The query must have a single attribute as result.
*/
if (list_length(query->targetList) != 1)
return false;
/*
* OK, we can treat it as a simple plan.
*/
return true;
}
/*
* Given the checks that exec_simple_check_plan did, none of the Asserts
* here should ever fail.
*/
/* Extract the single PlannedStmt */
Assert(list_length(cplan->stmt_list) == 1);
stmt = linitial_node(PlannedStmt, cplan->stmt_list);
Assert(stmt->commandType == CMD_SELECT);
/*
* Ordinarily, the plan node should be a simple Result. However, if
* debug_parallel_query is on, the planner might've stuck a Gather node
* atop that; and/or if this plan is for a scrollable cursor, the planner
* might've stuck a Material node atop it. The simplest way to deal with
* this is to look through the Gather and/or Material nodes. The upper
* node's tlist would normally contain a Var referencing the child node's
* output ... but setrefs.c might also have copied a Const as-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;
}
else if (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));
}
/*
* Save the simple expression, and initialize state to "not valid in
* current transaction".
*/
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);
}
/*
* exec_check_rw_parameter --- can we pass expanded object as read/write param?
*
* There are two separate cases in which we can optimize an update to a
* variable that has a read/write expanded value by letting the called
* expression operate directly on the expanded value. In both cases we
* are considering assignments like "var := array_append(var, foo)" where
* the assignment target is also an input to the RHS expression.
*
* Case 1 (RWOPT_TRANSFER rule): if the variable is "local" in the sense that
* its declaration is not outside any BEGIN...EXCEPTION block surrounding the
* assignment, then we do not need to worry about preserving its value if the
* RHS expression throws an error. If in addition the variable is referenced
* exactly once in the RHS expression, then we can optimize by converting the
* read/write expanded value into a transient value within the expression
* evaluation context, and then setting the variable's recorded value to NULL
* to prevent double-free attempts. This works regardless of any other
* details of the RHS expression. If the expression eventually returns that
* same expanded object (possibly modified) then the variable will re-acquire
* ownership; while if it returns something else or throws an error, the
* expanded object will be discarded as part of cleanup of the evaluation
* context.
*
* Case 2 (RWOPT_INPLACE rule): if we have a non-local assignment or if
* it looks like "var := array_append(var, var[1])" with multiple references
* to the target variable, then we can't use case 1. Nonetheless, if the
* top-level function is trusted not to corrupt its argument in case of an
* error, then when the var has an expanded object as value, it is safe to
* pass the value as a read/write pointer to the top-level function and let
* the function modify the value in-place. (Any other references have to be
* passed as read-only pointers as usual.) Only the top-level function has to
* be trusted, since if anything further down fails, the object hasn't been
* modified yet.
*
* This function checks to see if the assignment is optimizable according
* to either rule, and updates expr->expr_rwopt accordingly. In addition,
* it sets expr->expr_rw_param to the address of the Param within the
* expression that can be passed as read/write (there can be only one);
* or to NULL when there is no safe Param.
*
* Note that this mechanism intentionally allows just one Param to emit a
* read/write pointer; in case 2, the expression could contain other Params
* referencing the target variable, but those must be treated as read-only.
*
* Also note that we only apply this optimization within simple expressions.
* There's no point in it for non-simple expressions, because the
* exec_run_select code path will flatten any expanded result anyway.
*/
static void
exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid)
{
Expr *sexpr = expr->expr_simple_expr;
Oid funcid;
List *fargs;
Oid prosupport;
/* Shouldn't be here for non-simple expression */
Assert(sexpr != NULL);
/* Param should match the expression's assignment target, too */
Assert(paramid == expr->target_param + 1);
/*
* If the assignment is to a "local" variable (one whose value won't
* matter anymore if expression evaluation fails), and this Param is the
* only reference to that variable in the expression, then we can
* unconditionally optimize using the "transfer" method.
*/
if (expr->target_is_local)
{
count_param_references_context context;
/* 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;
}
}
/*
* Otherwise, see if we can trust the expression's top-level function to
* apply the "inplace" method.
*
* Top level of expression must be a simple FuncExpr, OpExpr, or
* SubscriptingRef, else we can't identify which function is relevant. But
* it's okay to look through any RelabelType above that, since that can't
* fail.
*/
if (IsA(sexpr, RelabelType))
sexpr = ((RelabelType *) sexpr)->arg;
if (IsA(sexpr, FuncExpr))
{
FuncExpr *fexpr = (FuncExpr *) sexpr;
/*
* We assume that only the refexpr and refassgnexpr (if any) are
* relevant to the support function's decision. If that turns out to
* be a bad idea, we could incorporate the subscript expressions into
* the fargs list somehow.
*/
fargs = list_make2(sbsref->refexpr, sbsref->refassgnexpr);
}
else
return;
/*
* The top-level function must be one that can handle in-place update
* safely. We allow functions to declare their ability to do that via a
* support function request.
*/
prosupport = get_func_support(funcid);
if (OidIsValid(prosupport))
{
SupportRequestModifyInPlace req;
Param *param;
if (param == NULL)
return; /* support function fails */
/* Verify support function followed the API */
Assert(IsA(param, Param));
Assert(param->paramkind == PARAM_EXTERN);
Assert(param->paramid == paramid);
/* Found the Param we want to pass as read/write */
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
expr->expr_rw_param = param;
return;
}
}
/*
* Count Params referencing the specified paramid, and return one of them
* if there are any.
*
* We actually only need to distinguish 0, 1, and N references; so we can
* abort the tree traversal as soon as we've found two.
*/
static bool
count_param_references(Node *node, count_param_references_context *context)
{
if (node == NULL)
return false;
else if (IsA(node, Param))
{
Param *param = (Param *) node;
/*
* exec_check_assignable --- is it OK to assign to the indicated datum?
*
* This should match pl_gram.y's check_assignable().
*/
static void
exec_check_assignable(PLpgSQL_execstate *estate, int dno)
{
PLpgSQL_datum *datum;
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;
}
}
/* ----------
* exec_set_found Set the global found variable to true/false
* ----------
*/
static void
exec_set_found(PLpgSQL_execstate *estate, bool state)
{
PLpgSQL_var *var;
var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
assign_simple_var(estate, var, BoolGetDatum(state), false, false);
}
/* *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.547Bemerkung:
(vorverarbeitet am 2026-10-11)
¤
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.