From b9527e984092e838790b543b014c0c2720ea4f11 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 13 Mar 2007 00:33:44 +0000 Subject: [PATCH] First phase of plan-invalidation project: create a plan cache management module and teach PREPARE and protocol-level prepared statements to use it. In service of this, rearrange utility-statement processing so that parse analysis does not assume table schemas can't change before execution for utility statements (necessary because we don't attempt to re-acquire locks for utility statements when reusing a stored plan). This requires some refactoring of the ProcessUtility API, but it ends up cleaner anyway, for instance we can get rid of the QueryContext global. Still to do: fix up SPI and related code to use the plan cache; I'm tempted to try to make SQL functions use it too. Also, there are at least some aspects of system state that we want to ensure remain the same during a replan as in the original processing; search_path certainly ought to behave that way for instance, and perhaps there are others. --- src/backend/access/transam/xact.c | 40 +- src/backend/bootstrap/bootparse.y | 6 +- src/backend/commands/cluster.c | 6 +- src/backend/commands/copy.c | 43 +- src/backend/commands/dbcommands.c | 7 +- src/backend/commands/explain.c | 184 ++--- src/backend/commands/indexcmds.c | 24 +- src/backend/commands/portalcmds.c | 70 +- src/backend/commands/prepare.c | 549 ++++++++------- src/backend/commands/schemacmds.c | 13 +- src/backend/commands/tablecmds.c | 10 +- src/backend/commands/tablespace.c | 10 +- src/backend/commands/vacuum.c | 13 +- src/backend/commands/view.c | 147 ++-- src/backend/executor/functions.c | 45 +- src/backend/executor/spi.c | 11 +- src/backend/nodes/copyfuncs.c | 4 +- src/backend/nodes/equalfuncs.c | 4 +- src/backend/nodes/outfuncs.c | 3 +- src/backend/nodes/params.c | 33 +- src/backend/optimizer/util/clauses.c | 34 +- src/backend/parser/analyze.c | 492 ++++---------- src/backend/parser/gram.y | 16 +- src/backend/postmaster/autovacuum.c | 11 +- src/backend/rewrite/rewriteDefine.c | 58 +- src/backend/tcop/postgres.c | 285 ++++---- src/backend/tcop/pquery.c | 74 +- src/backend/tcop/utility.c | 105 +-- src/backend/utils/cache/Makefile | 5 +- src/backend/utils/cache/plancache.c | 862 ++++++++++++++++++++++++ src/backend/utils/init/postinit.c | 6 +- src/backend/utils/mmgr/README | 15 +- src/backend/utils/mmgr/mcxt.c | 5 +- src/backend/utils/mmgr/portalmem.c | 86 ++- src/backend/utils/resowner/README | 15 +- src/backend/utils/resowner/resowner.c | 98 ++- src/include/access/xact.h | 8 +- src/include/commands/cluster.h | 4 +- src/include/commands/copy.h | 4 +- src/include/commands/defrem.h | 3 +- src/include/commands/explain.h | 11 +- src/include/commands/portalcmds.h | 5 +- src/include/commands/prepare.h | 41 +- src/include/commands/schemacmds.h | 5 +- src/include/commands/vacuum.h | 4 +- src/include/commands/view.h | 4 +- src/include/nodes/params.h | 5 +- src/include/nodes/parsenodes.h | 18 +- src/include/parser/analyze.h | 6 +- src/include/rewrite/rewriteDefine.h | 12 +- src/include/tcop/pquery.h | 4 +- src/include/tcop/utility.h | 7 +- src/include/utils/memutils.h | 5 +- src/include/utils/plancache.h | 105 +++ src/include/utils/portal.h | 18 +- src/include/utils/resowner.h | 10 +- src/test/regress/expected/plancache.out | 102 +++ src/test/regress/expected/rules.out | 2 + src/test/regress/parallel_schedule | 4 +- src/test/regress/serial_schedule | 3 +- src/test/regress/sql/plancache.sql | 53 ++ 61 files changed, 2478 insertions(+), 1354 deletions(-) create mode 100644 src/backend/utils/cache/plancache.c create mode 100644 src/include/utils/plancache.h create mode 100644 src/test/regress/expected/plancache.out create mode 100644 src/test/regress/sql/plancache.sql diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index db81e3bd70..6a2ebd4089 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.235 2007/03/12 22:09:27 petere Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.236 2007/03/13 00:33:38 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2504,12 +2504,13 @@ AbortCurrentTransaction(void) * could issue more commands and possibly cause a failure after the statement * completes). Subtransactions are verboten too. * - * stmtNode: pointer to parameter block for statement; this is used in - * a very klugy way to determine whether we are inside a function. - * stmtType: statement type name for error messages. + * isTopLevel: passed down from ProcessUtility to determine whether we are + * inside a function. (We will always fail if this is false, but it's + * convenient to centralize the check here instead of making callers do it.) + * stmtType: statement type name, for error messages. */ void -PreventTransactionChain(void *stmtNode, const char *stmtType) +PreventTransactionChain(bool isTopLevel, const char *stmtType) { /* * xact block already started? @@ -2532,11 +2533,9 @@ PreventTransactionChain(void *stmtNode, const char *stmtType) stmtType))); /* - * Are we inside a function call? If the statement's parameter block was - * allocated in QueryContext, assume it is an interactive command. - * Otherwise assume it is coming from a function. + * inside a function call? */ - if (!MemoryContextContains(QueryContext, stmtNode)) + if (!isTopLevel) ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ @@ -2562,12 +2561,12 @@ PreventTransactionChain(void *stmtNode, const char *stmtType) * use of the current statement's results. Likewise subtransactions. * Thus this is an inverse for PreventTransactionChain. * - * stmtNode: pointer to parameter block for statement; this is used in - * a very klugy way to determine whether we are inside a function. - * stmtType: statement type name for error messages. + * isTopLevel: passed down from ProcessUtility to determine whether we are + * inside a function. + * stmtType: statement type name, for error messages. */ void -RequireTransactionChain(void *stmtNode, const char *stmtType) +RequireTransactionChain(bool isTopLevel, const char *stmtType) { /* * xact block already started? @@ -2582,12 +2581,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType) return; /* - * Are we inside a function call? If the statement's parameter block was - * allocated in QueryContext, assume it is an interactive command. - * Otherwise assume it is coming from a function. + * inside a function call? */ - if (!MemoryContextContains(QueryContext, stmtNode)) + if (!isTopLevel) return; + ereport(ERROR, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ @@ -2602,11 +2600,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType) * a transaction block than when running as single commands. ANALYZE is * currently the only example. * - * stmtNode: pointer to parameter block for statement; this is used in - * a very klugy way to determine whether we are inside a function. + * isTopLevel: passed down from ProcessUtility to determine whether we are + * inside a function. */ bool -IsInTransactionChain(void *stmtNode) +IsInTransactionChain(bool isTopLevel) { /* * Return true on same conditions that would make PreventTransactionChain @@ -2618,7 +2616,7 @@ IsInTransactionChain(void *stmtNode) if (IsSubTransaction()) return true; - if (!MemoryContextContains(QueryContext, stmtNode)) + if (!isTopLevel) return true; if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y index 64c8c8139d..ff2f7f70c3 100644 --- a/src/backend/bootstrap/bootparse.y +++ b/src/backend/bootstrap/bootparse.y @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.87 2007/03/07 13:35:02 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.88 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -252,7 +252,7 @@ Boot_DeclareIndexStmt: LexIDStr($8), NULL, $10, - NULL, NIL, NIL, + NULL, NIL, false, false, false, false, false, true, false, false); do_end(); @@ -270,7 +270,7 @@ Boot_DeclareUniqueIndexStmt: LexIDStr($9), NULL, $11, - NULL, NIL, NIL, + NULL, NIL, true, false, false, false, false, true, false, false); do_end(); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 2a16b12be1..aa91136940 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.156 2007/02/01 19:10:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.157 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,7 +82,7 @@ static List *get_tables_to_cluster(MemoryContext cluster_context); *--------------------------------------------------------------------------- */ void -cluster(ClusterStmt *stmt) +cluster(ClusterStmt *stmt, bool isTopLevel) { if (stmt->relation != NULL) { @@ -173,7 +173,7 @@ cluster(ClusterStmt *stmt) * We cannot run this form of CLUSTER inside a user transaction block; * we'd be holding locks way too long. */ - PreventTransactionChain((void *) stmt, "CLUSTER"); + PreventTransactionChain(isTopLevel, "CLUSTER"); /* * Create special memory context for cross-transaction storage. diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 17f0135981..a2e1939ea2 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.277 2007/03/03 19:32:54 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.278 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -713,7 +713,7 @@ CopyLoadRawBuf(CopyState cstate) * the table. */ uint64 -DoCopy(const CopyStmt *stmt) +DoCopy(const CopyStmt *stmt, const char *queryString) { CopyState cstate; bool is_from = stmt->is_from; @@ -982,13 +982,11 @@ DoCopy(const CopyStmt *stmt) } else { - Query *query = stmt->query; List *rewritten; + Query *query; PlannedStmt *plan; DestReceiver *dest; - Assert(query); - Assert(query->commandType == CMD_SELECT); Assert(!is_from); cstate->rel = NULL; @@ -998,33 +996,18 @@ DoCopy(const CopyStmt *stmt) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("COPY (SELECT) WITH OIDS is not supported"))); - /* Query mustn't use INTO, either */ - if (query->into) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("COPY (SELECT INTO) is not supported"))); - /* - * The query has already been through parse analysis, but not - * rewriting or planning. Do that now. + * Run parse analysis and rewrite. Note this also acquires sufficient + * locks on the source table(s). * - * Because the planner is not cool about not scribbling on its input, - * we make a preliminary copy of the source querytree. This prevents + * Because the parser and planner tend to scribble on their input, we + * make a preliminary copy of the source querytree. This prevents * problems in the case that the COPY is in a portal or plpgsql * function and is executed repeatedly. (See also the same hack in - * EXPLAIN, DECLARE CURSOR and PREPARE.) XXX the planner really - * shouldn't modify its input ... FIXME someday. + * DECLARE CURSOR and PREPARE.) XXX FIXME someday. */ - query = copyObject(query); - - /* - * Must acquire locks in case we didn't come fresh from the parser. - * XXX this also scribbles on query, another reason for copyObject - */ - AcquireRewriteLocks(query); - - /* Rewrite through rule system */ - rewritten = QueryRewrite(query); + rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), + queryString, NULL, 0); /* We don't expect more or less than one result query */ if (list_length(rewritten) != 1) @@ -1033,6 +1016,12 @@ DoCopy(const CopyStmt *stmt) query = (Query *) linitial(rewritten); Assert(query->commandType == CMD_SELECT); + /* Query mustn't use INTO, either */ + if (query->into) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("COPY (SELECT INTO) is not supported"))); + /* plan the query */ plan = planner(query, false, 0, NULL); diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 63e15e505b..de4b239893 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.192 2007/02/09 16:12:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.193 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -97,9 +97,6 @@ createdb(const CreatedbStmt *stmt) int encoding = -1; int dbconnlimit = -1; - /* don't call this in a transaction block */ - PreventTransactionChain((void *) stmt, "CREATE DATABASE"); - /* Extract options from the statement node tree */ foreach(option, stmt->options) { @@ -545,8 +542,6 @@ dropdb(const char *dbname, bool missing_ok) Relation pgdbrel; HeapTuple tup; - PreventTransactionChain((void *) dbname, "DROP DATABASE"); - AssertArg(dbname); if (strcmp(dbname, get_database_name(MyDatabaseId)) == 0) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index 835b1ef6ad..1b2cfccce9 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994-5, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.159 2007/02/23 21:59:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.160 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -26,6 +26,7 @@ #include "optimizer/var.h" #include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" +#include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/lsyscache.h" @@ -41,8 +42,9 @@ typedef struct ExplainState List *rtable; /* range table */ } ExplainState; -static void ExplainOneQuery(Query *query, ExplainStmt *stmt, - ParamListInfo params, TupOutputState *tstate); +static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions, + ExplainStmt *stmt, const char *queryString, + ParamListInfo params, TupOutputState *tstate); static double elapsed_time(instr_time *starttime); static void explain_outNode(StringInfo str, Plan *plan, PlanState *planstate, @@ -62,62 +64,49 @@ static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols, * execute an EXPLAIN command */ void -ExplainQuery(ExplainStmt *stmt, ParamListInfo params, DestReceiver *dest) +ExplainQuery(ExplainStmt *stmt, const char *queryString, + ParamListInfo params, DestReceiver *dest) { - Query *query = stmt->query; + Oid *param_types; + int num_params; TupOutputState *tstate; List *rewritten; ListCell *l; + /* Convert parameter type data to the form parser wants */ + getParamListTypes(params, ¶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) + /* Explain every plan */ + foreach(l, rewritten) { - /* 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) - { - 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 diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 8c5fdbb6c9..ba185431be 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.156 2007/03/06 02:06:12 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.157 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -77,7 +77,6 @@ static bool relationHasPrimaryKey(Relation rel); * 'attributeList': a list of IndexElem specifying columns and expressions * to index on. * 'predicate': the partial-index condition, or NULL if none. - * 'rangetable': needed to interpret the predicate. * 'options': reloptions from WITH (in list-of-DefElem form). * 'unique': make the index enforce uniqueness. * 'primary': mark the index as a primary key in the catalogs. @@ -99,7 +98,6 @@ DefineIndex(RangeVar *heapRelation, char *tableSpaceName, List *attributeList, Expr *predicate, - List *rangetable, List *options, bool unique, bool primary, @@ -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 diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c index 0219650c06..98b200d2cf 100644 --- a/src/backend/commands/portalcmds.c +++ b/src/backend/commands/portalcmds.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.61 2007/02/20 17:32:14 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.62 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,8 +38,11 @@ * Execute SQL DECLARE CURSOR command. */ void -PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params) +PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, + const char *queryString, bool isTopLevel) { + Oid *param_types; + int num_params; List *rewritten; Query *query; PlannedStmt *plan; @@ -61,40 +64,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params) * user-visible effect). */ if (!(stmt->options & CURSOR_OPT_HOLD)) - RequireTransactionChain((void *) stmt, "DECLARE CURSOR"); + RequireTransactionChain(isTopLevel, "DECLARE CURSOR"); /* - * Because the planner is not cool about not scribbling on its input, we + * 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"))); + + /* 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 and is - * executed repeatedly. XXX the planner really shouldn't modify its input - * ... FIXME someday. + * 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. */ - query = copyObject(stmt->query); + rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query), + queryString, param_types, num_params); - /* - * The query has been through parse analysis, but not rewriting or - * planning as yet. Note that the grammar ensured we have a SELECT query, - * so we are not expecting rule rewriting to do anything strange. - */ - AcquireRewriteLocks(query); - rewritten = QueryRewrite(query); + /* We don't expect more or less than one result query */ if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query)) elog(ERROR, "unexpected rewrite result"); query = (Query *) linitial(rewritten); if (query->commandType != CMD_SELECT) elog(ERROR, "unexpected rewrite result"); + /* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */ if (query->into) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_DEFINITION), errmsg("DECLARE CURSOR cannot specify INTO"))); + if (query->rowMarks != NIL) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"), errdetail("Cursors must be READ ONLY."))); + /* plan the query */ plan = planner(query, true, stmt->options, params); /* @@ -106,23 +122,22 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params) plan = copyObject(plan); - /* - * XXX: debug_query_string is wrong here: the user might have submitted - * multiple semicolon delimited queries. - */ PortalDefineQuery(portal, NULL, - debug_query_string ? pstrdup(debug_query_string) : NULL, + queryString, "SELECT", /* cursor's query is always a SELECT */ list_make1(plan), - PortalGetHeapMemory(portal)); + NULL); - /* + /*---------- * Also copy the outer portal's parameter list into the inner portal's * memory context. We want to pass down the parameter values in case we - * had a command like DECLARE c CURSOR FOR SELECT ... WHERE foo = $1 This - * will have been parsed using the outer parameter set and the parameter - * value needs to be preserved for use when the cursor is executed. + * had a command like + * DECLARE c CURSOR FOR SELECT ... WHERE foo = $1 + * This will have been parsed using the outer parameter set and the + * parameter value needs to be preserved for use when the cursor is + * executed. + *---------- */ params = copyParamList(params); @@ -314,7 +329,6 @@ PersistHoldablePortal(Portal portal) Snapshot saveActiveSnapshot; ResourceOwner saveResourceOwner; MemoryContext savePortalContext; - MemoryContext saveQueryContext; MemoryContext oldcxt; /* @@ -356,14 +370,12 @@ PersistHoldablePortal(Portal portal) saveActiveSnapshot = ActiveSnapshot; saveResourceOwner = CurrentResourceOwner; savePortalContext = PortalContext; - saveQueryContext = QueryContext; PG_TRY(); { ActivePortal = portal; ActiveSnapshot = queryDesc->snapshot; CurrentResourceOwner = portal->resowner; PortalContext = PortalGetHeapMemory(portal); - QueryContext = portal->queryContext; MemoryContextSwitchTo(PortalContext); @@ -434,7 +446,6 @@ PersistHoldablePortal(Portal portal) ActiveSnapshot = saveActiveSnapshot; CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; PG_RE_THROW(); } @@ -449,7 +460,6 @@ PersistHoldablePortal(Portal portal) ActiveSnapshot = saveActiveSnapshot; CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; /* * We can now release any subsidiary memory of the portal's heap context; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 8a5382c737..2c284cb9be 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -10,7 +10,7 @@ * Copyright (c) 2002-2007, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.69 2007/02/20 17:32:14 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.70 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -22,6 +22,10 @@ #include "commands/explain.h" #include "commands/prepare.h" #include "funcapi.h" +#include "parser/analyze.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" #include "rewrite/rewriteHandler.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -39,20 +43,24 @@ static HTAB *prepared_queries = NULL; static void InitQueryHashTable(void); -static ParamListInfo EvaluateParams(EState *estate, - List *params, List *argtypes); -static Datum build_regtype_array(List *oid_list); +static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params, + const char *queryString, EState *estate); +static Datum build_regtype_array(Oid *param_types, int num_params); /* * Implements the 'PREPARE' utility statement. */ void -PrepareQuery(PrepareStmt *stmt) +PrepareQuery(PrepareStmt *stmt, const char *queryString) { - const char *commandTag; + Oid *argtypes = NULL; + int nargs; + List *queries; Query *query; + const char *commandTag; List *query_list, *plan_list; + int i; /* * Disallow empty-string statement name (conflicts with protocol-level @@ -63,7 +71,70 @@ PrepareQuery(PrepareStmt *stmt) (errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION), errmsg("invalid statement name: must not be empty"))); - switch (stmt->query->commandType) + /* Transform list of TypeNames to array of type OIDs */ + nargs = list_length(stmt->argtypes); + + if (nargs) + { + ParseState *pstate; + ListCell *l; + + /* + * typenameTypeId wants a ParseState to carry the source query string. + * Is it worth refactoring its API to avoid this? + */ + pstate = make_parsestate(NULL); + pstate->p_sourcetext = queryString; + + argtypes = (Oid *) palloc(nargs * sizeof(Oid)); + i = 0; + + foreach(l, stmt->argtypes) + { + TypeName *tn = lfirst(l); + Oid toid = typenameTypeId(pstate, tn); + + argtypes[i++] = toid; + } + } + + /* + * Analyze the statement using these parameter types (any parameters + * passed in from above us will not be visible to it), allowing + * information about unknown parameters to be deduced from context. + * + * Because parse analysis scribbles on the raw querytree, we must make + * a copy to ensure we have a pristine raw tree to cache. FIXME someday. + */ + queries = parse_analyze_varparams((Node *) copyObject(stmt->query), + queryString, + &argtypes, &nargs); + + /* + * Check that all parameter types were determined. + */ + for (i = 0; i < nargs; i++) + { + Oid argtype = argtypes[i]; + + if (argtype == InvalidOid || argtype == UNKNOWNOID) + ereport(ERROR, + (errcode(ERRCODE_INDETERMINATE_DATATYPE), + errmsg("could not determine data type of parameter $%d", + i + 1))); + } + + /* + * Shouldn't get any extra statements, since grammar only allows + * OptimizableStmt + */ + if (list_length(queries) != 1) + elog(ERROR, "unexpected extra stuff in prepared statement"); + + query = (Query *) linitial(queries); + Assert(IsA(query, Query)); + + switch (query->commandType) { case CMD_SELECT: commandTag = "SELECT"; @@ -85,38 +156,22 @@ PrepareQuery(PrepareStmt *stmt) break; } - /* - * Parse analysis is already done, but we must still rewrite and plan the - * query. - */ - - /* - * Because the planner is not cool about not scribbling on its input, we - * make a preliminary copy of the source querytree. This prevents - * problems in the case that the PREPARE is in a portal or plpgsql - * function and is executed repeatedly. (See also the same hack in - * DECLARE CURSOR and EXPLAIN.) XXX the planner really shouldn't modify - * its input ... FIXME someday. - */ - query = copyObject(stmt->query); - /* Rewrite the query. The result could be 0, 1, or many queries. */ - AcquireRewriteLocks(query); query_list = QueryRewrite(query); /* Generate plans for queries. Snapshot is already set. */ plan_list = pg_plan_queries(query_list, NULL, false); /* - * Save the results. We don't have the query string for this PREPARE, but - * we do have the string we got from the client, so use that. + * Save the results. */ StorePreparedStatement(stmt->name, - debug_query_string, + stmt->query, + queryString, commandTag, + argtypes, + nargs, plan_list, - stmt->argtype_oids, - true, true); } @@ -124,13 +179,13 @@ PrepareQuery(PrepareStmt *stmt) * Implements the 'EXECUTE' utility statement. */ void -ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, +ExecuteQuery(ExecuteStmt *stmt, const char *queryString, + ParamListInfo params, DestReceiver *dest, char *completionTag) { PreparedStatement *entry; - char *query_string; + CachedPlan *cplan; List *plan_list; - MemoryContext qcontext; ParamListInfo paramLI = NULL; EState *estate = NULL; Portal portal; @@ -138,20 +193,15 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, /* Look it up in the hash table */ entry = FetchPreparedStatement(stmt->name, true); - /* - * Punt if not fully planned. (Currently, that only happens for the - * protocol-level unnamed statement, which can't be accessed from SQL; - * so there's no point in doing more than a quick check here.) - */ - if (!entry->fully_planned) + /* Shouldn't have a non-fully-planned plancache entry */ + if (!entry->plansource->fully_planned) elog(ERROR, "EXECUTE does not support unplanned prepared statements"); - - query_string = entry->query_string; - plan_list = entry->stmt_list; - qcontext = entry->context; + /* Shouldn't get any non-fixed-result cached plan, either */ + if (!entry->plansource->fixed_result) + elog(ERROR, "EXECUTE does not support variable-result cached plans"); /* Evaluate parameters, if any */ - if (entry->argtype_list != NIL) + if (entry->plansource->num_params > 0) { /* * Need an EState to evaluate parameters; must not delete it till end @@ -159,7 +209,8 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list); + paramLI = EvaluateParams(entry, stmt->params, + queryString, estate); } /* Create a new portal to run the query in */ @@ -168,22 +219,23 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, portal->visible = false; /* - * For CREATE TABLE / AS EXECUTE, make a copy of the stored query so that - * we can modify its destination (yech, but this has always been ugly). - * For regular EXECUTE we can just use the stored query where it sits, - * since the executor is read-only. + * For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query + * so that we can modify its destination (yech, but this has always been + * ugly). For regular EXECUTE we can just use the cached query, since the + * executor is read-only. */ if (stmt->into) { MemoryContext oldContext; PlannedStmt *pstmt; - qcontext = PortalGetHeapMemory(portal); - oldContext = MemoryContextSwitchTo(qcontext); + /* Replan if needed, and increment plan refcount transiently */ + cplan = RevalidateCachedPlan(entry->plansource, true); - if (query_string) - query_string = pstrdup(query_string); - plan_list = copyObject(plan_list); + /* Copy plan into portal's context, and modify */ + oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal)); + + plan_list = copyObject(cplan->stmt_list); if (list_length(plan_list) != 1) ereport(ERROR, @@ -198,21 +250,32 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, pstmt->into = copyObject(stmt->into); MemoryContextSwitchTo(oldContext); + + /* We no longer need the cached plan refcount ... */ + ReleaseCachedPlan(cplan, true); + /* ... and we don't want the portal to depend on it, either */ + cplan = NULL; + } + else + { + /* Replan if needed, and increment plan refcount for portal */ + cplan = RevalidateCachedPlan(entry->plansource, false); + plan_list = cplan->stmt_list; } PortalDefineQuery(portal, NULL, - query_string, - entry->commandTag, + entry->plansource->query_string, + entry->plansource->commandTag, plan_list, - qcontext); + cplan); /* * Run the portal to completion. */ PortalStart(portal, paramLI, ActiveSnapshot); - (void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag); + (void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag); PortalDrop(portal, false); @@ -223,42 +286,106 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, } /* - * Evaluates a list of parameters, using the given executor state. It - * requires a list of the parameter expressions themselves, and a list of - * their types. It returns a filled-in ParamListInfo -- this can later - * be passed to CreateQueryDesc(), which allows the executor to make use - * of the parameters during query execution. + * EvaluateParams: evaluate a list of parameters. + * + * pstmt: statement we are getting parameters for. + * params: list of given parameter expressions (raw parser output!) + * queryString: source text for error messages. + * estate: executor state to use. + * + * Returns a filled-in ParamListInfo -- this can later be passed to + * CreateQueryDesc(), which allows the executor to make use of the parameters + * during query execution. */ static ParamListInfo -EvaluateParams(EState *estate, List *params, List *argtypes) +EvaluateParams(PreparedStatement *pstmt, List *params, + const char *queryString, EState *estate) { - int nargs = list_length(argtypes); + Oid *param_types = pstmt->plansource->param_types; + int num_params = pstmt->plansource->num_params; + int nparams = list_length(params); + ParseState *pstate; ParamListInfo paramLI; List *exprstates; - ListCell *le, - *la; - int i = 0; + ListCell *l; + int i; - /* Parser should have caught this error, but check for safety */ - if (list_length(params) != nargs) - elog(ERROR, "wrong number of arguments"); + 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))); - if (nargs == 0) + /* 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"); - plan_list = entry->stmt_list; + /* Replan if needed, and acquire a transient refcount */ + cplan = RevalidateCachedPlan(entry->plansource, true); + + plan_list = cplan->stmt_list; /* Evaluate parameters, if any */ - if (entry->argtype_list != NIL) + if (entry->plansource->num_params) { /* * Need an EState to evaluate parameters; must not delete it till end @@ -598,8 +640,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, */ estate = CreateExecutorState(); estate->es_param_list_info = params; - paramLI = EvaluateParams(estate, execstmt->params, - entry->argtype_list); + paramLI = EvaluateParams(entry, execstmt->params, + queryString, estate); } /* Explain each query */ @@ -610,14 +652,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, is_last_query = (lnext(p) == NULL); - if (!IsA(pstmt, PlannedStmt)) - { - if (IsA(pstmt, NotifyStmt)) - do_text_output_oneline(tstate, "NOTIFY"); - else - do_text_output_oneline(tstate, "UTILITY"); - } - else + if (IsA(pstmt, PlannedStmt)) { QueryDesc *qdesc; @@ -651,6 +686,11 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, ExplainOnePlan(qdesc, stmt, tstate); } + else + { + ExplainOneUtility((Node *) pstmt, stmt, queryString, + params, tstate); + } /* No need for CommandCounterIncrement, as ExplainOnePlan did it */ @@ -661,6 +701,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, if (estate) FreeExecutorState(estate); + + ReleaseCachedPlan(cplan, true); } /* @@ -739,14 +781,15 @@ pg_prepared_statement(PG_FUNCTION_ARGS) values[0] = DirectFunctionCall1(textin, CStringGetDatum(prep_stmt->stmt_name)); - if (prep_stmt->query_string == NULL) + if (prep_stmt->plansource->query_string == NULL) nulls[1] = true; else values[1] = DirectFunctionCall1(textin, - CStringGetDatum(prep_stmt->query_string)); + CStringGetDatum(prep_stmt->plansource->query_string)); values[2] = TimestampTzGetDatum(prep_stmt->prepare_time); - values[3] = build_regtype_array(prep_stmt->argtype_list); + values[3] = build_regtype_array(prep_stmt->plansource->param_types, + prep_stmt->plansource->num_params); values[4] = BoolGetDatum(prep_stmt->from_sql); tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); @@ -758,29 +801,23 @@ pg_prepared_statement(PG_FUNCTION_ARGS) } /* - * This utility function takes a List of Oids, and returns a Datum - * pointing to a one-dimensional Postgres array of regtypes. The empty - * list is returned as a zero-element array, not NULL. + * This utility function takes a C array of Oids, and returns a Datum + * pointing to a one-dimensional Postgres array of regtypes. An empty + * array is returned as a zero-element array, not NULL. */ static Datum -build_regtype_array(List *oid_list) +build_regtype_array(Oid *param_types, int num_params) { - ListCell *lc; - int len; - int i; Datum *tmp_ary; ArrayType *result; + int i; - len = list_length(oid_list); - tmp_ary = (Datum *) palloc(len * sizeof(Datum)); + tmp_ary = (Datum *) palloc(num_params * sizeof(Datum)); - i = 0; - foreach(lc, oid_list) - { - tmp_ary[i++] = ObjectIdGetDatum(lfirst_oid(lc)); - } + for (i = 0; i < num_params; i++) + tmp_ary[i] = ObjectIdGetDatum(param_types[i]); /* XXX: this hardcodes assumptions about the regtype type */ - result = construct_array(tmp_ary, len, REGTYPEOID, 4, true, 'i'); + result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i'); return PointerGetDatum(result); } diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index 09c2ca9f63..0912b8a62c 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.43 2007/02/01 19:10:26 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.44 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -38,7 +38,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI * CREATE SCHEMA */ void -CreateSchemaCommand(CreateSchemaStmt *stmt) +CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) { const char *schemaName = stmt->schemaname; const char *authId = stmt->authid; @@ -122,7 +122,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt) List *querytree_list; ListCell *querytree_item; - querytree_list = parse_analyze(parsetree, NULL, NULL, 0); + querytree_list = parse_analyze(parsetree, queryString, NULL, 0); foreach(querytree_item, querytree_list) { @@ -131,7 +131,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt) /* schemas should contain only utility stmts */ Assert(querytree->commandType == CMD_UTILITY); /* do this step */ - ProcessUtility(querytree->utilityStmt, NULL, None_Receiver, NULL); + ProcessUtility(querytree->utilityStmt, + queryString, + NULL, + false, /* not top level */ + None_Receiver, + NULL); /* make sure later steps can see the object created here */ CommandCounterIncrement(); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index ebc974f873..ddc62086f5 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.216 2007/03/06 02:06:13 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.217 2007/03/13 00:33:39 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3696,6 +3696,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, /* suppress notices when rebuilding existing index */ quiet = is_rebuild; + /* + * Run parse analysis. We don't have convenient access to the query text + * here, but it's probably not worth worrying about. + */ + stmt = analyzeIndexStmt(stmt, NULL); + + /* ... and do it */ DefineIndex(stmt->relation, /* relation */ stmt->idxname, /* index name */ InvalidOid, /* no predefined OID */ @@ -3703,7 +3710,6 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel, stmt->tableSpace, stmt->indexParams, /* parameters */ (Expr *) stmt->whereClause, - stmt->rangetable, stmt->options, stmt->unique, stmt->primary, diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index aa2b33c932..8e3bfbda86 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.43 2007/03/06 02:06:13 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.44 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -198,11 +198,6 @@ CreateTableSpace(CreateTableSpaceStmt *stmt) char *linkloc; Oid ownerId; - /* validate */ - - /* don't call this in a transaction block */ - PreventTransactionChain((void *) stmt, "CREATE TABLESPACE"); - /* Must be super user */ if (!superuser()) ereport(ERROR, @@ -385,9 +380,6 @@ DropTableSpace(DropTableSpaceStmt *stmt) ScanKeyData entry[1]; Oid tablespaceoid; - /* don't call this in a transaction block */ - PreventTransactionChain((void *) stmt, "DROP TABLESPACE"); - /* * Find the target tuple */ diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d13090ebf7..54864fbec9 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.347 2007/03/08 17:03:31 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.348 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -257,13 +257,14 @@ static Size PageGetFreeSpaceWithFillFactor(Relation relation, Page page); * relation OIDs to be processed, and vacstmt->relation is ignored. * (The non-NIL case is currently only used by autovacuum.) * + * isTopLevel should be passed down from ProcessUtility. + * * It is the caller's responsibility that both vacstmt and relids * (if given) be allocated in a memory context that won't disappear - * at transaction commit. In fact this context must be QueryContext - * to avoid complaints from PreventTransactionChain. + * at transaction commit. */ void -vacuum(VacuumStmt *vacstmt, List *relids) +vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel) { const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; volatile MemoryContext anl_context = NULL; @@ -293,11 +294,11 @@ vacuum(VacuumStmt *vacstmt, List *relids) */ if (vacstmt->vacuum) { - PreventTransactionChain((void *) vacstmt, stmttype); + PreventTransactionChain(isTopLevel, stmttype); in_outer_xact = false; } else - in_outer_xact = IsInTransactionChain((void *) vacstmt); + in_outer_xact = IsInTransactionChain(isTopLevel); /* * Send info about dead objects to the statistics collector, unless we are diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 42f9eafd3d..83f26f73ff 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.99 2007/01/05 22:19:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.100 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,6 +24,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" +#include "parser/analyze.h" #include "parser/parse_expr.h" #include "parser/parse_relation.h" #include "rewrite/rewriteDefine.h" @@ -258,54 +259,23 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc) */ } -static RuleStmt * -FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace) -{ - RuleStmt *rule; - - /* - * Create a RuleStmt that corresponds to the suitable rewrite rule args - * for DefineQueryRewrite(); - */ - rule = makeNode(RuleStmt); - rule->relation = copyObject((RangeVar *) view); - rule->rulename = pstrdup(ViewSelectRuleName); - rule->whereClause = NULL; - rule->event = CMD_SELECT; - rule->instead = true; - rule->actions = list_make1(viewParse); - rule->replace = replace; - - return rule; -} - static void DefineViewRules(const RangeVar *view, Query *viewParse, bool replace) { - RuleStmt *retrieve_rule; - -#ifdef NOTYET - RuleStmt *replace_rule; - RuleStmt *append_rule; - RuleStmt *delete_rule; -#endif - - retrieve_rule = FormViewRetrieveRule(view, viewParse, replace); - -#ifdef NOTYET - replace_rule = FormViewReplaceRule(view, viewParse); - append_rule = FormViewAppendRule(view, viewParse); - delete_rule = FormViewDeleteRule(view, viewParse); -#endif - - DefineQueryRewrite(retrieve_rule); - -#ifdef NOTYET - DefineQueryRewrite(replace_rule); - DefineQueryRewrite(append_rule); - DefineQueryRewrite(delete_rule); -#endif - + /* + * Set up the ON SELECT rule. Since the query has already been through + * parse analysis, we use DefineQueryRewrite() directly. + */ + DefineQueryRewrite(pstrdup(ViewSelectRuleName), + (RangeVar *) copyObject((RangeVar *) view), + NULL, + CMD_SELECT, + true, + replace, + list_make1(viewParse)); + /* + * Someday: automatic ON INSERT, etc + */ } /*--------------------------------------------------------------- @@ -374,34 +344,80 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse) return viewParse; } -/*------------------------------------------------------------------- +/* * DefineView - * - * - takes a "viewname", "parsetree" pair and then - * 1) construct the "virtual" relation - * 2) commit the command but NOT the transaction, - * so that the relation exists - * before the rules are defined. - * 2) define the "n" rules specified in the PRS2 paper - * over the "virtual" relation - *------------------------------------------------------------------- + * Execute a CREATE VIEW command. */ void -DefineView(RangeVar *view, Query *viewParse, bool replace) +DefineView(ViewStmt *stmt, const char *queryString) { + List *stmts; + Query *viewParse; Oid viewOid; + RangeVar *view; + + /* + * Run parse analysis to convert the raw parse tree to a Query. Note + * this also acquires sufficient locks on the source table(s). + * + * Since parse analysis scribbles on its input, copy the raw parse tree; + * this ensures we don't corrupt a prepared statement, for example. + */ + stmts = parse_analyze((Node *) copyObject(stmt->query), + queryString, NULL, 0); + + /* + * The grammar should ensure that the result is a single SELECT Query. + */ + if (list_length(stmts) != 1) + elog(ERROR, "unexpected parse analysis result"); + viewParse = (Query *) linitial(stmts); + if (!IsA(viewParse, Query) || + viewParse->commandType != CMD_SELECT) + elog(ERROR, "unexpected parse analysis result"); + + /* + * If a list of column names was given, run through and insert these into + * the actual query tree. - thomas 2000-03-08 + */ + if (stmt->aliases != NIL) + { + ListCell *alist_item = list_head(stmt->aliases); + ListCell *targetList; + + foreach(targetList, viewParse->targetList) + { + TargetEntry *te = (TargetEntry *) lfirst(targetList); + + Assert(IsA(te, TargetEntry)); + /* junk columns don't get aliases */ + if (te->resjunk) + continue; + te->resname = pstrdup(strVal(lfirst(alist_item))); + alist_item = lnext(alist_item); + if (alist_item == NULL) + break; /* done assigning aliases */ + } + + if (alist_item != NULL) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("CREATE VIEW specifies more column " + "names than columns"))); + } /* * If the user didn't explicitly ask for a temporary view, check whether * we need one implicitly. */ - if (!view->istemp) + view = stmt->view; + if (!view->istemp && isViewOnTempTable(viewParse)) { - view->istemp = isViewOnTempTable(viewParse); - if (view->istemp) - ereport(NOTICE, - (errmsg("view \"%s\" will be a temporary view", - view->relname))); + view = copyObject(view); /* don't corrupt original command */ + view->istemp = true; + ereport(NOTICE, + (errmsg("view \"%s\" will be a temporary view", + view->relname))); } /* @@ -410,7 +426,8 @@ DefineView(RangeVar *view, Query *viewParse, bool replace) * NOTE: if it already exists and replace is false, the xact will be * aborted. */ - viewOid = DefineVirtualRelation(view, viewParse->targetList, replace); + viewOid = DefineVirtualRelation(view, viewParse->targetList, + stmt->replace); /* * The relation we have just created is not visible to any other commands @@ -428,7 +445,7 @@ DefineView(RangeVar *view, Query *viewParse, bool replace) /* * Now create the rules associated with the view. */ - DefineViewRules(view, viewParse, replace); + DefineViewRules(view, viewParse, stmt->replace); } /* diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index 596a482fa1..7e648f437b 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.111 2007/02/20 17:32:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.112 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -58,6 +58,8 @@ typedef struct local_es */ typedef struct { + char *src; /* function body text (for error msgs) */ + Oid *argtypes; /* resolved types of arguments */ Oid rettype; /* actual return type */ int16 typlen; /* length of the return type */ @@ -82,7 +84,8 @@ static execution_state *init_execution_state(List *queryTree_list, bool readonly_func); static void init_sql_fcache(FmgrInfo *finfo); static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache); -static TupleTableSlot *postquel_getnext(execution_state *es); +static TupleTableSlot *postquel_getnext(execution_state *es, + SQLFunctionCachePtr fcache); static void postquel_end(execution_state *es); static void postquel_sub_params(SQLFunctionCachePtr fcache, FunctionCallInfo fcinfo); @@ -156,7 +159,6 @@ init_sql_fcache(FmgrInfo *finfo) Form_pg_proc procedureStruct; SQLFunctionCachePtr fcache; Oid *argOidVect; - char *src; int nargs; List *queryTree_list; Datum tmp; @@ -233,7 +235,7 @@ init_sql_fcache(FmgrInfo *finfo) fcache->argtypes = argOidVect; /* - * Parse and rewrite the queries in the function text. + * And of course we need the function body text. */ tmp = SysCacheGetAttr(PROCOID, procedureTuple, @@ -241,9 +243,12 @@ init_sql_fcache(FmgrInfo *finfo) &isNull); if (isNull) elog(ERROR, "null prosrc for function %u", foid); - src = DatumGetCString(DirectFunctionCall1(textout, tmp)); + fcache->src = DatumGetCString(DirectFunctionCall1(textout, tmp)); - queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs); + /* + * Parse and rewrite the queries in the function text. + */ + queryTree_list = pg_parse_and_rewrite(fcache->src, argOidVect, nargs); /* * Check that the function returns the type it claims to. Although @@ -270,8 +275,6 @@ init_sql_fcache(FmgrInfo *finfo) fcache->func_state = init_execution_state(queryTree_list, fcache->readonly_func); - pfree(src); - ReleaseSysCache(procedureTuple); finfo->fn_extra = (void *) fcache; @@ -331,7 +334,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache) } static TupleTableSlot * -postquel_getnext(execution_state *es) +postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) { TupleTableSlot *result; Snapshot saveActiveSnapshot; @@ -345,8 +348,12 @@ postquel_getnext(execution_state *es) if (es->qd->operation == CMD_UTILITY) { - ProcessUtility(es->qd->utilitystmt, es->qd->params, - es->qd->dest, NULL); + ProcessUtility(es->qd->utilitystmt, + fcache->src, + es->qd->params, + false, /* not top level */ + es->qd->dest, + NULL); result = NULL; } else @@ -465,7 +472,7 @@ postquel_execute(execution_state *es, if (es->status == F_EXEC_START) postquel_start(es, fcache); - slot = postquel_getnext(es); + slot = postquel_getnext(es, fcache); if (TupIsNull(slot)) { @@ -754,21 +761,11 @@ sql_exec_error_callback(void *arg) * If there is a syntax error position, convert to internal syntax error */ syntaxerrposition = geterrposition(); - if (syntaxerrposition > 0) + if (syntaxerrposition > 0 && fcache->src) { - bool isnull; - Datum tmp; - char *prosrc; - - tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc, - &isnull); - if (isnull) - elog(ERROR, "null prosrc"); - prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp)); errposition(0); internalerrposition(syntaxerrposition); - internalerrquery(prosrc); - pfree(prosrc); + internalerrquery(fcache->src); } /* diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 33ec21286d..f538f508ba 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.170 2007/02/20 17:32:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.171 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -927,7 +927,7 @@ SPI_cursor_open(const char *name, void *plan, spiplan->query, CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)), stmt_list, - PortalGetHeapMemory(portal)); + NULL); MemoryContextSwitchTo(oldcontext); @@ -1471,7 +1471,12 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls, } else { - ProcessUtility(stmt, paramLI, dest, NULL); + ProcessUtility(stmt, + NULL, /* XXX provide query string? */ + paramLI, + false, /* not top level */ + dest, + NULL); /* Update "processed" if stmt returned tuples */ if (_SPI_current->tuptable) _SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 19cb0b0b6f..4ca9ba4c0e 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.369 2007/02/27 01:11:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.370 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2142,7 +2142,6 @@ _copyIndexStmt(IndexStmt *from) COPY_NODE_FIELD(indexParams); COPY_NODE_FIELD(options); COPY_NODE_FIELD(whereClause); - COPY_NODE_FIELD(rangetable); COPY_SCALAR_FIELD(unique); COPY_SCALAR_FIELD(primary); COPY_SCALAR_FIELD(isconstraint); @@ -2785,7 +2784,6 @@ _copyPrepareStmt(PrepareStmt *from) COPY_STRING_FIELD(name); COPY_NODE_FIELD(argtypes); - COPY_NODE_FIELD(argtype_oids); COPY_NODE_FIELD(query); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 1007930814..ae247dfa3e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.300 2007/02/22 22:00:23 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.301 2007/03/13 00:33:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -994,7 +994,6 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b) COMPARE_NODE_FIELD(indexParams); COMPARE_NODE_FIELD(options); COMPARE_NODE_FIELD(whereClause); - COMPARE_NODE_FIELD(rangetable); COMPARE_SCALAR_FIELD(unique); COMPARE_SCALAR_FIELD(primary); COMPARE_SCALAR_FIELD(isconstraint); @@ -1536,7 +1535,6 @@ _equalPrepareStmt(PrepareStmt *a, PrepareStmt *b) { COMPARE_STRING_FIELD(name); COMPARE_NODE_FIELD(argtypes); - COMPARE_NODE_FIELD(argtype_oids); COMPARE_NODE_FIELD(query); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index ca3c79812c..68025ab368 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.302 2007/02/27 01:11:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.303 2007/03/13 00:33:40 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1505,7 +1505,6 @@ _outIndexStmt(StringInfo str, IndexStmt *node) WRITE_NODE_FIELD(indexParams); WRITE_NODE_FIELD(options); WRITE_NODE_FIELD(whereClause); - WRITE_NODE_FIELD(rangetable); WRITE_BOOL_FIELD(unique); WRITE_BOOL_FIELD(primary); WRITE_BOOL_FIELD(isconstraint); diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c index 15e7b1d24f..07593c5547 100644 --- a/src/backend/nodes/params.c +++ b/src/backend/nodes/params.c @@ -8,7 +8,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.8 2007/01/05 22:19:30 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.9 2007/03/13 00:33:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -60,3 +60,34 @@ copyParamList(ParamListInfo from) return retval; } + +/* + * Extract an array of parameter type OIDs from a ParamListInfo. + * + * The result is allocated in CurrentMemoryContext. + */ +void +getParamListTypes(ParamListInfo params, + Oid **param_types, int *num_params) +{ + Oid *ptypes; + int i; + + if (params == NULL || params->numParams <= 0) + { + *param_types = NULL; + *num_params = 0; + return; + } + + ptypes = (Oid *) palloc(params->numParams * sizeof(Oid)); + *param_types = ptypes; + *num_params = params->numParams; + + for (i = 0; i < params->numParams; i++) + { + ParamExternData *prm = ¶ms->params[i]; + + ptypes[i] = prm->ptype; + } +} diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 0f8231c4f9..11d2119f3c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.237 2007/03/06 22:45:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.238 2007/03/13 00:33:41 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -3603,38 +3603,6 @@ query_tree_walker(Query *query, return true; if (range_table_walker(query->rtable, walker, context, flags)) return true; - if (query->utilityStmt) - { - /* - * Certain utility commands contain general-purpose Querys embedded in - * them --- if this is one, invoke the walker on the sub-Query. - */ - if (IsA(query->utilityStmt, CopyStmt)) - { - if (walker(((CopyStmt *) query->utilityStmt)->query, context)) - return true; - } - if (IsA(query->utilityStmt, DeclareCursorStmt)) - { - if (walker(((DeclareCursorStmt *) query->utilityStmt)->query, context)) - return true; - } - if (IsA(query->utilityStmt, ExplainStmt)) - { - if (walker(((ExplainStmt *) query->utilityStmt)->query, context)) - return true; - } - if (IsA(query->utilityStmt, PrepareStmt)) - { - if (walker(((PrepareStmt *) query->utilityStmt)->query, context)) - return true; - } - if (IsA(query->utilityStmt, ViewStmt)) - { - if (walker(((ViewStmt *) query->utilityStmt)->query, context)) - return true; - } - } return false; } diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 63a5999a40..a4e4418b14 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -1,12 +1,26 @@ /*------------------------------------------------------------------------- * * analyze.c - * transform the parse tree into a query tree + * transform the raw parse tree into a query tree + * + * For optimizable statements, we are careful to obtain a suitable lock on + * each referenced table, and other modules of the backend preserve or + * re-obtain these locks before depending on the results. It is therefore + * okay to do significant semantic analysis of these statements. For + * utility commands, no locks are obtained here (and if they were, we could + * not be sure we'd still have them at execution). Hence the general rule + * for utility commands is to just dump them into a Query node untransformed. + * parse_analyze does do some purely syntactic transformations on CREATE TABLE + * and ALTER TABLE, but that's about it. In cases where this module contains + * mechanisms that are useful for utility statements, we provide separate + * subroutines that should be called at the beginning of utility execution; + * an example is analyzeIndexStmt. + * * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.361 2007/02/20 17:32:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.362 2007/03/13 00:33:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -93,26 +107,17 @@ typedef struct static List *do_parse_analyze(Node *parseTree, ParseState *pstate); static Query *transformStmt(ParseState *pstate, Node *stmt, List **extras_before, List **extras_after); -static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt, - List **extras_before, List **extras_after); static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt); static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt, List **extras_before, List **extras_after); static List *transformInsertRow(ParseState *pstate, List *exprlist, List *stmtcols, List *icolumns, List *attrnos); static List *transformReturningList(ParseState *pstate, List *returningList); -static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt); -static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt, - List **extras_before, List **extras_after); static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt); static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt); static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt); static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt); static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt); -static Query *transformDeclareCursorStmt(ParseState *pstate, - DeclareCursorStmt *stmt); -static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt); -static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt); static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt, List **extras_before, List **extras_after); static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt, @@ -155,7 +160,7 @@ static bool check_parameter_resolution_walker(Node *node, * * The result is a List of Query nodes (we need a list since some commands * produce multiple Queries). Optimizable statements require considerable - * transformation, while many utility-type statements are simply hung off + * transformation, while most utility-type statements are simply hung off * a dummy CMD_UTILITY Query node. */ List * @@ -315,59 +320,12 @@ transformStmt(ParseState *pstate, Node *parseTree, extras_before, extras_after); break; - case T_IndexStmt: - result = transformIndexStmt(pstate, (IndexStmt *) parseTree); - break; - - case T_RuleStmt: - result = transformRuleStmt(pstate, (RuleStmt *) parseTree, - extras_before, extras_after); - break; - - case T_ViewStmt: - result = transformViewStmt(pstate, (ViewStmt *) parseTree, - extras_before, extras_after); - break; - - case T_ExplainStmt: - { - ExplainStmt *n = (ExplainStmt *) parseTree; - - result = makeNode(Query); - result->commandType = CMD_UTILITY; - n->query = transformStmt(pstate, (Node *) n->query, - extras_before, extras_after); - result->utilityStmt = (Node *) parseTree; - } - break; - - case T_CopyStmt: - { - CopyStmt *n = (CopyStmt *) parseTree; - - result = makeNode(Query); - result->commandType = CMD_UTILITY; - if (n->query) - n->query = transformStmt(pstate, (Node *) n->query, - extras_before, extras_after); - result->utilityStmt = (Node *) parseTree; - } - break; - case T_AlterTableStmt: result = transformAlterTableStmt(pstate, (AlterTableStmt *) parseTree, extras_before, extras_after); break; - case T_PrepareStmt: - result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree); - break; - - case T_ExecuteStmt: - result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree); - break; - /* * Optimizable statements */ @@ -397,16 +355,11 @@ transformStmt(ParseState *pstate, Node *parseTree, } break; - case T_DeclareCursorStmt: - result = transformDeclareCursorStmt(pstate, - (DeclareCursorStmt *) parseTree); - break; - default: /* - * other statements don't require any transformation-- just return - * the original parsetree, yea! + * other statements don't require any transformation; just return + * the original parsetree with a Query node plastered on top. */ result = makeNode(Query); result->commandType = CMD_UTILITY; @@ -432,54 +385,6 @@ transformStmt(ParseState *pstate, Node *parseTree, return result; } -static Query * -transformViewStmt(ParseState *pstate, ViewStmt *stmt, - List **extras_before, List **extras_after) -{ - Query *result = makeNode(Query); - - result->commandType = CMD_UTILITY; - result->utilityStmt = (Node *) stmt; - - stmt->query = transformStmt(pstate, (Node *) stmt->query, - extras_before, extras_after); - - /* - * If a list of column names was given, run through and insert these into - * the actual query tree. - thomas 2000-03-08 - * - * Outer loop is over targetlist to make it easier to skip junk targetlist - * entries. - */ - if (stmt->aliases != NIL) - { - ListCell *alist_item = list_head(stmt->aliases); - ListCell *targetList; - - foreach(targetList, stmt->query->targetList) - { - TargetEntry *te = (TargetEntry *) lfirst(targetList); - - Assert(IsA(te, TargetEntry)); - /* junk columns don't get aliases */ - if (te->resjunk) - continue; - te->resname = pstrdup(strVal(lfirst(alist_item))); - alist_item = lnext(alist_item); - if (alist_item == NULL) - break; /* done assigning aliases */ - } - - if (alist_item != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE VIEW specifies more column " - "names than columns"))); - } - - return result; -} - /* * transformDeleteStmt - * transforms a Delete Statement @@ -1278,8 +1183,13 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt, /* * transformInhRelation * - * Change the LIKE 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); + + /* + * 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); /* 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); - - /* no to join list, yes to namespaces */ - addRTEtoQuery(pstate, rte, false, true, true); - - stmt->whereClause = transformWhereClause(pstate, 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 diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3204d0a401..1ce7170040 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.580 2007/02/20 17:32:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.581 2007/03/13 00:33:41 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -1662,7 +1662,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids { CopyStmt *n = makeNode(CopyStmt); n->relation = NULL; - n->query = (Query *) $2; + n->query = $2; n->attlist = NIL; n->is_from = false; n->filename = $4; @@ -4959,22 +4959,22 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list AS SelectStmt opt_check_option { ViewStmt *n = makeNode(ViewStmt); - n->replace = false; n->view = $4; n->view->istemp = $2; n->aliases = $5; - n->query = (Query *) $7; + n->query = $7; + n->replace = false; $$ = (Node *) n; } | CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list AS SelectStmt opt_check_option { ViewStmt *n = makeNode(ViewStmt); - n->replace = true; n->view = $6; n->view->istemp = $4; n->aliases = $7; - n->query = (Query *) $9; + n->query = $9; + n->replace = true; $$ = (Node *) n; } ; @@ -5406,7 +5406,7 @@ ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt ExplainStmt *n = makeNode(ExplainStmt); n->analyze = $2; n->verbose = $3; - n->query = (Query*)$4; + n->query = $4; $$ = (Node *)n; } ; @@ -5437,7 +5437,7 @@ PrepareStmt: PREPARE name prep_type_clause AS PreparableStmt PrepareStmt *n = makeNode(PrepareStmt); n->name = $2; n->argtypes = $3; - n->query = (Query *) $5; + n->query = $5; $$ = (Node *) n; } ; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 87384df8a2..b291855830 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.33 2007/03/07 13:35:02 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.34 2007/03/13 00:33:41 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1248,13 +1248,6 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze, vacstmt = makeNode(VacuumStmt); - /* - * Point QueryContext to the autovac memory context to fake out the - * PreventTransactionChain check inside vacuum(). Note that this is also - * why we palloc vacstmt instead of just using a local variable. - */ - QueryContext = CurrentMemoryContext; - /* Set up command parameters */ vacstmt->vacuum = dovacuum; vacstmt->full = false; @@ -1267,7 +1260,7 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze, /* Let pgstat know what we're doing */ autovac_report_activity(vacstmt, relid); - vacuum(vacstmt, list_make1_oid(relid)); + vacuum(vacstmt, list_make1_oid(relid), true); pfree(vacstmt); MemoryContextSwitchTo(old_cxt); diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c index d4212a4408..64a2a96f0e 100644 --- a/src/backend/rewrite/rewriteDefine.c +++ b/src/backend/rewrite/rewriteDefine.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.117 2007/02/01 19:10:27 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.118 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -20,6 +20,7 @@ #include "catalog/pg_rewrite.h" #include "miscadmin.h" #include "optimizer/clauses.h" +#include "parser/analyze.h" #include "parser/parse_expr.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteManip.h" @@ -177,15 +178,46 @@ InsertRule(char *rulname, return rewriteObjectId; } +/* + * DefineRule + * Execute a CREATE RULE command. + */ void -DefineQueryRewrite(RuleStmt *stmt) +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(char *rulename, + RangeVar *event_obj, + Node *event_qual, + CmdType event_type, + bool is_instead, + bool replace, + List *action) { - RangeVar *event_obj = stmt->relation; - Node *event_qual = stmt->whereClause; - CmdType event_type = stmt->event; - bool is_instead = stmt->instead; - bool replace = stmt->replace; - List *action = stmt->actions; Relation event_relation; Oid ev_relid; Oid ruleId; @@ -304,7 +336,7 @@ DefineQueryRewrite(RuleStmt *stmt) /* * ... and finally the rule must be named _RETURN. */ - if (strcmp(stmt->rulename, ViewSelectRuleName) != 0) + if (strcmp(rulename, ViewSelectRuleName) != 0) { /* * In versions before 7.3, the expected name was _RETviewname. For @@ -315,14 +347,14 @@ DefineQueryRewrite(RuleStmt *stmt) * worry about where a multibyte character might have gotten * truncated. */ - if (strncmp(stmt->rulename, "_RET", 4) != 0 || - strncmp(stmt->rulename + 4, event_obj->relname, + if (strncmp(rulename, "_RET", 4) != 0 || + strncmp(rulename + 4, event_obj->relname, NAMEDATALEN - 4 - 4) != 0) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("view rule for \"%s\" must be named \"%s\"", event_obj->relname, ViewSelectRuleName))); - stmt->rulename = pstrdup(ViewSelectRuleName); + rulename = pstrdup(ViewSelectRuleName); } /* @@ -411,7 +443,7 @@ DefineQueryRewrite(RuleStmt *stmt) /* discard rule if it's null action and not INSTEAD; it's a no-op */ if (action != NIL || is_instead) { - ruleId = InsertRule(stmt->rulename, + ruleId = InsertRule(rulename, event_type, ev_relid, event_attno, diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index cfb6731b23..f997d52410 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.527 2007/03/03 19:32:54 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.528 2007/03/13 00:33:42 tgl Exp $ * * NOTES * this is the "main" module of the postgres backend and @@ -140,8 +140,9 @@ static bool ignore_till_sync = false; * We keep it separate from the hashtable kept by commands/prepare.c * in order to reduce overhead for short-lived queries. */ +static CachedPlanSource *unnamed_stmt_psrc = NULL; +/* workspace for building a new unnamed statement in */ static MemoryContext unnamed_stmt_context = NULL; -static PreparedStatement *unnamed_stmt_pstmt = NULL; static bool EchoQuery = false; /* default don't echo */ @@ -173,6 +174,7 @@ static void finish_xact_command(void); static bool IsTransactionExitStmt(Node *parsetree); static bool IsTransactionExitStmtList(List *parseTrees); static bool IsTransactionStmtList(List *parseTrees); +static void drop_unnamed_stmt(void); static void SigHupHandler(SIGNAL_ARGS); static void log_disconnections(int code, Datum arg); @@ -794,21 +796,13 @@ exec_simple_query(const char *query_string) * statement and portal; this ensures we recover any storage used by prior * unnamed operations.) */ - unnamed_stmt_pstmt = NULL; - if (unnamed_stmt_context) - { - DropDependentPortals(unnamed_stmt_context); - MemoryContextDelete(unnamed_stmt_context); - } - unnamed_stmt_context = NULL; + drop_unnamed_stmt(); /* * Switch to appropriate context for constructing parsetrees. */ oldcontext = MemoryContextSwitchTo(MessageContext); - QueryContext = CurrentMemoryContext; - /* * Do basic parsing of the query or queries (this should be safe even if * we are in aborted transaction state!) @@ -906,7 +900,7 @@ exec_simple_query(const char *query_string) query_string, commandTag, plantree_list, - MessageContext); + NULL); /* * Start the portal. No parameters here. @@ -950,6 +944,7 @@ exec_simple_query(const char *query_string) */ (void) PortalRun(portal, FETCH_ALL, + true, /* top level */ receiver, receiver, completionTag); @@ -1009,8 +1004,6 @@ exec_simple_query(const char *query_string) if (!parsetree_list) NullCommand(dest); - QueryContext = NULL; - /* * Emit duration logging if appropriate. */ @@ -1049,10 +1042,10 @@ exec_parse_message(const char *query_string, /* string to execute */ { MemoryContext oldcontext; List *parsetree_list; + Node *raw_parse_tree; const char *commandTag; List *querytree_list, - *stmt_list, - *param_list; + *stmt_list; bool is_named; bool fully_planned; bool save_log_statement_stats = log_statement_stats; @@ -1088,12 +1081,12 @@ exec_parse_message(const char *query_string, /* string to execute */ * We have two strategies depending on whether the prepared statement is * named or not. For a named prepared statement, we do parsing in * MessageContext and copy the finished trees into the prepared - * statement's private context; then the reset of MessageContext releases + * statement's plancache entry; then the reset of MessageContext releases * temporary space used by parsing and planning. For an unnamed prepared * statement, we assume the statement isn't going to hang around long, so * getting rid of temp space quickly is probably not worth the costs of - * copying parse/plan trees. So in this case, we set up a special context - * for the unnamed statement, and do all the parsing work therein. + * copying parse/plan trees. So in this case, we create the plancache + * entry's context here, and do all the parsing work therein. */ is_named = (stmt_name[0] != '\0'); if (is_named) @@ -1104,16 +1097,10 @@ exec_parse_message(const char *query_string, /* string to execute */ else { /* Unnamed prepared statement --- release any prior unnamed stmt */ - unnamed_stmt_pstmt = NULL; - if (unnamed_stmt_context) - { - DropDependentPortals(unnamed_stmt_context); - MemoryContextDelete(unnamed_stmt_context); - } - unnamed_stmt_context = NULL; - /* create context for parsing/planning */ + drop_unnamed_stmt(); + /* Create context for parsing/planning */ unnamed_stmt_context = - AllocSetContextCreate(TopMemoryContext, + AllocSetContextCreate(CacheMemoryContext, "unnamed prepared statement", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, @@ -1121,8 +1108,6 @@ exec_parse_message(const char *query_string, /* string to execute */ oldcontext = MemoryContextSwitchTo(unnamed_stmt_context); } - QueryContext = CurrentMemoryContext; - /* * Do basic parsing of the query or queries (this should be safe even if * we are in aborted transaction state!) @@ -1141,13 +1126,14 @@ exec_parse_message(const char *query_string, /* string to execute */ if (parsetree_list != NIL) { - Node *parsetree = (Node *) linitial(parsetree_list); int i; + raw_parse_tree = (Node *) linitial(parsetree_list); + /* * Get the command name for possible use in status display. */ - commandTag = CreateCommandTag(parsetree); + commandTag = CreateCommandTag(raw_parse_tree); /* * If we are in an aborted transaction, reject all commands except @@ -1158,7 +1144,7 @@ exec_parse_message(const char *query_string, /* string to execute */ * state, but not many...) */ if (IsAbortedTransactionBlockState() && - !IsTransactionExitStmt(parsetree)) + !IsTransactionExitStmt(raw_parse_tree)) ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), errmsg("current transaction is aborted, " @@ -1168,20 +1154,22 @@ exec_parse_message(const char *query_string, /* string to execute */ * OK to analyze, rewrite, and plan this query. Note that the * originally specified parameter set is not required to be complete, * so we have to use parse_analyze_varparams(). + * + * XXX must use copyObject here since parse analysis scribbles on + * its input, and we need the unmodified raw parse tree for possible + * replanning later. */ if (log_parser_stats) ResetUsage(); - querytree_list = parse_analyze_varparams(parsetree, + querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree), query_string, ¶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; - qContext = PortalGetHeapMemory(portal); - oldContext = MemoryContextSwitchTo(qContext); - query_list = copyObject(pstmt->stmt_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); + + /* + * 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': diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 97a003ac89..b54dea45dc 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.114 2007/02/20 17:32:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.115 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -36,14 +36,14 @@ static void ProcessQuery(PlannedStmt *plan, ParamListInfo params, DestReceiver *dest, char *completionTag); -static void FillPortalStore(Portal portal); +static void FillPortalStore(Portal portal, bool isTopLevel); static uint32 RunFromStore(Portal portal, ScanDirection direction, long count, DestReceiver *dest); static long PortalRunSelect(Portal portal, bool forward, long count, DestReceiver *dest); -static void PortalRunUtility(Portal portal, Node *utilityStmt, +static void PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel, DestReceiver *dest, char *completionTag); -static void PortalRunMulti(Portal portal, +static void PortalRunMulti(Portal portal, bool isTopLevel, DestReceiver *dest, DestReceiver *altdest, char *completionTag); static long DoPortalRunFetch(Portal portal, @@ -148,8 +148,7 @@ ProcessQuery(PlannedStmt *plan, { QueryDesc *queryDesc; - ereport(DEBUG3, - (errmsg_internal("ProcessQuery"))); + elog(DEBUG3, "ProcessQuery"); /* * Must always set snapshot for plannable queries. Note we assume that @@ -232,8 +231,7 @@ ProcessQuery(PlannedStmt *plan, * Select portal execution strategy given the intended statement list. * * The list elements can be Querys, PlannedStmts, or utility statements. - * That's more general than portals need, but we use this for prepared - * statements as well. + * That's more general than portals need, but plancache.c uses this too. * * See the comments in portal.h. */ @@ -358,8 +356,7 @@ FetchPortalTargetList(Portal portal) * Returns NIL if the statement doesn't have a determinable targetlist. * * This can be applied to a Query, a PlannedStmt, or a utility statement. - * That's more general than portals need, but we use this for prepared - * statements as well. + * That's more general than portals need, but plancache.c uses this too. * * Note: do not modify the result. * @@ -452,11 +449,10 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot) int eflags; AssertArg(PortalIsValid(portal)); - AssertState(portal->queryContext != NULL); /* query defined? */ - AssertState(portal->status == PORTAL_NEW); /* else extra PortalStart */ + AssertState(portal->status == PORTAL_DEFINED); /* - * Set up global portal context pointers. (Should we set QueryContext?) + * Set up global portal context pointers. */ saveActivePortal = ActivePortal; saveActiveSnapshot = ActiveSnapshot; @@ -683,6 +679,9 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats) * interpreted as "all rows". Note that count is ignored in multi-query * situations, where we always run the portal to completion. * + * isTopLevel: true if query is being executed at backend "top level" + * (that is, directly from a client command message) + * * dest: where to send output of primary (canSetTag) query * * altdest: where to send output of non-primary queries @@ -695,7 +694,7 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats) * suspended due to exhaustion of the count parameter. */ bool -PortalRun(Portal portal, long count, +PortalRun(Portal portal, long count, bool isTopLevel, DestReceiver *dest, DestReceiver *altdest, char *completionTag) { @@ -706,7 +705,6 @@ PortalRun(Portal portal, long count, Snapshot saveActiveSnapshot; ResourceOwner saveResourceOwner; MemoryContext savePortalContext; - MemoryContext saveQueryContext; MemoryContext saveMemoryContext; AssertArg(PortalIsValid(portal)); @@ -717,8 +715,7 @@ PortalRun(Portal portal, long count, if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY) { - ereport(DEBUG3, - (errmsg_internal("PortalRun"))); + elog(DEBUG3, "PortalRun"); /* PORTAL_MULTI_QUERY logs its own stats per query */ ResetUsage(); } @@ -752,7 +749,6 @@ PortalRun(Portal portal, long count, saveActiveSnapshot = ActiveSnapshot; saveResourceOwner = CurrentResourceOwner; savePortalContext = PortalContext; - saveQueryContext = QueryContext; saveMemoryContext = CurrentMemoryContext; PG_TRY(); { @@ -760,7 +756,6 @@ PortalRun(Portal portal, long count, ActiveSnapshot = NULL; /* will be set later */ CurrentResourceOwner = portal->resowner; PortalContext = PortalGetHeapMemory(portal); - QueryContext = portal->queryContext; MemoryContextSwitchTo(PortalContext); @@ -790,7 +785,7 @@ PortalRun(Portal portal, long count, * results in the portal's tuplestore. */ if (!portal->holdStore) - FillPortalStore(portal); + FillPortalStore(portal, isTopLevel); /* * Now fetch desired portion of results. @@ -811,7 +806,8 @@ PortalRun(Portal portal, long count, break; case PORTAL_MULTI_QUERY: - PortalRunMulti(portal, dest, altdest, completionTag); + PortalRunMulti(portal, isTopLevel, + dest, altdest, completionTag); /* Prevent portal's commands from being re-executed */ portal->status = PORTAL_DONE; @@ -844,7 +840,6 @@ PortalRun(Portal portal, long count, else CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; PG_RE_THROW(); } @@ -861,7 +856,6 @@ PortalRun(Portal portal, long count, else CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY) ShowUsage("EXECUTOR STATISTICS"); @@ -1025,7 +1019,7 @@ PortalRunSelect(Portal portal, * This is used for PORTAL_ONE_RETURNING and PORTAL_UTIL_SELECT cases only. */ static void -FillPortalStore(Portal portal) +FillPortalStore(Portal portal, bool isTopLevel) { DestReceiver *treceiver; char completionTag[COMPLETION_TAG_BUFSIZE]; @@ -1044,12 +1038,13 @@ FillPortalStore(Portal portal) * MULTI_QUERY case, but send the primary query's output to the * tuplestore. Auxiliary query outputs are discarded. */ - PortalRunMulti(portal, treceiver, None_Receiver, completionTag); + PortalRunMulti(portal, isTopLevel, + treceiver, None_Receiver, completionTag); break; case PORTAL_UTIL_SELECT: PortalRunUtility(portal, (Node *) linitial(portal->stmts), - treceiver, completionTag); + isTopLevel, treceiver, completionTag); break; default: @@ -1137,11 +1132,10 @@ RunFromStore(Portal portal, ScanDirection direction, long count, * Execute a utility statement inside a portal. */ static void -PortalRunUtility(Portal portal, Node *utilityStmt, +PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel, DestReceiver *dest, char *completionTag) { - ereport(DEBUG3, - (errmsg_internal("ProcessUtility"))); + elog(DEBUG3, "ProcessUtility"); /* * Set snapshot if utility stmt needs one. Most reliable way to do this @@ -1173,7 +1167,12 @@ PortalRunUtility(Portal portal, Node *utilityStmt, else ActiveSnapshot = NULL; - ProcessUtility(utilityStmt, portal->portalParams, dest, completionTag); + ProcessUtility(utilityStmt, + portal->sourceText, + portal->portalParams, + isTopLevel, + dest, + completionTag); /* Some utility statements may change context on us */ MemoryContextSwitchTo(PortalGetHeapMemory(portal)); @@ -1189,7 +1188,7 @@ PortalRunUtility(Portal portal, Node *utilityStmt, * or non-SELECT-like queries) */ static void -PortalRunMulti(Portal portal, +PortalRunMulti(Portal portal, bool isTopLevel, DestReceiver *dest, DestReceiver *altdest, char *completionTag) { @@ -1260,9 +1259,9 @@ PortalRunMulti(Portal portal, * portal. */ if (list_length(portal->stmts) == 1) - PortalRunUtility(portal, stmt, dest, completionTag); + PortalRunUtility(portal, stmt, isTopLevel, dest, completionTag); else - PortalRunUtility(portal, stmt, altdest, NULL); + PortalRunUtility(portal, stmt, isTopLevel, altdest, NULL); } /* @@ -1305,6 +1304,8 @@ PortalRunMulti(Portal portal, * PortalRunFetch * Variant form of PortalRun that supports SQL FETCH directions. * + * Note: we presently assume that no callers of this want isTopLevel = true. + * * Returns number of rows processed (suitable for use in result tag) */ long @@ -1318,7 +1319,6 @@ PortalRunFetch(Portal portal, Snapshot saveActiveSnapshot; ResourceOwner saveResourceOwner; MemoryContext savePortalContext; - MemoryContext saveQueryContext; MemoryContext oldContext; AssertArg(PortalIsValid(portal)); @@ -1339,14 +1339,12 @@ PortalRunFetch(Portal portal, saveActiveSnapshot = ActiveSnapshot; saveResourceOwner = CurrentResourceOwner; savePortalContext = PortalContext; - saveQueryContext = QueryContext; PG_TRY(); { ActivePortal = portal; ActiveSnapshot = NULL; /* will be set later */ CurrentResourceOwner = portal->resowner; PortalContext = PortalGetHeapMemory(portal); - QueryContext = portal->queryContext; oldContext = MemoryContextSwitchTo(PortalContext); @@ -1364,7 +1362,7 @@ PortalRunFetch(Portal portal, * results in the portal's tuplestore. */ if (!portal->holdStore) - FillPortalStore(portal); + FillPortalStore(portal, false /* isTopLevel */); /* * Now fetch desired portion of results. @@ -1388,7 +1386,6 @@ PortalRunFetch(Portal portal, ActiveSnapshot = saveActiveSnapshot; CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; PG_RE_THROW(); } @@ -1403,7 +1400,6 @@ PortalRunFetch(Portal portal, ActiveSnapshot = saveActiveSnapshot; CurrentResourceOwner = saveResourceOwner; PortalContext = savePortalContext; - QueryContext = saveQueryContext; return result; } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 47051ad1ed..be274a72f1 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.273 2007/02/20 17:32:16 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.274 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -44,6 +44,7 @@ #include "commands/vacuum.h" #include "commands/view.h" #include "miscadmin.h" +#include "parser/analyze.h" #include "postmaster/bgwriter.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteRemove.h" @@ -368,7 +369,9 @@ check_xact_readonly(Node *parsetree) * general utility function invoker * * parsetree: the parse tree for the utility statement + * queryString: original source text of command (NULL if not available) * params: parameters to use during execution + * isTopLevel: true if executing a "top level" (interactively issued) command * dest: where to send results * completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE * in which to store a command completion status string. @@ -379,7 +382,9 @@ check_xact_readonly(Node *parsetree) */ void ProcessUtility(Node *parsetree, + const char *queryString, ParamListInfo params, + bool isTopLevel, DestReceiver *dest, char *completionTag) { @@ -444,12 +449,12 @@ ProcessUtility(Node *parsetree, break; case TRANS_STMT_COMMIT_PREPARED: - PreventTransactionChain(stmt, "COMMIT PREPARED"); + PreventTransactionChain(isTopLevel, "COMMIT PREPARED"); FinishPreparedTransaction(stmt->gid, true); break; case TRANS_STMT_ROLLBACK_PREPARED: - PreventTransactionChain(stmt, "ROLLBACK PREPARED"); + PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED"); FinishPreparedTransaction(stmt->gid, false); break; @@ -462,7 +467,7 @@ ProcessUtility(Node *parsetree, ListCell *cell; char *name = NULL; - RequireTransactionChain((void *) stmt, "SAVEPOINT"); + RequireTransactionChain(isTopLevel, "SAVEPOINT"); foreach(cell, stmt->options) { @@ -479,12 +484,12 @@ ProcessUtility(Node *parsetree, break; case TRANS_STMT_RELEASE: - RequireTransactionChain((void *) stmt, "RELEASE SAVEPOINT"); + RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT"); ReleaseSavepoint(stmt->options); break; case TRANS_STMT_ROLLBACK_TO: - RequireTransactionChain((void *) stmt, "ROLLBACK TO SAVEPOINT"); + RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT"); RollbackToSavepoint(stmt->options); /* @@ -500,7 +505,8 @@ ProcessUtility(Node *parsetree, * Portal (cursor) manipulation */ case T_DeclareCursorStmt: - PerformCursorOpen((DeclareCursorStmt *) parsetree, params); + PerformCursorOpen((DeclareCursorStmt *) parsetree, params, + queryString, isTopLevel); break; case T_ClosePortalStmt: @@ -520,7 +526,8 @@ ProcessUtility(Node *parsetree, * relation and attribute manipulation */ case T_CreateSchemaStmt: - CreateSchemaCommand((CreateSchemaStmt *) parsetree); + CreateSchemaCommand((CreateSchemaStmt *) parsetree, + queryString); break; case T_CreateStmt: @@ -540,10 +547,12 @@ ProcessUtility(Node *parsetree, break; case T_CreateTableSpaceStmt: + PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); CreateTableSpace((CreateTableSpaceStmt *) parsetree); break; case T_DropTableSpaceStmt: + PreventTransactionChain(isTopLevel, "DROP TABLESPACE"); DropTableSpace((DropTableSpaceStmt *) parsetree); break; @@ -640,8 +649,9 @@ ProcessUtility(Node *parsetree, case T_CopyStmt: { - uint64 processed = DoCopy((CopyStmt *) parsetree); + uint64 processed; + processed = DoCopy((CopyStmt *) parsetree, queryString); if (completionTag) snprintf(completionTag, COMPLETION_TAG_BUFSIZE, "COPY " UINT64_FORMAT, processed); @@ -649,11 +659,11 @@ ProcessUtility(Node *parsetree, break; case T_PrepareStmt: - PrepareQuery((PrepareStmt *) parsetree); + PrepareQuery((PrepareStmt *) parsetree, queryString); break; case T_ExecuteStmt: - ExecuteQuery((ExecuteStmt *) parsetree, params, + ExecuteQuery((ExecuteStmt *) parsetree, queryString, params, dest, completionTag); break; @@ -769,12 +779,8 @@ ProcessUtility(Node *parsetree, } break; - case T_ViewStmt: /* CREATE VIEW */ - { - ViewStmt *stmt = (ViewStmt *) parsetree; - - DefineView(stmt->view, stmt->query, stmt->replace); - } + case T_ViewStmt: /* CREATE VIEW */ + DefineView((ViewStmt *) parsetree, queryString); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ @@ -790,10 +796,15 @@ ProcessUtility(Node *parsetree, IndexStmt *stmt = (IndexStmt *) parsetree; if (stmt->concurrent) - PreventTransactionChain(stmt, "CREATE INDEX CONCURRENTLY"); + PreventTransactionChain(isTopLevel, + "CREATE INDEX CONCURRENTLY"); CheckRelationOwnership(stmt->relation, true); + /* Run parse analysis ... */ + stmt = analyzeIndexStmt(stmt, queryString); + + /* ... and do it */ DefineIndex(stmt->relation, /* relation */ stmt->idxname, /* index name */ InvalidOid, /* no predefined OID */ @@ -801,7 +812,6 @@ ProcessUtility(Node *parsetree, stmt->tableSpace, stmt->indexParams, /* parameters */ (Expr *) stmt->whereClause, - stmt->rangetable, stmt->options, stmt->unique, stmt->primary, @@ -815,7 +825,7 @@ ProcessUtility(Node *parsetree, break; case T_RuleStmt: /* CREATE RULE */ - DefineQueryRewrite((RuleStmt *) parsetree); + DefineRule((RuleStmt *) parsetree, queryString); break; case T_CreateSeqStmt: @@ -850,6 +860,7 @@ ProcessUtility(Node *parsetree, break; case T_CreatedbStmt: + PreventTransactionChain(isTopLevel, "CREATE DATABASE"); createdb((CreatedbStmt *) parsetree); break; @@ -865,6 +876,7 @@ ProcessUtility(Node *parsetree, { DropdbStmt *stmt = (DropdbStmt *) parsetree; + PreventTransactionChain(isTopLevel, "DROP DATABASE"); dropdb(stmt->dbname, stmt->missing_ok); } break; @@ -905,15 +917,15 @@ ProcessUtility(Node *parsetree, break; case T_ClusterStmt: - cluster((ClusterStmt *) parsetree); + cluster((ClusterStmt *) parsetree, isTopLevel); break; case T_VacuumStmt: - vacuum((VacuumStmt *) parsetree, NIL); + vacuum((VacuumStmt *) parsetree, NIL, isTopLevel); break; case T_ExplainStmt: - ExplainQuery((ExplainStmt *) parsetree, params, dest); + ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest); break; case T_VariableSetStmt: @@ -1079,6 +1091,14 @@ ProcessUtility(Node *parsetree, ReindexTable(stmt->relation); break; case OBJECT_DATABASE: + /* + * This cannot run inside a user transaction block; + * if we were inside a transaction, then its commit- + * and start-transaction-command calls would not have + * the intended effect! + */ + PreventTransactionChain(isTopLevel, + "REINDEX DATABASE"); ReindexDatabase(stmt->name, stmt->do_system, stmt->do_user); break; @@ -1166,16 +1186,8 @@ UtilityReturnsTuples(Node *parsetree) entry = FetchPreparedStatement(stmt->name, false); if (!entry) return false; /* not our business to raise error */ - switch (ChoosePortalStrategy(entry->stmt_list)) - { - case PORTAL_ONE_SELECT: - case PORTAL_ONE_RETURNING: - case PORTAL_UTIL_SELECT: - return true; - case PORTAL_MULTI_QUERY: - /* will not return tuples */ - break; - } + if (entry->plansource->resultDesc) + return true; return false; } @@ -2134,7 +2146,7 @@ GetCommandLogLevel(Node *parsetree) /* Look through an EXPLAIN ANALYZE to the contained stmt */ if (stmt->analyze) - return GetCommandLogLevel((Node *) stmt->query); + return GetCommandLogLevel(stmt->query); /* Plain EXPLAIN isn't so interesting */ lev = LOGSTMT_ALL; } @@ -2245,30 +2257,21 @@ GetCommandLogLevel(Node *parsetree) PrepareStmt *stmt = (PrepareStmt *) parsetree; /* Look through a PREPARE to the contained stmt */ - return GetCommandLogLevel((Node *) stmt->query); + lev = GetCommandLogLevel(stmt->query); } break; case T_ExecuteStmt: { ExecuteStmt *stmt = (ExecuteStmt *) parsetree; - PreparedStatement *pstmt; - ListCell *l; + PreparedStatement *ps; - /* 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; - - stmt_lev = GetCommandLogLevel(substmt); - lev = Min(lev, stmt_lev); - } - } + /* Look through an EXECUTE to the referenced stmt */ + ps = FetchPreparedStatement(stmt->name, false); + if (ps) + lev = GetCommandLogLevel(ps->plansource->raw_parse_tree); + else + lev = LOGSTMT_ALL; } break; diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile index 18e920bc1d..879f013e33 100644 --- a/src/backend/utils/cache/Makefile +++ b/src/backend/utils/cache/Makefile @@ -4,7 +4,7 @@ # Makefile for utils/cache # # IDENTIFICATION -# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.20 2007/01/20 17:16:13 petere Exp $ +# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.21 2007/03/13 00:33:42 tgl Exp $ # #------------------------------------------------------------------------- @@ -12,7 +12,8 @@ subdir = src/backend/utils/cache top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global -OBJS = catcache.o inval.o relcache.o syscache.o lsyscache.o typcache.o +OBJS = catcache.o inval.o plancache.o relcache.o \ + syscache.o lsyscache.o typcache.o all: SUBSYS.o diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c new file mode 100644 index 0000000000..95ed49818c --- /dev/null +++ b/src/backend/utils/cache/plancache.c @@ -0,0 +1,862 @@ +/*------------------------------------------------------------------------- + * + * plancache.c + * Plan cache management. + * + * We can store a cached plan in either fully-planned format, or just + * parsed-and-rewritten if the caller wishes to postpone planning until + * actual parameter values are available. CachedPlanSource has the same + * contents either way, but CachedPlan contains a list of PlannedStmts + * and bare utility statements in the first case, or a list of Query nodes + * in the second case. + * + * The plan cache manager itself is principally responsible for tracking + * whether cached plans should be invalidated because of schema changes in + * the tables they depend on. When (and if) the next demand for a cached + * plan occurs, the query will be replanned. Note that this could result + * in an error, for example if a column referenced by the query is no + * longer present. The creator of a cached plan can specify whether it + * is allowable for the query to change output tupdesc on replan (this + * could happen with "SELECT *" for example) --- if so, it's up to the + * caller to notice changes and cope with them. + * + * Currently, we use only relcache invalidation events to invalidate plans. + * This means that changes such as modification of a function definition do + * not invalidate plans using the function. This is not 100% OK --- for + * example, changing a SQL function that's been inlined really ought to + * cause invalidation of the plan that it's been inlined into --- but the + * cost of tracking additional types of object seems much higher than the + * gain, so we're just ignoring them for now. + * + * + * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * $PostgreSQL: pgsql/src/backend/utils/cache/plancache.c,v 1.1 2007/03/13 00:33:42 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "utils/plancache.h" +#include "executor/executor.h" +#include "optimizer/clauses.h" +#include "storage/lmgr.h" +#include "tcop/pquery.h" +#include "tcop/tcopprot.h" +#include "tcop/utility.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/resowner.h" + + +typedef struct +{ + void (*callback) (); + void *arg; +} ScanQueryWalkerContext; + +typedef struct +{ + Oid inval_relid; + CachedPlan *plan; +} InvalRelidContext; + + +static List *cached_plans_list = NIL; + +static void StoreCachedPlan(CachedPlanSource *plansource, List *stmt_list, + MemoryContext plan_context); +static void AcquireExecutorLocks(List *stmt_list, bool acquire); +static void AcquirePlannerLocks(List *stmt_list, bool acquire); +static void LockRelid(Oid relid, LOCKMODE lockmode, void *arg); +static void UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg); +static void ScanQueryForRelids(Query *parsetree, + void (*callback) (), + void *arg); +static bool ScanQueryWalker(Node *node, ScanQueryWalkerContext *context); +static bool rowmark_member(List *rowMarks, int rt_index); +static TupleDesc ComputeResultDesc(List *stmt_list); +static void PlanCacheCallback(Datum arg, Oid relid); +static void InvalRelid(Oid relid, LOCKMODE lockmode, + InvalRelidContext *context); + + +/* + * InitPlanCache: initialize module during InitPostgres. + * + * All we need to do is hook into inval.c's callback list. + */ +void +InitPlanCache(void) +{ + CacheRegisterRelcacheCallback(PlanCacheCallback, (Datum) 0); +} + +/* + * CreateCachedPlan: initially create a plan cache entry. + * + * The caller must already have successfully parsed/planned the query; + * about all that we do here is copy it into permanent storage. + * + * raw_parse_tree: output of raw_parser() + * query_string: original query text (can be NULL if not available, but + * that is discouraged because it degrades error message quality) + * commandTag: compile-time-constant tag for query, or NULL if empty query + * param_types: array of parameter type OIDs, or NULL if none + * num_params: number of parameters + * stmt_list: list of PlannedStmts/utility stmts, or list of Query trees + * fully_planned: are we caching planner or rewriter output? + * fixed_result: TRUE to disallow changes in result tupdesc + */ +CachedPlanSource * +CreateCachedPlan(Node *raw_parse_tree, + const char *query_string, + const char *commandTag, + Oid *param_types, + int num_params, + List *stmt_list, + bool fully_planned, + bool fixed_result) +{ + CachedPlanSource *plansource; + MemoryContext source_context; + MemoryContext oldcxt; + + /* + * Make a dedicated memory context for the CachedPlanSource and its + * subsidiary data. We expect it can be pretty small. + */ + source_context = AllocSetContextCreate(CacheMemoryContext, + "CachedPlanSource", + ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, + ALLOCSET_SMALL_MAXSIZE); + + /* + * Create and fill the CachedPlanSource struct within the new context. + */ + oldcxt = MemoryContextSwitchTo(source_context); + plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); + plansource->raw_parse_tree = copyObject(raw_parse_tree); + plansource->query_string = query_string ? pstrdup(query_string) : NULL; + plansource->commandTag = commandTag; /* no copying needed */ + if (num_params > 0) + { + plansource->param_types = (Oid *) palloc(num_params * sizeof(Oid)); + memcpy(plansource->param_types, param_types, num_params * sizeof(Oid)); + } + else + plansource->param_types = NULL; + plansource->num_params = num_params; + plansource->fully_planned = fully_planned; + plansource->fixed_result = fixed_result; + plansource->generation = 0; /* StoreCachedPlan will increment */ + plansource->resultDesc = ComputeResultDesc(stmt_list); + plansource->plan = NULL; + plansource->context = source_context; + plansource->orig_plan = NULL; + + /* + * Copy the current output plans into the plancache entry. + */ + StoreCachedPlan(plansource, stmt_list, NULL); + + /* + * Now we can add the entry to the list of cached plans. The List nodes + * live in CacheMemoryContext. + */ + MemoryContextSwitchTo(CacheMemoryContext); + + cached_plans_list = lappend(cached_plans_list, plansource); + + MemoryContextSwitchTo(oldcxt); + + return plansource; +} + +/* + * FastCreateCachedPlan: create a plan cache entry with minimal data copying. + * + * For plans that aren't expected to live very long, the copying overhead of + * CreateCachedPlan is annoying. We provide this variant entry point in which + * the caller has already placed all the data in a suitable memory context. + * The source data and completed plan are in the same context, since this + * avoids extra copy steps during plan construction. If the query ever does + * need replanning, we'll generate a separate new CachedPlan at that time, but + * the CachedPlanSource and the initial CachedPlan share the caller-provided + * context and go away together when neither is needed any longer. (Because + * the parser and planner generate extra cruft in addition to their real + * output, this approach means that the context probably contains a bunch of + * useless junk as well as the useful trees. Hence, this method is a + * space-for-time tradeoff, which is worth making for plans expected to be + * short-lived.) + * + * raw_parse_tree, query_string, param_types, and stmt_list must reside in the + * given context, which must have adequate lifespan (recommendation: make it a + * child of CacheMemoryContext). Otherwise the API is the same as + * CreateCachedPlan. + */ +CachedPlanSource * +FastCreateCachedPlan(Node *raw_parse_tree, + char *query_string, + const char *commandTag, + Oid *param_types, + int num_params, + List *stmt_list, + bool fully_planned, + bool fixed_result, + MemoryContext context) +{ + CachedPlanSource *plansource; + MemoryContext oldcxt; + + /* + * Create and fill the CachedPlanSource struct within the given context. + */ + oldcxt = MemoryContextSwitchTo(context); + plansource = (CachedPlanSource *) palloc(sizeof(CachedPlanSource)); + plansource->raw_parse_tree = raw_parse_tree; + plansource->query_string = query_string; + plansource->commandTag = commandTag; /* no copying needed */ + plansource->param_types = param_types; + plansource->num_params = num_params; + plansource->fully_planned = fully_planned; + plansource->fixed_result = fixed_result; + plansource->generation = 0; /* StoreCachedPlan will increment */ + plansource->resultDesc = ComputeResultDesc(stmt_list); + plansource->plan = NULL; + plansource->context = context; + plansource->orig_plan = NULL; + + /* + * Store the current output plans into the plancache entry. + */ + StoreCachedPlan(plansource, stmt_list, context); + + /* + * Since the context is owned by the CachedPlan, advance its refcount. + */ + plansource->orig_plan = plansource->plan; + plansource->orig_plan->refcount++; + + /* + * Now we can add the entry to the list of cached plans. The List nodes + * live in CacheMemoryContext. + */ + MemoryContextSwitchTo(CacheMemoryContext); + + cached_plans_list = lappend(cached_plans_list, plansource); + + MemoryContextSwitchTo(oldcxt); + + return plansource; +} + +/* + * StoreCachedPlan: store a built or rebuilt plan into a plancache entry. + * + * Common subroutine for CreateCachedPlan and RevalidateCachedPlan. + */ +static void +StoreCachedPlan(CachedPlanSource *plansource, + List *stmt_list, + MemoryContext plan_context) +{ + CachedPlan *plan; + MemoryContext oldcxt; + + if (plan_context == NULL) + { + /* + * Make a dedicated memory context for the CachedPlan and its + * subsidiary data. + */ + plan_context = AllocSetContextCreate(CacheMemoryContext, + "CachedPlan", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* + * Copy supplied data into the new context. + */ + oldcxt = MemoryContextSwitchTo(plan_context); + + stmt_list = (List *) copyObject(stmt_list); + } + else + { + /* Assume subsidiary data is in the given context */ + oldcxt = MemoryContextSwitchTo(plan_context); + } + + /* + * Create and fill the CachedPlan struct within the new context. + */ + plan = (CachedPlan *) palloc(sizeof(CachedPlan)); + plan->stmt_list = stmt_list; + plan->fully_planned = plansource->fully_planned; + plan->dead = false; + plan->refcount = 1; /* for the parent's link */ + plan->generation = ++(plansource->generation); + plan->context = plan_context; + + Assert(plansource->plan == NULL); + plansource->plan = plan; + + MemoryContextSwitchTo(oldcxt); +} + +/* + * DropCachedPlan: destroy a cached plan. + * + * Actually this only destroys the CachedPlanSource: the referenced CachedPlan + * is released, but not destroyed until its refcount goes to zero. That + * handles the situation where DropCachedPlan is called while the plan is + * still in use. + */ +void +DropCachedPlan(CachedPlanSource *plansource) +{ + /* Validity check that we were given a CachedPlanSource */ + Assert(list_member_ptr(cached_plans_list, plansource)); + + /* Remove it from the list */ + cached_plans_list = list_delete_ptr(cached_plans_list, plansource); + + /* Decrement child CachePlan's refcount and drop if no longer needed */ + if (plansource->plan) + ReleaseCachedPlan(plansource->plan, false); + + /* + * If CachedPlanSource has independent storage, just drop it. Otherwise + * decrement the refcount on the CachePlan that owns the storage. + */ + if (plansource->orig_plan == NULL) + { + /* Remove the CachedPlanSource and all subsidiary data */ + MemoryContextDelete(plansource->context); + } + else + { + Assert(plansource->context == plansource->orig_plan->context); + ReleaseCachedPlan(plansource->orig_plan, false); + } +} + +/* + * RevalidateCachedPlan: prepare for re-use of a previously cached plan. + * + * What we do here is re-acquire locks and rebuild the plan if necessary. + * On return, the plan is valid and we have sufficient locks to begin + * execution (or planning, if not fully_planned). + * + * On return, the refcount of the plan has been incremented; a later + * ReleaseCachedPlan() call is expected. The refcount has been reported + * to the CurrentResourceOwner if useResOwner is true. + * + * Note: if any replanning activity is required, the caller's memory context + * is used for that work. + */ +CachedPlan * +RevalidateCachedPlan(CachedPlanSource *plansource, bool useResOwner) +{ + CachedPlan *plan; + + /* Validity check that we were given a CachedPlanSource */ + Assert(list_member_ptr(cached_plans_list, plansource)); + + /* + * If the plan currently appears valid, acquire locks on the referenced + * objects; then check again. We need to do it this way to cover the + * race condition that an invalidation message arrives before we get + * the lock. + */ + plan = plansource->plan; + if (plan && !plan->dead) + { + /* + * Plan must have positive refcount because it is referenced by + * plansource; so no need to fear it disappears under us here. + */ + Assert(plan->refcount > 0); + + if (plan->fully_planned) + AcquireExecutorLocks(plan->stmt_list, true); + else + AcquirePlannerLocks(plan->stmt_list, true); + + /* + * By now, if any invalidation has happened, PlanCacheCallback + * will have marked the plan dead. + */ + if (plan->dead) + { + /* Ooops, the race case happened. Release useless locks. */ + if (plan->fully_planned) + AcquireExecutorLocks(plan->stmt_list, false); + else + AcquirePlannerLocks(plan->stmt_list, false); + } + } + + /* + * If plan has been invalidated, unlink it from the parent and release it. + */ + if (plan && plan->dead) + { + plansource->plan = NULL; + ReleaseCachedPlan(plan, false); + plan = NULL; + } + + /* + * Build a new plan if needed. + */ + if (!plan) + { + List *slist; + TupleDesc resultDesc; + + /* + * Run parse analysis and rule rewriting. The parser tends to + * scribble on its input, so we must copy the raw parse tree to + * prevent corruption of the cache. Note that we do not use + * parse_analyze_varparams(), assuming that the caller never wants the + * parameter types to change from the original values. + */ + slist = pg_analyze_and_rewrite(copyObject(plansource->raw_parse_tree), + plansource->query_string, + plansource->param_types, + plansource->num_params); + + if (plansource->fully_planned) + { + /* + * Generate plans for queries. Assume snapshot is not set yet + * (XXX this may be wasteful, won't all callers have done that?) + */ + slist = pg_plan_queries(slist, NULL, true); + } + + /* + * Check or update the result tupdesc. XXX should we use a weaker + * condition than equalTupleDescs() here? + */ + resultDesc = ComputeResultDesc(slist); + if (resultDesc == NULL && plansource->resultDesc == NULL) + { + /* OK, doesn't return tuples */ + } + else if (resultDesc == NULL || plansource->resultDesc == NULL || + !equalTupleDescs(resultDesc, plansource->resultDesc)) + { + MemoryContext oldcxt; + + /* can we give a better error message? */ + if (plansource->fixed_result) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cached plan must not change result type"))); + oldcxt = MemoryContextSwitchTo(plansource->context); + if (resultDesc) + resultDesc = CreateTupleDescCopy(resultDesc); + if (plansource->resultDesc) + FreeTupleDesc(plansource->resultDesc); + plansource->resultDesc = resultDesc; + MemoryContextSwitchTo(oldcxt); + } + + /* + * Store the plans into the plancache entry, advancing the generation + * count. + */ + StoreCachedPlan(plansource, slist, NULL); + + plan = plansource->plan; + } + + /* + * Last step: flag the plan as in use by caller. + */ + if (useResOwner) + ResourceOwnerEnlargePlanCacheRefs(CurrentResourceOwner); + plan->refcount++; + if (useResOwner) + ResourceOwnerRememberPlanCacheRef(CurrentResourceOwner, plan); + + return plan; +} + +/* + * ReleaseCachedPlan: release active use of a cached plan. + * + * This decrements the reference count, and frees the plan if the count + * has thereby gone to zero. If useResOwner is true, it is assumed that + * the reference count is managed by the CurrentResourceOwner. + * + * Note: useResOwner = false is used for releasing references that are in + * persistent data structures, such as the parent CachedPlanSource or a + * Portal. Transient references should be protected by a resource owner. + */ +void +ReleaseCachedPlan(CachedPlan *plan, bool useResOwner) +{ + if (useResOwner) + ResourceOwnerForgetPlanCacheRef(CurrentResourceOwner, plan); + Assert(plan->refcount > 0); + plan->refcount--; + if (plan->refcount == 0) + MemoryContextDelete(plan->context); +} + +/* + * AcquireExecutorLocks: acquire locks needed for execution of a fully-planned + * cached plan; or release them if acquire is false. + */ +static void +AcquireExecutorLocks(List *stmt_list, bool acquire) +{ + ListCell *lc1; + + foreach(lc1, stmt_list) + { + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc1); + int rt_index; + ListCell *lc2; + + Assert(!IsA(plannedstmt, Query)); + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + + rt_index = 0; + foreach(lc2, plannedstmt->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc2); + LOCKMODE lockmode; + + rt_index++; + + if (rte->rtekind != RTE_RELATION) + continue; + + /* + * Acquire the appropriate type of lock on each relation OID. + * Note that we don't actually try to open the rel, and hence + * will not fail if it's been dropped entirely --- we'll just + * transiently acquire a non-conflicting lock. + */ + if (list_member_int(plannedstmt->resultRelations, rt_index)) + lockmode = RowExclusiveLock; + else if (rowmark_member(plannedstmt->rowMarks, rt_index)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; + + if (acquire) + LockRelationOid(rte->relid, lockmode); + else + UnlockRelationOid(rte->relid, lockmode); + } + } +} + +/* + * AcquirePlannerLocks: acquire locks needed for planning and execution of a + * not-fully-planned cached plan; or release them if acquire is false. + * + * Note that we don't actually try to open the relations, and hence will not + * fail if one has been dropped entirely --- we'll just transiently acquire + * a non-conflicting lock. + */ +static void +AcquirePlannerLocks(List *stmt_list, bool acquire) +{ + ListCell *lc; + + foreach(lc, stmt_list) + { + Query *query = (Query *) lfirst(lc); + + Assert(IsA(query, Query)); + if (acquire) + ScanQueryForRelids(query, LockRelid, NULL); + else + ScanQueryForRelids(query, UnlockRelid, NULL); + } +} + +/* + * ScanQueryForRelids callback functions for AcquirePlannerLocks + */ +static void +LockRelid(Oid relid, LOCKMODE lockmode, void *arg) +{ + LockRelationOid(relid, lockmode); +} + +static void +UnlockRelid(Oid relid, LOCKMODE lockmode, void *arg) +{ + UnlockRelationOid(relid, lockmode); +} + +/* + * ScanQueryForRelids: recursively scan one Query and apply the callback + * function to each relation OID found therein. The callback function + * takes the arguments relation OID, lockmode, pointer arg. + */ +static void +ScanQueryForRelids(Query *parsetree, + void (*callback) (), + void *arg) +{ + ListCell *lc; + int rt_index; + + /* + * First, process RTEs of the current query level. + */ + rt_index = 0; + foreach(lc, parsetree->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc); + LOCKMODE lockmode; + + rt_index++; + switch (rte->rtekind) + { + case RTE_RELATION: + /* + * Determine the lock type required for this RTE. + */ + if (rt_index == parsetree->resultRelation) + lockmode = RowExclusiveLock; + else if (rowmark_member(parsetree->rowMarks, rt_index)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; + + (*callback) (rte->relid, lockmode, arg); + break; + + case RTE_SUBQUERY: + + /* + * The subquery RTE itself is all right, but we have to + * recurse to process the represented subquery. + */ + ScanQueryForRelids(rte->subquery, callback, arg); + break; + + default: + /* ignore other types of RTEs */ + break; + } + } + + /* + * Recurse into sublink subqueries, too. But we already did the ones in + * the rtable. + */ + if (parsetree->hasSubLinks) + { + ScanQueryWalkerContext context; + + context.callback = callback; + context.arg = arg; + query_tree_walker(parsetree, ScanQueryWalker, + (void *) &context, + QTW_IGNORE_RT_SUBQUERIES); + } +} + +/* + * Walker to find sublink subqueries for ScanQueryForRelids + */ +static bool +ScanQueryWalker(Node *node, ScanQueryWalkerContext *context) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + + /* Do what we came for */ + ScanQueryForRelids((Query *) sub->subselect, + context->callback, context->arg); + /* Fall through to process lefthand args of SubLink */ + } + + /* + * Do NOT recurse into Query nodes, because ScanQueryForRelids + * already processed subselects of subselects for us. + */ + return expression_tree_walker(node, ScanQueryWalker, + (void *) context); +} + +/* + * rowmark_member: check whether an RT index appears in a RowMarkClause list. + */ +static bool +rowmark_member(List *rowMarks, int rt_index) +{ + ListCell *l; + + foreach(l, rowMarks) + { + RowMarkClause *rc = (RowMarkClause *) lfirst(l); + + if (rc->rti == rt_index) + return true; + } + return false; +} + +/* + * ComputeResultDesc: given a list of either fully-planned statements or + * Queries, determine the result tupledesc it will produce. Returns NULL + * if the execution will not return tuples. + * + * Note: the result is created or copied into current memory context. + */ +static TupleDesc +ComputeResultDesc(List *stmt_list) +{ + Node *node; + Query *query; + PlannedStmt *pstmt; + + switch (ChoosePortalStrategy(stmt_list)) + { + case PORTAL_ONE_SELECT: + node = (Node *) linitial(stmt_list); + if (IsA(node, Query)) + { + query = (Query *) node; + return ExecCleanTypeFromTL(query->targetList, false); + } + if (IsA(node, PlannedStmt)) + { + pstmt = (PlannedStmt *) node; + return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false); + } + /* other cases shouldn't happen, but return NULL */ + break; + + case PORTAL_ONE_RETURNING: + node = PortalListGetPrimaryStmt(stmt_list); + if (IsA(node, Query)) + { + query = (Query *) node; + Assert(query->returningList); + return ExecCleanTypeFromTL(query->returningList, false); + } + if (IsA(node, PlannedStmt)) + { + pstmt = (PlannedStmt *) node; + Assert(pstmt->returningLists); + return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false); + } + /* other cases shouldn't happen, but return NULL */ + break; + + case PORTAL_UTIL_SELECT: + node = (Node *) linitial(stmt_list); + if (IsA(node, Query)) + { + query = (Query *) node; + Assert(query->utilityStmt); + return UtilityTupleDescriptor(query->utilityStmt); + } + /* else it's a bare utility statement */ + return UtilityTupleDescriptor(node); + + case PORTAL_MULTI_QUERY: + /* will not return tuples */ + break; + } + return NULL; +} + +/* + * PlanCacheCallback + * Relcache inval callback function + */ +static void +PlanCacheCallback(Datum arg, Oid relid) +{ + ListCell *lc1; + ListCell *lc2; + + foreach(lc1, cached_plans_list) + { + CachedPlanSource *plansource = (CachedPlanSource *) lfirst(lc1); + CachedPlan *plan = plansource->plan; + + /* No work if it's already invalidated */ + if (!plan || plan->dead) + continue; + if (plan->fully_planned) + { + foreach(lc2, plan->stmt_list) + { + PlannedStmt *plannedstmt = (PlannedStmt *) lfirst(lc2); + ListCell *lc3; + + Assert(!IsA(plannedstmt, Query)); + if (!IsA(plannedstmt, PlannedStmt)) + continue; /* Ignore utility statements */ + foreach(lc3, plannedstmt->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc3); + + if (rte->rtekind != RTE_RELATION) + continue; + if (relid == rte->relid) + { + /* Invalidate the plan! */ + plan->dead = true; + break; /* out of rangetable scan */ + } + } + if (plan->dead) + break; /* out of stmt_list scan */ + } + } + else + { + /* + * For not-fully-planned entries we use ScanQueryForRelids, + * since a recursive traversal is needed. The callback API + * is a bit tedious but avoids duplication of coding. + */ + InvalRelidContext context; + + context.inval_relid = relid; + context.plan = plan; + + foreach(lc2, plan->stmt_list) + { + Query *query = (Query *) lfirst(lc2); + + Assert(IsA(query, Query)); + ScanQueryForRelids(query, InvalRelid, (void *) &context); + } + } + } +} + +/* + * ScanQueryForRelids callback function for PlanCacheCallback + */ +static void +InvalRelid(Oid relid, LOCKMODE lockmode, InvalRelidContext *context) +{ + if (relid == context->inval_relid) + context->plan->dead = true; +} diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 8fdb3be75e..daef3199fa 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.174 2007/02/15 23:23:23 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.175 2007/03/13 00:33:42 tgl Exp $ * * *------------------------------------------------------------------------- @@ -28,6 +28,7 @@ #include "libpq/hba.h" #include "mb/pg_wchar.h" #include "miscadmin.h" +#include "pgstat.h" #include "postmaster/autovacuum.h" #include "postmaster/postmaster.h" #include "storage/backendid.h" @@ -40,10 +41,10 @@ #include "utils/acl.h" #include "utils/flatfiles.h" #include "utils/guc.h" +#include "utils/plancache.h" #include "utils/portal.h" #include "utils/relcache.h" #include "utils/syscache.h" -#include "pgstat.h" static bool FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace); @@ -429,6 +430,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username, */ RelationCacheInitialize(); InitCatalogCache(); + InitPlanCache(); /* Initialize portal manager */ EnablePortalManager(); diff --git a/src/backend/utils/mmgr/README b/src/backend/utils/mmgr/README index a91dfe3d76..05353a3335 100644 --- a/src/backend/utils/mmgr/README +++ b/src/backend/utils/mmgr/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.9 2006/09/07 22:52:01 tgl Exp $ +$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.10 2007/03/13 00:33:42 tgl Exp $ Notes about memory allocation redesign -------------------------------------- @@ -201,15 +201,6 @@ have dangling pointers leading to a crash at top-level commit. An example of data kept here is pending NOTIFY messages, which are sent at top-level commit, but only if the generating subtransaction did not abort. -QueryContext --- this is not actually a separate context, but a global -variable pointing to the context that holds the current command's parse tree. -(In simple-Query mode this points to MessageContext; when executing a -prepared statement it will point to the prepared statement's private context. -Note that the plan tree may or may not be in this same context.) -Generally it is not appropriate for any code to use QueryContext as an -allocation target --- from the point of view of any code that would be -referencing the QueryContext variable, it's a read-only context. - PortalContext --- this is not actually a separate context either, but a global variable pointing to the per-portal context of the currently active execution portal. This can be used if it's necessary to allocate storage @@ -229,9 +220,7 @@ Contexts for prepared statements and portals A prepared-statement object has an associated private context, in which the parse and plan trees for its query are stored. Because these trees are read-only to the executor, the prepared statement can be re-used many -times without further copying of these trees. QueryContext points at this -private context while executing any portal built from the prepared -statement. +times without further copying of these trees. An execution-portal object has a private context that is referenced by PortalContext when the portal is active. In the case of a portal created diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 14e9c70b2c..3337f819ee 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.59 2007/01/05 22:19:47 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.60 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -46,8 +46,7 @@ MemoryContext MessageContext = NULL; MemoryContext TopTransactionContext = NULL; MemoryContext CurTransactionContext = NULL; -/* These two are transient links to contexts owned by other objects: */ -MemoryContext QueryContext = NULL; +/* This is a transient link to the active portal's memory context: */ MemoryContext PortalContext = NULL; diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 3bd2ee6397..043ea1e57a 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -12,7 +12,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.99 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.100 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -149,9 +149,9 @@ GetPortalByName(const char *name) * cases should occur in present usages of this function. * * Copes if given a list of Querys --- can't happen in a portal, but this - * code also supports prepared statements, which need both cases. + * code also supports plancache.c, which needs both cases. * - * Note: the reason this is just handed a List is so that prepared statements + * Note: the reason this is just handed a List is so that plancache.c * can share the code. For use with a portal, use PortalGetPrimaryStmt * rather than calling this directly. */ @@ -275,9 +275,17 @@ CreateNewPortal(void) * * Notes: commandTag shall be NULL if and only if the original query string * (before rewriting) was an empty string. Also, the passed commandTag must - * be a pointer to a constant string, since it is not copied. The caller is - * responsible for ensuring that the passed prepStmtName (if any), sourceText - * (if any), and plan trees have adequate lifetime. + * be a pointer to a constant string, since it is not copied. However, + * prepStmtName and sourceText, if provided, are copied into the portal's + * heap context for safekeeping. + * + * If cplan is provided, then it is a cached plan containing the stmts, + * and the caller must have done RevalidateCachedPlan(), causing a refcount + * increment. The refcount will be released when the portal is destroyed. + * + * If cplan is NULL, then it is the caller's responsibility to ensure that + * the passed plan trees have adequate lifetime. Typically this is done by + * copying them into the portal's heap context. */ void PortalDefineQuery(Portal portal, @@ -285,18 +293,35 @@ PortalDefineQuery(Portal portal, const char *sourceText, const char *commandTag, List *stmts, - MemoryContext queryContext) + CachedPlan *cplan) { AssertArg(PortalIsValid(portal)); - AssertState(portal->queryContext == NULL); /* else defined already */ + AssertState(portal->status == PORTAL_NEW); Assert(commandTag != NULL || stmts == NIL); - portal->prepStmtName = prepStmtName; - portal->sourceText = sourceText; + portal->prepStmtName = prepStmtName ? + MemoryContextStrdup(PortalGetHeapMemory(portal), prepStmtName) : NULL; + portal->sourceText = sourceText ? + MemoryContextStrdup(PortalGetHeapMemory(portal), sourceText) : NULL; portal->commandTag = commandTag; portal->stmts = stmts; - portal->queryContext = queryContext; + portal->cplan = cplan; + portal->status = PORTAL_DEFINED; +} + +/* + * PortalReleaseCachedPlan + * Release a portal's reference to its cached plan, if any. + */ +static void +PortalReleaseCachedPlan(Portal portal) +{ + if (portal->cplan) + { + ReleaseCachedPlan(portal->cplan, false); + portal->cplan = NULL; + } } /* @@ -356,6 +381,10 @@ PortalDrop(Portal portal, bool isTopCommit) if (PointerIsValid(portal->cleanup)) (*portal->cleanup) (portal); + /* drop cached plan reference, if any */ + if (portal->cplan) + PortalReleaseCachedPlan(portal); + /* * Release any resources still attached to the portal. There are several * cases being covered here: @@ -423,29 +452,6 @@ PortalDrop(Portal portal, bool isTopCommit) pfree(portal); } -/* - * DropDependentPortals - * Drop any portals using the specified context as queryContext. - * - * This is normally used to make sure we can safely drop a prepared statement. - */ -void -DropDependentPortals(MemoryContext queryContext) -{ - HASH_SEQ_STATUS status; - PortalHashEnt *hentry; - - hash_seq_init(&status, PortalHashTable); - - while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) - { - Portal portal = hentry->portal; - - if (portal->queryContext == queryContext) - PortalDrop(portal, false); - } -} - /* * Pre-commit processing for portals. @@ -485,6 +491,10 @@ CommitHoldablePortals(void) PortalCreateHoldStore(portal); PersistHoldablePortal(portal); + /* drop cached plan reference, if any */ + if (portal->cplan) + PortalReleaseCachedPlan(portal); + /* * Any resources belonging to the portal will be released in the * upcoming transaction-wide cleanup; the portal will no longer @@ -630,6 +640,10 @@ AtAbort_Portals(void) portal->cleanup = NULL; } + /* drop cached plan reference, if any */ + if (portal->cplan) + PortalReleaseCachedPlan(portal); + /* * Any resources belonging to the portal will be released in the * upcoming transaction-wide cleanup; they will be gone before we run @@ -769,6 +783,10 @@ AtSubAbort_Portals(SubTransactionId mySubid, portal->cleanup = NULL; } + /* drop cached plan reference, if any */ + if (portal->cplan) + PortalReleaseCachedPlan(portal); + /* * Any resources belonging to the portal will be released in the * upcoming transaction-wide cleanup; they will be gone before we diff --git a/src/backend/utils/resowner/README b/src/backend/utils/resowner/README index 56629d5089..57be840dc1 100644 --- a/src/backend/utils/resowner/README +++ b/src/backend/utils/resowner/README @@ -1,4 +1,4 @@ -$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.4 2006/06/16 18:42:23 tgl Exp $ +$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.5 2007/03/13 00:33:42 tgl Exp $ Notes about resource owners --------------------------- @@ -60,12 +60,13 @@ subtransaction or portal. Therefore, the "release" operation on a child ResourceOwner transfers lock ownership to the parent instead of actually releasing the lock, if isCommit is true. -Currently, ResourceOwners contain direct support for recording ownership -of buffer pins, lmgr locks, and catcache, relcache, and tupdesc references. -Other objects can be associated with a ResourceOwner by recording the address -of the owning ResourceOwner in such an object. There is an API for other -modules to get control during ResourceOwner release, so that they can scan -their own data structures to find the objects that need to be deleted. +Currently, ResourceOwners contain direct support for recording ownership of +buffer pins, lmgr locks, and catcache, relcache, plancache, and tupdesc +references. Other objects can be associated with a ResourceOwner by recording +the address of the owning ResourceOwner in such an object. There is an API +for other modules to get control during ResourceOwner release, so that they +can scan their own data structures to find the objects that need to be +deleted. Whenever we are inside a transaction, the global variable CurrentResourceOwner shows which resource owner should be assigned diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index 70ddd443c7..92fe4742c7 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -14,7 +14,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.23 2007/01/05 22:19:47 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.24 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -56,6 +56,11 @@ typedef struct ResourceOwnerData Relation *relrefs; /* dynamically allocated array */ int maxrelrefs; /* currently allocated array size */ + /* We have built-in support for remembering plancache references */ + int nplanrefs; /* number of owned plancache pins */ + CachedPlan **planrefs; /* dynamically allocated array */ + int maxplanrefs; /* currently allocated array size */ + /* We have built-in support for remembering tupdesc references */ int ntupdescs; /* number of owned tupdesc references */ TupleDesc *tupdescs; /* dynamically allocated array */ @@ -90,6 +95,7 @@ static void ResourceOwnerReleaseInternal(ResourceOwner owner, bool isCommit, bool isTopLevel); static void PrintRelCacheLeakWarning(Relation rel); +static void PrintPlanCacheLeakWarning(CachedPlan *plan); static void PrintTupleDescLeakWarning(TupleDesc tupdesc); @@ -280,6 +286,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner, PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]); ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]); } + /* Ditto for plancache references */ + while (owner->nplanrefs > 0) + { + if (isCommit) + PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]); + ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true); + } /* Ditto for tupdesc references */ while (owner->ntupdescs > 0) { @@ -316,6 +329,7 @@ ResourceOwnerDelete(ResourceOwner owner) Assert(owner->ncatrefs == 0); Assert(owner->ncatlistrefs == 0); Assert(owner->nrelrefs == 0); + Assert(owner->nplanrefs == 0); Assert(owner->ntupdescs == 0); /* @@ -341,6 +355,8 @@ ResourceOwnerDelete(ResourceOwner owner) pfree(owner->catlistrefs); if (owner->relrefs) pfree(owner->relrefs); + if (owner->planrefs) + pfree(owner->planrefs); if (owner->tupdescs) pfree(owner->tupdescs); @@ -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. diff --git a/src/include/access/xact.h b/src/include/access/xact.h index e74b87b0ed..760b432456 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.84 2007/01/05 22:19:51 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.85 2007/03/13 00:33:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -164,9 +164,9 @@ extern bool IsTransactionBlock(void); extern bool IsTransactionOrTransactionBlock(void); extern char TransactionBlockStatusCode(void); extern void AbortOutOfAnyTransaction(void); -extern void PreventTransactionChain(void *stmtNode, const char *stmtType); -extern void RequireTransactionChain(void *stmtNode, const char *stmtType); -extern bool IsInTransactionChain(void *stmtNode); +extern void PreventTransactionChain(bool isTopLevel, const char *stmtType); +extern void RequireTransactionChain(bool isTopLevel, const char *stmtType); +extern bool IsInTransactionChain(bool isTopLevel); extern void RegisterXactCallback(XactCallback callback, void *arg); extern void UnregisterXactCallback(XactCallback callback, void *arg); extern void RegisterSubXactCallback(SubXactCallback callback, void *arg); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 6929bbe2af..0ed1e23138 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.31 2007/01/05 22:19:53 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.32 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,7 +17,7 @@ #include "utils/rel.h" -extern void cluster(ClusterStmt *stmt); +extern void cluster(ClusterStmt *stmt, bool isTopLevel); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck); diff --git a/src/include/commands/copy.h b/src/include/commands/copy.h index cda5749603..11ff84c57a 100644 --- a/src/include/commands/copy.h +++ b/src/include/commands/copy.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.29 2007/01/05 22:19:53 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.30 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,7 +18,7 @@ #include "tcop/dest.h" -extern uint64 DoCopy(const CopyStmt *stmt); +extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString); extern DestReceiver *CreateCopyDestReceiver(void); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 3d665ff5c2..5bb94a24f2 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.80 2007/01/23 05:07:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.81 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,7 +25,6 @@ extern void DefineIndex(RangeVar *heapRelation, char *tableSpaceName, List *attributeList, Expr *predicate, - List *rangetable, List *options, bool unique, bool primary, diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index 981064297f..42879ce5a4 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994-5, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.29 2007/01/05 22:19:53 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.30 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,11 +16,16 @@ #include "executor/executor.h" -extern void ExplainQuery(ExplainStmt *stmt, ParamListInfo params, - DestReceiver *dest); +extern void ExplainQuery(ExplainStmt *stmt, const char *queryString, + ParamListInfo params, DestReceiver *dest); extern TupleDesc ExplainResultDesc(ExplainStmt *stmt); +extern void ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt, + const char *queryString, + ParamListInfo params, + TupOutputState *tstate); + extern void ExplainOnePlan(QueryDesc *queryDesc, ExplainStmt *stmt, TupOutputState *tstate); diff --git a/src/include/commands/portalcmds.h b/src/include/commands/portalcmds.h index 50fe2f1328..3d77404613 100644 --- a/src/include/commands/portalcmds.h +++ b/src/include/commands/portalcmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.21 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.22 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -18,7 +18,8 @@ #include "utils/portal.h" -extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params); +extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params, + const char *queryString, bool isTopLevel); extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest, char *completionTag); diff --git a/src/include/commands/prepare.h b/src/include/commands/prepare.h index a921bf1b04..4e27ab3bb3 100644 --- a/src/include/commands/prepare.h +++ b/src/include/commands/prepare.h @@ -6,7 +6,7 @@ * * Copyright (c) 2002-2007, PostgreSQL Global Development Group * - * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.24 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.25 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -14,58 +14,49 @@ #define PREPARE_H #include "executor/executor.h" +#include "utils/plancache.h" #include "utils/timestamp.h" /* - * The data structure representing a prepared statement + * The data structure representing a prepared statement. This is now just + * a thin veneer over a plancache entry --- the main addition is that of + * a name. * - * A prepared statement might be fully planned, or only parsed-and-rewritten. - * If fully planned, stmt_list contains PlannedStmts and/or utility statements; - * if not, it contains Query nodes. - * - * Note: all subsidiary storage lives in the context denoted by the context - * field. However, the string referenced by commandTag is not subsidiary - * storage; it is assumed to be a compile-time-constant string. As with - * portals, commandTag shall be NULL if and only if the original query string - * (before rewriting) was an empty string. + * Note: all subsidiary storage lives in the referenced plancache entry. */ typedef struct { /* dynahash.c requires key to be first field */ char stmt_name[NAMEDATALEN]; - char *query_string; /* text of query, or NULL */ - const char *commandTag; /* command tag (a constant!), or NULL */ - List *stmt_list; /* list of statement or Query nodes */ - List *argtype_list; /* list of parameter type OIDs */ - bool fully_planned; /* what is in stmt_list, exactly? */ + CachedPlanSource *plansource; /* the actual cached plan */ bool from_sql; /* prepared via SQL, not FE/BE protocol? */ TimestampTz prepare_time; /* the time when the stmt was prepared */ - MemoryContext context; /* context containing this query */ } PreparedStatement; /* Utility statements PREPARE, EXECUTE, DEALLOCATE, EXPLAIN EXECUTE */ -extern void PrepareQuery(PrepareStmt *stmt); -extern void ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params, +extern void PrepareQuery(PrepareStmt *stmt, const char *queryString); +extern void ExecuteQuery(ExecuteStmt *stmt, const char *queryString, + ParamListInfo params, DestReceiver *dest, char *completionTag); extern void DeallocateQuery(DeallocateStmt *stmt); -extern void ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params, - TupOutputState *tstate); +extern void ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt, + const char *queryString, + ParamListInfo params, TupOutputState *tstate); /* Low-level access to stored prepared statements */ extern void StorePreparedStatement(const char *stmt_name, + Node *raw_parse_tree, const char *query_string, const char *commandTag, + Oid *param_types, + int num_params, List *stmt_list, - List *argtype_list, - bool fully_planned, bool from_sql); extern PreparedStatement *FetchPreparedStatement(const char *stmt_name, bool throwError); extern void DropPreparedStatement(const char *stmt_name, bool showError); -extern List *FetchPreparedStatementParams(const char *stmt_name); extern TupleDesc FetchPreparedStatementResultDesc(PreparedStatement *stmt); -extern bool PreparedStatementReturnsTuples(PreparedStatement *stmt); extern List *FetchPreparedStatementTargetList(PreparedStatement *stmt); #endif /* PREPARE_H */ diff --git a/src/include/commands/schemacmds.h b/src/include/commands/schemacmds.h index e8820a8c19..e70579c3c3 100644 --- a/src/include/commands/schemacmds.h +++ b/src/include/commands/schemacmds.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.15 2007/01/05 22:19:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.16 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,7 +17,8 @@ #include "nodes/parsenodes.h" -extern void CreateSchemaCommand(CreateSchemaStmt *parsetree); +extern void CreateSchemaCommand(CreateSchemaStmt *parsetree, + const char *queryString); extern void RemoveSchema(List *names, DropBehavior behavior, bool missing_ok); extern void RemoveSchemaById(Oid schemaOid); diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 09da5cfc0d..b77fbf4c71 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.69 2007/01/05 22:19:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.70 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -110,7 +110,7 @@ extern int vacuum_freeze_min_age; /* in commands/vacuum.c */ -extern void vacuum(VacuumStmt *vacstmt, List *relids); +extern void vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel); extern void vac_open_indexes(Relation relation, LOCKMODE lockmode, int *nindexes, Relation **Irel); extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode); diff --git a/src/include/commands/view.h b/src/include/commands/view.h index 2e0e525403..ff54935bbb 100644 --- a/src/include/commands/view.h +++ b/src/include/commands/view.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/view.h,v 1.24 2007/01/05 22:19:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/view.h,v 1.25 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,7 +16,7 @@ #include "nodes/parsenodes.h" -extern void DefineView(RangeVar *view, Query *view_parse, bool replace); +extern void DefineView(ViewStmt *stmt, const char *queryString); extern void RemoveView(const RangeVar *view, DropBehavior behavior); #endif /* VIEW_H */ diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h index 69bc117ef0..7a94152b42 100644 --- a/src/include/nodes/params.h +++ b/src/include/nodes/params.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.34 2007/01/05 22:19:55 momjian Exp $ + * $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.35 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -82,4 +82,7 @@ typedef struct ParamExecData /* Functions found in src/backend/nodes/params.c */ extern ParamListInfo copyParamList(ParamListInfo from); +extern void getParamListTypes(ParamListInfo params, + Oid **param_types, int *num_params); + #endif /* PARAMS_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index ec9ccb6ce3..e24b57e8a2 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.341 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.342 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1021,15 +1021,14 @@ typedef struct GrantRoleStmt * * We support "COPY relation FROM file", "COPY relation TO file", and * "COPY (query) TO file". In any given CopyStmt, exactly one of "relation" - * and "query" must be non-NULL. Note: "query" is a SelectStmt before - * parse analysis, and a Query afterwards. + * and "query" must be non-NULL. * ---------------------- */ typedef struct CopyStmt { NodeTag type; RangeVar *relation; /* the relation to copy */ - Query *query; /* the query to copy */ + Node *query; /* the SELECT query to copy */ List *attlist; /* List of column names (as Strings), or NIL * for all columns */ bool is_from; /* TO or FROM */ @@ -1487,8 +1486,6 @@ typedef struct IndexStmt List *indexParams; /* a list of IndexElem */ List *options; /* options from WITH clause */ Node *whereClause; /* qualification (partial-index predicate) */ - List *rangetable; /* range table for qual and/or expressions, - * filled in by transformStmt() */ bool unique; /* is index unique? */ bool primary; /* is index on primary key? */ bool isconstraint; /* is it from a CONSTRAINT clause? */ @@ -1713,7 +1710,7 @@ typedef struct ViewStmt NodeTag type; RangeVar *view; /* the view to be created */ List *aliases; /* target column names */ - Query *query; /* the SQL statement */ + Node *query; /* the SELECT query */ bool replace; /* replace an existing view? */ } ViewStmt; @@ -1805,7 +1802,7 @@ typedef struct VacuumStmt typedef struct ExplainStmt { NodeTag type; - Query *query; /* the query */ + Node *query; /* the query (as a raw parse tree) */ bool verbose; /* print plan info */ bool analyze; /* get statistics by executing plan */ } ExplainStmt; @@ -1940,9 +1937,8 @@ typedef struct PrepareStmt { NodeTag type; char *name; /* Name of plan, arbitrary */ - List *argtypes; /* Types of parameters (TypeNames) */ - List *argtype_oids; /* Types of parameters (OIDs) */ - Query *query; /* The query itself */ + List *argtypes; /* Types of parameters (List of TypeName) */ + Node *query; /* The query itself (as a raw parsetree) */ } PrepareStmt; diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index f53358801d..033dce6046 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.35 2007/01/05 22:19:56 momjian Exp $ + * $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.36 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,10 @@ extern List *parse_analyze(Node *parseTree, const char *sourceText, extern List *parse_analyze_varparams(Node *parseTree, const char *sourceText, Oid **paramTypes, int *numParams); extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState); + +extern IndexStmt *analyzeIndexStmt(IndexStmt *stmt, const char *queryString); +extern void analyzeRuleStmt(RuleStmt *stmt, const char *queryString, + List **actions, Node **whereClause); extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt); extern void CheckSelectLocking(Query *qry); extern void applyLockingClause(Query *qry, Index rtindex, diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h index c6d15c129f..d4673f82a6 100644 --- a/src/include/rewrite/rewriteDefine.h +++ b/src/include/rewrite/rewriteDefine.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.23 2007/01/05 22:19:57 momjian Exp $ + * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.24 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -16,7 +16,15 @@ #include "nodes/parsenodes.h" -extern void DefineQueryRewrite(RuleStmt *args); +extern void DefineRule(RuleStmt *stmt, const char *queryString); + +extern void DefineQueryRewrite(char *rulename, + RangeVar *event_obj, + Node *event_qual, + CmdType event_type, + bool is_instead, + bool replace, + List *action); extern void RenameRewriteRule(Oid owningRel, const char *oldName, const char *newName); diff --git a/src/include/tcop/pquery.h b/src/include/tcop/pquery.h index 5cab498c13..abf64f0ebf 100644 --- a/src/include/tcop/pquery.h +++ b/src/include/tcop/pquery.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.41 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.42 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -33,7 +33,7 @@ extern void PortalStart(Portal portal, ParamListInfo params, extern void PortalSetResultFormat(Portal portal, int nFormats, int16 *formats); -extern bool PortalRun(Portal portal, long count, +extern bool PortalRun(Portal portal, long count, bool isTopLevel, DestReceiver *dest, DestReceiver *altdest, char *completionTag); diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index 52c0225306..863a664cf5 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.31 2007/02/20 17:32:17 tgl Exp $ + * $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.32 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,8 +17,9 @@ #include "tcop/tcopprot.h" -extern void ProcessUtility(Node *parsetree, ParamListInfo params, - DestReceiver *dest, char *completionTag); +extern void ProcessUtility(Node *parsetree, const char *queryString, + ParamListInfo params, bool isTopLevel, + DestReceiver *dest, char *completionTag); extern bool UtilityReturnsTuples(Node *parsetree); diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index fd304b66ad..f046f397e8 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.61 2007/01/05 22:19:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.62 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -75,8 +75,7 @@ extern DLLIMPORT MemoryContext MessageContext; extern DLLIMPORT MemoryContext TopTransactionContext; extern DLLIMPORT MemoryContext CurTransactionContext; -/* These two are transient links to contexts owned by other objects: */ -extern DLLIMPORT MemoryContext QueryContext; +/* This is a transient link to the active portal's memory context: */ extern DLLIMPORT MemoryContext PortalContext; diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h new file mode 100644 index 0000000000..833ec473b1 --- /dev/null +++ b/src/include/utils/plancache.h @@ -0,0 +1,105 @@ +/*------------------------------------------------------------------------- + * + * plancache.h + * Plan cache definitions. + * + * See plancache.c for comments. + * + * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/include/utils/plancache.h,v 1.1 2007/03/13 00:33:43 tgl Exp $ + * + *------------------------------------------------------------------------- + */ +#ifndef PLANCACHE_H +#define PLANCACHE_H + +#include "access/tupdesc.h" + +/* + * CachedPlanSource represents the portion of a cached plan that persists + * across invalidation/replan cycles. It stores a raw parse tree (required), + * the original source text (optional, but highly recommended to improve + * error reports), and adjunct data. + * + * Normally, both the struct itself and the subsidiary data live in the + * context denoted by the context field, while the linked-to CachedPlan, if + * any, has its own context. Thus an invalidated CachedPlan can be dropped + * when no longer needed, and conversely a CachedPlanSource can be dropped + * without worrying whether any portals depend on particular instances of + * its plan. + * + * But for entries created by FastCreateCachedPlan, the CachedPlanSource + * and the initial version of the CachedPlan share the same memory context. + * In this case, we treat the memory context as belonging to the CachedPlan. + * The CachedPlanSource has an extra reference-counted link (orig_plan) + * to the CachedPlan, and the memory context goes away when the CachedPlan's + * reference count goes to zero. This arrangement saves overhead for plans + * that aren't expected to live long enough to need replanning, while not + * losing any flexibility if a replan turns out to be necessary. + * + * Note: the string referenced by commandTag is not subsidiary storage; + * it is assumed to be a compile-time-constant string. As with portals, + * commandTag shall be NULL if and only if the original query string (before + * rewriting) was an empty string. + */ +typedef struct CachedPlanSource +{ + Node *raw_parse_tree; /* output of raw_parser() */ + char *query_string; /* text of query, or NULL */ + const char *commandTag; /* command tag (a constant!), or NULL */ + Oid *param_types; /* array of parameter type OIDs, or NULL */ + int num_params; /* length of param_types array */ + bool fully_planned; /* do we cache planner or rewriter output? */ + bool fixed_result; /* disallow change in result tupdesc? */ + int generation; /* counter, starting at 1, for replans */ + TupleDesc resultDesc; /* result type; NULL = doesn't return tuples */ + struct CachedPlan *plan; /* link to plan, or NULL if not valid */ + MemoryContext context; /* context containing this CachedPlanSource */ + struct CachedPlan *orig_plan; /* link to plan owning my context */ +} CachedPlanSource; + +/* + * CachedPlan represents the portion of a cached plan that is discarded when + * invalidation occurs. The reference count includes both the link(s) from the + * parent CachedPlanSource, and any active plan executions, so the plan can be + * discarded exactly when refcount goes to zero. Both the struct itself and + * the subsidiary data live in the context denoted by the context field. + * This makes it easy to free a no-longer-needed cached plan. + */ +typedef struct CachedPlan +{ + List *stmt_list; /* list of statement or Query nodes */ + bool fully_planned; /* do we cache planner or rewriter output? */ + bool dead; /* if true, do not use */ + int refcount; /* count of live references to this struct */ + int generation; /* counter, starting at 1, for replans */ + MemoryContext context; /* context containing this CachedPlan */ +} CachedPlan; + + +extern void InitPlanCache(void); +extern CachedPlanSource *CreateCachedPlan(Node *raw_parse_tree, + const char *query_string, + const char *commandTag, + Oid *param_types, + int num_params, + List *stmt_list, + bool fully_planned, + bool fixed_result); +extern CachedPlanSource *FastCreateCachedPlan(Node *raw_parse_tree, + char *query_string, + const char *commandTag, + Oid *param_types, + int num_params, + List *stmt_list, + bool fully_planned, + bool fixed_result, + MemoryContext context); +extern void DropCachedPlan(CachedPlanSource *plansource); +extern CachedPlan *RevalidateCachedPlan(CachedPlanSource *plansource, + bool useResOwner); +extern void ReleaseCachedPlan(CachedPlan *plan, bool useResOwner); + +#endif /* PLANCACHE_H */ diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index aa432abb87..47651006a2 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -39,7 +39,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.73 2007/02/20 17:32:18 tgl Exp $ + * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.74 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -94,7 +94,8 @@ typedef enum PortalStrategy */ typedef enum PortalStatus { - PORTAL_NEW, /* in process of creation */ + PORTAL_NEW, /* freshly created */ + PORTAL_DEFINED, /* PortalDefineQuery done */ PORTAL_READY, /* PortalStart complete, can run it */ PORTAL_ACTIVE, /* portal is running (can't delete it) */ PORTAL_DONE, /* portal is finished (don't re-run it) */ @@ -125,15 +126,7 @@ typedef struct PortalData const char *sourceText; /* text of query, if known (may be NULL) */ const char *commandTag; /* command tag for original query */ List *stmts; /* PlannedStmts and/or utility statements */ - MemoryContext queryContext; /* where the plan trees live */ - - /* - * Note: queryContext effectively identifies which prepared statement the - * portal depends on, if any. The queryContext is *not* owned by the - * portal and is not to be deleted by portal destruction. (But for a - * cursor it is the same as "heap", and that context is deleted by portal - * destruction.) The plan trees may be in either queryContext or heap. - */ + CachedPlan *cplan; /* CachedPlan, if stmts are from one */ ParamListInfo portalParams; /* params to pass to query */ @@ -210,14 +203,13 @@ extern void AtSubCleanup_Portals(SubTransactionId mySubid); extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent); extern Portal CreateNewPortal(void); extern void PortalDrop(Portal portal, bool isTopCommit); -extern void DropDependentPortals(MemoryContext queryContext); extern Portal GetPortalByName(const char *name); extern void PortalDefineQuery(Portal portal, const char *prepStmtName, const char *sourceText, const char *commandTag, List *stmts, - MemoryContext queryContext); + CachedPlan *cplan); extern Node *PortalListGetPrimaryStmt(List *stmts); extern void PortalCreateHoldStore(Portal portal); diff --git a/src/include/utils/resowner.h b/src/include/utils/resowner.h index 663096a333..ea0d6a7406 100644 --- a/src/include/utils/resowner.h +++ b/src/include/utils/resowner.h @@ -12,7 +12,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.10 2007/01/05 22:19:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.11 2007/03/13 00:33:43 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,7 @@ #include "storage/buf.h" #include "utils/catcache.h" +#include "utils/plancache.h" /* @@ -106,6 +107,13 @@ extern void ResourceOwnerRememberRelationRef(ResourceOwner owner, extern void ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel); +/* support for plancache refcount management */ +extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner); +extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, + CachedPlan *plan); +extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, + CachedPlan *plan); + /* support for tupledesc refcount management */ extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner); extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner, diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out new file mode 100644 index 0000000000..4980a9ab68 --- /dev/null +++ b/src/test/regress/expected/plancache.out @@ -0,0 +1,102 @@ +-- +-- Tests to exercise the plan caching/invalidation mechanism +-- +CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl; +-- create and use a cached plan +PREPARE prepstmt AS SELECT * FROM foo; +EXECUTE prepstmt; + q1 | q2 +------------------+------------------- + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 123 + 4567890123456789 | 4567890123456789 + 4567890123456789 | -4567890123456789 +(5 rows) + +-- and one with parameters +PREPARE prepstmt2(bigint) AS SELECT * FROM foo WHERE q1 = $1; +EXECUTE prepstmt2(123); + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +-- invalidate the plans and see what happens +DROP TABLE foo; +EXECUTE prepstmt; +ERROR: relation "foo" does not exist +EXECUTE prepstmt2(123); +ERROR: relation "foo" does not exist +-- recreate the temp table (this demonstrates that the raw plan is +-- purely textual and doesn't depend on OIDs, for instance) +CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl ORDER BY 2; +EXECUTE prepstmt; + q1 | q2 +------------------+------------------- + 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(5 rows) + +EXECUTE prepstmt2(123); + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +-- prepared statements should prevent change in output tupdesc, +-- since clients probably aren't expecting that to change on the fly +ALTER TABLE foo ADD COLUMN q3 bigint; +EXECUTE prepstmt; +ERROR: cached plan must not change result type +EXECUTE prepstmt2(123); +ERROR: cached plan must not change result type +-- but we're nice guys and will let you undo your mistake +ALTER TABLE foo DROP COLUMN q3; +EXECUTE prepstmt; + q1 | q2 +------------------+------------------- + 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(5 rows) + +EXECUTE prepstmt2(123); + q1 | q2 +-----+------------------ + 123 | 456 + 123 | 4567890123456789 +(2 rows) + +-- Try it with a view, which isn't directly used in the resulting plan +-- but should trigger invalidation anyway +CREATE TEMP VIEW voo AS SELECT * FROM foo; +PREPARE vprep AS SELECT * FROM voo; +EXECUTE vprep; + q1 | q2 +------------------+------------------- + 4567890123456789 | -4567890123456789 + 4567890123456789 | 123 + 123 | 456 + 123 | 4567890123456789 + 4567890123456789 | 4567890123456789 +(5 rows) + +CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo; +EXECUTE vprep; + q1 | q2 +------------------+------------------- + 4567890123456789 | -2283945061728394 + 4567890123456789 | 61 + 123 | 228 + 123 | 2283945061728394 + 4567890123456789 | 2283945061728394 +(5 rows) + diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 70058605c0..30103f5d08 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1188,6 +1188,8 @@ drop rule foorule on foo; create rule foorule as on insert to foo where f1 < 100 do instead insert into foo2 values (f1); ERROR: column "f1" does not exist +LINE 2: do instead insert into foo2 values (f1); + ^ -- this is the correct way: create rule foorule as on insert to foo where f1 < 100 do instead insert into foo2 values (new.f1); diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 096d2c1c7a..35ebff8589 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -1,6 +1,6 @@ # ---------- # The first group of parallel test -# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.39 2007/02/09 03:35:35 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.40 2007/03/13 00:33:44 tgl Exp $ # ---------- test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid @@ -69,7 +69,7 @@ test: misc # ---------- # The fifth group of parallel test # ---------- -test: select_views portals_p2 rules foreign_key cluster dependency guc combocid +test: select_views portals_p2 rules foreign_key cluster dependency guc combocid plancache # ---------- # The sixth group of parallel test diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index d109dabdc2..31bac61260 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.37 2007/02/09 03:35:35 tgl Exp $ +# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.38 2007/03/13 00:33:44 tgl Exp $ # This should probably be in an order similar to parallel_schedule. test: boolean test: char @@ -89,6 +89,7 @@ test: cluster test: dependency test: guc test: combocid +test: plancache test: limit test: plpgsql test: copy2 diff --git a/src/test/regress/sql/plancache.sql b/src/test/regress/sql/plancache.sql new file mode 100644 index 0000000000..b952efe197 --- /dev/null +++ b/src/test/regress/sql/plancache.sql @@ -0,0 +1,53 @@ +-- +-- Tests to exercise the plan caching/invalidation mechanism +-- + +CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl; + +-- create and use a cached plan +PREPARE prepstmt AS SELECT * FROM foo; + +EXECUTE prepstmt; + +-- and one with parameters +PREPARE prepstmt2(bigint) AS SELECT * FROM foo WHERE q1 = $1; + +EXECUTE prepstmt2(123); + +-- invalidate the plans and see what happens +DROP TABLE foo; + +EXECUTE prepstmt; +EXECUTE prepstmt2(123); + +-- recreate the temp table (this demonstrates that the raw plan is +-- purely textual and doesn't depend on OIDs, for instance) +CREATE TEMP TABLE foo AS SELECT * FROM int8_tbl ORDER BY 2; + +EXECUTE prepstmt; +EXECUTE prepstmt2(123); + +-- prepared statements should prevent change in output tupdesc, +-- since clients probably aren't expecting that to change on the fly +ALTER TABLE foo ADD COLUMN q3 bigint; + +EXECUTE prepstmt; +EXECUTE prepstmt2(123); + +-- but we're nice guys and will let you undo your mistake +ALTER TABLE foo DROP COLUMN q3; + +EXECUTE prepstmt; +EXECUTE prepstmt2(123); + +-- Try it with a view, which isn't directly used in the resulting plan +-- but should trigger invalidation anyway +CREATE TEMP VIEW voo AS SELECT * FROM foo; + +PREPARE vprep AS SELECT * FROM voo; + +EXECUTE vprep; + +CREATE OR REPLACE TEMP VIEW voo AS SELECT q1, q2/2 AS q2 FROM foo; + +EXECUTE vprep;