3894 lines
131 KiB
C++
3894 lines
131 KiB
C++
/* -------------------------------------------------------------------------
|
|
*
|
|
* spi.cpp
|
|
* Server Programming Interface
|
|
*
|
|
* Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
|
|
* Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
* Portions Copyright (c) 2021, openGauss Contributors
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/runtime/executor/spi.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/hash.h"
|
|
#include "access/printtup.h"
|
|
#include "access/sysattr.h"
|
|
#include "access/tableam.h"
|
|
#include "access/xact.h"
|
|
#include "catalog/heap.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/prepare.h"
|
|
#include "commands/trigger.h"
|
|
#include "executor/executor.h"
|
|
#include "executor/spi_priv.h"
|
|
#include "miscadmin.h"
|
|
#include "parser/parser.h"
|
|
#include "pgxc/pgxc.h"
|
|
#include "tcop/autonomoustransaction.h"
|
|
#include "tcop/pquery.h"
|
|
#include "tcop/utility.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/datum.h"
|
|
#include "utils/dynahash.h"
|
|
#include "utils/globalplancore.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/rel.h"
|
|
#include "utils/rel_gs.h"
|
|
#include "utils/snapmgr.h"
|
|
#include "utils/syscache.h"
|
|
#include "utils/typcache.h"
|
|
#include "utils/elog.h"
|
|
#include "commands/sqladvisor.h"
|
|
#include "distributelayer/streamMain.h"
|
|
#include "replication/libpqsw.h"
|
|
|
|
#ifdef ENABLE_MOT
|
|
#include "storage/mot/jit_exec.h"
|
|
#endif
|
|
|
|
THR_LOCAL uint32 SPI_processed = 0;
|
|
THR_LOCAL SPITupleTable *SPI_tuptable = NULL;
|
|
THR_LOCAL int SPI_result;
|
|
|
|
static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, ParamListInfo paramLI, bool read_only,
|
|
bool isCollectParam = false);
|
|
|
|
void _SPI_prepare_plan(const char *src, SPIPlanPtr plan);
|
|
|
|
#ifdef ENABLE_MOT
|
|
static bool _SPI_prepare_plan_guarded(const char *src, SPIPlanPtr plan, parse_query_func parser);
|
|
#endif
|
|
|
|
#ifdef PGXC
|
|
static void _SPI_pgxc_prepare_plan(const char *src, List *src_parsetree, SPIPlanPtr plan, parse_query_func parser);
|
|
#endif
|
|
|
|
void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan);
|
|
|
|
int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot,
|
|
bool read_only, bool fire_triggers, long tcount, bool from_lock);
|
|
|
|
ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, Datum *Values, const char *Nulls,
|
|
Cursor_Data *cursor_data);
|
|
|
|
static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount, bool from_lock = false);
|
|
|
|
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 MemoryContext _SPI_execmem(void);
|
|
static MemoryContext _SPI_procmem(void);
|
|
static bool _SPI_checktuples(void);
|
|
extern void ClearVacuumStmt(VacuumStmt *stmt);
|
|
static void CopySPI_Plan(SPIPlanPtr newplan, SPIPlanPtr plan, MemoryContext plancxt);
|
|
|
|
static void pipelined_readonly_ereport()
|
|
{
|
|
if (u_sess->plsql_cxt.is_pipelined && !u_sess->plsql_cxt.is_exec_autonomous) {
|
|
ereport(ERROR, (errmodule(MOD_PLSQL), errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot perform a DML operation inside a query"),
|
|
errcause("DML operation like insert, update, delete or select-for-update cannot "
|
|
"be performed inside a query."),
|
|
erraction("Ensure that the offending DML operation is not performed or use an "
|
|
"autonomous transaction to perform the DML operation within the query.")));
|
|
}
|
|
}
|
|
|
|
/* =================== interface functions =================== */
|
|
int SPI_connect(CommandDest dest, void (*spiCallbackfn)(void *), void *clientData)
|
|
{
|
|
return SPI_connect_ext(dest, spiCallbackfn, clientData, 0);
|
|
}
|
|
|
|
int SPI_connect_ext(CommandDest dest, void (*spiCallbackfn)(void *), void *clientData, int options, Oid func_oid)
|
|
{
|
|
int new_depth;
|
|
/*
|
|
* When procedure called by Executor u_sess->SPI_cxt._curid expected to be equal to
|
|
* u_sess->SPI_cxt._connected
|
|
*/
|
|
if (u_sess->SPI_cxt._curid != u_sess->SPI_cxt._connected) {
|
|
return SPI_ERROR_CONNECT;
|
|
}
|
|
|
|
bool atomic = ((options & SPI_OPT_NONATOMIC) ? false : true);
|
|
if (u_sess->SPI_cxt._stack == NULL) {
|
|
if (u_sess->SPI_cxt._connected != -1 || u_sess->SPI_cxt._stack_depth != 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("SPI stack corrupted when connect SPI, %s",
|
|
(u_sess->SPI_cxt._connected != -1) ? "init level is not -1." : "stack depth is not zero.")));
|
|
}
|
|
new_depth = 16;
|
|
u_sess->SPI_cxt._stack = (_SPI_connection*)MemoryContextAlloc(
|
|
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER), new_depth * sizeof(_SPI_connection));
|
|
u_sess->SPI_cxt._stack_depth = new_depth;
|
|
} else {
|
|
if (u_sess->SPI_cxt._stack_depth <= 0 || u_sess->SPI_cxt._stack_depth <= u_sess->SPI_cxt._connected) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("SPI stack corrupted when connect SPI, stack depth %d", u_sess->SPI_cxt._stack_depth)));
|
|
}
|
|
if (u_sess->SPI_cxt._stack_depth == u_sess->SPI_cxt._connected + 1) {
|
|
new_depth = u_sess->SPI_cxt._stack_depth * 2;
|
|
u_sess->SPI_cxt._stack =
|
|
(_SPI_connection *)repalloc(u_sess->SPI_cxt._stack, new_depth * sizeof(_SPI_connection));
|
|
u_sess->SPI_cxt._stack_depth = new_depth;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We're entering procedure where u_sess->SPI_cxt._curid == u_sess->SPI_cxt._connected - 1
|
|
*/
|
|
u_sess->SPI_cxt._connected++;
|
|
Assert(u_sess->SPI_cxt._connected >= 0 && u_sess->SPI_cxt._connected < u_sess->SPI_cxt._stack_depth);
|
|
|
|
u_sess->SPI_cxt._current = &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._connected]);
|
|
u_sess->SPI_cxt._current->processed = 0;
|
|
u_sess->SPI_cxt._current->lastoid = InvalidOid;
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
u_sess->SPI_cxt._current->procCxt = NULL; /* in case we fail to create 'em */
|
|
u_sess->SPI_cxt._current->execCxt = NULL;
|
|
u_sess->SPI_cxt._current->connectSubid = GetCurrentSubTransactionId();
|
|
u_sess->SPI_cxt._current->dest = dest;
|
|
u_sess->SPI_cxt._current->spiCallback = (void (*)(void *))spiCallbackfn;
|
|
u_sess->SPI_cxt._current->clientData = clientData;
|
|
u_sess->SPI_cxt._current->func_oid = func_oid;
|
|
u_sess->SPI_cxt._current->spi_hash_key = INVALID_SPI_KEY;
|
|
u_sess->SPI_cxt._current->visit_id = (uint32)-1;
|
|
u_sess->SPI_cxt._current->plan_id = -1;
|
|
if (u_sess->attr.attr_sql.sql_compatibility == A_FORMAT) {
|
|
u_sess->SPI_cxt._current->stmtTimestamp = t_thrd.time_cxt.stmt_system_timestamp;
|
|
} else {
|
|
u_sess->SPI_cxt._current->stmtTimestamp = -1;
|
|
}
|
|
|
|
u_sess->SPI_cxt._current->atomic = atomic;
|
|
u_sess->SPI_cxt._current->internal_xact = false;
|
|
|
|
if (u_sess->SPI_cxt._connected == 0) {
|
|
/* may leak by last time, better clean it */
|
|
DestoryAutonomousSession(true);
|
|
}
|
|
|
|
/*
|
|
* 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().
|
|
*/
|
|
u_sess->SPI_cxt._current->procCxt = AllocSetContextCreate(
|
|
u_sess->SPI_cxt._current->atomic ? u_sess->top_transaction_mem_cxt : t_thrd.mem_cxt.portal_mem_cxt,
|
|
"SPI Proc", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
|
u_sess->SPI_cxt._current->execCxt = AllocSetContextCreate(
|
|
u_sess->SPI_cxt._current->atomic ? u_sess->top_transaction_mem_cxt : u_sess->SPI_cxt._current->procCxt,
|
|
"SPI Exec", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
|
/* ... and switch to procedure's context */
|
|
u_sess->SPI_cxt._current->savedcxt = MemoryContextSwitchTo(u_sess->SPI_cxt._current->procCxt);
|
|
|
|
return SPI_OK_CONNECT;
|
|
}
|
|
|
|
int SPI_finish(void)
|
|
{
|
|
SPI_STACK_LOG("begin", NULL, NULL);
|
|
int res = _SPI_begin_call(false); /* live in procedure memory */
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
/* Restore memory context as it was before procedure call */
|
|
(void)MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
|
|
/* Release memory used in procedure call */
|
|
MemoryContextDelete(u_sess->SPI_cxt._current->execCxt);
|
|
u_sess->SPI_cxt._current->execCxt = NULL;
|
|
MemoryContextDelete(u_sess->SPI_cxt._current->procCxt);
|
|
u_sess->SPI_cxt._current->procCxt = NULL;
|
|
|
|
/*
|
|
* Reset result variables, especially SPI_tuptable which is probably
|
|
* pointing at a just-deleted tuptable
|
|
*/
|
|
SPI_processed = 0;
|
|
u_sess->SPI_cxt.lastoid = InvalidOid;
|
|
SPI_tuptable = NULL;
|
|
|
|
/* Recover timestamp */
|
|
if (u_sess->SPI_cxt._current->stmtTimestamp > 0) {
|
|
SetCurrentStmtTimestamp(u_sess->SPI_cxt._current->stmtTimestamp);
|
|
}
|
|
|
|
/*
|
|
* After _SPI_begin_call u_sess->SPI_cxt._connected == u_sess->SPI_cxt._curid. Now we are closing
|
|
* connection to SPI and returning to upper Executor and so u_sess->SPI_cxt._connected
|
|
* must be equal to u_sess->SPI_cxt._curid.
|
|
*/
|
|
u_sess->SPI_cxt._connected--;
|
|
u_sess->SPI_cxt._curid--;
|
|
if (u_sess->SPI_cxt._connected == -1) {
|
|
u_sess->SPI_cxt._current = NULL;
|
|
} else {
|
|
u_sess->SPI_cxt._current = &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._connected]);
|
|
}
|
|
|
|
return SPI_OK_FINISH;
|
|
}
|
|
|
|
void SPI_save_current_stp_transaction_state()
|
|
{
|
|
SaveCurrentSTPTopTransactionState();
|
|
}
|
|
|
|
void SPI_restore_current_stp_transaction_state()
|
|
{
|
|
RestoreCurrentSTPTopTransactionState();
|
|
}
|
|
|
|
/* This function will be called by commit/rollback inside STP to start a new transaction */
|
|
void SPI_start_transaction(List* transactionHead)
|
|
{
|
|
Oid savedCurrentUser = InvalidOid;
|
|
int saveSecContext = 0;
|
|
MemoryContext savedContext = MemoryContextSwitchTo(t_thrd.mem_cxt.portal_mem_cxt);
|
|
GetUserIdAndSecContext(&savedCurrentUser, &saveSecContext);
|
|
if (transactionHead != NULL) {
|
|
ListCell* cell;
|
|
foreach(cell, transactionHead) {
|
|
transactionNode* node = (transactionNode*)lfirst(cell);
|
|
SetUserIdAndSecContext(node->userId, node->secContext);
|
|
break;
|
|
}
|
|
}
|
|
MemoryContextSwitchTo(savedContext);
|
|
MemoryContext oldcontext = CurrentMemoryContext;
|
|
PG_TRY();
|
|
{
|
|
StartTransactionCommand(true);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
SetUserIdAndSecContext(savedCurrentUser, saveSecContext);
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
MemoryContextSwitchTo(oldcontext);
|
|
SetUserIdAndSecContext(savedCurrentUser, saveSecContext);
|
|
}
|
|
|
|
/*
|
|
* Firstly Call this to check whether commit/rollback statement is supported, then call
|
|
* SPI_commit/SPI_rollback to change transaction state.
|
|
*/
|
|
void SPI_stp_transaction_check(bool read_only, bool savepoint)
|
|
{
|
|
/* Can not commit/rollback if it's atomic is true */
|
|
if (u_sess->SPI_cxt._current->atomic) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
|
|
errmsg("%s", u_sess->SPI_cxt.forbidden_commit_rollback_err_msg)));
|
|
}
|
|
|
|
/* If commit/rollback is not within store procedure report error */
|
|
if (!u_sess->SPI_cxt.is_stp) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
|
|
errmsg("cannot commit/rollback/savepoint within function or procedure started by trigger")));
|
|
}
|
|
|
|
if (u_sess->SPI_cxt.is_complex_sql) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
|
|
errmsg("cannot commit/rollback within function or procedure started by complicated SQL statements")));
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* Can not commit/rollback at non-CN nodes */
|
|
if (!IS_PGXC_COORDINATOR || IsConnFromCoord()) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
|
|
errmsg("cannot commit/rollback/savepoint at non-CN node")));
|
|
}
|
|
#endif
|
|
|
|
if (read_only) {
|
|
pipelined_readonly_ereport();
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("commit/rollback/savepoint is not allowed in a non-volatile function")));
|
|
/* translator: %s is a SQL statement name */
|
|
}
|
|
|
|
if (!savepoint && IsStpInOuterSubTransaction()) {
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("commit/rollback is not allowed in outer sub transaction block.")));
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* See SPI_stp_transaction_check.
|
|
*/
|
|
void SPI_commit()
|
|
{
|
|
MemoryContext oldcontext = CurrentMemoryContext;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
u_sess->SPI_cxt._current->internal_xact = true;
|
|
|
|
while (ActiveSnapshotSet()) {
|
|
PopActiveSnapshot();
|
|
}
|
|
|
|
CommitTransactionCommand(true);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
u_sess->SPI_cxt._current->internal_xact = false;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* See SPI_stp_transaction_check.
|
|
*/
|
|
void SPI_rollback()
|
|
{
|
|
MemoryContext oldcontext = CurrentMemoryContext;
|
|
|
|
/* see under SPI_commit() */
|
|
u_sess->SPI_cxt._current->internal_xact = true;
|
|
|
|
AbortCurrentTransaction(true);
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
u_sess->SPI_cxt._current->internal_xact = false;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* rollback and release current transaction.
|
|
*/
|
|
void SPI_savepoint_rollbackAndRelease(const char *spName, SubTransactionId subXid)
|
|
{
|
|
if (subXid == InvalidTransactionId) {
|
|
RollbackAndReleaseCurrentSubTransaction(true);
|
|
|
|
if (spName != NULL) {
|
|
u_sess->plsql_cxt.stp_savepoint_cnt--;
|
|
} else {
|
|
u_sess->SPI_cxt.portal_stp_exception_counter--;
|
|
}
|
|
} else if (subXid != GetCurrentSubTransactionId()) {
|
|
/* errors during starting a subtransaction */
|
|
AbortSubTransaction();
|
|
CleanupSubTransaction();
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* DN had aborted the complete transaction already while cancel_query failed. */
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
|
|
!t_thrd.xact_cxt.handlesDestroyedInCancelQuery) {
|
|
/* internal savepoint while spName is NULL */
|
|
const char *name = (spName != NULL) ? spName : "s1";
|
|
|
|
StringInfoData str;
|
|
initStringInfo(&str);
|
|
appendStringInfo(&str, "ROLLBACK TO %s;", name);
|
|
|
|
HandleReleaseOrRollbackSavepoint(str.data, name, SUB_STMT_ROLLBACK_TO);
|
|
/* CN send rollback savepoint to remote nodes to abort sub transaction remotely */
|
|
pgxc_node_remote_savepoint(str.data, EXEC_ON_DATANODES, false, false);
|
|
|
|
resetStringInfo(&str);
|
|
appendStringInfo(&str, "RELEASE %s;", name);
|
|
HandleReleaseOrRollbackSavepoint(str.data, name, SUB_STMT_RELEASE);
|
|
/* CN should send release savepoint command to remote nodes for savepoint name reuse */
|
|
pgxc_node_remote_savepoint(str.data, EXEC_ON_DATANODES, false, false);
|
|
|
|
FreeStringInfo(&str);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* define a savepint in STP
|
|
*/
|
|
void SPI_savepoint_create(const char* spName)
|
|
{
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* CN should send savepoint command to remote nodes to begin sub transaction remotely. */
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
|
|
/* internal savepoint while spName is NULL */
|
|
const char *name = (spName != NULL) ? spName : "s1";
|
|
|
|
StringInfoData str;
|
|
initStringInfo(&str);
|
|
appendStringInfo(&str, "SAVEPOINT %s;", name);
|
|
|
|
/* if do savepoint, always treat myself as local write node */
|
|
RegisterTransactionLocalNode(true);
|
|
RecordSavepoint(str.data, name, false, SUB_STMT_SAVEPOINT);
|
|
pgxc_node_remote_savepoint(str.data, EXEC_ON_DATANODES, true, true);
|
|
|
|
FreeStringInfo(&str);
|
|
}
|
|
#endif
|
|
|
|
SubTransactionId subXid = GetCurrentSubTransactionId();
|
|
PG_TRY();
|
|
{
|
|
BeginInternalSubTransaction(spName);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
SPI_savepoint_rollbackAndRelease(spName, subXid);
|
|
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
|
|
if (spName != NULL) {
|
|
u_sess->plsql_cxt.stp_savepoint_cnt++;
|
|
} else {
|
|
u_sess->SPI_cxt.portal_stp_exception_counter++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rollback to a savepoint in STP
|
|
*/
|
|
void SPI_savepoint_rollback(const char* spName)
|
|
{
|
|
/* mark subtransaction to be rollback as abort pending */
|
|
RollbackToSavepoint(spName, true);
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* savepoint for exception while spName is NULL */
|
|
const char *name = (spName != NULL) ? spName : "s1";
|
|
|
|
StringInfoData str;
|
|
initStringInfo(&str);
|
|
appendStringInfo(&str, "ROLLBACK TO %s;", name);
|
|
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
|
|
HandleReleaseOrRollbackSavepoint(str.data, name, SUB_STMT_ROLLBACK_TO);
|
|
/* CN send rollback savepoint to remote nodes to abort sub transaction remotely */
|
|
pgxc_node_remote_savepoint(str.data, EXEC_ON_DATANODES, false, false);
|
|
}
|
|
|
|
FreeStringInfo(&str);
|
|
#endif
|
|
|
|
/*
|
|
* Do this after remote savepoint, so no cancel in AbortSubTransaction will be sent to
|
|
* interrupt previous cursor fetch.
|
|
*/
|
|
CommitTransactionCommand(true);
|
|
}
|
|
|
|
/*
|
|
* release savepoint in STP
|
|
*/
|
|
void SPI_savepoint_release(const char* spName)
|
|
{
|
|
/* Commit the inner transaction, return to outer xact context */
|
|
ReleaseSavepoint(spName, true);
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* CN should send release savepoint command to remote nodes for savepoint name reuse */
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) {
|
|
/* internal savepoint while spName is NULL */
|
|
const char *name = (spName != NULL) ? spName : "s1";
|
|
|
|
StringInfoData str;
|
|
initStringInfo(&str);
|
|
appendStringInfo(&str, "RELEASE %s;", name);
|
|
|
|
HandleReleaseOrRollbackSavepoint(str.data, name, SUB_STMT_RELEASE);
|
|
pgxc_node_remote_savepoint(str.data, EXEC_ON_DATANODES, false, false);
|
|
|
|
FreeStringInfo(&str);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Do this after remote savepoint, so no cancel in AbortSubTransaction will be sent to
|
|
* interrupt previous cursor fetch.
|
|
*/
|
|
CommitTransactionCommand(true);
|
|
}
|
|
|
|
/*
|
|
* return SPI current connect's stack level.
|
|
*/
|
|
int SPI_connectid()
|
|
{
|
|
return u_sess->SPI_cxt._connected;
|
|
}
|
|
|
|
/*
|
|
* pop SPI connect till specified stack level.
|
|
*/
|
|
void SPI_disconnect(int connect)
|
|
{
|
|
while (u_sess->SPI_cxt._connected >= connect) {
|
|
/* Restore memory context as it was before procedure call */
|
|
(void)MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
|
|
/*
|
|
* Memory can't be destoryed now since cleanup as those as in AbortSubTransaction
|
|
* are not done still. We ignore them here, and they will be freed along with top
|
|
* transaction's termination or portal drop. Any idea to free it in advance?
|
|
*/
|
|
u_sess->SPI_cxt._current->execCxt = NULL;
|
|
u_sess->SPI_cxt._current->procCxt = NULL;
|
|
|
|
/* Recover timestamp */
|
|
if (u_sess->SPI_cxt._current->stmtTimestamp > 0) {
|
|
SetCurrentStmtTimestamp(u_sess->SPI_cxt._current->stmtTimestamp);
|
|
}
|
|
|
|
u_sess->SPI_cxt._connected--;
|
|
u_sess->SPI_cxt._curid = u_sess->SPI_cxt._connected;
|
|
if (u_sess->SPI_cxt._connected == -1) {
|
|
u_sess->SPI_cxt._current = NULL;
|
|
} else {
|
|
u_sess->SPI_cxt._current = &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._connected]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reset result variables, especially SPI_tuptable which is probably
|
|
* pointing at a just-deleted tuptable
|
|
*/
|
|
SPI_processed = 0;
|
|
u_sess->SPI_cxt.lastoid = InvalidOid;
|
|
SPI_tuptable = NULL;
|
|
}
|
|
|
|
/*
|
|
* Clean up SPI state. Called on transaction end (of non-SPI-internal
|
|
* transactions) and when returning to the main loop on error.
|
|
*/
|
|
void SPICleanup(void)
|
|
{
|
|
u_sess->SPI_cxt._current = NULL;
|
|
u_sess->SPI_cxt._connected = u_sess->SPI_cxt._curid = -1;
|
|
SPI_processed = 0;
|
|
u_sess->SPI_cxt.lastoid = InvalidOid;
|
|
SPI_tuptable = NULL;
|
|
}
|
|
|
|
/*
|
|
* Clean up SPI state at transaction commit or abort.
|
|
*/
|
|
void AtEOXact_SPI(bool isCommit, bool STP_rollback, bool STP_commit)
|
|
{
|
|
/* Do nothing if the transaction end was initiated by SPI */
|
|
if (STP_rollback || STP_commit) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Note that memory contexts belonging to SPI stack entries will be freed
|
|
* automatically, so we can ignore them here. We just need to restore our
|
|
* static variables to initial state.
|
|
*/
|
|
if (isCommit && u_sess->SPI_cxt._connected != -1) {
|
|
ereport(WARNING, (errcode(ERRCODE_WARNING), errmsg("transaction left non-empty SPI stack"),
|
|
errhint("Check for missing \"SPI_finish\" calls.")));
|
|
}
|
|
|
|
SPICleanup();
|
|
}
|
|
|
|
/*
|
|
* 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 STP_rollback, bool STP_commit)
|
|
{
|
|
/* Do nothing if the transaction end was initiated by SPI */
|
|
if (STP_rollback || STP_commit) {
|
|
return;
|
|
}
|
|
|
|
bool found = false;
|
|
while (u_sess->SPI_cxt._connected >= 0) {
|
|
_SPI_connection *connection = &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._connected]);
|
|
|
|
if (connection->connectSubid != mySubid) {
|
|
break; /* couldn't be any underneath it either */
|
|
}
|
|
|
|
/* During multi commit within stored procedure should not clean SPI_connected,
|
|
* it should be clean up when all the statements within STP is done
|
|
*/
|
|
if (connection->internal_xact) {
|
|
break;
|
|
}
|
|
|
|
found = true;
|
|
|
|
/*
|
|
* Release procedure memory explicitly (see note in SPI_connect)
|
|
*/
|
|
bool need_free_context = isCommit ? true : connection->atomic;
|
|
if (connection->execCxt && need_free_context) {
|
|
MemoryContextDelete(connection->execCxt);
|
|
connection->execCxt = NULL;
|
|
}
|
|
if (connection->procCxt && need_free_context) {
|
|
MemoryContextDelete(connection->procCxt);
|
|
connection->procCxt = NULL;
|
|
}
|
|
|
|
/* Recover timestamp */
|
|
if (connection->stmtTimestamp > 0) {
|
|
SetCurrentStmtTimestamp(connection->stmtTimestamp);
|
|
}
|
|
|
|
/*
|
|
* Pop the stack entry and reset global variables. Unlike
|
|
* SPI_finish(), we don't risk switching to memory contexts that might
|
|
* be already gone.
|
|
*/
|
|
u_sess->SPI_cxt._connected--;
|
|
u_sess->SPI_cxt._curid = u_sess->SPI_cxt._connected;
|
|
if (u_sess->SPI_cxt._connected == -1) {
|
|
u_sess->SPI_cxt._current = NULL;
|
|
} else {
|
|
u_sess->SPI_cxt._current = &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._connected]);
|
|
}
|
|
SPI_processed = 0;
|
|
u_sess->SPI_cxt.lastoid = InvalidOid;
|
|
SPI_tuptable = NULL;
|
|
}
|
|
|
|
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 (u_sess->SPI_cxt._current && !isCommit) {
|
|
/* free Executor memory the same as _SPI_end_call would do */
|
|
MemoryContextResetAndDeleteChildren(u_sess->SPI_cxt._current->execCxt);
|
|
/* throw away any partially created tuple-table */
|
|
SPI_freetuptable(u_sess->SPI_cxt._current->tuptable);
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
}
|
|
}
|
|
|
|
/* Pushes SPI stack to allow recursive SPI calls */
|
|
void SPI_push(void)
|
|
{
|
|
u_sess->SPI_cxt._curid++;
|
|
}
|
|
|
|
/* Pops SPI stack to allow recursive SPI calls */
|
|
void SPI_pop(void)
|
|
{
|
|
u_sess->SPI_cxt._curid--;
|
|
}
|
|
|
|
/* Conditional push: push only if we're inside a SPI procedure */
|
|
bool SPI_push_conditional(void)
|
|
{
|
|
bool pushed = (u_sess->SPI_cxt._curid != u_sess->SPI_cxt._connected);
|
|
|
|
if (pushed) {
|
|
u_sess->SPI_cxt._curid++;
|
|
/* We should now be in a state where SPI_connect would succeed */
|
|
Assert(u_sess->SPI_cxt._curid == u_sess->SPI_cxt._connected);
|
|
}
|
|
return pushed;
|
|
}
|
|
|
|
/* Conditional pop: pop only if SPI_push_conditional pushed */
|
|
void SPI_pop_conditional(bool pushed)
|
|
{
|
|
/* We should be in a state where SPI_connect would succeed */
|
|
Assert(u_sess->SPI_cxt._curid == u_sess->SPI_cxt._connected);
|
|
if (pushed) {
|
|
u_sess->SPI_cxt._curid--;
|
|
}
|
|
}
|
|
|
|
/* Restore state of SPI stack after aborting a subtransaction */
|
|
void SPI_restore_connection(void)
|
|
{
|
|
Assert(u_sess->SPI_cxt._connected >= 0);
|
|
u_sess->SPI_cxt._curid = u_sess->SPI_cxt._connected - 1;
|
|
}
|
|
|
|
void SPI_restore_connection_on_exception(void)
|
|
{
|
|
Assert(u_sess->SPI_cxt._connected >= 0);
|
|
if (u_sess->SPI_cxt._current && u_sess->SPI_cxt._curid > u_sess->SPI_cxt._connected - 1) {
|
|
MemoryContextResetAndDeleteChildren(u_sess->SPI_cxt._current->execCxt);
|
|
}
|
|
u_sess->SPI_cxt._curid = u_sess->SPI_cxt._connected - 1;
|
|
}
|
|
|
|
#ifdef PGXC
|
|
/* SPI_execute_direct:
|
|
* Runs the 'remote_sql' query string on the node 'nodename'
|
|
* Create the ExecDirectStmt parse tree node using remote_sql, and then prepare
|
|
* and execute it using SPI interface.
|
|
* This function is essentially used for making internal exec-direct operations;
|
|
* and this should not require super-user privileges. We cannot run EXEC-DIRECT
|
|
* query because it is meant only for superusers. So this function needs to
|
|
* bypass the parse stage. This is achieved here by calling
|
|
* _SPI_pgxc_prepare_plan which accepts a parse tree.
|
|
*/
|
|
int SPI_execute_direct(const char *remote_sql, char *nodename, parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
ExecDirectStmt *stmt = makeNode(ExecDirectStmt);
|
|
StringInfoData execdirect;
|
|
|
|
initStringInfo(&execdirect);
|
|
|
|
/* This string is never used. It is just passed to fill up spi_err_context.arg */
|
|
appendStringInfo(&execdirect, "EXECUTE DIRECT ON (%s) '%s'", nodename, remote_sql);
|
|
|
|
stmt->node_names = list_make1(makeString(nodename));
|
|
stmt->query = pstrdup(remote_sql);
|
|
|
|
SPI_STACK_LOG("begin", remote_sql, NULL);
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
|
|
errno_t errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = 0;
|
|
plan.spi_key = INVALID_SPI_KEY;
|
|
|
|
/* Now pass the ExecDirectStmt parsetree node */
|
|
_SPI_pgxc_prepare_plan(execdirect.data, list_make1(stmt), &plan, parser);
|
|
|
|
res = _SPI_execute_plan(&plan, NULL, InvalidSnapshot, InvalidSnapshot, false, true, 0, true);
|
|
|
|
SPI_STACK_LOG("end", remote_sql, NULL);
|
|
_SPI_end_call(true);
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Parse, plan, and execute a query string
|
|
* @isCollectParam: default false, is used to collect sql info in sqladvisor online mode.
|
|
*/
|
|
int SPI_execute(const char *src, bool read_only, long tcount, bool isCollectParam, parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
|
|
if (src == NULL || tcount < 0) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
SPI_STACK_LOG("begin", src, NULL);
|
|
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
errno_t errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = 0;
|
|
plan.spi_key = INVALID_SPI_KEY;
|
|
|
|
_SPI_prepare_oneshot_plan(src, &plan, parser);
|
|
|
|
res = _SPI_execute_plan(&plan, NULL, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount);
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (isCollectParam && checkSPIPlan(&plan)) {
|
|
collectDynWithArgs(src, NULL, 0);
|
|
}
|
|
#endif
|
|
SPI_STACK_LOG("end", src, NULL);
|
|
|
|
_SPI_end_call(true);
|
|
return res;
|
|
}
|
|
|
|
/* Obsolete version of SPI_execute */
|
|
int SPI_exec(const char *src, long tcount, parse_query_func parser)
|
|
{
|
|
return SPI_execute(src, false, tcount, false, parser);
|
|
}
|
|
|
|
/* Execute a previously prepared plan */
|
|
int SPI_execute_plan(SPIPlanPtr plan, Datum *Values, const char *Nulls, bool read_only, long tcount)
|
|
{
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
|
|
if (plan->nargs > 0 && Values == NULL)
|
|
return SPI_ERROR_PARAM;
|
|
|
|
SPI_STACK_LOG("begin", NULL, plan);
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls), InvalidSnapshot,
|
|
InvalidSnapshot, read_only, true, tcount);
|
|
|
|
SPI_STACK_LOG("end", NULL, plan);
|
|
_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_with_paramlist(SPIPlanPtr plan, ParamListInfo params, bool read_only, long tcount)
|
|
{
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
SPI_STACK_LOG("begin", NULL, plan);
|
|
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
res = _SPI_execute_plan(plan, params, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount);
|
|
|
|
SPI_STACK_LOG("end", NULL, plan);
|
|
_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)
|
|
{
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || tcount < 0) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
|
|
if (plan->nargs > 0 && Values == NULL) {
|
|
return SPI_ERROR_PARAM;
|
|
}
|
|
SPI_STACK_LOG("begin", NULL, plan);
|
|
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
res = _SPI_execute_plan(plan, _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls), snapshot,
|
|
crosscheck_snapshot, read_only, fire_triggers, tcount);
|
|
|
|
SPI_STACK_LOG("end", NULL, plan);
|
|
_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, Cursor_Data *cursor_data, parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
|
|
if (src == NULL || nargs < 0 || tcount < 0) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
|
|
if (nargs > 0 && (argtypes == NULL || Values == NULL)) {
|
|
return SPI_ERROR_PARAM;
|
|
}
|
|
|
|
SPI_STACK_LOG("begin", src, NULL);
|
|
int res = _SPI_begin_call(true);
|
|
if (res < 0) {
|
|
return res;
|
|
}
|
|
errno_t errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = 0;
|
|
plan.nargs = nargs;
|
|
plan.argtypes = argtypes;
|
|
plan.parserSetup = NULL;
|
|
plan.parserSetupArg = NULL;
|
|
|
|
ParamListInfo param_list_info = _SPI_convert_params(nargs, argtypes, Values, Nulls, cursor_data);
|
|
|
|
_SPI_prepare_oneshot_plan(src, &plan, parser);
|
|
|
|
res = _SPI_execute_plan(&plan, param_list_info, InvalidSnapshot, InvalidSnapshot, read_only, true, tcount);
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (checkAdivsorState() && checkSPIPlan(&plan)) {
|
|
collectDynWithArgs(src, param_list_info, plan.cursor_options);
|
|
}
|
|
#endif
|
|
SPI_STACK_LOG("end", src, NULL);
|
|
_SPI_end_call(true);
|
|
return res;
|
|
}
|
|
|
|
SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes, parse_query_func parser)
|
|
{
|
|
return SPI_prepare_cursor(src, nargs, argtypes, 0, parser);
|
|
}
|
|
|
|
#ifdef USE_SPQ
|
|
SPIPlanPtr SPI_prepare_spq(const char *src, int nargs, Oid *argtypes, parse_query_func parser)
|
|
{
|
|
return SPI_prepare_cursor(src, nargs, argtypes, CURSOR_OPT_SPQ_OK | CURSOR_OPT_SPQ_FORCE, parser);
|
|
}
|
|
#endif
|
|
|
|
SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, int cursorOptions, parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
|
|
if (src == NULL || nargs < 0 || (nargs > 0 && argtypes == NULL)) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
|
|
SPI_STACK_LOG("begin", src, NULL);
|
|
SPI_result = _SPI_begin_call(true);
|
|
if (SPI_result < 0) {
|
|
return NULL;
|
|
}
|
|
errno_t errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = cursorOptions;
|
|
plan.nargs = nargs;
|
|
plan.argtypes = argtypes;
|
|
plan.parserSetup = NULL;
|
|
plan.parserSetupArg = NULL;
|
|
plan.spi_key = INVALID_SPI_KEY;
|
|
plan.id = (uint32)-1;
|
|
|
|
/* don't call SPI_keepplan, so won't put plancache into first_save_plan.
|
|
so this plancache can't share into gpc, set spi_hash_key to invalid when call _SPI_prepare_plan. */
|
|
uint32 old_key = u_sess->SPI_cxt._current->spi_hash_key;
|
|
u_sess->SPI_cxt._current->spi_hash_key = INVALID_SPI_KEY;
|
|
PG_TRY();
|
|
{
|
|
_SPI_prepare_plan(src, &plan, parser);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
u_sess->SPI_cxt._current->spi_hash_key = old_key;
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
u_sess->SPI_cxt._current->spi_hash_key = old_key;
|
|
|
|
/* copy plan to procedure context */
|
|
SPIPlanPtr result = _SPI_make_plan_non_temp(&plan);
|
|
|
|
SPI_STACK_LOG("end", src, NULL);
|
|
_SPI_end_call(true);
|
|
|
|
return result;
|
|
}
|
|
|
|
SPIPlanPtr SPI_prepare_params(const char *src, ParserSetupHook parserSetup, void *parserSetupArg, int cursorOptions,
|
|
parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
|
|
if (src == NULL) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
|
|
SPI_STACK_LOG("begin", src, NULL);
|
|
SPI_result = _SPI_begin_call(true);
|
|
if (SPI_result < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
errno_t errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = cursorOptions;
|
|
plan.nargs = 0;
|
|
plan.argtypes = NULL;
|
|
plan.parserSetup = parserSetup;
|
|
plan.parserSetupArg = parserSetupArg;
|
|
plan.stmt_list = NIL;
|
|
plan.spi_key = INVALID_SPI_KEY;
|
|
plan.id = (uint32)-1;
|
|
|
|
#ifdef ENABLE_MOT
|
|
if (u_sess->mot_cxt.jit_compile_depth > 0) {
|
|
if (!_SPI_prepare_plan_guarded(src, &plan, parser)) {
|
|
_SPI_end_call(true);
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
} else {
|
|
_SPI_prepare_plan(src, &plan, parser);
|
|
}
|
|
#else
|
|
_SPI_prepare_plan(src, &plan, parser);
|
|
#endif
|
|
|
|
/* copy plan to procedure context */
|
|
SPIPlanPtr result = _SPI_make_plan_non_temp(&plan);
|
|
|
|
SPI_STACK_LOG("end", src, NULL);
|
|
_SPI_end_call(true);
|
|
|
|
return result;
|
|
}
|
|
|
|
int SPI_keepplan(SPIPlanPtr plan)
|
|
{
|
|
ListCell *lc = NULL;
|
|
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC || plan->saved || plan->oneshot) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
|
|
/*
|
|
* Mark it saved, reparent it under u_sess->cache_mem_cxt, 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, u_sess->cache_mem_cxt);
|
|
if (ENABLE_CN_GPC && plan->spi_key != INVALID_SPI_KEY) {
|
|
foreach (lc, SPI_plan_get_plan_sources(plan)) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc);
|
|
/* first created plan or shared plan from gpc */
|
|
if (!plansource->gpc.status.InShareTable()) {
|
|
Assert (!plansource->gpc.status.InUngpcPlanList());
|
|
SaveCachedPlan(plansource);
|
|
plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_SAVE_PLAN_LIST);
|
|
}
|
|
}
|
|
} else {
|
|
foreach (lc, plan->plancache_list) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc);
|
|
SaveCachedPlan(plansource);
|
|
}
|
|
}
|
|
// spiplan should on cache_mem_cxt
|
|
Assert(plan->magic == _SPI_PLAN_MAGIC);
|
|
if (ENABLE_CN_GPC && plan->spi_key != INVALID_SPI_KEY && list_length(plan->plancache_list) > 0) {
|
|
Assert (plan->spi_key == u_sess->SPI_cxt._current->spi_hash_key);
|
|
SPICacheTableInsertPlan(plan->spi_key, plan);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
SPIPlanPtr SPI_saveplan(SPIPlanPtr plan)
|
|
{
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
SPI_STACK_LOG("begin", NULL, plan);
|
|
SPI_result = _SPI_begin_call(false); /* don't change context */
|
|
if (SPI_result < 0) {
|
|
return NULL;
|
|
}
|
|
|
|
SPIPlanPtr new_plan = _SPI_save_plan(plan);
|
|
|
|
SPI_result = _SPI_end_call(false);
|
|
|
|
SPI_STACK_LOG("end", NULL, plan);
|
|
return new_plan;
|
|
}
|
|
|
|
int SPI_freeplan(SPIPlanPtr plan)
|
|
{
|
|
ListCell *lc = NULL;
|
|
|
|
if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC) {
|
|
return SPI_ERROR_ARGUMENT;
|
|
}
|
|
|
|
if (ENABLE_CN_GPC) {
|
|
CN_GPC_LOG("drop spi plan SPI_freeplan", 0, 0);
|
|
g_instance.plan_cache->RemovePlanCacheInSPIPlan(plan);
|
|
MemoryContextDelete(plan->plancxt);
|
|
return 0;
|
|
}
|
|
/* 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 old_ctx = NULL;
|
|
|
|
if (tuple == NULL) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
|
|
if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { /* connected */
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("SPI stack corrupted when copy tuple, connected level: %d", u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
old_ctx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
}
|
|
|
|
HeapTuple c_tuple = (HeapTuple)tableam_tops_copy_tuple(tuple);
|
|
|
|
if (old_ctx) {
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
return c_tuple;
|
|
}
|
|
|
|
HeapTupleHeader SPI_returntuple(HeapTuple tuple, TupleDesc tupdesc)
|
|
{
|
|
MemoryContext old_ctx = NULL;
|
|
|
|
if (tuple == NULL || tupdesc == NULL) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
|
|
/* For RECORD results, make sure a typmod has been assigned */
|
|
if (tupdesc->tdtypeid == RECORDOID && tupdesc->tdtypmod < 0) {
|
|
assign_record_type_typmod(tupdesc);
|
|
}
|
|
|
|
if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { /* connected */
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("SPI stack corrupted when return tuple, connected level: %d", u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
old_ctx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
}
|
|
|
|
HeapTupleHeader d_tup = (HeapTupleHeader)palloc(tuple->t_len);
|
|
errno_t rc = memcpy_s((char *)d_tup, tuple->t_len, (char *)tuple->t_data, tuple->t_len);
|
|
securec_check(rc, "\0", "\0");
|
|
|
|
HeapTupleHeaderSetDatumLength(d_tup, tuple->t_len);
|
|
HeapTupleHeaderSetTypeId(d_tup, tupdesc->tdtypeid);
|
|
HeapTupleHeaderSetTypMod(d_tup, tupdesc->tdtypmod);
|
|
|
|
if (old_ctx) {
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
return d_tup;
|
|
}
|
|
|
|
HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum, Datum *Values, const char *Nulls)
|
|
{
|
|
MemoryContext old_ctx = NULL;
|
|
HeapTuple m_tuple = NULL;
|
|
int num_of_attr;
|
|
Datum *v = NULL;
|
|
bool *n = NULL;
|
|
int i;
|
|
|
|
if (rel == NULL || tuple == NULL || natts < 0 || attnum == NULL || Values == NULL) {
|
|
SPI_result = SPI_ERROR_ARGUMENT;
|
|
return NULL;
|
|
}
|
|
|
|
if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { /* connected */
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("SPI stack corrupted when modify tuple, connected level: %d", u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
old_ctx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
}
|
|
SPI_result = 0;
|
|
num_of_attr = rel->rd_att->natts;
|
|
v = (Datum *)palloc(num_of_attr * sizeof(Datum));
|
|
n = (bool *)palloc(num_of_attr * 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] > num_of_attr) {
|
|
break;
|
|
}
|
|
v[attnum[i] - 1] = Values[i];
|
|
n[attnum[i] - 1] = (Nulls && Nulls[i] == 'n') ? true : false;
|
|
}
|
|
|
|
if (i == natts) {
|
|
/* no errors in *attnum */
|
|
m_tuple = heap_form_tuple(rel->rd_att, v, n);
|
|
/*
|
|
* copy the identification info of the old tuple: t_ctid, t_self, and
|
|
* OID (if any)
|
|
*/
|
|
m_tuple->t_data->t_ctid = tuple->t_data->t_ctid;
|
|
m_tuple->t_self = tuple->t_self;
|
|
m_tuple->t_tableOid = tuple->t_tableOid;
|
|
m_tuple->t_bucketId = tuple->t_bucketId;
|
|
HeapTupleCopyBase(m_tuple, tuple);
|
|
#ifdef PGXC
|
|
m_tuple->t_xc_node_id = tuple->t_xc_node_id;
|
|
#endif
|
|
|
|
if (rel->rd_att->tdhasoid) {
|
|
HeapTupleSetOid(m_tuple, HeapTupleGetOid(tuple));
|
|
}
|
|
} else {
|
|
m_tuple = NULL;
|
|
SPI_result = SPI_ERROR_NOATTRIBUTE;
|
|
}
|
|
|
|
pfree_ext(v);
|
|
pfree_ext(n);
|
|
|
|
if (old_ctx) {
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
return m_tuple;
|
|
}
|
|
|
|
int SPI_fnumber(TupleDesc tupdesc, const char *fname)
|
|
{
|
|
int res;
|
|
Form_pg_attribute sys_att;
|
|
|
|
for (res = 0; res < tupdesc->natts; res++) {
|
|
if (u_sess->attr.attr_sql.dolphin) {
|
|
if (namestrcasecmp(&tupdesc->attrs[res].attname, fname) == 0) {
|
|
return res + 1;
|
|
}
|
|
} else if (namestrcmp(&tupdesc->attrs[res].attname, fname) == 0) {
|
|
return res + 1;
|
|
}
|
|
}
|
|
|
|
sys_att = SystemAttributeByName(fname, true /* "oid" will be accepted */);
|
|
if (sys_att != NULL) {
|
|
return sys_att->attnum;
|
|
}
|
|
|
|
/* SPI_ERROR_NOATTRIBUTE is different from all sys column numbers */
|
|
return SPI_ERROR_NOATTRIBUTE;
|
|
}
|
|
|
|
char *SPI_fname(TupleDesc tupdesc, int fnumber)
|
|
{
|
|
Form_pg_attribute attr;
|
|
SPI_result = 0;
|
|
|
|
if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) {
|
|
SPI_result = SPI_ERROR_NOATTRIBUTE;
|
|
return NULL;
|
|
}
|
|
|
|
if (fnumber > 0) {
|
|
attr = &tupdesc->attrs[fnumber - 1];
|
|
} else {
|
|
attr = SystemAttributeDefinition(fnumber, true, false, false);
|
|
}
|
|
|
|
return pstrdup(NameStr(attr->attname));
|
|
}
|
|
|
|
char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
|
|
{
|
|
Datum val;
|
|
bool is_null = false;
|
|
Oid typoid, foutoid;
|
|
bool typo_is_varlen = false;
|
|
|
|
SPI_result = 0;
|
|
|
|
if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) {
|
|
SPI_result = SPI_ERROR_NOATTRIBUTE;
|
|
return NULL;
|
|
}
|
|
|
|
val = tableam_tops_tuple_getattr(tuple, (unsigned int)fnumber, tupdesc, &is_null);
|
|
|
|
if (is_null) {
|
|
return NULL;
|
|
}
|
|
|
|
if (fnumber > 0) {
|
|
typoid = tupdesc->attrs[fnumber - 1].atttypid;
|
|
} else {
|
|
typoid = (SystemAttributeDefinition(fnumber, true, false, false))->atttypid;
|
|
}
|
|
|
|
getTypeOutputInfo(typoid, &foutoid, &typo_is_varlen);
|
|
|
|
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 tableam_tops_tuple_getattr(tuple, (unsigned int)fnumber, tupdesc, isnull);
|
|
}
|
|
|
|
char *SPI_gettype(TupleDesc tupdesc, int fnumber)
|
|
{
|
|
Oid typoid;
|
|
HeapTuple type_tuple = NULL;
|
|
char *result = NULL;
|
|
SPI_result = 0;
|
|
|
|
if (fnumber > tupdesc->natts || fnumber == 0 || fnumber <= FirstLowInvalidHeapAttributeNumber) {
|
|
SPI_result = SPI_ERROR_NOATTRIBUTE;
|
|
return NULL;
|
|
}
|
|
|
|
if (fnumber > 0) {
|
|
typoid = tupdesc->attrs[fnumber - 1].atttypid;
|
|
} else {
|
|
typoid = (SystemAttributeDefinition(fnumber, true, false, false))->atttypid;
|
|
}
|
|
|
|
type_tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typoid));
|
|
if (!HeapTupleIsValid(type_tuple)) {
|
|
SPI_result = SPI_ERROR_TYPUNKNOWN;
|
|
return NULL;
|
|
}
|
|
|
|
result = pstrdup(NameStr(((Form_pg_type)GETSTRUCT(type_tuple))->typname));
|
|
ReleaseSysCache(type_tuple);
|
|
return result;
|
|
}
|
|
|
|
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 tupdesc->attrs[fnumber - 1].atttypid;
|
|
} else {
|
|
return (SystemAttributeDefinition(fnumber, true, false, false))->atttypid;
|
|
}
|
|
}
|
|
|
|
Oid SPI_getcollation(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 tupdesc->attrs[fnumber - 1].attcollation;
|
|
} else {
|
|
return (SystemAttributeDefinition(fnumber, true, false, false))->attcollation;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
MemoryContext old_ctx = NULL;
|
|
void *pointer = NULL;
|
|
|
|
if (u_sess->SPI_cxt._curid + 1 == u_sess->SPI_cxt._connected) { /* connected */
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid + 1])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("SPI stack corrupted when allocate, connected level: %d", u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
old_ctx = MemoryContextSwitchTo(u_sess->SPI_cxt._current->savedcxt);
|
|
}
|
|
|
|
pointer = palloc(size);
|
|
|
|
if (old_ctx) {
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
return pointer;
|
|
}
|
|
|
|
void *SPI_repalloc(void *pointer, Size size)
|
|
{
|
|
/* No longer need to worry which context chunk was in... */
|
|
return repalloc(pointer, size);
|
|
}
|
|
|
|
void SPI_pfree_ext(void *pointer)
|
|
{
|
|
/* No longer need to worry which context chunk was in... */
|
|
pfree_ext(pointer);
|
|
}
|
|
|
|
void SPI_freetuple(HeapTuple tuple)
|
|
{
|
|
/* No longer need to worry which context tuple was in... */
|
|
tableam_tops_free_tuple(tuple);
|
|
}
|
|
|
|
void SPI_freetuptable(SPITupleTable *tuptable)
|
|
{
|
|
if (tuptable != NULL) {
|
|
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 param_list_info;
|
|
|
|
/* build transient ParamListInfo in caller's context */
|
|
param_list_info = _SPI_convert_params(plan->nargs, plan->argtypes, Values, Nulls);
|
|
|
|
portal = SPI_cursor_open_internal(name, plan, param_list_info, read_only);
|
|
|
|
/* done with the transient ParamListInfo */
|
|
if (param_list_info) {
|
|
pfree_ext(param_list_info);
|
|
}
|
|
|
|
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, parse_query_func parser)
|
|
{
|
|
_SPI_plan plan;
|
|
errno_t errorno = EOK;
|
|
|
|
if (src == NULL || nargs < 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("open cursor with args has invalid arguments,%s",
|
|
(src == NULL) ? "query string is NULL" : "argument number is less than zero.")));
|
|
}
|
|
|
|
if (nargs > 0 && (argtypes == NULL || Values == NULL)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("open cursor with args has invalid arguments,%s",
|
|
(argtypes == NULL) ? "argument type is NULL" : "value is NULL")));
|
|
}
|
|
|
|
SPI_STACK_LOG("begin", src, NULL);
|
|
SPI_result = _SPI_begin_call(true);
|
|
if (SPI_result < 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
|
|
errmsg("SPI stack is corrupted when open cursor with args, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
errorno = memset_s(&plan, sizeof(_SPI_plan), '\0', sizeof(_SPI_plan));
|
|
securec_check(errorno, "\0", "\0");
|
|
|
|
plan.magic = _SPI_PLAN_MAGIC;
|
|
plan.cursor_options = cursorOptions;
|
|
plan.nargs = nargs;
|
|
plan.argtypes = argtypes;
|
|
plan.parserSetup = NULL;
|
|
plan.parserSetupArg = NULL;
|
|
plan.spi_key = INVALID_SPI_KEY;
|
|
plan.id = (uint32)-1;
|
|
|
|
/* build transient ParamListInfo in executor context */
|
|
ParamListInfo param_list_info = _SPI_convert_params(nargs, argtypes, Values, Nulls);
|
|
/* don't call SPI_keepplan, so won't put plancache into first_save_plan.
|
|
so this plancache can't share into gpc, set spi_hash_key to invalid when call _SPI_prepare_plan. */
|
|
uint32 old_key = u_sess->SPI_cxt._current->spi_hash_key;
|
|
u_sess->SPI_cxt._current->spi_hash_key = INVALID_SPI_KEY;
|
|
PG_TRY();
|
|
{
|
|
_SPI_prepare_plan(src, &plan, parser);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
u_sess->SPI_cxt._current->spi_hash_key = old_key;
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
u_sess->SPI_cxt._current->spi_hash_key = old_key;
|
|
|
|
/* We needn't copy the plan; SPI_cursor_open_internal will do so */
|
|
/* Adjust stack so that SPI_cursor_open_internal doesn't complain */
|
|
u_sess->SPI_cxt._curid--;
|
|
bool isCollectParam = false;
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (checkAdivsorState()) {
|
|
isCollectParam = true;
|
|
}
|
|
#endif
|
|
Portal result = SPI_cursor_open_internal(name, &plan, param_list_info, read_only, isCollectParam);
|
|
|
|
/* And clean up */
|
|
SPI_STACK_LOG("end", src, NULL);
|
|
u_sess->SPI_cxt._curid++;
|
|
_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, bool isCollectParam)
|
|
{
|
|
return SPI_cursor_open_internal(name, plan, params, read_only, isCollectParam);
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/* check plan's stream node and set flag */
|
|
static void check_portal_stream(Portal portal)
|
|
{
|
|
if (IS_PGXC_COORDINATOR && check_stream_for_loop_fetch(portal)) {
|
|
if (!ENABLE_SQL_BETA_FEATURE(PLPGSQL_STREAM_FETCHALL)) {
|
|
/* save flag for warning */
|
|
u_sess->SPI_cxt.has_stream_in_cursor_or_forloop_sql = portal->hasStreamForPlpgsql;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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,
|
|
bool isCollectParam)
|
|
{
|
|
CachedPlanSource *plansource = NULL;
|
|
List *stmt_list = NIL;
|
|
char *query_string = NULL;
|
|
Snapshot snapshot;
|
|
MemoryContext old_ctx;
|
|
Portal portal;
|
|
ErrorContextCallback spi_err_context;
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
AutoDopControl dopControl;
|
|
dopControl.CloseSmp();
|
|
#endif
|
|
NodeTag old_node_tag = t_thrd.postgres_cxt.cur_command_tag;
|
|
|
|
/*
|
|
* Check that the plan is something the Portal code will special-case as
|
|
* returning one tupleset.
|
|
*/
|
|
if (!SPI_is_cursor_plan(plan, paramLI)) {
|
|
/* try to give a good error message */
|
|
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);
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
|
/* translator: %s is name of a SQL command, eg INSERT */
|
|
errmsg("cannot open %s query as cursor", plansource->commandTag)));
|
|
}
|
|
|
|
Assert(list_length(plan->plancache_list) == 1);
|
|
plansource = (CachedPlanSource *)linitial(plan->plancache_list);
|
|
t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(plansource->raw_parse_tree);
|
|
|
|
SPI_STACK_LOG("begin", NULL, plan);
|
|
/* Push the SPI stack */
|
|
if (_SPI_begin_call(true) < 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE),
|
|
errmsg("SPI stack is corrupted when open cursor, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
/* Reset SPI result (note we deliberately don't touch lastoid) */
|
|
SPI_processed = 0;
|
|
SPI_tuptable = NULL;
|
|
u_sess->SPI_cxt._current->processed = 0;
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
|
|
/* Create the portal */
|
|
if (name == NULL || name[0] == '\0') {
|
|
/* Use a random nonconflicting name */
|
|
portal = CreateNewPortal(true);
|
|
} else {
|
|
/* In this path, error if portal of same name already exists */
|
|
portal = CreatePortal(name, false, false, true);
|
|
}
|
|
|
|
/* Copy the plan's query string into the portal */
|
|
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal), plansource->query_string);
|
|
|
|
/*
|
|
* Setup error traceback support for ereport(), in case GetCachedPlan
|
|
* throws an error.
|
|
*/
|
|
spi_err_context.callback = _SPI_error_callback;
|
|
spi_err_context.arg = (void *)plansource->query_string;
|
|
spi_err_context.previous = t_thrd.log_cxt.error_context_stack;
|
|
t_thrd.log_cxt.error_context_stack = &spi_err_context;
|
|
|
|
/*
|
|
* 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 */
|
|
CachedPlan* cplan = GetCachedPlan(plansource, paramLI, false);
|
|
|
|
if (ENABLE_GPC && plan->saved && plansource->gplan) {
|
|
stmt_list = CopyLocalStmt(cplan->stmt_list, u_sess->top_portal_cxt, &portal->copyCxt);
|
|
} else {
|
|
stmt_list = cplan->stmt_list;
|
|
}
|
|
|
|
/* Pop the error context stack */
|
|
t_thrd.log_cxt.error_context_stack = spi_err_context.previous;
|
|
|
|
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.
|
|
*/
|
|
old_ctx = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
|
stmt_list = (List *)copyObject(stmt_list);
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
ReleaseCachedPlan(cplan, false);
|
|
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);
|
|
portal->nextval_default_expr_type = plansource->nextval_default_expr_type;
|
|
|
|
/*
|
|
* 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 && IsA((Node *)linitial(stmt_list), PlannedStmt) &&
|
|
((PlannedStmt *)linitial(stmt_list))->rowMarks == NIL &&
|
|
ExecSupportsBackwardScan(((PlannedStmt *)linitial(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 && IsA((Node *)linitial(stmt_list), PlannedStmt) &&
|
|
((PlannedStmt *)linitial(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.")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = NULL;
|
|
|
|
foreach (lc, stmt_list) {
|
|
Node *pstmt = (Node *)lfirst(lc);
|
|
|
|
if (!CommandIsReadOnly(pstmt)) {
|
|
pipelined_readonly_ereport();
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/* translator: %s is a SQL statement name */
|
|
errmsg("%s is not allowed in a non-volatile function", CreateCommandTag(pstmt)),
|
|
errhint("You can change function definition.")));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set up the snapshot to use. */
|
|
if (read_only) {
|
|
snapshot = GetActiveSnapshot();
|
|
} else {
|
|
CommandCounterIncrement();
|
|
snapshot = GetTransactionSnapshot();
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (isCollectParam && checkCommandTag(portal->commandTag) && checkPlan(portal->stmts)) {
|
|
collectDynWithArgs(query_string, paramLI, portal->cursorOptions);
|
|
}
|
|
/* check plan if has stream */
|
|
check_portal_stream(portal);
|
|
#endif
|
|
|
|
/*
|
|
* 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) {
|
|
old_ctx = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
|
paramLI = copyParamList(paramLI);
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
/*
|
|
* Start portal execution.
|
|
*/
|
|
PortalStart(portal, paramLI, 0, snapshot);
|
|
|
|
Assert(portal->strategy != PORTAL_MULTI_QUERY);
|
|
|
|
/* Pop the SPI stack */
|
|
SPI_STACK_LOG("end", NULL, plan);
|
|
|
|
/* reset flag */
|
|
u_sess->SPI_cxt.has_stream_in_cursor_or_forloop_sql = false;
|
|
t_thrd.postgres_cxt.cur_command_tag = old_node_tag;
|
|
|
|
_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)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("invalid portal in SPI cursor close 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
|
|
* paramLI: hook function and possibly data values for plan
|
|
*/
|
|
bool SPI_is_cursor_plan(SPIPlanPtr plan, ParamListInfo paramLI)
|
|
{
|
|
CachedPlanSource *plan_source = NULL;
|
|
|
|
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 */
|
|
}
|
|
plan_source = (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 (plan_source->resultDesc) {
|
|
CachedPlan *cplan = NULL;
|
|
cplan = GetCachedPlan(plan_source, paramLI, false);
|
|
if (cplan->isShared())
|
|
(void)pg_atomic_fetch_add_u32((volatile uint32*)&cplan->global_refcount, 1);
|
|
PortalStrategy strategy = ChoosePortalStrategy(cplan->stmt_list);
|
|
ReleaseCachedPlan(cplan, false);
|
|
if (strategy == PORTAL_MULTI_QUERY) {
|
|
return false;
|
|
} else {
|
|
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 = NULL;
|
|
|
|
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)
|
|
{
|
|
char *buf = u_sess->SPI_cxt.buf;
|
|
|
|
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_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";
|
|
default:
|
|
break;
|
|
}
|
|
/* Unrecognized code ... return something useful ... */
|
|
errno_t sret = sprintf_s(buf, BUFLEN, "Unrecognized SPI code %d", code);
|
|
securec_check_ss(sret, "\0", "\0");
|
|
|
|
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. 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 *plan_source = NULL;
|
|
CachedPlan *cplan = NULL;
|
|
ErrorContextCallback spi_err_context;
|
|
|
|
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;
|
|
}
|
|
plan_source = (CachedPlanSource *)linitial(plan->plancache_list);
|
|
|
|
/* Setup error traceback support for ereport() */
|
|
spi_err_context.callback = _SPI_error_callback;
|
|
spi_err_context.arg = (void *)plan_source->query_string;
|
|
spi_err_context.previous = t_thrd.log_cxt.error_context_stack;
|
|
t_thrd.log_cxt.error_context_stack = &spi_err_context;
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
AutoDopControl dopControl;
|
|
dopControl.CloseSmp();
|
|
#endif
|
|
|
|
/* Get the generic plan for the query */
|
|
cplan = GetCachedPlan(plan_source, NULL, plan->saved);
|
|
if (!ENABLE_CACHEDPLAN_MGR) {
|
|
Assert(cplan == plan_source->gplan);
|
|
}
|
|
if (cplan->isShared())
|
|
(void)pg_atomic_fetch_add_u32((volatile uint32*)&cplan->global_refcount, 1);
|
|
|
|
/* Pop the error context stack */
|
|
t_thrd.log_cxt.error_context_stack = spi_err_context.previous;
|
|
|
|
return cplan;
|
|
}
|
|
|
|
/* =================== private functions =================== */
|
|
static void spi_check_connid()
|
|
{
|
|
/*
|
|
* When called by Executor u_sess->SPI_cxt._curid expected to be equal to
|
|
* u_sess->SPI_cxt._connected
|
|
*/
|
|
if (u_sess->SPI_cxt._curid != u_sess->SPI_cxt._connected || u_sess->SPI_cxt._connected < 0) {
|
|
ereport(ERROR, (errcode(ERRORCODE_SPI_IMPROPER_CALL),
|
|
errmsg("SPI stack level is corrupted when checking SPI id, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("SPI stack is corrupted when checking SPI id.")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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 = NULL;
|
|
MemoryContext old_ctx;
|
|
MemoryContext tup_tab_cxt;
|
|
|
|
spi_check_connid();
|
|
|
|
if (u_sess->SPI_cxt._current->tuptable != NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRORCODE_SPI_IMPROPER_CALL), errmsg("SPI tupletable is not cleaned when initializing SPI.")));
|
|
}
|
|
|
|
old_ctx = _SPI_procmem(); /* switch to procedure memory context */
|
|
|
|
tup_tab_cxt = AllocSetContextCreate(CurrentMemoryContext, "SPI TupTable", ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);
|
|
(void)MemoryContextSwitchTo(tup_tab_cxt);
|
|
|
|
u_sess->SPI_cxt._current->tuptable = tuptable = (SPITupleTable *)palloc(sizeof(SPITupleTable));
|
|
tuptable->tuptabcxt = tup_tab_cxt;
|
|
|
|
if (DestSPI == self->mydest) {
|
|
tuptable->alloced = tuptable->free = 128;
|
|
} else {
|
|
tuptable->alloced = tuptable->free = DEFAULT_SAMPLE_ROWCNT;
|
|
}
|
|
|
|
tuptable->vals = (HeapTuple *)palloc(tuptable->alloced * sizeof(HeapTuple));
|
|
tuptable->tupdesc = CreateTupleDescCopy(typeinfo);
|
|
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
/*
|
|
* spi_printtup
|
|
* store tuple retrieved by Executor into SPITupleTable
|
|
* of current SPI procedure
|
|
*/
|
|
void spi_printtup(TupleTableSlot *slot, DestReceiver *self)
|
|
{
|
|
SPITupleTable *tuptable = NULL;
|
|
MemoryContext old_ctx;
|
|
HeapTuple tuple;
|
|
|
|
spi_check_connid();
|
|
tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
if (tuptable == NULL) {
|
|
ereport(ERROR, (errcode(ERRORCODE_SPI_IMPROPER_CALL), errmsg("tuple is NULL when store to SPI tupletable.")));
|
|
}
|
|
|
|
old_ctx = MemoryContextSwitchTo(tuptable->tuptabcxt);
|
|
|
|
if (tuptable->free == 0) {
|
|
/* Double the size of the pointer array */
|
|
tuptable->free = tuptable->alloced;
|
|
tuptable->alloced += tuptable->free;
|
|
tuptable->vals = (HeapTuple *)repalloc(tuptable->vals, tuptable->alloced * sizeof(HeapTuple));
|
|
}
|
|
|
|
tuple = ExecCopySlotTuple(slot);
|
|
/* check ExecCopySlotTuple result */
|
|
if (tuple == NULL) {
|
|
ereport(WARNING, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
|
|
(errmsg("slot tuple copy failed, unexpexted return value. Maybe the slot tuple is invalid or tuple "
|
|
"data is null "))));
|
|
}
|
|
|
|
tuptable->vals[tuptable->alloced - tuptable->free] = tuple;
|
|
|
|
(tuptable->free)--;
|
|
|
|
(void)MemoryContextSwitchTo(old_ctx);
|
|
}
|
|
|
|
/*
|
|
* Static functions
|
|
*/
|
|
#ifdef ENABLE_MOT
|
|
static bool _SPI_prepare_plan_guarded(const char *src, SPIPlanPtr plan, parse_query_func parser)
|
|
{
|
|
bool result = true;
|
|
PG_TRY();
|
|
{
|
|
_SPI_prepare_plan(src, plan, parser);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
// report error and reset error state, but first switch back to executor memory context
|
|
_SPI_execmem();
|
|
ErrorData* edata = CopyErrorData();
|
|
JitExec::JitReportParseError(edata, src);
|
|
FlushErrorState();
|
|
FreeErrorData(edata);
|
|
result = false;
|
|
}
|
|
PG_END_TRY();
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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->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.
|
|
*/
|
|
void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, parse_query_func parser)
|
|
{
|
|
#ifdef PGXC
|
|
_SPI_pgxc_prepare_plan(src, NULL, plan, parser);
|
|
}
|
|
|
|
/*
|
|
* _SPI_pgxc_prepare_plan: Optionally accepts a parsetree which allows it to
|
|
* bypass the parse phase, and directly analyse, rewrite and plan. Meant to be
|
|
* called for internally executed execute-direct statements that are
|
|
* transparent to the user.
|
|
*/
|
|
static void _SPI_pgxc_prepare_plan(const char *src, List *src_parsetree, SPIPlanPtr plan, parse_query_func parser)
|
|
{
|
|
#endif
|
|
List *raw_parsetree_list = NIL;
|
|
List *plancache_list = NIL;
|
|
ListCell *list_item = NULL;
|
|
ErrorContextCallback spi_err_context;
|
|
|
|
/*
|
|
* Setup error traceback support for ereport()
|
|
*/
|
|
spi_err_context.callback = _SPI_error_callback;
|
|
spi_err_context.arg = (void *)src;
|
|
spi_err_context.previous = t_thrd.log_cxt.error_context_stack;
|
|
t_thrd.log_cxt.error_context_stack = &spi_err_context;
|
|
NodeTag old_node_tag = t_thrd.postgres_cxt.cur_command_tag;
|
|
|
|
/*
|
|
* Parse the request string into a list of raw parse trees.
|
|
*/
|
|
#ifdef PGXC
|
|
/* Parse it only if there isn't an already parsed tree passed */
|
|
if (src_parsetree != NIL)
|
|
raw_parsetree_list = src_parsetree;
|
|
else
|
|
#endif
|
|
raw_parsetree_list = pg_parse_query(src, NULL, parser);
|
|
/*
|
|
* Do parse analysis and rule rewrite for each raw parsetree, storing the
|
|
* results into unsaved plancache entries.
|
|
*/
|
|
plancache_list = NIL;
|
|
int i = 0;
|
|
bool enable_spi_gpc = false;
|
|
foreach (list_item, raw_parsetree_list) {
|
|
Node *parsetree = (Node *)lfirst(list_item);
|
|
List *stmt_list = NIL;
|
|
CachedPlanSource *plansource = NULL;
|
|
t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(parsetree);
|
|
// get cachedplan if has any
|
|
enable_spi_gpc = false;
|
|
if (ENABLE_CN_GPC && u_sess->SPI_cxt._current->spi_hash_key != INVALID_SPI_KEY
|
|
&& u_sess->SPI_cxt._current->visit_id != (uint32)-1) {
|
|
enable_spi_gpc = SPIParseEnableGPC(parsetree);
|
|
}
|
|
enable_spi_gpc = false;
|
|
if (enable_spi_gpc) {
|
|
Assert(src_parsetree == NIL);
|
|
Assert(plan->oneshot == false);
|
|
u_sess->SPI_cxt._current->plan_id = i;
|
|
SPISign spi_signature;
|
|
spi_signature.spi_key = u_sess->SPI_cxt._current->spi_hash_key;
|
|
spi_signature.func_oid = u_sess->SPI_cxt._current->func_oid;
|
|
spi_signature.spi_id = u_sess->SPI_cxt._current->visit_id;
|
|
spi_signature.plansource_id = u_sess->SPI_cxt._current->plan_id;
|
|
plansource = g_instance.plan_cache->Fetch(src, (uint32)strlen(src), plan->nargs,
|
|
plan->argtypes, &spi_signature);
|
|
if (plansource) {
|
|
Assert(plansource->is_oneshot == false);
|
|
Assert(plansource->gpc.status.IsSharePlan());
|
|
plancache_list = lappend(plancache_list, plansource);
|
|
i++;
|
|
// get param into expr
|
|
ParseState* pstate = NULL;
|
|
pstate = make_parsestate(NULL);
|
|
pstate->p_sourcetext = src;
|
|
(*plan->parserSetup)(pstate, plan->parserSetupArg);
|
|
(void*)transformTopLevelStmt(pstate, parsetree);
|
|
free_parsestate(pstate);
|
|
/* set spiplan id and spi_key if plancache is shared */
|
|
plan->id = u_sess->SPI_cxt._current->visit_id;
|
|
plan->spi_key = u_sess->SPI_cxt._current->spi_hash_key;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create the CachedPlanSource before we do parse analysis, since it
|
|
* needs to see the unmodified raw parse tree.
|
|
*/
|
|
plansource = CreateCachedPlan(parsetree, src,
|
|
#ifdef PGXC
|
|
NULL,
|
|
#endif
|
|
CreateCommandTag(parsetree), enable_spi_gpc);
|
|
|
|
/*
|
|
* 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_params(parsetree, src, plan->parserSetup, plan->parserSetupArg);
|
|
} else {
|
|
stmt_list = pg_analyze_and_rewrite(parsetree, src, plan->argtypes, plan->nargs);
|
|
}
|
|
plan->stmt_list = list_concat(plan->stmt_list, list_copy(stmt_list));
|
|
/* Finish filling in the CachedPlanSource */
|
|
CompleteCachedPlan(plansource, stmt_list, NULL, plan->argtypes, NULL, plan->nargs, plan->parserSetup,
|
|
plan->parserSetupArg, plan->cursor_options, false, /* not fixed result */
|
|
"");
|
|
|
|
if (enable_spi_gpc && plansource->gpc.status.IsSharePlan()) {
|
|
/* for needRecompilePlan, plansource need recreate each time, no need to global it.
|
|
* for temp table, only one session can use it, no need to global it */
|
|
bool need_recreate_everytime = checkRecompileCondition(plansource);
|
|
bool has_tmp_table = false;
|
|
ListCell* cell = NULL;
|
|
foreach(cell, stmt_list) {
|
|
Query* query = (Query*)lfirst(cell);
|
|
if (contains_temp_tables(query->rtable)) {
|
|
has_tmp_table = true;
|
|
break;
|
|
}
|
|
}
|
|
if (need_recreate_everytime || has_tmp_table) {
|
|
plansource->gpc.status.SetKind(GPC_UNSHARED);
|
|
} else {
|
|
/* set spiplan id and spi_key if plancache is shared */
|
|
plan->id = u_sess->SPI_cxt._current->visit_id;
|
|
plan->spi_key = u_sess->SPI_cxt._current->spi_hash_key;
|
|
}
|
|
}
|
|
plancache_list = lappend(plancache_list, plansource);
|
|
i++;
|
|
}
|
|
|
|
plan->plancache_list = plancache_list;
|
|
plan->oneshot = false;
|
|
u_sess->SPI_cxt._current->plan_id = -1;
|
|
t_thrd.postgres_cxt.cur_command_tag = old_node_tag;
|
|
|
|
/*
|
|
* Pop the error context stack
|
|
*/
|
|
t_thrd.log_cxt.error_context_stack = spi_err_context.previous;
|
|
}
|
|
|
|
static void SPIParseOneShotPlan(CachedPlanSource* plansource, SPIPlanPtr plan)
|
|
{
|
|
Node *parsetree = plansource->raw_parse_tree;
|
|
const char *src = plansource->query_string;
|
|
List *statement_list = NIL;
|
|
|
|
if (!plansource->is_complete) {
|
|
/*
|
|
* Parameter datatypes are driven by parserSetup hook if provided,
|
|
* otherwise we use the fixed parameter list.
|
|
*/
|
|
if (plan->parserSetup != NULL) {
|
|
Assert(plan->nargs == 0);
|
|
statement_list = pg_analyze_and_rewrite_params(parsetree, src, plan->parserSetup, plan->parserSetupArg);
|
|
} else {
|
|
statement_list = pg_analyze_and_rewrite(parsetree, src, plan->argtypes, plan->nargs);
|
|
}
|
|
|
|
/* Finish filling in the CachedPlanSource */
|
|
CompleteCachedPlan(plansource, statement_list, NULL, plan->argtypes, NULL, plan->nargs, plan->parserSetup,
|
|
plan->parserSetupArg, plan->cursor_options, false, ""); /* not fixed result */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
void _SPI_prepare_oneshot_plan(const char *src, SPIPlanPtr plan, parse_query_func parser)
|
|
{
|
|
List *raw_parsetree_list = NIL;
|
|
List *plancache_list = NIL;
|
|
ListCell *list_item = NULL;
|
|
ErrorContextCallback spi_err_context;
|
|
List *query_string_locationlist = NIL;
|
|
int stmt_num = 0;
|
|
NodeTag old_node_tag = t_thrd.postgres_cxt.cur_command_tag;
|
|
/*
|
|
* Setup error traceback support for ereport()
|
|
*/
|
|
spi_err_context.callback = _SPI_error_callback;
|
|
spi_err_context.arg = (void *)src;
|
|
spi_err_context.previous = t_thrd.log_cxt.error_context_stack;
|
|
t_thrd.log_cxt.error_context_stack = &spi_err_context;
|
|
|
|
/*
|
|
* Parse the request string into a list of raw parse trees.
|
|
*/
|
|
raw_parsetree_list = pg_parse_query(src, &query_string_locationlist, parser);
|
|
|
|
/*
|
|
* Construct plancache entries, but don't do parse analysis yet.
|
|
*/
|
|
plancache_list = NIL;
|
|
char **query_string_single = NULL;
|
|
|
|
foreach (list_item, raw_parsetree_list) {
|
|
Node *parsetree = (Node *)lfirst(list_item);
|
|
CachedPlanSource *plansource = NULL;
|
|
t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(parsetree);
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
if (g_instance.attr.attr_sql.enableRemoteExcute) {
|
|
libpqsw_check_ddl_on_primary(CreateCommandTag(parsetree));
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (IS_PGXC_COORDINATOR && PointerIsValid(query_string_locationlist) &&
|
|
list_length(query_string_locationlist) > 1) {
|
|
#else
|
|
if (PointerIsValid(query_string_locationlist) && list_length(query_string_locationlist) > 1) {
|
|
#endif
|
|
query_string_single = get_next_snippet(query_string_single, src, query_string_locationlist, &stmt_num);
|
|
plansource =
|
|
CreateOneShotCachedPlan(parsetree, query_string_single[stmt_num - 1], CreateCommandTag(parsetree));
|
|
} else {
|
|
plansource = CreateOneShotCachedPlan(parsetree, src, CreateCommandTag(parsetree));
|
|
}
|
|
|
|
plancache_list = lappend(plancache_list, plansource);
|
|
}
|
|
|
|
plan->plancache_list = plancache_list;
|
|
plan->oneshot = true;
|
|
t_thrd.postgres_cxt.cur_command_tag = old_node_tag;
|
|
|
|
/*
|
|
* Pop the error context stack
|
|
*/
|
|
t_thrd.log_cxt.error_context_stack = spi_err_context.previous;
|
|
}
|
|
|
|
bool RememberSpiPlanRef(CachedPlan* cplan, CachedPlanSource* plansource)
|
|
{
|
|
bool ans = false;
|
|
/* incase commit/rollback release cachedplan from resource owner during execute spi plan,
|
|
* make sure current spi has cplan. So without commit/rollback, spi should has 2 refcount on cplan */
|
|
if (cplan->isShared()) {
|
|
(void)pg_atomic_fetch_add_u32((volatile uint32*)&cplan->global_refcount, 1);
|
|
ans = true;
|
|
} else if (!plansource->is_oneshot) {
|
|
cplan->refcount++;
|
|
ans = true;
|
|
}
|
|
if (ans) {
|
|
SPICachedPlanStack* cur_spi_cplan = (SPICachedPlanStack*)MemoryContextAlloc(
|
|
SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), sizeof(SPICachedPlanStack));
|
|
cur_spi_cplan->previous = u_sess->SPI_cxt.spi_exec_cplan_stack;
|
|
cur_spi_cplan->cplan = cplan;
|
|
cur_spi_cplan->subtranid = GetCurrentSubTransactionId();
|
|
u_sess->SPI_cxt.spi_exec_cplan_stack = cur_spi_cplan;
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
void ForgetSpiPlanRef()
|
|
{
|
|
if (u_sess->SPI_cxt.spi_exec_cplan_stack == NULL)
|
|
return;
|
|
SPICachedPlanStack* cur_spi_cplan = u_sess->SPI_cxt.spi_exec_cplan_stack;
|
|
CachedPlan* cplan = cur_spi_cplan->cplan;
|
|
u_sess->SPI_cxt.spi_exec_cplan_stack = cur_spi_cplan->previous;
|
|
pfree_ext(cur_spi_cplan);
|
|
ReleaseCachedPlan(cplan, false);
|
|
}
|
|
|
|
ResourceOwner AddCplanRefAgainIfNecessary(SPIPlanPtr plan,
|
|
CachedPlanSource* plansource, CachedPlan* cplan, TransactionId oldTransactionId, ResourceOwner oldOwner)
|
|
{
|
|
/*
|
|
* When commit/rollback occurs, or its subtransaction is finished by savepoint, except
|
|
* for the oneshot plan, plan cache's refcount has already been released in resourceowner.
|
|
* To match subsequent processing, an extra increment about reference is required here.
|
|
*/
|
|
if ((oldTransactionId != SPI_get_top_transaction_id() ||
|
|
!ResourceOwnerIsValid(oldOwner)) && !plansource->is_oneshot) {
|
|
/* need addrefcount and save into resource owner again */
|
|
if (plan->saved)
|
|
ResourceOwnerEnlargePlanCacheRefs(t_thrd.utils_cxt.CurrentResourceOwner);
|
|
if (!cplan->isShared()) {
|
|
cplan->refcount++;
|
|
} else {
|
|
(void)pg_atomic_fetch_add_u32((volatile uint32*)&cplan->global_refcount, 1);
|
|
}
|
|
if (plan->saved)
|
|
ResourceOwnerRememberPlanCacheRef(t_thrd.utils_cxt.CurrentResourceOwner, cplan);
|
|
|
|
return plan->saved ? t_thrd.utils_cxt.CurrentResourceOwner : oldOwner;
|
|
}
|
|
|
|
return oldOwner;
|
|
}
|
|
|
|
void FreeMultiQueryString(SPIPlanPtr plan)
|
|
{
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (IS_PGXC_COORDINATOR && PointerIsValid(plan->plancache_list) && list_length(plan->plancache_list) > 1) {
|
|
#else
|
|
if (plan->oneshot && PointerIsValid(plan->plancache_list) && list_length(plan->plancache_list) > 1) {
|
|
#endif
|
|
ListCell *list_item = NULL;
|
|
|
|
foreach (list_item, plan->plancache_list) {
|
|
CachedPlanSource *PlanSource = (CachedPlanSource *)lfirst(list_item);
|
|
if (PlanSource->query_string != NULL) {
|
|
pfree_ext((PlanSource->query_string));
|
|
PlanSource->query_string = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Execute the given plan with the given parameter values
|
|
*
|
|
* 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
|
|
* read_only: TRUE for read-only execution (no CommandCounterIncrement)
|
|
* 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
|
|
* tcount: execution tuple-count limit, or 0 for none
|
|
*/
|
|
static int _SPI_execute_plan0(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot,
|
|
bool read_only, bool fire_triggers, long tcount, bool from_lock)
|
|
{
|
|
int my_res = 0;
|
|
uint32 my_processed = 0;
|
|
Oid my_lastoid = InvalidOid;
|
|
MemoryContext tmp_cxt = NULL;
|
|
SPITupleTable *my_tuptable = NULL;
|
|
int res = 0;
|
|
bool pushed_active_snap = false;
|
|
ErrorContextCallback spi_err_context;
|
|
CachedPlan *cplan = NULL;
|
|
ListCell *lc1 = NULL;
|
|
bool tmp_enable_light_proxy = u_sess->attr.attr_sql.enable_light_proxy;
|
|
NodeTag old_command_tag = t_thrd.postgres_cxt.cur_command_tag;
|
|
TransactionId oldTransactionId = SPI_get_top_transaction_id();
|
|
bool need_remember_cplan = false;
|
|
|
|
/*
|
|
* With savepoint in STP feature, CurrentResourceOwner is different before and after _SPI_pquery.
|
|
* We need to hold the original one in order to forget the snapshot, plan reference.and etc.
|
|
*/
|
|
ResourceOwner oldOwner = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
|
|
/* not allow Light CN */
|
|
u_sess->attr.attr_sql.enable_light_proxy = false;
|
|
|
|
/*
|
|
* Setup error traceback support for ereport()
|
|
*/
|
|
spi_err_context.callback = _SPI_error_callback;
|
|
spi_err_context.arg = NULL; /* we'll fill this below */
|
|
spi_err_context.previous = t_thrd.log_cxt.error_context_stack;
|
|
t_thrd.log_cxt.error_context_stack = &spi_err_context;
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
if (snapshot != InvalidSnapshot) {
|
|
if (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;
|
|
}
|
|
}
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
AutoDopControl dopControl;
|
|
dopControl.CloseSmp();
|
|
#endif
|
|
|
|
foreach (lc1, plan->plancache_list) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc1);
|
|
List *stmt_list = NIL;
|
|
|
|
spi_err_context.arg = (void *)plansource->query_string;
|
|
t_thrd.postgres_cxt.cur_command_tag = transform_node_tag(plansource->raw_parse_tree);
|
|
|
|
/*
|
|
* If this is a one-shot plan, we still need to do parse analysis.
|
|
*/
|
|
if (plan->oneshot) {
|
|
SPIParseOneShotPlan(plansource, plan);
|
|
}
|
|
|
|
/*
|
|
* Replan if needed, and increment plan refcount. If it's a saved
|
|
* plan, the refcount must be backed by the CurrentResourceOwner.
|
|
*/
|
|
cplan = GetCachedPlan(plansource, paramLI, plan->saved);
|
|
|
|
/* use shared plan here, add refcount */
|
|
if (cplan->isShared())
|
|
(void)pg_atomic_fetch_add_u32((volatile uint32*)&cplan->global_refcount, 1);
|
|
|
|
if (ENABLE_GPC && plansource->gplan)
|
|
stmt_list = CopyLocalStmt(cplan->stmt_list, u_sess->SPI_cxt._current->execCxt, &tmp_cxt);
|
|
else
|
|
stmt_list = cplan->stmt_list;
|
|
|
|
/*
|
|
* In the default non-read-only case, get a new snapshot, replacing
|
|
* any that we pushed in a previous cycle.
|
|
*/
|
|
if (snapshot == InvalidSnapshot && !read_only) {
|
|
if (pushed_active_snap) {
|
|
PopActiveSnapshot();
|
|
}
|
|
PushActiveSnapshot(GetTransactionSnapshot());
|
|
pushed_active_snap = true;
|
|
}
|
|
|
|
/*
|
|
* Handle No Plans Here.
|
|
* 1 release cplan.
|
|
* 2 increase counter if needed
|
|
*/
|
|
if (stmt_list == NIL) {
|
|
ReleaseCachedPlan(cplan, plan->saved);
|
|
cplan = NULL;
|
|
|
|
if (!read_only) {
|
|
CommandCounterIncrement();
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
need_remember_cplan = RememberSpiPlanRef(cplan, plansource);
|
|
|
|
for (int i = 0; i < stmt_list->length; i++) {
|
|
Node *stmt = (Node *)list_nth(stmt_list, i);
|
|
bool canSetTag = false;
|
|
|
|
u_sess->SPI_cxt._current->processed = 0;
|
|
u_sess->SPI_cxt._current->lastoid = InvalidOid;
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
|
|
if (IsA(stmt, PlannedStmt)) {
|
|
canSetTag = ((PlannedStmt *)stmt)->canSetTag;
|
|
|
|
if (((PlannedStmt*)stmt)->commandType != CMD_SELECT) {
|
|
SPI_forbid_exec_push_down_with_exception();
|
|
}
|
|
} else {
|
|
/* utilities are canSetTag if only thing in list */
|
|
canSetTag = (list_length(stmt_list) == 1);
|
|
|
|
if (IsA(stmt, CopyStmt)) {
|
|
CopyStmt *cstmt = (CopyStmt *)stmt;
|
|
|
|
if (cstmt->filename == NULL) {
|
|
my_res = SPI_ERROR_COPY;
|
|
goto fail;
|
|
}
|
|
} else if (IsA(stmt, TransactionStmt)) {
|
|
my_res = SPI_ERROR_TRANSACTION;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (read_only && !CommandIsReadOnly(stmt)) {
|
|
pipelined_readonly_ereport();
|
|
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
/* translator: %s is a SQL statement name */
|
|
errmsg("%s is not allowed in a non-volatile function", CreateCommandTag(stmt))));
|
|
}
|
|
|
|
/*
|
|
* If not read-only mode, advance the command counter before each
|
|
* command and update the snapshot.
|
|
*/
|
|
if (!read_only) {
|
|
CommandCounterIncrement();
|
|
UpdateActiveSnapshotCommandId();
|
|
}
|
|
|
|
DestReceiver *dest = CreateDestReceiver(canSetTag ? u_sess->SPI_cxt._current->dest : DestNone);
|
|
if (u_sess->SPI_cxt._current->dest == DestSqlProcSPI && u_sess->hook_cxt.pluginSpiReciverParamHook)
|
|
((SpiReciverParamHook)u_sess->hook_cxt.pluginSpiReciverParamHook)(dest,plan);
|
|
|
|
if (IsA(stmt, PlannedStmt) && ((PlannedStmt *)stmt)->utilityStmt == NULL) {
|
|
QueryDesc *qdesc = NULL;
|
|
Snapshot snap = ActiveSnapshotSet() ? GetActiveSnapshot() : InvalidSnapshot;
|
|
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
Port *MyPort = u_sess->proc_cxt.MyProcPort;
|
|
if (MyPort && MyPort->protocol_config && MyPort->protocol_config->fn_set_DR_params) {
|
|
MyPort->protocol_config->fn_set_DR_params(dest, ((PlannedStmt *)stmt)->planTree->targetlist);
|
|
}
|
|
#endif
|
|
|
|
#ifdef ENABLE_MOT
|
|
qdesc = CreateQueryDesc((PlannedStmt *)stmt, plansource->query_string, snap, crosscheck_snapshot, dest,
|
|
paramLI, 0, plansource->mot_jit_context);
|
|
#else
|
|
qdesc = CreateQueryDesc((PlannedStmt *)stmt, plansource->query_string, snap, crosscheck_snapshot, dest,
|
|
paramLI, 0);
|
|
#endif
|
|
|
|
res = _SPI_pquery(qdesc, fire_triggers, canSetTag ? tcount : 0, from_lock);
|
|
ResourceOwner tmp = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
|
FreeQueryDesc(qdesc);
|
|
t_thrd.utils_cxt.CurrentResourceOwner = tmp;
|
|
} else {
|
|
char completionTag[COMPLETION_TAG_BUFSIZE];
|
|
|
|
/*
|
|
* Reset schema name for analyze in stored procedure.
|
|
* When analyze has error, there is no time for schema name to be reseted.
|
|
* It will be kept in the plan for stored procedure and the result
|
|
* is uncertain.
|
|
*/
|
|
if (IsA(stmt, VacuumStmt)) {
|
|
ClearVacuumStmt((VacuumStmt *)stmt);
|
|
}
|
|
if (IsA(stmt, CreateSeqStmt)) {
|
|
ClearCreateSeqStmtUUID((CreateSeqStmt *)stmt);
|
|
}
|
|
if (IsA(stmt, CreateStmt)) {
|
|
ClearCreateStmtUUIDS((CreateStmt *)stmt);
|
|
}
|
|
|
|
if (IsA(stmt, CreateRoleStmt) || IsA(stmt, AlterRoleStmt) ||
|
|
(IsA(stmt, VariableSetStmt) && ((VariableSetStmt *)stmt)->kind == VAR_SET_ROLEPWD)) {
|
|
stmt = (Node *)copyObject(stmt);
|
|
}
|
|
|
|
processutility_context proutility_cxt;
|
|
proutility_cxt.parse_tree = stmt;
|
|
proutility_cxt.query_string = plansource->query_string;
|
|
proutility_cxt.readOnlyTree = true; /* protect plancache's node tree */
|
|
proutility_cxt.params = paramLI;
|
|
proutility_cxt.is_top_level = false; /* not top level */
|
|
ProcessUtility(&proutility_cxt,
|
|
dest,
|
|
#ifdef PGXC
|
|
false,
|
|
#endif /* PGXC */
|
|
completionTag, PROCESS_UTILITY_QUERY);
|
|
|
|
/* Update "processed" if stmt returned tuples */
|
|
if (u_sess->SPI_cxt._current->tuptable) {
|
|
u_sess->SPI_cxt._current->processed =
|
|
u_sess->SPI_cxt._current->tuptable->alloced - u_sess->SPI_cxt._current->tuptable->free;
|
|
}
|
|
|
|
/*
|
|
* CREATE TABLE AS is a messy special case for historical
|
|
* reasons. We must set u_sess->SPI_cxt._current->processed even though
|
|
* the tuples weren't returned to the caller, and we must
|
|
* return a special result code if the statement was spelled
|
|
* SELECT INTO.
|
|
*/
|
|
if (IsA(stmt, CreateTableAsStmt) && ((CreateTableAsStmt *)stmt)->relkind != OBJECT_MATVIEW) {
|
|
Assert(strncmp(completionTag, "SELECT ", 7) == 0);
|
|
u_sess->SPI_cxt._current->processed = strtoul(completionTag + 7, NULL, 10);
|
|
if (((CreateTableAsStmt *)stmt)->is_select_into) {
|
|
res = SPI_OK_SELINTO;
|
|
} else {
|
|
res = SPI_OK_UTILITY;
|
|
}
|
|
} else {
|
|
res = SPI_OK_UTILITY;
|
|
}
|
|
|
|
if (IsA(stmt, CreateRoleStmt) || IsA(stmt, AlterRoleStmt) ||
|
|
(IsA(stmt, VariableSetStmt) && ((VariableSetStmt *)stmt)->kind == VAR_SET_ROLEPWD)) {
|
|
pfree_ext(stmt);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The last canSetTag query sets the status values returned to the
|
|
* caller. Be careful to free any tuptables not returned, to
|
|
* avoid intratransaction memory leak.
|
|
*/
|
|
if (canSetTag) {
|
|
my_processed = u_sess->SPI_cxt._current->processed;
|
|
my_lastoid = u_sess->SPI_cxt._current->lastoid;
|
|
SPI_freetuptable(my_tuptable);
|
|
my_tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
my_res = res;
|
|
} else {
|
|
SPI_freetuptable(u_sess->SPI_cxt._current->tuptable);
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
}
|
|
/* we know that the receiver doesn't need a destroy call */
|
|
if (res < 0) {
|
|
my_res = res;
|
|
goto fail;
|
|
}
|
|
/* When commit/rollback occurs
|
|
* the plan cache will be release refcount by resourceowner(except for oneshot plan) */
|
|
oldOwner = AddCplanRefAgainIfNecessary(plan, plansource, cplan, oldTransactionId, oldOwner);
|
|
}
|
|
|
|
/* Done with this plan, so release refcount */
|
|
if (need_remember_cplan)
|
|
ForgetSpiPlanRef();
|
|
ResourceOwner tmp = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
|
ReleaseCachedPlan(cplan, plan->saved);
|
|
t_thrd.utils_cxt.CurrentResourceOwner = tmp;
|
|
cplan = NULL;
|
|
if (ENABLE_GPC && tmp_cxt)
|
|
MemoryContextDelete(tmp_cxt);
|
|
|
|
/*
|
|
* 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 (!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 != NULL) {
|
|
if (need_remember_cplan)
|
|
ForgetSpiPlanRef();
|
|
ResourceOwner tmp = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
|
ReleaseCachedPlan(cplan, plan->saved);
|
|
t_thrd.utils_cxt.CurrentResourceOwner = tmp;
|
|
}
|
|
/*
|
|
* When plan->plancache_list > 1 means it's a multi query and have been malloc memory
|
|
* through get_next_snippet, so we need free them here.
|
|
*/
|
|
FreeMultiQueryString(plan);
|
|
|
|
/*
|
|
* Pop the error context stack
|
|
*/
|
|
t_thrd.log_cxt.error_context_stack = spi_err_context.previous;
|
|
|
|
/* Save results for caller */
|
|
SPI_processed = my_processed;
|
|
u_sess->SPI_cxt.lastoid = my_lastoid;
|
|
SPI_tuptable = my_tuptable;
|
|
|
|
/* tuptable now is caller's responsibility, not SPI's */
|
|
u_sess->SPI_cxt._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;
|
|
}
|
|
|
|
u_sess->attr.attr_sql.enable_light_proxy = tmp_enable_light_proxy;
|
|
t_thrd.postgres_cxt.cur_command_tag = old_command_tag;
|
|
|
|
return my_res;
|
|
}
|
|
|
|
static bool IsNeedSubTxnForSPIPlan(SPIPlanPtr plan)
|
|
{
|
|
/* not required to act as the same as O */
|
|
if (!PLSTMT_IMPLICIT_SAVEPOINT) {
|
|
return false;
|
|
}
|
|
|
|
/* not inside exception block */
|
|
if (u_sess->SPI_cxt.portal_stp_exception_counter == 0) {
|
|
return false;
|
|
}
|
|
|
|
/* wrap statement doing modifications with subtransaction, so changes can be rollback. */
|
|
ListCell *lc1 = NULL;
|
|
foreach (lc1, plan->plancache_list) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc1);
|
|
|
|
if (plan->oneshot) {
|
|
SPIParseOneShotPlan(plansource, plan);
|
|
}
|
|
|
|
ListCell* l = NULL;
|
|
foreach (l, plansource->query_list) {
|
|
Query* qry = (Query*)lfirst(l);
|
|
|
|
/*
|
|
* Here act as the opsite as CommandIsReadOnly does. (1) utility cmd;
|
|
* (2) SELECT FOR UPDATE/SHARE; (3) data-modifying CTE.
|
|
*/
|
|
if (qry->commandType != CMD_SELECT || qry->rowMarks != NULL || qry->hasModifyingCTE) {
|
|
return true;
|
|
}
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
/*
|
|
* DN aborts subtransaction automatically once error occurs. Current start an new implicit
|
|
* savepoint to isolate from runtime error even that it is a read only statement.
|
|
*
|
|
* If statement contains only system table, it will run at CN without savepint. It's
|
|
* a worth thing to check it?
|
|
*/
|
|
return list_length(plansource->relationOids) != 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Execute plan with the given parameter values
|
|
*
|
|
* Here we would wrap it with a savepoint for rollback its updates.
|
|
*/
|
|
extern int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot,
|
|
Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount, bool from_lock)
|
|
{
|
|
int my_res = 0;
|
|
|
|
if (IsNeedSubTxnForSPIPlan(plan)) {
|
|
volatile int curExceptionCounter;
|
|
MemoryContext oldcontext = CurrentMemoryContext;
|
|
int64 stackId = u_sess->plsql_cxt.nextStackEntryId;
|
|
|
|
/* start an implicit savepoint for this stmt. */
|
|
SPI_savepoint_create(NULL);
|
|
|
|
/* switch into old memory context, don't run it under subtransaction. */
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
curExceptionCounter = u_sess->SPI_cxt.portal_stp_exception_counter;
|
|
PG_TRY();
|
|
{
|
|
my_res = _SPI_execute_plan0(plan, paramLI, snapshot,
|
|
crosscheck_snapshot, read_only, fire_triggers, tcount, from_lock);
|
|
}
|
|
PG_CATCH();
|
|
{
|
|
/* rollback this stmt's updates. */
|
|
if (curExceptionCounter == u_sess->SPI_cxt.portal_stp_exception_counter &&
|
|
GetCurrentTransactionName() == NULL) {
|
|
SPI_savepoint_rollbackAndRelease(NULL, InvalidTransactionId);
|
|
stp_cleanup_subxact_resource(stackId);
|
|
}
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
|
|
/*
|
|
* if there is any new inner subtransaction, don't release savepoint.
|
|
* any idea for this remained subtransaction?
|
|
*/
|
|
if (curExceptionCounter == u_sess->SPI_cxt.portal_stp_exception_counter &&
|
|
GetCurrentTransactionName() == NULL) {
|
|
SPI_savepoint_release(NULL);
|
|
stp_cleanup_subxact_resource(stackId);
|
|
}
|
|
} else {
|
|
my_res = _SPI_execute_plan0(plan, paramLI, snapshot,
|
|
crosscheck_snapshot, read_only, fire_triggers, tcount, from_lock);
|
|
}
|
|
|
|
return my_res;
|
|
}
|
|
|
|
/* For transaction, mysubid is TopSubTransactionId */
|
|
void ReleaseSpiPlanRef(TransactionId mysubid)
|
|
{
|
|
SPICachedPlanStack* cur_spi_cplan = u_sess->SPI_cxt.spi_exec_cplan_stack;
|
|
|
|
while (cur_spi_cplan != NULL && cur_spi_cplan->subtranid >= mysubid) {
|
|
CachedPlan* cplan = cur_spi_cplan->cplan;
|
|
u_sess->SPI_cxt.spi_exec_cplan_stack = cur_spi_cplan->previous;
|
|
pfree_ext(cur_spi_cplan);
|
|
cur_spi_cplan = u_sess->SPI_cxt.spi_exec_cplan_stack;
|
|
ReleaseCachedPlan(cplan, false);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convert arrays of query parameters to form wanted by planner and executor
|
|
*/
|
|
ParamListInfo _SPI_convert_params(int nargs, Oid *argtypes, Datum *Values, const char *Nulls,
|
|
Cursor_Data *cursor_data)
|
|
{
|
|
ParamListInfo param_list_info;
|
|
|
|
if (nargs > 0) {
|
|
int i;
|
|
|
|
param_list_info = (ParamListInfo)palloc(offsetof(ParamListInfoData, params) + nargs * sizeof(ParamExternData));
|
|
/* we have static list of params, so no hooks needed */
|
|
param_list_info->paramFetch = NULL;
|
|
param_list_info->paramFetchArg = NULL;
|
|
param_list_info->parserSetup = NULL;
|
|
param_list_info->parserSetupArg = NULL;
|
|
param_list_info->params_need_process = false;
|
|
param_list_info->uParamInfo = DEFUALT_INFO;
|
|
param_list_info->params_lazy_bind = false;
|
|
param_list_info->numParams = nargs;
|
|
|
|
for (i = 0; i < nargs; i++) {
|
|
ParamExternData *prm = ¶m_list_info->params[i];
|
|
|
|
prm->value = Values[i];
|
|
prm->isnull = (Nulls && Nulls[i] == 'n');
|
|
prm->pflags = PARAM_FLAG_CONST;
|
|
prm->ptype = argtypes[i];
|
|
if (cursor_data != NULL) {
|
|
CopyCursorInfoData(&prm->cursor_data, &cursor_data[i]);
|
|
}
|
|
prm->tabInfo = NULL;
|
|
}
|
|
} else {
|
|
param_list_info = NULL;
|
|
}
|
|
return param_list_info;
|
|
}
|
|
|
|
static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount, bool from_lock)
|
|
{
|
|
int operation = queryDesc->operation;
|
|
int eflags;
|
|
int res;
|
|
|
|
switch (operation) {
|
|
case CMD_SELECT:
|
|
Assert(queryDesc->plannedstmt->utilityStmt == NULL);
|
|
if (queryDesc->dest->mydest != DestSPI
|
|
#ifndef ENABLE_MULTIPLE_NODES
|
|
&& queryDesc->dest->mydest != DestRemote
|
|
#endif
|
|
) {
|
|
/* 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;
|
|
}
|
|
|
|
TransactionId oldTransactionId = SPI_get_top_transaction_id();
|
|
/*
|
|
* With savepoint in STP feature, CurrentResourceOwner is different before and after executor.
|
|
* We need to hold the original one in order to forget the snapshot, plan reference.and etc.
|
|
*/
|
|
ResourceOwner oldOwner = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
|
|
ExecutorStart(queryDesc, eflags);
|
|
|
|
bool forced_control = !from_lock && IS_PGXC_COORDINATOR &&
|
|
(t_thrd.wlm_cxt.parctl_state.simple == 1 || u_sess->wlm_cxt->is_active_statements_reset) &&
|
|
ENABLE_WORKLOAD_CONTROL;
|
|
Qid stroedproc_qid = { 0, 0, 0 };
|
|
unsigned char stroedproc_parctl_state_except = 0;
|
|
WLMStatusTag stroedproc_g_collectInfo_status = WLM_STATUS_RESERVE;
|
|
bool stroedproc_is_active_statements_reset = false;
|
|
if (forced_control) {
|
|
if (!u_sess->wlm_cxt->is_active_statements_reset && !u_sess->attr.attr_resource.enable_transaction_parctl) {
|
|
u_sess->wlm_cxt->stroedproc_rp_reserve = t_thrd.wlm_cxt.parctl_state.rp_reserve;
|
|
u_sess->wlm_cxt->stroedproc_rp_release = t_thrd.wlm_cxt.parctl_state.rp_release;
|
|
u_sess->wlm_cxt->stroedproc_release = t_thrd.wlm_cxt.parctl_state.release;
|
|
}
|
|
|
|
/* Retain the parameters of the main statement */
|
|
if (!IsQidInvalid(&u_sess->wlm_cxt->wlm_params.qid)) {
|
|
error_t rc = memcpy_s(&stroedproc_qid, sizeof(Qid), &u_sess->wlm_cxt->wlm_params.qid, sizeof(Qid));
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
stroedproc_parctl_state_except = t_thrd.wlm_cxt.parctl_state.except;
|
|
stroedproc_g_collectInfo_status = t_thrd.wlm_cxt.collect_info->status;
|
|
stroedproc_is_active_statements_reset = u_sess->wlm_cxt->is_active_statements_reset;
|
|
|
|
t_thrd.wlm_cxt.parctl_state.subquery = 1;
|
|
WLMInitQueryPlan(queryDesc);
|
|
dywlm_client_manager(queryDesc);
|
|
}
|
|
|
|
ExecutorRun(queryDesc, ForwardScanDirection, tcount);
|
|
|
|
if (forced_control) {
|
|
t_thrd.wlm_cxt.parctl_state.except = 0;
|
|
if (g_instance.wlm_cxt->dynamic_workload_inited && (t_thrd.wlm_cxt.parctl_state.simple == 0)) {
|
|
dywlm_client_release(&t_thrd.wlm_cxt.parctl_state);
|
|
} else {
|
|
// only release resource pool count
|
|
if (IS_PGXC_COORDINATOR && !IsConnFromCoord() &&
|
|
(u_sess->wlm_cxt->parctl_state_exit || IsQueuedSubquery())) {
|
|
WLMReleaseGroupActiveStatement();
|
|
}
|
|
}
|
|
|
|
WLMSetCollectInfoStatus(WLM_STATUS_FINISHED);
|
|
t_thrd.wlm_cxt.parctl_state.subquery = 0;
|
|
t_thrd.wlm_cxt.parctl_state.except = stroedproc_parctl_state_except;
|
|
t_thrd.wlm_cxt.collect_info->status = stroedproc_g_collectInfo_status;
|
|
u_sess->wlm_cxt->is_active_statements_reset = stroedproc_is_active_statements_reset;
|
|
if (!IsQidInvalid(&stroedproc_qid)) {
|
|
error_t rc = memcpy_s(&u_sess->wlm_cxt->wlm_params.qid, sizeof(Qid), &stroedproc_qid, sizeof(Qid));
|
|
securec_check(rc, "\0", "\0");
|
|
}
|
|
|
|
/* restore state condition if guc para is off since it contains unreleased count */
|
|
if (!u_sess->attr.attr_resource.enable_transaction_parctl && (u_sess->wlm_cxt->reserved_in_active_statements ||
|
|
u_sess->wlm_cxt->reserved_in_group_statements || u_sess->wlm_cxt->reserved_in_group_statements_simple)) {
|
|
t_thrd.wlm_cxt.parctl_state.rp_reserve = u_sess->wlm_cxt->stroedproc_rp_reserve;
|
|
t_thrd.wlm_cxt.parctl_state.rp_release = u_sess->wlm_cxt->stroedproc_rp_release;
|
|
t_thrd.wlm_cxt.parctl_state.release = u_sess->wlm_cxt->stroedproc_release;
|
|
}
|
|
}
|
|
|
|
u_sess->SPI_cxt._current->processed = queryDesc->estate->es_processed;
|
|
u_sess->SPI_cxt._current->lastoid = queryDesc->estate->es_lastoid;
|
|
|
|
if ((res == SPI_OK_SELECT || queryDesc->plannedstmt->hasReturning) && queryDesc->dest->mydest == DestSPI) {
|
|
if (_SPI_checktuples()) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED),
|
|
errmsg("consistency check on SPI tuple count failed when execute plan, %s",
|
|
(u_sess->SPI_cxt._current->tuptable == NULL) ? "tupletable is NULL." : "processed tuples is not matched.")));
|
|
}
|
|
}
|
|
|
|
ExecutorFinish(queryDesc);
|
|
/*
|
|
* If there are commit/rollback within stored procedure. Snapshot has already free during commit/rollback process
|
|
* Therefore, need to set queryDesc snapshots to NULL. Otherwise the reference will be stale pointers.
|
|
*/
|
|
if (oldTransactionId != SPI_get_top_transaction_id()) {
|
|
queryDesc->snapshot = NULL;
|
|
queryDesc->crosscheck_snapshot = NULL;
|
|
queryDesc->estate->es_snapshot = NULL;
|
|
queryDesc->estate->es_crosscheck_snapshot = NULL;
|
|
}
|
|
|
|
ResourceOwner tmp = t_thrd.utils_cxt.CurrentResourceOwner;
|
|
t_thrd.utils_cxt.CurrentResourceOwner = oldOwner;
|
|
ExecutorEnd(queryDesc);
|
|
t_thrd.utils_cxt.CurrentResourceOwner = tmp;
|
|
|
|
/* 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
|
|
*/
|
|
void _SPI_error_callback(void *arg)
|
|
{
|
|
/* We can't expose query when under analyzing with tablesample. */
|
|
if (u_sess->analyze_cxt.is_under_analyze) {
|
|
return;
|
|
}
|
|
|
|
const char *query = (const char *)arg;
|
|
int syntax_err_pos;
|
|
|
|
if (query == NULL) { /* in case arg wasn't set yet */
|
|
return;
|
|
}
|
|
|
|
char *mask_string = maskPassword(query);
|
|
if (mask_string == NULL) {
|
|
mask_string = (char *)query;
|
|
}
|
|
|
|
/*
|
|
* If there is a syntax error position, convert to internal syntax error;
|
|
* otherwise treat the query as an item of context stack
|
|
*/
|
|
syntax_err_pos = geterrposition();
|
|
if (syntax_err_pos > 0) {
|
|
errposition(0);
|
|
internalerrposition(syntax_err_pos);
|
|
internalerrquery(mask_string);
|
|
} else {
|
|
errcontext("SQL statement \"%s\"", mask_string);
|
|
}
|
|
|
|
if (mask_string != query) {
|
|
pfree(mask_string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* _SPI_cursor_operation
|
|
*
|
|
* Do a FETCH or MOVE in a cursor
|
|
*/
|
|
static void _SPI_cursor_operation(Portal portal, FetchDirection direction, long count, DestReceiver *dest)
|
|
{
|
|
long n_fetched;
|
|
|
|
/* Check that the portal is valid */
|
|
if (!PortalIsValid(portal)) {
|
|
ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("invalid portal in SPI cursor operation")));
|
|
}
|
|
|
|
/* Push the SPI stack */
|
|
SPI_STACK_LOG("begin", portal->sourceText, NULL);
|
|
if (_SPI_begin_call(true) < 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE),
|
|
errmsg("SPI stack is corrupted when perform cursor operation, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
/* Reset the SPI result (note we deliberately don't touch lastoid) */
|
|
SPI_processed = 0;
|
|
SPI_tuptable = NULL;
|
|
u_sess->SPI_cxt._current->processed = 0;
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
|
|
/* Run the cursor */
|
|
n_fetched = 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,
|
|
* u_sess->SPI_cxt._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.
|
|
*/
|
|
u_sess->SPI_cxt._current->processed = n_fetched;
|
|
|
|
if (dest->mydest == DestSPI && _SPI_checktuples()) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("consistency check on SPI tuple count failed, %s",
|
|
(u_sess->SPI_cxt._current->tuptable == NULL) ? "tupletable is NULL." : "processed tuples is not matched.")));
|
|
}
|
|
|
|
/* Put the result into place for access by caller */
|
|
SPI_processed = u_sess->SPI_cxt._current->processed;
|
|
SPI_tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
|
|
/* tuptable now is caller's responsibility, not SPI's */
|
|
u_sess->SPI_cxt._current->tuptable = NULL;
|
|
|
|
/* Pop the SPI stack */
|
|
SPI_STACK_LOG("end", portal->sourceText, NULL);
|
|
_SPI_end_call(true);
|
|
}
|
|
|
|
/*
|
|
* _SPI_hold_cursor
|
|
*
|
|
* hold a pinned cursor
|
|
*/
|
|
void _SPI_hold_cursor(bool is_rollback)
|
|
{
|
|
/* Push the SPI stack */
|
|
SPI_STACK_LOG("begin", NULL, NULL);
|
|
if (_SPI_begin_call(true) < 0) {
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE),
|
|
errmsg("SPI stack is corrupted when perform cursor operation, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
|
|
HoldPinnedPortals(is_rollback);
|
|
|
|
/* Pop the SPI stack */
|
|
SPI_STACK_LOG("end", NULL, NULL);
|
|
_SPI_end_call(true);
|
|
}
|
|
|
|
static MemoryContext _SPI_execmem(void)
|
|
{
|
|
return MemoryContextSwitchTo(u_sess->SPI_cxt._current->execCxt);
|
|
}
|
|
|
|
static MemoryContext _SPI_procmem(void)
|
|
{
|
|
return MemoryContextSwitchTo(u_sess->SPI_cxt._current->procCxt);
|
|
}
|
|
|
|
/*
|
|
* _SPI_begin_call: begin a SPI operation within a connected procedure
|
|
*/
|
|
int _SPI_begin_call(bool execmem)
|
|
{
|
|
if (u_sess->SPI_cxt._curid + 1 != u_sess->SPI_cxt._connected)
|
|
return SPI_ERROR_UNCONNECTED;
|
|
u_sess->SPI_cxt._curid++;
|
|
if (u_sess->SPI_cxt._current != &(u_sess->SPI_cxt._stack[u_sess->SPI_cxt._curid])) {
|
|
ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("SPI stack corrupted when begin SPI operation.")));
|
|
}
|
|
|
|
if (execmem) { /* switch to the Executor memory context */
|
|
_SPI_execmem();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* _SPI_end_call: end a SPI operation within a connected procedure
|
|
*
|
|
* Note: this currently has no failure return cases, so callers don't check
|
|
*/
|
|
int _SPI_end_call(bool procmem)
|
|
{
|
|
/*
|
|
* We're returning to procedure where u_sess->SPI_cxt._curid == u_sess->SPI_cxt._connected - 1
|
|
*/
|
|
u_sess->SPI_cxt._curid--;
|
|
|
|
/* must put last after smp thread has reach the sync point, then we can release the memory. */
|
|
if (procmem) {
|
|
/* switch to the procedure memory context */
|
|
_SPI_procmem();
|
|
/* and free Executor memory */
|
|
MemoryContextResetAndDeleteChildren(u_sess->SPI_cxt._current->execCxt);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool _SPI_checktuples(void)
|
|
{
|
|
uint64 processed = u_sess->SPI_cxt._current->processed;
|
|
SPITupleTable *tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
bool failed = false;
|
|
|
|
if (tuptable == NULL) { /* spi_dest_startup was not called */
|
|
failed = true;
|
|
}
|
|
|
|
else if (processed != (tuptable->alloced - tuptable->free)) {
|
|
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 = NULL;
|
|
MemoryContext parentcxt = u_sess->SPI_cxt._current->procCxt;
|
|
MemoryContext plancxt = NULL;
|
|
MemoryContext oldcxt = NULL;
|
|
ListCell *lc = NULL;
|
|
|
|
/* 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, so use smaller-than-default
|
|
* alloc parameters.
|
|
*/
|
|
plancxt = AllocSetContextCreate(parentcxt, "SPI Plan", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE,
|
|
ALLOCSET_SMALL_MAXSIZE);
|
|
oldcxt = MemoryContextSwitchTo(plancxt);
|
|
/* Copy the SPI_plan struct and subsidiary data into the new context */
|
|
newplan = (SPIPlanPtr)palloc(sizeof(_SPI_plan));
|
|
CopySPI_Plan(newplan, plan, plancxt);
|
|
|
|
/*
|
|
* 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);
|
|
/* shared spiplan's plancache is global */
|
|
if (plansource->gpc.status.IsPrivatePlan())
|
|
CachedPlanSetParentContext(plansource, parentcxt);
|
|
|
|
/* Build new list, with list cells in plancxt */
|
|
newplan->plancache_list = lappend(newplan->plancache_list, plansource);
|
|
}
|
|
|
|
(void)MemoryContextSwitchTo(oldcxt);
|
|
|
|
/* For safety, unlink the CachedPlanSources from the temporary plan */
|
|
plan->plancache_list = NIL;
|
|
|
|
return newplan;
|
|
}
|
|
|
|
void CopySPI_Plan(SPIPlanPtr newplan, SPIPlanPtr plan, MemoryContext plancxt)
|
|
{
|
|
newplan->magic = _SPI_PLAN_MAGIC;
|
|
newplan->saved = false;
|
|
newplan->oneshot = false;
|
|
newplan->plancache_list = NIL;
|
|
newplan->plancxt = plancxt;
|
|
newplan->cursor_options = plan->cursor_options;
|
|
newplan->nargs = plan->nargs;
|
|
newplan->stmt_list = NIL;
|
|
newplan->id = plan->id;
|
|
newplan->spi_key = plan->spi_key;
|
|
if (plan->nargs > 0) {
|
|
newplan->argtypes = (Oid *)palloc(plan->nargs * sizeof(Oid));
|
|
errno_t rc = memcpy_s(newplan->argtypes, plan->nargs * sizeof(Oid), plan->argtypes, plan->nargs * sizeof(Oid));
|
|
securec_check(rc, "\0", "\0");
|
|
} else {
|
|
newplan->argtypes = NULL;
|
|
}
|
|
newplan->parserSetup = plan->parserSetup;
|
|
newplan->parserSetupArg = plan->parserSetupArg;
|
|
ListCell* lc = NULL;
|
|
foreach (lc, plan->stmt_list) {
|
|
Query* q = (Query*)lfirst(lc);
|
|
Query* new_q = (Query*)copyObject(q);
|
|
newplan->stmt_list = lappend(newplan->stmt_list, new_q);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Make a "saved" copy of the given plan.
|
|
*/
|
|
static SPIPlanPtr _SPI_save_plan(SPIPlanPtr plan)
|
|
{
|
|
SPIPlanPtr newplan = NULL;
|
|
MemoryContext plancxt = NULL;
|
|
MemoryContext oldcxt = NULL;
|
|
ListCell *lc = NULL;
|
|
|
|
/* 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_MINSIZE, ALLOCSET_SMALL_INITSIZE,
|
|
ALLOCSET_SMALL_MAXSIZE);
|
|
oldcxt = MemoryContextSwitchTo(plancxt);
|
|
|
|
/* Copy the SPI plan into its own context */
|
|
newplan = (SPIPlanPtr)palloc(sizeof(_SPI_plan));
|
|
CopySPI_Plan(newplan, plan, plancxt);
|
|
|
|
/* Copy all the plancache entries */
|
|
foreach (lc, plan->plancache_list) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc);
|
|
CachedPlanSource *newsource = NULL;
|
|
|
|
newsource = CopyCachedPlan(plansource, false);
|
|
newplan->plancache_list = lappend(newplan->plancache_list, newsource);
|
|
}
|
|
|
|
(void)MemoryContextSwitchTo(oldcxt);
|
|
|
|
/*
|
|
* Mark it saved, reparent it under u_sess->cache_mem_cxt, 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, u_sess->cache_mem_cxt);
|
|
|
|
foreach (lc, newplan->plancache_list) {
|
|
CachedPlanSource *plansource = (CachedPlanSource *)lfirst(lc);
|
|
|
|
SaveCachedPlan(plansource);
|
|
}
|
|
|
|
return newplan;
|
|
}
|
|
|
|
/*
|
|
* spi_dest_shutdownAnalyze: We receive 30000 samples each time and callback to process when analyze for table sample,
|
|
* if the num of last batch less than 30000, we should callback to process in this.
|
|
*
|
|
* Parameters:
|
|
* @in self: a base type for destination-specific local state.
|
|
*
|
|
* Returns: void
|
|
*/
|
|
static void spi_dest_shutdownAnalyze(DestReceiver *self)
|
|
{
|
|
SPITupleTable *tuptable = NULL;
|
|
|
|
spi_check_connid();
|
|
tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
if (tuptable == NULL) {
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_ERROR), errmsg("SPI tupletable is NULL when shutdown SPI for analyze.")));
|
|
}
|
|
|
|
if ((tuptable->free < tuptable->alloced) && (u_sess->SPI_cxt._current->spiCallback)) {
|
|
SPI_tuptable = tuptable;
|
|
SPI_processed = tuptable->alloced - tuptable->free;
|
|
u_sess->SPI_cxt._current->spiCallback(u_sess->SPI_cxt._current->clientData);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* spi_dest_destroyAnalyze: pfree the state for receiver.
|
|
*
|
|
* Parameters:
|
|
* @in self: a base type for destination-specific local state.
|
|
*
|
|
* Returns: void
|
|
*/
|
|
static void spi_dest_destroyAnalyze(DestReceiver *self)
|
|
{
|
|
pfree_ext(self);
|
|
}
|
|
|
|
/*
|
|
* spi_dest_printTupleAnalyze: Receive sample tuples each time and callback to process
|
|
* when analyze for table sample.
|
|
*
|
|
* Parameters:
|
|
* @in slot: the struct which executor stores tuples.
|
|
* @in self: a base type for destination-specific local state.
|
|
*
|
|
* Returns: void
|
|
*/
|
|
static void spi_dest_printTupleAnalyze(TupleTableSlot *slot, DestReceiver *self)
|
|
{
|
|
SPITupleTable *tuptable = NULL;
|
|
MemoryContext oldcxt = NULL;
|
|
|
|
spi_check_connid();
|
|
tuptable = u_sess->SPI_cxt._current->tuptable;
|
|
if (tuptable == NULL) {
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SPI_ERROR), errmsg("SPI tupletable is NULL when store tuple to it for analyze.")));
|
|
}
|
|
|
|
oldcxt = MemoryContextSwitchTo(tuptable->tuptabcxt);
|
|
|
|
if (tuptable->free == 0) {
|
|
SPI_processed = tuptable->alloced - tuptable->free;
|
|
SPI_tuptable = tuptable;
|
|
|
|
/* Callback process tuples we have received. */
|
|
if (u_sess->SPI_cxt._current->spiCallback) {
|
|
u_sess->SPI_cxt._current->spiCallback(u_sess->SPI_cxt._current->clientData);
|
|
}
|
|
|
|
for (uint32 i = 0; i < SPI_processed; i++) {
|
|
pfree_ext(tuptable->vals[i]);
|
|
}
|
|
|
|
pfree_ext(tuptable->vals);
|
|
tuptable->free = tuptable->alloced;
|
|
tuptable->vals = (HeapTuple *)palloc0(tuptable->alloced * sizeof(HeapTuple));
|
|
}
|
|
|
|
tuptable->vals[tuptable->alloced - tuptable->free] = ExecCopySlotTuple(slot);
|
|
(tuptable->free)--;
|
|
|
|
(void)MemoryContextSwitchTo(oldcxt);
|
|
}
|
|
|
|
/*
|
|
* createAnalyzeSPIDestReceiver: create a DestReceiver for printtup of SPI when analyze for table sample..
|
|
*
|
|
* Parameters:
|
|
* @in dest: identify the desired destination, results sent to SPI manager.
|
|
*
|
|
* Returns: DestReceiver*
|
|
*/
|
|
DestReceiver *createAnalyzeSPIDestReceiver(CommandDest dest)
|
|
{
|
|
DestReceiver *spi_dst_receiver = (DestReceiver *)palloc0(sizeof(DestReceiver));
|
|
|
|
spi_dst_receiver->rStartup = spi_dest_startup;
|
|
spi_dst_receiver->receiveSlot = spi_dest_printTupleAnalyze;
|
|
spi_dst_receiver->rShutdown = spi_dest_shutdownAnalyze;
|
|
spi_dst_receiver->rDestroy = spi_dest_destroyAnalyze;
|
|
spi_dst_receiver->mydest = dest;
|
|
|
|
return spi_dst_receiver;
|
|
}
|
|
|
|
/*
|
|
* spi_exec_with_callback: this is a helper method that executes a SQL statement using
|
|
* the SPI interface. It optionally calls a callback function with result pointer.
|
|
*
|
|
* Parameters:
|
|
* @in dest: indentify execute the plan using oreitation-row or column
|
|
* @in src: SQL string
|
|
* @in read_only: is it a read-only call?
|
|
* @in tcount: execution tuple-count limit, or 0 for none
|
|
* @in spec: the sample info of special attribute for compute statistic
|
|
* @in callbackFn: callback function to be executed once SPI is done.
|
|
* @in clientData: argument to call back function (usually pointer to data-structure
|
|
* that the callback function populates).
|
|
*
|
|
* Returns: void
|
|
*/
|
|
void spi_exec_with_callback(CommandDest dest, const char *src, bool read_only, long tcount, bool direct_call,
|
|
void (*callbackFn)(void *), void *clientData, parse_query_func parser)
|
|
{
|
|
bool connected = false;
|
|
int ret = 0;
|
|
|
|
PG_TRY();
|
|
{
|
|
if (SPI_OK_CONNECT != SPI_connect(dest, callbackFn, clientData)) {
|
|
ereport(ERROR, (errcode(ERRCODE_SPI_CONNECTION_FAILURE),
|
|
errmsg("Unable to connect to execute internal query, current level: %d, connected level: %d",
|
|
u_sess->SPI_cxt._curid, u_sess->SPI_cxt._connected)));
|
|
}
|
|
connected = true;
|
|
|
|
elog(DEBUG1, "Executing SQL: %s", src);
|
|
|
|
/* Do the query. */
|
|
ret = SPI_execute(src, read_only, tcount, false, parser);
|
|
Assert(ret > 0);
|
|
|
|
if (direct_call && callbackFn != NULL) {
|
|
callbackFn(clientData);
|
|
}
|
|
|
|
connected = false;
|
|
SPI_STACK_LOG("finish", NULL, NULL);
|
|
(void)SPI_finish();
|
|
}
|
|
/* Clean up in case of error. */
|
|
PG_CATCH();
|
|
{
|
|
if (connected) {
|
|
SPI_STACK_LOG("finish", NULL, NULL);
|
|
SPI_finish();
|
|
}
|
|
|
|
/* Carry on with error handling. */
|
|
PG_RE_THROW();
|
|
}
|
|
PG_END_TRY();
|
|
}
|
|
/*
|
|
* SPI_forbid_exec_push_down_with_exception
|
|
* Function with exception can't be pushed down,because the
|
|
* exception start a subtransaction in DN Node,it will cause the result
|
|
* incorrect
|
|
* Returns: Void
|
|
*/
|
|
void SPI_forbid_exec_push_down_with_exception()
|
|
{
|
|
if (u_sess->SPI_cxt.current_stp_with_exception && IS_PGXC_DATANODE && IsConnFromCoord()) {
|
|
ereport(FATAL, (errmsg("the function or procedure with exception can't be pushed down for execution")));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* SPI_get_top_transaction_id
|
|
* Returns the virtual transaction ID created at the beginning of the transaction
|
|
* The distribution only allows commit and rollback on CN, which returns InvalidTransactionId when the DN is called.
|
|
* When cn or singlenode: returns the virtual transaction id
|
|
*
|
|
* Returns: TransactionId
|
|
*/
|
|
TransactionId SPI_get_top_transaction_id()
|
|
{
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
if (IS_PGXC_COORDINATOR) {
|
|
return t_thrd.proc->lxid;
|
|
} else {
|
|
return InvalidTransactionId;
|
|
}
|
|
#else
|
|
return t_thrd.proc->lxid;
|
|
#endif
|
|
}
|
|
|
|
List* _SPI_get_querylist(SPIPlanPtr plan)
|
|
{
|
|
return plan ? plan->stmt_list : NULL;
|
|
}
|
|
|
|
void _SPI_prepare_oneshot_plan_for_validator(const char *src, SPIPlanPtr plan, parse_query_func parser)
|
|
{
|
|
_SPI_prepare_oneshot_plan(src, plan, parser);
|
|
}
|
|
|
|
void InitSPIPlanCxt()
|
|
{
|
|
/* initialize memory context */
|
|
if (g_instance.spi_plan_cxt.global_spi_plan_context == NULL) {
|
|
g_instance.spi_plan_cxt.global_spi_plan_context = AllocSetContextCreate(
|
|
g_instance.instance_context, "SPIPlanContext",
|
|
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT);
|
|
}
|
|
}
|