]> granicus.if.org Git - postgresql/blobdiff - src/backend/commands/prepare.c
RESET SESSION, plus related new DDL commands. Patch from Marko Kreen,
[postgresql] / src / backend / commands / prepare.c
index eccce9d10d6492236bdbb468268ffa5354387d7b..fe1a8532f070fd81f2b73301ad8cf38d5ef8b274 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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"
 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 = &paramLI->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);
 }