Skip to content
Snippets Groups Projects
Select Git revision
  • benchmark-tools
  • postgres-lambda
  • master default
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
23 results

prepare.c

Blame
    • Tom Lane's avatar
      9bedd128
      Add support for invoking parser callback hooks via SPI and in cached plans. · 9bedd128
      Tom Lane authored
      As proof of concept, modify plpgsql to use the hooks.  plpgsql is still
      inserting $n symbols textually, but the "back end" of the parsing process now
      goes through the ParamRef hook instead of using a fixed parameter-type array,
      and then execution only fetches actually-referenced parameters, using a hook
      added to ParamListInfo.
      
      Although there's a lot left to be done in plpgsql, this already cures the
      "if (TG_OP = 'INSERT' and NEW.foo ...)"  problem, as illustrated by the
      changed regression test.
      9bedd128
      History
      Add support for invoking parser callback hooks via SPI and in cached plans.
      Tom Lane authored
      As proof of concept, modify plpgsql to use the hooks.  plpgsql is still
      inserting $n symbols textually, but the "back end" of the parsing process now
      goes through the ParamRef hook instead of using a fixed parameter-type array,
      and then execution only fetches actually-referenced parameters, using a hook
      added to ParamListInfo.
      
      Although there's a lot left to be done in plpgsql, this already cures the
      "if (TG_OP = 'INSERT' and NEW.foo ...)"  problem, as illustrated by the
      changed regression test.
    prepare.c 22.97 KiB
    /*-------------------------------------------------------------------------
     *
     * prepare.c
     *	  Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
     *
     * This module also implements storage of prepared statements that are
     * accessed via the extended FE/BE query protocol.
     *
     *
     * Copyright (c) 2002-2009, PostgreSQL Global Development Group
     *
     * IDENTIFICATION
     *	  $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.100 2009/11/04 22:26:05 tgl Exp $
     *
     *-------------------------------------------------------------------------
     */
    #include "postgres.h"
    
    #include "access/xact.h"
    #include "catalog/pg_type.h"
    #include "commands/prepare.h"
    #include "miscadmin.h"
    #include "nodes/nodeFuncs.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/builtins.h"
    #include "utils/memutils.h"
    #include "utils/snapmgr.h"
    
    
    /*
     * The hash table in which prepared queries are stored. This is
     * per-backend: query plans are not shared between backends.
     * The keys for this hash table are the arguments to PREPARE and EXECUTE
     * (statement names); the entries are PreparedStatement structs.
     */
    static HTAB *prepared_queries = NULL;
    
    static void InitQueryHashTable(void);
    static ParamListInfo EvaluateParams(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, const char *queryString)
    {
    	Oid		   *argtypes = NULL;
    	int			nargs;
    	Query	   *query;
    	List	   *query_list,
    			   *plan_list;
    	int			i;
    
    	/*
    	 * Disallow empty-string statement name (conflicts with protocol-level
    	 * unnamed statement).
    	 */
    	if (!stmt->name || stmt->name[0] == '\0')
    		ereport(ERROR,
    				(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
    				 errmsg("invalid statement name: must not be empty")));
    
    	/* 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, NULL);
    
    			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.
    	 */
    	query = 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)));
    	}
    
    	/*
    	 * grammar only allows OptimizableStmt, so this check should be redundant
    	 */
    	switch (query->commandType)
    	{
    		case CMD_SELECT:
    		case CMD_INSERT:
    		case CMD_UPDATE:
    		case CMD_DELETE:
    			/* OK */
    			break;
    		default:
    			ereport(ERROR,
    					(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
    					 errmsg("utility statements cannot be prepared")));
    			break;
    	}
    
    	/* Rewrite the query. The result could be 0, 1, or many queries. */
    	query_list = QueryRewrite(query);
    
    	/* Generate plans for queries. */
    	plan_list = pg_plan_queries(query_list, 0, NULL);
    
    	/*
    	 * Save the results.
    	 */
    	StorePreparedStatement(stmt->name,
    						   stmt->query,
    						   queryString,
    						   CreateCommandTag((Node *) query),
    						   argtypes,
    						   nargs,
    						   0,	/* default cursor options */
    						   plan_list,
    						   true);
    }
    
    /*
     * Implements the 'EXECUTE' utility statement.
     *
     * Note: this is one of very few places in the code that needs to deal with
     * two query strings at once.  The passed-in queryString is that of the
     * EXECUTE, which we might need for error reporting while processing the
     * parameter expressions.  The query_string that we copy from the plan
     * source is that of the original PREPARE.
     */
    void
    ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
    			 ParamListInfo params,
    			 DestReceiver *dest, char *completionTag)
    {
    	PreparedStatement *entry;
    	CachedPlan *cplan;
    	List	   *plan_list;
    	ParamListInfo paramLI = NULL;
    	EState	   *estate = NULL;
    	Portal		portal;
    	char	   *query_string;
    
    	/* Look it up in the hash table */
    	entry = FetchPreparedStatement(stmt->name, true);
    
    	/* 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->plansource->num_params > 0)
    	{
    		/*
    		 * Need an EState to evaluate parameters; must not delete it till end
    		 * of query, in case parameters are pass-by-reference.
    		 */
    		estate = CreateExecutorState();
    		estate->es_param_list_info = params;
    		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;
    
    	/* Copy the plan's saved query string into the portal's memory */
    	query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
    									   entry->plansource->query_string);
    
    	/*
    	 * 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;
    		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));
    
    		plan_list = copyObject(cplan->stmt_list);
    
    		if (list_length(plan_list) != 1)
    			ereport(ERROR,
    					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    					 errmsg("prepared statement is not a SELECT")));
    		pstmt = (PlannedStmt *) linitial(plan_list);
    		if (!IsA(pstmt, PlannedStmt) ||
    			pstmt->commandType != CMD_SELECT ||
    			pstmt->utilityStmt != NULL)
    			ereport(ERROR,
    					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    					 errmsg("prepared statement is not a SELECT")));
    		pstmt->intoClause = 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->plansource->commandTag,
    					  plan_list,
    					  cplan);
    
    	/*
    	 * Run the portal to completion.
    	 */
    	PortalStart(portal, paramLI, GetActiveSnapshot());
    
    	(void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag);
    
    	PortalDrop(portal, false);
    
    	if (estate)
    		FreeExecutorState(estate);
    
    	/* No need to pfree other memory, MemoryContext will be reset */
    }
    
    /*
     * 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(PreparedStatement *pstmt, List *params,
    			   const char *queryString, EState *estate)
    {
    	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   *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")));
    		if (pstate->p_hasWindowFuncs)
    			ereport(ERROR,
    					(errcode(ERRCODE_WINDOWING_ERROR),
    				 errmsg("cannot use window 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,
    									 -1);
    
    		if (expr == NULL)
    			ereport(ERROR,
    					(errcode(ERRCODE_DATATYPE_MISMATCH),
    					 errmsg("parameter $%d of type %s cannot be coerced to the expected type %s",
    							i + 1,
    							format_type_be(given_type_id),
    							format_type_be(expected_type_id)),
    			   errhint("You will need to rewrite or cast the expression.")));
    
    		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) +
    			   (num_params - 1) *sizeof(ParamExternData));
    	/* we have static list of params, so no hooks needed */
    	paramLI->paramFetch = NULL;
    	paramLI->paramFetchArg = NULL;
    	paramLI->parserSetup = NULL;
    	paramLI->parserSetupArg = NULL;
    	paramLI->numParams = num_params;
    
    	i = 0;
    	foreach(l, exprstates)
    	{
    		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++;
    	}
    
    	return paramLI;
    }
    
    
    /*
     * Initialize query hash table upon first use.
     */
    static void
    InitQueryHashTable(void)
    {
    	HASHCTL		hash_ctl;
    
    	MemSet(&hash_ctl, 0, sizeof(hash_ctl));
    
    	hash_ctl.keysize = NAMEDATALEN;
    	hash_ctl.entrysize = sizeof(PreparedStatement);
    
    	prepared_queries = hash_create("Prepared Queries",
    								   32,
    								   &hash_ctl,
    								   HASH_ELEM);
    }
    
    /*
     * Store all the data pertaining to a query in the hash table using
     * 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
     * be NULL only if the original query (before rewriting) was empty.
     */
    void
    StorePreparedStatement(const char *stmt_name,
    					   Node *raw_parse_tree,
    					   const char *query_string,
    					   const char *commandTag,
    					   Oid *param_types,
    					   int num_params,
    					   int cursor_options,
    					   List *stmt_list,
    					   bool from_sql)
    {
    	PreparedStatement *entry;
    	CachedPlanSource *plansource;
    	bool		found;
    
    	/* Initialize the hash table, if necessary */
    	if (!prepared_queries)
    		InitQueryHashTable();
    
    	/* Check for pre-existing entry of same name */
    	hash_search(prepared_queries, stmt_name, HASH_FIND, &found);
    
    	if (found)
    		ereport(ERROR,
    				(errcode(ERRCODE_DUPLICATE_PSTATEMENT),
    				 errmsg("prepared statement \"%s\" already exists",
    						stmt_name)));
    
    	/* Create a plancache entry */
    	plansource = CreateCachedPlan(raw_parse_tree,
    								  query_string,
    								  commandTag,
    								  param_types,
    								  num_params,
    								  cursor_options,
    								  stmt_list,
    								  true,
    								  true);
    
    	/* Now we can add entry to hash table */
    	entry = (PreparedStatement *) hash_search(prepared_queries,
    											  stmt_name,
    											  HASH_ENTER,
    											  &found);
    
    	/* Shouldn't get a duplicate entry */
    	if (found)
    		elog(ERROR, "duplicate prepared statement \"%s\"",
    			 stmt_name);
    
    	/* 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)
    {
    	PreparedStatement *entry;
    
    	/*
    	 * If the hash table hasn't been initialized, it can't be storing
    	 * anything, therefore it couldn't possibly store our plan.
    	 */
    	if (prepared_queries)
    		entry = (PreparedStatement *) hash_search(prepared_queries,
    												  stmt_name,
    												  HASH_FIND,
    												  NULL);
    	else
    		entry = NULL;
    
    	if (!entry && throwError)
    		ereport(ERROR,
    				(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
    				 errmsg("prepared statement \"%s\" does not exist",
    						stmt_name)));
    
    	return entry;
    }
    
    /*
     * Given a prepared statement, determine the result tupledesc it will
     * produce.  Returns NULL if the execution will not return tuples.
     *
     * Note: the result is created or copied into current memory context.
     */
    TupleDesc
    FetchPreparedStatementResultDesc(PreparedStatement *stmt)
    {
    	/*
    	 * Since we don't allow prepared statements' result tupdescs to change,
    	 * there's no need for a revalidate call here.
    	 */
    	Assert(stmt->plansource->fixed_result);
    	if (stmt->plansource->resultDesc)
    		return CreateTupleDescCopy(stmt->plansource->resultDesc);
    	else
    		return NULL;
    }
    
    /*
     * Given a prepared statement that returns tuples, extract the query
     * targetlist.	Returns NIL if the statement doesn't have a determinable
     * targetlist.
     *
     * Note: this is pretty ugly, but since it's only used in corner cases like
     * Describe Statement on an EXECUTE command, we don't worry too much about
     * efficiency.
     */
    List *
    FetchPreparedStatementTargetList(PreparedStatement *stmt)
    {
    	List	   *tlist;
    	CachedPlan *cplan;
    
    	/* No point in looking if it doesn't return tuples */
    	if (stmt->plansource->resultDesc == NULL)
    		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;
    }
    
    /*
     * Implements the 'DEALLOCATE' utility statement: deletes the
     * specified plan from storage.
     */
    void
    DeallocateQuery(DeallocateStmt *stmt)
    {
    	if (stmt->name)
    		DropPreparedStatement(stmt->name, true);
    	else
    		DropAllPreparedStatements();
    }
    
    /*
     * Internal version of DEALLOCATE
     *
     * If showError is false, dropping a nonexistent statement is a no-op.
     */
    void
    DropPreparedStatement(const char *stmt_name, bool showError)
    {
    	PreparedStatement *entry;
    
    	/* Find the query's hash table entry; raise error if wanted */
    	entry = FetchPreparedStatement(stmt_name, showError);
    
    	if (entry)
    	{
    		/* Release the plancache entry */
    		DropCachedPlan(entry->plansource);
    
    		/* Now we can remove the hash table entry */
    		hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
    	}
    }
    
    /*
     * Drop all cached statements.
     */
    void
    DropAllPreparedStatements(void)
    {
    	HASH_SEQ_STATUS seq;
    	PreparedStatement *entry;
    
    	/* nothing cached */
    	if (!prepared_queries)
    		return;
    
    	/* walk over cache */
    	hash_seq_init(&seq, prepared_queries);
    	while ((entry = hash_seq_search(&seq)) != NULL)
    	{
    		/* Release the plancache entry */
    		DropCachedPlan(entry->plansource);
    
    		/* Now we can remove the hash table entry */
    		hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
    	}
    }
    
    /*
     * Implements the 'EXPLAIN EXECUTE' utility statement.
     *
     * Note: the passed-in queryString is that of the EXPLAIN EXECUTE,
     * not the original PREPARE; we get the latter string from the plancache.
     */
    void
    ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
    					const char *queryString, ParamListInfo params)
    {
    	PreparedStatement *entry;
    	const char *query_string;
    	CachedPlan *cplan;
    	List	   *plan_list;
    	ListCell   *p;
    	ParamListInfo paramLI = NULL;
    	EState	   *estate = NULL;
    
    	/* Look it up in the hash table */
    	entry = FetchPreparedStatement(execstmt->name, true);
    
    	/* 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");
    
    	query_string = entry->plansource->query_string;
    
    	/* Replan if needed, and acquire a transient refcount */
    	cplan = RevalidateCachedPlan(entry->plansource, true);
    
    	plan_list = cplan->stmt_list;
    
    	/* Evaluate parameters, if any */
    	if (entry->plansource->num_params)
    	{
    		/*
    		 * Need an EState to evaluate parameters; must not delete it till end
    		 * of query, in case parameters are pass-by-reference.
    		 */
    		estate = CreateExecutorState();
    		estate->es_param_list_info = params;
    		paramLI = EvaluateParams(entry, execstmt->params,
    								 queryString, estate);
    	}
    
    	/* Explain each query */
    	foreach(p, plan_list)
    	{
    		PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
    
    		if (IsA(pstmt, PlannedStmt))
    		{
    			if (execstmt->into)
    			{
    				if (pstmt->commandType != CMD_SELECT ||
    					pstmt->utilityStmt != NULL)
    					ereport(ERROR,
    							(errcode(ERRCODE_WRONG_OBJECT_TYPE),
    							 errmsg("prepared statement is not a SELECT")));
    
    				/* Copy the stmt so we can modify it */
    				pstmt = copyObject(pstmt);
    
    				pstmt->intoClause = execstmt->into;
    			}
    
    			ExplainOnePlan(pstmt, es, query_string, paramLI);
    		}
    		else
    		{
    			ExplainOneUtility((Node *) pstmt, es, query_string, params);
    		}
    
    		/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
    
    		/* Separate plans with an appropriate separator */
    		if (lnext(p) != NULL)
    			ExplainSeparatePlans(es);
    	}
    
    	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)
    {
    	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
    	TupleDesc	tupdesc;
    	Tuplestorestate *tupstore;
    	MemoryContext per_query_ctx;
    	MemoryContext oldcontext;
    
    	/* check to see if caller supports us returning a tuplestore */
    	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
    		ereport(ERROR,
    				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    				 errmsg("set-valued function called in context that cannot accept a set")));
    	if (!(rsinfo->allowedModes & SFRM_Materialize))
    		ereport(ERROR,
    				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    				 errmsg("materialize mode required, but it is not " \
    						"allowed in this context")));
    
    	/* need to build tuplestore in query context */
    	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
    	oldcontext = MemoryContextSwitchTo(per_query_ctx);
    
    	/*
    	 * build tupdesc for result tuples. This must match the definition of the
    	 * pg_prepared_statements view in system_views.sql
    	 */
    	tupdesc = CreateTemplateTupleDesc(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);
    
    	/*
    	 * We put all the tuples into a tuplestore in one scan of the hashtable.
    	 * This avoids any issue of the hashtable possibly changing between calls.
    	 */
    	tupstore =
    		tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
    							  false, work_mem);
    
    	/* hash table might be uninitialized */
    	if (prepared_queries)
    	{
    		HASH_SEQ_STATUS hash_seq;
    		PreparedStatement *prep_stmt;
    
    		hash_seq_init(&hash_seq, prepared_queries);
    		while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL)
    		{
    			Datum		values[5];
    			bool		nulls[5];
    
    			/* generate junk in short-term context */
    			MemoryContextSwitchTo(oldcontext);
    
    			MemSet(nulls, 0, sizeof(nulls));
    
    			values[0] = CStringGetTextDatum(prep_stmt->stmt_name);
    			values[1] = CStringGetTextDatum(prep_stmt->plansource->query_string);
    			values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
    			values[3] = build_regtype_array(prep_stmt->plansource->param_types,
    										  prep_stmt->plansource->num_params);
    			values[4] = BoolGetDatum(prep_stmt->from_sql);
    
    			/* switch to appropriate context while storing the tuple */
    			MemoryContextSwitchTo(per_query_ctx);
    			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
    		}
    	}
    
    	/* clean up and return the tuplestore */
    	tuplestore_donestoring(tupstore);
    
    	MemoryContextSwitchTo(oldcontext);
    
    	rsinfo->returnMode = SFRM_Materialize;
    	rsinfo->setResult = tupstore;
    	rsinfo->setDesc = tupdesc;
    
    	return (Datum) 0;
    }
    
    /*
     * This utility function takes a C array of Oids, and returns a Datum
     * pointing to a one-dimensional Postgres array of regtypes. An empty
     * array is returned as a zero-element array, not NULL.
     */
    static Datum
    build_regtype_array(Oid *param_types, int num_params)
    {
    	Datum	   *tmp_ary;
    	ArrayType  *result;
    	int			i;
    
    	tmp_ary = (Datum *) palloc(num_params * sizeof(Datum));
    
    	for (i = 0; i < num_params; i++)
    		tmp_ary[i] = ObjectIdGetDatum(param_types[i]);
    
    	/* XXX: this hardcodes assumptions about the regtype type */
    	result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i');
    	return PointerGetDatum(result);
    }