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