/*------------------------------------------------------------------------- * * spi.c * Server Programming Interface * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * src/backend/executor/spi.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/htup_details.h" #include "access/printtup.h" #include "access/sysattr.h" #include "access/xact.h" #include "catalog/heap.h" #include "catalog/pg_type.h" #include "commands/trigger.h" #include "executor/executor.h" #include "executor/spi_priv.h" #include "miscadmin.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/typcache.h" /* * These global variables are part of the API for various SPI functions * (a horrible API choice, but it's too late now). To reduce the risk of * interference between different SPI callers, we save and restore them * when entering/exiting a SPI nesting level. */ uint64 SPI_processed = 0; SPITupleTable *SPI_tuptable = NULL; int SPI_result = 0; static _SPI_connection *_SPI_stack = NULL; static _SPI_connection *_SPI_current = NULL; static int _SPI_stack_depth = 0; /* allocated size of _SPI_stack */ static int _SPI_connected = -1; /* current stack index */ typedef struct SPICallbackArg { const char *query; RawParseMode mode; } SPICallbackArg; static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamListInfo paramLI, bool read_only); static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan); static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan); static int _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, Snapshot snapshot, Snapshot crosscheck_snapshot, bool fire_triggers); static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, Datum *Values, const char *Nulls); static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount); static void _SPI_error_callback(void *arg); static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long count, DestReceiver *dest); static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan); static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan); static int _SPI_begin_call(bool use_exec); static int _SPI_end_call(bool use_exec); static MemoryContext _SPI_execmem(void); static MemoryContext _SPI_procmem(void); static bool _SPI_checktuples(void); /* =================== interface functions =================== */ int SPI_connect(void) { return SPI_connect_ext(0); } int SPI_connect_ext(int options) { int newdepth; /* Enlarge stack if necessary */ if (_SPI_stack == NULL) { if (_SPI_connected != -1 || _SPI_stack_depth != 0) elog(ERROR, "SPI stack corrupted"); newdepth = 16; _SPI_stack = (_SPI_connection *) MemoryContextAlloc(TopMemoryContext, newdepth * sizeof(_SPI_connection)); _SPI_stack_depth = newdepth; } else { if (_SPI_stack_depth <= 0 || _SPI_stack_depth <= _SPI_connected) elog(ERROR, "SPI stack corrupted"); if (_SPI_stack_depth == _SPI_connected + 1) { newdepth = _SPI_stack_depth * 2; _SPI_stack = (_SPI_connection *) repalloc(_SPI_stack, newdepth * sizeof(_SPI_connection)); _SPI_stack_depth = newdepth; } } /* Enter new stack level */ _SPI_connected++; Assert(_SPI_connected >= 0 && _SPI_connected < _SPI_stack_depth); _SPI_current = &(_SPI_stack[_SPI_connected]); _SPI_current->processed = 0; _SPI_current->tuptable = NULL; _SPI_current->execSubid = InvalidSubTransactionId; slist_init(&_SPI_current->tuptables); _SPI_current->procCxt = NULL; /* in case we fail to create 'em */ _SPI_current->execCxt = NULL; _SPI_current->connectSubid = GetCurrentSubTransactionId(); _SPI_current->queryEnv = NULL; _SPI_current->atomic = (options & SPI_OPT_NONATOMIC ? false : true); _SPI_current->internal_xact = false; _SPI_current->outer_processed = SPI_processed; _SPI_current->outer_tuptable = SPI_tuptable; _SPI_current->outer_result = SPI_result; /* * Create memory contexts for this procedure * * In atomic contexts (the normal case), we use TopTransactionContext, * otherwise PortalContext, so that it lives across transaction * boundaries. * * XXX It could be better to use PortalContext as the parent context in * all cases, but we may not be inside a portal (consider deferred-trigger * execution). Perhaps CurTransactionContext could be an option? For now * it doesn't matter because we clean up explicitly in AtEOSubXact_SPI(); * but see also AtEOXact_SPI(). */ _SPI_current->procCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : PortalContext, "SPI Proc", ALLOCSET_DEFAULT_SIZES); _SPI_current->execCxt = AllocSetContextCreate(_SPI_current->atomic ? TopTransactionContext : _SPI_current->procCxt, "SPI Exec", ALLOCSET_DEFAULT_SIZES); /* ... and switch to procedure's context */ _SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt); /* * Reset API global variables so that current caller cannot accidentally * depend on state of an outer caller. */ SPI_processed = 0; SPI_tuptable = NULL; SPI_result = 0; return SPI_OK_CONNECT; } int SPI_finish(void) { int res; res = _SPI_begin_call(false); /* just check we're connected */ if (res < 0) return res; /* Restore memory context as it was before procedure call */ MemoryContextSwitchTo(_SPI_current->savedcxt); /* Release memory used in procedure call (including tuptables) */ MemoryContextDelete(_SPI_current->execCxt); _SPI_current->execCxt = NULL; MemoryContextDelete(_SPI_current->procCxt); _SPI_current->procCxt = NULL; /* * Restore outer API variables, especially SPI_tuptable which is probably * pointing at a just-deleted tuptable */ SPI_processed = _SPI_current->outer_processed; SPI_tuptable = _SPI_current->outer_tuptable; SPI_result = _SPI_current->outer_result; /* Exit stack level */ _SPI_connected--; if (_SPI_connected < 0) _SPI_current = NULL; else _SPI_current = &(_SPI_stack[_SPI_connected]); return SPI_OK_FINISH; } /* * SPI_start_transaction is a no-op, kept for backwards compatibility. * SPI callers are *always* inside a transaction. */ void SPI_start_transaction(void) { } static void _SPI_commit(bool chain) { MemoryContext oldcontext = CurrentMemoryContext; SavedTransactionCharacteristics savetc; /* * Complain if we are in a context that doesn't permit transaction * termination. (Note: here and _SPI_rollback should be the only places * that throw ERRCODE_INVALID_TRANSACTION_TERMINATION, so that callers can * test for that with security that they know what happened.) */ if (_SPI_current->atomic) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("invalid transaction termination"))); /* * This restriction is required by PLs implemented on top of SPI. They * use subtransactions to establish exception blocks that are supposed to * be rolled back together if there is an error. Terminating the * top-level transaction in such a block violates that idea. A future PL * implementation might have different ideas about this, in which case * this restriction would have to be refined or the check possibly be * moved out of SPI into the PLs. Note however that the code below relies * on not being within a subtransaction. */ if (IsSubTransaction()) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("cannot commit while a subtransaction is active"))); if (chain) SaveTransactionCharacteristics(&savetc); /* Catch any error occurring during the COMMIT */ PG_TRY(); { /* Protect current SPI stack entry against deletion */ _SPI_current->internal_xact = true; /* * Hold any pinned portals that any PLs might be using. We have to do * this before changing transaction state, since this will run * user-defined code that might throw an error. */ HoldPinnedPortals(); /* Release snapshots associated with portals */ ForgetPortalSnapshots(); /* Do the deed */ CommitTransactionCommand(); /* Immediately start a new transaction */ StartTransactionCommand(); if (chain) RestoreTransactionCharacteristics(&savetc); MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; } PG_CATCH(); { ErrorData *edata; /* Save error info in caller's context */ MemoryContextSwitchTo(oldcontext); edata = CopyErrorData(); FlushErrorState(); /* * Abort the failed transaction. If this fails too, we'll just * propagate the error out ... there's not that much we can do. */ AbortCurrentTransaction(); /* ... and start a new one */ StartTransactionCommand(); if (chain) RestoreTransactionCharacteristics(&savetc); MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; /* Now that we've cleaned up the transaction, re-throw the error */ ReThrowError(edata); } PG_END_TRY(); } void SPI_commit(void) { _SPI_commit(false); } void SPI_commit_and_chain(void) { _SPI_commit(true); } static void _SPI_rollback(bool chain) { MemoryContext oldcontext = CurrentMemoryContext; SavedTransactionCharacteristics savetc; /* see under SPI_commit() */ if (_SPI_current->atomic) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("invalid transaction termination"))); /* see under SPI_commit() */ if (IsSubTransaction()) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), errmsg("cannot roll back while a subtransaction is active"))); if (chain) SaveTransactionCharacteristics(&savetc); /* Catch any error occurring during the ROLLBACK */ PG_TRY(); { /* Protect current SPI stack entry against deletion */ _SPI_current->internal_xact = true; /* * Hold any pinned portals that any PLs might be using. We have to do * this before changing transaction state, since this will run * user-defined code that might throw an error, and in any case * couldn't be run in an already-aborted transaction. */ HoldPinnedPortals(); /* Release snapshots associated with portals */ ForgetPortalSnapshots(); /* Do the deed */ AbortCurrentTransaction(); /* Immediately start a new transaction */ StartTransactionCommand(); if (chain) RestoreTransactionCharacteristics(&savetc); MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; } PG_CATCH(); { ErrorData *edata; /* Save error info in caller's context */ MemoryContextSwitchTo(oldcontext); edata = CopyErrorData(); FlushErrorState(); /* * Try again to abort the failed transaction. If this fails too, * we'll just propagate the error out ... there's not that much we can * do. */ AbortCurrentTransaction(); /* ... and start a new one */ StartTransactionCommand(); if (chain) RestoreTransactionCharacteristics(&savetc); MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; /* Now that we've cleaned up the transaction, re-throw the error */ ReThrowError(edata); } PG_END_TRY(); } void SPI_rollback(void) { _SPI_rollback(false); } void SPI_rollback_and_chain(void) { _SPI_rollback(true); } /* * Clean up SPI state at transaction commit or abort. */ void AtEOXact_SPI(bool isCommit) { bool found = false; /* * Pop stack entries, stopping if we find one marked internal_xact (that * one belongs to the caller of SPI_commit or SPI_rollback). */ while (_SPI_connected >= 0) { _SPI_connection *connection = &(_SPI_stack[_SPI_connected]); if (connection->internal_xact) break; found = true; /* * We need not release the procedure's memory contexts explicitly, as * they'll go away automatically when their parent context does; see * notes in SPI_connect_ext. */ /* * Restore outer global variables and pop the stack entry. Unlike * SPI_finish(), we don't risk switching to memory contexts that might * be already gone. */ SPI_processed = connection->outer_processed; SPI_tuptable = connection->outer_tuptable; SPI_result = connection->outer_result; _SPI_connected--; if (_SPI_connected < 0) _SPI_current = NULL; else _SPI_current = &(_SPI_stack[_SPI_connected]); } /* We should only find entries to pop during an ABORT. */ if (found && isCommit) ereport(WARNING, (errcode(ERRCODE_WARNING), errmsg("transaction left non-empty SPI stack"), errhint("Check for missing \"SPI_finish\" calls."))); } /* * Clean up SPI state at subtransaction commit or abort. * * During commit, there shouldn't be any unclosed entries remaining from * the current subtransaction; we emit a warning if any are found. */ void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid) { bool found = false; while (_SPI_connected >= 0) { _SPI_connection *connection = &(_SPI_stack[_SPI_connected]); if (connection->connectSubid != mySubid) break; /* couldn't be any underneath it either */ if (connection->internal_xact) break; found = true; /* * Release procedure memory explicitly (see note in SPI_connect) */ if (connection->execCxt) { MemoryContextDelete(connection->execCxt); connection->execCxt = NULL; } if (connection->procCxt) { MemoryContextDelete(connection->procCxt); connection->procCxt = NULL; } /* * Restore outer global variables and pop the stack entry. Unlike * SPI_finish(), we don't risk switching to memory contexts that might * be already gone. */ SPI_processed = connection->outer_processed; SPI_tuptable = connection->outer_tuptable; SPI_result = connection->outer_result; _SPI_connected--; if (_SPI_connected < 0) _SPI_current = NULL; else _SPI_current = &(_SPI_stack[_SPI_connected]); } if (found && isCommit) ereport(WARNING, (errcode(ERRCODE_WARNING), errmsg("subtransaction left non-empty SPI stack"), errhint("Check for missing \"SPI_finish\" calls."))); /* * If we are aborting a subtransaction and there is an open SPI context * surrounding the subxact, clean up to prevent memory leakage. */ if (_SPI_current && !isCommit) { slist_mutable_iter siter; /* * Throw away executor state if current executor operation was started * within current subxact (essentially, force a _SPI_end_call(true)). */ if (_SPI_current->execSubid >= mySubid) { _SPI_current->execSubid = InvalidSubTransactionId; MemoryContextResetAndDeleteChildren(_SPI_current->execCxt); } /* throw away any tuple tables created within current subxact */ slist_foreach_modify(siter, &_SPI_current->tuptables) { SPITupleTable *tuptable; tuptable = slist_container(SPITupleTable, next, siter.cur); if (tuptable->subid >= mySubid) { /* * If we used SPI_freetuptable() here, its internal search of * the tuptables list would make this operation O(N^2). * Instead, just free the tuptable manually. This should * match what SPI_freetuptable() does. */ slist_delete_current(&siter); if (tuptable == _SPI_current->tuptable) _SPI_current->tuptable = NULL; if (tuptable == SPI_tuptable) SPI_tuptable = NULL; MemoryContextDelete(tuptable->tuptabcxt); } } } } /* * Are we executing inside a procedure (that is, a nonatomic SPI context)? */ bool SPI_inside_nonatomic_context(void) { if (_SPI_current == NULL) return false; /* not in any SPI context at all */ if (_SPI_current->atomic) return false; /* it's atomic (ie function not procedure) */ return true; } /* Parse, plan, and execute a query string */ int SPI_execute(const char *src, bool read_only, long tcount) { _SPI_plan plan; SPIExecuteOptions options; int res; if (src == NULL || tcount < 0) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(true); if (res < 0) return res; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = CURSOR_OPT_PARALLEL_OK; _SPI_prepare_oneshot_plan(src, &plan); memset(&options, 0, sizeof(options)); options.read_only = read_only; options.tcount = tcount; res = _SPI_execute_plan(&plan, &options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } /* Obsolete version of SPI_execute */ int SPI_exec(const char *src, long tcount) { return SPI_execute(src, false, tcount); } /* Parse, plan, and execute a query string, with extensible options */ int SPI_execute_extended(const char *src, const SPIExecuteOptions *options) { int res; _SPI_plan plan; if (src == NULL || options == NULL) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(true); if (res < 0) return res; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = CURSOR_OPT_PARALLEL_OK; if (options->params) { plan.parserSetup = options->params->parserSetup; plan.parserSetupArg = options->params->parserSetupArg; } _SPI_prepare_oneshot_plan(src, &plan); res = _SPI_execute_plan(&plan, options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } /* Execute a previously prepared plan */ int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only, long tcount) { SPIExecuteOptions options; int res; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) return SPI_ERROR_ARGUMENT; if (plan->nargs > 0 && Values == NULL) return SPI_ERROR_PARAM; res = _SPI_begin_call(true); if (res < 0) return res; memset(&options, 0, sizeof(options)); options.params = _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls); options.read_only = read_only; options.tcount = tcount; res = _SPI_execute_plan(plan, &options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } /* Obsolete version of SPI_execute_plan */ int SPI_execp(SPIPlanPtr plan, Datum *Values, const char *Nulls, long tcount) { return SPI_execute_plan(plan, Values, Nulls, false, tcount); } /* Execute a previously prepared plan */ int SPI_execute_plan_extended(SPIPlanPtr plan, const SPIExecuteOptions *options) { int res; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || options == NULL) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(true); if (res < 0) return res; res = _SPI_execute_plan(plan, options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } /* Execute a previously prepared plan */ int SPI_execute_plan_with_paramlist(SPIPlanPtr plan, ParamListInfo params, bool read_only, long tcount) { SPIExecuteOptions options; int res; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(true); if (res < 0) return res; memset(&options, 0, sizeof(options)); options.params = params; options.read_only = read_only; options.tcount = tcount; res = _SPI_execute_plan(plan, &options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } /* * SPI_execute_snapshot -- identical to SPI_execute_plan, except that we allow * the caller to specify exactly which snapshots to use, which will be * registered here. Also, the caller may specify that AFTER triggers should be * queued as part of the outer query rather than being fired immediately at the * end of the command. * * This is currently not documented in spi.sgml because it is only intended * for use by RI triggers. * * Passing snapshot == InvalidSnapshot will select the normal behavior of * fetching a new snapshot for each query. */ int SPI_execute_snapshot(SPIPlanPtr plan, Datum *Values, const char *Nulls, Snapshot snapshot, Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount) { SPIExecuteOptions options; int res; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) return SPI_ERROR_ARGUMENT; if (plan->nargs > 0 && Values == NULL) return SPI_ERROR_PARAM; res = _SPI_begin_call(true); if (res < 0) return res; memset(&options, 0, sizeof(options)); options.params = _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls); options.read_only = read_only; options.tcount = tcount; res = _SPI_execute_plan(plan, &options, snapshot, crosscheck_snapshot, fire_triggers); _SPI_end_call(true); return res; } /* * SPI_execute_with_args -- plan and execute a query with supplied arguments * * This is functionally equivalent to SPI_prepare followed by * SPI_execute_plan. */ int SPI_execute_with_args(const char *src, int nargs, Oid *argtypes, Datum *Values, const char *Nulls, bool read_only, long tcount) { int res; _SPI_plan plan; ParamListInfo paramLI; SPIExecuteOptions options; if (src == NULL || nargs < 0 || tcount < 0) return SPI_ERROR_ARGUMENT; if (nargs > 0 && (argtypes == NULL || Values == NULL)) return SPI_ERROR_PARAM; res = _SPI_begin_call(true); if (res < 0) return res; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = CURSOR_OPT_PARALLEL_OK; plan.nargs = nargs; plan.argtypes = argtypes; plan.parserSetup = NULL; plan.parserSetupArg = NULL; paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); _SPI_prepare_oneshot_plan(src, &plan); memset(&options, 0, sizeof(options)); options.params = paramLI; options.read_only = read_only; options.tcount = tcount; res = _SPI_execute_plan(&plan, &options, InvalidSnapshot, InvalidSnapshot, true); _SPI_end_call(true); return res; } SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes) { return SPI_prepare_cursor(src, nargs, argtypes, 0); } SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, int cursorOptions) { _SPI_plan plan; SPIPlanPtr result; if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL)) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } SPI_result = _SPI_begin_call(true); if (SPI_result < 0) return NULL; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = cursorOptions; plan.nargs = nargs; plan.argtypes = argtypes; plan.parserSetup = NULL; plan.parserSetupArg = NULL; _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); return result; } SPIPlanPtr SPI_prepare_extended(const char *src, const SPIPrepareOptions *options) { _SPI_plan plan; SPIPlanPtr result; if (src == NULL || options == NULL) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } SPI_result = _SPI_begin_call(true); if (SPI_result < 0) return NULL; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = options->parseMode; plan.cursor_options = options->cursorOptions; plan.nargs = 0; plan.argtypes = NULL; plan.parserSetup = options->parserSetup; plan.parserSetupArg = options->parserSetupArg; _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); return result; } SPIPlanPtr SPI_prepare_params(const char *src, ParserSetupHook parserSetup, void *parserSetupArg, int cursorOptions) { _SPI_plan plan; SPIPlanPtr result; if (src == NULL) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } SPI_result = _SPI_begin_call(true); if (SPI_result < 0) return NULL; memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = cursorOptions; plan.nargs = 0; plan.argtypes = NULL; plan.parserSetup = parserSetup; plan.parserSetupArg = parserSetupArg; _SPI_prepare_plan(src, &plan); /* copy plan to procedure context */ result = _SPI_make_plan_non_temp(&plan); _SPI_end_call(true); return result; } int SPI_keepplan(SPIPlanPtr plan) { ListCell *lc; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved || plan->oneshot) return SPI_ERROR_ARGUMENT; /* * Mark it saved, reparent it under CacheMemoryContext, and mark all the * component CachedPlanSources as saved. This sequence cannot fail * partway through, so there's no risk of long-term memory leakage. */ plan->saved = true; MemoryContextSetParent(plan->plancxt, CacheMemoryContext); foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); SaveCachedPlan(plansource); } return 0; } SPIPlanPtr SPI_saveplan(SPIPlanPtr plan) { SPIPlanPtr newplan; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } SPI_result = _SPI_begin_call(false); /* don't change context */ if (SPI_result < 0) return NULL; newplan = _SPI_save_plan(plan); SPI_result = _SPI_end_call(false); return newplan; } int SPI_freeplan(SPIPlanPtr plan) { ListCell *lc; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) return SPI_ERROR_ARGUMENT; /* Release the plancache entries */ foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); DropCachedPlan(plansource); } /* Now get rid of the _SPI_plan and subsidiary data in its plancxt */ MemoryContextDelete(plan->plancxt); return 0; } HeapTuple SPI_copytuple(HeapTuple tuple) { MemoryContext oldcxt; HeapTuple ctuple; if (tuple == NULL) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } if (_SPI_current == NULL) { SPI_result = SPI_ERROR_UNCONNECTED; return NULL; } oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); ctuple = heap_copytuple(tuple); MemoryContextSwitchTo(oldcxt); return ctuple; } HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc) { MemoryContext oldcxt; HeapTupleHeader dtup; if (tuple == NULL || tupdesc == NULL) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } if (_SPI_current == NULL) { SPI_result = SPI_ERROR_UNCONNECTED; return NULL; } /* For RECORD results, make sure a typmod has been assigned */ if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) assign_record_type_typmod(tupdesc); oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); dtup = DatumGetHeapTupleHeader(heap_copy_tuple_as_datum(tuple, tupdesc)); MemoryContextSwitchTo(oldcxt); return dtup; } HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum, Datum *Values, const char *Nulls) { MemoryContext oldcxt; HeapTuple mtuple; int numberOfAttributes; Datum *v; bool *n; int i; if (rel == NULL || tuple == NULL || natts < 0 || attnum == NULL || Values == NULL) { SPI_result = SPI_ERROR_ARGUMENT; return NULL; } if (_SPI_current == NULL) { SPI_result = SPI_ERROR_UNCONNECTED; return NULL; } oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); SPI_result = 0; numberOfAttributes = rel->rd_att->natts; v = (Datum *) palloc(numberOfAttributes * sizeof(Datum)); n = (bool *) palloc(numberOfAttributes * sizeof(bool)); /* fetch old values and nulls */ heap_deform_tuple(tuple, rel->rd_att, v, n); /* replace values and nulls */ for (i = 0; i < natts; i++) { if (attnum[i] <= 0 || attnum[i] > numberOfAttributes) break; v[attnum[i] - 1] = Values[i]; n[attnum[i] - 1] = (Nulls && Nulls[i] == 'n'); } if (i == natts) /* no errors in *attnum */ { mtuple = heap_form_tuple(rel->rd_att, v, n); /* * copy the identification info of the old tuple: t_ctid, t_self, and * OID (if any) */ mtuple->t_data->t_ctid = tuple->t_data->t_ctid; mtuple->t_self = tuple->t_self; mtuple->t_tableOid = tuple->t_tableOid; } else { mtuple = NULL; SPI_result = SPI_ERROR_NOATTRIBUTE; } pfree(v); pfree(n); MemoryContextSwitchTo(oldcxt); return mtuple; } int SPI_fnumber(TupleDesc tupdesc, const char *fname) { int res; const FormData_pg_attribute *sysatt; for (res = 0; res < tupdesc->natts; res++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, res); if (namestrcmp(&attr->attname, fname) == 0 && !attr->attisdropped) return res + 1; } sysatt = SystemAttributeByName(fname); if (sysatt != NULL) return sysatt->attnum; /* SPI_ERROR_NOATTRIBUTE is different from all sys column numbers */ return SPI_ERROR_NOATTRIBUTE; } char * SPI_fname(TupleDesc tupdesc, int fnumber) { const FormData_pg_attribute *att; SPI_result = 0; if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) { SPI_result = SPI_ERROR_NOATTRIBUTE; return NULL; } if (fnumber > 0) att = TupleDescAttr(tupdesc, fnumber - 1); else att = SystemAttributeDefinition(fnumber); return pstrdup(NameStr(att->attname)); } char * SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber) { Datum val; bool isnull; Oid typoid, foutoid; bool typisvarlena; SPI_result = 0; if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) { SPI_result = SPI_ERROR_NOATTRIBUTE; return NULL; } val = heap_getattr(tuple, fnumber, tupdesc, &isnull); if (isnull) return NULL; if (fnumber > 0) typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid; else typoid = (SystemAttributeDefinition(fnumber))->atttypid; getTypeOutputInfo(typoid, &foutoid, &typisvarlena); return OidOutputFunctionCall(foutoid, val); } Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, bool *isnull) { SPI_result = 0; if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) { SPI_result = SPI_ERROR_NOATTRIBUTE; *isnull = true; return (Datum) NULL; } return heap_getattr(tuple, fnumber, tupdesc, isnull); } char * SPI_gettype(TupleDesc tupdesc, int fnumber) { Oid typoid; HeapTuple typeTuple; char *result; SPI_result = 0; if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) { SPI_result = SPI_ERROR_NOATTRIBUTE; return NULL; } if (fnumber > 0) typoid = TupleDescAttr(tupdesc, fnumber - 1)->atttypid; else typoid = (SystemAttributeDefinition(fnumber))->atttypid; typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typoid)); if (!HeapTupleIsValid(typeTuple)) { SPI_result = SPI_ERROR_TYPUNKNOWN; return NULL; } result = pstrdup(NameStr(((Form_pg_type) GETSTRUCT(typeTuple))->typname)); ReleaseSysCache(typeTuple); return result; } /* * Get the data type OID for a column. * * There's nothing similar for typmod and typcollation. The rare consumers * thereof should inspect the TupleDesc directly. */ Oid SPI_gettypeid(TupleDesc tupdesc, int fnumber) { SPI_result = 0; if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) { SPI_result = SPI_ERROR_NOATTRIBUTE; return InvalidOid; } if (fnumber > 0) return TupleDescAttr(tupdesc, fnumber - 1)->atttypid; else return (SystemAttributeDefinition(fnumber))->atttypid; } char * SPI_getrelname(Relation rel) { return pstrdup(RelationGetRelationName(rel)); } char * SPI_getnspname(Relation rel) { return get_namespace_name(RelationGetNamespace(rel)); } void * SPI_palloc(Size size) { if (_SPI_current == NULL) elog(ERROR, "SPI_palloc called while not connected to SPI"); return MemoryContextAlloc(_SPI_current->savedcxt, size); } void * SPI_repalloc(void *pointer, Size size) { /* No longer need to worry which context chunk was in... */ return repalloc(pointer, size); } void SPI_pfree(void *pointer) { /* No longer need to worry which context chunk was in... */ pfree(pointer); } Datum SPI_datumTransfer(Datum value, bool typByVal, int typLen) { MemoryContext oldcxt; Datum result; if (_SPI_current == NULL) elog(ERROR, "SPI_datumTransfer called while not connected to SPI"); oldcxt = MemoryContextSwitchTo(_SPI_current->savedcxt); result = datumTransfer(value, typByVal, typLen); MemoryContextSwitchTo(oldcxt); return result; } void SPI_freetuple(HeapTuple tuple) { /* No longer need to worry which context tuple was in... */ heap_freetuple(tuple); } void SPI_freetuptable(SPITupleTable *tuptable) { bool found = false; /* ignore call if NULL pointer */ if (tuptable == NULL) return; /* * Search only the topmost SPI context for a matching tuple table. */ if (_SPI_current != NULL) { slist_mutable_iter siter; /* find tuptable in active list, then remove it */ slist_foreach_modify(siter, &_SPI_current->tuptables) { SPITupleTable *tt; tt = slist_container(SPITupleTable, next, siter.cur); if (tt == tuptable) { slist_delete_current(&siter); found = true; break; } } } /* * Refuse the deletion if we didn't find it in the topmost SPI context. * This is primarily a guard against double deletion, but might prevent * other errors as well. Since the worst consequence of not deleting a * tuptable would be a transient memory leak, this is just a WARNING. */ if (!found) { elog(WARNING, "attempt to delete invalid SPITupleTable %p", tuptable); return; } /* for safety, reset global variables that might point at tuptable */ if (tuptable == _SPI_current->tuptable) _SPI_current->tuptable = NULL; if (tuptable == SPI_tuptable) SPI_tuptable = NULL; /* release all memory belonging to tuptable */ MemoryContextDelete(tuptable->tuptabcxt); } /* * SPI_cursor_open() * * Open a prepared SPI plan as a portal */ Portal SPI_cursor_open(const char *name, SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only) { Portal portal; ParamListInfo paramLI; /* build transient ParamListInfo in caller's context */ paramLI = _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls); portal = SPI_cursor_open_internal(name, plan, paramLI, read_only); /* done with the transient ParamListInfo */ if (paramLI) pfree(paramLI); return portal; } /* * SPI_cursor_open_with_args() * * Parse and plan a query and open it as a portal. */ Portal SPI_cursor_open_with_args(const char *name, const char *src, int nargs, Oid *argtypes, Datum *Values, const char *Nulls, bool read_only, int cursorOptions) { Portal result; _SPI_plan plan; ParamListInfo paramLI; if (src == NULL || nargs < 0) elog(ERROR, "SPI_cursor_open_with_args called with invalid arguments"); if (nargs > 0 && (argtypes == NULL || Values == NULL)) elog(ERROR, "SPI_cursor_open_with_args called with missing parameters"); SPI_result = _SPI_begin_call(true); if (SPI_result < 0) elog(ERROR, "SPI_cursor_open_with_args called while not connected"); memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = cursorOptions; plan.nargs = nargs; plan.argtypes = argtypes; plan.parserSetup = NULL; plan.parserSetupArg = NULL; /* build transient ParamListInfo in executor context */ paramLI = _SPI_convert_params(nargs, argtypes, Values, Nulls); _SPI_prepare_plan(src, &plan); /* We needn't copy the plan; SPI_cursor_open_internal will do so */ result = SPI_cursor_open_internal(name, &plan, paramLI, read_only); /* And clean up */ _SPI_end_call(true); return result; } /* * SPI_cursor_open_with_paramlist() * * Same as SPI_cursor_open except that parameters (if any) are passed * as a ParamListInfo, which supports dynamic parameter set determination */ Portal SPI_cursor_open_with_paramlist(const char *name, SPIPlanPtr plan, ParamListInfo params, bool read_only) { return SPI_cursor_open_internal(name, plan, params, read_only); } /* Parse a query and open it as a cursor */ Portal SPI_cursor_parse_open(const char *name, const char *src, const SPIParseOpenOptions *options) { Portal result; _SPI_plan plan; if (src == NULL || options == NULL) elog(ERROR, "SPI_cursor_parse_open called with invalid arguments"); SPI_result = _SPI_begin_call(true); if (SPI_result < 0) elog(ERROR, "SPI_cursor_parse_open called while not connected"); memset(&plan, 0, sizeof(_SPI_plan)); plan.magic = _SPI_PLAN_MAGIC; plan.parse_mode = RAW_PARSE_DEFAULT; plan.cursor_options = options->cursorOptions; if (options->params) { plan.parserSetup = options->params->parserSetup; plan.parserSetupArg = options->params->parserSetupArg; } _SPI_prepare_plan(src, &plan); /* We needn't copy the plan; SPI_cursor_open_internal will do so */ result = SPI_cursor_open_internal(name, &plan, options->params, options->read_only); /* And clean up */ _SPI_end_call(true); return result; } /* * SPI_cursor_open_internal() * * Common code for SPI_cursor_open variants */ static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamListInfo paramLI, bool read_only) { CachedPlanSource *plansource; CachedPlan *cplan; List *stmt_list; char *query_string; Snapshot snapshot; MemoryContext oldcontext; Portal portal; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; /* * Check that the plan is something the Portal code will special-case as * returning one tupleset. */ if (!SPI_is_cursor_plan(plan)) { /* try to give a good error message */ const char *cmdtag; if (list_length(plan->plancache_list) != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("cannot open multi-query plan as cursor"))); plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* A SELECT that fails SPI_is_cursor_plan() must be SELECT INTO */ if (plansource->commandTag == CMDTAG_SELECT) cmdtag = "SELECT INTO"; else cmdtag = GetCommandTagName(plansource->commandTag); ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), /* translator: %s is name of a SQL command, eg INSERT */ errmsg("cannot open %s query as cursor", cmdtag))); } Assert(list_length(plan->plancache_list) == 1); plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* Push the SPI stack */ if (_SPI_begin_call(true) < 0) elog(ERROR, "SPI_cursor_open called while not connected"); /* Reset SPI result (note we deliberately don't touch lastoid) */ SPI_processed = 0; SPI_tuptable = NULL; _SPI_current->processed = 0; _SPI_current->tuptable = NULL; /* Create the portal */ if (name == NULL || name[0] == '\0') { /* Use a random nonconflicting name */ portal = CreateNewPortal(); } else { /* In this path, error if portal of same name already exists */ portal = CreatePortal(name, false, false); } /* Copy the plan's query string into the portal */ query_string = MemoryContextStrdup(portal->portalContext, plansource->query_string); /* * Setup error traceback support for ereport(), in case GetCachedPlan * throws an error. */ spicallbackarg.query = plansource->query_string; spicallbackarg.mode = plan->parse_mode; spierrcontext.callback = _SPI_error_callback; spierrcontext.arg = &spicallbackarg; spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; /* * Note: for a saved plan, we mustn't have any failure occur between * GetCachedPlan and PortalDefineQuery; that would result in leaking our * plancache refcount. */ /* Replan if needed, and increment plan refcount for portal */ cplan = GetCachedPlan(plansource, paramLI, NULL, _SPI_current->queryEnv); stmt_list = cplan->stmt_list; if (!plan->saved) { /* * We don't want the portal to depend on an unsaved CachedPlanSource, * so must copy the plan into the portal's context. An error here * will result in leaking our refcount on the plan, but it doesn't * matter because the plan is unsaved and hence transient anyway. */ oldcontext = MemoryContextSwitchTo(portal->portalContext); stmt_list = copyObject(stmt_list); MemoryContextSwitchTo(oldcontext); ReleaseCachedPlan(cplan, NULL); cplan = NULL; /* portal shouldn't depend on cplan */ } /* * Set up the portal. */ PortalDefineQuery(portal, NULL, /* no statement name */ query_string, plansource->commandTag, stmt_list, cplan); /* * Set up options for portal. Default SCROLL type is chosen the same way * as PerformCursorOpen does it. */ portal->cursorOptions = plan->cursor_options; if (!(portal->cursorOptions & (CURSOR_OPT_SCROLL | CURSOR_OPT_NO_SCROLL))) { if (list_length(stmt_list) == 1 && linitial_node(PlannedStmt, stmt_list)->commandType != CMD_UTILITY && linitial_node(PlannedStmt, stmt_list)->rowMarks == NIL && ExecSupportsBackwardScan(linitial_node(PlannedStmt, stmt_list)->planTree)) portal->cursorOptions |= CURSOR_OPT_SCROLL; else portal->cursorOptions |= CURSOR_OPT_NO_SCROLL; } /* * Disallow SCROLL with SELECT FOR UPDATE. This is not redundant with the * check in transformDeclareCursorStmt because the cursor options might * not have come through there. */ if (portal->cursorOptions & CURSOR_OPT_SCROLL) { if (list_length(stmt_list) == 1 && linitial_node(PlannedStmt, stmt_list)->commandType != CMD_UTILITY && linitial_node(PlannedStmt, stmt_list)->rowMarks != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DECLARE SCROLL CURSOR ... FOR UPDATE/SHARE is not supported"), errdetail("Scrollable cursors must be READ ONLY."))); } /* Make current query environment available to portal at execution time. */ portal->queryEnv = _SPI_current->queryEnv; /* * If told to be read-only, we'd better check for read-only queries. This * can't be done earlier because we need to look at the finished, planned * queries. (In particular, we don't want to do it between GetCachedPlan * and PortalDefineQuery, because throwing an error between those steps * would result in leaking our plancache refcount.) */ if (read_only) { ListCell *lc; foreach(lc, stmt_list) { PlannedStmt *pstmt = lfirst_node(PlannedStmt, lc); if (!CommandIsReadOnly(pstmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", CreateCommandName((Node *) pstmt)))); } } /* Set up the snapshot to use. */ if (read_only) snapshot = GetActiveSnapshot(); else { CommandCounterIncrement(); snapshot = GetTransactionSnapshot(); } /* * If the plan has parameters, copy them into the portal. Note that this * must be done after revalidating the plan, because in dynamic parameter * cases the set of parameters could have changed during re-parsing. */ if (paramLI) { oldcontext = MemoryContextSwitchTo(portal->portalContext); paramLI = copyParamList(paramLI); MemoryContextSwitchTo(oldcontext); } /* * Start portal execution. */ PortalStart(portal, paramLI, 0, snapshot); Assert(portal->strategy != PORTAL_MULTI_QUERY); /* Pop the error context stack */ error_context_stack = spierrcontext.previous; /* Pop the SPI stack */ _SPI_end_call(true); /* Return the created portal */ return portal; } /* * SPI_cursor_find() * * Find the portal of an existing open cursor */ Portal SPI_cursor_find(const char *name) { return GetPortalByName(name); } /* * SPI_cursor_fetch() * * Fetch rows in a cursor */ void SPI_cursor_fetch(Portal portal, bool forward, long count) { _SPI_cursor_operation(portal, forward ? FETCH_FORWARD : FETCH_BACKWARD, count, CreateDestReceiver(DestSPI)); /* we know that the DestSPI receiver doesn't need a destroy call */ } /* * SPI_cursor_move() * * Move in a cursor */ void SPI_cursor_move(Portal portal, bool forward, long count) { _SPI_cursor_operation(portal, forward ? FETCH_FORWARD : FETCH_BACKWARD, count, None_Receiver); } /* * SPI_scroll_cursor_fetch() * * Fetch rows in a scrollable cursor */ void SPI_scroll_cursor_fetch(Portal portal, FetchDirection direction, long count) { _SPI_cursor_operation(portal, direction, count, CreateDestReceiver(DestSPI)); /* we know that the DestSPI receiver doesn't need a destroy call */ } /* * SPI_scroll_cursor_move() * * Move in a scrollable cursor */ void SPI_scroll_cursor_move(Portal portal, FetchDirection direction, long count) { _SPI_cursor_operation(portal, direction, count, None_Receiver); } /* * SPI_cursor_close() * * Close a cursor */ void SPI_cursor_close(Portal portal) { if (!PortalIsValid(portal)) elog(ERROR, "invalid portal in SPI cursor operation"); PortalDrop(portal, false); } /* * Returns the Oid representing the type id for argument at argIndex. First * parameter is at index zero. */ Oid SPI_getargtypeid(SPIPlanPtr plan, int argIndex) { if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || argIndex < 0 || argIndex >= plan->nargs) { SPI_result = SPI_ERROR_ARGUMENT; return InvalidOid; } return plan->argtypes[argIndex]; } /* * Returns the number of arguments for the prepared plan. */ int SPI_getargcount(SPIPlanPtr plan) { if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { SPI_result = SPI_ERROR_ARGUMENT; return -1; } return plan->nargs; } /* * Returns true if the plan contains exactly one command * and that command returns tuples to the caller (eg, SELECT or * INSERT ... RETURNING, but not SELECT ... INTO). In essence, * the result indicates if the command can be used with SPI_cursor_open * * Parameters * plan: A plan previously prepared using SPI_prepare */ bool SPI_is_cursor_plan(SPIPlanPtr plan) { CachedPlanSource *plansource; if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) { SPI_result = SPI_ERROR_ARGUMENT; return false; } if (list_length(plan->plancache_list) != 1) { SPI_result = 0; return false; /* not exactly 1 pre-rewrite command */ } plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* * We used to force revalidation of the cached plan here, but that seems * unnecessary: invalidation could mean a change in the rowtype of the * tuples returned by a plan, but not whether it returns tuples at all. */ SPI_result = 0; /* Does it return tuples? */ if (plansource->resultDesc) return true; return false; } /* * SPI_plan_is_valid --- test whether a SPI plan is currently valid * (that is, not marked as being in need of revalidation). * * See notes for CachedPlanIsValid before using this. */ bool SPI_plan_is_valid(SPIPlanPtr plan) { ListCell *lc; Assert(plan->magic == _SPI_PLAN_MAGIC); foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); if (!CachedPlanIsValid(plansource)) return false; } return true; } /* * SPI_result_code_string --- convert any SPI return code to a string * * This is often useful in error messages. Most callers will probably * only pass negative (error-case) codes, but for generality we recognize * the success codes too. */ const char * SPI_result_code_string(int code) { static char buf[64]; switch (code) { case SPI_ERROR_CONNECT: return "SPI_ERROR_CONNECT"; case SPI_ERROR_COPY: return "SPI_ERROR_COPY"; case SPI_ERROR_OPUNKNOWN: return "SPI_ERROR_OPUNKNOWN"; case SPI_ERROR_UNCONNECTED: return "SPI_ERROR_UNCONNECTED"; case SPI_ERROR_ARGUMENT: return "SPI_ERROR_ARGUMENT"; case SPI_ERROR_PARAM: return "SPI_ERROR_PARAM"; case SPI_ERROR_TRANSACTION: return "SPI_ERROR_TRANSACTION"; case SPI_ERROR_NOATTRIBUTE: return "SPI_ERROR_NOATTRIBUTE"; case SPI_ERROR_NOOUTFUNC: return "SPI_ERROR_NOOUTFUNC"; case SPI_ERROR_TYPUNKNOWN: return "SPI_ERROR_TYPUNKNOWN"; case SPI_ERROR_REL_DUPLICATE: return "SPI_ERROR_REL_DUPLICATE"; case SPI_ERROR_REL_NOT_FOUND: return "SPI_ERROR_REL_NOT_FOUND"; case SPI_OK_CONNECT: return "SPI_OK_CONNECT"; case SPI_OK_FINISH: return "SPI_OK_FINISH"; case SPI_OK_FETCH: return "SPI_OK_FETCH"; case SPI_OK_UTILITY: return "SPI_OK_UTILITY"; case SPI_OK_SELECT: return "SPI_OK_SELECT"; case SPI_OK_SELINTO: return "SPI_OK_SELINTO"; case SPI_OK_INSERT: return "SPI_OK_INSERT"; case SPI_OK_DELETE: return "SPI_OK_DELETE"; case SPI_OK_UPDATE: return "SPI_OK_UPDATE"; case SPI_OK_CURSOR: return "SPI_OK_CURSOR"; case SPI_OK_INSERT_RETURNING: return "SPI_OK_INSERT_RETURNING"; case SPI_OK_DELETE_RETURNING: return "SPI_OK_DELETE_RETURNING"; case SPI_OK_UPDATE_RETURNING: return "SPI_OK_UPDATE_RETURNING"; case SPI_OK_REWRITTEN: return "SPI_OK_REWRITTEN"; case SPI_OK_REL_REGISTER: return "SPI_OK_REL_REGISTER"; case SPI_OK_REL_UNREGISTER: return "SPI_OK_REL_UNREGISTER"; case SPI_OK_TD_REGISTER: return "SPI_OK_TD_REGISTER"; case SPI_OK_MERGE: return "SPI_OK_MERGE"; } /* Unrecognized code ... return something useful ... */ sprintf(buf, "Unrecognized SPI code %d", code); return buf; } /* * SPI_plan_get_plan_sources --- get a SPI plan's underlying list of * CachedPlanSources. * * This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL * look directly into the SPIPlan for itself). It's not documented in * spi.sgml because we'd just as soon not have too many places using this. */ List * SPI_plan_get_plan_sources(SPIPlanPtr plan) { Assert(plan->magic == _SPI_PLAN_MAGIC); return plan->plancache_list; } /* * SPI_plan_get_cached_plan --- get a SPI plan's generic CachedPlan, * if the SPI plan contains exactly one CachedPlanSource. If not, * return NULL. * * The plan's refcount is incremented (and logged in CurrentResourceOwner, * if it's a saved plan). Caller is responsible for doing ReleaseCachedPlan. * * This is exported so that PL/pgSQL can use it (this beats letting PL/pgSQL * look directly into the SPIPlan for itself). It's not documented in * spi.sgml because we'd just as soon not have too many places using this. */ CachedPlan * SPI_plan_get_cached_plan(SPIPlanPtr plan) { CachedPlanSource *plansource; CachedPlan *cplan; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; Assert(plan->magic == _SPI_PLAN_MAGIC); /* Can't support one-shot plans here */ if (plan->oneshot) return NULL; /* Must have exactly one CachedPlanSource */ if (list_length(plan->plancache_list) != 1) return NULL; plansource = (CachedPlanSource *) linitial(plan->plancache_list); /* Setup error traceback support for ereport() */ spicallbackarg.query = plansource->query_string; spicallbackarg.mode = plan->parse_mode; spierrcontext.callback = _SPI_error_callback; spierrcontext.arg = &spicallbackarg; spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; /* Get the generic plan for the query */ cplan = GetCachedPlan(plansource, NULL, plan->saved ? CurrentResourceOwner : NULL, _SPI_current->queryEnv); Assert(cplan == plansource->gplan); /* Pop the error context stack */ error_context_stack = spierrcontext.previous; return cplan; } /* =================== private functions =================== */ /* * spi_dest_startup * Initialize to receive tuples from Executor into SPITupleTable * of current SPI procedure */ void spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo) { SPITupleTable *tuptable; MemoryContext oldcxt; MemoryContext tuptabcxt; if (_SPI_current == NULL) elog(ERROR, "spi_dest_startup called while not connected to SPI"); if (_SPI_current->tuptable != NULL) elog(ERROR, "improper call to spi_dest_startup"); /* We create the tuple table context as a child of procCxt */ oldcxt = _SPI_procmem(); /* switch to procedure memory context */ tuptabcxt = AllocSetContextCreate(CurrentMemoryContext, "SPI TupTable", ALLOCSET_DEFAULT_SIZES); MemoryContextSwitchTo(tuptabcxt); _SPI_current->tuptable = tuptable = (SPITupleTable *) palloc0(sizeof(SPITupleTable)); tuptable->tuptabcxt = tuptabcxt; tuptable->subid = GetCurrentSubTransactionId(); /* * The tuptable is now valid enough to be freed by AtEOSubXact_SPI, so put * it onto the SPI context's tuptables list. This will ensure it's not * leaked even in the unlikely event the following few lines fail. */ slist_push_head(&_SPI_current->tuptables, &tuptable->next); /* set up initial allocations */ tuptable->alloced = 128; tuptable->vals = (HeapTuple *) palloc(tuptable->alloced * sizeof(HeapTuple)); tuptable->numvals = 0; tuptable->tupdesc = CreateTupleDescCopy(typeinfo); MemoryContextSwitchTo(oldcxt); } /* * spi_printtup * store tuple retrieved by Executor into SPITupleTable * of current SPI procedure */ bool spi_printtup(TupleTableSlot *slot, DestReceiver *self) { SPITupleTable *tuptable; MemoryContext oldcxt; if (_SPI_current == NULL) elog(ERROR, "spi_printtup called while not connected to SPI"); tuptable = _SPI_current->tuptable; if (tuptable == NULL) elog(ERROR, "improper call to spi_printtup"); oldcxt = MemoryContextSwitchTo(tuptable->tuptabcxt); if (tuptable->numvals >= tuptable->alloced) { /* Double the size of the pointer array */ uint64 newalloced = tuptable->alloced * 2; tuptable->vals = (HeapTuple *) repalloc_huge(tuptable->vals, newalloced * sizeof(HeapTuple)); tuptable->alloced = newalloced; } tuptable->vals[tuptable->numvals] = ExecCopySlotHeapTuple(slot); (tuptable->numvals)++; MemoryContextSwitchTo(oldcxt); return true; } /* * Static functions */ /* * Parse and analyze a querystring. * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup * and plan->parserSetupArg) must be valid, as must plan->parse_mode and * plan->cursor_options. * * Results are stored into *plan (specifically, plan->plancache_list). * Note that the result data is all in CurrentMemoryContext or child contexts * thereof; in practice this means it is in the SPI executor context, and * what we are creating is a "temporary" SPIPlan. Cruft generated during * parsing is also left in CurrentMemoryContext. */ static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan) { List *raw_parsetree_list; List *plancache_list; ListCell *list_item; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; /* * Setup error traceback support for ereport() */ spicallbackarg.query = src; spicallbackarg.mode = plan->parse_mode; spierrcontext.callback = _SPI_error_callback; spierrcontext.arg = &spicallbackarg; spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; /* * Parse the request string into a list of raw parse trees. */ raw_parsetree_list = raw_parser(src, plan->parse_mode); /* * Do parse analysis and rule rewrite for each raw parsetree, storing the * results into unsaved plancache entries. */ plancache_list = NIL; foreach(list_item, raw_parsetree_list) { RawStmt *parsetree = lfirst_node(RawStmt, list_item); List *stmt_list; CachedPlanSource *plansource; /* * Create the CachedPlanSource before we do parse analysis, since it * needs to see the unmodified raw parse tree. */ plansource = CreateCachedPlan(parsetree, src, CreateCommandTag(parsetree->stmt)); /* * Parameter datatypes are driven by parserSetup hook if provided, * otherwise we use the fixed parameter list. */ if (plan->parserSetup != NULL) { Assert(plan->nargs == 0); stmt_list = pg_analyze_and_rewrite_withcb(parsetree, src, plan->parserSetup, plan->parserSetupArg, _SPI_current->queryEnv); } else { stmt_list = pg_analyze_and_rewrite_fixedparams(parsetree, src, plan->argtypes, plan->nargs, _SPI_current->queryEnv); } /* Finish filling in the CachedPlanSource */ CompleteCachedPlan(plansource, stmt_list, NULL, plan->argtypes, plan->nargs, plan->parserSetup, plan->parserSetupArg, plan->cursor_options, false); /* not fixed result */ plancache_list = lappend(plancache_list, plansource); } plan->plancache_list = plancache_list; plan->oneshot = false; /* * Pop the error context stack */ error_context_stack = spierrcontext.previous; } /* * Parse, but don't analyze, a querystring. * * This is a stripped-down version of _SPI_prepare_plan that only does the * initial raw parsing. It creates "one shot" CachedPlanSources * that still require parse analysis before execution is possible. * * The advantage of using the "one shot" form of CachedPlanSource is that * we eliminate data copying and invalidation overhead. Postponing parse * analysis also prevents issues if some of the raw parsetrees are DDL * commands that affect validity of later parsetrees. Both of these * attributes are good things for SPI_execute() and similar cases. * * Results are stored into *plan (specifically, plan->plancache_list). * Note that the result data is all in CurrentMemoryContext or child contexts * thereof; in practice this means it is in the SPI executor context, and * what we are creating is a "temporary" SPIPlan. Cruft generated during * parsing is also left in CurrentMemoryContext. */ static void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan) { List *raw_parsetree_list; List *plancache_list; ListCell *list_item; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; /* * Setup error traceback support for ereport() */ spicallbackarg.query = src; spicallbackarg.mode = plan->parse_mode; spierrcontext.callback = _SPI_error_callback; spierrcontext.arg = &spicallbackarg; spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; /* * Parse the request string into a list of raw parse trees. */ raw_parsetree_list = raw_parser(src, plan->parse_mode); /* * Construct plancache entries, but don't do parse analysis yet. */ plancache_list = NIL; foreach(list_item, raw_parsetree_list) { RawStmt *parsetree = lfirst_node(RawStmt, list_item); CachedPlanSource *plansource; plansource = CreateOneShotCachedPlan(parsetree, src, CreateCommandTag(parsetree->stmt)); plancache_list = lappend(plancache_list, plansource); } plan->plancache_list = plancache_list; plan->oneshot = true; /* * Pop the error context stack */ error_context_stack = spierrcontext.previous; } /* * _SPI_execute_plan: execute the given plan with the given options * * options contains options accessible from outside SPI: * params: parameter values to pass to query * read_only: true for read-only execution (no CommandCounterIncrement) * allow_nonatomic: true to allow nonatomic CALL/DO execution * must_return_tuples: throw error if query doesn't return tuples * tcount: execution tuple-count limit, or 0 for none * dest: DestReceiver to receive output, or NULL for normal SPI output * owner: ResourceOwner that will be used to hold refcount on plan; * if NULL, CurrentResourceOwner is used (ignored for non-saved plan) * * Additional, only-internally-accessible options: * snapshot: query snapshot to use, or InvalidSnapshot for the normal * behavior of taking a new snapshot for each query. * crosscheck_snapshot: for RI use, all others pass InvalidSnapshot * fire_triggers: true to fire AFTER triggers at end of query (normal case); * false means any AFTER triggers are postponed to end of outer query */ static int _SPI_execute_plan(SPIPlanPtr plan, const SPIExecuteOptions *options, Snapshot snapshot, Snapshot crosscheck_snapshot, bool fire_triggers) { int my_res = 0; uint64 my_processed = 0; SPITupleTable *my_tuptable = NULL; int res = 0; bool pushed_active_snap = false; ResourceOwner plan_owner = options->owner; SPICallbackArg spicallbackarg; ErrorContextCallback spierrcontext; CachedPlan *cplan = NULL; ListCell *lc1; /* * Setup error traceback support for ereport() */ spicallbackarg.query = NULL; /* we'll fill this below */ spicallbackarg.mode = plan->parse_mode; spierrcontext.callback = _SPI_error_callback; spierrcontext.arg = &spicallbackarg; spierrcontext.previous = error_context_stack; error_context_stack = &spierrcontext; /* * We support four distinct snapshot management behaviors: * * snapshot != InvalidSnapshot, read_only = true: use exactly the given * snapshot. * * snapshot != InvalidSnapshot, read_only = false: use the given snapshot, * modified by advancing its command ID before each querytree. * * snapshot == InvalidSnapshot, read_only = true: use the entry-time * ActiveSnapshot, if any (if there isn't one, we run with no snapshot). * * snapshot == InvalidSnapshot, read_only = false: take a full new * snapshot for each user command, and advance its command ID before each * querytree within the command. * * In the first two cases, we can just push the snap onto the stack once * for the whole plan list. * * Note that snapshot != InvalidSnapshot implies an atomic execution * context. */ if (snapshot != InvalidSnapshot) { Assert(!options->allow_nonatomic); if (options->read_only) { PushActiveSnapshot(snapshot); pushed_active_snap = true; } else { /* Make sure we have a private copy of the snapshot to modify */ PushCopiedSnapshot(snapshot); pushed_active_snap = true; } } /* * Ensure that we have a resource owner if plan is saved, and not if it * isn't. */ if (!plan->saved) plan_owner = NULL; else if (plan_owner == NULL) plan_owner = CurrentResourceOwner; /* * We interpret must_return_tuples as "there must be at least one query, * and all of them must return tuples". This is a bit laxer than * SPI_is_cursor_plan's check, but there seems no reason to enforce that * there be only one query. */ if (options->must_return_tuples && plan->plancache_list == NIL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("empty query does not return tuples"))); foreach(lc1, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); List *stmt_list; ListCell *lc2; spicallbackarg.query = plansource->query_string; /* * If this is a one-shot plan, we still need to do parse analysis. */ if (plan->oneshot) { RawStmt *parsetree = plansource->raw_parse_tree; const char *src = plansource->query_string; List *querytree_list; /* * Parameter datatypes are driven by parserSetup hook if provided, * otherwise we use the fixed parameter list. */ if (parsetree == NULL) querytree_list = NIL; else if (plan->parserSetup != NULL) { Assert(plan->nargs == 0); querytree_list = pg_analyze_and_rewrite_withcb(parsetree, src, plan->parserSetup, plan->parserSetupArg, _SPI_current->queryEnv); } else { querytree_list = pg_analyze_and_rewrite_fixedparams(parsetree, src, plan->argtypes, plan->nargs, _SPI_current->queryEnv); } /* Finish filling in the CachedPlanSource */ CompleteCachedPlan(plansource, querytree_list, NULL, plan->argtypes, plan->nargs, plan->parserSetup, plan->parserSetupArg, plan->cursor_options, false); /* not fixed result */ } /* * If asked to, complain when query does not return tuples. * (Replanning can't change this, so we can check it before that. * However, we can't check it till after parse analysis, so in the * case of a one-shot plan this is the earliest we could check.) */ if (options->must_return_tuples && !plansource->resultDesc) { /* try to give a good error message */ const char *cmdtag; /* A SELECT without resultDesc must be SELECT INTO */ if (plansource->commandTag == CMDTAG_SELECT) cmdtag = "SELECT INTO"; else cmdtag = GetCommandTagName(plansource->commandTag); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), /* translator: %s is name of a SQL command, eg INSERT */ errmsg("%s query does not return tuples", cmdtag))); } /* * Replan if needed, and increment plan refcount. If it's a saved * plan, the refcount must be backed by the plan_owner. */ cplan = GetCachedPlan(plansource, options->params, plan_owner, _SPI_current->queryEnv); stmt_list = cplan->stmt_list; /* * If we weren't given a specific snapshot to use, and the statement * list requires a snapshot, set that up. */ if (snapshot == InvalidSnapshot && (list_length(stmt_list) > 1 || (list_length(stmt_list) == 1 && PlannedStmtRequiresSnapshot(linitial_node(PlannedStmt, stmt_list))))) { /* * First, ensure there's a Portal-level snapshot. This back-fills * the snapshot stack in case the previous operation was a COMMIT * or ROLLBACK inside a procedure or DO block. (We can't put back * the Portal snapshot any sooner, or we'd break cases like doing * SET or LOCK just after COMMIT.) It's enough to check once per * statement list, since COMMIT/ROLLBACK/CALL/DO can't appear * within a multi-statement list. */ EnsurePortalSnapshotExists(); /* * In the default non-read-only case, get a new per-statement-list * snapshot, replacing any that we pushed in a previous cycle. * Skip it when doing non-atomic execution, though (we rely * entirely on the Portal snapshot in that case). */ if (!options->read_only && !options->allow_nonatomic) { if (pushed_active_snap) PopActiveSnapshot(); PushActiveSnapshot(GetTransactionSnapshot()); pushed_active_snap = true; } } foreach(lc2, stmt_list) { PlannedStmt *stmt = lfirst_node(PlannedStmt, lc2); bool canSetTag = stmt->canSetTag; DestReceiver *dest; /* * Reset output state. (Note that if a non-SPI receiver is used, * _SPI_current->processed will stay zero, and that's what we'll * report to the caller. It's the receiver's job to count tuples * in that case.) */ _SPI_current->processed = 0; _SPI_current->tuptable = NULL; /* Check for unsupported cases. */ if (stmt->utilityStmt) { if (IsA(stmt->utilityStmt, CopyStmt)) { CopyStmt *cstmt = (CopyStmt *) stmt->utilityStmt; if (cstmt->filename == NULL) { my_res = SPI_ERROR_COPY; goto fail; } } else if (IsA(stmt->utilityStmt, TransactionStmt)) { my_res = SPI_ERROR_TRANSACTION; goto fail; } } if (options->read_only && !CommandIsReadOnly(stmt)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s is a SQL statement name */ errmsg("%s is not allowed in a non-volatile function", CreateCommandName((Node *) stmt)))); /* * If not read-only mode, advance the command counter before each * command and update the snapshot. (But skip it if the snapshot * isn't under our control.) */ if (!options->read_only && pushed_active_snap) { CommandCounterIncrement(); UpdateActiveSnapshotCommandId(); } /* * Select appropriate tuple receiver. Output from non-canSetTag * subqueries always goes to the bit bucket. */ if (!canSetTag) dest = CreateDestReceiver(DestNone); else if (options->dest) dest = options->dest; else dest = CreateDestReceiver(DestSPI); if (stmt->utilityStmt == NULL) { QueryDesc *qdesc; Snapshot snap; if (ActiveSnapshotSet()) snap = GetActiveSnapshot(); else snap = InvalidSnapshot; qdesc = CreateQueryDesc(stmt, plansource->query_string, snap, crosscheck_snapshot, dest, options->params, _SPI_current->queryEnv, 0); res = _SPI_pquery(qdesc, fire_triggers, canSetTag ? options->tcount : 0); FreeQueryDesc(qdesc); } else { ProcessUtilityContext context; QueryCompletion qc; /* * If the SPI context is atomic, or we were not told to allow * nonatomic operations, tell ProcessUtility this is an atomic * execution context. */ if (_SPI_current->atomic || !options->allow_nonatomic) context = PROCESS_UTILITY_QUERY; else context = PROCESS_UTILITY_QUERY_NONATOMIC; InitializeQueryCompletion(&qc); ProcessUtility(stmt, plansource->query_string, true, /* protect plancache's node tree */ context, options->params, _SPI_current->queryEnv, dest, &qc); /* Update "processed" if stmt returned tuples */ if (_SPI_current->tuptable) _SPI_current->processed = _SPI_current->tuptable->numvals; res = SPI_OK_UTILITY; /* * Some utility statements return a row count, even though the * tuples are not returned to the caller. */ if (IsA(stmt->utilityStmt, CreateTableAsStmt)) { CreateTableAsStmt *ctastmt = (CreateTableAsStmt *) stmt->utilityStmt; if (qc.commandTag == CMDTAG_SELECT) _SPI_current->processed = qc.nprocessed; else { /* * Must be an IF NOT EXISTS that did nothing, or a * CREATE ... WITH NO DATA. */ Assert(ctastmt->if_not_exists || ctastmt->into->skipData); _SPI_current->processed = 0; } /* * For historical reasons, if CREATE TABLE AS was spelled * as SELECT INTO, return a special return code. */ if (ctastmt->is_select_into) res = SPI_OK_SELINTO; } else if (IsA(stmt->utilityStmt, CopyStmt)) { Assert(qc.commandTag == CMDTAG_COPY); _SPI_current->processed = qc.nprocessed; } } /* * The last canSetTag query sets the status values returned to the * caller. Be careful to free any tuptables not returned, to * avoid intra-transaction memory leak. */ if (canSetTag) { my_processed = _SPI_current->processed; SPI_freetuptable(my_tuptable); my_tuptable = _SPI_current->tuptable; my_res = res; } else { SPI_freetuptable(_SPI_current->tuptable); _SPI_current->tuptable = NULL; } /* * We don't issue a destroy call to the receiver. The SPI and * None receivers would ignore it anyway, while if the caller * supplied a receiver, it's not our job to destroy it. */ if (res < 0) { my_res = res; goto fail; } } /* Done with this plan, so release refcount */ ReleaseCachedPlan(cplan, plan_owner); cplan = NULL; /* * If not read-only mode, advance the command counter after the last * command. This ensures that its effects are visible, in case it was * DDL that would affect the next CachedPlanSource. */ if (!options->read_only) CommandCounterIncrement(); } fail: /* Pop the snapshot off the stack if we pushed one */ if (pushed_active_snap) PopActiveSnapshot(); /* We no longer need the cached plan refcount, if any */ if (cplan) ReleaseCachedPlan(cplan, plan_owner); /* * Pop the error context stack */ error_context_stack = spierrcontext.previous; /* Save results for caller */ SPI_processed = my_processed; SPI_tuptable = my_tuptable; /* tuptable now is caller's responsibility, not SPI's */ _SPI_current->tuptable = NULL; /* * If none of the queries had canSetTag, return SPI_OK_REWRITTEN. Prior to * 8.4, we used return the last query's result code, but not its auxiliary * results, but that's confusing. */ if (my_res == 0) my_res = SPI_OK_REWRITTEN; return my_res; } /* * Convert arrays of query parameters to form wanted by planner and executor */ static ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, Datum *Values, const char *Nulls) { ParamListInfo paramLI; if (nargs > 0) { paramLI = makeParamList(nargs); for (int i = 0; i < nargs; i++) { ParamExternData *prm = ¶mLI->params[i]; prm->value = Values[i]; prm->isnull = (Nulls && Nulls[i] == 'n'); prm->pflags = PARAM_FLAG_CONST; prm->ptype = argtypes[i]; } } else paramLI = NULL; return paramLI; } static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) { int operation = queryDesc->operation; int eflags; int res; switch (operation) { case CMD_SELECT: if (queryDesc->dest->mydest == DestNone) { /* Don't return SPI_OK_SELECT if we're discarding result */ res = SPI_OK_UTILITY; } else res = SPI_OK_SELECT; break; case CMD_INSERT: if (queryDesc->plannedstmt->hasReturning) res = SPI_OK_INSERT_RETURNING; else res = SPI_OK_INSERT; break; case CMD_DELETE: if (queryDesc->plannedstmt->hasReturning) res = SPI_OK_DELETE_RETURNING; else res = SPI_OK_DELETE; break; case CMD_UPDATE: if (queryDesc->plannedstmt->hasReturning) res = SPI_OK_UPDATE_RETURNING; else res = SPI_OK_UPDATE; break; case CMD_MERGE: res = SPI_OK_MERGE; break; default: return SPI_ERROR_OPUNKNOWN; } #ifdef SPI_EXECUTOR_STATS if (ShowExecutorStats) ResetUsage(); #endif /* Select execution options */ if (fire_triggers) eflags = 0; /* default run-to-completion flags */ else eflags = EXEC_FLAG_SKIP_TRIGGERS; ExecutorStart(queryDesc, eflags); ExecutorRun(queryDesc, ForwardScanDirection, tcount, true); _SPI_current->processed = queryDesc->estate->es_processed; if ((res == SPI_OK_SELECT || queryDesc->plannedstmt->hasReturning) && queryDesc->dest->mydest == DestSPI) { if (_SPI_checktuples()) elog(ERROR, "consistency check on SPI tuple count failed"); } ExecutorFinish(queryDesc); ExecutorEnd(queryDesc); /* FreeQueryDesc is done by the caller */ #ifdef SPI_EXECUTOR_STATS if (ShowExecutorStats) ShowUsage("SPI EXECUTOR STATS"); #endif return res; } /* * _SPI_error_callback * * Add context information when a query invoked via SPI fails */ static void _SPI_error_callback(void *arg) { SPICallbackArg *carg = (SPICallbackArg *) arg; const char *query = carg->query; int syntaxerrposition; if (query == NULL) /* in case arg wasn't set yet */ return; /* * If there is a syntax error position, convert to internal syntax error; * otherwise treat the query as an item of context stack */ syntaxerrposition = geterrposition(); if (syntaxerrposition > 0) { errposition(0); internalerrposition(syntaxerrposition); internalerrquery(query); } else { /* Use the parse mode to decide how to describe the query */ switch (carg->mode) { case RAW_PARSE_PLPGSQL_EXPR: errcontext("SQL expression \"%s\"", query); break; case RAW_PARSE_PLPGSQL_ASSIGN1: case RAW_PARSE_PLPGSQL_ASSIGN2: case RAW_PARSE_PLPGSQL_ASSIGN3: errcontext("PL/pgSQL assignment \"%s\"", query); break; default: errcontext("SQL statement \"%s\"", query); break; } } } /* * _SPI_cursor_operation() * * Do a FETCH or MOVE in a cursor */ static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long count, DestReceiver *dest) { uint64 nfetched; /* Check that the portal is valid */ if (!PortalIsValid(portal)) elog(ERROR, "invalid portal in SPI cursor operation"); /* Push the SPI stack */ if (_SPI_begin_call(true) < 0) elog(ERROR, "SPI cursor operation called while not connected"); /* Reset the SPI result (note we deliberately don't touch lastoid) */ SPI_processed = 0; SPI_tuptable = NULL; _SPI_current->processed = 0; _SPI_current->tuptable = NULL; /* Run the cursor */ nfetched = PortalRunFetch(portal, direction, count, dest); /* * Think not to combine this store with the preceding function call. If * the portal contains calls to functions that use SPI, then _SPI_stack is * likely to move around while the portal runs. When control returns, * _SPI_current will point to the correct stack entry... but the pointer * may be different than it was beforehand. So we must be sure to re-fetch * the pointer after the function call completes. */ _SPI_current->processed = nfetched; if (dest->mydest == DestSPI && _SPI_checktuples()) elog(ERROR, "consistency check on SPI tuple count failed"); /* Put the result into place for access by caller */ SPI_processed = _SPI_current->processed; SPI_tuptable = _SPI_current->tuptable; /* tuptable now is caller's responsibility, not SPI's */ _SPI_current->tuptable = NULL; /* Pop the SPI stack */ _SPI_end_call(true); } static MemoryContext _SPI_execmem(void) { return MemoryContextSwitchTo(_SPI_current->execCxt); } static MemoryContext _SPI_procmem(void) { return MemoryContextSwitchTo(_SPI_current->procCxt); } /* * _SPI_begin_call: begin a SPI operation within a connected procedure * * use_exec is true if we intend to make use of the procedure's execCxt * during this SPI operation. We'll switch into that context, and arrange * for it to be cleaned up at _SPI_end_call or if an error occurs. */ static int _SPI_begin_call(bool use_exec) { if (_SPI_current == NULL) return SPI_ERROR_UNCONNECTED; if (use_exec) { /* remember when the Executor operation started */ _SPI_current->execSubid = GetCurrentSubTransactionId(); /* switch to the Executor memory context */ _SPI_execmem(); } return 0; } /* * _SPI_end_call: end a SPI operation within a connected procedure * * use_exec must be the same as in the previous _SPI_begin_call * * Note: this currently has no failure return cases, so callers don't check */ static int _SPI_end_call(bool use_exec) { if (use_exec) { /* switch to the procedure memory context */ _SPI_procmem(); /* mark Executor context no longer in use */ _SPI_current->execSubid = InvalidSubTransactionId; /* and free Executor memory */ MemoryContextResetAndDeleteChildren(_SPI_current->execCxt); } return 0; } static bool _SPI_checktuples(void) { uint64 processed = _SPI_current->processed; SPITupleTable *tuptable = _SPI_current->tuptable; bool failed = false; if (tuptable == NULL) /* spi_dest_startup was not called */ failed = true; else if (processed != tuptable->numvals) failed = true; return failed; } /* * Convert a "temporary" SPIPlan into an "unsaved" plan. * * The passed _SPI_plan struct is on the stack, and all its subsidiary data * is in or under the current SPI executor context. Copy the plan into the * SPI procedure context so it will survive _SPI_end_call(). To minimize * data copying, this destructively modifies the input plan, by taking the * plancache entries away from it and reparenting them to the new SPIPlan. */ static SPIPlanPtr _SPI_make_plan_non_temp(SPIPlanPtr plan) { SPIPlanPtr newplan; MemoryContext parentcxt = _SPI_current->procCxt; MemoryContext plancxt; MemoryContext oldcxt; ListCell *lc; /* Assert the input is a temporary SPIPlan */ Assert(plan->magic == _SPI_PLAN_MAGIC); Assert(plan->plancxt == NULL); /* One-shot plans can't be saved */ Assert(!plan->oneshot); /* * Create a memory context for the plan, underneath the procedure context. * We don't expect the plan to be very large. */ plancxt = AllocSetContextCreate(parentcxt, "SPI Plan", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(plancxt); /* Copy the _SPI_plan struct and subsidiary data into the new context */ newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->plancxt = plancxt; newplan->parse_mode = plan->parse_mode; newplan->cursor_options = plan->cursor_options; newplan->nargs = plan->nargs; if (plan->nargs > 0) { newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid)); memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid)); } else newplan->argtypes = NULL; newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; /* * Reparent all the CachedPlanSources into the procedure context. In * theory this could fail partway through due to the pallocs, but we don't * care too much since both the procedure context and the executor context * would go away on error. */ foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); CachedPlanSetParentContext(plansource, parentcxt); /* Build new list, with list cells in plancxt */ newplan->plancache_list = lappend(newplan->plancache_list, plansource); } MemoryContextSwitchTo(oldcxt); /* For safety, unlink the CachedPlanSources from the temporary plan */ plan->plancache_list = NIL; return newplan; } /* * Make a "saved" copy of the given plan. */ static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan) { SPIPlanPtr newplan; MemoryContext plancxt; MemoryContext oldcxt; ListCell *lc; /* One-shot plans can't be saved */ Assert(!plan->oneshot); /* * Create a memory context for the plan. We don't expect the plan to be * very large, so use smaller-than-default alloc parameters. It's a * transient context until we finish copying everything. */ plancxt = AllocSetContextCreate(CurrentMemoryContext, "SPI Plan", ALLOCSET_SMALL_SIZES); oldcxt = MemoryContextSwitchTo(plancxt); /* Copy the SPI plan into its own context */ newplan = (SPIPlanPtr) palloc0(sizeof(_SPI_plan)); newplan->magic = _SPI_PLAN_MAGIC; newplan->plancxt = plancxt; newplan->parse_mode = plan->parse_mode; newplan->cursor_options = plan->cursor_options; newplan->nargs = plan->nargs; if (plan->nargs > 0) { newplan->argtypes = (Oid *) palloc(plan->nargs * sizeof(Oid)); memcpy(newplan->argtypes, plan->argtypes, plan->nargs * sizeof(Oid)); } else newplan->argtypes = NULL; newplan->parserSetup = plan->parserSetup; newplan->parserSetupArg = plan->parserSetupArg; /* Copy all the plancache entries */ foreach(lc, plan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); CachedPlanSource *newsource; newsource = CopyCachedPlan(plansource); newplan->plancache_list = lappend(newplan->plancache_list, newsource); } MemoryContextSwitchTo(oldcxt); /* * Mark it saved, reparent it under CacheMemoryContext, and mark all the * component CachedPlanSources as saved. This sequence cannot fail * partway through, so there's no risk of long-term memory leakage. */ newplan->saved = true; MemoryContextSetParent(newplan->plancxt, CacheMemoryContext); foreach(lc, newplan->plancache_list) { CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc); SaveCachedPlan(plansource); } return newplan; } /* * Internal lookup of ephemeral named relation by name. */ static EphemeralNamedRelation _SPI_find_ENR_by_name(const char *name) { /* internal static function; any error is bug in SPI itself */ Assert(name != NULL); /* fast exit if no tuplestores have been added */ if (_SPI_current->queryEnv == NULL) return NULL; return get_ENR(_SPI_current->queryEnv, name); } /* * Register an ephemeral named relation for use by the planner and executor on * subsequent calls using this SPI connection. */ int SPI_register_relation(EphemeralNamedRelation enr) { EphemeralNamedRelation match; int res; if (enr == NULL || enr->md.name == NULL) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(false); /* keep current memory context */ if (res < 0) return res; match = _SPI_find_ENR_by_name(enr->md.name); if (match) res = SPI_ERROR_REL_DUPLICATE; else { if (_SPI_current->queryEnv == NULL) _SPI_current->queryEnv = create_queryEnv(); register_ENR(_SPI_current->queryEnv, enr); res = SPI_OK_REL_REGISTER; } _SPI_end_call(false); return res; } /* * Unregister an ephemeral named relation by name. This will probably be a * rarely used function, since SPI_finish will clear it automatically. */ int SPI_unregister_relation(const char *name) { EphemeralNamedRelation match; int res; if (name == NULL) return SPI_ERROR_ARGUMENT; res = _SPI_begin_call(false); /* keep current memory context */ if (res < 0) return res; match = _SPI_find_ENR_by_name(name); if (match) { unregister_ENR(_SPI_current->queryEnv, match->md.name); res = SPI_OK_REL_UNREGISTER; } else res = SPI_ERROR_REL_NOT_FOUND; _SPI_end_call(false); return res; } /* * Register the transient relations from 'tdata' using this SPI connection. * This should be called by PL implementations' trigger handlers after * connecting, in order to make transition tables visible to any queries run * in this connection. */ int SPI_register_trigger_data(TriggerData *tdata) { if (tdata == NULL) return SPI_ERROR_ARGUMENT; if (tdata->tg_newtable) { EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData)); int rc; enr->md.name = tdata->tg_trigger->tgnewtable; enr->md.reliddesc = tdata->tg_relation->rd_id; enr->md.tupdesc = NULL; enr->md.enrtype = ENR_NAMED_TUPLESTORE; enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_newtable); enr->reldata = tdata->tg_newtable; rc = SPI_register_relation(enr); if (rc != SPI_OK_REL_REGISTER) return rc; } if (tdata->tg_oldtable) { EphemeralNamedRelation enr = palloc(sizeof(EphemeralNamedRelationData)); int rc; enr->md.name = tdata->tg_trigger->tgoldtable; enr->md.reliddesc = tdata->tg_relation->rd_id; enr->md.tupdesc = NULL; enr->md.enrtype = ENR_NAMED_TUPLESTORE; enr->md.enrtuples = tuplestore_tuple_count(tdata->tg_oldtable); enr->reldata = tdata->tg_oldtable; rc = SPI_register_relation(enr); if (rc != SPI_OK_REL_REGISTER) return rc; } return SPI_OK_TD_REGISTER; }