diff options
Diffstat (limited to 'src/backend/commands/prepare.c')
-rw-r--r-- | src/backend/commands/prepare.c | 812 |
1 files changed, 812 insertions, 0 deletions
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c new file mode 100644 index 0000000..5e03c7c --- /dev/null +++ b/src/backend/commands/prepare.c @@ -0,0 +1,812 @@ +/*------------------------------------------------------------------------- + * + * prepare.c + * Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE + * + * This module also implements storage of prepared statements that are + * accessed via the extended FE/BE query protocol. + * + * + * Copyright (c) 2002-2021, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/commands/prepare.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <limits.h> + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "commands/createas.h" +#include "commands/prepare.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" +#include "parser/analyze.h" +#include "parser/parse_coerce.h" +#include "parser/parse_collate.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" +#include "rewrite/rewriteHandler.h" +#include "tcop/pquery.h" +#include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/snapmgr.h" +#include "utils/timestamp.h" + + +/* + * The hash table in which prepared queries are stored. This is + * per-backend: query plans are not shared between backends. + * The keys for this hash table are the arguments to PREPARE and EXECUTE + * (statement names); the entries are PreparedStatement structs. + */ +static HTAB *prepared_queries = NULL; + +static void InitQueryHashTable(void); +static ParamListInfo EvaluateParams(ParseState *pstate, + PreparedStatement *pstmt, List *params, + EState *estate); +static Datum build_regtype_array(Oid *param_types, int num_params); + +/* + * Implements the 'PREPARE' utility statement. + */ +void +PrepareQuery(ParseState *pstate, PrepareStmt *stmt, + int stmt_location, int stmt_len) +{ + RawStmt *rawstmt; + CachedPlanSource *plansource; + Oid *argtypes = NULL; + int nargs; + Query *query; + List *query_list; + int i; + + /* + * Disallow empty-string statement name (conflicts with protocol-level + * unnamed statement). + */ + if (!stmt->name || stmt->name[0] == '\0') + ereport(ERROR, + (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), + errmsg("invalid statement name: must not be empty"))); + + /* + * Need to wrap the contained statement in a RawStmt node to pass it to + * parse analysis. + */ + rawstmt = makeNode(RawStmt); + rawstmt->stmt = stmt->query; + rawstmt->stmt_location = stmt_location; + rawstmt->stmt_len = stmt_len; + + /* + * Create the CachedPlanSource before we do parse analysis, since it needs + * to see the unmodified raw parse tree. + */ + plansource = CreateCachedPlan(rawstmt, pstate->p_sourcetext, + CreateCommandTag(stmt->query)); + + /* Transform list of TypeNames to array of type OIDs */ + nargs = list_length(stmt->argtypes); + + if (nargs) + { + ListCell *l; + + argtypes = (Oid *) palloc(nargs * sizeof(Oid)); + i = 0; + + foreach(l, stmt->argtypes) + { + TypeName *tn = lfirst(l); + Oid toid = typenameTypeId(pstate, tn); + + argtypes[i++] = toid; + } + } + + /* + * Analyze the statement using these parameter types (any parameters + * passed in from above us will not be visible to it), allowing + * information about unknown parameters to be deduced from context. + */ + query = parse_analyze_varparams(rawstmt, pstate->p_sourcetext, + &argtypes, &nargs); + + /* + * Check that all parameter types were determined. + */ + for (i = 0; i < nargs; i++) + { + Oid argtype = argtypes[i]; + + if (argtype == InvalidOid || argtype == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_DATATYPE), + errmsg("could not determine data type of parameter $%d", + i + 1))); + } + + /* + * grammar only allows PreparableStmt, so this check should be redundant + */ + switch (query->commandType) + { + case CMD_SELECT: + case CMD_INSERT: + case CMD_UPDATE: + case CMD_DELETE: + /* OK */ + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), + errmsg("utility statements cannot be prepared"))); + break; + } + + /* Rewrite the query. The result could be 0, 1, or many queries. */ + query_list = QueryRewrite(query); + + /* Finish filling in the CachedPlanSource */ + CompleteCachedPlan(plansource, + query_list, + NULL, + argtypes, + nargs, + NULL, + NULL, + CURSOR_OPT_PARALLEL_OK, /* allow parallel mode */ + true); /* fixed result */ + + /* + * Save the results. + */ + StorePreparedStatement(stmt->name, + plansource, + true); +} + +/* + * ExecuteQuery --- implement the 'EXECUTE' utility statement. + * + * This code also supports CREATE TABLE ... AS EXECUTE. That case is + * indicated by passing a non-null intoClause. The DestReceiver is already + * set up correctly for CREATE TABLE AS, but we still have to make a few + * other adjustments here. + */ +void +ExecuteQuery(ParseState *pstate, + ExecuteStmt *stmt, IntoClause *intoClause, + ParamListInfo params, + DestReceiver *dest, QueryCompletion *qc) +{ + PreparedStatement *entry; + CachedPlan *cplan; + List *plan_list; + ParamListInfo paramLI = NULL; + EState *estate = NULL; + Portal portal; + char *query_string; + int eflags; + long count; + + /* Look it up in the hash table */ + entry = FetchPreparedStatement(stmt->name, true); + + /* Shouldn't find a non-fixed-result cached plan */ + if (!entry->plansource->fixed_result) + elog(ERROR, "EXECUTE does not support variable-result cached plans"); + + /* Evaluate parameters, if any */ + if (entry->plansource->num_params > 0) + { + /* + * Need an EState to evaluate parameters; must not delete it till end + * of query, in case parameters are pass-by-reference. Note that the + * passed-in "params" could possibly be referenced in the parameter + * expressions. + */ + estate = CreateExecutorState(); + estate->es_param_list_info = params; + paramLI = EvaluateParams(pstate, entry, stmt->params, estate); + } + + /* Create a new portal to run the query in */ + portal = CreateNewPortal(); + /* Don't display the portal in pg_cursors, it is for internal use only */ + portal->visible = false; + + /* Copy the plan's saved query string into the portal's memory */ + query_string = MemoryContextStrdup(portal->portalContext, + entry->plansource->query_string); + + /* Replan if needed, and increment plan refcount for portal */ + cplan = GetCachedPlan(entry->plansource, paramLI, NULL, NULL); + plan_list = cplan->stmt_list; + + /* + * DO NOT add any logic that could possibly throw an error between + * GetCachedPlan and PortalDefineQuery, or you'll leak the plan refcount. + */ + PortalDefineQuery(portal, + NULL, + query_string, + entry->plansource->commandTag, + plan_list, + cplan); + + /* + * For CREATE TABLE ... AS EXECUTE, we must verify that the prepared + * statement is one that produces tuples. Currently we insist that it be + * a plain old SELECT. In future we might consider supporting other + * things such as INSERT ... RETURNING, but there are a couple of issues + * to be settled first, notably how WITH NO DATA should be handled in such + * a case (do we really want to suppress execution?) and how to pass down + * the OID-determining eflags (PortalStart won't handle them in such a + * case, and for that matter it's not clear the executor will either). + * + * For CREATE TABLE ... AS EXECUTE, we also have to ensure that the proper + * eflags and fetch count are passed to PortalStart/PortalRun. + */ + if (intoClause) + { + PlannedStmt *pstmt; + + if (list_length(plan_list) != 1) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("prepared statement is not a SELECT"))); + pstmt = linitial_node(PlannedStmt, plan_list); + if (pstmt->commandType != CMD_SELECT) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("prepared statement is not a SELECT"))); + + /* Set appropriate eflags */ + eflags = GetIntoRelEFlags(intoClause); + + /* And tell PortalRun whether to run to completion or not */ + if (intoClause->skipData) + count = 0; + else + count = FETCH_ALL; + } + else + { + /* Plain old EXECUTE */ + eflags = 0; + count = FETCH_ALL; + } + + /* + * Run the portal as appropriate. + */ + PortalStart(portal, paramLI, eflags, GetActiveSnapshot()); + + (void) PortalRun(portal, count, false, true, dest, dest, qc); + + PortalDrop(portal, false); + + if (estate) + FreeExecutorState(estate); + + /* No need to pfree other memory, MemoryContext will be reset */ +} + +/* + * EvaluateParams: evaluate a list of parameters. + * + * pstate: parse state + * pstmt: statement we are getting parameters for. + * params: list of given parameter expressions (raw parser output!) + * estate: executor state to use. + * + * Returns a filled-in ParamListInfo -- this can later be passed to + * CreateQueryDesc(), which allows the executor to make use of the parameters + * during query execution. + */ +static ParamListInfo +EvaluateParams(ParseState *pstate, PreparedStatement *pstmt, List *params, + EState *estate) +{ + Oid *param_types = pstmt->plansource->param_types; + int num_params = pstmt->plansource->num_params; + int nparams = list_length(params); + ParamListInfo paramLI; + List *exprstates; + ListCell *l; + int i; + + if (nparams != num_params) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("wrong number of parameters for prepared statement \"%s\"", + pstmt->stmt_name), + errdetail("Expected %d parameters but got %d.", + num_params, nparams))); + + /* Quick exit if no parameters */ + if (num_params == 0) + return NULL; + + /* + * We have to run parse analysis for the expressions. Since the parser is + * not cool about scribbling on its input, copy first. + */ + params = copyObject(params); + + i = 0; + foreach(l, params) + { + Node *expr = lfirst(l); + Oid expected_type_id = param_types[i]; + Oid given_type_id; + + expr = transformExpr(pstate, expr, EXPR_KIND_EXECUTE_PARAMETER); + + given_type_id = exprType(expr); + + expr = coerce_to_target_type(pstate, expr, given_type_id, + expected_type_id, -1, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + -1); + + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("parameter $%d of type %s cannot be coerced to the expected type %s", + i + 1, + format_type_be(given_type_id), + format_type_be(expected_type_id)), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation(lfirst(l))))); + + /* Take care of collations in the finished expression. */ + assign_expr_collations(pstate, expr); + + lfirst(l) = expr; + i++; + } + + /* Prepare the expressions for execution */ + exprstates = ExecPrepareExprList(params, estate); + + paramLI = makeParamList(num_params); + + i = 0; + foreach(l, exprstates) + { + ExprState *n = (ExprState *) lfirst(l); + ParamExternData *prm = ¶mLI->params[i]; + + prm->ptype = param_types[i]; + prm->pflags = PARAM_FLAG_CONST; + prm->value = ExecEvalExprSwitchContext(n, + GetPerTupleExprContext(estate), + &prm->isnull); + + i++; + } + + return paramLI; +} + + +/* + * Initialize query hash table upon first use. + */ +static void +InitQueryHashTable(void) +{ + HASHCTL hash_ctl; + + hash_ctl.keysize = NAMEDATALEN; + hash_ctl.entrysize = sizeof(PreparedStatement); + + prepared_queries = hash_create("Prepared Queries", + 32, + &hash_ctl, + HASH_ELEM | HASH_STRINGS); +} + +/* + * Store all the data pertaining to a query in the hash table using + * the specified key. The passed CachedPlanSource should be "unsaved" + * in case we get an error here; we'll save it once we've created the hash + * table entry. + */ +void +StorePreparedStatement(const char *stmt_name, + CachedPlanSource *plansource, + bool from_sql) +{ + PreparedStatement *entry; + TimestampTz cur_ts = GetCurrentStatementStartTimestamp(); + bool found; + + /* Initialize the hash table, if necessary */ + if (!prepared_queries) + InitQueryHashTable(); + + /* Add entry to hash table */ + entry = (PreparedStatement *) hash_search(prepared_queries, + stmt_name, + HASH_ENTER, + &found); + + /* Shouldn't get a duplicate entry */ + if (found) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_PSTATEMENT), + errmsg("prepared statement \"%s\" already exists", + stmt_name))); + + /* Fill in the hash table entry */ + entry->plansource = plansource; + entry->from_sql = from_sql; + entry->prepare_time = cur_ts; + + /* Now it's safe to move the CachedPlanSource to permanent memory */ + SaveCachedPlan(plansource); +} + +/* + * Lookup an existing query in the hash table. If the query does not + * actually exist, throw ereport(ERROR) or return NULL per second parameter. + * + * Note: this does not force the referenced plancache entry to be valid, + * since not all callers care. + */ +PreparedStatement * +FetchPreparedStatement(const char *stmt_name, bool throwError) +{ + PreparedStatement *entry; + + /* + * If the hash table hasn't been initialized, it can't be storing + * anything, therefore it couldn't possibly store our plan. + */ + if (prepared_queries) + entry = (PreparedStatement *) hash_search(prepared_queries, + stmt_name, + HASH_FIND, + NULL); + else + entry = NULL; + + if (!entry && throwError) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_PSTATEMENT), + errmsg("prepared statement \"%s\" does not exist", + stmt_name))); + + return entry; +} + +/* + * Given a prepared statement, determine the result tupledesc it will + * produce. Returns NULL if the execution will not return tuples. + * + * Note: the result is created or copied into current memory context. + */ +TupleDesc +FetchPreparedStatementResultDesc(PreparedStatement *stmt) +{ + /* + * Since we don't allow prepared statements' result tupdescs to change, + * there's no need to worry about revalidating the cached plan here. + */ + Assert(stmt->plansource->fixed_result); + if (stmt->plansource->resultDesc) + return CreateTupleDescCopy(stmt->plansource->resultDesc); + else + return NULL; +} + +/* + * Given a prepared statement that returns tuples, extract the query + * targetlist. Returns NIL if the statement doesn't have a determinable + * targetlist. + * + * Note: this is pretty ugly, but since it's only used in corner cases like + * Describe Statement on an EXECUTE command, we don't worry too much about + * efficiency. + */ +List * +FetchPreparedStatementTargetList(PreparedStatement *stmt) +{ + List *tlist; + + /* Get the plan's primary targetlist */ + tlist = CachedPlanGetTargetList(stmt->plansource, NULL); + + /* Copy into caller's context in case plan gets invalidated */ + return copyObject(tlist); +} + +/* + * Implements the 'DEALLOCATE' utility statement: deletes the + * specified plan from storage. + */ +void +DeallocateQuery(DeallocateStmt *stmt) +{ + if (stmt->name) + DropPreparedStatement(stmt->name, true); + else + DropAllPreparedStatements(); +} + +/* + * Internal version of DEALLOCATE + * + * If showError is false, dropping a nonexistent statement is a no-op. + */ +void +DropPreparedStatement(const char *stmt_name, bool showError) +{ + PreparedStatement *entry; + + /* Find the query's hash table entry; raise error if wanted */ + entry = FetchPreparedStatement(stmt_name, showError); + + if (entry) + { + /* Release the plancache entry */ + DropCachedPlan(entry->plansource); + + /* Now we can remove the hash table entry */ + hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL); + } +} + +/* + * Drop all cached statements. + */ +void +DropAllPreparedStatements(void) +{ + HASH_SEQ_STATUS seq; + PreparedStatement *entry; + + /* nothing cached */ + if (!prepared_queries) + return; + + /* walk over cache */ + hash_seq_init(&seq, prepared_queries); + while ((entry = hash_seq_search(&seq)) != NULL) + { + /* Release the plancache entry */ + DropCachedPlan(entry->plansource); + + /* Now we can remove the hash table entry */ + hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL); + } +} + +/* + * Implements the 'EXPLAIN EXECUTE' utility statement. + * + * "into" is NULL unless we are doing EXPLAIN CREATE TABLE AS EXECUTE, + * in which case executing the query should result in creating that table. + * + * Note: the passed-in queryString is that of the EXPLAIN EXECUTE, + * not the original PREPARE; we get the latter string from the plancache. + */ +void +ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, + const char *queryString, ParamListInfo params, + QueryEnvironment *queryEnv) +{ + PreparedStatement *entry; + const char *query_string; + CachedPlan *cplan; + List *plan_list; + ListCell *p; + ParamListInfo paramLI = NULL; + EState *estate = NULL; + instr_time planstart; + instr_time planduration; + BufferUsage bufusage_start, + bufusage; + + if (es->buffers) + bufusage_start = pgBufferUsage; + INSTR_TIME_SET_CURRENT(planstart); + + /* Look it up in the hash table */ + entry = FetchPreparedStatement(execstmt->name, true); + + /* Shouldn't find a non-fixed-result cached plan */ + if (!entry->plansource->fixed_result) + elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans"); + + query_string = entry->plansource->query_string; + + /* Evaluate parameters, if any */ + if (entry->plansource->num_params) + { + ParseState *pstate; + + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + /* + * Need an EState to evaluate parameters; must not delete it till end + * of query, in case parameters are pass-by-reference. Note that the + * passed-in "params" could possibly be referenced in the parameter + * expressions. + */ + estate = CreateExecutorState(); + estate->es_param_list_info = params; + + paramLI = EvaluateParams(pstate, entry, execstmt->params, estate); + } + + /* Replan if needed, and acquire a transient refcount */ + cplan = GetCachedPlan(entry->plansource, paramLI, + CurrentResourceOwner, queryEnv); + + INSTR_TIME_SET_CURRENT(planduration); + INSTR_TIME_SUBTRACT(planduration, planstart); + + /* calc differences of buffer counters. */ + if (es->buffers) + { + memset(&bufusage, 0, sizeof(BufferUsage)); + BufferUsageAccumDiff(&bufusage, &pgBufferUsage, &bufusage_start); + } + + plan_list = cplan->stmt_list; + + /* Explain each query */ + foreach(p, plan_list) + { + PlannedStmt *pstmt = lfirst_node(PlannedStmt, p); + + if (pstmt->commandType != CMD_UTILITY) + ExplainOnePlan(pstmt, into, es, query_string, paramLI, queryEnv, + &planduration, (es->buffers ? &bufusage : NULL)); + else + ExplainOneUtility(pstmt->utilityStmt, into, es, query_string, + paramLI, queryEnv); + + /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ + + /* Separate plans with an appropriate separator */ + if (lnext(plan_list, p) != NULL) + ExplainSeparatePlans(es); + } + + if (estate) + FreeExecutorState(estate); + + ReleaseCachedPlan(cplan, CurrentResourceOwner); +} + +/* + * This set returning function reads all the prepared statements and + * returns a set of (name, statement, prepare_time, param_types, from_sql, + * generic_plans, custom_plans). + */ +Datum +pg_prepared_statement(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* need to build tuplestore in query context */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + /* + * build tupdesc for result tuples. This must match the definition of the + * pg_prepared_statements view in system_views.sql + */ + tupdesc = CreateTemplateTupleDesc(7); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "statement", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "prepare_time", + TIMESTAMPTZOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "parameter_types", + REGTYPEARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "from_sql", + BOOLOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "generic_plans", + INT8OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 7, "custom_plans", + INT8OID, -1, 0); + + /* + * We put all the tuples into a tuplestore in one scan of the hashtable. + * This avoids any issue of the hashtable possibly changing between calls. + */ + tupstore = + tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, + false, work_mem); + + /* generate junk in short-term context */ + MemoryContextSwitchTo(oldcontext); + + /* hash table might be uninitialized */ + if (prepared_queries) + { + HASH_SEQ_STATUS hash_seq; + PreparedStatement *prep_stmt; + + hash_seq_init(&hash_seq, prepared_queries); + while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL) + { + Datum values[7]; + bool nulls[7]; + + MemSet(nulls, 0, sizeof(nulls)); + + values[0] = CStringGetTextDatum(prep_stmt->stmt_name); + values[1] = CStringGetTextDatum(prep_stmt->plansource->query_string); + values[2] = TimestampTzGetDatum(prep_stmt->prepare_time); + values[3] = build_regtype_array(prep_stmt->plansource->param_types, + prep_stmt->plansource->num_params); + values[4] = BoolGetDatum(prep_stmt->from_sql); + values[5] = Int64GetDatumFast(prep_stmt->plansource->num_generic_plans); + values[6] = Int64GetDatumFast(prep_stmt->plansource->num_custom_plans); + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + return (Datum) 0; +} + +/* + * This utility function takes a C array of Oids, and returns a Datum + * pointing to a one-dimensional Postgres array of regtypes. An empty + * array is returned as a zero-element array, not NULL. + */ +static Datum +build_regtype_array(Oid *param_types, int num_params) +{ + Datum *tmp_ary; + ArrayType *result; + int i; + + tmp_ary = (Datum *) palloc(num_params * sizeof(Datum)); + + for (i = 0; i < num_params; i++) + tmp_ary[i] = ObjectIdGetDatum(param_types[i]); + + /* XXX: this hardcodes assumptions about the regtype type */ + result = construct_array(tmp_ary, num_params, REGTYPEOID, + 4, true, TYPALIGN_INT); + return PointerGetDatum(result); +} |