First phase of plan-invalidation project: create a plan cache management
authorTom Lane
Tue, 13 Mar 2007 00:33:44 +0000 (00:33 +0000)
committerTom Lane
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 db81e3bd70c87cc0c90064d497852757935062ba..6a2ebd40891494651ae6711c425b36e0a47b1758 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 64c8c8139da26c8dbe67f13c476979b9eac5cdfc..ff2f7f70c3cd55ca97a6f4db4d005c52517dd589 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 2a16b12be1d54635a808498d564860017e738264..aa911369409584e02cfda92b641af23d5dfd8089 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 17f0135981eaebfb99c8d02b9818854950a134f0..a2e1939ea252b49be37d6dcfe28bd3bde15e1214 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 63e15e505b7f452eec9d1fb56cfbd6fc65391827..de4b239893acc540ea81fed2b944a4bbb02de9d9 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 835b1ef6ade2af64a0baf51536167eeb6a75a356..1b2cfccce9f7779c0a22ac45eb164bac9f78836e 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, ¶m_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 */
@@ -210,6 +170,78 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
    ExplainOnePlan(queryDesc, stmt, tstate);
 }
 
+/*
+ * 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, ¶m_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
index 8c5fdbb6c94662e9179475affd5ad320f30cbcc2..ba185431bec595e739c28b834936762cc6aba153 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,
@@ -300,18 +298,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
     */
@@ -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)
@@ -1241,13 +1228,6 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
        aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
                       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
index 0219650c069fc30210397499c2ab8dccdb0e7db1..98b200d2cff87a81e83776ec2323a8f0a9c9726c 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, ¶m_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 8a5382c737815f975818d43b3c564cb9bd8a0759..2c284cb9be04d34d3ac9496251882b83e5a90e8a 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 = ¶mLI->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)
@@ -401,20 +515,6 @@ FetchPreparedStatement(const char *stmt_name, bool throwError)
    return entry;
 }
 
-/*
- * 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 09c2ca9f6338a4ff591eee217c92a21b7e206a59..0912b8a62cc193a621da8a9b2c0b50e73bc9242f 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 ebc974f8731018b44b9656bc52721ede707b99d5..ddc62086f51a8c6928d646715005a34d06878c5d 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 aa2b33c932641aeabc4ed76ace59900bac6fab38..8e3bfbda863eb65536e8409e2542f39af30745fc 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 d13090ebf726f7f669e4823b4d1f2f95489a5477..54864fbec906a0bac42c7fb3dac14b8a8008360c 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 42f9eafd3d2f48299d94691f2ffcd44f91cdb8dd..83f26f73ffb20b9ea69085e4efad74d1d283af5c 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 596a482fa137b39a7df020451e12ad45284173f3..7e648f437b67435eff63ba2965bde2ece771b206 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 33ec21286d96d291502a3617992bcac5477cf9d1..f538f508ba3f9a773b9c1251e6465ee150ffdba8 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 19cb0b0b6fc40896fa161a69506d12d842fa8442..4ca9ba4c0e27ef4d94bf6feefe74802ddfe8f95c 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 1007930814ac36694122856b47582bb99621fd0e..ae247dfa3ea11366656104f1621e4ae0f2e9d534 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 ca3c79812c1bddb0094257f08dcc7ecdeca14205..68025ab36861fcd4b32b7c50e03eff07e4b70fe8 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 15e7b1d24f1a5bac47ec4db022e400ca96ca30ef..07593c55470c1a88772d997dd384d63d332ec89d 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 = ¶ms->params[i];
+
+       ptypes[i] = prm->ptype;
+   }
+}
index 0f8231c4f913ffae04e211b34e9426b5a643a796..11d2119f3c6c6df4352fcfdf71c34052cfc59640 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 63a5999a401d29cca1130518e198820d450c6f2d..a4e4418b145034cc460eb1f09f31061fd7ccc2bb 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  portion of a CREATE TABLE statement into the
- * column definitions which recreate the user defined column portions of .
+ * Change the LIKE  portion of a CREATE TABLE statement into
+ * column definitions which recreate the user defined column portions of
+ * .
+ *
+ * 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,12 +1865,20 @@ 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
@@ -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 3204d0a401ae2039fc807fc1fa33c5a6b9bce327..1ce71700405a7773c09ca630eaae417d945bbf78 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 87384df8a2d48d3a704e6d1f2ae7208e594f1fd6..b29185583052d044efa0db2bd585139e6717100e 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 d4212a440890300ae5210301eada5ef900669dc4..64a2a96f0e9a8d7b1056c183f62fdeabaa698848 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 cfb6731b23482d174cc40739f95cca61a68861b8..f997d5241015930cd1ebe2e797aaad7cce8b7cc1 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,
                                                 ¶mTypes,
                                                 &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 : "";
+   debug_query_string = psrc->query_string ? psrc->query_string : "";
 
    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 : "",
                            *portal_name ? "/" : "",
                            *portal_name ? portal_name : "",
-                           pstmt->query_string ? pstmt->query_string : ""),
+                           psrc->query_string ? psrc->query_string : ""),
                     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 97a003ac89da9246ea909589f3488cae748fdc61..b54dea45dcaa87667d97ebca512068147762ae0b 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 47051ad1ed2c9bb24794ac45f2bff2f6e07a0b6c..be274a72f10e3bc82fa15bc524d0628fec16840f 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 18e920bc1dfdeabb88e56543954bcd2130cafe11..879f013e3351114978be3dabecad0f5dbc652e18 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 8fdb3be75e2f1b68faad4c86d00f733ac60a9d89..daef3199fa6285a2848cf81783c5c39fef63ee24 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 a91dfe3d760150a0cf1e50e7a666d33c921513e5..05353a3335d9a50a1b88cff2ba2c1b90a3f9d6b7 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 14e9c70b2cfd9dbd976a3ab4531434257f960ba2..3337f819ee313c7a7ebeb30ce5eb5b50cd906cef 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 3bd2ee6397f5daa84456c7bfaf1648d10a70334a..043ea1e57a4f5ef71292c369919fc0aaaed364f5 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 56629d5089d0e72ea625bf8dd49c785b6db8ec2e..57be840dc1714570ea1dc3eff7233bd089e9164d 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 70ddd443c7b651f206122bf47738a7cd0c2ffa3a..92fe4742c7c573fe2d30f1ae5ac2ffbd3cf10a91 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);
 
@@ -758,6 +774,86 @@ PrintRelCacheLeakWarning(Relation rel)
         RelationGetRelationName(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.
index e74b87b0ed3d086fc3a3eb66d1ef4e93232799dc..760b4324568b273d81b09bb91cf81e5bc45b661d 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 6929bbe2afb60353e8a6d9fa3f5f02d8b4a341fa..0ed1e231388860f97457309d75aa3e5825dcdf67 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 cda5749603bcf371afb5a755bba87db53103da59..11ff84c57a079f4183b06e76d5887977363921f2 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 3d665ff5c2cb9b2a907511693c107e282e01a1ed..5bb94a24f25294fb946a5f96465ea0760cf9bdfe 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 981064297f90cf0c51fc6025e685776a30ff65c6..42879ce5a40713d296729f2f30184942a5c4c621 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 50fe2f132843ec65d8be2cc2d111c3db63292d5e..3d774046136670793f9b5df4c49961f52967ef60 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 a921bf1b0451bb52866a3c31f5d233adeb5e71f2..4e27ab3bb3918365e3fb1cde10b783c1342fc335 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 e8820a8c1913329e44e8ae26141366bc9836d229..e70579c3c370033b90d894b25ceaf8a4a162e607 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 09da5cfc0da935ddb8164267f6420f96b90d1932..b77fbf4c7197e77b7164450aa00dddcd951bef31 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 2e0e5254039b9f3dcf40ed1b3b7eb3958199ad31..ff54935bbbd160132c8853b08fc0012b75746c4d 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 69bc117ef09f0d870d9f323bfefc9e7ade8fdfb6..7a94152b42244f9315c97bbd0bcf5efc2663259d 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 ec9ccb6ce30c15827e8c4671b5b77cd4c0718370..e24b57e8a2326a1c4c4017104eef8923401193b2 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 f53358801d966280b4a3b4c186536d8110aad043..033dce604620687812a8825c789fa7715c73333c 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 c6d15c129f65779abdd2c6a4653c6937e67bdf36..d4673f82a6c048098900eccd91a2485169a6618e 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 5cab498c13a579076741440c6b4f1fcbf7961428..abf64f0ebfa58ae078a6001a2f09f17cc22220d7 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 52c02253068c5acdba60a6507658716662738cb3..863a664cf528e780abe2b287ef47524b0d01ea2d 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 fd304b66ad5812a8d9c6679fe6b13c048b825fae..f046f397e8cbdc4570c0f3b3123c41467620c7f6 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 aa432abb8768adb0a32a12db24fcb6c295d5bf3d..47651006a21037bc545e0166711ecc9b677d9930 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 663096a333fb72ea9b5ef12f22bdbe8669eeb057..ea0d6a74066ee318dad5c1f45579c8e8f50e0950 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 70058605c0ab409b5d5b7304e69a2ebfcc2deb89..30103f5d08b7ef9c47aa6cf0b82c07aab4bbc283 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 096d2c1c7a777335431eb4d78ff599efe7f69926..35ebff85895d00d57a40e00e577a91d234e2ce0b 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 d109dabdc25fbbd53d50c89707b4838b2c7823f5..31bac6126075918ee9de02d7f804ee6ae89364f3 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;