aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2007-03-13 00:33:44 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2007-03-13 00:33:44 +0000
commitb9527e984092e838790b543b014c0c2720ea4f11 (patch)
tree60a6063280d446701e1b93e1149eaeb9ce13a128 /src/backend/commands
parentf84308f1958313f6cd1644d74b6a8ff49a871f8d (diff)
downloadpostgresql-b9527e984092e838790b543b014c0c2720ea4f11.tar.gz
postgresql-b9527e984092e838790b543b014c0c2720ea4f11.zip
First phase of plan-invalidation project: create a plan cache management
module and teach PREPARE and protocol-level prepared statements to use it. In service of this, rearrange utility-statement processing so that parse analysis does not assume table schemas can't change before execution for utility statements (necessary because we don't attempt to re-acquire locks for utility statements when reusing a stored plan). This requires some refactoring of the ProcessUtility API, but it ends up cleaner anyway, for instance we can get rid of the QueryContext global. Still to do: fix up SPI and related code to use the plan cache; I'm tempted to try to make SQL functions use it too. Also, there are at least some aspects of system state that we want to ensure remain the same during a replan as in the original processing; search_path certainly ought to behave that way for instance, and perhaps there are others.
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/cluster.c6
-rw-r--r--src/backend/commands/copy.c43
-rw-r--r--src/backend/commands/dbcommands.c7
-rw-r--r--src/backend/commands/explain.c184
-rw-r--r--src/backend/commands/indexcmds.c24
-rw-r--r--src/backend/commands/portalcmds.c68
-rw-r--r--src/backend/commands/prepare.c551
-rw-r--r--src/backend/commands/schemacmds.c13
-rw-r--r--src/backend/commands/tablecmds.c10
-rw-r--r--src/backend/commands/tablespace.c10
-rw-r--r--src/backend/commands/vacuum.c13
-rw-r--r--src/backend/commands/view.c147
12 files changed, 570 insertions, 506 deletions
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 2a16b12be1d..aa911369409 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.156 2007/02/01 19:10:25 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.157 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -82,7 +82,7 @@ static List *get_tables_to_cluster(MemoryContext cluster_context);
*---------------------------------------------------------------------------
*/
void
-cluster(ClusterStmt *stmt)
+cluster(ClusterStmt *stmt, bool isTopLevel)
{
if (stmt->relation != NULL)
{
@@ -173,7 +173,7 @@ cluster(ClusterStmt *stmt)
* We cannot run this form of CLUSTER inside a user transaction block;
* we'd be holding locks way too long.
*/
- PreventTransactionChain((void *) stmt, "CLUSTER");
+ PreventTransactionChain(isTopLevel, "CLUSTER");
/*
* Create special memory context for cross-transaction storage.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 17f0135981e..a2e1939ea25 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.277 2007/03/03 19:32:54 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.278 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -713,7 +713,7 @@ CopyLoadRawBuf(CopyState cstate)
* the table.
*/
uint64
-DoCopy(const CopyStmt *stmt)
+DoCopy(const CopyStmt *stmt, const char *queryString)
{
CopyState cstate;
bool is_from = stmt->is_from;
@@ -982,13 +982,11 @@ DoCopy(const CopyStmt *stmt)
}
else
{
- Query *query = stmt->query;
List *rewritten;
+ Query *query;
PlannedStmt *plan;
DestReceiver *dest;
- Assert(query);
- Assert(query->commandType == CMD_SELECT);
Assert(!is_from);
cstate->rel = NULL;
@@ -998,33 +996,18 @@ DoCopy(const CopyStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT) WITH OIDS is not supported")));
- /* Query mustn't use INTO, either */
- if (query->into)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("COPY (SELECT INTO) is not supported")));
-
/*
- * The query has already been through parse analysis, but not
- * rewriting or planning. Do that now.
+ * Run parse analysis and rewrite. Note this also acquires sufficient
+ * locks on the source table(s).
*
- * Because the planner is not cool about not scribbling on its input,
- * we make a preliminary copy of the source querytree. This prevents
+ * Because the parser and planner tend to scribble on their input, we
+ * make a preliminary copy of the source querytree. This prevents
* problems in the case that the COPY is in a portal or plpgsql
* function and is executed repeatedly. (See also the same hack in
- * EXPLAIN, DECLARE CURSOR and PREPARE.) XXX the planner really
- * shouldn't modify its input ... FIXME someday.
+ * DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
- query = copyObject(query);
-
- /*
- * Must acquire locks in case we didn't come fresh from the parser.
- * XXX this also scribbles on query, another reason for copyObject
- */
- AcquireRewriteLocks(query);
-
- /* Rewrite through rule system */
- rewritten = QueryRewrite(query);
+ rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
+ queryString, NULL, 0);
/* We don't expect more or less than one result query */
if (list_length(rewritten) != 1)
@@ -1033,6 +1016,12 @@ DoCopy(const CopyStmt *stmt)
query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
+ /* Query mustn't use INTO, either */
+ if (query->into)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("COPY (SELECT INTO) is not supported")));
+
/* plan the query */
plan = planner(query, false, 0, NULL);
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 63e15e505b7..de4b239893a 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.192 2007/02/09 16:12:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.193 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -97,9 +97,6 @@ createdb(const CreatedbStmt *stmt)
int encoding = -1;
int dbconnlimit = -1;
- /* don't call this in a transaction block */
- PreventTransactionChain((void *) stmt, "CREATE DATABASE");
-
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
@@ -545,8 +542,6 @@ dropdb(const char *dbname, bool missing_ok)
Relation pgdbrel;
HeapTuple tup;
- PreventTransactionChain((void *) dbname, "DROP DATABASE");
-
AssertArg(dbname);
if (strcmp(dbname, get_database_name(MyDatabaseId)) == 0)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 835b1ef6ade..1b2cfccce9f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.159 2007/02/23 21:59:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.160 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -26,6 +26,7 @@
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
+#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
@@ -41,8 +42,9 @@ typedef struct ExplainState
List *rtable; /* range table */
} ExplainState;
-static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
- ParamListInfo params, TupOutputState *tstate);
+static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
+ ExplainStmt *stmt, const char *queryString,
+ ParamListInfo params, TupOutputState *tstate);
static double elapsed_time(instr_time *starttime);
static void explain_outNode(StringInfo str,
Plan *plan, PlanState *planstate,
@@ -62,62 +64,49 @@ static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
* execute an EXPLAIN command
*/
void
-ExplainQuery(ExplainStmt *stmt, ParamListInfo params, DestReceiver *dest)
+ExplainQuery(ExplainStmt *stmt, const char *queryString,
+ ParamListInfo params, DestReceiver *dest)
{
- Query *query = stmt->query;
+ Oid *param_types;
+ int num_params;
TupOutputState *tstate;
List *rewritten;
ListCell *l;
+ /* Convert parameter type data to the form parser wants */
+ getParamListTypes(params, &param_types, &num_params);
+
/*
- * Because the planner is not cool about not scribbling on its input, we
+ * Run parse analysis and rewrite. Note this also acquires sufficient
+ * locks on the source table(s).
+ *
+ * Because the parser and planner tend to scribble on their input, we
* make a preliminary copy of the source querytree. This prevents
* problems in the case that the EXPLAIN is in a portal or plpgsql
* function and is executed repeatedly. (See also the same hack in
- * DECLARE CURSOR and PREPARE.) XXX the planner really shouldn't modify
- * its input ... FIXME someday.
+ * DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
- query = copyObject(query);
+ rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
+ queryString, param_types, num_params);
/* prepare for projection of tuples */
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
- if (query->commandType == CMD_UTILITY)
+ if (rewritten == NIL)
{
- /* Rewriter will not cope with utility statements */
- if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
- ExplainOneQuery(query, stmt, params, tstate);
- else if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
- ExplainExecuteQuery(stmt, params, tstate);
- else
- do_text_output_oneline(tstate, "Utility statements have no plan structure");
+ /* In the case of an INSTEAD NOTHING, tell at least that */
+ do_text_output_oneline(tstate, "Query rewrites to nothing");
}
else
{
- /*
- * Must acquire locks in case we didn't come fresh from the parser.
- * XXX this also scribbles on query, another reason for copyObject
- */
- AcquireRewriteLocks(query);
-
- /* Rewrite through rule system */
- rewritten = QueryRewrite(query);
-
- if (rewritten == NIL)
- {
- /* In the case of an INSTEAD NOTHING, tell at least that */
- do_text_output_oneline(tstate, "Query rewrites to nothing");
- }
- else
+ /* Explain every plan */
+ foreach(l, rewritten)
{
- /* Explain every plan */
- foreach(l, rewritten)
- {
- ExplainOneQuery(lfirst(l), stmt, params, tstate);
- /* put a blank line between plans */
- if (lnext(l) != NULL)
- do_text_output_oneline(tstate, "");
- }
+ ExplainOneQuery((Query *) lfirst(l), false, 0,
+ stmt, queryString, params, tstate);
+ /* put a blank line between plans */
+ if (lnext(l) != NULL)
+ do_text_output_oneline(tstate, "");
}
}
@@ -142,51 +131,22 @@ ExplainResultDesc(ExplainStmt *stmt)
/*
* ExplainOneQuery -
- * print out the execution plan for one query
+ * print out the execution plan for one Query
*/
static void
-ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
- TupOutputState *tstate)
+ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
+ ExplainStmt *stmt, const char *queryString,
+ ParamListInfo params, TupOutputState *tstate)
{
PlannedStmt *plan;
QueryDesc *queryDesc;
- bool isCursor = false;
- int cursorOptions = 0;
/* planner will not cope with utility statements */
if (query->commandType == CMD_UTILITY)
{
- if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
- {
- DeclareCursorStmt *dcstmt;
- List *rewritten;
-
- dcstmt = (DeclareCursorStmt *) query->utilityStmt;
- query = (Query *) dcstmt->query;
- isCursor = true;
- cursorOptions = dcstmt->options;
- /* Still need to rewrite cursor command */
- Assert(query->commandType == CMD_SELECT);
- /* get locks (we assume ExplainQuery already copied tree) */
- AcquireRewriteLocks(query);
- rewritten = QueryRewrite(query);
- if (list_length(rewritten) != 1)
- elog(ERROR, "unexpected rewrite result");
- query = (Query *) linitial(rewritten);
- Assert(query->commandType == CMD_SELECT);
- /* do not actually execute the underlying query! */
- stmt->analyze = false;
- }
- else if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
- {
- do_text_output_oneline(tstate, "NOTIFY");
- return;
- }
- else
- {
- do_text_output_oneline(tstate, "UTILITY");
- return;
- }
+ ExplainOneUtility(query->utilityStmt, stmt,
+ queryString, params, tstate);
+ return;
}
/* plan the query */
@@ -211,6 +171,78 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
}
/*
+ * ExplainOneUtility -
+ * print out the execution plan for one utility statement
+ * (In general, utility statements don't have plans, but there are some
+ * we treat as special cases)
+ *
+ * This is exported because it's called back from prepare.c in the
+ * EXPLAIN EXECUTE case
+ */
+void
+ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
+ const char *queryString, ParamListInfo params,
+ TupOutputState *tstate)
+{
+ if (utilityStmt == NULL)
+ return;
+
+ if (IsA(utilityStmt, DeclareCursorStmt))
+ {
+ DeclareCursorStmt *dcstmt = (DeclareCursorStmt *) utilityStmt;
+ Oid *param_types;
+ int num_params;
+ Query *query;
+ List *rewritten;
+ ExplainStmt newstmt;
+
+ /* Convert parameter type data to the form parser wants */
+ getParamListTypes(params, &param_types, &num_params);
+
+ /*
+ * Run parse analysis and rewrite. Note this also acquires sufficient
+ * locks on the source table(s).
+ *
+ * Because the parser and planner tend to scribble on their input, we
+ * make a preliminary copy of the source querytree. This prevents
+ * problems in the case that the DECLARE CURSOR is in a portal or
+ * plpgsql function and is executed repeatedly. (See also the same
+ * hack in COPY and PREPARE.) XXX FIXME someday.
+ */
+ rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query),
+ queryString,
+ param_types, num_params);
+
+ /* We don't expect more or less than one result query */
+ if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
+ elog(ERROR, "unexpected rewrite result");
+ query = (Query *) linitial(rewritten);
+ if (query->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected rewrite result");
+
+ /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
+ if (query->into)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("DECLARE CURSOR cannot specify INTO")));
+
+ /* do not actually execute the underlying query! */
+ memcpy(&newstmt, stmt, sizeof(ExplainStmt));
+ newstmt.analyze = false;
+ ExplainOneQuery(query, true, dcstmt->options, &newstmt,
+ queryString, params, tstate);
+ }
+ else if (IsA(utilityStmt, ExecuteStmt))
+ ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
+ queryString, params, tstate);
+ else if (IsA(utilityStmt, NotifyStmt))
+ do_text_output_oneline(tstate, "NOTIFY");
+ else
+ do_text_output_oneline(tstate,
+ "Utility statements have no plan structure");
+}
+
+/*
* ExplainOnePlan -
* given a planned query, execute it if needed, and then print
* EXPLAIN output
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8c5fdbb6c94..ba185431bec 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.156 2007/03/06 02:06:12 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.157 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -77,7 +77,6 @@ static bool relationHasPrimaryKey(Relation rel);
* 'attributeList': a list of IndexElem specifying columns and expressions
* to index on.
* 'predicate': the partial-index condition, or NULL if none.
- * 'rangetable': needed to interpret the predicate.
* 'options': reloptions from WITH (in list-of-DefElem form).
* 'unique': make the index enforce uniqueness.
* 'primary': mark the index as a primary key in the catalogs.
@@ -99,7 +98,6 @@ DefineIndex(RangeVar *heapRelation,
char *tableSpaceName,
List *attributeList,
Expr *predicate,
- List *rangetable,
List *options,
bool unique,
bool primary,
@@ -301,18 +299,6 @@ DefineIndex(RangeVar *heapRelation,
ReleaseSysCache(tuple);
/*
- * If a range table was created then check that only the base rel is
- * mentioned.
- */
- if (rangetable != NIL)
- {
- if (list_length(rangetable) != 1 || getrelid(1, rangetable) != relationId)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
- errmsg("index expressions and predicates can refer only to the table being indexed")));
- }
-
- /*
* Validate predicate, if given
*/
if (predicate)
@@ -1218,6 +1204,7 @@ ReindexTable(RangeVar *relation)
*
* To reduce the probability of deadlocks, each table is reindexed in a
* separate transaction, so we can release the lock on it right away.
+ * That means this must not be called within a user transaction block!
*/
void
ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
@@ -1242,13 +1229,6 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
databaseName);
/*
- * We cannot run inside a user transaction block; if we were inside a
- * transaction, then our commit- and start-transaction-command calls would
- * not have the intended effect!
- */
- PreventTransactionChain((void *) databaseName, "REINDEX DATABASE");
-
- /*
* Create a memory context that will survive forced transaction commits we
* do below. Since it is a child of PortalContext, it will go away
* eventually even if we suffer an error; there's no need for special
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 0219650c069..98b200d2cff 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.61 2007/02/20 17:32:14 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.62 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -38,8 +38,11 @@
* Execute SQL DECLARE CURSOR command.
*/
void
-PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
+PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
+ const char *queryString, bool isTopLevel)
{
+ Oid *param_types;
+ int num_params;
List *rewritten;
Query *query;
PlannedStmt *plan;
@@ -61,40 +64,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
* user-visible effect).
*/
if (!(stmt->options & CURSOR_OPT_HOLD))
- RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
+ RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
/*
- * 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 DECLARE CURSOR is in a portal and is
- * executed repeatedly. XXX the planner really shouldn't modify its input
- * ... FIXME someday.
+ * Don't allow both SCROLL and NO SCROLL to be specified
*/
- query = copyObject(stmt->query);
+ if ((stmt->options & CURSOR_OPT_SCROLL) &&
+ (stmt->options & CURSOR_OPT_NO_SCROLL))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cannot specify both SCROLL and NO SCROLL")));
+
+ /* Convert parameter type data to the form parser wants */
+ getParamListTypes(params, &param_types, &num_params);
/*
- * The query has been through parse analysis, but not rewriting or
- * planning as yet. Note that the grammar ensured we have a SELECT query,
- * so we are not expecting rule rewriting to do anything strange.
+ * Run parse analysis and rewrite. Note this also acquires sufficient
+ * locks on the source table(s).
+ *
+ * Because the parser and planner tend to scribble on their input, we
+ * make a preliminary copy of the source querytree. This prevents
+ * problems in the case that the DECLARE CURSOR is in a portal or plpgsql
+ * function and is executed repeatedly. (See also the same hack in
+ * COPY and PREPARE.) XXX FIXME someday.
*/
- AcquireRewriteLocks(query);
- rewritten = QueryRewrite(query);
+ rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
+ queryString, param_types, num_params);
+
+ /* We don't expect more or less than one result query */
if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten);
if (query->commandType != CMD_SELECT)
elog(ERROR, "unexpected rewrite result");
+ /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (query->into)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR cannot specify INTO")));
+
if (query->rowMarks != NIL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
errdetail("Cursors must be READ ONLY.")));
+ /* plan the query */
plan = planner(query, true, stmt->options, params);
/*
@@ -106,23 +122,22 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
plan = copyObject(plan);
- /*
- * XXX: debug_query_string is wrong here: the user might have submitted
- * multiple semicolon delimited queries.
- */
PortalDefineQuery(portal,
NULL,
- debug_query_string ? pstrdup(debug_query_string) : NULL,
+ queryString,
"SELECT", /* cursor's query is always a SELECT */
list_make1(plan),
- PortalGetHeapMemory(portal));
+ NULL);
- /*
+ /*----------
* Also copy the outer portal's parameter list into the inner portal's
* memory context. We want to pass down the parameter values in case we
- * had a command like DECLARE c CURSOR FOR SELECT ... WHERE foo = $1 This
- * will have been parsed using the outer parameter set and the parameter
- * value needs to be preserved for use when the cursor is executed.
+ * had a command like
+ * DECLARE c CURSOR FOR SELECT ... WHERE foo = $1
+ * This will have been parsed using the outer parameter set and the
+ * parameter value needs to be preserved for use when the cursor is
+ * executed.
+ *----------
*/
params = copyParamList(params);
@@ -314,7 +329,6 @@ PersistHoldablePortal(Portal portal)
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
- MemoryContext saveQueryContext;
MemoryContext oldcxt;
/*
@@ -356,14 +370,12 @@ PersistHoldablePortal(Portal portal)
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
- saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = queryDesc->snapshot;
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
- QueryContext = portal->queryContext;
MemoryContextSwitchTo(PortalContext);
@@ -434,7 +446,6 @@ PersistHoldablePortal(Portal portal)
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
PG_RE_THROW();
}
@@ -449,7 +460,6 @@ PersistHoldablePortal(Portal portal)
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
/*
* We can now release any subsidiary memory of the portal's heap context;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index 8a5382c7378..2c284cb9be0 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -10,7 +10,7 @@
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.69 2007/02/20 17:32:14 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.70 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -22,6 +22,10 @@
#include "commands/explain.h"
#include "commands/prepare.h"
#include "funcapi.h"
+#include "parser/analyze.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_expr.h"
+#include "parser/parse_type.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/pquery.h"
#include "tcop/tcopprot.h"
@@ -39,20 +43,24 @@
static HTAB *prepared_queries = NULL;
static void InitQueryHashTable(void);
-static ParamListInfo EvaluateParams(EState *estate,
- List *params, List *argtypes);
-static Datum build_regtype_array(List *oid_list);
+static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
+ const char *queryString, EState *estate);
+static Datum build_regtype_array(Oid *param_types, int num_params);
/*
* Implements the 'PREPARE' utility statement.
*/
void
-PrepareQuery(PrepareStmt *stmt)
+PrepareQuery(PrepareStmt *stmt, const char *queryString)
{
- const char *commandTag;
+ Oid *argtypes = NULL;
+ int nargs;
+ List *queries;
Query *query;
+ const char *commandTag;
List *query_list,
*plan_list;
+ int i;
/*
* Disallow empty-string statement name (conflicts with protocol-level
@@ -63,7 +71,70 @@ PrepareQuery(PrepareStmt *stmt)
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
errmsg("invalid statement name: must not be empty")));
- switch (stmt->query->commandType)
+ /* Transform list of TypeNames to array of type OIDs */
+ nargs = list_length(stmt->argtypes);
+
+ if (nargs)
+ {
+ ParseState *pstate;
+ ListCell *l;
+
+ /*
+ * typenameTypeId wants a ParseState to carry the source query string.
+ * Is it worth refactoring its API to avoid this?
+ */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ argtypes = (Oid *) palloc(nargs * sizeof(Oid));
+ i = 0;
+
+ foreach(l, stmt->argtypes)
+ {
+ TypeName *tn = lfirst(l);
+ Oid toid = typenameTypeId(pstate, tn);
+
+ argtypes[i++] = toid;
+ }
+ }
+
+ /*
+ * Analyze the statement using these parameter types (any parameters
+ * passed in from above us will not be visible to it), allowing
+ * information about unknown parameters to be deduced from context.
+ *
+ * Because parse analysis scribbles on the raw querytree, we must make
+ * a copy to ensure we have a pristine raw tree to cache. FIXME someday.
+ */
+ queries = parse_analyze_varparams((Node *) copyObject(stmt->query),
+ queryString,
+ &argtypes, &nargs);
+
+ /*
+ * Check that all parameter types were determined.
+ */
+ for (i = 0; i < nargs; i++)
+ {
+ Oid argtype = argtypes[i];
+
+ if (argtype == InvalidOid || argtype == UNKNOWNOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INDETERMINATE_DATATYPE),
+ errmsg("could not determine data type of parameter $%d",
+ i + 1)));
+ }
+
+ /*
+ * Shouldn't get any extra statements, since grammar only allows
+ * OptimizableStmt
+ */
+ if (list_length(queries) != 1)
+ elog(ERROR, "unexpected extra stuff in prepared statement");
+
+ query = (Query *) linitial(queries);
+ Assert(IsA(query, Query));
+
+ switch (query->commandType)
{
case CMD_SELECT:
commandTag = "SELECT";
@@ -85,38 +156,22 @@ PrepareQuery(PrepareStmt *stmt)
break;
}
- /*
- * Parse analysis is already done, but we must still rewrite and plan the
- * query.
- */
-
- /*
- * Because the planner is not cool about not scribbling on its input, we
- * make a preliminary copy of the source querytree. This prevents
- * problems in the case that the PREPARE is in a portal or plpgsql
- * function and is executed repeatedly. (See also the same hack in
- * DECLARE CURSOR and EXPLAIN.) XXX the planner really shouldn't modify
- * its input ... FIXME someday.
- */
- query = copyObject(stmt->query);
-
/* Rewrite the query. The result could be 0, 1, or many queries. */
- AcquireRewriteLocks(query);
query_list = QueryRewrite(query);
/* Generate plans for queries. Snapshot is already set. */
plan_list = pg_plan_queries(query_list, NULL, false);
/*
- * Save the results. We don't have the query string for this PREPARE, but
- * we do have the string we got from the client, so use that.
+ * Save the results.
*/
StorePreparedStatement(stmt->name,
- debug_query_string,
+ stmt->query,
+ queryString,
commandTag,
+ argtypes,
+ nargs,
plan_list,
- stmt->argtype_oids,
- true,
true);
}
@@ -124,13 +179,13 @@ PrepareQuery(PrepareStmt *stmt)
* Implements the 'EXECUTE' utility statement.
*/
void
-ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
+ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
+ ParamListInfo params,
DestReceiver *dest, char *completionTag)
{
PreparedStatement *entry;
- char *query_string;
+ CachedPlan *cplan;
List *plan_list;
- MemoryContext qcontext;
ParamListInfo paramLI = NULL;
EState *estate = NULL;
Portal portal;
@@ -138,20 +193,15 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
/* Look it up in the hash table */
entry = FetchPreparedStatement(stmt->name, true);
- /*
- * Punt if not fully planned. (Currently, that only happens for the
- * protocol-level unnamed statement, which can't be accessed from SQL;
- * so there's no point in doing more than a quick check here.)
- */
- if (!entry->fully_planned)
+ /* Shouldn't have a non-fully-planned plancache entry */
+ if (!entry->plansource->fully_planned)
elog(ERROR, "EXECUTE does not support unplanned prepared statements");
-
- query_string = entry->query_string;
- plan_list = entry->stmt_list;
- qcontext = entry->context;
+ /* 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
@@ -159,7 +209,8 @@ 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 */
@@ -168,22 +219,23 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
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;
PlannedStmt *pstmt;
- qcontext = PortalGetHeapMemory(portal);
- oldContext = MemoryContextSwitchTo(qcontext);
+ /* Replan if needed, and increment plan refcount transiently */
+ cplan = RevalidateCachedPlan(entry->plansource, true);
- if (query_string)
- query_string = pstrdup(query_string);
- plan_list = copyObject(plan_list);
+ /* 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,
@@ -198,21 +250,32 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
pstmt->into = copyObject(stmt->into);
MemoryContextSwitchTo(oldContext);
+
+ /* We no longer need the cached plan refcount ... */
+ ReleaseCachedPlan(cplan, true);
+ /* ... and we don't want the portal to depend on it, either */
+ cplan = NULL;
+ }
+ else
+ {
+ /* Replan if needed, and increment plan refcount for portal */
+ cplan = RevalidateCachedPlan(entry->plansource, false);
+ plan_list = cplan->stmt_list;
}
PortalDefineQuery(portal,
NULL,
- query_string,
- entry->commandTag,
+ 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);
@@ -223,42 +286,106 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
}
/*
- * Evaluates a list of parameters, using the given executor state. It
- * requires a list of the parameter expressions themselves, and a list of
- * their types. It returns a filled-in ParamListInfo -- this can later
- * be passed to CreateQueryDesc(), which allows the executor to make use
- * of the parameters during query execution.
+ * EvaluateParams: evaluate a list of parameters.
+ *
+ * pstmt: statement we are getting parameters for.
+ * params: list of given parameter expressions (raw parser output!)
+ * queryString: source text for error messages.
+ * estate: executor state to use.
+ *
+ * Returns a filled-in ParamListInfo -- this can later be passed to
+ * CreateQueryDesc(), which allows the executor to make use of the parameters
+ * during query execution.
*/
static ParamListInfo
-EvaluateParams(EState *estate, List *params, List *argtypes)
+EvaluateParams(PreparedStatement *pstmt, List *params,
+ const char *queryString, EState *estate)
{
- int nargs = list_length(argtypes);
+ Oid *param_types = pstmt->plansource->param_types;
+ int num_params = pstmt->plansource->num_params;
+ int nparams = list_length(params);
+ ParseState *pstate;
ParamListInfo paramLI;
List *exprstates;
- ListCell *le,
- *la;
- int i = 0;
-
- /* Parser should have caught this error, but check for safety */
- if (list_length(params) != nargs)
- elog(ERROR, "wrong number of arguments");
+ ListCell *l;
+ int i;
- if (nargs == 0)
+ if (nparams != num_params)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("wrong number of parameters for prepared statement \"%s\"",
+ pstmt->stmt_name),
+ errdetail("Expected %d parameters but got %d.",
+ num_params, nparams)));
+
+ /* Quick exit if no parameters */
+ if (num_params == 0)
return NULL;
+ /*
+ * We have to run parse analysis for the expressions. Since the
+ * parser is not cool about scribbling on its input, copy first.
+ */
+ params = (List *) copyObject(params);
+
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ i = 0;
+ foreach(l, params)
+ {
+ Node *expr = lfirst(l);
+ Oid expected_type_id = param_types[i];
+ Oid given_type_id;
+
+ expr = transformExpr(pstate, expr);
+
+ /* Cannot contain subselects or aggregates */
+ if (pstate->p_hasSubLinks)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot use subquery in EXECUTE parameter")));
+ if (pstate->p_hasAggs)
+ ereport(ERROR,
+ (errcode(ERRCODE_GROUPING_ERROR),
+ errmsg("cannot use aggregate function in EXECUTE parameter")));
+
+ given_type_id = exprType(expr);
+
+ expr = coerce_to_target_type(pstate, expr, given_type_id,
+ expected_type_id, -1,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST);
+
+ if (expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("parameter $%d of type %s cannot be coerced to the expected type %s",
+ i + 1,
+ format_type_be(given_type_id),
+ format_type_be(expected_type_id)),
+ errhint("You will need to rewrite or cast the expression.")));
+
+ lfirst(l) = expr;
+ i++;
+ }
+
+ /* Prepare the expressions for execution */
exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
/* sizeof(ParamListInfoData) includes the first array element */
- paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
- (nargs - 1) *sizeof(ParamExternData));
- paramLI->numParams = nargs;
+ paramLI = (ParamListInfo)
+ palloc(sizeof(ParamListInfoData) +
+ (num_params - 1) *sizeof(ParamExternData));
+ paramLI->numParams = num_params;
- forboth(le, exprstates, la, argtypes)
+ i = 0;
+ foreach(l, exprstates)
{
- ExprState *n = lfirst(le);
+ ExprState *n = lfirst(l);
ParamExternData *prm = &paramLI->params[i];
- prm->ptype = lfirst_oid(la);
+ prm->ptype = param_types[i];
prm->pflags = 0;
prm->value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate),
@@ -293,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
@@ -302,17 +430,16 @@ InitQueryHashTable(void)
*/
void
StorePreparedStatement(const char *stmt_name,
+ Node *raw_parse_tree,
const char *query_string,
const char *commandTag,
+ Oid *param_types,
+ int num_params,
List *stmt_list,
- List *argtype_list,
- bool fully_planned,
bool from_sql)
{
PreparedStatement *entry;
- MemoryContext oldcxt,
- entrycxt;
- char *qstring;
+ CachedPlanSource *plansource;
bool found;
/* Initialize the hash table, if necessary */
@@ -328,24 +455,15 @@ 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;
- stmt_list = (List *) copyObject(stmt_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,
@@ -358,22 +476,18 @@ 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->stmt_list = stmt_list;
- entry->argtype_list = argtype_list;
- entry->fully_planned = fully_planned;
+ /* Fill in the hash table entry */
+ entry->plansource = plansource;
entry->from_sql = from_sql;
- entry->context = entrycxt;
entry->prepare_time = GetCurrentStatementStartTimestamp();
-
- MemoryContextSwitchTo(oldcxt);
}
/*
* 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)
@@ -402,20 +516,6 @@ FetchPreparedStatement(const char *stmt_name, bool throwError)
}
/*
- * 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.
*
@@ -424,85 +524,15 @@ FetchPreparedStatementParams(const char *stmt_name)
TupleDesc
FetchPreparedStatementResultDesc(PreparedStatement *stmt)
{
- Node *node;
- Query *query;
- PlannedStmt *pstmt;
-
- switch (ChoosePortalStrategy(stmt->stmt_list))
- {
- case PORTAL_ONE_SELECT:
- node = (Node *) linitial(stmt->stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- return ExecCleanTypeFromTL(query->targetList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
-
- case PORTAL_ONE_RETURNING:
- node = PortalListGetPrimaryStmt(stmt->stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->returningList);
- return ExecCleanTypeFromTL(query->returningList, false);
- }
- if (IsA(node, PlannedStmt))
- {
- pstmt = (PlannedStmt *) node;
- Assert(pstmt->returningLists);
- return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false);
- }
- /* other cases shouldn't happen, but return NULL */
- break;
-
- case PORTAL_UTIL_SELECT:
- node = (Node *) linitial(stmt->stmt_list);
- if (IsA(node, Query))
- {
- query = (Query *) node;
- Assert(query->utilityStmt);
- return UtilityTupleDescriptor(query->utilityStmt);
- }
- /* else it's a bare utility statement */
- return UtilityTupleDescriptor(node);
-
- case PORTAL_MULTI_QUERY:
- /* will not return tuples */
- break;
- }
- return NULL;
-}
-
-/*
- * Given a prepared statement, determine whether it will return tuples.
- *
- * Note: this is used rather than just testing the result of
- * FetchPreparedStatementResultDesc() because that routine can fail if
- * invoked in an aborted transaction. This one is safe to use in any
- * context. Be sure to keep the two routines in sync!
- */
-bool
-PreparedStatementReturnsTuples(PreparedStatement *stmt)
-{
- switch (ChoosePortalStrategy(stmt->stmt_list))
- {
- case PORTAL_ONE_SELECT:
- case PORTAL_ONE_RETURNING:
- case PORTAL_UTIL_SELECT:
- return true;
-
- case PORTAL_MULTI_QUERY:
- /* will not return tuples */
- break;
- }
- return false;
+ /*
+ * Since we don't allow prepared statements' result tupdescs to change,
+ * there's no need for a revalidate call here.
+ */
+ Assert(stmt->plansource->fixed_result);
+ if (stmt->plansource->resultDesc)
+ return CreateTupleDescCopy(stmt->plansource->resultDesc);
+ else
+ return NULL;
}
/*
@@ -510,16 +540,32 @@ PreparedStatementReturnsTuples(PreparedStatement *stmt)
* targetlist. Returns NIL if the statement doesn't have a determinable
* targetlist.
*
- * Note: do not modify the result.
+ * 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)
{
- /* no point in looking if it doesn't return tuples */
- if (ChoosePortalStrategy(stmt->stmt_list) == PORTAL_MULTI_QUERY)
+ List *tlist;
+ CachedPlan *cplan;
+
+ /* No point in looking if it doesn't return tuples */
+ if (stmt->plansource->resultDesc == NULL)
return NIL;
- /* get the primary statement and find out what it returns */
- return FetchStatementTargetList(PortalListGetPrimaryStmt(stmt->stmt_list));
+
+ /* 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;
}
/*
@@ -547,12 +593,8 @@ 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);
-
- /* Flush the context holding the subsidiary data */
- MemoryContextDelete(entry->context);
+ /* Release the plancache entry */
+ DropCachedPlan(entry->plansource);
/* Now we can remove the hash table entry */
hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
@@ -563,34 +605,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;
+ 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);
- /*
- * Punt if not fully planned. (Currently, that only happens for the
- * protocol-level unnamed statement, which can't be accessed from SQL;
- * so there's no point in doing more than a quick check here.)
- */
- if (!entry->fully_planned)
+ /* 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");
+
+ /* Replan if needed, and acquire a transient refcount */
+ cplan = RevalidateCachedPlan(entry->plansource, true);
- plan_list = entry->stmt_list;
+ 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
@@ -598,8 +640,8 @@ 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 */
@@ -610,14 +652,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
is_last_query = (lnext(p) == NULL);
- if (!IsA(pstmt, PlannedStmt))
- {
- if (IsA(pstmt, NotifyStmt))
- do_text_output_oneline(tstate, "NOTIFY");
- else
- do_text_output_oneline(tstate, "UTILITY");
- }
- else
+ if (IsA(pstmt, PlannedStmt))
{
QueryDesc *qdesc;
@@ -651,6 +686,11 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
ExplainOnePlan(qdesc, stmt, tstate);
}
+ else
+ {
+ ExplainOneUtility((Node *) pstmt, stmt, queryString,
+ params, tstate);
+ }
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
@@ -661,6 +701,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
if (estate)
FreeExecutorState(estate);
+
+ ReleaseCachedPlan(cplan, true);
}
/*
@@ -739,14 +781,15 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
values[0] = DirectFunctionCall1(textin,
CStringGetDatum(prep_stmt->stmt_name));
- if (prep_stmt->query_string == NULL)
+ if (prep_stmt->plansource->query_string == NULL)
nulls[1] = true;
else
values[1] = DirectFunctionCall1(textin,
- CStringGetDatum(prep_stmt->query_string));
+ CStringGetDatum(prep_stmt->plansource->query_string));
values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
- values[3] = build_regtype_array(prep_stmt->argtype_list);
+ values[3] = build_regtype_array(prep_stmt->plansource->param_types,
+ prep_stmt->plansource->num_params);
values[4] = BoolGetDatum(prep_stmt->from_sql);
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
@@ -758,29 +801,23 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
}
/*
- * This utility function takes a List of Oids, and returns a Datum
- * pointing to a one-dimensional Postgres array of regtypes. The empty
- * list is returned as a zero-element array, not NULL.
+ * This utility function takes a C array of Oids, and returns a Datum
+ * pointing to a one-dimensional Postgres array of regtypes. An empty
+ * array is returned as a zero-element array, not NULL.
*/
static Datum
-build_regtype_array(List *oid_list)
+build_regtype_array(Oid *param_types, int num_params)
{
- ListCell *lc;
- int len;
- int i;
Datum *tmp_ary;
ArrayType *result;
+ int i;
- len = list_length(oid_list);
- tmp_ary = (Datum *) palloc(len * sizeof(Datum));
+ tmp_ary = (Datum *) palloc(num_params * sizeof(Datum));
- i = 0;
- foreach(lc, oid_list)
- {
- tmp_ary[i++] = ObjectIdGetDatum(lfirst_oid(lc));
- }
+ for (i = 0; i < num_params; i++)
+ tmp_ary[i] = ObjectIdGetDatum(param_types[i]);
/* XXX: this hardcodes assumptions about the regtype type */
- result = construct_array(tmp_ary, len, REGTYPEOID, 4, true, 'i');
+ result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i');
return PointerGetDatum(result);
}
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 09c2ca9f633..0912b8a62cc 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.43 2007/02/01 19:10:26 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.44 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -38,7 +38,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
* CREATE SCHEMA
*/
void
-CreateSchemaCommand(CreateSchemaStmt *stmt)
+CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
{
const char *schemaName = stmt->schemaname;
const char *authId = stmt->authid;
@@ -122,7 +122,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt)
List *querytree_list;
ListCell *querytree_item;
- querytree_list = parse_analyze(parsetree, NULL, NULL, 0);
+ querytree_list = parse_analyze(parsetree, queryString, NULL, 0);
foreach(querytree_item, querytree_list)
{
@@ -131,7 +131,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt)
/* schemas should contain only utility stmts */
Assert(querytree->commandType == CMD_UTILITY);
/* do this step */
- ProcessUtility(querytree->utilityStmt, NULL, None_Receiver, NULL);
+ ProcessUtility(querytree->utilityStmt,
+ queryString,
+ NULL,
+ false, /* not top level */
+ None_Receiver,
+ NULL);
/* make sure later steps can see the object created here */
CommandCounterIncrement();
}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index ebc974f8731..ddc62086f51 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.216 2007/03/06 02:06:13 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.217 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -3696,6 +3696,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
+ /*
+ * Run parse analysis. We don't have convenient access to the query text
+ * here, but it's probably not worth worrying about.
+ */
+ stmt = analyzeIndexStmt(stmt, NULL);
+
+ /* ... and do it */
DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
@@ -3703,7 +3710,6 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
stmt->tableSpace,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
- stmt->rangetable,
stmt->options,
stmt->unique,
stmt->primary,
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index aa2b33c9326..8e3bfbda863 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -37,7 +37,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.43 2007/03/06 02:06:13 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.44 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -198,11 +198,6 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
char *linkloc;
Oid ownerId;
- /* validate */
-
- /* don't call this in a transaction block */
- PreventTransactionChain((void *) stmt, "CREATE TABLESPACE");
-
/* Must be super user */
if (!superuser())
ereport(ERROR,
@@ -385,9 +380,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
ScanKeyData entry[1];
Oid tablespaceoid;
- /* don't call this in a transaction block */
- PreventTransactionChain((void *) stmt, "DROP TABLESPACE");
-
/*
* Find the target tuple
*/
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d13090ebf72..54864fbec90 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.347 2007/03/08 17:03:31 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.348 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -257,13 +257,14 @@ static Size PageGetFreeSpaceWithFillFactor(Relation relation, Page page);
* relation OIDs to be processed, and vacstmt->relation is ignored.
* (The non-NIL case is currently only used by autovacuum.)
*
+ * isTopLevel should be passed down from ProcessUtility.
+ *
* It is the caller's responsibility that both vacstmt and relids
* (if given) be allocated in a memory context that won't disappear
- * at transaction commit. In fact this context must be QueryContext
- * to avoid complaints from PreventTransactionChain.
+ * at transaction commit.
*/
void
-vacuum(VacuumStmt *vacstmt, List *relids)
+vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel)
{
const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
volatile MemoryContext anl_context = NULL;
@@ -293,11 +294,11 @@ vacuum(VacuumStmt *vacstmt, List *relids)
*/
if (vacstmt->vacuum)
{
- PreventTransactionChain((void *) vacstmt, stmttype);
+ PreventTransactionChain(isTopLevel, stmttype);
in_outer_xact = false;
}
else
- in_outer_xact = IsInTransactionChain((void *) vacstmt);
+ in_outer_xact = IsInTransactionChain(isTopLevel);
/*
* Send info about dead objects to the statistics collector, unless we are
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 42f9eafd3d2..83f26f73ffb 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.99 2007/01/05 22:19:27 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.100 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -24,6 +24,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
+#include "parser/analyze.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
@@ -258,54 +259,23 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
*/
}
-static RuleStmt *
-FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace)
-{
- RuleStmt *rule;
-
- /*
- * Create a RuleStmt that corresponds to the suitable rewrite rule args
- * for DefineQueryRewrite();
- */
- rule = makeNode(RuleStmt);
- rule->relation = copyObject((RangeVar *) view);
- rule->rulename = pstrdup(ViewSelectRuleName);
- rule->whereClause = NULL;
- rule->event = CMD_SELECT;
- rule->instead = true;
- rule->actions = list_make1(viewParse);
- rule->replace = replace;
-
- return rule;
-}
-
static void
DefineViewRules(const RangeVar *view, Query *viewParse, bool replace)
{
- RuleStmt *retrieve_rule;
-
-#ifdef NOTYET
- RuleStmt *replace_rule;
- RuleStmt *append_rule;
- RuleStmt *delete_rule;
-#endif
-
- retrieve_rule = FormViewRetrieveRule(view, viewParse, replace);
-
-#ifdef NOTYET
- replace_rule = FormViewReplaceRule(view, viewParse);
- append_rule = FormViewAppendRule(view, viewParse);
- delete_rule = FormViewDeleteRule(view, viewParse);
-#endif
-
- DefineQueryRewrite(retrieve_rule);
-
-#ifdef NOTYET
- DefineQueryRewrite(replace_rule);
- DefineQueryRewrite(append_rule);
- DefineQueryRewrite(delete_rule);
-#endif
-
+ /*
+ * Set up the ON SELECT rule. Since the query has already been through
+ * parse analysis, we use DefineQueryRewrite() directly.
+ */
+ DefineQueryRewrite(pstrdup(ViewSelectRuleName),
+ (RangeVar *) copyObject((RangeVar *) view),
+ NULL,
+ CMD_SELECT,
+ true,
+ replace,
+ list_make1(viewParse));
+ /*
+ * Someday: automatic ON INSERT, etc
+ */
}
/*---------------------------------------------------------------
@@ -374,34 +344,80 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
return viewParse;
}
-/*-------------------------------------------------------------------
+/*
* DefineView
- *
- * - takes a "viewname", "parsetree" pair and then
- * 1) construct the "virtual" relation
- * 2) commit the command but NOT the transaction,
- * so that the relation exists
- * before the rules are defined.
- * 2) define the "n" rules specified in the PRS2 paper
- * over the "virtual" relation
- *-------------------------------------------------------------------
+ * Execute a CREATE VIEW command.
*/
void
-DefineView(RangeVar *view, Query *viewParse, bool replace)
+DefineView(ViewStmt *stmt, const char *queryString)
{
+ List *stmts;
+ Query *viewParse;
Oid viewOid;
+ RangeVar *view;
+
+ /*
+ * Run parse analysis to convert the raw parse tree to a Query. Note
+ * this also acquires sufficient locks on the source table(s).
+ *
+ * Since parse analysis scribbles on its input, copy the raw parse tree;
+ * this ensures we don't corrupt a prepared statement, for example.
+ */
+ stmts = parse_analyze((Node *) copyObject(stmt->query),
+ queryString, NULL, 0);
+
+ /*
+ * The grammar should ensure that the result is a single SELECT Query.
+ */
+ if (list_length(stmts) != 1)
+ elog(ERROR, "unexpected parse analysis result");
+ viewParse = (Query *) linitial(stmts);
+ if (!IsA(viewParse, Query) ||
+ viewParse->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected parse analysis result");
+
+ /*
+ * If a list of column names was given, run through and insert these into
+ * the actual query tree. - thomas 2000-03-08
+ */
+ if (stmt->aliases != NIL)
+ {
+ ListCell *alist_item = list_head(stmt->aliases);
+ ListCell *targetList;
+
+ foreach(targetList, viewParse->targetList)
+ {
+ TargetEntry *te = (TargetEntry *) lfirst(targetList);
+
+ Assert(IsA(te, TargetEntry));
+ /* junk columns don't get aliases */
+ if (te->resjunk)
+ continue;
+ te->resname = pstrdup(strVal(lfirst(alist_item)));
+ alist_item = lnext(alist_item);
+ if (alist_item == NULL)
+ break; /* done assigning aliases */
+ }
+
+ if (alist_item != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("CREATE VIEW specifies more column "
+ "names than columns")));
+ }
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly.
*/
- if (!view->istemp)
+ view = stmt->view;
+ if (!view->istemp && isViewOnTempTable(viewParse))
{
- view->istemp = isViewOnTempTable(viewParse);
- if (view->istemp)
- ereport(NOTICE,
- (errmsg("view \"%s\" will be a temporary view",
- view->relname)));
+ view = copyObject(view); /* don't corrupt original command */
+ view->istemp = true;
+ ereport(NOTICE,
+ (errmsg("view \"%s\" will be a temporary view",
+ view->relname)));
}
/*
@@ -410,7 +426,8 @@ DefineView(RangeVar *view, Query *viewParse, bool replace)
* NOTE: if it already exists and replace is false, the xact will be
* aborted.
*/
- viewOid = DefineVirtualRelation(view, viewParse->targetList, replace);
+ viewOid = DefineVirtualRelation(view, viewParse->targetList,
+ stmt->replace);
/*
* The relation we have just created is not visible to any other commands
@@ -428,7 +445,7 @@ DefineView(RangeVar *view, Query *viewParse, bool replace)
/*
* Now create the rules associated with the view.
*/
- DefineViewRules(view, viewParse, replace);
+ DefineViewRules(view, viewParse, stmt->replace);
}
/*