]> 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 dc84f57506a3650c64a08dab56590fa1b0db22fb..fe1a8532f070fd81f2b73301ad8cf38d5ef8b274 100644 (file)
@@ -7,25 +7,30 @@
  * accessed via the extended FE/BE query protocol.
  *
  *
- * Copyright (c) 2002-2005, PostgreSQL Global Development Group
+ * Copyright (c) 2002-2007, PostgreSQL Global Development Group
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.43 2005/11/29 01:25:49 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.71 2007/04/12 06:53:46 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
+#include "access/xact.h"
+#include "catalog/pg_type.h"
 #include "commands/explain.h"
 #include "commands/prepare.h"
-#include "executor/executor.h"
-#include "utils/guc.h"
-#include "optimizer/planner.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"
 #include "tcop/utility.h"
-#include "utils/hsearch.h"
+#include "utils/builtins.h"
 #include "utils/memutils.h"
 
 
 static HTAB *prepared_queries = NULL;
 
 static void InitQueryHashTable(void);
-static ParamListInfo EvaluateParams(EState *estate,
-                          List *params, List *argtypes);
+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
@@ -61,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";
@@ -83,52 +156,36 @@ 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);
 }
 
 /*
  * 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;
@@ -136,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
@@ -152,60 +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
-        */
+       /* 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);
+               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,
-                                         query_string,
-                                         entry->commandTag,
-                                         query_list,
+                                         NULL,
+                                         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);
 
@@ -216,50 +286,115 @@ 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;
+       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 = (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);
 
-       /* Parser should have caught this error, but check for safety */
-       if (list_length(params) != nargs)
-               elog(ERROR, "wrong number of arguments");
+               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)
-               palloc0((nargs + 1) * sizeof(ParamListInfoData));
+               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);
-               bool            isNull;
-
-               paramLI[i].value = ExecEvalExprSwitchContext(n,
-                                                                                         GetPerTupleExprContext(estate),
-                                                                                                        &isNull,
-                                                                                                        NULL);
-               paramLI[i].kind = PARAM_NUM;
-               paramLI[i].id = i + 1;
-               paramLI[i].ptype = lfirst_oid(la);
-               paramLI[i].isnull = isNull;
+               ExprState  *n = lfirst(l);
+               ParamExternData *prm = &paramLI->params[i];
+
+               prm->ptype = param_types[i];
+               prm->pflags = 0;
+               prm->value = ExecEvalExprSwitchContext(n,
+                                                                                          GetPerTupleExprContext(estate),
+                                                                                          &prm->isnull,
+                                                                                          NULL);
 
                i++;
        }
 
-       paramLI[i].kind = PARAM_INVALID;
-
        return paramLI;
 }
 
@@ -285,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
@@ -294,17 +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 */
@@ -312,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,
@@ -323,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);
 
@@ -354,25 +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;
-
-       MemoryContextSwitchTo(oldcxt);
+       /* Fill in the hash table entry */
+       entry->plansource = plansource;
+       entry->from_sql = from_sql;
+       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;
 
        /*
@@ -380,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 NULL-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;
 
@@ -405,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.
@@ -428,23 +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_UTIL_SELECT:
-                       query = (Query *) linitial(stmt->query_list);
-                       return UtilityTupleDescriptor(query->utilityStmt);
-
-               case PORTAL_MULTI_QUERY:
-                       /* will not return tuples */
-                       break;
-       }
-       return NULL;
+       /*
+        * 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;
 }
 
 /*
@@ -452,51 +540,32 @@ FetchPreparedStatementResultDesc(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_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;
 }
 
 /*
@@ -506,7 +575,10 @@ FetchPreparedStatementTargetList(PreparedStatement *stmt)
 void
 DeallocateQuery(DeallocateStmt *stmt)
 {
-       DropPreparedStatement(stmt->name, true);
+       if (stmt->name)
+               DropPreparedStatement(stmt->name, true);
+       else
+               DropAllPreparedStatements();
 }
 
 /*
@@ -524,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);
 
-               /* Flush the context holding the subsidiary data */
-               MemoryContextDelete(entry->context);
+               /* 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);
@@ -540,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
@@ -572,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;
                        }
 
                        /*
@@ -619,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 */
 
@@ -636,4 +729,123 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
 
        if (estate)
                FreeExecutorState(estate);
+
+       ReleaseCachedPlan(cplan, true);
+}
+
+/*
+ * This set returning function reads all the prepared statements and
+ * returns a set of (name, statement, prepare_time, param_types, from_sql).
+ */
+Datum
+pg_prepared_statement(PG_FUNCTION_ARGS)
+{
+       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;
+
+               /* create a function context for cross-call persistence */
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               /*
+                * switch to memory context appropriate for multiple function calls
+                */
+               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               /* allocate memory for user context */
+               if (prepared_queries)
+               {
+                       hash_seq = (HASH_SEQ_STATUS *) palloc(sizeof(HASH_SEQ_STATUS));
+                       hash_seq_init(hash_seq, prepared_queries);
+                       funcctx->user_fctx = (void *) hash_seq;
+               }
+               else
+                       funcctx->user_fctx = NULL;
+
+               /*
+                * 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",
+                                                  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);
+
+               funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /* stuff done on every call of the function */
+       funcctx = SRF_PERCALL_SETUP();
+       hash_seq = (HASH_SEQ_STATUS *) funcctx->user_fctx;
+
+       /* if the hash table is uninitialized, we're done */
+       if (hash_seq == NULL)
+               SRF_RETURN_DONE(funcctx);
+
+       prep_stmt = hash_seq_search(hash_seq);
+       if (prep_stmt)
+       {
+               Datum           result;
+               HeapTuple       tuple;
+               Datum           values[5];
+               bool            nulls[5];
+
+               MemSet(nulls, 0, sizeof(nulls));
+
+               values[0] = DirectFunctionCall1(textin,
+                                                                         CStringGetDatum(prep_stmt->stmt_name));
+
+               if (prep_stmt->plansource->query_string == NULL)
+                       nulls[1] = true;
+               else
+                       values[1] = DirectFunctionCall1(textin,
+                                               CStringGetDatum(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);
+
+               tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+               result = HeapTupleGetDatum(tuple);
+               SRF_RETURN_NEXT(funcctx, result);
+       }
+
+       SRF_RETURN_DONE(funcctx);
+}
+
+/*
+ * 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, 'i');
+       return PointerGetDatum(result);
 }