2002-08-27 06:55:12 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* prepare.c
|
|
|
|
* Prepareable SQL statements via PREPARE, EXECUTE and DEALLOCATE
|
|
|
|
*
|
2003-05-05 02:44:56 +02:00
|
|
|
* This module also implements storage of prepared statements that are
|
|
|
|
* accessed via the extended FE/BE query protocol.
|
|
|
|
*
|
|
|
|
*
|
2009-01-01 18:24:05 +01:00
|
|
|
* Copyright (c) 2002-2009, PostgreSQL Global Development Group
|
2002-08-27 06:55:12 +02:00
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2009-12-29 18:40:59 +01:00
|
|
|
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.101 2009/12/29 17:40:59 heikki Exp $
|
2002-08-27 06:55:12 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
|
2006-07-13 18:49:20 +02:00
|
|
|
#include "access/xact.h"
|
2006-01-08 08:00:27 +01:00
|
|
|
#include "catalog/pg_type.h"
|
2002-08-27 06:55:12 +02:00
|
|
|
#include "commands/prepare.h"
|
2007-04-27 01:24:46 +02:00
|
|
|
#include "miscadmin.h"
|
2008-08-26 00:42:34 +02:00
|
|
|
#include "nodes/nodeFuncs.h"
|
2007-03-13 01:33:44 +01:00
|
|
|
#include "parser/analyze.h"
|
|
|
|
#include "parser/parse_coerce.h"
|
|
|
|
#include "parser/parse_expr.h"
|
|
|
|
#include "parser/parse_type.h"
|
2002-08-27 06:55:12 +02:00
|
|
|
#include "rewrite/rewriteHandler.h"
|
|
|
|
#include "tcop/pquery.h"
|
|
|
|
#include "tcop/tcopprot.h"
|
|
|
|
#include "tcop/utility.h"
|
2006-01-08 08:00:27 +01:00
|
|
|
#include "utils/builtins.h"
|
2002-08-27 06:55:12 +02:00
|
|
|
#include "utils/memutils.h"
|
2008-03-26 19:48:59 +01:00
|
|
|
#include "utils/snapmgr.h"
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2006-04-25 16:11:59 +02:00
|
|
|
|
2002-08-27 06:55:12 +02:00
|
|
|
/*
|
|
|
|
* The hash table in which prepared queries are stored. This is
|
|
|
|
* per-backend: query plans are not shared between backends.
|
2003-05-05 02:44:56 +02:00
|
|
|
* The keys for this hash table are the arguments to PREPARE and EXECUTE
|
|
|
|
* (statement names); the entries are PreparedStatement structs.
|
2002-08-27 06:55:12 +02:00
|
|
|
*/
|
|
|
|
static HTAB *prepared_queries = NULL;
|
|
|
|
|
|
|
|
static void InitQueryHashTable(void);
|
2007-03-13 01:33:44 +01:00
|
|
|
static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
|
2007-11-15 22:14:46 +01:00
|
|
|
const char *queryString, EState *estate);
|
2007-03-13 01:33:44 +01:00
|
|
|
static Datum build_regtype_array(Oid *param_types, int num_params);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Implements the 'PREPARE' utility statement.
|
|
|
|
*/
|
|
|
|
void
|
2007-03-13 01:33:44 +01:00
|
|
|
PrepareQuery(PrepareStmt *stmt, const char *queryString)
|
2002-08-27 06:55:12 +02:00
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
Oid *argtypes = NULL;
|
|
|
|
int nargs;
|
2004-12-12 21:17:06 +01:00
|
|
|
Query *query;
|
2002-09-04 22:31:48 +02:00
|
|
|
List *query_list,
|
2003-05-02 22:54:36 +02:00
|
|
|
*plan_list;
|
2007-03-13 01:33:44 +01:00
|
|
|
int i;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
/*
|
|
|
|
* Disallow empty-string statement name (conflicts with protocol-level
|
|
|
|
* unnamed statement).
|
|
|
|
*/
|
|
|
|
if (!stmt->name || stmt->name[0] == '\0')
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
|
|
|
|
errmsg("invalid statement name: must not be empty")));
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* 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);
|
2007-11-11 20:22:49 +01:00
|
|
|
Oid toid = typenameTypeId(pstate, tn, NULL);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
*
|
2007-11-15 22:14:46 +01:00
|
|
|
* 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.
|
2007-03-13 01:33:44 +01:00
|
|
|
*/
|
2007-06-24 00:12:52 +02:00
|
|
|
query = parse_analyze_varparams((Node *) copyObject(stmt->query),
|
|
|
|
queryString,
|
|
|
|
&argtypes, &nargs);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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)));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2007-06-24 00:12:52 +02:00
|
|
|
* grammar only allows OptimizableStmt, so this check should be redundant
|
2007-03-13 01:33:44 +01:00
|
|
|
*/
|
|
|
|
switch (query->commandType)
|
2003-05-05 02:44:56 +02:00
|
|
|
{
|
|
|
|
case CMD_SELECT:
|
|
|
|
case CMD_INSERT:
|
|
|
|
case CMD_UPDATE:
|
|
|
|
case CMD_DELETE:
|
2007-04-28 00:05:49 +02:00
|
|
|
/* OK */
|
2003-05-05 02:44:56 +02:00
|
|
|
break;
|
|
|
|
default:
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
|
|
|
|
errmsg("utility statements cannot be prepared")));
|
2003-05-05 02:44:56 +02:00
|
|
|
break;
|
|
|
|
}
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/* Rewrite the query. The result could be 0, 1, or many queries. */
|
2004-12-12 21:17:06 +01:00
|
|
|
query_list = QueryRewrite(query);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2008-12-13 03:29:22 +01:00
|
|
|
/* Generate plans for queries. */
|
|
|
|
plan_list = pg_plan_queries(query_list, 0, NULL);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2005-05-24 06:18:04 +02:00
|
|
|
/*
|
2007-03-13 01:33:44 +01:00
|
|
|
* Save the results.
|
2005-05-24 06:18:04 +02:00
|
|
|
*/
|
2003-05-05 02:44:56 +02:00
|
|
|
StorePreparedStatement(stmt->name,
|
2007-03-13 01:33:44 +01:00
|
|
|
stmt->query,
|
|
|
|
queryString,
|
2007-04-28 00:05:49 +02:00
|
|
|
CreateCommandTag((Node *) query),
|
2007-03-13 01:33:44 +01:00
|
|
|
argtypes,
|
|
|
|
nargs,
|
2007-11-15 22:14:46 +01:00
|
|
|
0, /* default cursor options */
|
2003-05-05 02:44:56 +02:00
|
|
|
plan_list,
|
2006-01-08 08:00:27 +01:00
|
|
|
true);
|
2002-08-27 06:55:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Implements the 'EXECUTE' utility statement.
|
2008-07-21 17:26:55 +02:00
|
|
|
*
|
|
|
|
* 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.
|
2002-08-27 06:55:12 +02:00
|
|
|
*/
|
|
|
|
void
|
2007-03-13 01:33:44 +01:00
|
|
|
ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
|
|
|
|
ParamListInfo params,
|
2005-11-29 02:25:50 +01:00
|
|
|
DestReceiver *dest, char *completionTag)
|
2002-08-27 06:55:12 +02:00
|
|
|
{
|
2003-05-05 02:44:56 +02:00
|
|
|
PreparedStatement *entry;
|
2007-03-13 01:33:44 +01:00
|
|
|
CachedPlan *cplan;
|
2007-02-20 18:32:18 +01:00
|
|
|
List *plan_list;
|
2002-08-27 06:55:12 +02:00
|
|
|
ParamListInfo paramLI = NULL;
|
2003-02-03 00:46:38 +01:00
|
|
|
EState *estate = NULL;
|
2003-05-02 22:54:36 +02:00
|
|
|
Portal portal;
|
Adjust things so that the query_string of a cached plan and the sourceText of
a portal are never NULL, but reliably provide the source text of the query.
It turns out that there was only one place that was really taking a short-cut,
which was the 'EXECUTE' utility statement. That doesn't seem like a
sufficiently critical performance hotspot to justify not offering a guarantee
of validity of the portal source text. Fix it to copy the source text over
from the cached plan. Add Asserts in the places that set up cached plans and
portals to reject null source strings, and simplify a bunch of places that
formerly needed to guard against nulls.
There may be a few places that cons up statements for execution without
having any source text at all; I found one such in ConvertTriggerToFK().
It seems sufficient to inject a phony source string in such a case,
for instance
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
NULL, false, None_Receiver, NULL);
We should take a second look at the usage of debug_query_string,
particularly the recently added current_query() SQL function.
ITAGAKI Takahiro and Tom Lane
2008-07-18 22:26:06 +02:00
|
|
|
char *query_string;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/* Look it up in the hash table */
|
2003-05-05 02:44:56 +02:00
|
|
|
entry = FetchPreparedStatement(stmt->name, true);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Shouldn't have a non-fully-planned plancache entry */
|
|
|
|
if (!entry->plansource->fully_planned)
|
2007-02-20 18:32:18 +01:00
|
|
|
elog(ERROR, "EXECUTE does not support unplanned prepared statements");
|
2007-03-13 01:33:44 +01:00
|
|
|
/* 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");
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/* Evaluate parameters, if any */
|
2007-03-13 01:33:44 +01:00
|
|
|
if (entry->plansource->num_params > 0)
|
2002-08-27 06:55:12 +02:00
|
|
|
{
|
2003-02-03 00:46:38 +01:00
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Need an EState to evaluate parameters; must not delete it till end
|
|
|
|
* of query, in case parameters are pass-by-reference.
|
2003-02-03 00:46:38 +01:00
|
|
|
*/
|
|
|
|
estate = CreateExecutorState();
|
2005-11-29 02:25:50 +01:00
|
|
|
estate->es_param_list_info = params;
|
2007-03-13 01:33:44 +01:00
|
|
|
paramLI = EvaluateParams(entry, stmt->params,
|
|
|
|
queryString, estate);
|
2002-08-27 06:55:12 +02:00
|
|
|
}
|
|
|
|
|
2006-01-18 07:49:30 +01:00
|
|
|
/* Create a new portal to run the query in */
|
2003-05-02 22:54:36 +02:00
|
|
|
portal = CreateNewPortal();
|
2006-01-18 07:49:30 +01:00
|
|
|
/* Don't display the portal in pg_cursors, it is for internal use only */
|
|
|
|
portal->visible = false;
|
2006-10-04 02:30:14 +02:00
|
|
|
|
Adjust things so that the query_string of a cached plan and the sourceText of
a portal are never NULL, but reliably provide the source text of the query.
It turns out that there was only one place that was really taking a short-cut,
which was the 'EXECUTE' utility statement. That doesn't seem like a
sufficiently critical performance hotspot to justify not offering a guarantee
of validity of the portal source text. Fix it to copy the source text over
from the cached plan. Add Asserts in the places that set up cached plans and
portals to reject null source strings, and simplify a bunch of places that
formerly needed to guard against nulls.
There may be a few places that cons up statements for execution without
having any source text at all; I found one such in ConvertTriggerToFK().
It seems sufficient to inject a phony source string in such a case,
for instance
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
NULL, false, None_Receiver, NULL);
We should take a second look at the usage of debug_query_string,
particularly the recently added current_query() SQL function.
ITAGAKI Takahiro and Tom Lane
2008-07-18 22:26:06 +02:00
|
|
|
/* Copy the plan's saved query string into the portal's memory */
|
|
|
|
query_string = MemoryContextStrdup(PortalGetHeapMemory(portal),
|
|
|
|
entry->plansource->query_string);
|
|
|
|
|
2003-05-02 22:54:36 +02:00
|
|
|
/*
|
2007-03-13 01:33:44 +01:00
|
|
|
* 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.
|
2003-05-02 22:54:36 +02:00
|
|
|
*/
|
|
|
|
if (stmt->into)
|
|
|
|
{
|
|
|
|
MemoryContext oldContext;
|
2007-02-20 18:32:18 +01:00
|
|
|
PlannedStmt *pstmt;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Replan if needed, and increment plan refcount transiently */
|
|
|
|
cplan = RevalidateCachedPlan(entry->plansource, true);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Copy plan into portal's context, and modify */
|
|
|
|
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
|
|
|
|
|
|
|
plan_list = copyObject(cplan->stmt_list);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-02-20 18:32:18 +01:00
|
|
|
if (list_length(plan_list) != 1)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
|
|
errmsg("prepared statement is not a SELECT")));
|
2007-02-20 18:32:18 +01:00
|
|
|
pstmt = (PlannedStmt *) linitial(plan_list);
|
|
|
|
if (!IsA(pstmt, PlannedStmt) ||
|
2007-04-28 00:05:49 +02:00
|
|
|
pstmt->commandType != CMD_SELECT ||
|
|
|
|
pstmt->utilityStmt != NULL)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
|
|
errmsg("prepared statement is not a SELECT")));
|
2007-04-28 00:05:49 +02:00
|
|
|
pstmt->intoClause = copyObject(stmt->into);
|
2002-12-15 17:17:59 +01:00
|
|
|
|
2003-05-02 22:54:36 +02:00
|
|
|
MemoryContextSwitchTo(oldContext);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
/* 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;
|
2003-05-02 22:54:36 +02:00
|
|
|
}
|
2002-12-15 17:17:59 +01:00
|
|
|
|
2003-05-02 22:54:36 +02:00
|
|
|
PortalDefineQuery(portal,
|
2006-08-08 03:23:15 +02:00
|
|
|
NULL,
|
Adjust things so that the query_string of a cached plan and the sourceText of
a portal are never NULL, but reliably provide the source text of the query.
It turns out that there was only one place that was really taking a short-cut,
which was the 'EXECUTE' utility statement. That doesn't seem like a
sufficiently critical performance hotspot to justify not offering a guarantee
of validity of the portal source text. Fix it to copy the source text over
from the cached plan. Add Asserts in the places that set up cached plans and
portals to reject null source strings, and simplify a bunch of places that
formerly needed to guard against nulls.
There may be a few places that cons up statements for execution without
having any source text at all; I found one such in ConvertTriggerToFK().
It seems sufficient to inject a phony source string in such a case,
for instance
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
NULL, false, None_Receiver, NULL);
We should take a second look at the usage of debug_query_string,
particularly the recently added current_query() SQL function.
ITAGAKI Takahiro and Tom Lane
2008-07-18 22:26:06 +02:00
|
|
|
query_string,
|
2007-03-13 01:33:44 +01:00
|
|
|
entry->plansource->commandTag,
|
2003-05-02 22:54:36 +02:00
|
|
|
plan_list,
|
2007-03-13 01:33:44 +01:00
|
|
|
cplan);
|
2002-12-15 17:17:59 +01:00
|
|
|
|
2003-05-02 22:54:36 +02:00
|
|
|
/*
|
|
|
|
* Run the portal to completion.
|
|
|
|
*/
|
2008-05-12 22:02:02 +02:00
|
|
|
PortalStart(portal, paramLI, GetActiveSnapshot());
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
(void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-02 22:54:36 +02:00
|
|
|
PortalDrop(portal, false);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-02-03 00:46:38 +01:00
|
|
|
if (estate)
|
|
|
|
FreeExecutorState(estate);
|
2002-12-15 17:17:59 +01:00
|
|
|
|
|
|
|
/* No need to pfree other memory, MemoryContext will be reset */
|
2002-08-27 06:55:12 +02:00
|
|
|
}
|
|
|
|
|
2003-02-03 00:46:38 +01:00
|
|
|
/*
|
2007-03-13 01:33:44 +01:00
|
|
|
* 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.
|
2003-02-03 00:46:38 +01:00
|
|
|
*/
|
|
|
|
static ParamListInfo
|
2007-03-13 01:33:44 +01:00
|
|
|
EvaluateParams(PreparedStatement *pstmt, List *params,
|
|
|
|
const char *queryString, EState *estate)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
Oid *param_types = pstmt->plansource->param_types;
|
|
|
|
int num_params = pstmt->plansource->num_params;
|
|
|
|
int nparams = list_length(params);
|
|
|
|
ParseState *pstate;
|
2003-08-04 02:43:34 +02:00
|
|
|
ParamListInfo paramLI;
|
|
|
|
List *exprstates;
|
2007-03-13 01:33:44 +01:00
|
|
|
ListCell *l;
|
|
|
|
int i;
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
if (nparams != num_params)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
2007-11-15 22:14:46 +01:00
|
|
|
errmsg("wrong number of parameters for prepared statement \"%s\"",
|
|
|
|
pstmt->stmt_name),
|
2007-03-13 01:33:44 +01:00
|
|
|
errdetail("Expected %d parameters but got %d.",
|
|
|
|
num_params, nparams)));
|
|
|
|
|
|
|
|
/* Quick exit if no parameters */
|
|
|
|
if (num_params == 0)
|
2006-04-22 03:26:01 +02:00
|
|
|
return NULL;
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* We have to run parse analysis for the expressions. Since the parser is
|
|
|
|
* not cool about scribbling on its input, copy first.
|
2007-03-13 01:33:44 +01:00
|
|
|
*/
|
|
|
|
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),
|
2007-11-15 22:14:46 +01:00
|
|
|
errmsg("cannot use aggregate function in EXECUTE parameter")));
|
2008-12-28 19:54:01 +01:00
|
|
|
if (pstate->p_hasWindowFuncs)
|
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WINDOWING_ERROR),
|
2009-06-11 16:49:15 +02:00
|
|
|
errmsg("cannot use window function in EXECUTE parameter")));
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
given_type_id = exprType(expr);
|
|
|
|
|
|
|
|
expr = coerce_to_target_type(pstate, expr, given_type_id,
|
|
|
|
expected_type_id, -1,
|
|
|
|
COERCION_ASSIGNMENT,
|
2008-08-29 01:09:48 +02:00
|
|
|
COERCE_IMPLICIT_CAST,
|
|
|
|
-1);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
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)),
|
2007-11-15 22:14:46 +01:00
|
|
|
errhint("You will need to rewrite or cast the expression.")));
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
lfirst(l) = expr;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Prepare the expressions for execution */
|
2003-02-03 00:46:38 +01:00
|
|
|
exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
|
|
|
|
|
2006-04-22 03:26:01 +02:00
|
|
|
/* sizeof(ParamListInfoData) includes the first array element */
|
2007-03-13 01:33:44 +01:00
|
|
|
paramLI = (ParamListInfo)
|
|
|
|
palloc(sizeof(ParamListInfoData) +
|
|
|
|
(num_params - 1) *sizeof(ParamExternData));
|
2009-11-04 23:26:08 +01:00
|
|
|
/* we have static list of params, so no hooks needed */
|
|
|
|
paramLI->paramFetch = NULL;
|
|
|
|
paramLI->paramFetchArg = NULL;
|
|
|
|
paramLI->parserSetup = NULL;
|
|
|
|
paramLI->parserSetupArg = NULL;
|
2007-03-13 01:33:44 +01:00
|
|
|
paramLI->numParams = num_params;
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
i = 0;
|
|
|
|
foreach(l, exprstates)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
ExprState *n = lfirst(l);
|
2006-04-22 03:26:01 +02:00
|
|
|
ParamExternData *prm = ¶mLI->params[i];
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
prm->ptype = param_types[i];
|
2006-09-06 22:40:48 +02:00
|
|
|
prm->pflags = 0;
|
2006-04-22 03:26:01 +02:00
|
|
|
prm->value = ExecEvalExprSwitchContext(n,
|
|
|
|
GetPerTupleExprContext(estate),
|
|
|
|
&prm->isnull,
|
|
|
|
NULL);
|
2003-02-03 00:46:38 +01:00
|
|
|
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return paramLI;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-08-27 06:55:12 +02:00
|
|
|
/*
|
|
|
|
* Initialize query hash table upon first use.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
InitQueryHashTable(void)
|
|
|
|
{
|
2002-09-04 22:31:48 +02:00
|
|
|
HASHCTL hash_ctl;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
MemSet(&hash_ctl, 0, sizeof(hash_ctl));
|
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
hash_ctl.keysize = NAMEDATALEN;
|
|
|
|
hash_ctl.entrysize = sizeof(PreparedStatement);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
prepared_queries = hash_create("Prepared Queries",
|
|
|
|
32,
|
|
|
|
&hash_ctl,
|
|
|
|
HASH_ELEM);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Store all the data pertaining to a query in the hash table using
|
2007-03-13 01:33:44 +01:00
|
|
|
* 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.
|
2003-05-05 02:44:56 +02:00
|
|
|
*
|
|
|
|
* Exception: commandTag is presumed to be a pointer to a constant string,
|
2003-08-04 02:43:34 +02:00
|
|
|
* or possibly NULL, so it need not be copied. Note that commandTag should
|
2003-05-05 02:44:56 +02:00
|
|
|
* be NULL only if the original query (before rewriting) was empty.
|
2002-08-27 06:55:12 +02:00
|
|
|
*/
|
2003-05-05 02:44:56 +02:00
|
|
|
void
|
|
|
|
StorePreparedStatement(const char *stmt_name,
|
2007-03-13 01:33:44 +01:00
|
|
|
Node *raw_parse_tree,
|
2003-05-05 02:44:56 +02:00
|
|
|
const char *query_string,
|
|
|
|
const char *commandTag,
|
2007-03-13 01:33:44 +01:00
|
|
|
Oid *param_types,
|
|
|
|
int num_params,
|
2007-04-16 20:21:07 +02:00
|
|
|
int cursor_options,
|
2007-02-20 18:32:18 +01:00
|
|
|
List *stmt_list,
|
2006-01-08 08:00:27 +01:00
|
|
|
bool from_sql)
|
2002-08-27 06:55:12 +02:00
|
|
|
{
|
2003-05-05 02:44:56 +02:00
|
|
|
PreparedStatement *entry;
|
2007-03-13 01:33:44 +01:00
|
|
|
CachedPlanSource *plansource;
|
2002-09-04 22:31:48 +02:00
|
|
|
bool found;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/* Initialize the hash table, if necessary */
|
|
|
|
if (!prepared_queries)
|
|
|
|
InitQueryHashTable();
|
|
|
|
|
|
|
|
/* Check for pre-existing entry of same name */
|
2006-09-27 20:40:10 +02:00
|
|
|
hash_search(prepared_queries, stmt_name, HASH_FIND, &found);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
if (found)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_DUPLICATE_PSTATEMENT),
|
|
|
|
errmsg("prepared statement \"%s\" already exists",
|
|
|
|
stmt_name)));
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Create a plancache entry */
|
|
|
|
plansource = CreateCachedPlan(raw_parse_tree,
|
|
|
|
query_string,
|
|
|
|
commandTag,
|
|
|
|
param_types,
|
|
|
|
num_params,
|
2007-04-16 20:21:07 +02:00
|
|
|
cursor_options,
|
2007-03-13 01:33:44 +01:00
|
|
|
stmt_list,
|
|
|
|
true,
|
|
|
|
true);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/* Now we can add entry to hash table */
|
2003-05-05 02:44:56 +02:00
|
|
|
entry = (PreparedStatement *) hash_search(prepared_queries,
|
2006-09-27 20:40:10 +02:00
|
|
|
stmt_name,
|
2003-05-05 02:44:56 +02:00
|
|
|
HASH_ENTER,
|
|
|
|
&found);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2005-05-29 06:23:07 +02:00
|
|
|
/* Shouldn't get a duplicate entry */
|
|
|
|
if (found)
|
|
|
|
elog(ERROR, "duplicate prepared statement \"%s\"",
|
2002-08-27 06:55:12 +02:00
|
|
|
stmt_name);
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Fill in the hash table entry */
|
|
|
|
entry->plansource = plansource;
|
2007-02-20 18:32:18 +01:00
|
|
|
entry->from_sql = from_sql;
|
2006-06-21 00:52:00 +02:00
|
|
|
entry->prepare_time = GetCurrentStatementStartTimestamp();
|
2002-08-27 06:55:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2003-02-03 00:46:38 +01:00
|
|
|
* Lookup an existing query in the hash table. If the query does not
|
2003-07-20 23:56:35 +02:00
|
|
|
* actually exist, throw ereport(ERROR) or return NULL per second parameter.
|
2007-03-13 01:33:44 +01:00
|
|
|
*
|
|
|
|
* Note: this does not force the referenced plancache entry to be valid,
|
|
|
|
* since not all callers care.
|
2002-08-27 06:55:12 +02:00
|
|
|
*/
|
2003-05-05 02:44:56 +02:00
|
|
|
PreparedStatement *
|
|
|
|
FetchPreparedStatement(const char *stmt_name, bool throwError)
|
2002-08-27 06:55:12 +02:00
|
|
|
{
|
2003-05-05 02:44:56 +02:00
|
|
|
PreparedStatement *entry;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If the hash table hasn't been initialized, it can't be storing
|
|
|
|
* anything, therefore it couldn't possibly store our plan.
|
|
|
|
*/
|
2003-05-05 02:44:56 +02:00
|
|
|
if (prepared_queries)
|
|
|
|
entry = (PreparedStatement *) hash_search(prepared_queries,
|
2006-09-27 20:40:10 +02:00
|
|
|
stmt_name,
|
2003-05-05 02:44:56 +02:00
|
|
|
HASH_FIND,
|
|
|
|
NULL);
|
|
|
|
else
|
|
|
|
entry = NULL;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
if (!entry && throwError)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
|
|
|
|
errmsg("prepared statement \"%s\" does not exist",
|
|
|
|
stmt_name)));
|
2002-08-27 06:55:12 +02:00
|
|
|
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
2003-05-06 23:51:42 +02:00
|
|
|
/*
|
|
|
|
* 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
|
2003-08-08 23:42:59 +02:00
|
|
|
FetchPreparedStatementResultDesc(PreparedStatement *stmt)
|
2003-05-06 23:51:42 +02:00
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
2005-12-14 18:06:28 +01:00
|
|
|
}
|
|
|
|
|
2005-06-22 19:45:46 +02:00
|
|
|
/*
|
|
|
|
* Given a prepared statement that returns tuples, extract the query
|
2005-10-15 04:49:52 +02:00
|
|
|
* targetlist. Returns NIL if the statement doesn't have a determinable
|
2005-06-22 19:45:46 +02:00
|
|
|
* targetlist.
|
|
|
|
*
|
2007-03-13 01:33:44 +01:00
|
|
|
* 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.
|
2005-06-22 19:45:46 +02:00
|
|
|
*/
|
|
|
|
List *
|
|
|
|
FetchPreparedStatementTargetList(PreparedStatement *stmt)
|
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
List *tlist;
|
|
|
|
CachedPlan *cplan;
|
|
|
|
|
|
|
|
/* No point in looking if it doesn't return tuples */
|
|
|
|
if (stmt->plansource->resultDesc == NULL)
|
2007-02-20 18:32:18 +01:00
|
|
|
return NIL;
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
/* 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;
|
2005-06-22 19:45:46 +02:00
|
|
|
}
|
|
|
|
|
2002-08-27 06:55:12 +02:00
|
|
|
/*
|
|
|
|
* Implements the 'DEALLOCATE' utility statement: deletes the
|
|
|
|
* specified plan from storage.
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
DeallocateQuery(DeallocateStmt *stmt)
|
|
|
|
{
|
2007-04-12 08:53:49 +02:00
|
|
|
if (stmt->name)
|
|
|
|
DropPreparedStatement(stmt->name, true);
|
|
|
|
else
|
|
|
|
DropAllPreparedStatements();
|
2003-05-05 02:44:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
/* Find the query's hash table entry; raise error if wanted */
|
|
|
|
entry = FetchPreparedStatement(stmt_name, showError);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
if (entry)
|
|
|
|
{
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Release the plancache entry */
|
|
|
|
DropCachedPlan(entry->plansource);
|
2002-08-27 06:55:12 +02:00
|
|
|
|
2003-05-05 02:44:56 +02:00
|
|
|
/* Now we can remove the hash table entry */
|
|
|
|
hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
|
|
|
|
}
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
|
|
|
|
2007-04-12 08:53:49 +02:00
|
|
|
/*
|
|
|
|
* 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2003-02-03 00:46:38 +01:00
|
|
|
/*
|
|
|
|
* Implements the 'EXPLAIN EXECUTE' utility statement.
|
2009-01-02 21:42:00 +01:00
|
|
|
*
|
|
|
|
* Note: the passed-in queryString is that of the EXPLAIN EXECUTE,
|
|
|
|
* not the original PREPARE; we get the latter string from the plancache.
|
2003-02-03 00:46:38 +01:00
|
|
|
*/
|
|
|
|
void
|
2009-07-27 01:34:18 +02:00
|
|
|
ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainState *es,
|
|
|
|
const char *queryString, ParamListInfo params)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
2003-05-05 02:44:56 +02:00
|
|
|
PreparedStatement *entry;
|
2009-01-02 21:42:00 +01:00
|
|
|
const char *query_string;
|
2007-03-13 01:33:44 +01:00
|
|
|
CachedPlan *cplan;
|
2007-02-20 18:32:18 +01:00
|
|
|
List *plan_list;
|
|
|
|
ListCell *p;
|
2003-02-03 00:46:38 +01:00
|
|
|
ParamListInfo paramLI = NULL;
|
|
|
|
EState *estate = NULL;
|
|
|
|
|
|
|
|
/* Look it up in the hash table */
|
2003-05-05 02:44:56 +02:00
|
|
|
entry = FetchPreparedStatement(execstmt->name, true);
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Shouldn't have a non-fully-planned plancache entry */
|
|
|
|
if (!entry->plansource->fully_planned)
|
2007-02-20 18:32:18 +01:00
|
|
|
elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements");
|
2007-03-13 01:33:44 +01:00
|
|
|
/* 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");
|
|
|
|
|
2009-01-02 21:42:00 +01:00
|
|
|
query_string = entry->plansource->query_string;
|
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
/* Replan if needed, and acquire a transient refcount */
|
|
|
|
cplan = RevalidateCachedPlan(entry->plansource, true);
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
plan_list = cplan->stmt_list;
|
2003-02-03 00:46:38 +01:00
|
|
|
|
|
|
|
/* Evaluate parameters, if any */
|
2007-03-13 01:33:44 +01:00
|
|
|
if (entry->plansource->num_params)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
|
|
|
/*
|
2005-10-15 04:49:52 +02:00
|
|
|
* Need an EState to evaluate parameters; must not delete it till end
|
|
|
|
* of query, in case parameters are pass-by-reference.
|
2003-02-03 00:46:38 +01:00
|
|
|
*/
|
|
|
|
estate = CreateExecutorState();
|
2005-11-29 02:25:50 +01:00
|
|
|
estate->es_param_list_info = params;
|
2007-03-13 01:33:44 +01:00
|
|
|
paramLI = EvaluateParams(entry, execstmt->params,
|
|
|
|
queryString, estate);
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Explain each query */
|
2007-02-20 18:32:18 +01:00
|
|
|
foreach(p, plan_list)
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
2007-02-20 18:32:18 +01:00
|
|
|
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
if (IsA(pstmt, PlannedStmt))
|
2003-02-03 00:46:38 +01:00
|
|
|
{
|
|
|
|
if (execstmt->into)
|
|
|
|
{
|
2007-04-28 00:05:49 +02:00
|
|
|
if (pstmt->commandType != CMD_SELECT ||
|
|
|
|
pstmt->utilityStmt != NULL)
|
2003-07-20 23:56:35 +02:00
|
|
|
ereport(ERROR,
|
|
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
2005-10-15 04:49:52 +02:00
|
|
|
errmsg("prepared statement is not a SELECT")));
|
2003-02-03 00:46:38 +01:00
|
|
|
|
2007-02-20 18:32:18 +01:00
|
|
|
/* Copy the stmt so we can modify it */
|
|
|
|
pstmt = copyObject(pstmt);
|
2003-05-06 22:26:28 +02:00
|
|
|
|
2007-04-28 00:05:49 +02:00
|
|
|
pstmt->intoClause = execstmt->into;
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
|
|
|
|
2009-07-27 01:34:18 +02:00
|
|
|
ExplainOnePlan(pstmt, es, query_string, paramLI);
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
2007-03-13 01:33:44 +01:00
|
|
|
else
|
|
|
|
{
|
2009-07-27 01:34:18 +02:00
|
|
|
ExplainOneUtility((Node *) pstmt, es, query_string, params);
|
2007-03-13 01:33:44 +01:00
|
|
|
}
|
2003-02-03 00:46:38 +01:00
|
|
|
|
|
|
|
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
|
|
|
|
|
2009-08-10 07:46:50 +02:00
|
|
|
/* Separate plans with an appropriate separator */
|
|
|
|
if (lnext(p) != NULL)
|
|
|
|
ExplainSeparatePlans(es);
|
2003-02-03 00:46:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (estate)
|
|
|
|
FreeExecutorState(estate);
|
2007-03-13 01:33:44 +01:00
|
|
|
|
|
|
|
ReleaseCachedPlan(cplan, true);
|
2002-08-27 06:55:12 +02:00
|
|
|
}
|
2006-01-08 08:00:27 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This set returning function reads all the prepared statements and
|
2006-01-16 19:15:31 +01:00
|
|
|
* returns a set of (name, statement, prepare_time, param_types, from_sql).
|
2006-01-08 08:00:27 +01:00
|
|
|
*/
|
|
|
|
Datum
|
|
|
|
pg_prepared_statement(PG_FUNCTION_ARGS)
|
|
|
|
{
|
2007-04-27 01:24:46 +02:00
|
|
|
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")));
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
/* need to build tuplestore in query context */
|
|
|
|
per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
|
|
|
|
oldcontext = MemoryContextSwitchTo(per_query_ctx);
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
/*
|
2007-11-15 22:14:46 +01:00
|
|
|
* build tupdesc for result tuples. This must match the definition of the
|
|
|
|
* pg_prepared_statements view in system_views.sql
|
2007-04-27 01:24:46 +02:00
|
|
|
*/
|
|
|
|
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);
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2008-10-29 01:00:39 +01:00
|
|
|
tupstore =
|
|
|
|
tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
|
|
|
|
false, work_mem);
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2009-12-29 18:40:59 +01:00
|
|
|
/* generate junk in short-term context */
|
|
|
|
MemoryContextSwitchTo(oldcontext);
|
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
/* hash table might be uninitialized */
|
|
|
|
if (prepared_queries)
|
|
|
|
{
|
|
|
|
HASH_SEQ_STATUS hash_seq;
|
|
|
|
PreparedStatement *prep_stmt;
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
hash_seq_init(&hash_seq, prepared_queries);
|
|
|
|
while ((prep_stmt = hash_seq_search(&hash_seq)) != NULL)
|
|
|
|
{
|
|
|
|
Datum values[5];
|
|
|
|
bool nulls[5];
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2008-03-25 23:42:46 +01:00
|
|
|
values[0] = CStringGetTextDatum(prep_stmt->stmt_name);
|
Adjust things so that the query_string of a cached plan and the sourceText of
a portal are never NULL, but reliably provide the source text of the query.
It turns out that there was only one place that was really taking a short-cut,
which was the 'EXECUTE' utility statement. That doesn't seem like a
sufficiently critical performance hotspot to justify not offering a guarantee
of validity of the portal source text. Fix it to copy the source text over
from the cached plan. Add Asserts in the places that set up cached plans and
portals to reject null source strings, and simplify a bunch of places that
formerly needed to guard against nulls.
There may be a few places that cons up statements for execution without
having any source text at all; I found one such in ConvertTriggerToFK().
It seems sufficient to inject a phony source string in such a case,
for instance
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
NULL, false, None_Receiver, NULL);
We should take a second look at the usage of debug_query_string,
particularly the recently added current_query() SQL function.
ITAGAKI Takahiro and Tom Lane
2008-07-18 22:26:06 +02:00
|
|
|
values[1] = CStringGetTextDatum(prep_stmt->plansource->query_string);
|
2007-04-27 01:24:46 +02:00
|
|
|
values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
|
|
|
|
values[3] = build_regtype_array(prep_stmt->plansource->param_types,
|
2007-11-15 22:14:46 +01:00
|
|
|
prep_stmt->plansource->num_params);
|
2007-04-27 01:24:46 +02:00
|
|
|
values[4] = BoolGetDatum(prep_stmt->from_sql);
|
|
|
|
|
2008-03-25 20:26:54 +01:00
|
|
|
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
|
2007-04-27 01:24:46 +02:00
|
|
|
}
|
2006-01-08 08:00:27 +01:00
|
|
|
}
|
|
|
|
|
2007-04-27 01:24:46 +02:00
|
|
|
/* clean up and return the tuplestore */
|
|
|
|
tuplestore_donestoring(tupstore);
|
|
|
|
|
|
|
|
rsinfo->returnMode = SFRM_Materialize;
|
|
|
|
rsinfo->setResult = tupstore;
|
|
|
|
rsinfo->setDesc = tupdesc;
|
|
|
|
|
|
|
|
return (Datum) 0;
|
2006-01-08 08:00:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2007-03-13 01:33:44 +01:00
|
|
|
* 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.
|
2006-01-08 08:00:27 +01:00
|
|
|
*/
|
|
|
|
static Datum
|
2007-03-13 01:33:44 +01:00
|
|
|
build_regtype_array(Oid *param_types, int num_params)
|
2006-01-08 08:00:27 +01:00
|
|
|
{
|
2006-01-16 19:15:31 +01:00
|
|
|
Datum *tmp_ary;
|
|
|
|
ArrayType *result;
|
2007-03-13 01:33:44 +01:00
|
|
|
int i;
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
tmp_ary = (Datum *) palloc(num_params * sizeof(Datum));
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2007-03-13 01:33:44 +01:00
|
|
|
for (i = 0; i < num_params; i++)
|
|
|
|
tmp_ary[i] = ObjectIdGetDatum(param_types[i]);
|
2006-01-08 08:00:27 +01:00
|
|
|
|
2006-01-16 19:15:31 +01:00
|
|
|
/* XXX: this hardcodes assumptions about the regtype type */
|
2007-03-13 01:33:44 +01:00
|
|
|
result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i');
|
2006-01-16 19:15:31 +01:00
|
|
|
return PointerGetDatum(result);
|
2006-01-08 08:00:27 +01:00
|
|
|
}
|