OSDN Git Service

First phase of plan-invalidation project: create a plan cache management
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Mar 2007 00:33:44 +0000 (00:33 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 13 Mar 2007 00:33:44 +0000 (00:33 +0000)
module and teach PREPARE and protocol-level prepared statements to use it.
In service of this, rearrange utility-statement processing so that parse
analysis does not assume table schemas can't change before execution for
utility statements (necessary because we don't attempt to re-acquire locks
for utility statements when reusing a stored plan).  This requires some
refactoring of the ProcessUtility API, but it ends up cleaner anyway,
for instance we can get rid of the QueryContext global.

Still to do: fix up SPI and related code to use the plan cache; I'm tempted to
try to make SQL functions use it too.  Also, there are at least some aspects
of system state that we want to ensure remain the same during a replan as in
the original processing; search_path certainly ought to behave that way for
instance, and perhaps there are others.

61 files changed:
src/backend/access/transam/xact.c
src/backend/bootstrap/bootparse.y
src/backend/commands/cluster.c
src/backend/commands/copy.c
src/backend/commands/dbcommands.c
src/backend/commands/explain.c
src/backend/commands/indexcmds.c
src/backend/commands/portalcmds.c
src/backend/commands/prepare.c
src/backend/commands/schemacmds.c
src/backend/commands/tablecmds.c
src/backend/commands/tablespace.c
src/backend/commands/vacuum.c
src/backend/commands/view.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/params.c
src/backend/optimizer/util/clauses.c
src/backend/parser/analyze.c
src/backend/parser/gram.y
src/backend/postmaster/autovacuum.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/postgres.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/backend/utils/cache/Makefile
src/backend/utils/cache/plancache.c [new file with mode: 0644]
src/backend/utils/init/postinit.c
src/backend/utils/mmgr/README
src/backend/utils/mmgr/mcxt.c
src/backend/utils/mmgr/portalmem.c
src/backend/utils/resowner/README
src/backend/utils/resowner/resowner.c
src/include/access/xact.h
src/include/commands/cluster.h
src/include/commands/copy.h
src/include/commands/defrem.h
src/include/commands/explain.h
src/include/commands/portalcmds.h
src/include/commands/prepare.h
src/include/commands/schemacmds.h
src/include/commands/vacuum.h
src/include/commands/view.h
src/include/nodes/params.h
src/include/nodes/parsenodes.h
src/include/parser/analyze.h
src/include/rewrite/rewriteDefine.h
src/include/tcop/pquery.h
src/include/tcop/utility.h
src/include/utils/memutils.h
src/include/utils/plancache.h [new file with mode: 0644]
src/include/utils/portal.h
src/include/utils/resowner.h
src/test/regress/expected/plancache.out [new file with mode: 0644]
src/test/regress/expected/rules.out
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/plancache.sql [new file with mode: 0644]

index db81e3b..6a2ebd4 100644 (file)
@@ -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 &&
index 64c8c81..ff2f7f7 100644 (file)
@@ -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();
index 2a16b12..aa91136 100644 (file)
@@ -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.
index 17f0135..a2e1939 100644 (file)
@@ -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);
 
index 63e15e5..de4b239 100644 (file)
@@ -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)
index 835b1ef..1b2cfcc 100644 (file)
@@ -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
index 8c5fdbb..ba18543 100644 (file)
@@ -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
index 0219650..98b200d 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
  *             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;
index 8a5382c..2c284cb 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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"
 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);
 }
index 09c2ca9..0912b8a 100644 (file)
@@ -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();
                }
index ebc974f..ddc6208 100644 (file)
@@ -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,
index aa2b33c..8e3bfbd 100644 (file)
@@ -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
         */
index d13090e..54864fb 100644 (file)
@@ -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
index 42f9eaf..83f26f7 100644 (file)
@@ -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);
 }
 
 /*
index 596a482..7e648f4 100644 (file)
@@ -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);
        }
 
        /*
index 33ec212..f538f50 100644 (file)
@@ -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;
index 19cb0b0..4ca9ba4 100644 (file)
@@ -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;
index 1007930..ae247df 100644 (file)
@@ -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;
index ca3c798..68025ab 100644 (file)
@@ -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);
index 15e7b1d..07593c5 100644 (file)
@@ -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;
+       }
+}
index 0f8231c..11d2119 100644 (file)
@@ -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;
 }
 
index 63a5999..a4e4418 100644 (file)
@@ -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
index 3204d0a..1ce7170 100644 (file)
@@ -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;
                                }
                ;
index 87384df..b291855 100644 (file)
@@ -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);
index d4212a4..64a2a96 100644 (file)
@@ -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,
index cfb6731..f997d52 100644 (file)
@@ -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':
index 97a003a..b54dea4 100644 (file)
@@ -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;
 }
index 47051ad..be274a7 100644 (file)
@@ -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;
 
index 18e920b..879f013 100644 (file)
@@ -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 (file)
index 0000000..95ed498
--- /dev/null
@@ -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;
+}
index 8fdb3be..daef319 100644 (file)
@@ -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"
 #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();
index a91dfe3..05353a3 100644 (file)
@@ -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
index 14e9c70..3337f81 100644 (file)
@@ -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;
 
 
index 3bd2ee6..043ea1e 100644 (file)
@@ -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
index 56629d5..57be840 100644 (file)
@@ -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
index 70ddd44..92fe474 100644 (file)
@@ -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
index e74b87b..760b432 100644 (file)
@@ -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);
index 6929bbe..0ed1e23 100644 (file)
@@ -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);
index cda5749..11ff84c 100644 (file)
@@ -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);
 
index 3d665ff..5bb94a2 100644 (file)
@@ -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,
index 9810642..42879ce 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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);
 
index 50fe2f1..3d77404 100644 (file)
@@ -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);
index a921bf1..4e27ab3 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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 */
index e8820a8..e70579c 100644 (file)
@@ -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);
index 09da5cf..b77fbf4 100644 (file)
@@ -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);
index 2e0e525..ff54935 100644 (file)
@@ -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 */
index 69bc117..7a94152 100644 (file)
@@ -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 */
index ec9ccb6..e24b57e 100644 (file)
@@ -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;
 
 
index f533588..033dce6 100644 (file)
@@ -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,
index c6d15c1..d4673f8 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 
 #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);
index 5cab498..abf64f0 100644 (file)
@@ -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);
 
index 52c0225..863a664 100644 (file)
@@ -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);
 
index fd304b6..f046f39 100644 (file)
@@ -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 (file)
index 0000000..833ec47
--- /dev/null
@@ -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 */
index aa432ab..4765100 100644 (file)
@@ -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);
 
index 663096a..ea0d6a7 100644 (file)
@@ -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 (file)
index 0000000..4980a9a
--- /dev/null
@@ -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)
+
index 7005860..30103f5 100644 (file)
@@ -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);
index 096d2c1..35ebff8 100644 (file)
@@ -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
index d109dab..31bac61 100644 (file)
@@ -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 (file)
index 0000000..b952efe
--- /dev/null
@@ -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;