diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 4a6b966..4bfc4f4 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -22,6 +22,8 @@ #include "commands/trigger.h" #include "executor/executor.h" #include "executor/spi_priv.h" +#include "nodes/pg_list.h" +#include "parser/analyze.h" #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/builtins.h" @@ -31,6 +33,7 @@ #include "utils/snapmgr.h" #include "utils/syscache.h" #include "utils/typcache.h" +#include "rewrite/rewriteHandler.h" uint32 SPI_processed = 0; @@ -50,6 +53,11 @@ static Portal SPI_cursor_open_internal(const char *name, SPIPlanPtr plan, static void _SPI_prepare_plan(const char *src, SPIPlanPtr plan, ParamListInfo boundParams); +static int _SPI_prepare_statement_plan(const char *src, SPIPlanPtr plan, + SPIParamCallback pcb, void *cb_data); + +static int _SPI_execute_statements(const char *src); + static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot snapshot, Snapshot crosscheck_snapshot, bool read_only, bool fire_triggers, long tcount); @@ -524,6 +532,55 @@ SPI_execute_with_args(const char *src, } SPIPlanPtr +SPI_prepare_statement(const char *src, int cursorOptions, SPIParamCallback pcb, void *cbdata, TupleDesc *resultDesc) +{ + _SPI_plan plan; + SPIPlanPtr rplan; + + SPI_result = _SPI_begin_call(true); + if (SPI_result < 0) + return NULL; + + memset(&plan, 0, sizeof(_SPI_plan)); + plan.magic = _SPI_PLAN_MAGIC; + plan.cursor_options = cursorOptions; + plan.nargs = -1; + plan.argtypes = NULL; + + SPI_result = _SPI_prepare_statement_plan(src, &plan, pcb, cbdata); + + if (SPI_result < 0) + rplan = NULL; + else + { + rplan = _SPI_copy_plan(&plan, _SPI_current->procCxt); + if (rplan != NULL) + *resultDesc = + ((CachedPlanSource *) linitial(rplan->plancache_list))->resultDesc; + } + + _SPI_end_call(true); + + return rplan; +} + +int +SPI_execute_statements(const char *src) +{ + int r = -1; + + SPI_result = _SPI_begin_call(true); + if (SPI_result < 0) + return r; + + SPI_result = r = _SPI_execute_statements(src); + + _SPI_end_call(true); + + return r; +} + +SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes) { return SPI_prepare_cursor(src, nargs, argtypes, 0); @@ -1648,6 +1705,278 @@ spi_printtup(TupleTableSlot *slot, DestReceiver *self) */ /* + * Parse and plan a *single* statement. + * + * Given a destination plan, parse and plan the given statement string into the + * SPIPlanPtr. + * + * In contrast to _SPI_prepare_plan, this function will analyze the raw parse + * tree and extract the statement's parameters from the query. After collecting + * the parameter types, the SPIParamCallback is invoked to allow the original caller + * to note the parameter types, and to provide actual parameters if so desired. + * + * This requires that the given SQL is a single statement. + * Transaction and STDIN/STDOUT COPY statements are restricted. + */ +static int +_SPI_prepare_statement_plan(const char *src, SPIPlanPtr plan, SPIParamCallback spipcb, void *cbdata) +{ + List *raw_parsetree_list; + Node *rpt; + List *plancache_list; + Query *query; + ErrorContextCallback spierrcontext; + Oid *argtypes = NULL; + int nargs = -1; + int cursor_options = plan->cursor_options; + List *stmt_list; + CachedPlanSource *plansource; + CachedPlan *cplan; + Datum *param_datums = NULL; + bool *param_nulls = NULL; + ParamListInfo boundParams; + const char *commandTag; + + /* + * Setup error traceback support for ereport() + */ + spierrcontext.callback = _SPI_error_callback; + spierrcontext.arg = (void *) src; + spierrcontext.previous = error_context_stack; + error_context_stack = &spierrcontext; + + /* + * Parse the request string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(src); + if (list_length(raw_parsetree_list) != 1) + { + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("cannot insert multiple commands into a prepared statement"))); + } + + rpt = (Node *) linitial(raw_parsetree_list); + + /* + * Validate that it fits the basic criteria for a single SPI statement. + * + * Don't wait for execution time to fail on these. + */ + if (IsA(rpt, TransactionStmt)) + { + SPI_result = SPI_ERROR_TRANSACTION; + goto fail; + } + if (IsA(rpt, CopyStmt) && ((CopyStmt *) rpt)->filename == NULL) + { + SPI_result = SPI_ERROR_COPY; + goto fail; + } + + /* + * for repalloc + */ + nargs = 0; + argtypes = palloc(sizeof(Oid) * 0); + + /* Need a copyObject here to keep parser from modifying raw tree */ + query = parse_analyze_varparams( + (Node *) copyObject(rpt), src, &argtypes, &nargs); + + plan->argtypes = argtypes; + plan->nargs = nargs; + + commandTag = CreateCommandTag(rpt); + /* + * Run the callback to give the caller the parameter + * type Oids and, potentially, collect parameters for the statement. + */ + spipcb(cbdata, commandTag, nargs, argtypes, ¶m_datums, ¶m_nulls); + + /* + * If the callback set some parameters, use them as constants in the plan. + */ + if (PointerIsValid(param_datums)) + { + if (!PointerIsValid(param_nulls)) + { + SPI_result = SPI_ERROR_ARGUMENT; + goto fail; + } + + boundParams = _SPI_convert_params(nargs, argtypes, + param_datums, param_nulls, + PARAM_FLAG_CONST); + } + else + boundParams = NULL; + + /* + * Don't rewrite utility statements. + */ + if (query->commandType == CMD_UTILITY) + stmt_list = list_make1(query); + else + stmt_list = QueryRewrite(query); + + stmt_list = pg_plan_queries(stmt_list, cursor_options, boundParams); + + plansource = (CachedPlanSource *) palloc0(sizeof(CachedPlanSource)); + cplan = (CachedPlan *) palloc0(sizeof(CachedPlan)); + + plansource->raw_parse_tree = rpt; + plansource->query_string = pstrdup(src); + plansource->commandTag = commandTag; + plansource->param_types = argtypes; + plansource->num_params = nargs; + plansource->fully_planned = true; + plansource->fixed_result = true; + plansource->resultDesc = PlanCacheComputeResultDesc(stmt_list); + plansource->plan = cplan; + + cplan->stmt_list = stmt_list; + cplan->fully_planned = true; + + plancache_list = NIL; + plancache_list = lappend(plancache_list, plansource); + plan->plancache_list = plancache_list; + + /* + * Pop the error context stack + */ + error_context_stack = spierrcontext.previous; + + return 0; +fail: + error_context_stack = spierrcontext.previous; + return SPI_result; +} + +/* + * Parse, plan and execute all the statements in the given string. + * Execution occurs directly after a statement is planned and *before* the next + * statement is planned. This allows statements to depend on objects created by + * statements executed prior. + * + * This interface is designed for allowing users to execute bulk DDL/DML. + */ +static int +_SPI_execute_statements(const char *src) +{ + int r = 0; + List *raw_parsetree_list; + ListCell *list_item; + DestReceiver *dest; + ErrorContextCallback spierrcontext; + + /* + * Setup error traceback support for ereport() + */ + spierrcontext.callback = _SPI_error_callback; + spierrcontext.arg = (void *) src; + spierrcontext.previous = error_context_stack; + error_context_stack = &spierrcontext; + + /* + * Parse the request string into a list of raw parse trees. + */ + raw_parsetree_list = pg_parse_query(src); + + /* + * Everything is sent to DestNone. + */ + _SPI_current->processed = 0; + _SPI_current->lastoid = InvalidOid; + _SPI_current->tuptable = NULL; + dest = CreateDestReceiver(DestNone); + + /* + * Iterate over the parsetree list, executing each statement. + */ + foreach(list_item, raw_parsetree_list) + { + Node *parsetree = (Node *) lfirst(list_item); + List *stmt_list; + ListCell *lc2; + + /* + * Before each statement. + */ + CommandCounterIncrement(); + + /* + * No parameters. + */ + stmt_list = pg_analyze_and_rewrite(parsetree, src, NULL, 0); + stmt_list = pg_plan_queries(stmt_list, 0, NULL); + + foreach(lc2, stmt_list) + { + Node *stmt = (Node *) lfirst(lc2); + + if (!IsA(stmt, PlannedStmt)) + { + /* + * Filter prohibited statements. + */ + if (IsA(stmt, CopyStmt)) + { + CopyStmt *cstmt = (CopyStmt *) stmt; + + if (cstmt->filename == NULL) + { + return SPI_ERROR_COPY; + } + } + else if (IsA(stmt, TransactionStmt)) + { + return SPI_ERROR_TRANSACTION; + } + } + + /* + * Before each command. + */ + CommandCounterIncrement(); + PushActiveSnapshot(GetTransactionSnapshot()); + + if (IsA(stmt, PlannedStmt) && + ((PlannedStmt *) stmt)->utilityStmt == NULL) + { + QueryDesc *qdesc; + + qdesc = CreateQueryDesc( + (PlannedStmt *) stmt, src, + GetActiveSnapshot(), /* xact snapshot */ + InvalidSnapshot, /* no crosscheck */ + dest, NULL, false); + r = _SPI_pquery(qdesc, true, 0); + FreeQueryDesc(qdesc); + } + else + { + ProcessUtility(stmt, src, + NULL, /* no params */ + false, /* not top level */ + dest, /* DestNone */ + NULL); + r = SPI_OK_UTILITY; + } + + PopActiveSnapshot(); + + if (r < 0) + return r; + } + } + + error_context_stack = spierrcontext.previous; + + return r; +} + +/* * Parse and plan a querystring. * * At entry, plan->argtypes and plan->nargs (or alternatively plan->parserSetup diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 025ca35..5b7ffa2 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -1021,6 +1021,23 @@ internalerrquery(const char *query) } /* + * getelevel --- return the currently set elevel + * + * This is only intended for use in error callback subroutines, since there + * is no other place outside elog.c where the concept is meaningful. + */ +int +getelevel(void) +{ + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + return edata->elevel; +} + +/* * geterrcode --- return the currently set SQLSTATE error code * * This is only intended for use in error callback subroutines, since there diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 33f4f15..b23f7a1 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -31,6 +31,16 @@ typedef struct SPITupleTable /* Plans are opaque structs for standard users of SPI */ typedef struct _SPI_plan *SPIPlanPtr; +/* + * Callback function used by SPI_prepare_statement to give the + * caller the type Oid's of the statement's parameters. + * + * Additionally, the caller has the ability to give the param_values. + */ +typedef void (*SPIParamCallback)( + void *cb_data, const char *commandTag, + int nargs, Oid *typoids, Datum **param_values, char **param_nulls); + #define SPI_ERROR_CONNECT (-1) #define SPI_ERROR_COPY (-2) #define SPI_ERROR_OPUNKNOWN (-3) @@ -88,6 +98,9 @@ extern int SPI_execute_with_args(const char *src, int nargs, Oid *argtypes, Datum *Values, const char *Nulls, bool read_only, long tcount); +extern int SPI_execute_statements(const char *src); +extern SPIPlanPtr SPI_prepare_statement(const char *src, int cursorOptions, + SPIParamCallback pcb, void *pcb_arg, TupleDesc *resultDesc); extern SPIPlanPtr SPI_prepare(const char *src, int nargs, Oid *argtypes); extern SPIPlanPtr SPI_prepare_cursor(const char *src, int nargs, Oid *argtypes, int cursorOptions); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 0982ca4..ea5a1c1 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -180,6 +180,7 @@ extern int errposition(int cursorpos); extern int internalerrposition(int cursorpos); extern int internalerrquery(const char *query); +extern int getelevel(void); extern int geterrcode(void); extern int geterrposition(void); extern int getinternalerrposition(void);