X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Fcommands%2Fprepare.c;h=fe1a8532f070fd81f2b73301ad8cf38d5ef8b274;hb=d13e903beaecd45a3721e4c2a7f9ff842ce94a79;hp=eccce9d10d6492236bdbb468268ffa5354387d7b;hpb=893632be4e17ccd791cfda17d2e99bb2312f6ff8;p=postgresql diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index eccce9d10d..fe1a8532f0 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -7,10 +7,10 @@ * accessed via the extended FE/BE query protocol. * * - * Copyright (c) 2002-2006, PostgreSQL Global Development Group + * Copyright (c) 2002-2007, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.64 2006/09/07 22:52:00 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.71 2007/04/12 06:53:46 neilc Exp $ * *------------------------------------------------------------------------- */ @@ -22,6 +22,10 @@ #include "commands/explain.h" #include "commands/prepare.h" #include "funcapi.h" +#include "parser/analyze.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" #include "rewrite/rewriteHandler.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -39,20 +43,24 @@ static HTAB *prepared_queries = NULL; static void InitQueryHashTable(void); -static ParamListInfo EvaluateParams(EState *estate, - List *params, List *argtypes); -static Datum build_regtype_array(List *oid_list); +static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params, + const char *queryString, EState *estate); +static Datum build_regtype_array(Oid *param_types, int num_params); /* * Implements the 'PREPARE' utility statement. */ void -PrepareQuery(PrepareStmt *stmt) +PrepareQuery(PrepareStmt *stmt, const char *queryString) { - const char *commandTag; + Oid *argtypes = NULL; + int nargs; + List *queries; Query *query; + const char *commandTag; List *query_list, *plan_list; + int i; /* * Disallow empty-string statement name (conflicts with protocol-level @@ -63,7 +71,70 @@ PrepareQuery(PrepareStmt *stmt) (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), errmsg("invalid statement name: must not be empty"))); - switch (stmt->query->commandType) + /* Transform list of TypeNames to array of type OIDs */ + nargs = list_length(stmt->argtypes); + + if (nargs) + { + ParseState *pstate; + ListCell *l; + + /* + * typenameTypeId wants a ParseState to carry the source query string. + * Is it worth refactoring its API to avoid this? + */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + 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. + * + * Because parse analysis scribbles on the raw querytree, we must make + * a copy to ensure we have a pristine raw tree to cache. FIXME someday. + */ + queries = parse_analyze_varparams((Node *) copyObject(stmt->query), + queryString, + &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))); + } + + /* + * Shouldn't get any extra statements, since grammar only allows + * OptimizableStmt + */ + if (list_length(queries) != 1) + elog(ERROR, "unexpected extra stuff in prepared statement"); + + query = (Query *) linitial(queries); + Assert(IsA(query, Query)); + + switch (query->commandType) { case CMD_SELECT: commandTag = "SELECT"; @@ -85,38 +156,22 @@ PrepareQuery(PrepareStmt *stmt) break; } - /* - * Parse analysis is already done, but we must still rewrite and plan the - * query. - */ - - /* - * Because the planner is not cool about not scribbling on its input, we - * make a preliminary copy of the source querytree. This prevents - * problems in the case that the PREPARE is in a portal or plpgsql - * function and is executed repeatedly. (See also the same hack in - * DECLARE CURSOR and EXPLAIN.) XXX the planner really shouldn't modify - * its input ... FIXME someday. - */ - query = copyObject(stmt->query); - /* Rewrite the query. The result could be 0, 1, or many queries. */ - AcquireRewriteLocks(query); query_list = QueryRewrite(query); /* Generate plans for queries. Snapshot is already set. */ plan_list = pg_plan_queries(query_list, NULL, false); /* - * Save the results. We don't have the query string for this PREPARE, but - * we do have the string we got from the client, so use that. + * Save the results. */ StorePreparedStatement(stmt->name, - debug_query_string, + stmt->query, + queryString, commandTag, - query_list, + argtypes, + nargs, plan_list, - stmt->argtype_oids, true); } @@ -124,14 +179,13 @@ PrepareQuery(PrepareStmt *stmt) * Implements the 'EXECUTE' utility statement. */ void -ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, +ExecuteQuery(ExecuteStmt *stmt, const char *queryString, + ParamListInfo params, DestReceiver *dest, char *completionTag) { PreparedStatement *entry; - char *query_string; - List *query_list, - *plan_list; - MemoryContext qcontext; + CachedPlan *cplan; + List *plan_list; ParamListInfo paramLI = NULL; EState *estate = NULL; Portal portal; @@ -139,15 +193,15 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, /* Look it up in the hash table */ entry = FetchPreparedStatement(stmt->name, true); - query_string = entry->query_string; - query_list = entry->query_list; - plan_list = entry->plan_list; - qcontext = entry->context; - - Assert(list_length(query_list) == list_length(plan_list)); + /* Shouldn't have a non-fully-planned plancache entry */ + if (!entry->plansource->fully_planned) + elog(ERROR, "EXECUTE does not support unplanned prepared statements"); + /* Shouldn't get any non-fixed-result cached plan, either */ + if (!entry->plansource->fixed_result) + elog(ERROR, "EXECUTE does not support variable-result cached plans"); /* Evaluate parameters, if any */ - if (entry->argtype_list != NIL) + if (entry->plansource->num_params > 0) { /* * Need an EState to evaluate parameters; must not delete it till end @@ -155,65 +209,73 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list); + paramLI = EvaluateParams(entry, stmt->params, + queryString, 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; - + /* - * For CREATE TABLE / AS EXECUTE, make a copy of the stored query so that - * we can modify its destination (yech, but this has always been ugly). - * For regular EXECUTE we can just use the stored query where it sits, - * since the executor is read-only. + * For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query + * so that we can modify its destination (yech, but this has always been + * ugly). For regular EXECUTE we can just use the cached query, since the + * executor is read-only. */ if (stmt->into) { MemoryContext oldContext; - Query *query; + PlannedStmt *pstmt; + /* Replan if needed, and increment plan refcount transiently */ + cplan = RevalidateCachedPlan(entry->plansource, true); + + /* Copy plan into portal's context, and modify */ oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); - if (query_string) - query_string = pstrdup(query_string); - query_list = copyObject(query_list); - plan_list = copyObject(plan_list); - qcontext = PortalGetHeapMemory(portal); + plan_list = copyObject(cplan->stmt_list); - if (list_length(query_list) != 1) + if (list_length(plan_list) != 1) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); - query = (Query *) linitial(query_list); - if (query->commandType != CMD_SELECT) + pstmt = (PlannedStmt *) linitial(plan_list); + if (!IsA(pstmt, PlannedStmt) || + pstmt->commandType != CMD_SELECT) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); - query->into = copyObject(stmt->into); - query->intoOptions = copyObject(stmt->intoOptions); - query->intoOnCommit = stmt->into_on_commit; - if (stmt->into_tbl_space) - query->intoTableSpaceName = pstrdup(stmt->into_tbl_space); + pstmt->into = copyObject(stmt->into); MemoryContextSwitchTo(oldContext); + + /* We no longer need the cached plan refcount ... */ + ReleaseCachedPlan(cplan, true); + /* ... and we don't want the portal to depend on it, either */ + cplan = NULL; + } + else + { + /* Replan if needed, and increment plan refcount for portal */ + cplan = RevalidateCachedPlan(entry->plansource, false); + plan_list = cplan->stmt_list; } PortalDefineQuery(portal, NULL, - query_string, - entry->commandTag, - query_list, + entry->plansource->query_string, + entry->plansource->commandTag, plan_list, - qcontext); + cplan); /* * Run the portal to completion. */ PortalStart(portal, paramLI, ActiveSnapshot); - (void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag); + (void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag); PortalDrop(portal, false); @@ -224,42 +286,106 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, } /* - * Evaluates a list of parameters, using the given executor state. It - * requires a list of the parameter expressions themselves, and a list of - * their types. It 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. + * EvaluateParams: evaluate a list of parameters. + * + * pstmt: statement we are getting parameters for. + * params: list of given parameter expressions (raw parser output!) + * queryString: source text for error messages. + * 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(EState *estate, List *params, List *argtypes) +EvaluateParams(PreparedStatement *pstmt, List *params, + const char *queryString, EState *estate) { - int nargs = list_length(argtypes); + Oid *param_types = pstmt->plansource->param_types; + int num_params = pstmt->plansource->num_params; + int nparams = list_length(params); + ParseState *pstate; ParamListInfo paramLI; List *exprstates; - ListCell *le, - *la; - int i = 0; - - /* Parser should have caught this error, but check for safety */ - if (list_length(params) != nargs) - elog(ERROR, "wrong number of arguments"); + ListCell *l; + int i; - if (nargs == 0) + 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 = (List *) copyObject(params); + + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + i = 0; + foreach(l, params) + { + Node *expr = lfirst(l); + Oid expected_type_id = param_types[i]; + Oid given_type_id; + + expr = transformExpr(pstate, expr); + + /* Cannot contain subselects or aggregates */ + if (pstate->p_hasSubLinks) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot use subquery in EXECUTE parameter"))); + if (pstate->p_hasAggs) + ereport(ERROR, + (errcode(ERRCODE_GROUPING_ERROR), + errmsg("cannot use aggregate function in 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); + + 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."))); + + lfirst(l) = expr; + i++; + } + + /* Prepare the expressions for execution */ exprstates = (List *) ExecPrepareExpr((Expr *) params, estate); /* sizeof(ParamListInfoData) includes the first array element */ - paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) + - (nargs - 1) * sizeof(ParamExternData)); - paramLI->numParams = nargs; + paramLI = (ParamListInfo) + palloc(sizeof(ParamListInfoData) + + (num_params - 1) *sizeof(ParamExternData)); + paramLI->numParams = num_params; - forboth(le, exprstates, la, argtypes) + i = 0; + foreach(l, exprstates) { - ExprState *n = lfirst(le); + ExprState *n = lfirst(l); ParamExternData *prm = ¶mLI->params[i]; - prm->ptype = lfirst_oid(la); + prm->ptype = param_types[i]; prm->pflags = 0; prm->value = ExecEvalExprSwitchContext(n, GetPerTupleExprContext(estate), @@ -294,8 +420,9 @@ InitQueryHashTable(void) /* * Store all the data pertaining to a query in the hash table using - * the specified key. A copy of the data is made in a memory context belonging - * to the hash entry, so the caller can dispose of their copy. + * the specified key. All the given data is copied into either the hashtable + * entry or the underlying plancache entry, so the caller can dispose of its + * copy. * * Exception: commandTag is presumed to be a pointer to a constant string, * or possibly NULL, so it need not be copied. Note that commandTag should @@ -303,18 +430,16 @@ InitQueryHashTable(void) */ void StorePreparedStatement(const char *stmt_name, + Node *raw_parse_tree, const char *query_string, const char *commandTag, - List *query_list, - List *plan_list, - List *argtype_list, + Oid *param_types, + int num_params, + List *stmt_list, bool from_sql) { PreparedStatement *entry; - MemoryContext oldcxt, - entrycxt; - char *qstring; - char key[NAMEDATALEN]; + CachedPlanSource *plansource; bool found; /* Initialize the hash table, if necessary */ @@ -322,10 +447,7 @@ StorePreparedStatement(const char *stmt_name, InitQueryHashTable(); /* Check for pre-existing entry of same name */ - /* See notes in FetchPreparedStatement */ - StrNCpy(key, stmt_name, sizeof(key)); - - hash_search(prepared_queries, key, HASH_FIND, &found); + hash_search(prepared_queries, stmt_name, HASH_FIND, &found); if (found) ereport(ERROR, @@ -333,29 +455,19 @@ StorePreparedStatement(const char *stmt_name, errmsg("prepared statement \"%s\" already exists", stmt_name))); - /* Make a permanent memory context for the hashtable entry */ - entrycxt = AllocSetContextCreate(TopMemoryContext, - stmt_name, - ALLOCSET_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_SMALL_MAXSIZE); - - oldcxt = MemoryContextSwitchTo(entrycxt); - - /* - * We need to copy the data so that it is stored in the correct memory - * context. Do this before making hashtable entry, so that an - * out-of-memory failure only wastes memory and doesn't leave us with an - * incomplete (ie corrupt) hashtable entry. - */ - qstring = query_string ? pstrdup(query_string) : NULL; - query_list = (List *) copyObject(query_list); - plan_list = (List *) copyObject(plan_list); - argtype_list = list_copy(argtype_list); + /* Create a plancache entry */ + plansource = CreateCachedPlan(raw_parse_tree, + query_string, + commandTag, + param_types, + num_params, + stmt_list, + true, + true); /* Now we can add entry to hash table */ entry = (PreparedStatement *) hash_search(prepared_queries, - key, + stmt_name, HASH_ENTER, &found); @@ -364,27 +476,22 @@ StorePreparedStatement(const char *stmt_name, elog(ERROR, "duplicate prepared statement \"%s\"", stmt_name); - /* Fill in the hash table entry with copied data */ - entry->query_string = qstring; - entry->commandTag = commandTag; - entry->query_list = query_list; - entry->plan_list = plan_list; - entry->argtype_list = argtype_list; - entry->context = entrycxt; - entry->prepare_time = GetCurrentStatementStartTimestamp(); + /* Fill in the hash table entry */ + entry->plansource = plansource; entry->from_sql = from_sql; - - MemoryContextSwitchTo(oldcxt); + entry->prepare_time = GetCurrentStatementStartTimestamp(); } /* * 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) { - char key[NAMEDATALEN]; PreparedStatement *entry; /* @@ -392,19 +499,10 @@ FetchPreparedStatement(const char *stmt_name, bool throwError) * anything, therefore it couldn't possibly store our plan. */ if (prepared_queries) - { - /* - * We can't just use the statement name as supplied by the user: the - * hash package is picky enough that it needs to be NUL-padded out to - * the appropriate length to work correctly. - */ - StrNCpy(key, stmt_name, sizeof(key)); - entry = (PreparedStatement *) hash_search(prepared_queries, - key, + stmt_name, HASH_FIND, NULL); - } else entry = NULL; @@ -417,20 +515,6 @@ FetchPreparedStatement(const char *stmt_name, bool throwError) return entry; } -/* - * Look up a prepared statement given the name (giving error if not found). - * If found, return the list of argument type OIDs. - */ -List * -FetchPreparedStatementParams(const char *stmt_name) -{ - PreparedStatement *entry; - - entry = FetchPreparedStatement(stmt_name, true); - - return entry->argtype_list; -} - /* * Given a prepared statement, determine the result tupledesc it will * produce. Returns NULL if the execution will not return tuples. @@ -440,52 +524,15 @@ FetchPreparedStatementParams(const char *stmt_name) TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt) { - Query *query; - - switch (ChoosePortalStrategy(stmt->query_list)) - { - case PORTAL_ONE_SELECT: - query = (Query *) linitial(stmt->query_list); - return ExecCleanTypeFromTL(query->targetList, false); - - case PORTAL_ONE_RETURNING: - query = PortalListGetPrimaryQuery(stmt->query_list); - return ExecCleanTypeFromTL(query->returningList, false); - - case PORTAL_UTIL_SELECT: - query = (Query *) linitial(stmt->query_list); - return UtilityTupleDescriptor(query->utilityStmt); - - case PORTAL_MULTI_QUERY: - /* will not return tuples */ - break; - } - return NULL; -} - -/* - * Given a prepared statement, determine whether it will return tuples. - * - * Note: this is used rather than just testing the result of - * FetchPreparedStatementResultDesc() because that routine can fail if - * invoked in an aborted transaction. This one is safe to use in any - * context. Be sure to keep the two routines in sync! - */ -bool -PreparedStatementReturnsTuples(PreparedStatement *stmt) -{ - switch (ChoosePortalStrategy(stmt->query_list)) - { - case PORTAL_ONE_SELECT: - case PORTAL_ONE_RETURNING: - case PORTAL_UTIL_SELECT: - return true; - - case PORTAL_MULTI_QUERY: - /* will not return tuples */ - break; - } - return false; + /* + * Since we don't allow prepared statements' result tupdescs to change, + * there's no need for a revalidate call here. + */ + Assert(stmt->plansource->fixed_result); + if (stmt->plansource->resultDesc) + return CreateTupleDescCopy(stmt->plansource->resultDesc); + else + return NULL; } /* @@ -493,53 +540,32 @@ PreparedStatementReturnsTuples(PreparedStatement *stmt) * targetlist. Returns NIL if the statement doesn't have a determinable * targetlist. * - * Note: do not modify the result. - * - * XXX be careful to keep this in sync with FetchPortalTargetList, - * and with UtilityReturnsTuples. + * 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) { - PortalStrategy strategy = ChoosePortalStrategy(stmt->query_list); + List *tlist; + CachedPlan *cplan; - if (strategy == PORTAL_ONE_SELECT) - return ((Query *) linitial(stmt->query_list))->targetList; - if (strategy == PORTAL_ONE_RETURNING) - return (PortalListGetPrimaryQuery(stmt->query_list))->returningList; - if (strategy == PORTAL_UTIL_SELECT) - { - Node *utilityStmt; + /* No point in looking if it doesn't return tuples */ + if (stmt->plansource->resultDesc == NULL) + return NIL; - utilityStmt = ((Query *) linitial(stmt->query_list))->utilityStmt; - switch (nodeTag(utilityStmt)) - { - case T_FetchStmt: - { - FetchStmt *substmt = (FetchStmt *) utilityStmt; - Portal subportal; - - Assert(!substmt->ismove); - subportal = GetPortalByName(substmt->portalname); - Assert(PortalIsValid(subportal)); - return FetchPortalTargetList(subportal); - } - - case T_ExecuteStmt: - { - ExecuteStmt *substmt = (ExecuteStmt *) utilityStmt; - PreparedStatement *entry; - - Assert(!substmt->into); - entry = FetchPreparedStatement(substmt->name, true); - return FetchPreparedStatementTargetList(entry); - } - - default: - break; - } - } - return NIL; + /* Make sure the plan is up to date */ + cplan = RevalidateCachedPlan(stmt->plansource, true); + + /* Get the primary statement and find out what it returns */ + tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list)); + + /* Copy into caller's context so we can release the plancache entry */ + tlist = (List *) copyObject(tlist); + + ReleaseCachedPlan(cplan, true); + + return tlist; } /* @@ -549,7 +575,10 @@ FetchPreparedStatementTargetList(PreparedStatement *stmt) void DeallocateQuery(DeallocateStmt *stmt) { - DropPreparedStatement(stmt->name, true); + if (stmt->name) + DropPreparedStatement(stmt->name, true); + else + DropAllPreparedStatements(); } /* @@ -567,12 +596,33 @@ DropPreparedStatement(const char *stmt_name, bool showError) if (entry) { - /* Drop any open portals that depend on this prepared statement */ - Assert(MemoryContextIsValid(entry->context)); - DropDependentPortals(entry->context); + /* 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; - /* Flush the context holding the subsidiary data */ - MemoryContextDelete(entry->context); + /* 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); @@ -583,31 +633,34 @@ DropPreparedStatement(const char *stmt_name, bool showError) * Implements the 'EXPLAIN EXECUTE' utility statement. */ void -ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, - TupOutputState *tstate) +ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt, + const char *queryString, + ParamListInfo params, TupOutputState *tstate) { - ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt; PreparedStatement *entry; - ListCell *q, - *p; - List *query_list, - *plan_list; + CachedPlan *cplan; + List *plan_list; + ListCell *p; ParamListInfo paramLI = NULL; EState *estate = NULL; - /* explain.c should only call me for EXECUTE stmt */ - Assert(execstmt && IsA(execstmt, ExecuteStmt)); - /* Look it up in the hash table */ entry = FetchPreparedStatement(execstmt->name, true); - query_list = entry->query_list; - plan_list = entry->plan_list; + /* Shouldn't have a non-fully-planned plancache entry */ + if (!entry->plansource->fully_planned) + elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements"); + /* Shouldn't get any non-fixed-result cached plan, either */ + if (!entry->plansource->fixed_result) + elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans"); - Assert(list_length(query_list) == list_length(plan_list)); + /* Replan if needed, and acquire a transient refcount */ + cplan = RevalidateCachedPlan(entry->plansource, true); + + plan_list = cplan->stmt_list; /* Evaluate parameters, if any */ - if (entry->argtype_list != NIL) + if (entry->plansource->num_params) { /* * Need an EState to evaluate parameters; must not delete it till end @@ -615,41 +668,33 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(estate, execstmt->params, - entry->argtype_list); + paramLI = EvaluateParams(entry, execstmt->params, + queryString, estate); } /* Explain each query */ - forboth(q, query_list, p, plan_list) + foreach(p, plan_list) { - Query *query = (Query *) lfirst(q); - Plan *plan = (Plan *) lfirst(p); + PlannedStmt *pstmt = (PlannedStmt *) lfirst(p); bool is_last_query; is_last_query = (lnext(p) == NULL); - if (query->commandType == CMD_UTILITY) - { - if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt)) - do_text_output_oneline(tstate, "NOTIFY"); - else - do_text_output_oneline(tstate, "UTILITY"); - } - else + if (IsA(pstmt, PlannedStmt)) { QueryDesc *qdesc; if (execstmt->into) { - if (query->commandType != CMD_SELECT) + if (pstmt->commandType != CMD_SELECT) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); - /* Copy the query so we can modify it */ - query = copyObject(query); + /* Copy the stmt so we can modify it */ + pstmt = copyObject(pstmt); - query->into = execstmt->into; + pstmt->into = execstmt->into; } /* @@ -662,13 +707,18 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, ActiveSnapshot->curcid = GetCurrentCommandId(); /* Create a QueryDesc requesting no output */ - qdesc = CreateQueryDesc(query, plan, + qdesc = CreateQueryDesc(pstmt, ActiveSnapshot, InvalidSnapshot, None_Receiver, paramLI, stmt->analyze); ExplainOnePlan(qdesc, stmt, tstate); } + else + { + ExplainOneUtility((Node *) pstmt, stmt, queryString, + params, tstate); + } /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ @@ -679,6 +729,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, if (estate) FreeExecutorState(estate); + + ReleaseCachedPlan(cplan, true); } /* @@ -688,22 +740,21 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, Datum pg_prepared_statement(PG_FUNCTION_ARGS) { - FuncCallContext *funcctx; - HASH_SEQ_STATUS *hash_seq; - PreparedStatement *prep_stmt; + FuncCallContext *funcctx; + HASH_SEQ_STATUS *hash_seq; + PreparedStatement *prep_stmt; /* stuff done only on the first call of the function */ if (SRF_IS_FIRSTCALL()) { - TupleDesc tupdesc; - MemoryContext oldcontext; + TupleDesc tupdesc; + MemoryContext oldcontext; /* create a function context for cross-call persistence */ funcctx = SRF_FIRSTCALL_INIT(); /* - * switch to memory context appropriate for multiple function - * calls + * switch to memory context appropriate for multiple function calls */ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); @@ -718,9 +769,8 @@ pg_prepared_statement(PG_FUNCTION_ARGS) funcctx->user_fctx = NULL; /* - * build tupdesc for result tuples. This must match the - * definition of the pg_prepared_statements view in - * system_views.sql + * build tupdesc for result tuples. This must match the definition of + * the pg_prepared_statements view in system_views.sql */ tupdesc = CreateTemplateTupleDesc(5, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", @@ -749,24 +799,25 @@ pg_prepared_statement(PG_FUNCTION_ARGS) prep_stmt = hash_seq_search(hash_seq); if (prep_stmt) { - Datum result; - HeapTuple tuple; - Datum values[5]; - bool nulls[5]; + Datum result; + HeapTuple tuple; + Datum values[5]; + bool nulls[5]; MemSet(nulls, 0, sizeof(nulls)); values[0] = DirectFunctionCall1(textin, - CStringGetDatum(prep_stmt->stmt_name)); + CStringGetDatum(prep_stmt->stmt_name)); - if (prep_stmt->query_string == NULL) + if (prep_stmt->plansource->query_string == NULL) nulls[1] = true; else values[1] = DirectFunctionCall1(textin, - CStringGetDatum(prep_stmt->query_string)); + CStringGetDatum(prep_stmt->plansource->query_string)); values[2] = TimestampTzGetDatum(prep_stmt->prepare_time); - values[3] = build_regtype_array(prep_stmt->argtype_list); + values[3] = build_regtype_array(prep_stmt->plansource->param_types, + prep_stmt->plansource->num_params); values[4] = BoolGetDatum(prep_stmt->from_sql); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -778,34 +829,23 @@ pg_prepared_statement(PG_FUNCTION_ARGS) } /* - * This utility function takes a List of Oids, and returns a Datum - * pointing to a one-dimensional Postgres array of regtypes. The empty - * list is returned as a zero-element array, not NULL. + * 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(List *oid_list) +build_regtype_array(Oid *param_types, int num_params) { - ListCell *lc; - int len; - int i; Datum *tmp_ary; ArrayType *result; + int i; - len = list_length(oid_list); - tmp_ary = (Datum *) palloc(len * sizeof(Datum)); - - i = 0; - foreach(lc, oid_list) - { - Oid oid; - Datum oid_str; + tmp_ary = (Datum *) palloc(num_params * sizeof(Datum)); - oid = lfirst_oid(lc); - oid_str = DirectFunctionCall1(oidout, ObjectIdGetDatum(oid)); - tmp_ary[i++] = DirectFunctionCall1(regtypein, oid_str); - } + 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, len, REGTYPEOID, 4, true, 'i'); + result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i'); return PointerGetDatum(result); }