aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/transam/xact.c40
-rw-r--r--src/backend/bootstrap/bootparse.y6
-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
-rw-r--r--src/backend/executor/functions.c45
-rw-r--r--src/backend/executor/spi.c11
-rw-r--r--src/backend/nodes/copyfuncs.c4
-rw-r--r--src/backend/nodes/equalfuncs.c4
-rw-r--r--src/backend/nodes/outfuncs.c3
-rw-r--r--src/backend/nodes/params.c33
-rw-r--r--src/backend/optimizer/util/clauses.c34
-rw-r--r--src/backend/parser/analyze.c492
-rw-r--r--src/backend/parser/gram.y16
-rw-r--r--src/backend/postmaster/autovacuum.c11
-rw-r--r--src/backend/rewrite/rewriteDefine.c58
-rw-r--r--src/backend/tcop/postgres.c285
-rw-r--r--src/backend/tcop/pquery.c74
-rw-r--r--src/backend/tcop/utility.c105
-rw-r--r--src/backend/utils/cache/Makefile5
-rw-r--r--src/backend/utils/cache/plancache.c862
-rw-r--r--src/backend/utils/init/postinit.c6
-rw-r--r--src/backend/utils/mmgr/README15
-rw-r--r--src/backend/utils/mmgr/mcxt.c5
-rw-r--r--src/backend/utils/mmgr/portalmem.c86
-rw-r--r--src/backend/utils/resowner/README15
-rw-r--r--src/backend/utils/resowner/resowner.c98
-rw-r--r--src/include/access/xact.h8
-rw-r--r--src/include/commands/cluster.h4
-rw-r--r--src/include/commands/copy.h4
-rw-r--r--src/include/commands/defrem.h3
-rw-r--r--src/include/commands/explain.h11
-rw-r--r--src/include/commands/portalcmds.h5
-rw-r--r--src/include/commands/prepare.h41
-rw-r--r--src/include/commands/schemacmds.h5
-rw-r--r--src/include/commands/vacuum.h4
-rw-r--r--src/include/commands/view.h4
-rw-r--r--src/include/nodes/params.h5
-rw-r--r--src/include/nodes/parsenodes.h18
-rw-r--r--src/include/parser/analyze.h6
-rw-r--r--src/include/rewrite/rewriteDefine.h12
-rw-r--r--src/include/tcop/pquery.h4
-rw-r--r--src/include/tcop/utility.h7
-rw-r--r--src/include/utils/memutils.h5
-rw-r--r--src/include/utils/plancache.h105
-rw-r--r--src/include/utils/portal.h18
-rw-r--r--src/include/utils/resowner.h10
-rw-r--r--src/test/regress/expected/plancache.out102
-rw-r--r--src/test/regress/expected/rules.out2
-rw-r--r--src/test/regress/parallel_schedule4
-rw-r--r--src/test/regress/serial_schedule3
-rw-r--r--src/test/regress/sql/plancache.sql53
61 files changed, 2478 insertions, 1354 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index db81e3bd70c..6a2ebd40891 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.235 2007/03/12 22:09:27 petere Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.236 2007/03/13 00:33:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2504,12 +2504,13 @@ AbortCurrentTransaction(void)
* could issue more commands and possibly cause a failure after the statement
* completes). Subtransactions are verboten too.
*
- * stmtNode: pointer to parameter block for statement; this is used in
- * a very klugy way to determine whether we are inside a function.
- * stmtType: statement type name for error messages.
+ * isTopLevel: passed down from ProcessUtility to determine whether we are
+ * inside a function. (We will always fail if this is false, but it's
+ * convenient to centralize the check here instead of making callers do it.)
+ * stmtType: statement type name, for error messages.
*/
void
-PreventTransactionChain(void *stmtNode, const char *stmtType)
+PreventTransactionChain(bool isTopLevel, const char *stmtType)
{
/*
* xact block already started?
@@ -2532,11 +2533,9 @@ PreventTransactionChain(void *stmtNode, const char *stmtType)
stmtType)));
/*
- * Are we inside a function call? If the statement's parameter block was
- * allocated in QueryContext, assume it is an interactive command.
- * Otherwise assume it is coming from a function.
+ * inside a function call?
*/
- if (!MemoryContextContains(QueryContext, stmtNode))
+ if (!isTopLevel)
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
@@ -2562,12 +2561,12 @@ PreventTransactionChain(void *stmtNode, const char *stmtType)
* use of the current statement's results. Likewise subtransactions.
* Thus this is an inverse for PreventTransactionChain.
*
- * stmtNode: pointer to parameter block for statement; this is used in
- * a very klugy way to determine whether we are inside a function.
- * stmtType: statement type name for error messages.
+ * isTopLevel: passed down from ProcessUtility to determine whether we are
+ * inside a function.
+ * stmtType: statement type name, for error messages.
*/
void
-RequireTransactionChain(void *stmtNode, const char *stmtType)
+RequireTransactionChain(bool isTopLevel, const char *stmtType)
{
/*
* xact block already started?
@@ -2582,12 +2581,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType)
return;
/*
- * Are we inside a function call? If the statement's parameter block was
- * allocated in QueryContext, assume it is an interactive command.
- * Otherwise assume it is coming from a function.
+ * inside a function call?
*/
- if (!MemoryContextContains(QueryContext, stmtNode))
+ if (!isTopLevel)
return;
+
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
@@ -2602,11 +2600,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType)
* a transaction block than when running as single commands. ANALYZE is
* currently the only example.
*
- * stmtNode: pointer to parameter block for statement; this is used in
- * a very klugy way to determine whether we are inside a function.
+ * isTopLevel: passed down from ProcessUtility to determine whether we are
+ * inside a function.
*/
bool
-IsInTransactionChain(void *stmtNode)
+IsInTransactionChain(bool isTopLevel)
{
/*
* Return true on same conditions that would make PreventTransactionChain
@@ -2618,7 +2616,7 @@ IsInTransactionChain(void *stmtNode)
if (IsSubTransaction())
return true;
- if (!MemoryContextContains(QueryContext, stmtNode))
+ if (!isTopLevel)
return true;
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 64c8c8139da..ff2f7f70c3c 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.87 2007/03/07 13:35:02 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.88 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -252,7 +252,7 @@ Boot_DeclareIndexStmt:
LexIDStr($8),
NULL,
$10,
- NULL, NIL, NIL,
+ NULL, NIL,
false, false, false,
false, false, true, false, false);
do_end();
@@ -270,7 +270,7 @@ Boot_DeclareUniqueIndexStmt:
LexIDStr($9),
NULL,
$11,
- NULL, NIL, NIL,
+ NULL, NIL,
true, false, false,
false, false, true, false, false);
do_end();
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);
}
/*
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 596a482fa13..7e648f437b6 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.111 2007/02/20 17:32:15 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.112 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -58,6 +58,8 @@ typedef struct local_es
*/
typedef struct
{
+ char *src; /* function body text (for error msgs) */
+
Oid *argtypes; /* resolved types of arguments */
Oid rettype; /* actual return type */
int16 typlen; /* length of the return type */
@@ -82,7 +84,8 @@ static execution_state *init_execution_state(List *queryTree_list,
bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
-static TupleTableSlot *postquel_getnext(execution_state *es);
+static TupleTableSlot *postquel_getnext(execution_state *es,
+ SQLFunctionCachePtr fcache);
static void postquel_end(execution_state *es);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
@@ -156,7 +159,6 @@ init_sql_fcache(FmgrInfo *finfo)
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
Oid *argOidVect;
- char *src;
int nargs;
List *queryTree_list;
Datum tmp;
@@ -233,7 +235,7 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->argtypes = argOidVect;
/*
- * Parse and rewrite the queries in the function text.
+ * And of course we need the function body text.
*/
tmp = SysCacheGetAttr(PROCOID,
procedureTuple,
@@ -241,9 +243,12 @@ init_sql_fcache(FmgrInfo *finfo)
&isNull);
if (isNull)
elog(ERROR, "null prosrc for function %u", foid);
- src = DatumGetCString(DirectFunctionCall1(textout, tmp));
+ fcache->src = DatumGetCString(DirectFunctionCall1(textout, tmp));
- queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
+ /*
+ * Parse and rewrite the queries in the function text.
+ */
+ queryTree_list = pg_parse_and_rewrite(fcache->src, argOidVect, nargs);
/*
* Check that the function returns the type it claims to. Although
@@ -270,8 +275,6 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->func_state = init_execution_state(queryTree_list,
fcache->readonly_func);
- pfree(src);
-
ReleaseSysCache(procedureTuple);
finfo->fn_extra = (void *) fcache;
@@ -331,7 +334,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
}
static TupleTableSlot *
-postquel_getnext(execution_state *es)
+postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{
TupleTableSlot *result;
Snapshot saveActiveSnapshot;
@@ -345,8 +348,12 @@ postquel_getnext(execution_state *es)
if (es->qd->operation == CMD_UTILITY)
{
- ProcessUtility(es->qd->utilitystmt, es->qd->params,
- es->qd->dest, NULL);
+ ProcessUtility(es->qd->utilitystmt,
+ fcache->src,
+ es->qd->params,
+ false, /* not top level */
+ es->qd->dest,
+ NULL);
result = NULL;
}
else
@@ -465,7 +472,7 @@ postquel_execute(execution_state *es,
if (es->status == F_EXEC_START)
postquel_start(es, fcache);
- slot = postquel_getnext(es);
+ slot = postquel_getnext(es, fcache);
if (TupIsNull(slot))
{
@@ -754,21 +761,11 @@ sql_exec_error_callback(void *arg)
* If there is a syntax error position, convert to internal syntax error
*/
syntaxerrposition = geterrposition();
- if (syntaxerrposition > 0)
+ if (syntaxerrposition > 0 && fcache->src)
{
- bool isnull;
- Datum tmp;
- char *prosrc;
-
- tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc,
- &isnull);
- if (isnull)
- elog(ERROR, "null prosrc");
- prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
errposition(0);
internalerrposition(syntaxerrposition);
- internalerrquery(prosrc);
- pfree(prosrc);
+ internalerrquery(fcache->src);
}
/*
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 33ec21286d9..f538f508ba3 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.170 2007/02/20 17:32:15 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.171 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -927,7 +927,7 @@ SPI_cursor_open(const char *name, void *plan,
spiplan->query,
CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)),
stmt_list,
- PortalGetHeapMemory(portal));
+ NULL);
MemoryContextSwitchTo(oldcontext);
@@ -1471,7 +1471,12 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
}
else
{
- ProcessUtility(stmt, paramLI, dest, NULL);
+ ProcessUtility(stmt,
+ NULL, /* XXX provide query string? */
+ paramLI,
+ false, /* not top level */
+ dest,
+ NULL);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free;
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 19cb0b0b6fc..4ca9ba4c0e2 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.369 2007/02/27 01:11:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.370 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2142,7 +2142,6 @@ _copyIndexStmt(IndexStmt *from)
COPY_NODE_FIELD(indexParams);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(whereClause);
- COPY_NODE_FIELD(rangetable);
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
@@ -2785,7 +2784,6 @@ _copyPrepareStmt(PrepareStmt *from)
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(argtypes);
- COPY_NODE_FIELD(argtype_oids);
COPY_NODE_FIELD(query);
return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 1007930814a..ae247dfa3ea 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.300 2007/02/22 22:00:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.301 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -994,7 +994,6 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b)
COMPARE_NODE_FIELD(indexParams);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(whereClause);
- COMPARE_NODE_FIELD(rangetable);
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
@@ -1536,7 +1535,6 @@ _equalPrepareStmt(PrepareStmt *a, PrepareStmt *b)
{
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(argtypes);
- COMPARE_NODE_FIELD(argtype_oids);
COMPARE_NODE_FIELD(query);
return true;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ca3c79812c1..68025ab3686 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.302 2007/02/27 01:11:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.303 2007/03/13 00:33:40 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@@ -1505,7 +1505,6 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
WRITE_NODE_FIELD(indexParams);
WRITE_NODE_FIELD(options);
WRITE_NODE_FIELD(whereClause);
- WRITE_NODE_FIELD(rangetable);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c
index 15e7b1d24f1..07593c55470 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.8 2007/01/05 22:19:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.9 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -60,3 +60,34 @@ copyParamList(ParamListInfo from)
return retval;
}
+
+/*
+ * Extract an array of parameter type OIDs from a ParamListInfo.
+ *
+ * The result is allocated in CurrentMemoryContext.
+ */
+void
+getParamListTypes(ParamListInfo params,
+ Oid **param_types, int *num_params)
+{
+ Oid *ptypes;
+ int i;
+
+ if (params == NULL || params->numParams <= 0)
+ {
+ *param_types = NULL;
+ *num_params = 0;
+ return;
+ }
+
+ ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
+ *param_types = ptypes;
+ *num_params = params->numParams;
+
+ for (i = 0; i < params->numParams; i++)
+ {
+ ParamExternData *prm = &params->params[i];
+
+ ptypes[i] = prm->ptype;
+ }
+}
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 0f8231c4f91..11d2119f3c6 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.237 2007/03/06 22:45:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.238 2007/03/13 00:33:41 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -3603,38 +3603,6 @@ query_tree_walker(Query *query,
return true;
if (range_table_walker(query->rtable, walker, context, flags))
return true;
- if (query->utilityStmt)
- {
- /*
- * Certain utility commands contain general-purpose Querys embedded in
- * them --- if this is one, invoke the walker on the sub-Query.
- */
- if (IsA(query->utilityStmt, CopyStmt))
- {
- if (walker(((CopyStmt *) query->utilityStmt)->query, context))
- return true;
- }
- if (IsA(query->utilityStmt, DeclareCursorStmt))
- {
- if (walker(((DeclareCursorStmt *) query->utilityStmt)->query, context))
- return true;
- }
- if (IsA(query->utilityStmt, ExplainStmt))
- {
- if (walker(((ExplainStmt *) query->utilityStmt)->query, context))
- return true;
- }
- if (IsA(query->utilityStmt, PrepareStmt))
- {
- if (walker(((PrepareStmt *) query->utilityStmt)->query, context))
- return true;
- }
- if (IsA(query->utilityStmt, ViewStmt))
- {
- if (walker(((ViewStmt *) query->utilityStmt)->query, context))
- return true;
- }
- }
return false;
}
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 63a5999a401..a4e4418b145 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -1,12 +1,26 @@
/*-------------------------------------------------------------------------
*
* analyze.c
- * transform the parse tree into a query tree
+ * transform the raw parse tree into a query tree
+ *
+ * For optimizable statements, we are careful to obtain a suitable lock on
+ * each referenced table, and other modules of the backend preserve or
+ * re-obtain these locks before depending on the results. It is therefore
+ * okay to do significant semantic analysis of these statements. For
+ * utility commands, no locks are obtained here (and if they were, we could
+ * not be sure we'd still have them at execution). Hence the general rule
+ * for utility commands is to just dump them into a Query node untransformed.
+ * parse_analyze does do some purely syntactic transformations on CREATE TABLE
+ * and ALTER TABLE, but that's about it. In cases where this module contains
+ * mechanisms that are useful for utility statements, we provide separate
+ * subroutines that should be called at the beginning of utility execution;
+ * an example is analyzeIndexStmt.
+ *
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.361 2007/02/20 17:32:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.362 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -93,26 +107,17 @@ typedef struct
static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static Query *transformStmt(ParseState *pstate, Node *stmt,
List **extras_before, List **extras_after);
-static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
- List **extras_before, List **extras_after);
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
static List *transformReturningList(ParseState *pstate, List *returningList);
-static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
-static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
- List **extras_before, List **extras_after);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
-static Query *transformDeclareCursorStmt(ParseState *pstate,
- DeclareCursorStmt *stmt);
-static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
-static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
@@ -155,7 +160,7 @@ static bool check_parameter_resolution_walker(Node *node,
*
* The result is a List of Query nodes (we need a list since some commands
* produce multiple Queries). Optimizable statements require considerable
- * transformation, while many utility-type statements are simply hung off
+ * transformation, while most utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node.
*/
List *
@@ -315,59 +320,12 @@ transformStmt(ParseState *pstate, Node *parseTree,
extras_before, extras_after);
break;
- case T_IndexStmt:
- result = transformIndexStmt(pstate, (IndexStmt *) parseTree);
- break;
-
- case T_RuleStmt:
- result = transformRuleStmt(pstate, (RuleStmt *) parseTree,
- extras_before, extras_after);
- break;
-
- case T_ViewStmt:
- result = transformViewStmt(pstate, (ViewStmt *) parseTree,
- extras_before, extras_after);
- break;
-
- case T_ExplainStmt:
- {
- ExplainStmt *n = (ExplainStmt *) parseTree;
-
- result = makeNode(Query);
- result->commandType = CMD_UTILITY;
- n->query = transformStmt(pstate, (Node *) n->query,
- extras_before, extras_after);
- result->utilityStmt = (Node *) parseTree;
- }
- break;
-
- case T_CopyStmt:
- {
- CopyStmt *n = (CopyStmt *) parseTree;
-
- result = makeNode(Query);
- result->commandType = CMD_UTILITY;
- if (n->query)
- n->query = transformStmt(pstate, (Node *) n->query,
- extras_before, extras_after);
- result->utilityStmt = (Node *) parseTree;
- }
- break;
-
case T_AlterTableStmt:
result = transformAlterTableStmt(pstate,
(AlterTableStmt *) parseTree,
extras_before, extras_after);
break;
- case T_PrepareStmt:
- result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree);
- break;
-
- case T_ExecuteStmt:
- result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree);
- break;
-
/*
* Optimizable statements
*/
@@ -397,16 +355,11 @@ transformStmt(ParseState *pstate, Node *parseTree,
}
break;
- case T_DeclareCursorStmt:
- result = transformDeclareCursorStmt(pstate,
- (DeclareCursorStmt *) parseTree);
- break;
-
default:
/*
- * other statements don't require any transformation-- just return
- * the original parsetree, yea!
+ * other statements don't require any transformation; just return
+ * the original parsetree with a Query node plastered on top.
*/
result = makeNode(Query);
result->commandType = CMD_UTILITY;
@@ -432,54 +385,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
return result;
}
-static Query *
-transformViewStmt(ParseState *pstate, ViewStmt *stmt,
- List **extras_before, List **extras_after)
-{
- Query *result = makeNode(Query);
-
- result->commandType = CMD_UTILITY;
- result->utilityStmt = (Node *) stmt;
-
- stmt->query = transformStmt(pstate, (Node *) stmt->query,
- extras_before, extras_after);
-
- /*
- * If a list of column names was given, run through and insert these into
- * the actual query tree. - thomas 2000-03-08
- *
- * Outer loop is over targetlist to make it easier to skip junk targetlist
- * entries.
- */
- if (stmt->aliases != NIL)
- {
- ListCell *alist_item = list_head(stmt->aliases);
- ListCell *targetList;
-
- foreach(targetList, stmt->query->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")));
- }
-
- return result;
-}
-
/*
* transformDeleteStmt -
* transforms a Delete Statement
@@ -1278,8 +1183,13 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
/*
* transformInhRelation
*
- * Change the LIKE <subtable> portion of a CREATE TABLE statement into the
- * column definitions which recreate the user defined column portions of <subtable>.
+ * Change the LIKE <subtable> portion of a CREATE TABLE statement into
+ * column definitions which recreate the user defined column portions of
+ * <subtable>.
+ *
+ * Note: because we do this at parse analysis time, any change in the
+ * referenced table between parse analysis and execution won't be reflected
+ * into the new table. Is this OK?
*/
static void
transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
@@ -1644,7 +1554,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
* that strikes me as too anal-retentive. - tgl 2001-02-14
*
* XXX in ALTER TABLE case, it'd be nice to look for duplicate
- * pre-existing indexes, too.
+ * pre-existing indexes, too. However, that seems to risk race
+ * conditions since we can't be sure the command will be executed
+ * immediately.
*/
Assert(cxt->alist == NIL);
if (cxt->pkey != NULL)
@@ -1746,37 +1658,55 @@ transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
}
/*
- * transformIndexStmt -
- * transforms the qualification of the index statement
+ * analyzeIndexStmt - perform parse analysis for CREATE INDEX
+ *
+ * Note that this has to be performed during execution not parse analysis, so
+ * it's called by ProcessUtility. (Most other callers don't need to bother,
+ * because this is a no-op for an index not using either index expressions or
+ * a predicate expression.)
*/
-static Query *
-transformIndexStmt(ParseState *pstate, IndexStmt *stmt)
+IndexStmt *
+analyzeIndexStmt(IndexStmt *stmt, const char *queryString)
{
- Query *qry;
- RangeTblEntry *rte = NULL;
+ Relation rel;
+ ParseState *pstate;
+ RangeTblEntry *rte;
ListCell *l;
- qry = makeNode(Query);
- qry->commandType = CMD_UTILITY;
+ /*
+ * We must not scribble on the passed-in IndexStmt, so copy it. (This
+ * is overkill, but easy.)
+ */
+ stmt = (IndexStmt *) copyObject(stmt);
- /* take care of the where clause */
- if (stmt->whereClause)
- {
- /*
- * Put the parent table into the rtable so that the WHERE clause can
- * refer to its fields without qualification. Note that this only
- * works if the parent table already exists --- so we can't easily
- * support predicates on indexes created implicitly by CREATE TABLE.
- * Fortunately, that's not necessary.
- */
- rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
+ /*
+ * Open the parent table with appropriate locking. We must do this
+ * because addRangeTableEntry() would acquire only AccessShareLock,
+ * leaving DefineIndex() needing to do a lock upgrade with consequent
+ * risk of deadlock. Make sure this stays in sync with the type of
+ * lock DefineIndex() wants.
+ */
+ rel = heap_openrv(stmt->relation,
+ (stmt->concurrent ? ShareUpdateExclusiveLock : ShareLock));
+
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
+ /*
+ * Put the parent table into the rtable so that the expressions can
+ * refer to its fields without qualification.
+ */
+ rte = addRangeTableEntry(pstate, stmt->relation, NULL, false, true);
- /* no to join list, yes to namespaces */
- addRTEtoQuery(pstate, rte, false, true, true);
+ /* no to join list, yes to namespaces */
+ addRTEtoQuery(pstate, rte, false, true, true);
- stmt->whereClause = transformWhereClause(pstate, stmt->whereClause,
+ /* take care of the where clause */
+ if (stmt->whereClause)
+ stmt->whereClause = transformWhereClause(pstate,
+ stmt->whereClause,
"WHERE");
- }
/* take care of any index expressions */
foreach(l, stmt->indexParams)
@@ -1785,14 +1715,6 @@ transformIndexStmt(ParseState *pstate, IndexStmt *stmt)
if (ielem->expr)
{
- /* Set up rtable as for predicate, see notes above */
- if (rte == NULL)
- {
- rte = addRangeTableEntry(pstate, stmt->relation, NULL,
- false, true);
- /* no to join list, yes to namespaces */
- addRTEtoQuery(pstate, rte, false, true, true);
- }
ielem->expr = transformExpr(pstate, ielem->expr);
/*
@@ -1807,32 +1729,44 @@ transformIndexStmt(ParseState *pstate, IndexStmt *stmt)
}
}
- qry->hasSubLinks = pstate->p_hasSubLinks;
- stmt->rangetable = pstate->p_rtable;
+ /*
+ * Check that only the base rel is mentioned.
+ */
+ if (list_length(pstate->p_rtable) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ errmsg("index expressions and predicates can refer only to the table being indexed")));
- qry->utilityStmt = (Node *) stmt;
+ release_pstate_resources(pstate);
+ pfree(pstate);
- return qry;
+ /* Close relation, but keep the lock */
+ heap_close(rel, NoLock);
+
+ return stmt;
}
+
/*
- * transformRuleStmt -
- * transform a Create Rule Statement. The actions is a list of parse
- * trees which is transformed into a list of query trees.
+ * analyzeRuleStmt -
+ * transform a Create Rule Statement. The action is a list of parse
+ * trees which is transformed into a list of query trees, and we also
+ * transform the WHERE clause if any.
+ *
+ * Note that this has to be performed during execution not parse analysis,
+ * so it's called by DefineRule. Also note that we must not scribble on
+ * the passed-in RuleStmt, so we do copyObject() on the actions and WHERE
+ * clause.
*/
-static Query *
-transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
- List **extras_before, List **extras_after)
+void
+analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
+ List **actions, Node **whereClause)
{
- Query *qry;
Relation rel;
+ ParseState *pstate;
RangeTblEntry *oldrte;
RangeTblEntry *newrte;
- qry = makeNode(Query);
- qry->commandType = CMD_UTILITY;
- qry->utilityStmt = (Node *) stmt;
-
/*
* To avoid deadlock, make sure the first thing we do is grab
* AccessExclusiveLock on the target relation. This will be needed by
@@ -1841,12 +1775,15 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
*/
rel = heap_openrv(stmt->relation, AccessExclusiveLock);
+ /* Set up pstate */
+ pstate = make_parsestate(NULL);
+ pstate->p_sourcetext = queryString;
+
/*
* NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
* Set up their RTEs in the main pstate for use in parsing the rule
* qualification.
*/
- Assert(pstate->p_rtable == NIL);
oldrte = addRangeTableEntryForRelation(pstate, rel,
makeAlias("*OLD*", NIL),
false, false);
@@ -1886,8 +1823,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
}
/* take care of the where clause */
- stmt->whereClause = transformWhereClause(pstate, stmt->whereClause,
- "WHERE");
+ *whereClause = transformWhereClause(pstate,
+ (Node *) copyObject(stmt->whereClause),
+ "WHERE");
if (list_length(pstate->p_rtable) != 2) /* naughty, naughty... */
ereport(ERROR,
@@ -1900,9 +1838,6 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
(errcode(ERRCODE_GROUPING_ERROR),
errmsg("cannot use aggregate function in rule WHERE condition")));
- /* save info about sublinks in where clause */
- qry->hasSubLinks = pstate->p_hasSubLinks;
-
/*
* 'instead nothing' rules with a qualification need a query rangetable so
* the rewrite handler can add the negated rule qualification to the
@@ -1917,7 +1852,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
nothing_qry->rtable = pstate->p_rtable;
nothing_qry->jointree = makeFromExpr(NIL, NULL); /* no join wanted */
- stmt->actions = list_make1(nothing_qry);
+ *actions = list_make1(nothing_qry);
}
else
{
@@ -1930,13 +1865,21 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
foreach(l, stmt->actions)
{
Node *action = (Node *) lfirst(l);
- ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
+ ParseState *sub_pstate = make_parsestate(NULL);
Query *sub_qry,
*top_subqry;
+ List *extras_before = NIL;
+ List *extras_after = NIL;
bool has_old,
has_new;
/*
+ * Since outer ParseState isn't parent of inner, have to pass
+ * down the query text by hand.
+ */
+ sub_pstate->p_sourcetext = queryString;
+
+ /*
* Set up OLD/NEW in the rtable for this statement. The entries
* are added only to relnamespace, not varnamespace, because we
* don't want them to be referred to by unqualified field names
@@ -1955,8 +1898,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
addRTEtoQuery(sub_pstate, newrte, false, true, false);
/* Transform the rule action statement */
- top_subqry = transformStmt(sub_pstate, action,
- extras_before, extras_after);
+ top_subqry = transformStmt(sub_pstate,
+ (Node *) copyObject(action),
+ &extras_before, &extras_after);
/*
* We cannot support utility-statement actions (eg NOTIFY) with
@@ -1964,7 +1908,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
* the utility action execute conditionally.
*/
if (top_subqry->commandType == CMD_UTILITY &&
- stmt->whereClause != NULL)
+ *whereClause != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("rules with WHERE conditions can only have SELECT, INSERT, UPDATE, or DELETE actions")));
@@ -1982,7 +1926,7 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
* perhaps be relaxed someday, but for now, we may as well reject
* such a rule immediately.
*/
- if (sub_qry->setOperations != NULL && stmt->whereClause != NULL)
+ if (sub_qry->setOperations != NULL && *whereClause != NULL)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
@@ -1992,10 +1936,10 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
*/
has_old =
rangeTableEntry_used((Node *) sub_qry, PRS2_OLD_VARNO, 0) ||
- rangeTableEntry_used(stmt->whereClause, PRS2_OLD_VARNO, 0);
+ rangeTableEntry_used(*whereClause, PRS2_OLD_VARNO, 0);
has_new =
rangeTableEntry_used((Node *) sub_qry, PRS2_NEW_VARNO, 0) ||
- rangeTableEntry_used(stmt->whereClause, PRS2_NEW_VARNO, 0);
+ rangeTableEntry_used(*whereClause, PRS2_NEW_VARNO, 0);
switch (stmt->event)
{
@@ -2063,27 +2007,28 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt,
sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
}
+ newactions = list_concat(newactions, extras_before);
newactions = lappend(newactions, top_subqry);
+ newactions = list_concat(newactions, extras_after);
release_pstate_resources(sub_pstate);
pfree(sub_pstate);
}
- stmt->actions = newactions;
+ *actions = newactions;
}
+ release_pstate_resources(pstate);
+ pfree(pstate);
+
/* Close relation, but keep the exclusive lock */
heap_close(rel, NoLock);
-
- return qry;
}
/*
* transformSelectStmt -
* transforms a Select Statement
- *
- * Note: this is also used for DECLARE CURSOR statements.
*/
static Query *
transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
@@ -2991,6 +2936,11 @@ transformReturningList(ParseState *pstate, List *returningList)
/*
* transformAlterTableStmt -
* transform an Alter Table Statement
+ *
+ * CAUTION: resist the temptation to do any work here that depends on the
+ * current state of the table. Actual execution of the command might not
+ * occur till some future transaction. Hence, we do only purely syntactic
+ * transformations here, comparable to the processing of CREATE TABLE.
*/
static Query *
transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
@@ -3162,184 +3112,6 @@ transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
return qry;
}
-static Query *
-transformDeclareCursorStmt(ParseState *pstate, DeclareCursorStmt *stmt)
-{
- Query *result = makeNode(Query);
- List *extras_before = NIL,
- *extras_after = NIL;
-
- result->commandType = CMD_UTILITY;
- result->utilityStmt = (Node *) stmt;
-
- /*
- * Don't allow both SCROLL and NO SCROLL to be specified
- */
- 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")));
-
- stmt->query = (Node *) transformStmt(pstate, stmt->query,
- &extras_before, &extras_after);
-
- /* Shouldn't get any extras, since grammar only allows SelectStmt */
- if (extras_before || extras_after)
- elog(ERROR, "unexpected extra stuff in cursor statement");
- if (!IsA(stmt->query, Query) ||
- ((Query *) stmt->query)->commandType != CMD_SELECT)
- elog(ERROR, "unexpected non-SELECT command in cursor statement");
-
- /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
- if (((Query *) stmt->query)->into)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
- errmsg("DECLARE CURSOR cannot specify INTO")));
-
- return result;
-}
-
-
-static Query *
-transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt)
-{
- Query *result = makeNode(Query);
- List *argtype_oids; /* argtype OIDs in a list */
- Oid *argtoids = NULL; /* and as an array */
- int nargs;
- List *queries;
- int i;
-
- result->commandType = CMD_UTILITY;
- result->utilityStmt = (Node *) stmt;
-
- /* Transform list of TypeNames to list (and array) of type OIDs */
- nargs = list_length(stmt->argtypes);
-
- if (nargs)
- {
- ListCell *l;
-
- argtoids = (Oid *) palloc(nargs * sizeof(Oid));
- i = 0;
-
- foreach(l, stmt->argtypes)
- {
- TypeName *tn = lfirst(l);
- Oid toid = typenameTypeId(pstate, tn);
-
- argtoids[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.
- */
- queries = parse_analyze_varparams((Node *) stmt->query,
- pstate->p_sourcetext,
- &argtoids, &nargs);
-
- /*
- * Shouldn't get any extra statements, since grammar only allows
- * OptimizableStmt
- */
- if (list_length(queries) != 1)
- elog(ERROR, "unexpected extra stuff in prepared statement");
-
- /*
- * Check that all parameter types were determined, and convert the array
- * of OIDs into a list for storage.
- */
- argtype_oids = NIL;
- for (i = 0; i < nargs; i++)
- {
- Oid argtype = argtoids[i];
-
- if (argtype == InvalidOid || argtype == UNKNOWNOID)
- ereport(ERROR,
- (errcode(ERRCODE_INDETERMINATE_DATATYPE),
- errmsg("could not determine data type of parameter $%d",
- i + 1)));
-
- argtype_oids = lappend_oid(argtype_oids, argtype);
- }
-
- stmt->argtype_oids = argtype_oids;
- stmt->query = linitial(queries);
- return result;
-}
-
-static Query *
-transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt)
-{
- Query *result = makeNode(Query);
- List *paramtypes;
-
- result->commandType = CMD_UTILITY;
- result->utilityStmt = (Node *) stmt;
-
- paramtypes = FetchPreparedStatementParams(stmt->name);
-
- if (stmt->params || paramtypes)
- {
- int nparams = list_length(stmt->params);
- int nexpected = list_length(paramtypes);
- ListCell *l,
- *l2;
- int i = 1;
-
- if (nparams != nexpected)
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("wrong number of parameters for prepared statement \"%s\"",
- stmt->name),
- errdetail("Expected %d parameters but got %d.",
- nexpected, nparams)));
-
- forboth(l, stmt->params, l2, paramtypes)
- {
- Node *expr = lfirst(l);
- Oid expected_type_id = lfirst_oid(l2);
- 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,
- 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++;
- }
- }
-
- return result;
-}
/* exported so planner can check again after rewriting, query pullup, etc */
void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3204d0a401a..1ce71700405 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.580 2007/02/20 17:32:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.581 2007/03/13 00:33:41 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -1662,7 +1662,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
- n->query = (Query *) $2;
+ n->query = $2;
n->attlist = NIL;
n->is_from = false;
n->filename = $4;
@@ -4959,22 +4959,22 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
- n->replace = false;
n->view = $4;
n->view->istemp = $2;
n->aliases = $5;
- n->query = (Query *) $7;
+ n->query = $7;
+ n->replace = false;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
- n->replace = true;
n->view = $6;
n->view->istemp = $4;
n->aliases = $7;
- n->query = (Query *) $9;
+ n->query = $9;
+ n->replace = true;
$$ = (Node *) n;
}
;
@@ -5406,7 +5406,7 @@ ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
ExplainStmt *n = makeNode(ExplainStmt);
n->analyze = $2;
n->verbose = $3;
- n->query = (Query*)$4;
+ n->query = $4;
$$ = (Node *)n;
}
;
@@ -5437,7 +5437,7 @@ PrepareStmt: PREPARE name prep_type_clause AS PreparableStmt
PrepareStmt *n = makeNode(PrepareStmt);
n->name = $2;
n->argtypes = $3;
- n->query = (Query *) $5;
+ n->query = $5;
$$ = (Node *) n;
}
;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 87384df8a2d..b2918558305 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.33 2007/03/07 13:35:02 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.34 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1248,13 +1248,6 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze,
vacstmt = makeNode(VacuumStmt);
- /*
- * Point QueryContext to the autovac memory context to fake out the
- * PreventTransactionChain check inside vacuum(). Note that this is also
- * why we palloc vacstmt instead of just using a local variable.
- */
- QueryContext = CurrentMemoryContext;
-
/* Set up command parameters */
vacstmt->vacuum = dovacuum;
vacstmt->full = false;
@@ -1267,7 +1260,7 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze,
/* Let pgstat know what we're doing */
autovac_report_activity(vacstmt, relid);
- vacuum(vacstmt, list_make1_oid(relid));
+ vacuum(vacstmt, list_make1_oid(relid), true);
pfree(vacstmt);
MemoryContextSwitchTo(old_cxt);
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index d4212a44089..64a2a96f0e9 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.117 2007/02/01 19:10:27 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.118 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,6 +20,7 @@
#include "catalog/pg_rewrite.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "parser/analyze.h"
#include "parser/parse_expr.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
@@ -177,15 +178,46 @@ InsertRule(char *rulname,
return rewriteObjectId;
}
+/*
+ * DefineRule
+ * Execute a CREATE RULE command.
+ */
+void
+DefineRule(RuleStmt *stmt, const char *queryString)
+{
+ List *actions;
+ Node *whereClause;
+
+ /* Parse analysis ... */
+ analyzeRuleStmt(stmt, queryString, &actions, &whereClause);
+
+ /* ... and execution */
+ DefineQueryRewrite(stmt->rulename,
+ stmt->relation,
+ whereClause,
+ stmt->event,
+ stmt->instead,
+ stmt->replace,
+ actions);
+}
+
+
+/*
+ * DefineQueryRewrite
+ * Create a rule
+ *
+ * This is essentially the same as DefineRule() except that the rule's
+ * action and qual have already been passed through parse analysis.
+ */
void
-DefineQueryRewrite(RuleStmt *stmt)
+DefineQueryRewrite(char *rulename,
+ RangeVar *event_obj,
+ Node *event_qual,
+ CmdType event_type,
+ bool is_instead,
+ bool replace,
+ List *action)
{
- RangeVar *event_obj = stmt->relation;
- Node *event_qual = stmt->whereClause;
- CmdType event_type = stmt->event;
- bool is_instead = stmt->instead;
- bool replace = stmt->replace;
- List *action = stmt->actions;
Relation event_relation;
Oid ev_relid;
Oid ruleId;
@@ -304,7 +336,7 @@ DefineQueryRewrite(RuleStmt *stmt)
/*
* ... and finally the rule must be named _RETURN.
*/
- if (strcmp(stmt->rulename, ViewSelectRuleName) != 0)
+ if (strcmp(rulename, ViewSelectRuleName) != 0)
{
/*
* In versions before 7.3, the expected name was _RETviewname. For
@@ -315,14 +347,14 @@ DefineQueryRewrite(RuleStmt *stmt)
* worry about where a multibyte character might have gotten
* truncated.
*/
- if (strncmp(stmt->rulename, "_RET", 4) != 0 ||
- strncmp(stmt->rulename + 4, event_obj->relname,
+ if (strncmp(rulename, "_RET", 4) != 0 ||
+ strncmp(rulename + 4, event_obj->relname,
NAMEDATALEN - 4 - 4) != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("view rule for \"%s\" must be named \"%s\"",
event_obj->relname, ViewSelectRuleName)));
- stmt->rulename = pstrdup(ViewSelectRuleName);
+ rulename = pstrdup(ViewSelectRuleName);
}
/*
@@ -411,7 +443,7 @@ DefineQueryRewrite(RuleStmt *stmt)
/* discard rule if it's null action and not INSTEAD; it's a no-op */
if (action != NIL || is_instead)
{
- ruleId = InsertRule(stmt->rulename,
+ ruleId = InsertRule(rulename,
event_type,
ev_relid,
event_attno,
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index cfb6731b234..f997d524101 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.527 2007/03/03 19:32:54 neilc Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.528 2007/03/13 00:33:42 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -140,8 +140,9 @@ static bool ignore_till_sync = false;
* We keep it separate from the hashtable kept by commands/prepare.c
* in order to reduce overhead for short-lived queries.
*/
+static CachedPlanSource *unnamed_stmt_psrc = NULL;
+/* workspace for building a new unnamed statement in */
static MemoryContext unnamed_stmt_context = NULL;
-static PreparedStatement *unnamed_stmt_pstmt = NULL;
static bool EchoQuery = false; /* default don't echo */
@@ -173,6 +174,7 @@ static void finish_xact_command(void);
static bool IsTransactionExitStmt(Node *parsetree);
static bool IsTransactionExitStmtList(List *parseTrees);
static bool IsTransactionStmtList(List *parseTrees);
+static void drop_unnamed_stmt(void);
static void SigHupHandler(SIGNAL_ARGS);
static void log_disconnections(int code, Datum arg);
@@ -794,21 +796,13 @@ exec_simple_query(const char *query_string)
* statement and portal; this ensures we recover any storage used by prior
* unnamed operations.)
*/
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
+ drop_unnamed_stmt();
/*
* Switch to appropriate context for constructing parsetrees.
*/
oldcontext = MemoryContextSwitchTo(MessageContext);
- QueryContext = CurrentMemoryContext;
-
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@@ -906,7 +900,7 @@ exec_simple_query(const char *query_string)
query_string,
commandTag,
plantree_list,
- MessageContext);
+ NULL);
/*
* Start the portal. No parameters here.
@@ -950,6 +944,7 @@ exec_simple_query(const char *query_string)
*/
(void) PortalRun(portal,
FETCH_ALL,
+ true, /* top level */
receiver,
receiver,
completionTag);
@@ -1009,8 +1004,6 @@ exec_simple_query(const char *query_string)
if (!parsetree_list)
NullCommand(dest);
- QueryContext = NULL;
-
/*
* Emit duration logging if appropriate.
*/
@@ -1049,10 +1042,10 @@ exec_parse_message(const char *query_string, /* string to execute */
{
MemoryContext oldcontext;
List *parsetree_list;
+ Node *raw_parse_tree;
const char *commandTag;
List *querytree_list,
- *stmt_list,
- *param_list;
+ *stmt_list;
bool is_named;
bool fully_planned;
bool save_log_statement_stats = log_statement_stats;
@@ -1088,12 +1081,12 @@ exec_parse_message(const char *query_string, /* string to execute */
* We have two strategies depending on whether the prepared statement is
* named or not. For a named prepared statement, we do parsing in
* MessageContext and copy the finished trees into the prepared
- * statement's private context; then the reset of MessageContext releases
+ * statement's plancache entry; then the reset of MessageContext releases
* temporary space used by parsing and planning. For an unnamed prepared
* statement, we assume the statement isn't going to hang around long, so
* getting rid of temp space quickly is probably not worth the costs of
- * copying parse/plan trees. So in this case, we set up a special context
- * for the unnamed statement, and do all the parsing work therein.
+ * copying parse/plan trees. So in this case, we create the plancache
+ * entry's context here, and do all the parsing work therein.
*/
is_named = (stmt_name[0] != '\0');
if (is_named)
@@ -1104,16 +1097,10 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Unnamed prepared statement --- release any prior unnamed stmt */
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
- /* create context for parsing/planning */
+ drop_unnamed_stmt();
+ /* Create context for parsing/planning */
unnamed_stmt_context =
- AllocSetContextCreate(TopMemoryContext,
+ AllocSetContextCreate(CacheMemoryContext,
"unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
@@ -1121,8 +1108,6 @@ exec_parse_message(const char *query_string, /* string to execute */
oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
}
- QueryContext = CurrentMemoryContext;
-
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@@ -1141,13 +1126,14 @@ exec_parse_message(const char *query_string, /* string to execute */
if (parsetree_list != NIL)
{
- Node *parsetree = (Node *) linitial(parsetree_list);
int i;
+ raw_parse_tree = (Node *) linitial(parsetree_list);
+
/*
* Get the command name for possible use in status display.
*/
- commandTag = CreateCommandTag(parsetree);
+ commandTag = CreateCommandTag(raw_parse_tree);
/*
* If we are in an aborted transaction, reject all commands except
@@ -1158,7 +1144,7 @@ exec_parse_message(const char *query_string, /* string to execute */
* state, but not many...)
*/
if (IsAbortedTransactionBlockState() &&
- !IsTransactionExitStmt(parsetree))
+ !IsTransactionExitStmt(raw_parse_tree))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@@ -1168,20 +1154,22 @@ exec_parse_message(const char *query_string, /* string to execute */
* OK to analyze, rewrite, and plan this query. Note that the
* originally specified parameter set is not required to be complete,
* so we have to use parse_analyze_varparams().
+ *
+ * XXX must use copyObject here since parse analysis scribbles on
+ * its input, and we need the unmodified raw parse tree for possible
+ * replanning later.
*/
if (log_parser_stats)
ResetUsage();
- querytree_list = parse_analyze_varparams(parsetree,
+ querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree),
query_string,
&paramTypes,
&numParams);
/*
- * Check all parameter types got determined, and convert array
- * representation to a list for storage.
+ * Check all parameter types got determined.
*/
- param_list = NIL;
for (i = 0; i < numParams; i++)
{
Oid ptype = paramTypes[i];
@@ -1191,7 +1179,6 @@ exec_parse_message(const char *query_string, /* string to execute */
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("could not determine data type of parameter $%d",
i + 1)));
- param_list = lappend_oid(param_list, ptype);
}
if (log_parser_stats)
@@ -1217,9 +1204,9 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Empty input string. This is legal. */
+ raw_parse_tree = NULL;
commandTag = NULL;
stmt_list = NIL;
- param_list = NIL;
fully_planned = true;
}
@@ -1232,35 +1219,33 @@ exec_parse_message(const char *query_string, /* string to execute */
if (is_named)
{
StorePreparedStatement(stmt_name,
+ raw_parse_tree,
query_string,
commandTag,
+ paramTypes,
+ numParams,
stmt_list,
- param_list,
- fully_planned,
false);
}
else
{
- PreparedStatement *pstmt;
-
- pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
/* query_string needs to be copied into unnamed_stmt_context */
- pstmt->query_string = pstrdup(query_string);
/* the rest is there already */
- pstmt->commandTag = commandTag;
- pstmt->stmt_list = stmt_list;
- pstmt->argtype_list = param_list;
- pstmt->fully_planned = fully_planned;
- pstmt->from_sql = false;
- pstmt->context = unnamed_stmt_context;
- /* Now the unnamed statement is complete and valid */
- unnamed_stmt_pstmt = pstmt;
+ unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree,
+ pstrdup(query_string),
+ commandTag,
+ paramTypes,
+ numParams,
+ stmt_list,
+ fully_planned,
+ true,
+ unnamed_stmt_context);
+ /* context now belongs to the plancache entry */
+ unnamed_stmt_context = NULL;
}
MemoryContextSwitchTo(oldcontext);
- QueryContext = NULL;
-
/*
* We do NOT close the open transaction command here; that only happens
* when the client sends Sync. Instead, do CommandCounterIncrement just
@@ -1315,12 +1300,11 @@ exec_bind_message(StringInfo input_message)
int numParams;
int numRFormats;
int16 *rformats = NULL;
- PreparedStatement *pstmt;
+ CachedPlanSource *psrc;
+ CachedPlan *cplan;
Portal portal;
ParamListInfo params;
- List *query_list;
List *plan_list;
- MemoryContext qContext;
bool save_log_statement_stats = log_statement_stats;
char msec_str[32];
@@ -1335,12 +1319,17 @@ exec_bind_message(StringInfo input_message)
/* Find prepared statement */
if (stmt_name[0] != '\0')
+ {
+ PreparedStatement *pstmt;
+
pstmt = FetchPreparedStatement(stmt_name, true);
+ psrc = pstmt->plansource;
+ }
else
{
/* special-case the unnamed statement */
- pstmt = unnamed_stmt_pstmt;
- if (!pstmt)
+ psrc = unnamed_stmt_psrc;
+ if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
@@ -1349,7 +1338,7 @@ exec_bind_message(StringInfo input_message)
/*
* Report query to various monitoring facilities.
*/
- debug_query_string = pstmt->query_string ? pstmt->query_string : "<BIND>";
+ debug_query_string = psrc->query_string ? psrc->query_string : "<BIND>";
pgstat_report_activity(debug_query_string);
@@ -1388,11 +1377,11 @@ exec_bind_message(StringInfo input_message)
errmsg("bind message has %d parameter formats but %d parameters",
numPFormats, numParams)));
- if (numParams != list_length(pstmt->argtype_list))
+ if (numParams != psrc->num_params)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
- numParams, stmt_name, list_length(pstmt->argtype_list))));
+ numParams, stmt_name, psrc->num_params)));
/*
* If we are in aborted transaction state, the only portals we can
@@ -1403,7 +1392,7 @@ exec_bind_message(StringInfo input_message)
* functions.
*/
if (IsAbortedTransactionBlockState() &&
- (!IsTransactionExitStmtList(pstmt->stmt_list) ||
+ (!IsTransactionExitStmt(psrc->raw_parse_tree) ||
numParams != 0))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
@@ -1424,7 +1413,6 @@ exec_bind_message(StringInfo input_message)
*/
if (numParams > 0)
{
- ListCell *l;
MemoryContext oldContext;
int paramno;
@@ -1435,10 +1423,9 @@ exec_bind_message(StringInfo input_message)
(numParams - 1) *sizeof(ParamExternData));
params->numParams = numParams;
- paramno = 0;
- foreach(l, pstmt->argtype_list)
+ for (paramno = 0; paramno < numParams; paramno++)
{
- Oid ptype = lfirst_oid(l);
+ Oid ptype = psrc->param_types[paramno];
int32 plength;
Datum pval;
bool isNull;
@@ -1554,8 +1541,6 @@ exec_bind_message(StringInfo input_message)
*/
params->params[paramno].pflags = PARAM_FLAG_CONST;
params->params[paramno].ptype = ptype;
-
- paramno++;
}
MemoryContextSwitchTo(oldContext);
@@ -1576,46 +1561,62 @@ exec_bind_message(StringInfo input_message)
pq_getmsgend(input_message);
- /*
- * If we didn't plan the query before, do it now. This allows the planner
- * to make use of the concrete parameter values we now have. Because we
- * use PARAM_FLAG_CONST, the plan is good only for this set of param
- * values, and so we generate the plan in the portal's own memory context
- * where it will be thrown away after use. As in exec_parse_message, we
- * make no attempt to recover planner temporary memory until the end of
- * the operation.
- *
- * XXX because the planner has a bad habit of scribbling on its input, we
- * have to make a copy of the parse trees, just in case someone binds and
- * executes an unnamed statement multiple times; this also means that the
- * portal's queryContext becomes its own heap context rather than the
- * prepared statement's context. FIXME someday
- */
- if (pstmt->fully_planned)
+ if (psrc->fully_planned)
{
- plan_list = pstmt->stmt_list;
- qContext = pstmt->context;
+ /*
+ * Revalidate the cached plan; this may result in replanning. Any
+ * cruft will be generated in MessageContext. The plan refcount
+ * will be assigned to the Portal, so it will be released at portal
+ * destruction.
+ */
+ cplan = RevalidateCachedPlan(psrc, false);
+ plan_list = cplan->stmt_list;
}
else
{
MemoryContext oldContext;
+ List *query_list;
+
+ /*
+ * Revalidate the cached plan; this may result in redoing parse
+ * analysis and rewriting (but not planning). Any cruft will be
+ * generated in MessageContext. The plan refcount is assigned to
+ * CurrentResourceOwner.
+ */
+ cplan = RevalidateCachedPlan(psrc, true);
- qContext = PortalGetHeapMemory(portal);
- oldContext = MemoryContextSwitchTo(qContext);
- query_list = copyObject(pstmt->stmt_list);
+ /*
+ * We didn't plan the query before, so do it now. This allows the
+ * planner to make use of the concrete parameter values we now have.
+ * Because we use PARAM_FLAG_CONST, the plan is good only for this set
+ * of param values, and so we generate the plan in the portal's own
+ * memory context where it will be thrown away after use. As in
+ * exec_parse_message, we make no attempt to recover planner temporary
+ * memory until the end of the operation.
+ *
+ * XXX because the planner has a bad habit of scribbling on its input,
+ * we have to make a copy of the parse trees. FIXME someday.
+ */
+ oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
+ query_list = copyObject(cplan->stmt_list);
plan_list = pg_plan_queries(query_list, params, true);
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;
}
/*
* Define portal and start execution.
*/
PortalDefineQuery(portal,
- *pstmt->stmt_name ? pstmt->stmt_name : NULL,
- pstmt->query_string,
- pstmt->commandTag,
+ stmt_name[0] ? stmt_name : NULL,
+ psrc->query_string,
+ psrc->commandTag,
plan_list,
- qContext);
+ cplan);
PortalStart(portal, params, InvalidSnapshot);
@@ -1647,7 +1648,7 @@ exec_bind_message(StringInfo input_message)
*stmt_name ? stmt_name : "<unnamed>",
*portal_name ? "/" : "",
*portal_name ? portal_name : "",
- pstmt->query_string ? pstmt->query_string : "<source not stored>"),
+ psrc->query_string ? psrc->query_string : "<source not stored>"),
errhidestmt(true),
errdetail_params(params)));
break;
@@ -1809,6 +1810,7 @@ exec_execute_message(const char *portal_name, long max_rows)
completed = PortalRun(portal,
max_rows,
+ true, /* top level */
receiver,
receiver,
completionTag);
@@ -1981,9 +1983,9 @@ errdetail_execute(List *raw_parsetree_list)
PreparedStatement *pstmt;
pstmt = FetchPreparedStatement(stmt->name, false);
- if (pstmt && pstmt->query_string)
+ if (pstmt && pstmt->plansource->query_string)
{
- errdetail("prepare: %s", pstmt->query_string);
+ errdetail("prepare: %s", pstmt->plansource->query_string);
return 0;
}
}
@@ -2064,10 +2066,9 @@ errdetail_params(ParamListInfo params)
static void
exec_describe_statement_message(const char *stmt_name)
{
- PreparedStatement *pstmt;
- TupleDesc tupdesc;
- ListCell *l;
+ CachedPlanSource *psrc;
StringInfoData buf;
+ int i;
/*
* Start up a transaction command. (Note that this will normally change
@@ -2080,28 +2081,37 @@ exec_describe_statement_message(const char *stmt_name)
/* Find prepared statement */
if (stmt_name[0] != '\0')
+ {
+ PreparedStatement *pstmt;
+
pstmt = FetchPreparedStatement(stmt_name, true);
+ psrc = pstmt->plansource;
+ }
else
{
/* special-case the unnamed statement */
- pstmt = unnamed_stmt_pstmt;
- if (!pstmt)
+ psrc = unnamed_stmt_psrc;
+ if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
}
+ /* Prepared statements shouldn't have changeable result descs */
+ Assert(psrc->fixed_result);
+
/*
- * If we are in aborted transaction state, we can't safely create a result
- * tupledesc, because that needs catalog accesses. Hence, refuse to
- * Describe statements that return data. (We shouldn't just refuse all
- * Describes, since that might break the ability of some clients to issue
- * COMMIT or ROLLBACK commands, if they use code that blindly Describes
- * whatever it does.) We can Describe parameters without doing anything
- * dangerous, so we don't restrict that.
+ * If we are in aborted transaction state, we can't run
+ * SendRowDescriptionMessage(), because that needs catalog accesses.
+ * (We can't do RevalidateCachedPlan, either, but that's a lesser problem.)
+ * Hence, refuse to Describe statements that return data. (We shouldn't
+ * just refuse all Describes, since that might break the ability of some
+ * clients to issue COMMIT or ROLLBACK commands, if they use code that
+ * blindly Describes whatever it does.) We can Describe parameters
+ * without doing anything dangerous, so we don't restrict that.
*/
if (IsAbortedTransactionBlockState() &&
- PreparedStatementReturnsTuples(pstmt))
+ psrc->resultDesc)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@@ -2114,11 +2124,11 @@ exec_describe_statement_message(const char *stmt_name)
* First describe the parameters...
*/
pq_beginmessage(&buf, 't'); /* parameter description message type */
- pq_sendint(&buf, list_length(pstmt->argtype_list), 2);
+ pq_sendint(&buf, psrc->num_params, 2);
- foreach(l, pstmt->argtype_list)
+ for (i = 0; i < psrc->num_params; i++)
{
- Oid ptype = lfirst_oid(l);
+ Oid ptype = psrc->param_types[i];
pq_sendint(&buf, (int) ptype, 4);
}
@@ -2127,11 +2137,21 @@ exec_describe_statement_message(const char *stmt_name)
/*
* Next send RowDescription or NoData to describe the result...
*/
- tupdesc = FetchPreparedStatementResultDesc(pstmt);
- if (tupdesc)
- SendRowDescriptionMessage(tupdesc,
- FetchPreparedStatementTargetList(pstmt),
- NULL);
+ if (psrc->resultDesc)
+ {
+ CachedPlan *cplan;
+ List *tlist;
+
+ /* Make sure the plan is up to date */
+ cplan = RevalidateCachedPlan(psrc, true);
+
+ /* Get the primary statement and find out what it returns */
+ tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
+
+ SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
+
+ ReleaseCachedPlan(cplan, true);
+ }
else
pq_putemptymessage('n'); /* NoData */
@@ -2308,6 +2328,24 @@ IsTransactionStmtList(List *parseTrees)
return false;
}
+/* Release any existing unnamed prepared statement */
+static void
+drop_unnamed_stmt(void)
+{
+ /* Release any completed unnamed statement */
+ if (unnamed_stmt_psrc)
+ DropCachedPlan(unnamed_stmt_psrc);
+ unnamed_stmt_psrc = NULL;
+ /*
+ * If we failed while trying to build a prior unnamed statement, we may
+ * have a memory context that wasn't assigned to a completed plancache
+ * entry. If so, drop it to avoid a permanent memory leak.
+ */
+ if (unnamed_stmt_context)
+ MemoryContextDelete(unnamed_stmt_context);
+ unnamed_stmt_context = NULL;
+}
+
/* --------------------------------
* signal handler routines used in PostgresMain()
@@ -3313,7 +3351,6 @@ PostgresMain(int argc, char *argv[], const char *username)
*/
MemoryContextSwitchTo(TopMemoryContext);
FlushErrorState();
- QueryContext = NULL;
/*
* If we were handling an extended-query-protocol message, initiate
@@ -3558,13 +3595,7 @@ PostgresMain(int argc, char *argv[], const char *username)
else
{
/* special-case the unnamed statement */
- unnamed_stmt_pstmt = NULL;
- if (unnamed_stmt_context)
- {
- DropDependentPortals(unnamed_stmt_context);
- MemoryContextDelete(unnamed_stmt_context);
- }
- unnamed_stmt_context = NULL;
+ drop_unnamed_stmt();
}
break;
case 'P':
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 97a003ac89d..b54dea45dca 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.114 2007/02/20 17:32:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.115 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -36,14 +36,14 @@ static void ProcessQuery(PlannedStmt *plan,
ParamListInfo params,
DestReceiver *dest,
char *completionTag);
-static void FillPortalStore(Portal portal);
+static void FillPortalStore(Portal portal, bool isTopLevel);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
DestReceiver *dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
DestReceiver *dest);
-static void PortalRunUtility(Portal portal, Node *utilityStmt,
+static void PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
DestReceiver *dest, char *completionTag);
-static void PortalRunMulti(Portal portal,
+static void PortalRunMulti(Portal portal, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag);
static long DoPortalRunFetch(Portal portal,
@@ -148,8 +148,7 @@ ProcessQuery(PlannedStmt *plan,
{
QueryDesc *queryDesc;
- ereport(DEBUG3,
- (errmsg_internal("ProcessQuery")));
+ elog(DEBUG3, "ProcessQuery");
/*
* Must always set snapshot for plannable queries. Note we assume that
@@ -232,8 +231,7 @@ ProcessQuery(PlannedStmt *plan,
* Select portal execution strategy given the intended statement list.
*
* The list elements can be Querys, PlannedStmts, or utility statements.
- * That's more general than portals need, but we use this for prepared
- * statements as well.
+ * That's more general than portals need, but plancache.c uses this too.
*
* See the comments in portal.h.
*/
@@ -358,8 +356,7 @@ FetchPortalTargetList(Portal portal)
* Returns NIL if the statement doesn't have a determinable targetlist.
*
* This can be applied to a Query, a PlannedStmt, or a utility statement.
- * That's more general than portals need, but we use this for prepared
- * statements as well.
+ * That's more general than portals need, but plancache.c uses this too.
*
* Note: do not modify the result.
*
@@ -452,11 +449,10 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
int eflags;
AssertArg(PortalIsValid(portal));
- AssertState(portal->queryContext != NULL); /* query defined? */
- AssertState(portal->status == PORTAL_NEW); /* else extra PortalStart */
+ AssertState(portal->status == PORTAL_DEFINED);
/*
- * Set up global portal context pointers. (Should we set QueryContext?)
+ * Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
saveActiveSnapshot = ActiveSnapshot;
@@ -683,6 +679,9 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
* interpreted as "all rows". Note that count is ignored in multi-query
* situations, where we always run the portal to completion.
*
+ * isTopLevel: true if query is being executed at backend "top level"
+ * (that is, directly from a client command message)
+ *
* dest: where to send output of primary (canSetTag) query
*
* altdest: where to send output of non-primary queries
@@ -695,7 +694,7 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
* suspended due to exhaustion of the count parameter.
*/
bool
-PortalRun(Portal portal, long count,
+PortalRun(Portal portal, long count, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag)
{
@@ -706,7 +705,6 @@ PortalRun(Portal portal, long count,
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
- MemoryContext saveQueryContext;
MemoryContext saveMemoryContext;
AssertArg(PortalIsValid(portal));
@@ -717,8 +715,7 @@ PortalRun(Portal portal, long count,
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
{
- ereport(DEBUG3,
- (errmsg_internal("PortalRun")));
+ elog(DEBUG3, "PortalRun");
/* PORTAL_MULTI_QUERY logs its own stats per query */
ResetUsage();
}
@@ -752,7 +749,6 @@ PortalRun(Portal portal, long count,
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
- saveQueryContext = QueryContext;
saveMemoryContext = CurrentMemoryContext;
PG_TRY();
{
@@ -760,7 +756,6 @@ PortalRun(Portal portal, long count,
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
- QueryContext = portal->queryContext;
MemoryContextSwitchTo(PortalContext);
@@ -790,7 +785,7 @@ PortalRun(Portal portal, long count,
* results in the portal's tuplestore.
*/
if (!portal->holdStore)
- FillPortalStore(portal);
+ FillPortalStore(portal, isTopLevel);
/*
* Now fetch desired portion of results.
@@ -811,7 +806,8 @@ PortalRun(Portal portal, long count,
break;
case PORTAL_MULTI_QUERY:
- PortalRunMulti(portal, dest, altdest, completionTag);
+ PortalRunMulti(portal, isTopLevel,
+ dest, altdest, completionTag);
/* Prevent portal's commands from being re-executed */
portal->status = PORTAL_DONE;
@@ -844,7 +840,6 @@ PortalRun(Portal portal, long count,
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
PG_RE_THROW();
}
@@ -861,7 +856,6 @@ PortalRun(Portal portal, long count,
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
ShowUsage("EXECUTOR STATISTICS");
@@ -1025,7 +1019,7 @@ PortalRunSelect(Portal portal,
* This is used for PORTAL_ONE_RETURNING and PORTAL_UTIL_SELECT cases only.
*/
static void
-FillPortalStore(Portal portal)
+FillPortalStore(Portal portal, bool isTopLevel)
{
DestReceiver *treceiver;
char completionTag[COMPLETION_TAG_BUFSIZE];
@@ -1044,12 +1038,13 @@ FillPortalStore(Portal portal)
* MULTI_QUERY case, but send the primary query's output to the
* tuplestore. Auxiliary query outputs are discarded.
*/
- PortalRunMulti(portal, treceiver, None_Receiver, completionTag);
+ PortalRunMulti(portal, isTopLevel,
+ treceiver, None_Receiver, completionTag);
break;
case PORTAL_UTIL_SELECT:
PortalRunUtility(portal, (Node *) linitial(portal->stmts),
- treceiver, completionTag);
+ isTopLevel, treceiver, completionTag);
break;
default:
@@ -1137,11 +1132,10 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
* Execute a utility statement inside a portal.
*/
static void
-PortalRunUtility(Portal portal, Node *utilityStmt,
+PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
DestReceiver *dest, char *completionTag)
{
- ereport(DEBUG3,
- (errmsg_internal("ProcessUtility")));
+ elog(DEBUG3, "ProcessUtility");
/*
* Set snapshot if utility stmt needs one. Most reliable way to do this
@@ -1173,7 +1167,12 @@ PortalRunUtility(Portal portal, Node *utilityStmt,
else
ActiveSnapshot = NULL;
- ProcessUtility(utilityStmt, portal->portalParams, dest, completionTag);
+ ProcessUtility(utilityStmt,
+ portal->sourceText,
+ portal->portalParams,
+ isTopLevel,
+ dest,
+ completionTag);
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
@@ -1189,7 +1188,7 @@ PortalRunUtility(Portal portal, Node *utilityStmt,
* or non-SELECT-like queries)
*/
static void
-PortalRunMulti(Portal portal,
+PortalRunMulti(Portal portal, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag)
{
@@ -1260,9 +1259,9 @@ PortalRunMulti(Portal portal,
* portal.
*/
if (list_length(portal->stmts) == 1)
- PortalRunUtility(portal, stmt, dest, completionTag);
+ PortalRunUtility(portal, stmt, isTopLevel, dest, completionTag);
else
- PortalRunUtility(portal, stmt, altdest, NULL);
+ PortalRunUtility(portal, stmt, isTopLevel, altdest, NULL);
}
/*
@@ -1305,6 +1304,8 @@ PortalRunMulti(Portal portal,
* PortalRunFetch
* Variant form of PortalRun that supports SQL FETCH directions.
*
+ * Note: we presently assume that no callers of this want isTopLevel = true.
+ *
* Returns number of rows processed (suitable for use in result tag)
*/
long
@@ -1318,7 +1319,6 @@ PortalRunFetch(Portal portal,
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
- MemoryContext saveQueryContext;
MemoryContext oldContext;
AssertArg(PortalIsValid(portal));
@@ -1339,14 +1339,12 @@ PortalRunFetch(Portal portal,
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
- saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
- QueryContext = portal->queryContext;
oldContext = MemoryContextSwitchTo(PortalContext);
@@ -1364,7 +1362,7 @@ PortalRunFetch(Portal portal,
* results in the portal's tuplestore.
*/
if (!portal->holdStore)
- FillPortalStore(portal);
+ FillPortalStore(portal, false /* isTopLevel */);
/*
* Now fetch desired portion of results.
@@ -1388,7 +1386,6 @@ PortalRunFetch(Portal portal,
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
PG_RE_THROW();
}
@@ -1403,7 +1400,6 @@ PortalRunFetch(Portal portal,
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
- QueryContext = saveQueryContext;
return result;
}
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 47051ad1ed2..be274a72f10 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.273 2007/02/20 17:32:16 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.274 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -44,6 +44,7 @@
#include "commands/vacuum.h"
#include "commands/view.h"
#include "miscadmin.h"
+#include "parser/analyze.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
@@ -368,7 +369,9 @@ check_xact_readonly(Node *parsetree)
* general utility function invoker
*
* parsetree: the parse tree for the utility statement
+ * queryString: original source text of command (NULL if not available)
* params: parameters to use during execution
+ * isTopLevel: true if executing a "top level" (interactively issued) command
* dest: where to send results
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
@@ -379,7 +382,9 @@ check_xact_readonly(Node *parsetree)
*/
void
ProcessUtility(Node *parsetree,
+ const char *queryString,
ParamListInfo params,
+ bool isTopLevel,
DestReceiver *dest,
char *completionTag)
{
@@ -444,12 +449,12 @@ ProcessUtility(Node *parsetree,
break;
case TRANS_STMT_COMMIT_PREPARED:
- PreventTransactionChain(stmt, "COMMIT PREPARED");
+ PreventTransactionChain(isTopLevel, "COMMIT PREPARED");
FinishPreparedTransaction(stmt->gid, true);
break;
case TRANS_STMT_ROLLBACK_PREPARED:
- PreventTransactionChain(stmt, "ROLLBACK PREPARED");
+ PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED");
FinishPreparedTransaction(stmt->gid, false);
break;
@@ -462,7 +467,7 @@ ProcessUtility(Node *parsetree,
ListCell *cell;
char *name = NULL;
- RequireTransactionChain((void *) stmt, "SAVEPOINT");
+ RequireTransactionChain(isTopLevel, "SAVEPOINT");
foreach(cell, stmt->options)
{
@@ -479,12 +484,12 @@ ProcessUtility(Node *parsetree,
break;
case TRANS_STMT_RELEASE:
- RequireTransactionChain((void *) stmt, "RELEASE SAVEPOINT");
+ RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT");
ReleaseSavepoint(stmt->options);
break;
case TRANS_STMT_ROLLBACK_TO:
- RequireTransactionChain((void *) stmt, "ROLLBACK TO SAVEPOINT");
+ RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT");
RollbackToSavepoint(stmt->options);
/*
@@ -500,7 +505,8 @@ ProcessUtility(Node *parsetree,
* Portal (cursor) manipulation
*/
case T_DeclareCursorStmt:
- PerformCursorOpen((DeclareCursorStmt *) parsetree, params);
+ PerformCursorOpen((DeclareCursorStmt *) parsetree, params,
+ queryString, isTopLevel);
break;
case T_ClosePortalStmt:
@@ -520,7 +526,8 @@ ProcessUtility(Node *parsetree,
* relation and attribute manipulation
*/
case T_CreateSchemaStmt:
- CreateSchemaCommand((CreateSchemaStmt *) parsetree);
+ CreateSchemaCommand((CreateSchemaStmt *) parsetree,
+ queryString);
break;
case T_CreateStmt:
@@ -540,10 +547,12 @@ ProcessUtility(Node *parsetree,
break;
case T_CreateTableSpaceStmt:
+ PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt *) parsetree);
break;
case T_DropTableSpaceStmt:
+ PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt *) parsetree);
break;
@@ -640,8 +649,9 @@ ProcessUtility(Node *parsetree,
case T_CopyStmt:
{
- uint64 processed = DoCopy((CopyStmt *) parsetree);
+ uint64 processed;
+ processed = DoCopy((CopyStmt *) parsetree, queryString);
if (completionTag)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"COPY " UINT64_FORMAT, processed);
@@ -649,11 +659,11 @@ ProcessUtility(Node *parsetree,
break;
case T_PrepareStmt:
- PrepareQuery((PrepareStmt *) parsetree);
+ PrepareQuery((PrepareStmt *) parsetree, queryString);
break;
case T_ExecuteStmt:
- ExecuteQuery((ExecuteStmt *) parsetree, params,
+ ExecuteQuery((ExecuteStmt *) parsetree, queryString, params,
dest, completionTag);
break;
@@ -769,12 +779,8 @@ ProcessUtility(Node *parsetree,
}
break;
- case T_ViewStmt: /* CREATE VIEW */
- {
- ViewStmt *stmt = (ViewStmt *) parsetree;
-
- DefineView(stmt->view, stmt->query, stmt->replace);
- }
+ case T_ViewStmt: /* CREATE VIEW */
+ DefineView((ViewStmt *) parsetree, queryString);
break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */
@@ -790,10 +796,15 @@ ProcessUtility(Node *parsetree,
IndexStmt *stmt = (IndexStmt *) parsetree;
if (stmt->concurrent)
- PreventTransactionChain(stmt, "CREATE INDEX CONCURRENTLY");
+ PreventTransactionChain(isTopLevel,
+ "CREATE INDEX CONCURRENTLY");
CheckRelationOwnership(stmt->relation, true);
+ /* Run parse analysis ... */
+ stmt = analyzeIndexStmt(stmt, queryString);
+
+ /* ... and do it */
DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
@@ -801,7 +812,6 @@ ProcessUtility(Node *parsetree,
stmt->tableSpace,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
- stmt->rangetable,
stmt->options,
stmt->unique,
stmt->primary,
@@ -815,7 +825,7 @@ ProcessUtility(Node *parsetree,
break;
case T_RuleStmt: /* CREATE RULE */
- DefineQueryRewrite((RuleStmt *) parsetree);
+ DefineRule((RuleStmt *) parsetree, queryString);
break;
case T_CreateSeqStmt:
@@ -850,6 +860,7 @@ ProcessUtility(Node *parsetree,
break;
case T_CreatedbStmt:
+ PreventTransactionChain(isTopLevel, "CREATE DATABASE");
createdb((CreatedbStmt *) parsetree);
break;
@@ -865,6 +876,7 @@ ProcessUtility(Node *parsetree,
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ PreventTransactionChain(isTopLevel, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok);
}
break;
@@ -905,15 +917,15 @@ ProcessUtility(Node *parsetree,
break;
case T_ClusterStmt:
- cluster((ClusterStmt *) parsetree);
+ cluster((ClusterStmt *) parsetree, isTopLevel);
break;
case T_VacuumStmt:
- vacuum((VacuumStmt *) parsetree, NIL);
+ vacuum((VacuumStmt *) parsetree, NIL, isTopLevel);
break;
case T_ExplainStmt:
- ExplainQuery((ExplainStmt *) parsetree, params, dest);
+ ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
case T_VariableSetStmt:
@@ -1079,6 +1091,14 @@ ProcessUtility(Node *parsetree,
ReindexTable(stmt->relation);
break;
case OBJECT_DATABASE:
+ /*
+ * This cannot run inside a user transaction block;
+ * if we were inside a transaction, then its commit-
+ * and start-transaction-command calls would not have
+ * the intended effect!
+ */
+ PreventTransactionChain(isTopLevel,
+ "REINDEX DATABASE");
ReindexDatabase(stmt->name,
stmt->do_system, stmt->do_user);
break;
@@ -1166,16 +1186,8 @@ UtilityReturnsTuples(Node *parsetree)
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return false; /* not our business to raise error */
- switch (ChoosePortalStrategy(entry->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;
- }
+ if (entry->plansource->resultDesc)
+ return true;
return false;
}
@@ -2134,7 +2146,7 @@ GetCommandLogLevel(Node *parsetree)
/* Look through an EXPLAIN ANALYZE to the contained stmt */
if (stmt->analyze)
- return GetCommandLogLevel((Node *) stmt->query);
+ return GetCommandLogLevel(stmt->query);
/* Plain EXPLAIN isn't so interesting */
lev = LOGSTMT_ALL;
}
@@ -2245,30 +2257,21 @@ GetCommandLogLevel(Node *parsetree)
PrepareStmt *stmt = (PrepareStmt *) parsetree;
/* Look through a PREPARE to the contained stmt */
- return GetCommandLogLevel((Node *) stmt->query);
+ lev = GetCommandLogLevel(stmt->query);
}
break;
case T_ExecuteStmt:
{
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
- PreparedStatement *pstmt;
- ListCell *l;
-
- /* Look through an EXECUTE to the referenced stmt(s) */
- lev = LOGSTMT_ALL;
- pstmt = FetchPreparedStatement(stmt->name, false);
- if (pstmt)
- {
- foreach(l, pstmt->stmt_list)
- {
- Node *substmt = (Node *) lfirst(l);
- LogStmtLevel stmt_lev;
+ PreparedStatement *ps;
- stmt_lev = GetCommandLogLevel(substmt);
- lev = Min(lev, stmt_lev);
- }
- }
+ /* Look through an EXECUTE to the referenced stmt */
+ ps = FetchPreparedStatement(stmt->name, false);
+ if (ps)
+ lev = GetCommandLogLevel(ps->plansource->raw_parse_tree);
+ else
+ lev = LOGSTMT_ALL;
}
break;
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index 18e920bc1df..879f013e335 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -4,7 +4,7 @@
# Makefile for utils/cache
#
# IDENTIFICATION
-# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.20 2007/01/20 17:16:13 petere Exp $
+# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.21 2007/03/13 00:33:42 tgl Exp $
#
#-------------------------------------------------------------------------
@@ -12,7 +12,8 @@ subdir = src/backend/utils/cache
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = catcache.o inval.o relcache.o syscache.o lsyscache.o typcache.o
+OBJS = catcache.o inval.o plancache.o relcache.o \
+ syscache.o lsyscache.o typcache.o
all: SUBSYS.o
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
new file mode 100644
index 00000000000..95ed49818cb
--- /dev/null
+++ b/src/backend/utils/cache/plancache.c
@@ -0,0 +1,862 @@
+/*-------------------------------------------------------------------------
+ *
+ * plancache.c
+ * Plan cache management.
+ *
+ * We can store a cached plan in either fully-planned format, or just
+ * parsed-and-rewritten if the caller wishes to postpone planning until
+ * actual parameter values are available. CachedPlanSource has the same
+ * contents either way, but CachedPlan contains a list of PlannedStmts
+ * and bare utility statements in the first case, or a list of Query nodes
+ * in the second case.
+ *
+ * The plan cache manager itself is principally responsible for tracking
+ * whether cached plans should be invalidated because of schema changes in
+ * the tables they depend on. When (and if) the next demand for a cached
+ * plan occurs, the query will be replanned. Note that this could result
+ * in an error, for example if a column referenced by the query is no
+ * longer present. The creator of a cached plan can specify whether it
+ * is allowable for the query to change output tupdesc on replan (this
+ * could happen with "SELECT *" for example) --- if so, it's up to the
+ * caller to notice changes and cope with them.
+ *
+ * Currently, we use only relcache invalidation events to invalidate plans.
+ * This means that changes such as modification of a function definition do
+ * not invalidate plans using the function. This is not 100% OK --- for
+ * example, changing a SQL function that's been inlined really ought to
+ * cause invalidation of the plan that it's been inlined into --- but the
+ * cost of tracking additional types of object seems much higher than the
+ * gain, so we're just ignoring them for now.
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.1 2007/03/13 00:33:42 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "utils/plancache.h"
+#include "executor/executor.h"
+#include "optimizer/clauses.h"
+#include "storage/lmgr.h"
+#include "tcop/pquery.h"
+#include "tcop/tcopprot.h"
+#include "tcop/utility.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/resowner.h"
+
+
+typedef struct
+{
+ void (*callback) ();
+ void *arg;
+} ScanQueryWalkerContext;
+
+typedef struct
+{
+ Oid inval_relid;
+ CachedPlan *plan;
+} InvalRelidContext;
+
+
+static List *cached_plans_list = NIL;
+
+static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list,
+ MemoryContext plan_context);
+static void AcquireExecutorLocks(List *stmt_list, bool acquire);
+static void AcquirePlannerLocks(List *stmt_list, bool acquire);
+static void LockRelid(Oid relid, LOCKMODE lockmode, void *arg);
+static void UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg);
+static void ScanQueryForRelids(Query *parsetree,
+ void (*callback) (),
+ void *arg);
+static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context);
+static bool rowmark_member(List *rowMarks, int rt_index);
+static TupleDesc ComputeResultDesc(List *stmt_list);
+static void PlanCacheCallback(Datum arg, Oid relid);
+static void InvalRelid(Oid relid, LOCKMODE lockmode,
+ InvalRelidContext *context);
+
+
+/*
+ * InitPlanCache: initialize module during InitPostgres.
+ *
+ * All we need to do is hook into inval.c's callback list.
+ */
+void
+InitPlanCache(void)
+{
+ CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0);
+}
+
+/*
+ * CreateCachedPlan: initially create a plan cache entry.
+ *
+ * The caller must already have successfully parsed/planned the query;
+ * about all that we do here is copy it into permanent storage.
+ *
+ * raw_parse_tree: output of raw_parser()
+ * query_string: original query text (can be NULL if not available, but
+ * that is discouraged because it degrades error message quality)
+ * commandTag: compile-time-constant tag for query, or NULL if empty query
+ * param_types: array of parameter type OIDs, or NULL if none
+ * num_params: number of parameters
+ * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees
+ * fully_planned: are we caching planner or rewriter output?
+ * fixed_result: TRUE to disallow changes in result tupdesc
+ */
+CachedPlanSource *
+CreateCachedPlan(Node *raw_parse_tree,
+ const char *query_string,
+ const char *commandTag,
+ Oid *param_types,
+ int num_params,
+ List *stmt_list,
+ bool fully_planned,
+ bool fixed_result)
+{
+ CachedPlanSource *plansource;
+ MemoryContext source_context;
+ MemoryContext oldcxt;
+
+ /*
+ * Make a dedicated memory context for the CachedPlanSource and its
+ * subsidiary data. We expect it can be pretty small.
+ */
+ source_context = AllocSetContextCreate(CacheMemoryContext,
+ "CachedPlanSource",
+ ALLOCSET_SMALL_MINSIZE,
+ ALLOCSET_SMALL_INITSIZE,
+ ALLOCSET_SMALL_MAXSIZE);
+
+ /*
+ * Create and fill the CachedPlanSource struct within the new context.
+ */
+ oldcxt = MemoryContextSwitchTo(source_context);
+ plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
+ plansource->raw_parse_tree = copyObject(raw_parse_tree);
+ plansource->query_string = query_string ? pstrdup(query_string) : NULL;
+ plansource->commandTag = commandTag; /* no copying needed */
+ if (num_params > 0)
+ {
+ plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid));
+ memcpy(plansource->param_types, param_types, num_params * sizeof(Oid));
+ }
+ else
+ plansource->param_types = NULL;
+ plansource->num_params = num_params;
+ plansource->fully_planned = fully_planned;
+ plansource->fixed_result = fixed_result;
+ plansource->generation = 0; /* StoreCachedPlan will increment */
+ plansource->resultDesc = ComputeResultDesc(stmt_list);
+ plansource->plan = NULL;
+ plansource->context = source_context;
+ plansource->orig_plan = NULL;
+
+ /*
+ * Copy the current output plans into the plancache entry.
+ */
+ StoreCachedPlan(plansource, stmt_list, NULL);
+
+ /*
+ * Now we can add the entry to the list of cached plans. The List nodes
+ * live in CacheMemoryContext.
+ */
+ MemoryContextSwitchTo(CacheMemoryContext);
+
+ cached_plans_list = lappend(cached_plans_list, plansource);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return plansource;
+}
+
+/*
+ * FastCreateCachedPlan: create a plan cache entry with minimal data copying.
+ *
+ * For plans that aren't expected to live very long, the copying overhead of
+ * CreateCachedPlan is annoying. We provide this variant entry point in which
+ * the caller has already placed all the data in a suitable memory context.
+ * The source data and completed plan are in the same context, since this
+ * avoids extra copy steps during plan construction. If the query ever does
+ * need replanning, we'll generate a separate new CachedPlan at that time, but
+ * the CachedPlanSource and the initial CachedPlan share the caller-provided
+ * context and go away together when neither is needed any longer. (Because
+ * the parser and planner generate extra cruft in addition to their real
+ * output, this approach means that the context probably contains a bunch of
+ * useless junk as well as the useful trees. Hence, this method is a
+ * space-for-time tradeoff, which is worth making for plans expected to be
+ * short-lived.)
+ *
+ * raw_parse_tree, query_string, param_types, and stmt_list must reside in the
+ * given context, which must have adequate lifespan (recommendation: make it a
+ * child of CacheMemoryContext). Otherwise the API is the same as
+ * CreateCachedPlan.
+ */
+CachedPlanSource *
+FastCreateCachedPlan(Node *raw_parse_tree,
+ char *query_string,
+ const char *commandTag,
+ Oid *param_types,
+ int num_params,
+ List *stmt_list,
+ bool fully_planned,
+ bool fixed_result,
+ MemoryContext context)
+{
+ CachedPlanSource *plansource;
+ MemoryContext oldcxt;
+
+ /*
+ * Create and fill the CachedPlanSource struct within the given context.
+ */
+ oldcxt = MemoryContextSwitchTo(context);
+ plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource));
+ plansource->raw_parse_tree = raw_parse_tree;
+ plansource->query_string = query_string;
+ plansource->commandTag = commandTag; /* no copying needed */
+ plansource->param_types = param_types;
+ plansource->num_params = num_params;
+ plansource->fully_planned = fully_planned;
+ plansource->fixed_result = fixed_result;
+ plansource->generation = 0; /* StoreCachedPlan will increment */
+ plansource->resultDesc = ComputeResultDesc(stmt_list);
+ plansource->plan = NULL;
+ plansource->context = context;
+ plansource->orig_plan = NULL;
+
+ /*
+ * Store the current output plans into the plancache entry.
+ */
+ StoreCachedPlan(plansource, stmt_list, context);
+
+ /*
+ * Since the context is owned by the CachedPlan, advance its refcount.
+ */
+ plansource->orig_plan = plansource->plan;
+ plansource->orig_plan->refcount++;
+
+ /*
+ * Now we can add the entry to the list of cached plans. The List nodes
+ * live in CacheMemoryContext.
+ */
+ MemoryContextSwitchTo(CacheMemoryContext);
+
+ cached_plans_list = lappend(cached_plans_list, plansource);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ return plansource;
+}
+
+/*
+ * StoreCachedPlan: store a built or rebuilt plan into a plancache entry.
+ *
+ * Common subroutine for CreateCachedPlan and RevalidateCachedPlan.
+ */
+static void
+StoreCachedPlan(CachedPlanSource *plansource,
+ List *stmt_list,
+ MemoryContext plan_context)
+{
+ CachedPlan *plan;
+ MemoryContext oldcxt;
+
+ if (plan_context == NULL)
+ {
+ /*
+ * Make a dedicated memory context for the CachedPlan and its
+ * subsidiary data.
+ */
+ plan_context = AllocSetContextCreate(CacheMemoryContext,
+ "CachedPlan",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+
+ /*
+ * Copy supplied data into the new context.
+ */
+ oldcxt = MemoryContextSwitchTo(plan_context);
+
+ stmt_list = (List *) copyObject(stmt_list);
+ }
+ else
+ {
+ /* Assume subsidiary data is in the given context */
+ oldcxt = MemoryContextSwitchTo(plan_context);
+ }
+
+ /*
+ * Create and fill the CachedPlan struct within the new context.
+ */
+ plan = (CachedPlan *) palloc(sizeof(CachedPlan));
+ plan->stmt_list = stmt_list;
+ plan->fully_planned = plansource->fully_planned;
+ plan->dead = false;
+ plan->refcount = 1; /* for the parent's link */
+ plan->generation = ++(plansource->generation);
+ plan->context = plan_context;
+
+ Assert(plansource->plan == NULL);
+ plansource->plan = plan;
+
+ MemoryContextSwitchTo(oldcxt);
+}
+
+/*
+ * DropCachedPlan: destroy a cached plan.
+ *
+ * Actually this only destroys the CachedPlanSource: the referenced CachedPlan
+ * is released, but not destroyed until its refcount goes to zero. That
+ * handles the situation where DropCachedPlan is called while the plan is
+ * still in use.
+ */
+void
+DropCachedPlan(CachedPlanSource *plansource)
+{
+ /* Validity check that we were given a CachedPlanSource */
+ Assert(list_member_ptr(cached_plans_list, plansource));
+
+ /* Remove it from the list */
+ cached_plans_list = list_delete_ptr(cached_plans_list, plansource);
+
+ /* Decrement child CachePlan's refcount and drop if no longer needed */
+ if (plansource->plan)
+ ReleaseCachedPlan(plansource->plan, false);
+
+ /*
+ * If CachedPlanSource has independent storage, just drop it. Otherwise
+ * decrement the refcount on the CachePlan that owns the storage.
+ */
+ if (plansource->orig_plan == NULL)
+ {
+ /* Remove the CachedPlanSource and all subsidiary data */
+ MemoryContextDelete(plansource->context);
+ }
+ else
+ {
+ Assert(plansource->context == plansource->orig_plan->context);
+ ReleaseCachedPlan(plansource->orig_plan, false);
+ }
+}
+
+/*
+ * RevalidateCachedPlan: prepare for re-use of a previously cached plan.
+ *
+ * What we do here is re-acquire locks and rebuild the plan if necessary.
+ * On return, the plan is valid and we have sufficient locks to begin
+ * execution (or planning, if not fully_planned).
+ *
+ * On return, the refcount of the plan has been incremented; a later
+ * ReleaseCachedPlan() call is expected. The refcount has been reported
+ * to the CurrentResourceOwner if useResOwner is true.
+ *
+ * Note: if any replanning activity is required, the caller's memory context
+ * is used for that work.
+ */
+CachedPlan *
+RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner)
+{
+ CachedPlan *plan;
+
+ /* Validity check that we were given a CachedPlanSource */
+ Assert(list_member_ptr(cached_plans_list, plansource));
+
+ /*
+ * If the plan currently appears valid, acquire locks on the referenced
+ * objects; then check again. We need to do it this way to cover the
+ * race condition that an invalidation message arrives before we get
+ * the lock.
+ */
+ plan = plansource->plan;
+ if (plan && !plan->dead)
+ {
+ /*
+ * Plan must have positive refcount because it is referenced by
+ * plansource; so no need to fear it disappears under us here.
+ */
+ Assert(plan->refcount > 0);
+
+ if (plan->fully_planned)
+ AcquireExecutorLocks(plan->stmt_list, true);
+ else
+ AcquirePlannerLocks(plan->stmt_list, true);
+
+ /*
+ * By now, if any invalidation has happened, PlanCacheCallback
+ * will have marked the plan dead.
+ */
+ if (plan->dead)
+ {
+ /* Ooops, the race case happened. Release useless locks. */
+ if (plan->fully_planned)
+ AcquireExecutorLocks(plan->stmt_list, false);
+ else
+ AcquirePlannerLocks(plan->stmt_list, false);
+ }
+ }
+
+ /*
+ * If plan has been invalidated, unlink it from the parent and release it.
+ */
+ if (plan && plan->dead)
+ {
+ plansource->plan = NULL;
+ ReleaseCachedPlan(plan, false);
+ plan = NULL;
+ }
+
+ /*
+ * Build a new plan if needed.
+ */
+ if (!plan)
+ {
+ List *slist;
+ TupleDesc resultDesc;
+
+ /*
+ * Run parse analysis and rule rewriting. The parser tends to
+ * scribble on its input, so we must copy the raw parse tree to
+ * prevent corruption of the cache. Note that we do not use
+ * parse_analyze_varparams(), assuming that the caller never wants the
+ * parameter types to change from the original values.
+ */
+ slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree),
+ plansource->query_string,
+ plansource->param_types,
+ plansource->num_params);
+
+ if (plansource->fully_planned)
+ {
+ /*
+ * Generate plans for queries. Assume snapshot is not set yet
+ * (XXX this may be wasteful, won't all callers have done that?)
+ */
+ slist = pg_plan_queries(slist, NULL, true);
+ }
+
+ /*
+ * Check or update the result tupdesc. XXX should we use a weaker
+ * condition than equalTupleDescs() here?
+ */
+ resultDesc = ComputeResultDesc(slist);
+ if (resultDesc == NULL && plansource->resultDesc == NULL)
+ {
+ /* OK, doesn't return tuples */
+ }
+ else if (resultDesc == NULL || plansource->resultDesc == NULL ||
+ !equalTupleDescs(resultDesc, plansource->resultDesc))
+ {
+ MemoryContext oldcxt;
+
+ /* can we give a better error message? */
+ if (plansource->fixed_result)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cached plan must not change result type")));
+ oldcxt = MemoryContextSwitchTo(plansource->context);
+ if (resultDesc)
+ resultDesc = CreateTupleDescCopy(resultDesc);
+ if (plansource->resultDesc)
+ FreeTupleDesc(plansource->resultDesc);
+ plansource->resultDesc = resultDesc;
+ MemoryContextSwitchTo(oldcxt);
+ }
+
+ /*
+ * Store the plans into the plancache entry, advancing the generation
+ * count.
+ */
+ StoreCachedPlan(plansource, slist, NULL);
+
+ plan = plansource->plan;
+ }
+
+ /*
+ * Last step: flag the plan as in use by caller.
+ */
+ if (useResOwner)
+ ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner);
+ plan->refcount++;
+ if (useResOwner)
+ ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan);
+
+ return plan;
+}
+
+/*
+ * ReleaseCachedPlan: release active use of a cached plan.
+ *
+ * This decrements the reference count, and frees the plan if the count
+ * has thereby gone to zero. If useResOwner is true, it is assumed that
+ * the reference count is managed by the CurrentResourceOwner.
+ *
+ * Note: useResOwner = false is used for releasing references that are in
+ * persistent data structures, such as the parent CachedPlanSource or a
+ * Portal. Transient references should be protected by a resource owner.
+ */
+void
+ReleaseCachedPlan(CachedPlan *plan, bool useResOwner)
+{
+ if (useResOwner)
+ ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan);
+ Assert(plan->refcount > 0);
+ plan->refcount--;
+ if (plan->refcount == 0)
+ MemoryContextDelete(plan->context);
+}
+
+/*
+ * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned
+ * cached plan; or release them if acquire is false.
+ */
+static void
+AcquireExecutorLocks(List *stmt_list, bool acquire)
+{
+ ListCell *lc1;
+
+ foreach(lc1, stmt_list)
+ {
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc1);
+ int rt_index;
+ ListCell *lc2;
+
+ Assert(!IsA(plannedstmt, Query));
+ if (!IsA(plannedstmt, PlannedStmt))
+ continue; /* Ignore utility statements */
+
+ rt_index = 0;
+ foreach(lc2, plannedstmt->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2);
+ LOCKMODE lockmode;
+
+ rt_index++;
+
+ if (rte->rtekind != RTE_RELATION)
+ continue;
+
+ /*
+ * Acquire the appropriate type of lock on each relation OID.
+ * Note that we don't actually try to open the rel, and hence
+ * will not fail if it's been dropped entirely --- we'll just
+ * transiently acquire a non-conflicting lock.
+ */
+ if (list_member_int(plannedstmt->resultRelations, rt_index))
+ lockmode = RowExclusiveLock;
+ else if (rowmark_member(plannedstmt->rowMarks, rt_index))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ if (acquire)
+ LockRelationOid(rte->relid, lockmode);
+ else
+ UnlockRelationOid(rte->relid, lockmode);
+ }
+ }
+}
+
+/*
+ * AcquirePlannerLocks: acquire locks needed for planning and execution of a
+ * not-fully-planned cached plan; or release them if acquire is false.
+ *
+ * Note that we don't actually try to open the relations, and hence will not
+ * fail if one has been dropped entirely --- we'll just transiently acquire
+ * a non-conflicting lock.
+ */
+static void
+AcquirePlannerLocks(List *stmt_list, bool acquire)
+{
+ ListCell *lc;
+
+ foreach(lc, stmt_list)
+ {
+ Query *query = (Query *) lfirst(lc);
+
+ Assert(IsA(query, Query));
+ if (acquire)
+ ScanQueryForRelids(query, LockRelid, NULL);
+ else
+ ScanQueryForRelids(query, UnlockRelid, NULL);
+ }
+}
+
+/*
+ * ScanQueryForRelids callback functions for AcquirePlannerLocks
+ */
+static void
+LockRelid(Oid relid, LOCKMODE lockmode, void *arg)
+{
+ LockRelationOid(relid, lockmode);
+}
+
+static void
+UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg)
+{
+ UnlockRelationOid(relid, lockmode);
+}
+
+/*
+ * ScanQueryForRelids: recursively scan one Query and apply the callback
+ * function to each relation OID found therein. The callback function
+ * takes the arguments relation OID, lockmode, pointer arg.
+ */
+static void
+ScanQueryForRelids(Query *parsetree,
+ void (*callback) (),
+ void *arg)
+{
+ ListCell *lc;
+ int rt_index;
+
+ /*
+ * First, process RTEs of the current query level.
+ */
+ rt_index = 0;
+ foreach(lc, parsetree->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+ LOCKMODE lockmode;
+
+ rt_index++;
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ /*
+ * Determine the lock type required for this RTE.
+ */
+ if (rt_index == parsetree->resultRelation)
+ lockmode = RowExclusiveLock;
+ else if (rowmark_member(parsetree->rowMarks, rt_index))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ (*callback) (rte->relid, lockmode, arg);
+ break;
+
+ case RTE_SUBQUERY:
+
+ /*
+ * The subquery RTE itself is all right, but we have to
+ * recurse to process the represented subquery.
+ */
+ ScanQueryForRelids(rte->subquery, callback, arg);
+ break;
+
+ default:
+ /* ignore other types of RTEs */
+ break;
+ }
+ }
+
+ /*
+ * Recurse into sublink subqueries, too. But we already did the ones in
+ * the rtable.
+ */
+ if (parsetree->hasSubLinks)
+ {
+ ScanQueryWalkerContext context;
+
+ context.callback = callback;
+ context.arg = arg;
+ query_tree_walker(parsetree, ScanQueryWalker,
+ (void *) &context,
+ QTW_IGNORE_RT_SUBQUERIES);
+ }
+}
+
+/*
+ * Walker to find sublink subqueries for ScanQueryForRelids
+ */
+static bool
+ScanQueryWalker(Node *node, ScanQueryWalkerContext *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, SubLink))
+ {
+ SubLink *sub = (SubLink *) node;
+
+ /* Do what we came for */
+ ScanQueryForRelids((Query *) sub->subselect,
+ context->callback, context->arg);
+ /* Fall through to process lefthand args of SubLink */
+ }
+
+ /*
+ * Do NOT recurse into Query nodes, because ScanQueryForRelids
+ * already processed subselects of subselects for us.
+ */
+ return expression_tree_walker(node, ScanQueryWalker,
+ (void *) context);
+}
+
+/*
+ * rowmark_member: check whether an RT index appears in a RowMarkClause list.
+ */
+static bool
+rowmark_member(List *rowMarks, int rt_index)
+{
+ ListCell *l;
+
+ foreach(l, rowMarks)
+ {
+ RowMarkClause *rc = (RowMarkClause *) lfirst(l);
+
+ if (rc->rti == rt_index)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * ComputeResultDesc: given a list of either fully-planned statements or
+ * Queries, 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.
+ */
+static TupleDesc
+ComputeResultDesc(List *stmt_list)
+{
+ Node *node;
+ Query *query;
+ PlannedStmt *pstmt;
+
+ switch (ChoosePortalStrategy(stmt_list))
+ {
+ case PORTAL_ONE_SELECT:
+ node = (Node *) linitial(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_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_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;
+}
+
+/*
+ * PlanCacheCallback
+ * Relcache inval callback function
+ */
+static void
+PlanCacheCallback(Datum arg, Oid relid)
+{
+ ListCell *lc1;
+ ListCell *lc2;
+
+ foreach(lc1, cached_plans_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1);
+ CachedPlan *plan = plansource->plan;
+
+ /* No work if it's already invalidated */
+ if (!plan || plan->dead)
+ continue;
+ if (plan->fully_planned)
+ {
+ foreach(lc2, plan->stmt_list)
+ {
+ PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2);
+ ListCell *lc3;
+
+ Assert(!IsA(plannedstmt, Query));
+ if (!IsA(plannedstmt, PlannedStmt))
+ continue; /* Ignore utility statements */
+ foreach(lc3, plannedstmt->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc3);
+
+ if (rte->rtekind != RTE_RELATION)
+ continue;
+ if (relid == rte->relid)
+ {
+ /* Invalidate the plan! */
+ plan->dead = true;
+ break; /* out of rangetable scan */
+ }
+ }
+ if (plan->dead)
+ break; /* out of stmt_list scan */
+ }
+ }
+ else
+ {
+ /*
+ * For not-fully-planned entries we use ScanQueryForRelids,
+ * since a recursive traversal is needed. The callback API
+ * is a bit tedious but avoids duplication of coding.
+ */
+ InvalRelidContext context;
+
+ context.inval_relid = relid;
+ context.plan = plan;
+
+ foreach(lc2, plan->stmt_list)
+ {
+ Query *query = (Query *) lfirst(lc2);
+
+ Assert(IsA(query, Query));
+ ScanQueryForRelids(query, InvalRelid, (void *) &context);
+ }
+ }
+ }
+}
+
+/*
+ * ScanQueryForRelids callback function for PlanCacheCallback
+ */
+static void
+InvalRelid(Oid relid, LOCKMODE lockmode, InvalRelidContext *context)
+{
+ if (relid == context->inval_relid)
+ context->plan->dead = true;
+}
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 8fdb3be75e2..daef3199fa6 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.174 2007/02/15 23:23:23 alvherre Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.175 2007/03/13 00:33:42 tgl Exp $
*
*
*-------------------------------------------------------------------------
@@ -28,6 +28,7 @@
#include "libpq/hba.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
+#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/postmaster.h"
#include "storage/backendid.h"
@@ -40,10 +41,10 @@
#include "utils/acl.h"
#include "utils/flatfiles.h"
#include "utils/guc.h"
+#include "utils/plancache.h"
#include "utils/portal.h"
#include "utils/relcache.h"
#include "utils/syscache.h"
-#include "pgstat.h"
static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace);
@@ -429,6 +430,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
*/
RelationCacheInitialize();
InitCatalogCache();
+ InitPlanCache();
/* Initialize portal manager */
EnablePortalManager();
diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README
index a91dfe3d760..05353a3335d 100644
--- a/src/backend/utils/mmgr/README
+++ b/src/backend/utils/mmgr/README
@@ -1,4 +1,4 @@
-$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.9 2006/09/07 22:52:01 tgl Exp $
+$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.10 2007/03/13 00:33:42 tgl Exp $
Notes about memory allocation redesign
--------------------------------------
@@ -201,15 +201,6 @@ have dangling pointers leading to a crash at top-level commit. An example of
data kept here is pending NOTIFY messages, which are sent at top-level commit,
but only if the generating subtransaction did not abort.
-QueryContext --- this is not actually a separate context, but a global
-variable pointing to the context that holds the current command's parse tree.
-(In simple-Query mode this points to MessageContext; when executing a
-prepared statement it will point to the prepared statement's private context.
-Note that the plan tree may or may not be in this same context.)
-Generally it is not appropriate for any code to use QueryContext as an
-allocation target --- from the point of view of any code that would be
-referencing the QueryContext variable, it's a read-only context.
-
PortalContext --- this is not actually a separate context either, but a
global variable pointing to the per-portal context of the currently active
execution portal. This can be used if it's necessary to allocate storage
@@ -229,9 +220,7 @@ Contexts for prepared statements and portals
A prepared-statement object has an associated private context, in which
the parse and plan trees for its query are stored. Because these trees
are read-only to the executor, the prepared statement can be re-used many
-times without further copying of these trees. QueryContext points at this
-private context while executing any portal built from the prepared
-statement.
+times without further copying of these trees.
An execution-portal object has a private context that is referenced by
PortalContext when the portal is active. In the case of a portal created
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 14e9c70b2cf..3337f819ee3 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.59 2007/01/05 22:19:47 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.60 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -46,8 +46,7 @@ MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext CurTransactionContext = NULL;
-/* These two are transient links to contexts owned by other objects: */
-MemoryContext QueryContext = NULL;
+/* This is a transient link to the active portal's memory context: */
MemoryContext PortalContext = NULL;
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index 3bd2ee6397f..043ea1e57a4 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.99 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.100 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -149,9 +149,9 @@ GetPortalByName(const char *name)
* cases should occur in present usages of this function.
*
* Copes if given a list of Querys --- can't happen in a portal, but this
- * code also supports prepared statements, which need both cases.
+ * code also supports plancache.c, which needs both cases.
*
- * Note: the reason this is just handed a List is so that prepared statements
+ * Note: the reason this is just handed a List is so that plancache.c
* can share the code. For use with a portal, use PortalGetPrimaryStmt
* rather than calling this directly.
*/
@@ -275,9 +275,17 @@ CreateNewPortal(void)
*
* Notes: commandTag shall be NULL if and only if the original query string
* (before rewriting) was an empty string. Also, the passed commandTag must
- * be a pointer to a constant string, since it is not copied. The caller is
- * responsible for ensuring that the passed prepStmtName (if any), sourceText
- * (if any), and plan trees have adequate lifetime.
+ * be a pointer to a constant string, since it is not copied. However,
+ * prepStmtName and sourceText, if provided, are copied into the portal's
+ * heap context for safekeeping.
+ *
+ * If cplan is provided, then it is a cached plan containing the stmts,
+ * and the caller must have done RevalidateCachedPlan(), causing a refcount
+ * increment. The refcount will be released when the portal is destroyed.
+ *
+ * If cplan is NULL, then it is the caller's responsibility to ensure that
+ * the passed plan trees have adequate lifetime. Typically this is done by
+ * copying them into the portal's heap context.
*/
void
PortalDefineQuery(Portal portal,
@@ -285,18 +293,35 @@ PortalDefineQuery(Portal portal,
const char *sourceText,
const char *commandTag,
List *stmts,
- MemoryContext queryContext)
+ CachedPlan *cplan)
{
AssertArg(PortalIsValid(portal));
- AssertState(portal->queryContext == NULL); /* else defined already */
+ AssertState(portal->status == PORTAL_NEW);
Assert(commandTag != NULL || stmts == NIL);
- portal->prepStmtName = prepStmtName;
- portal->sourceText = sourceText;
+ portal->prepStmtName = prepStmtName ?
+ MemoryContextStrdup(PortalGetHeapMemory(portal), prepStmtName) : NULL;
+ portal->sourceText = sourceText ?
+ MemoryContextStrdup(PortalGetHeapMemory(portal), sourceText) : NULL;
portal->commandTag = commandTag;
portal->stmts = stmts;
- portal->queryContext = queryContext;
+ portal->cplan = cplan;
+ portal->status = PORTAL_DEFINED;
+}
+
+/*
+ * PortalReleaseCachedPlan
+ * Release a portal's reference to its cached plan, if any.
+ */
+static void
+PortalReleaseCachedPlan(Portal portal)
+{
+ if (portal->cplan)
+ {
+ ReleaseCachedPlan(portal->cplan, false);
+ portal->cplan = NULL;
+ }
}
/*
@@ -356,6 +381,10 @@ PortalDrop(Portal portal, bool isTopCommit)
if (PointerIsValid(portal->cleanup))
(*portal->cleanup) (portal);
+ /* drop cached plan reference, if any */
+ if (portal->cplan)
+ PortalReleaseCachedPlan(portal);
+
/*
* Release any resources still attached to the portal. There are several
* cases being covered here:
@@ -423,29 +452,6 @@ PortalDrop(Portal portal, bool isTopCommit)
pfree(portal);
}
-/*
- * DropDependentPortals
- * Drop any portals using the specified context as queryContext.
- *
- * This is normally used to make sure we can safely drop a prepared statement.
- */
-void
-DropDependentPortals(MemoryContext queryContext)
-{
- HASH_SEQ_STATUS status;
- PortalHashEnt *hentry;
-
- hash_seq_init(&status, PortalHashTable);
-
- while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
- {
- Portal portal = hentry->portal;
-
- if (portal->queryContext == queryContext)
- PortalDrop(portal, false);
- }
-}
-
/*
* Pre-commit processing for portals.
@@ -485,6 +491,10 @@ CommitHoldablePortals(void)
PortalCreateHoldStore(portal);
PersistHoldablePortal(portal);
+ /* drop cached plan reference, if any */
+ if (portal->cplan)
+ PortalReleaseCachedPlan(portal);
+
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; the portal will no longer
@@ -630,6 +640,10 @@ AtAbort_Portals(void)
portal->cleanup = NULL;
}
+ /* drop cached plan reference, if any */
+ if (portal->cplan)
+ PortalReleaseCachedPlan(portal);
+
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; they will be gone before we run
@@ -769,6 +783,10 @@ AtSubAbort_Portals(SubTransactionId mySubid,
portal->cleanup = NULL;
}
+ /* drop cached plan reference, if any */
+ if (portal->cplan)
+ PortalReleaseCachedPlan(portal);
+
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; they will be gone before we
diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README
index 56629d5089d..57be840dc17 100644
--- a/src/backend/utils/resowner/README
+++ b/src/backend/utils/resowner/README
@@ -1,4 +1,4 @@
-$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.4 2006/06/16 18:42:23 tgl Exp $
+$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.5 2007/03/13 00:33:42 tgl Exp $
Notes about resource owners
---------------------------
@@ -60,12 +60,13 @@ subtransaction or portal. Therefore, the "release" operation on a child
ResourceOwner transfers lock ownership to the parent instead of actually
releasing the lock, if isCommit is true.
-Currently, ResourceOwners contain direct support for recording ownership
-of buffer pins, lmgr locks, and catcache, relcache, and tupdesc references.
-Other objects can be associated with a ResourceOwner by recording the address
-of the owning ResourceOwner in such an object. There is an API for other
-modules to get control during ResourceOwner release, so that they can scan
-their own data structures to find the objects that need to be deleted.
+Currently, ResourceOwners contain direct support for recording ownership of
+buffer pins, lmgr locks, and catcache, relcache, plancache, and tupdesc
+references. Other objects can be associated with a ResourceOwner by recording
+the address of the owning ResourceOwner in such an object. There is an API
+for other modules to get control during ResourceOwner release, so that they
+can scan their own data structures to find the objects that need to be
+deleted.
Whenever we are inside a transaction, the global variable
CurrentResourceOwner shows which resource owner should be assigned
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 70ddd443c7b..92fe4742c7c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.23 2007/01/05 22:19:47 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.24 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -56,6 +56,11 @@ typedef struct ResourceOwnerData
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
+ /* We have built-in support for remembering plancache references */
+ int nplanrefs; /* number of owned plancache pins */
+ CachedPlan **planrefs; /* dynamically allocated array */
+ int maxplanrefs; /* currently allocated array size */
+
/* We have built-in support for remembering tupdesc references */
int ntupdescs; /* number of owned tupdesc references */
TupleDesc *tupdescs; /* dynamically allocated array */
@@ -90,6 +95,7 @@ static void ResourceOwnerReleaseInternal(ResourceOwner owner,
bool isCommit,
bool isTopLevel);
static void PrintRelCacheLeakWarning(Relation rel);
+static void PrintPlanCacheLeakWarning(CachedPlan *plan);
static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
@@ -280,6 +286,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
}
+ /* Ditto for plancache references */
+ while (owner->nplanrefs > 0)
+ {
+ if (isCommit)
+ PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
+ ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
+ }
/* Ditto for tupdesc references */
while (owner->ntupdescs > 0)
{
@@ -316,6 +329,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner->ncatrefs == 0);
Assert(owner->ncatlistrefs == 0);
Assert(owner->nrelrefs == 0);
+ Assert(owner->nplanrefs == 0);
Assert(owner->ntupdescs == 0);
/*
@@ -341,6 +355,8 @@ ResourceOwnerDelete(ResourceOwner owner)
pfree(owner->catlistrefs);
if (owner->relrefs)
pfree(owner->relrefs);
+ if (owner->planrefs)
+ pfree(owner->planrefs);
if (owner->tupdescs)
pfree(owner->tupdescs);
@@ -760,6 +776,86 @@ PrintRelCacheLeakWarning(Relation rel)
/*
* Make sure there is room for at least one more entry in a ResourceOwner's
+ * plancache reference array.
+ *
+ * This is separate from actually inserting an entry because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+void
+ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
+{
+ int newmax;
+
+ if (owner->nplanrefs < owner->maxplanrefs)
+ return; /* nothing to do */
+
+ if (owner->planrefs == NULL)
+ {
+ newmax = 16;
+ owner->planrefs = (CachedPlan **)
+ MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
+ owner->maxplanrefs = newmax;
+ }
+ else
+ {
+ newmax = owner->maxplanrefs * 2;
+ owner->planrefs = (CachedPlan **)
+ repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
+ owner->maxplanrefs = newmax;
+ }
+}
+
+/*
+ * Remember that a plancache reference is owned by a ResourceOwner
+ *
+ * Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
+ */
+void
+ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+ Assert(owner->nplanrefs < owner->maxplanrefs);
+ owner->planrefs[owner->nplanrefs] = plan;
+ owner->nplanrefs++;
+}
+
+/*
+ * Forget that a plancache reference is owned by a ResourceOwner
+ */
+void
+ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
+{
+ CachedPlan **planrefs = owner->planrefs;
+ int np1 = owner->nplanrefs - 1;
+ int i;
+
+ for (i = np1; i >= 0; i--)
+ {
+ if (planrefs[i] == plan)
+ {
+ while (i < np1)
+ {
+ planrefs[i] = planrefs[i + 1];
+ i++;
+ }
+ owner->nplanrefs = np1;
+ return;
+ }
+ }
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
+}
+
+/*
+ * Debugging subroutine
+ */
+static void
+PrintPlanCacheLeakWarning(CachedPlan *plan)
+{
+ elog(WARNING, "plancache reference leak: plan %p not closed", plan);
+}
+
+/*
+ * Make sure there is room for at least one more entry in a ResourceOwner's
* tupdesc reference array.
*
* This is separate from actually inserting an entry because if we run out
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index e74b87b0ed3..760b4324568 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.84 2007/01/05 22:19:51 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.85 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -164,9 +164,9 @@ extern bool IsTransactionBlock(void);
extern bool IsTransactionOrTransactionBlock(void);
extern char TransactionBlockStatusCode(void);
extern void AbortOutOfAnyTransaction(void);
-extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
-extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
-extern bool IsInTransactionChain(void *stmtNode);
+extern void PreventTransactionChain(bool isTopLevel, const char *stmtType);
+extern void RequireTransactionChain(bool isTopLevel, const char *stmtType);
+extern bool IsInTransactionChain(bool isTopLevel);
extern void RegisterXactCallback(XactCallback callback, void *arg);
extern void UnregisterXactCallback(XactCallback callback, void *arg);
extern void RegisterSubXactCallback(SubXactCallback callback, void *arg);
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 6929bbe2afb..0ed1e231388 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.31 2007/01/05 22:19:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.32 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,7 +17,7 @@
#include "utils/rel.h"
-extern void cluster(ClusterStmt *stmt);
+extern void cluster(ClusterStmt *stmt, bool isTopLevel);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck);
diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h
index cda5749603b..11ff84c57a0 100644
--- a/src/include/commands/copy.h
+++ b/src/include/commands/copy.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.29 2007/01/05 22:19:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.30 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -18,7 +18,7 @@
#include "tcop/dest.h"
-extern uint64 DoCopy(const CopyStmt *stmt);
+extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
extern DestReceiver *CreateCopyDestReceiver(void);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 3d665ff5c2c..5bb94a24f25 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.80 2007/01/23 05:07:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.81 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -25,7 +25,6 @@ extern void DefineIndex(RangeVar *heapRelation,
char *tableSpaceName,
List *attributeList,
Expr *predicate,
- List *rangetable,
List *options,
bool unique,
bool primary,
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 981064297f9..42879ce5a40 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.29 2007/01/05 22:19:53 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.30 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,11 +16,16 @@
#include "executor/executor.h"
-extern void ExplainQuery(ExplainStmt *stmt, ParamListInfo params,
- DestReceiver *dest);
+extern void ExplainQuery(ExplainStmt *stmt, const char *queryString,
+ ParamListInfo params, DestReceiver *dest);
extern TupleDesc ExplainResultDesc(ExplainStmt *stmt);
+extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
+ const char *queryString,
+ ParamListInfo params,
+ TupOutputState *tstate);
+
extern void ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt,
TupOutputState *tstate);
diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h
index 50fe2f13284..3d774046136 100644
--- a/src/include/commands/portalcmds.h
+++ b/src/include/commands/portalcmds.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.21 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.22 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -18,7 +18,8 @@
#include "utils/portal.h"
-extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params);
+extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
+ const char *queryString, bool isTopLevel);
extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest,
char *completionTag);
diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h
index a921bf1b045..4e27ab3bb39 100644
--- a/src/include/commands/prepare.h
+++ b/src/include/commands/prepare.h
@@ -6,7 +6,7 @@
*
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.24 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.25 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -14,58 +14,49 @@
#define PREPARE_H
#include "executor/executor.h"
+#include "utils/plancache.h"
#include "utils/timestamp.h"
/*
- * The data structure representing a prepared statement
+ * The data structure representing a prepared statement. This is now just
+ * a thin veneer over a plancache entry --- the main addition is that of
+ * a name.
*
- * A prepared statement might be fully planned, or only parsed-and-rewritten.
- * If fully planned, stmt_list contains PlannedStmts and/or utility statements;
- * if not, it contains Query nodes.
- *
- * Note: all subsidiary storage lives in the context denoted by the context
- * field. However, the string referenced by commandTag is not subsidiary
- * storage; it is assumed to be a compile-time-constant string. As with
- * portals, commandTag shall be NULL if and only if the original query string
- * (before rewriting) was an empty string.
+ * Note: all subsidiary storage lives in the referenced plancache entry.
*/
typedef struct
{
/* dynahash.c requires key to be first field */
char stmt_name[NAMEDATALEN];
- char *query_string; /* text of query, or NULL */
- const char *commandTag; /* command tag (a constant!), or NULL */
- List *stmt_list; /* list of statement or Query nodes */
- List *argtype_list; /* list of parameter type OIDs */
- bool fully_planned; /* what is in stmt_list, exactly? */
+ CachedPlanSource *plansource; /* the actual cached plan */
bool from_sql; /* prepared via SQL, not FE/BE protocol? */
TimestampTz prepare_time; /* the time when the stmt was prepared */
- MemoryContext context; /* context containing this query */
} PreparedStatement;
/* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */
-extern void PrepareQuery(PrepareStmt *stmt);
-extern void ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
+extern void PrepareQuery(PrepareStmt *stmt, const char *queryString);
+extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
+ ParamListInfo params,
DestReceiver *dest, char *completionTag);
extern void DeallocateQuery(DeallocateStmt *stmt);
-extern void ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
- TupOutputState *tstate);
+extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
+ const char *queryString,
+ ParamListInfo params, TupOutputState *tstate);
/* Low-level access to stored prepared statements */
extern 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);
extern PreparedStatement *FetchPreparedStatement(const char *stmt_name,
bool throwError);
extern void DropPreparedStatement(const char *stmt_name, bool showError);
-extern List *FetchPreparedStatementParams(const char *stmt_name);
extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt);
-extern bool PreparedStatementReturnsTuples(PreparedStatement *stmt);
extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt);
#endif /* PREPARE_H */
diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h
index e8820a8c191..e70579c3c37 100644
--- a/src/include/commands/schemacmds.h
+++ b/src/include/commands/schemacmds.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.15 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.16 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,7 +17,8 @@
#include "nodes/parsenodes.h"
-extern void CreateSchemaCommand(CreateSchemaStmt *parsetree);
+extern void CreateSchemaCommand(CreateSchemaStmt *parsetree,
+ const char *queryString);
extern void RemoveSchema(List *names, DropBehavior behavior, bool missing_ok);
extern void RemoveSchemaById(Oid schemaOid);
diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h
index 09da5cfc0da..b77fbf4c719 100644
--- a/src/include/commands/vacuum.h
+++ b/src/include/commands/vacuum.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.69 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.70 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -110,7 +110,7 @@ extern int vacuum_freeze_min_age;
/* in commands/vacuum.c */
-extern void vacuum(VacuumStmt *vacstmt, List *relids);
+extern void vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);
diff --git a/src/include/commands/view.h b/src/include/commands/view.h
index 2e0e5254039..ff54935bbbd 100644
--- a/src/include/commands/view.h
+++ b/src/include/commands/view.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/commands/view.h,v 1.24 2007/01/05 22:19:54 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/commands/view.h,v 1.25 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,7 +16,7 @@
#include "nodes/parsenodes.h"
-extern void DefineView(RangeVar *view, Query *view_parse, bool replace);
+extern void DefineView(ViewStmt *stmt, const char *queryString);
extern void RemoveView(const RangeVar *view, DropBehavior behavior);
#endif /* VIEW_H */
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index 69bc117ef09..7a94152b422 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.34 2007/01/05 22:19:55 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.35 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -82,4 +82,7 @@ typedef struct ParamExecData
/* Functions found in src/backend/nodes/params.c */
extern ParamListInfo copyParamList(ParamListInfo from);
+extern void getParamListTypes(ParamListInfo params,
+ Oid **param_types, int *num_params);
+
#endif /* PARAMS_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ec9ccb6ce30..e24b57e8a23 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.341 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.342 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1021,15 +1021,14 @@ typedef struct GrantRoleStmt
*
* We support "COPY relation FROM file", "COPY relation TO file", and
* "COPY (query) TO file". In any given CopyStmt, exactly one of "relation"
- * and "query" must be non-NULL. Note: "query" is a SelectStmt before
- * parse analysis, and a Query afterwards.
+ * and "query" must be non-NULL.
* ----------------------
*/
typedef struct CopyStmt
{
NodeTag type;
RangeVar *relation; /* the relation to copy */
- Query *query; /* the query to copy */
+ Node *query; /* the SELECT query to copy */
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
@@ -1487,8 +1486,6 @@ typedef struct IndexStmt
List *indexParams; /* a list of IndexElem */
List *options; /* options from WITH clause */
Node *whereClause; /* qualification (partial-index predicate) */
- List *rangetable; /* range table for qual and/or expressions,
- * filled in by transformStmt() */
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
@@ -1713,7 +1710,7 @@ typedef struct ViewStmt
NodeTag type;
RangeVar *view; /* the view to be created */
List *aliases; /* target column names */
- Query *query; /* the SQL statement */
+ Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
} ViewStmt;
@@ -1805,7 +1802,7 @@ typedef struct VacuumStmt
typedef struct ExplainStmt
{
NodeTag type;
- Query *query; /* the query */
+ Node *query; /* the query (as a raw parse tree) */
bool verbose; /* print plan info */
bool analyze; /* get statistics by executing plan */
} ExplainStmt;
@@ -1940,9 +1937,8 @@ typedef struct PrepareStmt
{
NodeTag type;
char *name; /* Name of plan, arbitrary */
- List *argtypes; /* Types of parameters (TypeNames) */
- List *argtype_oids; /* Types of parameters (OIDs) */
- Query *query; /* The query itself */
+ List *argtypes; /* Types of parameters (List of TypeName) */
+ Node *query; /* The query itself (as a raw parsetree) */
} PrepareStmt;
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
index f53358801d9..033dce60462 100644
--- a/src/include/parser/analyze.h
+++ b/src/include/parser/analyze.h
@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.35 2007/01/05 22:19:56 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.36 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,6 +21,10 @@ extern List *parse_analyze(Node *parseTree, const char *sourceText,
extern List *parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams);
extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
+
+extern IndexStmt *analyzeIndexStmt(IndexStmt *stmt, const char *queryString);
+extern void analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
+ List **actions, Node **whereClause);
extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt);
extern void CheckSelectLocking(Query *qry);
extern void applyLockingClause(Query *qry, Index rtindex,
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index c6d15c129f6..d4673f82a6c 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.23 2007/01/05 22:19:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.24 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -16,7 +16,15 @@
#include "nodes/parsenodes.h"
-extern void DefineQueryRewrite(RuleStmt *args);
+extern void DefineRule(RuleStmt *stmt, const char *queryString);
+
+extern void DefineQueryRewrite(char *rulename,
+ RangeVar *event_obj,
+ Node *event_qual,
+ CmdType event_type,
+ bool is_instead,
+ bool replace,
+ List *action);
extern void RenameRewriteRule(Oid owningRel, const char *oldName,
const char *newName);
diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h
index 5cab498c13a..abf64f0ebfa 100644
--- a/src/include/tcop/pquery.h
+++ b/src/include/tcop/pquery.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.41 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.42 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -33,7 +33,7 @@ extern void PortalStart(Portal portal, ParamListInfo params,
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
-extern bool PortalRun(Portal portal, long count,
+extern bool PortalRun(Portal portal, long count, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag);
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 52c02253068..863a664cf52 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.31 2007/02/20 17:32:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.32 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -17,8 +17,9 @@
#include "tcop/tcopprot.h"
-extern void ProcessUtility(Node *parsetree, ParamListInfo params,
- DestReceiver *dest, char *completionTag);
+extern void ProcessUtility(Node *parsetree, const char *queryString,
+ ParamListInfo params, bool isTopLevel,
+ DestReceiver *dest, char *completionTag);
extern bool UtilityReturnsTuples(Node *parsetree);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index fd304b66ad5..f046f397e8c 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.61 2007/01/05 22:19:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.62 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -75,8 +75,7 @@ extern DLLIMPORT MemoryContext MessageContext;
extern DLLIMPORT MemoryContext TopTransactionContext;
extern DLLIMPORT MemoryContext CurTransactionContext;
-/* These two are transient links to contexts owned by other objects: */
-extern DLLIMPORT MemoryContext QueryContext;
+/* This is a transient link to the active portal's memory context: */
extern DLLIMPORT MemoryContext PortalContext;
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
new file mode 100644
index 00000000000..833ec473b13
--- /dev/null
+++ b/src/include/utils/plancache.h
@@ -0,0 +1,105 @@
+/*-------------------------------------------------------------------------
+ *
+ * plancache.h
+ * Plan cache definitions.
+ *
+ * See plancache.c for comments.
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.1 2007/03/13 00:33:43 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PLANCACHE_H
+#define PLANCACHE_H
+
+#include "access/tupdesc.h"
+
+/*
+ * CachedPlanSource represents the portion of a cached plan that persists
+ * across invalidation/replan cycles. It stores a raw parse tree (required),
+ * the original source text (optional, but highly recommended to improve
+ * error reports), and adjunct data.
+ *
+ * Normally, both the struct itself and the subsidiary data live in the
+ * context denoted by the context field, while the linked-to CachedPlan, if
+ * any, has its own context. Thus an invalidated CachedPlan can be dropped
+ * when no longer needed, and conversely a CachedPlanSource can be dropped
+ * without worrying whether any portals depend on particular instances of
+ * its plan.
+ *
+ * But for entries created by FastCreateCachedPlan, the CachedPlanSource
+ * and the initial version of the CachedPlan share the same memory context.
+ * In this case, we treat the memory context as belonging to the CachedPlan.
+ * The CachedPlanSource has an extra reference-counted link (orig_plan)
+ * to the CachedPlan, and the memory context goes away when the CachedPlan's
+ * reference count goes to zero. This arrangement saves overhead for plans
+ * that aren't expected to live long enough to need replanning, while not
+ * losing any flexibility if a replan turns out to be necessary.
+ *
+ * Note: the string referenced by commandTag is not subsidiary storage;
+ * it is assumed to be a compile-time-constant string. As with portals,
+ * commandTag shall be NULL if and only if the original query string (before
+ * rewriting) was an empty string.
+ */
+typedef struct CachedPlanSource
+{
+ Node *raw_parse_tree; /* output of raw_parser() */
+ char *query_string; /* text of query, or NULL */
+ const char *commandTag; /* command tag (a constant!), or NULL */
+ Oid *param_types; /* array of parameter type OIDs, or NULL */
+ int num_params; /* length of param_types array */
+ bool fully_planned; /* do we cache planner or rewriter output? */
+ bool fixed_result; /* disallow change in result tupdesc? */
+ int generation; /* counter, starting at 1, for replans */
+ TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */
+ struct CachedPlan *plan; /* link to plan, or NULL if not valid */
+ MemoryContext context; /* context containing this CachedPlanSource */
+ struct CachedPlan *orig_plan; /* link to plan owning my context */
+} CachedPlanSource;
+
+/*
+ * CachedPlan represents the portion of a cached plan that is discarded when
+ * invalidation occurs. The reference count includes both the link(s) from the
+ * parent CachedPlanSource, and any active plan executions, so the plan can be
+ * discarded exactly when refcount goes to zero. Both the struct itself and
+ * the subsidiary data live in the context denoted by the context field.
+ * This makes it easy to free a no-longer-needed cached plan.
+ */
+typedef struct CachedPlan
+{
+ List *stmt_list; /* list of statement or Query nodes */
+ bool fully_planned; /* do we cache planner or rewriter output? */
+ bool dead; /* if true, do not use */
+ int refcount; /* count of live references to this struct */
+ int generation; /* counter, starting at 1, for replans */
+ MemoryContext context; /* context containing this CachedPlan */
+} CachedPlan;
+
+
+extern void InitPlanCache(void);
+extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree,
+ const char *query_string,
+ const char *commandTag,
+ Oid *param_types,
+ int num_params,
+ List *stmt_list,
+ bool fully_planned,
+ bool fixed_result);
+extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree,
+ char *query_string,
+ const char *commandTag,
+ Oid *param_types,
+ int num_params,
+ List *stmt_list,
+ bool fully_planned,
+ bool fixed_result,
+ MemoryContext context);
+extern void DropCachedPlan(CachedPlanSource *plansource);
+extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource,
+ bool useResOwner);
+extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner);
+
+#endif /* PLANCACHE_H */
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index aa432abb876..47651006a21 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -39,7 +39,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.73 2007/02/20 17:32:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.74 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -94,7 +94,8 @@ typedef enum PortalStrategy
*/
typedef enum PortalStatus
{
- PORTAL_NEW, /* in process of creation */
+ PORTAL_NEW, /* freshly created */
+ PORTAL_DEFINED, /* PortalDefineQuery done */
PORTAL_READY, /* PortalStart complete, can run it */
PORTAL_ACTIVE, /* portal is running (can't delete it) */
PORTAL_DONE, /* portal is finished (don't re-run it) */
@@ -125,15 +126,7 @@ typedef struct PortalData
const char *sourceText; /* text of query, if known (may be NULL) */
const char *commandTag; /* command tag for original query */
List *stmts; /* PlannedStmts and/or utility statements */
- MemoryContext queryContext; /* where the plan trees live */
-
- /*
- * Note: queryContext effectively identifies which prepared statement the
- * portal depends on, if any. The queryContext is *not* owned by the
- * portal and is not to be deleted by portal destruction. (But for a
- * cursor it is the same as "heap", and that context is deleted by portal
- * destruction.) The plan trees may be in either queryContext or heap.
- */
+ CachedPlan *cplan; /* CachedPlan, if stmts are from one */
ParamListInfo portalParams; /* params to pass to query */
@@ -210,14 +203,13 @@ extern void AtSubCleanup_Portals(SubTransactionId mySubid);
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PortalDrop(Portal portal, bool isTopCommit);
-extern void DropDependentPortals(MemoryContext queryContext);
extern Portal GetPortalByName(const char *name);
extern void PortalDefineQuery(Portal portal,
const char *prepStmtName,
const char *sourceText,
const char *commandTag,
List *stmts,
- MemoryContext queryContext);
+ CachedPlan *cplan);
extern Node *PortalListGetPrimaryStmt(List *stmts);
extern void PortalCreateHoldStore(Portal portal);
diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h
index 663096a333f..ea0d6a74066 100644
--- a/src/include/utils/resowner.h
+++ b/src/include/utils/resowner.h
@@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.10 2007/01/05 22:19:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.11 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,6 +21,7 @@
#include "storage/buf.h"
#include "utils/catcache.h"
+#include "utils/plancache.h"
/*
@@ -106,6 +107,13 @@ extern void ResourceOwnerRememberRelationRef(ResourceOwner owner,
extern void ResourceOwnerForgetRelationRef(ResourceOwner owner,
Relation rel);
+/* support for plancache refcount management */
+extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner);
+extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner,
+ CachedPlan *plan);
+extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner,
+ CachedPlan *plan);
+
/* support for tupledesc refcount management */
extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner);
extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner,
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
new file mode 100644
index 00000000000..4980a9ab68b
--- /dev/null
+++ b/src/test/regress/expected/plancache.out
@@ -0,0 +1,102 @@
+--
+-- Tests to exercise the plan caching/invalidation mechanism
+--
+CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl;
+-- create and use a cached plan
+PREPARE prepstmt AS SELECT * FROM foo;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 123
+ 4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789
+(5 rows)
+
+-- and one with parameters
+PREPARE prepstmt2(bigint) AS SELECT * FROM foo WHERE q1 = $1;
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- invalidate the plans and see what happens
+DROP TABLE foo;
+EXECUTE prepstmt;
+ERROR: relation "foo" does not exist
+EXECUTE prepstmt2(123);
+ERROR: relation "foo" does not exist
+-- recreate the temp table (this demonstrates that the raw plan is
+-- purely textual and doesn't depend on OIDs, for instance)
+CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl ORDER BY 2;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- prepared statements should prevent change in output tupdesc,
+-- since clients probably aren't expecting that to change on the fly
+ALTER TABLE foo ADD COLUMN q3 bigint;
+EXECUTE prepstmt;
+ERROR: cached plan must not change result type
+EXECUTE prepstmt2(123);
+ERROR: cached plan must not change result type
+-- but we're nice guys and will let you undo your mistake
+ALTER TABLE foo DROP COLUMN q3;
+EXECUTE prepstmt;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+EXECUTE prepstmt2(123);
+ q1 | q2
+-----+------------------
+ 123 | 456
+ 123 | 4567890123456789
+(2 rows)
+
+-- Try it with a view, which isn't directly used in the resulting plan
+-- but should trigger invalidation anyway
+CREATE TEMP VIEW voo AS SELECT * FROM foo;
+PREPARE vprep AS SELECT * FROM voo;
+EXECUTE vprep;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -4567890123456789
+ 4567890123456789 | 123
+ 123 | 456
+ 123 | 4567890123456789
+ 4567890123456789 | 4567890123456789
+(5 rows)
+
+CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo;
+EXECUTE vprep;
+ q1 | q2
+------------------+-------------------
+ 4567890123456789 | -2283945061728394
+ 4567890123456789 | 61
+ 123 | 228
+ 123 | 2283945061728394
+ 4567890123456789 | 2283945061728394
+(5 rows)
+
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70058605c0a..30103f5d08b 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1188,6 +1188,8 @@ drop rule foorule on foo;
create rule foorule as on insert to foo where f1 < 100
do instead insert into foo2 values (f1);
ERROR: column "f1" does not exist
+LINE 2: do instead insert into foo2 values (f1);
+ ^
-- this is the correct way:
create rule foorule as on insert to foo where f1 < 100
do instead insert into foo2 values (new.f1);
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 096d2c1c7a7..35ebff85895 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -1,6 +1,6 @@
# ----------
# The first group of parallel test
-# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.39 2007/02/09 03:35:35 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.40 2007/03/13 00:33:44 tgl Exp $
# ----------
test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid
@@ -69,7 +69,7 @@ test: misc
# ----------
# The fifth group of parallel test
# ----------
-test: select_views portals_p2 rules foreign_key cluster dependency guc combocid
+test: select_views portals_p2 rules foreign_key cluster dependency guc combocid plancache
# ----------
# The sixth group of parallel test
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index d109dabdc25..31bac612607 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.37 2007/02/09 03:35:35 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.38 2007/03/13 00:33:44 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
@@ -89,6 +89,7 @@ test: cluster
test: dependency
test: guc
test: combocid
+test: plancache
test: limit
test: plpgsql
test: copy2
diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql
new file mode 100644
index 00000000000..b952efe1972
--- /dev/null
+++ b/src/test/regress/sql/plancache.sql
@@ -0,0 +1,53 @@
+--
+-- Tests to exercise the plan caching/invalidation mechanism
+--
+
+CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl;
+
+-- create and use a cached plan
+PREPARE prepstmt AS SELECT * FROM foo;
+
+EXECUTE prepstmt;
+
+-- and one with parameters
+PREPARE prepstmt2(bigint) AS SELECT * FROM foo WHERE q1 = $1;
+
+EXECUTE prepstmt2(123);
+
+-- invalidate the plans and see what happens
+DROP TABLE foo;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- recreate the temp table (this demonstrates that the raw plan is
+-- purely textual and doesn't depend on OIDs, for instance)
+CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl ORDER BY 2;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- prepared statements should prevent change in output tupdesc,
+-- since clients probably aren't expecting that to change on the fly
+ALTER TABLE foo ADD COLUMN q3 bigint;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- but we're nice guys and will let you undo your mistake
+ALTER TABLE foo DROP COLUMN q3;
+
+EXECUTE prepstmt;
+EXECUTE prepstmt2(123);
+
+-- Try it with a view, which isn't directly used in the resulting plan
+-- but should trigger invalidation anyway
+CREATE TEMP VIEW voo AS SELECT * FROM foo;
+
+PREPARE vprep AS SELECT * FROM voo;
+
+EXECUTE vprep;
+
+CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo;
+
+EXECUTE vprep;