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.
This commit is contained in:
Tom Lane 2007-03-13 00:33:44 +00:00
parent f84308f195
commit b9527e9840
61 changed files with 2478 additions and 1354 deletions

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.235 2007/03/12 22:09:27 petere Exp $
* $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.236 2007/03/13 00:33:38 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2504,12 +2504,13 @@ AbortCurrentTransaction(void)
* could issue more commands and possibly cause a failure after the statement
* completes). Subtransactions are verboten too.
*
* stmtNode: pointer to parameter block for statement; this is used in
* a very klugy way to determine whether we are inside a function.
* stmtType: statement type name for error messages.
* isTopLevel: passed down from ProcessUtility to determine whether we are
* inside a function. (We will always fail if this is false, but it's
* convenient to centralize the check here instead of making callers do it.)
* stmtType: statement type name, for error messages.
*/
void
PreventTransactionChain(void *stmtNode, const char *stmtType)
PreventTransactionChain(bool isTopLevel, const char *stmtType)
{
/*
* xact block already started?
@ -2532,11 +2533,9 @@ PreventTransactionChain(void *stmtNode, const char *stmtType)
stmtType)));
/*
* Are we inside a function call? If the statement's parameter block was
* allocated in QueryContext, assume it is an interactive command.
* Otherwise assume it is coming from a function.
* inside a function call?
*/
if (!MemoryContextContains(QueryContext, stmtNode))
if (!isTopLevel)
ereport(ERROR,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
@ -2562,12 +2561,12 @@ PreventTransactionChain(void *stmtNode, const char *stmtType)
* use of the current statement's results. Likewise subtransactions.
* Thus this is an inverse for PreventTransactionChain.
*
* stmtNode: pointer to parameter block for statement; this is used in
* a very klugy way to determine whether we are inside a function.
* stmtType: statement type name for error messages.
* isTopLevel: passed down from ProcessUtility to determine whether we are
* inside a function.
* stmtType: statement type name, for error messages.
*/
void
RequireTransactionChain(void *stmtNode, const char *stmtType)
RequireTransactionChain(bool isTopLevel, const char *stmtType)
{
/*
* xact block already started?
@ -2582,12 +2581,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType)
return;
/*
* Are we inside a function call? If the statement's parameter block was
* allocated in QueryContext, assume it is an interactive command.
* Otherwise assume it is coming from a function.
* inside a function call?
*/
if (!MemoryContextContains(QueryContext, stmtNode))
if (!isTopLevel)
return;
ereport(ERROR,
(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
/* translator: %s represents an SQL statement name */
@ -2602,11 +2600,11 @@ RequireTransactionChain(void *stmtNode, const char *stmtType)
* a transaction block than when running as single commands. ANALYZE is
* currently the only example.
*
* stmtNode: pointer to parameter block for statement; this is used in
* a very klugy way to determine whether we are inside a function.
* isTopLevel: passed down from ProcessUtility to determine whether we are
* inside a function.
*/
bool
IsInTransactionChain(void *stmtNode)
IsInTransactionChain(bool isTopLevel)
{
/*
* Return true on same conditions that would make PreventTransactionChain
@ -2618,7 +2616,7 @@ IsInTransactionChain(void *stmtNode)
if (IsSubTransaction())
return true;
if (!MemoryContextContains(QueryContext, stmtNode))
if (!isTopLevel)
return true;
if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&

View File

@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.87 2007/03/07 13:35:02 alvherre Exp $
* $PostgreSQL: pgsql/src/backend/bootstrap/bootparse.y,v 1.88 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -252,7 +252,7 @@ Boot_DeclareIndexStmt:
LexIDStr($8),
NULL,
$10,
NULL, NIL, NIL,
NULL, NIL,
false, false, false,
false, false, true, false, false);
do_end();
@ -270,7 +270,7 @@ Boot_DeclareUniqueIndexStmt:
LexIDStr($9),
NULL,
$11,
NULL, NIL, NIL,
NULL, NIL,
true, false, false,
false, false, true, false, false);
do_end();

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.156 2007/02/01 19:10:25 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/cluster.c,v 1.157 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -82,7 +82,7 @@ static List *get_tables_to_cluster(MemoryContext cluster_context);
*---------------------------------------------------------------------------
*/
void
cluster(ClusterStmt *stmt)
cluster(ClusterStmt *stmt, bool isTopLevel)
{
if (stmt->relation != NULL)
{
@ -173,7 +173,7 @@ cluster(ClusterStmt *stmt)
* We cannot run this form of CLUSTER inside a user transaction block;
* we'd be holding locks way too long.
*/
PreventTransactionChain((void *) stmt, "CLUSTER");
PreventTransactionChain(isTopLevel, "CLUSTER");
/*
* Create special memory context for cross-transaction storage.

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.277 2007/03/03 19:32:54 neilc Exp $
* $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.278 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -713,7 +713,7 @@ CopyLoadRawBuf(CopyState cstate)
* the table.
*/
uint64
DoCopy(const CopyStmt *stmt)
DoCopy(const CopyStmt *stmt, const char *queryString)
{
CopyState cstate;
bool is_from = stmt->is_from;
@ -982,13 +982,11 @@ DoCopy(const CopyStmt *stmt)
}
else
{
Query *query = stmt->query;
List *rewritten;
Query *query;
PlannedStmt *plan;
DestReceiver *dest;
Assert(query);
Assert(query->commandType == CMD_SELECT);
Assert(!is_from);
cstate->rel = NULL;
@ -998,33 +996,18 @@ DoCopy(const CopyStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT) WITH OIDS is not supported")));
/* Query mustn't use INTO, either */
if (query->into)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
/*
* The query has already been through parse analysis, but not
* rewriting or planning. Do that now.
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
*
* Because the planner is not cool about not scribbling on its input,
* we make a preliminary copy of the source querytree. This prevents
* Because the parser and planner tend to scribble on their input, we
* make a preliminary copy of the source querytree. This prevents
* problems in the case that the COPY is in a portal or plpgsql
* function and is executed repeatedly. (See also the same hack in
* EXPLAIN, DECLARE CURSOR and PREPARE.) XXX the planner really
* shouldn't modify its input ... FIXME someday.
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
query = copyObject(query);
/*
* Must acquire locks in case we didn't come fresh from the parser.
* XXX this also scribbles on query, another reason for copyObject
*/
AcquireRewriteLocks(query);
/* Rewrite through rule system */
rewritten = QueryRewrite(query);
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
queryString, NULL, 0);
/* We don't expect more or less than one result query */
if (list_length(rewritten) != 1)
@ -1033,6 +1016,12 @@ DoCopy(const CopyStmt *stmt)
query = (Query *) linitial(rewritten);
Assert(query->commandType == CMD_SELECT);
/* Query mustn't use INTO, either */
if (query->into)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("COPY (SELECT INTO) is not supported")));
/* plan the query */
plan = planner(query, false, 0, NULL);

View File

@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.192 2007/02/09 16:12:18 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.193 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -97,9 +97,6 @@ createdb(const CreatedbStmt *stmt)
int encoding = -1;
int dbconnlimit = -1;
/* don't call this in a transaction block */
PreventTransactionChain((void *) stmt, "CREATE DATABASE");
/* Extract options from the statement node tree */
foreach(option, stmt->options)
{
@ -545,8 +542,6 @@ dropdb(const char *dbname, bool missing_ok)
Relation pgdbrel;
HeapTuple tup;
PreventTransactionChain((void *) dbname, "DROP DATABASE");
AssertArg(dbname);
if (strcmp(dbname, get_database_name(MyDatabaseId)) == 0)

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.159 2007/02/23 21:59:44 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.160 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -26,6 +26,7 @@
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
#include "tcop/tcopprot.h"
#include "utils/builtins.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
@ -41,8 +42,9 @@ typedef struct ExplainState
List *rtable; /* range table */
} ExplainState;
static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
ParamListInfo params, TupOutputState *tstate);
static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
ExplainStmt *stmt, const char *queryString,
ParamListInfo params, TupOutputState *tstate);
static double elapsed_time(instr_time *starttime);
static void explain_outNode(StringInfo str,
Plan *plan, PlanState *planstate,
@ -62,62 +64,49 @@ static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
* execute an EXPLAIN command
*/
void
ExplainQuery(ExplainStmt *stmt, ParamListInfo params, DestReceiver *dest)
ExplainQuery(ExplainStmt *stmt, const char *queryString,
ParamListInfo params, DestReceiver *dest)
{
Query *query = stmt->query;
Oid *param_types;
int num_params;
TupOutputState *tstate;
List *rewritten;
ListCell *l;
/* Convert parameter type data to the form parser wants */
getParamListTypes(params, &param_types, &num_params);
/*
* Because the planner is not cool about not scribbling on its input, we
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
*
* Because the parser and planner tend to scribble on their input, we
* make a preliminary copy of the source querytree. This prevents
* problems in the case that the EXPLAIN is in a portal or plpgsql
* function and is executed repeatedly. (See also the same hack in
* DECLARE CURSOR and PREPARE.) XXX the planner really shouldn't modify
* its input ... FIXME someday.
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
*/
query = copyObject(query);
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
queryString, param_types, num_params);
/* prepare for projection of tuples */
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
if (query->commandType == CMD_UTILITY)
if (rewritten == NIL)
{
/* Rewriter will not cope with utility statements */
if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
ExplainOneQuery(query, stmt, params, tstate);
else if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
ExplainExecuteQuery(stmt, params, tstate);
else
do_text_output_oneline(tstate, "Utility statements have no plan structure");
/* In the case of an INSTEAD NOTHING, tell at least that */
do_text_output_oneline(tstate, "Query rewrites to nothing");
}
else
{
/*
* Must acquire locks in case we didn't come fresh from the parser.
* XXX this also scribbles on query, another reason for copyObject
*/
AcquireRewriteLocks(query);
/* Rewrite through rule system */
rewritten = QueryRewrite(query);
if (rewritten == NIL)
/* 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, &param_types, &num_params);
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
*
* Because the parser and planner tend to scribble on their input, we
* make a preliminary copy of the source querytree. This prevents
* problems in the case that the DECLARE CURSOR is in a portal or
* plpgsql function and is executed repeatedly. (See also the same
* hack in COPY and PREPARE.) XXX FIXME someday.
*/
rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query),
queryString,
param_types, num_params);
/* We don't expect more or less than one result query */
if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
elog(ERROR, "unexpected rewrite result");
query = (Query *) linitial(rewritten);
if (query->commandType != CMD_SELECT)
elog(ERROR, "unexpected rewrite result");
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
if (query->into)
ereport(ERROR,
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
errmsg("DECLARE CURSOR cannot specify INTO")));
/* do not actually execute the underlying query! */
memcpy(&newstmt, stmt, sizeof(ExplainStmt));
newstmt.analyze = false;
ExplainOneQuery(query, true, dcstmt->options, &newstmt,
queryString, params, tstate);
}
else if (IsA(utilityStmt, ExecuteStmt))
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
queryString, params, tstate);
else if (IsA(utilityStmt, NotifyStmt))
do_text_output_oneline(tstate, "NOTIFY");
else
do_text_output_oneline(tstate,
"Utility statements have no plan structure");
}
/*
* ExplainOnePlan -
* given a planned query, execute it if needed, and then print

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.156 2007/03/06 02:06:12 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/indexcmds.c,v 1.157 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -77,7 +77,6 @@ static bool relationHasPrimaryKey(Relation rel);
* 'attributeList': a list of IndexElem specifying columns and expressions
* to index on.
* 'predicate': the partial-index condition, or NULL if none.
* 'rangetable': needed to interpret the predicate.
* 'options': reloptions from WITH (in list-of-DefElem form).
* 'unique': make the index enforce uniqueness.
* 'primary': mark the index as a primary key in the catalogs.
@ -99,7 +98,6 @@ DefineIndex(RangeVar *heapRelation,
char *tableSpaceName,
List *attributeList,
Expr *predicate,
List *rangetable,
List *options,
bool unique,
bool primary,
@ -300,18 +298,6 @@ DefineIndex(RangeVar *heapRelation,
ReleaseSysCache(tuple);
/*
* If a range table was created then check that only the base rel is
* mentioned.
*/
if (rangetable != NIL)
{
if (list_length(rangetable) != 1 || getrelid(1, rangetable) != relationId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
errmsg("index expressions and predicates can refer only to the table being indexed")));
}
/*
* Validate predicate, if given
*/
@ -1218,6 +1204,7 @@ ReindexTable(RangeVar *relation)
*
* To reduce the probability of deadlocks, each table is reindexed in a
* separate transaction, so we can release the lock on it right away.
* That means this must not be called within a user transaction block!
*/
void
ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
@ -1241,13 +1228,6 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user)
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_DATABASE,
databaseName);
/*
* We cannot run inside a user transaction block; if we were inside a
* transaction, then our commit- and start-transaction-command calls would
* not have the intended effect!
*/
PreventTransactionChain((void *) databaseName, "REINDEX DATABASE");
/*
* Create a memory context that will survive forced transaction commits we
* do below. Since it is a child of PortalContext, it will go away

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.61 2007/02/20 17:32:14 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.62 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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, &param_types, &num_params);
/*
* Run parse analysis and rewrite. Note this also acquires sufficient
* locks on the source table(s).
*
* Because the parser and planner tend to scribble on their input, we
* make a preliminary copy of the source querytree. This prevents
* problems in the case that the DECLARE CURSOR is in a portal 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;

View File

@ -10,7 +10,7 @@
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.69 2007/02/20 17:32:14 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.70 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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 = &paramLI->params[i];
prm->ptype = lfirst_oid(la);
prm->ptype = param_types[i];
prm->pflags = 0;
prm->value = ExecEvalExprSwitchContext(n,
GetPerTupleExprContext(estate),
@ -293,8 +420,9 @@ InitQueryHashTable(void)
/*
* Store all the data pertaining to a query in the hash table using
* the specified key. A copy of the data is made in a memory context belonging
* to the hash entry, so the caller can dispose of their copy.
* the specified key. All the given data is copied into either the hashtable
* entry or the underlying plancache entry, so the caller can dispose of its
* copy.
*
* Exception: commandTag is presumed to be a pointer to a constant string,
* or possibly NULL, so it need not be copied. Note that commandTag should
@ -302,17 +430,16 @@ InitQueryHashTable(void)
*/
void
StorePreparedStatement(const char *stmt_name,
Node *raw_parse_tree,
const char *query_string,
const char *commandTag,
Oid *param_types,
int num_params,
List *stmt_list,
List *argtype_list,
bool fully_planned,
bool from_sql)
{
PreparedStatement *entry;
MemoryContext oldcxt,
entrycxt;
char *qstring;
CachedPlanSource *plansource;
bool found;
/* Initialize the hash table, if necessary */
@ -328,24 +455,15 @@ StorePreparedStatement(const char *stmt_name,
errmsg("prepared statement \"%s\" already exists",
stmt_name)));
/* Make a permanent memory context for the hashtable entry */
entrycxt = AllocSetContextCreate(TopMemoryContext,
stmt_name,
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
oldcxt = MemoryContextSwitchTo(entrycxt);
/*
* We need to copy the data so that it is stored in the correct memory
* context. Do this before making hashtable entry, so that an
* out-of-memory failure only wastes memory and doesn't leave us with an
* incomplete (ie corrupt) hashtable entry.
*/
qstring = query_string ? pstrdup(query_string) : NULL;
stmt_list = (List *) copyObject(stmt_list);
argtype_list = list_copy(argtype_list);
/* Create a plancache entry */
plansource = CreateCachedPlan(raw_parse_tree,
query_string,
commandTag,
param_types,
num_params,
stmt_list,
true,
true);
/* Now we can add entry to hash table */
entry = (PreparedStatement *) hash_search(prepared_queries,
@ -358,22 +476,18 @@ StorePreparedStatement(const char *stmt_name,
elog(ERROR, "duplicate prepared statement \"%s\"",
stmt_name);
/* Fill in the hash table entry with copied data */
entry->query_string = qstring;
entry->commandTag = commandTag;
entry->stmt_list = stmt_list;
entry->argtype_list = argtype_list;
entry->fully_planned = fully_planned;
/* Fill in the hash table entry */
entry->plansource = plansource;
entry->from_sql = from_sql;
entry->context = entrycxt;
entry->prepare_time = GetCurrentStatementStartTimestamp();
MemoryContextSwitchTo(oldcxt);
}
/*
* Lookup an existing query in the hash table. If the query does not
* actually exist, throw ereport(ERROR) or return NULL per second parameter.
*
* Note: this does not force the referenced plancache entry to be valid,
* since not all callers care.
*/
PreparedStatement *
FetchPreparedStatement(const char *stmt_name, bool throwError)
@ -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);
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.43 2007/02/01 19:10:26 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/schemacmds.c,v 1.44 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -38,7 +38,7 @@ static void AlterSchemaOwner_internal(HeapTuple tup, Relation rel, Oid newOwnerI
* CREATE SCHEMA
*/
void
CreateSchemaCommand(CreateSchemaStmt *stmt)
CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
{
const char *schemaName = stmt->schemaname;
const char *authId = stmt->authid;
@ -122,7 +122,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt)
List *querytree_list;
ListCell *querytree_item;
querytree_list = parse_analyze(parsetree, NULL, NULL, 0);
querytree_list = parse_analyze(parsetree, queryString, NULL, 0);
foreach(querytree_item, querytree_list)
{
@ -131,7 +131,12 @@ CreateSchemaCommand(CreateSchemaStmt *stmt)
/* schemas should contain only utility stmts */
Assert(querytree->commandType == CMD_UTILITY);
/* do this step */
ProcessUtility(querytree->utilityStmt, NULL, None_Receiver, NULL);
ProcessUtility(querytree->utilityStmt,
queryString,
NULL,
false, /* not top level */
None_Receiver,
NULL);
/* make sure later steps can see the object created here */
CommandCounterIncrement();
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.216 2007/03/06 02:06:13 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.217 2007/03/13 00:33:39 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -3696,6 +3696,13 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
/*
* Run parse analysis. We don't have convenient access to the query text
* here, but it's probably not worth worrying about.
*/
stmt = analyzeIndexStmt(stmt, NULL);
/* ... and do it */
DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
@ -3703,7 +3710,6 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
stmt->tableSpace,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->rangetable,
stmt->options,
stmt->unique,
stmt->primary,

View File

@ -37,7 +37,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.43 2007/03/06 02:06:13 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/tablespace.c,v 1.44 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -198,11 +198,6 @@ CreateTableSpace(CreateTableSpaceStmt *stmt)
char *linkloc;
Oid ownerId;
/* validate */
/* don't call this in a transaction block */
PreventTransactionChain((void *) stmt, "CREATE TABLESPACE");
/* Must be super user */
if (!superuser())
ereport(ERROR,
@ -385,9 +380,6 @@ DropTableSpace(DropTableSpaceStmt *stmt)
ScanKeyData entry[1];
Oid tablespaceoid;
/* don't call this in a transaction block */
PreventTransactionChain((void *) stmt, "DROP TABLESPACE");
/*
* Find the target tuple
*/

View File

@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.347 2007/03/08 17:03:31 tgl Exp $
* $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.348 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -257,13 +257,14 @@ static Size PageGetFreeSpaceWithFillFactor(Relation relation, Page page);
* relation OIDs to be processed, and vacstmt->relation is ignored.
* (The non-NIL case is currently only used by autovacuum.)
*
* isTopLevel should be passed down from ProcessUtility.
*
* It is the caller's responsibility that both vacstmt and relids
* (if given) be allocated in a memory context that won't disappear
* at transaction commit. In fact this context must be QueryContext
* to avoid complaints from PreventTransactionChain.
* at transaction commit.
*/
void
vacuum(VacuumStmt *vacstmt, List *relids)
vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel)
{
const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE";
volatile MemoryContext anl_context = NULL;
@ -293,11 +294,11 @@ vacuum(VacuumStmt *vacstmt, List *relids)
*/
if (vacstmt->vacuum)
{
PreventTransactionChain((void *) vacstmt, stmttype);
PreventTransactionChain(isTopLevel, stmttype);
in_outer_xact = false;
}
else
in_outer_xact = IsInTransactionChain((void *) vacstmt);
in_outer_xact = IsInTransactionChain(isTopLevel);
/*
* Send info about dead objects to the statistics collector, unless we are

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.99 2007/01/05 22:19:27 momjian Exp $
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.100 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -24,6 +24,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
#include "parser/analyze.h"
#include "parser/parse_expr.h"
#include "parser/parse_relation.h"
#include "rewrite/rewriteDefine.h"
@ -258,54 +259,23 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
*/
}
static RuleStmt *
FormViewRetrieveRule(const RangeVar *view, Query *viewParse, bool replace)
{
RuleStmt *rule;
/*
* Create a RuleStmt that corresponds to the suitable rewrite rule args
* for DefineQueryRewrite();
*/
rule = makeNode(RuleStmt);
rule->relation = copyObject((RangeVar *) view);
rule->rulename = pstrdup(ViewSelectRuleName);
rule->whereClause = NULL;
rule->event = CMD_SELECT;
rule->instead = true;
rule->actions = list_make1(viewParse);
rule->replace = replace;
return rule;
}
static void
DefineViewRules(const RangeVar *view, Query *viewParse, bool replace)
{
RuleStmt *retrieve_rule;
#ifdef NOTYET
RuleStmt *replace_rule;
RuleStmt *append_rule;
RuleStmt *delete_rule;
#endif
retrieve_rule = FormViewRetrieveRule(view, viewParse, replace);
#ifdef NOTYET
replace_rule = FormViewReplaceRule(view, viewParse);
append_rule = FormViewAppendRule(view, viewParse);
delete_rule = FormViewDeleteRule(view, viewParse);
#endif
DefineQueryRewrite(retrieve_rule);
#ifdef NOTYET
DefineQueryRewrite(replace_rule);
DefineQueryRewrite(append_rule);
DefineQueryRewrite(delete_rule);
#endif
/*
* Set up the ON SELECT rule. Since the query has already been through
* parse analysis, we use DefineQueryRewrite() directly.
*/
DefineQueryRewrite(pstrdup(ViewSelectRuleName),
(RangeVar *) copyObject((RangeVar *) view),
NULL,
CMD_SELECT,
true,
replace,
list_make1(viewParse));
/*
* Someday: automatic ON INSERT, etc
*/
}
/*---------------------------------------------------------------
@ -374,34 +344,80 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
return viewParse;
}
/*-------------------------------------------------------------------
/*
* DefineView
*
* - takes a "viewname", "parsetree" pair and then
* 1) construct the "virtual" relation
* 2) commit the command but NOT the transaction,
* so that the relation exists
* before the rules are defined.
* 2) define the "n" rules specified in the PRS2 paper
* over the "virtual" relation
*-------------------------------------------------------------------
* Execute a CREATE VIEW command.
*/
void
DefineView(RangeVar *view, Query *viewParse, bool replace)
DefineView(ViewStmt *stmt, const char *queryString)
{
List *stmts;
Query *viewParse;
Oid viewOid;
RangeVar *view;
/*
* Run parse analysis to convert the raw parse tree to a Query. Note
* this also acquires sufficient locks on the source table(s).
*
* Since parse analysis scribbles on its input, copy the raw parse tree;
* this ensures we don't corrupt a prepared statement, for example.
*/
stmts = parse_analyze((Node *) copyObject(stmt->query),
queryString, NULL, 0);
/*
* The grammar should ensure that the result is a single SELECT Query.
*/
if (list_length(stmts) != 1)
elog(ERROR, "unexpected parse analysis result");
viewParse = (Query *) linitial(stmts);
if (!IsA(viewParse, Query) ||
viewParse->commandType != CMD_SELECT)
elog(ERROR, "unexpected parse analysis result");
/*
* If a list of column names was given, run through and insert these into
* the actual query tree. - thomas 2000-03-08
*/
if (stmt->aliases != NIL)
{
ListCell *alist_item = list_head(stmt->aliases);
ListCell *targetList;
foreach(targetList, viewParse->targetList)
{
TargetEntry *te = (TargetEntry *) lfirst(targetList);
Assert(IsA(te, TargetEntry));
/* junk columns don't get aliases */
if (te->resjunk)
continue;
te->resname = pstrdup(strVal(lfirst(alist_item)));
alist_item = lnext(alist_item);
if (alist_item == NULL)
break; /* done assigning aliases */
}
if (alist_item != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE VIEW specifies more column "
"names than columns")));
}
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly.
*/
if (!view->istemp)
view = stmt->view;
if (!view->istemp && isViewOnTempTable(viewParse))
{
view->istemp = isViewOnTempTable(viewParse);
if (view->istemp)
ereport(NOTICE,
(errmsg("view \"%s\" will be a temporary view",
view->relname)));
view = copyObject(view); /* don't corrupt original command */
view->istemp = true;
ereport(NOTICE,
(errmsg("view \"%s\" will be a temporary view",
view->relname)));
}
/*
@ -410,7 +426,8 @@ DefineView(RangeVar *view, Query *viewParse, bool replace)
* NOTE: if it already exists and replace is false, the xact will be
* aborted.
*/
viewOid = DefineVirtualRelation(view, viewParse->targetList, replace);
viewOid = DefineVirtualRelation(view, viewParse->targetList,
stmt->replace);
/*
* The relation we have just created is not visible to any other commands
@ -428,7 +445,7 @@ DefineView(RangeVar *view, Query *viewParse, bool replace)
/*
* Now create the rules associated with the view.
*/
DefineViewRules(view, viewParse, replace);
DefineViewRules(view, viewParse, stmt->replace);
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.111 2007/02/20 17:32:15 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.112 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -58,6 +58,8 @@ typedef struct local_es
*/
typedef struct
{
char *src; /* function body text (for error msgs) */
Oid *argtypes; /* resolved types of arguments */
Oid rettype; /* actual return type */
int16 typlen; /* length of the return type */
@ -82,7 +84,8 @@ static execution_state *init_execution_state(List *queryTree_list,
bool readonly_func);
static void init_sql_fcache(FmgrInfo *finfo);
static void postquel_start(execution_state *es, SQLFunctionCachePtr fcache);
static TupleTableSlot *postquel_getnext(execution_state *es);
static TupleTableSlot *postquel_getnext(execution_state *es,
SQLFunctionCachePtr fcache);
static void postquel_end(execution_state *es);
static void postquel_sub_params(SQLFunctionCachePtr fcache,
FunctionCallInfo fcinfo);
@ -156,7 +159,6 @@ init_sql_fcache(FmgrInfo *finfo)
Form_pg_proc procedureStruct;
SQLFunctionCachePtr fcache;
Oid *argOidVect;
char *src;
int nargs;
List *queryTree_list;
Datum tmp;
@ -233,7 +235,7 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->argtypes = argOidVect;
/*
* Parse and rewrite the queries in the function text.
* And of course we need the function body text.
*/
tmp = SysCacheGetAttr(PROCOID,
procedureTuple,
@ -241,9 +243,12 @@ init_sql_fcache(FmgrInfo *finfo)
&isNull);
if (isNull)
elog(ERROR, "null prosrc for function %u", foid);
src = DatumGetCString(DirectFunctionCall1(textout, tmp));
fcache->src = DatumGetCString(DirectFunctionCall1(textout, tmp));
queryTree_list = pg_parse_and_rewrite(src, argOidVect, nargs);
/*
* Parse and rewrite the queries in the function text.
*/
queryTree_list = pg_parse_and_rewrite(fcache->src, argOidVect, nargs);
/*
* Check that the function returns the type it claims to. Although
@ -270,8 +275,6 @@ init_sql_fcache(FmgrInfo *finfo)
fcache->func_state = init_execution_state(queryTree_list,
fcache->readonly_func);
pfree(src);
ReleaseSysCache(procedureTuple);
finfo->fn_extra = (void *) fcache;
@ -331,7 +334,7 @@ postquel_start(execution_state *es, SQLFunctionCachePtr fcache)
}
static TupleTableSlot *
postquel_getnext(execution_state *es)
postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
{
TupleTableSlot *result;
Snapshot saveActiveSnapshot;
@ -345,8 +348,12 @@ postquel_getnext(execution_state *es)
if (es->qd->operation == CMD_UTILITY)
{
ProcessUtility(es->qd->utilitystmt, es->qd->params,
es->qd->dest, NULL);
ProcessUtility(es->qd->utilitystmt,
fcache->src,
es->qd->params,
false, /* not top level */
es->qd->dest,
NULL);
result = NULL;
}
else
@ -465,7 +472,7 @@ postquel_execute(execution_state *es,
if (es->status == F_EXEC_START)
postquel_start(es, fcache);
slot = postquel_getnext(es);
slot = postquel_getnext(es, fcache);
if (TupIsNull(slot))
{
@ -754,21 +761,11 @@ sql_exec_error_callback(void *arg)
* If there is a syntax error position, convert to internal syntax error
*/
syntaxerrposition = geterrposition();
if (syntaxerrposition > 0)
if (syntaxerrposition > 0 && fcache->src)
{
bool isnull;
Datum tmp;
char *prosrc;
tmp = SysCacheGetAttr(PROCOID, func_tuple, Anum_pg_proc_prosrc,
&isnull);
if (isnull)
elog(ERROR, "null prosrc");
prosrc = DatumGetCString(DirectFunctionCall1(textout, tmp));
errposition(0);
internalerrposition(syntaxerrposition);
internalerrquery(prosrc);
pfree(prosrc);
internalerrquery(fcache->src);
}
/*

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.170 2007/02/20 17:32:15 tgl Exp $
* $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.171 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -927,7 +927,7 @@ SPI_cursor_open(const char *name, void *plan,
spiplan->query,
CreateCommandTag(PortalListGetPrimaryStmt(stmt_list)),
stmt_list,
PortalGetHeapMemory(portal));
NULL);
MemoryContextSwitchTo(oldcontext);
@ -1471,7 +1471,12 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
}
else
{
ProcessUtility(stmt, paramLI, dest, NULL);
ProcessUtility(stmt,
NULL, /* XXX provide query string? */
paramLI,
false, /* not top level */
dest,
NULL);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
_SPI_current->processed = _SPI_current->tuptable->alloced - _SPI_current->tuptable->free;

View File

@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.369 2007/02/27 01:11:25 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.370 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -2142,7 +2142,6 @@ _copyIndexStmt(IndexStmt *from)
COPY_NODE_FIELD(indexParams);
COPY_NODE_FIELD(options);
COPY_NODE_FIELD(whereClause);
COPY_NODE_FIELD(rangetable);
COPY_SCALAR_FIELD(unique);
COPY_SCALAR_FIELD(primary);
COPY_SCALAR_FIELD(isconstraint);
@ -2785,7 +2784,6 @@ _copyPrepareStmt(PrepareStmt *from)
COPY_STRING_FIELD(name);
COPY_NODE_FIELD(argtypes);
COPY_NODE_FIELD(argtype_oids);
COPY_NODE_FIELD(query);
return newnode;

View File

@ -18,7 +18,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.300 2007/02/22 22:00:23 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.301 2007/03/13 00:33:40 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -994,7 +994,6 @@ _equalIndexStmt(IndexStmt *a, IndexStmt *b)
COMPARE_NODE_FIELD(indexParams);
COMPARE_NODE_FIELD(options);
COMPARE_NODE_FIELD(whereClause);
COMPARE_NODE_FIELD(rangetable);
COMPARE_SCALAR_FIELD(unique);
COMPARE_SCALAR_FIELD(primary);
COMPARE_SCALAR_FIELD(isconstraint);
@ -1536,7 +1535,6 @@ _equalPrepareStmt(PrepareStmt *a, PrepareStmt *b)
{
COMPARE_STRING_FIELD(name);
COMPARE_NODE_FIELD(argtypes);
COMPARE_NODE_FIELD(argtype_oids);
COMPARE_NODE_FIELD(query);
return true;

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.302 2007/02/27 01:11:25 tgl Exp $
* $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.303 2007/03/13 00:33:40 tgl Exp $
*
* NOTES
* Every node type that can appear in stored rules' parsetrees *must*
@ -1505,7 +1505,6 @@ _outIndexStmt(StringInfo str, IndexStmt *node)
WRITE_NODE_FIELD(indexParams);
WRITE_NODE_FIELD(options);
WRITE_NODE_FIELD(whereClause);
WRITE_NODE_FIELD(rangetable);
WRITE_BOOL_FIELD(unique);
WRITE_BOOL_FIELD(primary);
WRITE_BOOL_FIELD(isconstraint);

View File

@ -8,7 +8,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.8 2007/01/05 22:19:30 momjian Exp $
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.9 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -60,3 +60,34 @@ copyParamList(ParamListInfo from)
return retval;
}
/*
* Extract an array of parameter type OIDs from a ParamListInfo.
*
* The result is allocated in CurrentMemoryContext.
*/
void
getParamListTypes(ParamListInfo params,
Oid **param_types, int *num_params)
{
Oid *ptypes;
int i;
if (params == NULL || params->numParams <= 0)
{
*param_types = NULL;
*num_params = 0;
return;
}
ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
*param_types = ptypes;
*num_params = params->numParams;
for (i = 0; i < params->numParams; i++)
{
ParamExternData *prm = &params->params[i];
ptypes[i] = prm->ptype;
}
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.237 2007/03/06 22:45:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/util/clauses.c,v 1.238 2007/03/13 00:33:41 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -3603,38 +3603,6 @@ query_tree_walker(Query *query,
return true;
if (range_table_walker(query->rtable, walker, context, flags))
return true;
if (query->utilityStmt)
{
/*
* Certain utility commands contain general-purpose Querys embedded in
* them --- if this is one, invoke the walker on the sub-Query.
*/
if (IsA(query->utilityStmt, CopyStmt))
{
if (walker(((CopyStmt *) query->utilityStmt)->query, context))
return true;
}
if (IsA(query->utilityStmt, DeclareCursorStmt))
{
if (walker(((DeclareCursorStmt *) query->utilityStmt)->query, context))
return true;
}
if (IsA(query->utilityStmt, ExplainStmt))
{
if (walker(((ExplainStmt *) query->utilityStmt)->query, context))
return true;
}
if (IsA(query->utilityStmt, PrepareStmt))
{
if (walker(((PrepareStmt *) query->utilityStmt)->query, context))
return true;
}
if (IsA(query->utilityStmt, ViewStmt))
{
if (walker(((ViewStmt *) query->utilityStmt)->query, context))
return true;
}
}
return false;
}

View File

@ -1,12 +1,26 @@
/*-------------------------------------------------------------------------
*
* analyze.c
* transform the parse tree into a query tree
* transform the raw parse tree into a query tree
*
* For optimizable statements, we are careful to obtain a suitable lock on
* each referenced table, and other modules of the backend preserve or
* re-obtain these locks before depending on the results. It is therefore
* okay to do significant semantic analysis of these statements. For
* utility commands, no locks are obtained here (and if they were, we could
* not be sure we'd still have them at execution). Hence the general rule
* for utility commands is to just dump them into a Query node untransformed.
* parse_analyze does do some purely syntactic transformations on CREATE TABLE
* and ALTER TABLE, but that's about it. In cases where this module contains
* mechanisms that are useful for utility statements, we provide separate
* subroutines that should be called at the beginning of utility execution;
* an example is analyzeIndexStmt.
*
*
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.361 2007/02/20 17:32:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/analyze.c,v 1.362 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -93,26 +107,17 @@ typedef struct
static List *do_parse_analyze(Node *parseTree, ParseState *pstate);
static Query *transformStmt(ParseState *pstate, Node *stmt,
List **extras_before, List **extras_after);
static Query *transformViewStmt(ParseState *pstate, ViewStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt);
static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt,
List **extras_before, List **extras_after);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos);
static List *transformReturningList(ParseState *pstate, List *returningList);
static Query *transformIndexStmt(ParseState *pstate, IndexStmt *stmt);
static Query *transformRuleStmt(ParseState *query, RuleStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt);
static Query *transformValuesClause(ParseState *pstate, SelectStmt *stmt);
static Query *transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt);
static Node *transformSetOperationTree(ParseState *pstate, SelectStmt *stmt);
static Query *transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt);
static Query *transformDeclareCursorStmt(ParseState *pstate,
DeclareCursorStmt *stmt);
static Query *transformPrepareStmt(ParseState *pstate, PrepareStmt *stmt);
static Query *transformExecuteStmt(ParseState *pstate, ExecuteStmt *stmt);
static Query *transformCreateStmt(ParseState *pstate, CreateStmt *stmt,
List **extras_before, List **extras_after);
static Query *transformAlterTableStmt(ParseState *pstate, AlterTableStmt *stmt,
@ -155,7 +160,7 @@ static bool check_parameter_resolution_walker(Node *node,
*
* The result is a List of Query nodes (we need a list since some commands
* produce multiple Queries). Optimizable statements require considerable
* transformation, while many utility-type statements are simply hung off
* transformation, while most utility-type statements are simply hung off
* a dummy CMD_UTILITY Query node.
*/
List *
@ -315,59 +320,12 @@ transformStmt(ParseState *pstate, Node *parseTree,
extras_before, extras_after);
break;
case T_IndexStmt:
result = transformIndexStmt(pstate, (IndexStmt *) parseTree);
break;
case T_RuleStmt:
result = transformRuleStmt(pstate, (RuleStmt *) parseTree,
extras_before, extras_after);
break;
case T_ViewStmt:
result = transformViewStmt(pstate, (ViewStmt *) parseTree,
extras_before, extras_after);
break;
case T_ExplainStmt:
{
ExplainStmt *n = (ExplainStmt *) parseTree;
result = makeNode(Query);
result->commandType = CMD_UTILITY;
n->query = transformStmt(pstate, (Node *) n->query,
extras_before, extras_after);
result->utilityStmt = (Node *) parseTree;
}
break;
case T_CopyStmt:
{
CopyStmt *n = (CopyStmt *) parseTree;
result = makeNode(Query);
result->commandType = CMD_UTILITY;
if (n->query)
n->query = transformStmt(pstate, (Node *) n->query,
extras_before, extras_after);
result->utilityStmt = (Node *) parseTree;
}
break;
case T_AlterTableStmt:
result = transformAlterTableStmt(pstate,
(AlterTableStmt *) parseTree,
extras_before, extras_after);
break;
case T_PrepareStmt:
result = transformPrepareStmt(pstate, (PrepareStmt *) parseTree);
break;
case T_ExecuteStmt:
result = transformExecuteStmt(pstate, (ExecuteStmt *) parseTree);
break;
/*
* Optimizable statements
*/
@ -397,16 +355,11 @@ transformStmt(ParseState *pstate, Node *parseTree,
}
break;
case T_DeclareCursorStmt:
result = transformDeclareCursorStmt(pstate,
(DeclareCursorStmt *) parseTree);
break;
default:
/*
* other statements don't require any transformation-- just return
* the original parsetree, yea!
* other statements don't require any transformation; just return
* the original parsetree with a Query node plastered on top.
*/
result = makeNode(Query);
result->commandType = CMD_UTILITY;
@ -432,54 +385,6 @@ transformStmt(ParseState *pstate, Node *parseTree,
return result;
}
static Query *
transformViewStmt(ParseState *pstate, ViewStmt *stmt,
List **extras_before, List **extras_after)
{
Query *result = makeNode(Query);
result->commandType = CMD_UTILITY;
result->utilityStmt = (Node *) stmt;
stmt->query = transformStmt(pstate, (Node *) stmt->query,
extras_before, extras_after);
/*
* If a list of column names was given, run through and insert these into
* the actual query tree. - thomas 2000-03-08
*
* Outer loop is over targetlist to make it easier to skip junk targetlist
* entries.
*/
if (stmt->aliases != NIL)
{
ListCell *alist_item = list_head(stmt->aliases);
ListCell *targetList;
foreach(targetList, stmt->query->targetList)
{
TargetEntry *te = (TargetEntry *) lfirst(targetList);
Assert(IsA(te, TargetEntry));
/* junk columns don't get aliases */
if (te->resjunk)
continue;
te->resname = pstrdup(strVal(lfirst(alist_item)));
alist_item = lnext(alist_item);
if (alist_item == NULL)
break; /* done assigning aliases */
}
if (alist_item != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CREATE VIEW specifies more column "
"names than columns")));
}
return result;
}
/*
* transformDeleteStmt -
* transforms a Delete Statement
@ -1278,8 +1183,13 @@ transformTableConstraint(ParseState *pstate, CreateStmtContext *cxt,
/*
* transformInhRelation
*
* Change the LIKE <subtable> portion of a CREATE TABLE statement into the
* column definitions which recreate the user defined column portions of <subtable>.
* Change the LIKE <subtable> portion of a CREATE TABLE statement into
* column definitions which recreate the user defined column portions of
* <subtable>.
*
* Note: because we do this at parse analysis time, any change in the
* referenced table between parse analysis and execution won't be reflected
* into the new table. Is this OK?
*/
static void
transformInhRelation(ParseState *pstate, CreateStmtContext *cxt,
@ -1644,7 +1554,9 @@ transformIndexConstraints(ParseState *pstate, CreateStmtContext *cxt)
* that strikes me as too anal-retentive. - tgl 2001-02-14
*
* XXX in ALTER TABLE case, it'd be nice to look for duplicate
* pre-existing indexes, too.
* pre-existing indexes, too. However, that seems to risk race
* conditions since we can't be sure the command will be executed
* immediately.
*/
Assert(cxt->alist == NIL);
if (cxt->pkey != NULL)
@ -1746,37 +1658,55 @@ transformFKConstraints(ParseState *pstate, CreateStmtContext *cxt,
}
/*
* transformIndexStmt -
* transforms the qualification of the index statement
* analyzeIndexStmt - perform parse analysis for CREATE INDEX
*
* Note that this has to be performed during execution not parse analysis, so
* it's called by ProcessUtility. (Most other callers don't need to bother,
* because this is a no-op for an index not using either index expressions or
* a predicate expression.)
*/
static Query *
transformIndexStmt(ParseState *pstate, IndexStmt *stmt)
IndexStmt *
analyzeIndexStmt(IndexStmt *stmt, const char *queryString)
{
Query *qry;
RangeTblEntry *rte = NULL;
Relation rel;
ParseState *pstate;
RangeTblEntry *rte;
ListCell *l;
qry = makeNode(Query);
qry->commandType = CMD_UTILITY;
/*
* We must not scribble on the passed-in IndexStmt, so copy it. (This
* is overkill, but easy.)
*/
stmt = (IndexStmt *) copyObject(stmt);
/*
* 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

View File

@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.580 2007/02/20 17:32:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.581 2007/03/13 00:33:41 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@ -1662,7 +1662,7 @@ CopyStmt: COPY opt_binary qualified_name opt_column_list opt_oids
{
CopyStmt *n = makeNode(CopyStmt);
n->relation = NULL;
n->query = (Query *) $2;
n->query = $2;
n->attlist = NIL;
n->is_from = false;
n->filename = $4;
@ -4959,22 +4959,22 @@ ViewStmt: CREATE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
n->replace = false;
n->view = $4;
n->view->istemp = $2;
n->aliases = $5;
n->query = (Query *) $7;
n->query = $7;
n->replace = false;
$$ = (Node *) n;
}
| CREATE OR REPLACE OptTemp VIEW qualified_name opt_column_list
AS SelectStmt opt_check_option
{
ViewStmt *n = makeNode(ViewStmt);
n->replace = true;
n->view = $6;
n->view->istemp = $4;
n->aliases = $7;
n->query = (Query *) $9;
n->query = $9;
n->replace = true;
$$ = (Node *) n;
}
;
@ -5406,7 +5406,7 @@ ExplainStmt: EXPLAIN opt_analyze opt_verbose ExplainableStmt
ExplainStmt *n = makeNode(ExplainStmt);
n->analyze = $2;
n->verbose = $3;
n->query = (Query*)$4;
n->query = $4;
$$ = (Node *)n;
}
;
@ -5437,7 +5437,7 @@ PrepareStmt: PREPARE name prep_type_clause AS PreparableStmt
PrepareStmt *n = makeNode(PrepareStmt);
n->name = $2;
n->argtypes = $3;
n->query = (Query *) $5;
n->query = $5;
$$ = (Node *) n;
}
;

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.33 2007/03/07 13:35:02 alvherre Exp $
* $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.34 2007/03/13 00:33:41 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1248,13 +1248,6 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze,
vacstmt = makeNode(VacuumStmt);
/*
* Point QueryContext to the autovac memory context to fake out the
* PreventTransactionChain check inside vacuum(). Note that this is also
* why we palloc vacstmt instead of just using a local variable.
*/
QueryContext = CurrentMemoryContext;
/* Set up command parameters */
vacstmt->vacuum = dovacuum;
vacstmt->full = false;
@ -1267,7 +1260,7 @@ autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze,
/* Let pgstat know what we're doing */
autovac_report_activity(vacstmt, relid);
vacuum(vacstmt, list_make1_oid(relid));
vacuum(vacstmt, list_make1_oid(relid), true);
pfree(vacstmt);
MemoryContextSwitchTo(old_cxt);

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.117 2007/02/01 19:10:27 momjian Exp $
* $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.118 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -20,6 +20,7 @@
#include "catalog/pg_rewrite.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
#include "parser/analyze.h"
#include "parser/parse_expr.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteManip.h"
@ -177,15 +178,46 @@ InsertRule(char *rulname,
return rewriteObjectId;
}
/*
* DefineRule
* Execute a CREATE RULE command.
*/
void
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,

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.527 2007/03/03 19:32:54 neilc Exp $
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.528 2007/03/13 00:33:42 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@ -140,8 +140,9 @@ static bool ignore_till_sync = false;
* We keep it separate from the hashtable kept by commands/prepare.c
* in order to reduce overhead for short-lived queries.
*/
static CachedPlanSource *unnamed_stmt_psrc = NULL;
/* workspace for building a new unnamed statement in */
static MemoryContext unnamed_stmt_context = NULL;
static PreparedStatement *unnamed_stmt_pstmt = NULL;
static bool EchoQuery = false; /* default don't echo */
@ -173,6 +174,7 @@ static void finish_xact_command(void);
static bool IsTransactionExitStmt(Node *parsetree);
static bool IsTransactionExitStmtList(List *parseTrees);
static bool IsTransactionStmtList(List *parseTrees);
static void drop_unnamed_stmt(void);
static void SigHupHandler(SIGNAL_ARGS);
static void log_disconnections(int code, Datum arg);
@ -794,21 +796,13 @@ exec_simple_query(const char *query_string)
* statement and portal; this ensures we recover any storage used by prior
* unnamed operations.)
*/
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
drop_unnamed_stmt();
/*
* Switch to appropriate context for constructing parsetrees.
*/
oldcontext = MemoryContextSwitchTo(MessageContext);
QueryContext = CurrentMemoryContext;
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@ -906,7 +900,7 @@ exec_simple_query(const char *query_string)
query_string,
commandTag,
plantree_list,
MessageContext);
NULL);
/*
* Start the portal. No parameters here.
@ -950,6 +944,7 @@ exec_simple_query(const char *query_string)
*/
(void) PortalRun(portal,
FETCH_ALL,
true, /* top level */
receiver,
receiver,
completionTag);
@ -1009,8 +1004,6 @@ exec_simple_query(const char *query_string)
if (!parsetree_list)
NullCommand(dest);
QueryContext = NULL;
/*
* Emit duration logging if appropriate.
*/
@ -1049,10 +1042,10 @@ exec_parse_message(const char *query_string, /* string to execute */
{
MemoryContext oldcontext;
List *parsetree_list;
Node *raw_parse_tree;
const char *commandTag;
List *querytree_list,
*stmt_list,
*param_list;
*stmt_list;
bool is_named;
bool fully_planned;
bool save_log_statement_stats = log_statement_stats;
@ -1088,12 +1081,12 @@ exec_parse_message(const char *query_string, /* string to execute */
* We have two strategies depending on whether the prepared statement is
* named or not. For a named prepared statement, we do parsing in
* MessageContext and copy the finished trees into the prepared
* statement's private context; then the reset of MessageContext releases
* statement's plancache entry; then the reset of MessageContext releases
* temporary space used by parsing and planning. For an unnamed prepared
* statement, we assume the statement isn't going to hang around long, so
* getting rid of temp space quickly is probably not worth the costs of
* copying parse/plan trees. So in this case, we set up a special context
* for the unnamed statement, and do all the parsing work therein.
* copying parse/plan trees. So in this case, we create the plancache
* entry's context here, and do all the parsing work therein.
*/
is_named = (stmt_name[0] != '\0');
if (is_named)
@ -1104,16 +1097,10 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Unnamed prepared statement --- release any prior unnamed stmt */
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
/* create context for parsing/planning */
drop_unnamed_stmt();
/* Create context for parsing/planning */
unnamed_stmt_context =
AllocSetContextCreate(TopMemoryContext,
AllocSetContextCreate(CacheMemoryContext,
"unnamed prepared statement",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
@ -1121,8 +1108,6 @@ exec_parse_message(const char *query_string, /* string to execute */
oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
}
QueryContext = CurrentMemoryContext;
/*
* Do basic parsing of the query or queries (this should be safe even if
* we are in aborted transaction state!)
@ -1141,13 +1126,14 @@ exec_parse_message(const char *query_string, /* string to execute */
if (parsetree_list != NIL)
{
Node *parsetree = (Node *) linitial(parsetree_list);
int i;
raw_parse_tree = (Node *) linitial(parsetree_list);
/*
* Get the command name for possible use in status display.
*/
commandTag = CreateCommandTag(parsetree);
commandTag = CreateCommandTag(raw_parse_tree);
/*
* If we are in an aborted transaction, reject all commands except
@ -1158,7 +1144,7 @@ exec_parse_message(const char *query_string, /* string to execute */
* state, but not many...)
*/
if (IsAbortedTransactionBlockState() &&
!IsTransactionExitStmt(parsetree))
!IsTransactionExitStmt(raw_parse_tree))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@ -1168,20 +1154,22 @@ exec_parse_message(const char *query_string, /* string to execute */
* OK to analyze, rewrite, and plan this query. Note that the
* originally specified parameter set is not required to be complete,
* so we have to use parse_analyze_varparams().
*
* XXX must use copyObject here since parse analysis scribbles on
* its input, and we need the unmodified raw parse tree for possible
* replanning later.
*/
if (log_parser_stats)
ResetUsage();
querytree_list = parse_analyze_varparams(parsetree,
querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree),
query_string,
&paramTypes,
&numParams);
/*
* Check all parameter types got determined, and convert array
* representation to a list for storage.
* Check all parameter types got determined.
*/
param_list = NIL;
for (i = 0; i < numParams; i++)
{
Oid ptype = paramTypes[i];
@ -1191,7 +1179,6 @@ exec_parse_message(const char *query_string, /* string to execute */
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
errmsg("could not determine data type of parameter $%d",
i + 1)));
param_list = lappend_oid(param_list, ptype);
}
if (log_parser_stats)
@ -1217,9 +1204,9 @@ exec_parse_message(const char *query_string, /* string to execute */
else
{
/* Empty input string. This is legal. */
raw_parse_tree = NULL;
commandTag = NULL;
stmt_list = NIL;
param_list = NIL;
fully_planned = true;
}
@ -1232,35 +1219,33 @@ exec_parse_message(const char *query_string, /* string to execute */
if (is_named)
{
StorePreparedStatement(stmt_name,
raw_parse_tree,
query_string,
commandTag,
paramTypes,
numParams,
stmt_list,
param_list,
fully_planned,
false);
}
else
{
PreparedStatement *pstmt;
pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
/* query_string needs to be copied into unnamed_stmt_context */
pstmt->query_string = pstrdup(query_string);
/* the rest is there already */
pstmt->commandTag = commandTag;
pstmt->stmt_list = stmt_list;
pstmt->argtype_list = param_list;
pstmt->fully_planned = fully_planned;
pstmt->from_sql = false;
pstmt->context = unnamed_stmt_context;
/* Now the unnamed statement is complete and valid */
unnamed_stmt_pstmt = pstmt;
unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree,
pstrdup(query_string),
commandTag,
paramTypes,
numParams,
stmt_list,
fully_planned,
true,
unnamed_stmt_context);
/* context now belongs to the plancache entry */
unnamed_stmt_context = NULL;
}
MemoryContextSwitchTo(oldcontext);
QueryContext = NULL;
/*
* We do NOT close the open transaction command here; that only happens
* when the client sends Sync. Instead, do CommandCounterIncrement just
@ -1315,12 +1300,11 @@ exec_bind_message(StringInfo input_message)
int numParams;
int numRFormats;
int16 *rformats = NULL;
PreparedStatement *pstmt;
CachedPlanSource *psrc;
CachedPlan *cplan;
Portal portal;
ParamListInfo params;
List *query_list;
List *plan_list;
MemoryContext qContext;
bool save_log_statement_stats = log_statement_stats;
char msec_str[32];
@ -1335,12 +1319,17 @@ exec_bind_message(StringInfo input_message)
/* Find prepared statement */
if (stmt_name[0] != '\0')
{
PreparedStatement *pstmt;
pstmt = FetchPreparedStatement(stmt_name, true);
psrc = pstmt->plansource;
}
else
{
/* special-case the unnamed statement */
pstmt = unnamed_stmt_pstmt;
if (!pstmt)
psrc = unnamed_stmt_psrc;
if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
@ -1349,7 +1338,7 @@ exec_bind_message(StringInfo input_message)
/*
* Report query to various monitoring facilities.
*/
debug_query_string = pstmt->query_string ? pstmt->query_string : "<BIND>";
debug_query_string = psrc->query_string ? psrc->query_string : "<BIND>";
pgstat_report_activity(debug_query_string);
@ -1388,11 +1377,11 @@ exec_bind_message(StringInfo input_message)
errmsg("bind message has %d parameter formats but %d parameters",
numPFormats, numParams)));
if (numParams != list_length(pstmt->argtype_list))
if (numParams != psrc->num_params)
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("bind message supplies %d parameters, but prepared statement \"%s\" requires %d",
numParams, stmt_name, list_length(pstmt->argtype_list))));
numParams, stmt_name, psrc->num_params)));
/*
* If we are in aborted transaction state, the only portals we can
@ -1403,7 +1392,7 @@ exec_bind_message(StringInfo input_message)
* functions.
*/
if (IsAbortedTransactionBlockState() &&
(!IsTransactionExitStmtList(pstmt->stmt_list) ||
(!IsTransactionExitStmt(psrc->raw_parse_tree) ||
numParams != 0))
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
@ -1424,7 +1413,6 @@ exec_bind_message(StringInfo input_message)
*/
if (numParams > 0)
{
ListCell *l;
MemoryContext oldContext;
int paramno;
@ -1435,10 +1423,9 @@ exec_bind_message(StringInfo input_message)
(numParams - 1) *sizeof(ParamExternData));
params->numParams = numParams;
paramno = 0;
foreach(l, pstmt->argtype_list)
for (paramno = 0; paramno < numParams; paramno++)
{
Oid ptype = lfirst_oid(l);
Oid ptype = psrc->param_types[paramno];
int32 plength;
Datum pval;
bool isNull;
@ -1554,8 +1541,6 @@ exec_bind_message(StringInfo input_message)
*/
params->params[paramno].pflags = PARAM_FLAG_CONST;
params->params[paramno].ptype = ptype;
paramno++;
}
MemoryContextSwitchTo(oldContext);
@ -1576,46 +1561,62 @@ exec_bind_message(StringInfo input_message)
pq_getmsgend(input_message);
/*
* If we didn't plan the query before, do it now. This allows the planner
* to make use of the concrete parameter values we now have. Because we
* use PARAM_FLAG_CONST, the plan is good only for this set of param
* values, and so we generate the plan in the portal's own memory context
* where it will be thrown away after use. As in exec_parse_message, we
* make no attempt to recover planner temporary memory until the end of
* the operation.
*
* XXX because the planner has a bad habit of scribbling on its input, we
* have to make a copy of the parse trees, just in case someone binds and
* executes an unnamed statement multiple times; this also means that the
* portal's queryContext becomes its own heap context rather than the
* prepared statement's context. FIXME someday
*/
if (pstmt->fully_planned)
if (psrc->fully_planned)
{
plan_list = pstmt->stmt_list;
qContext = pstmt->context;
/*
* Revalidate the cached plan; this may result in replanning. Any
* cruft will be generated in MessageContext. The plan refcount
* will be assigned to the Portal, so it will be released at portal
* destruction.
*/
cplan = RevalidateCachedPlan(psrc, false);
plan_list = cplan->stmt_list;
}
else
{
MemoryContext oldContext;
List *query_list;
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 : "<unnamed>",
*portal_name ? "/" : "",
*portal_name ? portal_name : "",
pstmt->query_string ? pstmt->query_string : "<source not stored>"),
psrc->query_string ? psrc->query_string : "<source not stored>"),
errhidestmt(true),
errdetail_params(params)));
break;
@ -1809,6 +1810,7 @@ exec_execute_message(const char *portal_name, long max_rows)
completed = PortalRun(portal,
max_rows,
true, /* top level */
receiver,
receiver,
completionTag);
@ -1981,9 +1983,9 @@ errdetail_execute(List *raw_parsetree_list)
PreparedStatement *pstmt;
pstmt = FetchPreparedStatement(stmt->name, false);
if (pstmt && pstmt->query_string)
if (pstmt && pstmt->plansource->query_string)
{
errdetail("prepare: %s", pstmt->query_string);
errdetail("prepare: %s", pstmt->plansource->query_string);
return 0;
}
}
@ -2064,10 +2066,9 @@ errdetail_params(ParamListInfo params)
static void
exec_describe_statement_message(const char *stmt_name)
{
PreparedStatement *pstmt;
TupleDesc tupdesc;
ListCell *l;
CachedPlanSource *psrc;
StringInfoData buf;
int i;
/*
* Start up a transaction command. (Note that this will normally change
@ -2080,28 +2081,37 @@ exec_describe_statement_message(const char *stmt_name)
/* Find prepared statement */
if (stmt_name[0] != '\0')
{
PreparedStatement *pstmt;
pstmt = FetchPreparedStatement(stmt_name, true);
psrc = pstmt->plansource;
}
else
{
/* special-case the unnamed statement */
pstmt = unnamed_stmt_pstmt;
if (!pstmt)
psrc = unnamed_stmt_psrc;
if (!psrc)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
errmsg("unnamed prepared statement does not exist")));
}
/* Prepared statements shouldn't have changeable result descs */
Assert(psrc->fixed_result);
/*
* If we are in aborted transaction state, we can't safely create a result
* tupledesc, because that needs catalog accesses. Hence, refuse to
* Describe statements that return data. (We shouldn't just refuse all
* Describes, since that might break the ability of some clients to issue
* COMMIT or ROLLBACK commands, if they use code that blindly Describes
* whatever it does.) We can Describe parameters without doing anything
* dangerous, so we don't restrict that.
* If we are in aborted transaction state, we can't run
* SendRowDescriptionMessage(), because that needs catalog accesses.
* (We can't do RevalidateCachedPlan, either, but that's a lesser problem.)
* Hence, refuse to Describe statements that return data. (We shouldn't
* just refuse all Describes, since that might break the ability of some
* clients to issue COMMIT or ROLLBACK commands, if they use code that
* blindly Describes whatever it does.) We can Describe parameters
* without doing anything dangerous, so we don't restrict that.
*/
if (IsAbortedTransactionBlockState() &&
PreparedStatementReturnsTuples(pstmt))
psrc->resultDesc)
ereport(ERROR,
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
errmsg("current transaction is aborted, "
@ -2114,11 +2124,11 @@ exec_describe_statement_message(const char *stmt_name)
* First describe the parameters...
*/
pq_beginmessage(&buf, 't'); /* parameter description message type */
pq_sendint(&buf, list_length(pstmt->argtype_list), 2);
pq_sendint(&buf, psrc->num_params, 2);
foreach(l, pstmt->argtype_list)
for (i = 0; i < psrc->num_params; i++)
{
Oid ptype = lfirst_oid(l);
Oid ptype = psrc->param_types[i];
pq_sendint(&buf, (int) ptype, 4);
}
@ -2127,11 +2137,21 @@ exec_describe_statement_message(const char *stmt_name)
/*
* Next send RowDescription or NoData to describe the result...
*/
tupdesc = FetchPreparedStatementResultDesc(pstmt);
if (tupdesc)
SendRowDescriptionMessage(tupdesc,
FetchPreparedStatementTargetList(pstmt),
NULL);
if (psrc->resultDesc)
{
CachedPlan *cplan;
List *tlist;
/* Make sure the plan is up to date */
cplan = RevalidateCachedPlan(psrc, true);
/* Get the primary statement and find out what it returns */
tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
SendRowDescriptionMessage(psrc->resultDesc, tlist, NULL);
ReleaseCachedPlan(cplan, true);
}
else
pq_putemptymessage('n'); /* NoData */
@ -2308,6 +2328,24 @@ IsTransactionStmtList(List *parseTrees)
return false;
}
/* Release any existing unnamed prepared statement */
static void
drop_unnamed_stmt(void)
{
/* Release any completed unnamed statement */
if (unnamed_stmt_psrc)
DropCachedPlan(unnamed_stmt_psrc);
unnamed_stmt_psrc = NULL;
/*
* If we failed while trying to build a prior unnamed statement, we may
* have a memory context that wasn't assigned to a completed plancache
* entry. If so, drop it to avoid a permanent memory leak.
*/
if (unnamed_stmt_context)
MemoryContextDelete(unnamed_stmt_context);
unnamed_stmt_context = NULL;
}
/* --------------------------------
* signal handler routines used in PostgresMain()
@ -3313,7 +3351,6 @@ PostgresMain(int argc, char *argv[], const char *username)
*/
MemoryContextSwitchTo(TopMemoryContext);
FlushErrorState();
QueryContext = NULL;
/*
* If we were handling an extended-query-protocol message, initiate
@ -3558,13 +3595,7 @@ PostgresMain(int argc, char *argv[], const char *username)
else
{
/* special-case the unnamed statement */
unnamed_stmt_pstmt = NULL;
if (unnamed_stmt_context)
{
DropDependentPortals(unnamed_stmt_context);
MemoryContextDelete(unnamed_stmt_context);
}
unnamed_stmt_context = NULL;
drop_unnamed_stmt();
}
break;
case 'P':

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.114 2007/02/20 17:32:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.115 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -36,14 +36,14 @@ static void ProcessQuery(PlannedStmt *plan,
ParamListInfo params,
DestReceiver *dest,
char *completionTag);
static void FillPortalStore(Portal portal);
static void FillPortalStore(Portal portal, bool isTopLevel);
static uint32 RunFromStore(Portal portal, ScanDirection direction, long count,
DestReceiver *dest);
static long PortalRunSelect(Portal portal, bool forward, long count,
DestReceiver *dest);
static void PortalRunUtility(Portal portal, Node *utilityStmt,
static void PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
DestReceiver *dest, char *completionTag);
static void PortalRunMulti(Portal portal,
static void PortalRunMulti(Portal portal, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag);
static long DoPortalRunFetch(Portal portal,
@ -148,8 +148,7 @@ ProcessQuery(PlannedStmt *plan,
{
QueryDesc *queryDesc;
ereport(DEBUG3,
(errmsg_internal("ProcessQuery")));
elog(DEBUG3, "ProcessQuery");
/*
* Must always set snapshot for plannable queries. Note we assume that
@ -232,8 +231,7 @@ ProcessQuery(PlannedStmt *plan,
* Select portal execution strategy given the intended statement list.
*
* The list elements can be Querys, PlannedStmts, or utility statements.
* That's more general than portals need, but we use this for prepared
* statements as well.
* That's more general than portals need, but plancache.c uses this too.
*
* See the comments in portal.h.
*/
@ -358,8 +356,7 @@ FetchPortalTargetList(Portal portal)
* Returns NIL if the statement doesn't have a determinable targetlist.
*
* This can be applied to a Query, a PlannedStmt, or a utility statement.
* That's more general than portals need, but we use this for prepared
* statements as well.
* That's more general than portals need, but plancache.c uses this too.
*
* Note: do not modify the result.
*
@ -452,11 +449,10 @@ PortalStart(Portal portal, ParamListInfo params, Snapshot snapshot)
int eflags;
AssertArg(PortalIsValid(portal));
AssertState(portal->queryContext != NULL); /* query defined? */
AssertState(portal->status == PORTAL_NEW); /* else extra PortalStart */
AssertState(portal->status == PORTAL_DEFINED);
/*
* Set up global portal context pointers. (Should we set QueryContext?)
* Set up global portal context pointers.
*/
saveActivePortal = ActivePortal;
saveActiveSnapshot = ActiveSnapshot;
@ -683,6 +679,9 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
* interpreted as "all rows". Note that count is ignored in multi-query
* situations, where we always run the portal to completion.
*
* isTopLevel: true if query is being executed at backend "top level"
* (that is, directly from a client command message)
*
* dest: where to send output of primary (canSetTag) query
*
* altdest: where to send output of non-primary queries
@ -695,7 +694,7 @@ PortalSetResultFormat(Portal portal, int nFormats, int16 *formats)
* suspended due to exhaustion of the count parameter.
*/
bool
PortalRun(Portal portal, long count,
PortalRun(Portal portal, long count, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag)
{
@ -706,7 +705,6 @@ PortalRun(Portal portal, long count,
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
MemoryContext saveMemoryContext;
AssertArg(PortalIsValid(portal));
@ -717,8 +715,7 @@ PortalRun(Portal portal, long count,
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
{
ereport(DEBUG3,
(errmsg_internal("PortalRun")));
elog(DEBUG3, "PortalRun");
/* PORTAL_MULTI_QUERY logs its own stats per query */
ResetUsage();
}
@ -752,7 +749,6 @@ PortalRun(Portal portal, long count,
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
saveMemoryContext = CurrentMemoryContext;
PG_TRY();
{
@ -760,7 +756,6 @@ PortalRun(Portal portal, long count,
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
MemoryContextSwitchTo(PortalContext);
@ -790,7 +785,7 @@ PortalRun(Portal portal, long count,
* results in the portal's tuplestore.
*/
if (!portal->holdStore)
FillPortalStore(portal);
FillPortalStore(portal, isTopLevel);
/*
* Now fetch desired portion of results.
@ -811,7 +806,8 @@ PortalRun(Portal portal, long count,
break;
case PORTAL_MULTI_QUERY:
PortalRunMulti(portal, dest, altdest, completionTag);
PortalRunMulti(portal, isTopLevel,
dest, altdest, completionTag);
/* Prevent portal's commands from being re-executed */
portal->status = PORTAL_DONE;
@ -844,7 +840,6 @@ PortalRun(Portal portal, long count,
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
PG_RE_THROW();
}
@ -861,7 +856,6 @@ PortalRun(Portal portal, long count,
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
ShowUsage("EXECUTOR STATISTICS");
@ -1025,7 +1019,7 @@ PortalRunSelect(Portal portal,
* This is used for PORTAL_ONE_RETURNING and PORTAL_UTIL_SELECT cases only.
*/
static void
FillPortalStore(Portal portal)
FillPortalStore(Portal portal, bool isTopLevel)
{
DestReceiver *treceiver;
char completionTag[COMPLETION_TAG_BUFSIZE];
@ -1044,12 +1038,13 @@ FillPortalStore(Portal portal)
* MULTI_QUERY case, but send the primary query's output to the
* tuplestore. Auxiliary query outputs are discarded.
*/
PortalRunMulti(portal, treceiver, None_Receiver, completionTag);
PortalRunMulti(portal, isTopLevel,
treceiver, None_Receiver, completionTag);
break;
case PORTAL_UTIL_SELECT:
PortalRunUtility(portal, (Node *) linitial(portal->stmts),
treceiver, completionTag);
isTopLevel, treceiver, completionTag);
break;
default:
@ -1137,11 +1132,10 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
* Execute a utility statement inside a portal.
*/
static void
PortalRunUtility(Portal portal, Node *utilityStmt,
PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
DestReceiver *dest, char *completionTag)
{
ereport(DEBUG3,
(errmsg_internal("ProcessUtility")));
elog(DEBUG3, "ProcessUtility");
/*
* Set snapshot if utility stmt needs one. Most reliable way to do this
@ -1173,7 +1167,12 @@ PortalRunUtility(Portal portal, Node *utilityStmt,
else
ActiveSnapshot = NULL;
ProcessUtility(utilityStmt, portal->portalParams, dest, completionTag);
ProcessUtility(utilityStmt,
portal->sourceText,
portal->portalParams,
isTopLevel,
dest,
completionTag);
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
@ -1189,7 +1188,7 @@ PortalRunUtility(Portal portal, Node *utilityStmt,
* or non-SELECT-like queries)
*/
static void
PortalRunMulti(Portal portal,
PortalRunMulti(Portal portal, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag)
{
@ -1260,9 +1259,9 @@ PortalRunMulti(Portal portal,
* portal.
*/
if (list_length(portal->stmts) == 1)
PortalRunUtility(portal, stmt, dest, completionTag);
PortalRunUtility(portal, stmt, isTopLevel, dest, completionTag);
else
PortalRunUtility(portal, stmt, altdest, NULL);
PortalRunUtility(portal, stmt, isTopLevel, altdest, NULL);
}
/*
@ -1305,6 +1304,8 @@ PortalRunMulti(Portal portal,
* PortalRunFetch
* Variant form of PortalRun that supports SQL FETCH directions.
*
* Note: we presently assume that no callers of this want isTopLevel = true.
*
* Returns number of rows processed (suitable for use in result tag)
*/
long
@ -1318,7 +1319,6 @@ PortalRunFetch(Portal portal,
Snapshot saveActiveSnapshot;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveQueryContext;
MemoryContext oldContext;
AssertArg(PortalIsValid(portal));
@ -1339,14 +1339,12 @@ PortalRunFetch(Portal portal,
saveActiveSnapshot = ActiveSnapshot;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveQueryContext = QueryContext;
PG_TRY();
{
ActivePortal = portal;
ActiveSnapshot = NULL; /* will be set later */
CurrentResourceOwner = portal->resowner;
PortalContext = PortalGetHeapMemory(portal);
QueryContext = portal->queryContext;
oldContext = MemoryContextSwitchTo(PortalContext);
@ -1364,7 +1362,7 @@ PortalRunFetch(Portal portal,
* results in the portal's tuplestore.
*/
if (!portal->holdStore)
FillPortalStore(portal);
FillPortalStore(portal, false /* isTopLevel */);
/*
* Now fetch desired portion of results.
@ -1388,7 +1386,6 @@ PortalRunFetch(Portal portal,
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
PG_RE_THROW();
}
@ -1403,7 +1400,6 @@ PortalRunFetch(Portal portal,
ActiveSnapshot = saveActiveSnapshot;
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
QueryContext = saveQueryContext;
return result;
}

View File

@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.273 2007/02/20 17:32:16 tgl Exp $
* $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.274 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -44,6 +44,7 @@
#include "commands/vacuum.h"
#include "commands/view.h"
#include "miscadmin.h"
#include "parser/analyze.h"
#include "postmaster/bgwriter.h"
#include "rewrite/rewriteDefine.h"
#include "rewrite/rewriteRemove.h"
@ -368,7 +369,9 @@ check_xact_readonly(Node *parsetree)
* general utility function invoker
*
* parsetree: the parse tree for the utility statement
* queryString: original source text of command (NULL if not available)
* params: parameters to use during execution
* isTopLevel: true if executing a "top level" (interactively issued) command
* dest: where to send results
* completionTag: points to a buffer of size COMPLETION_TAG_BUFSIZE
* in which to store a command completion status string.
@ -379,7 +382,9 @@ check_xact_readonly(Node *parsetree)
*/
void
ProcessUtility(Node *parsetree,
const char *queryString,
ParamListInfo params,
bool isTopLevel,
DestReceiver *dest,
char *completionTag)
{
@ -444,12 +449,12 @@ ProcessUtility(Node *parsetree,
break;
case TRANS_STMT_COMMIT_PREPARED:
PreventTransactionChain(stmt, "COMMIT PREPARED");
PreventTransactionChain(isTopLevel, "COMMIT PREPARED");
FinishPreparedTransaction(stmt->gid, true);
break;
case TRANS_STMT_ROLLBACK_PREPARED:
PreventTransactionChain(stmt, "ROLLBACK PREPARED");
PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED");
FinishPreparedTransaction(stmt->gid, false);
break;
@ -462,7 +467,7 @@ ProcessUtility(Node *parsetree,
ListCell *cell;
char *name = NULL;
RequireTransactionChain((void *) stmt, "SAVEPOINT");
RequireTransactionChain(isTopLevel, "SAVEPOINT");
foreach(cell, stmt->options)
{
@ -479,12 +484,12 @@ ProcessUtility(Node *parsetree,
break;
case TRANS_STMT_RELEASE:
RequireTransactionChain((void *) stmt, "RELEASE SAVEPOINT");
RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT");
ReleaseSavepoint(stmt->options);
break;
case TRANS_STMT_ROLLBACK_TO:
RequireTransactionChain((void *) stmt, "ROLLBACK TO SAVEPOINT");
RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT");
RollbackToSavepoint(stmt->options);
/*
@ -500,7 +505,8 @@ ProcessUtility(Node *parsetree,
* Portal (cursor) manipulation
*/
case T_DeclareCursorStmt:
PerformCursorOpen((DeclareCursorStmt *) parsetree, params);
PerformCursorOpen((DeclareCursorStmt *) parsetree, params,
queryString, isTopLevel);
break;
case T_ClosePortalStmt:
@ -520,7 +526,8 @@ ProcessUtility(Node *parsetree,
* relation and attribute manipulation
*/
case T_CreateSchemaStmt:
CreateSchemaCommand((CreateSchemaStmt *) parsetree);
CreateSchemaCommand((CreateSchemaStmt *) parsetree,
queryString);
break;
case T_CreateStmt:
@ -540,10 +547,12 @@ ProcessUtility(Node *parsetree,
break;
case T_CreateTableSpaceStmt:
PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt *) parsetree);
break;
case T_DropTableSpaceStmt:
PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt *) parsetree);
break;
@ -640,8 +649,9 @@ ProcessUtility(Node *parsetree,
case T_CopyStmt:
{
uint64 processed = DoCopy((CopyStmt *) parsetree);
uint64 processed;
processed = DoCopy((CopyStmt *) parsetree, queryString);
if (completionTag)
snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
"COPY " UINT64_FORMAT, processed);
@ -649,11 +659,11 @@ ProcessUtility(Node *parsetree,
break;
case T_PrepareStmt:
PrepareQuery((PrepareStmt *) parsetree);
PrepareQuery((PrepareStmt *) parsetree, queryString);
break;
case T_ExecuteStmt:
ExecuteQuery((ExecuteStmt *) parsetree, params,
ExecuteQuery((ExecuteStmt *) parsetree, queryString, params,
dest, completionTag);
break;
@ -769,12 +779,8 @@ ProcessUtility(Node *parsetree,
}
break;
case T_ViewStmt: /* CREATE VIEW */
{
ViewStmt *stmt = (ViewStmt *) parsetree;
DefineView(stmt->view, stmt->query, stmt->replace);
}
case T_ViewStmt: /* CREATE VIEW */
DefineView((ViewStmt *) parsetree, queryString);
break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */
@ -790,10 +796,15 @@ ProcessUtility(Node *parsetree,
IndexStmt *stmt = (IndexStmt *) parsetree;
if (stmt->concurrent)
PreventTransactionChain(stmt, "CREATE INDEX CONCURRENTLY");
PreventTransactionChain(isTopLevel,
"CREATE INDEX CONCURRENTLY");
CheckRelationOwnership(stmt->relation, true);
/* Run parse analysis ... */
stmt = analyzeIndexStmt(stmt, queryString);
/* ... and do it */
DefineIndex(stmt->relation, /* relation */
stmt->idxname, /* index name */
InvalidOid, /* no predefined OID */
@ -801,7 +812,6 @@ ProcessUtility(Node *parsetree,
stmt->tableSpace,
stmt->indexParams, /* parameters */
(Expr *) stmt->whereClause,
stmt->rangetable,
stmt->options,
stmt->unique,
stmt->primary,
@ -815,7 +825,7 @@ ProcessUtility(Node *parsetree,
break;
case T_RuleStmt: /* CREATE RULE */
DefineQueryRewrite((RuleStmt *) parsetree);
DefineRule((RuleStmt *) parsetree, queryString);
break;
case T_CreateSeqStmt:
@ -850,6 +860,7 @@ ProcessUtility(Node *parsetree,
break;
case T_CreatedbStmt:
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
createdb((CreatedbStmt *) parsetree);
break;
@ -865,6 +876,7 @@ ProcessUtility(Node *parsetree,
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
PreventTransactionChain(isTopLevel, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok);
}
break;
@ -905,15 +917,15 @@ ProcessUtility(Node *parsetree,
break;
case T_ClusterStmt:
cluster((ClusterStmt *) parsetree);
cluster((ClusterStmt *) parsetree, isTopLevel);
break;
case T_VacuumStmt:
vacuum((VacuumStmt *) parsetree, NIL);
vacuum((VacuumStmt *) parsetree, NIL, isTopLevel);
break;
case T_ExplainStmt:
ExplainQuery((ExplainStmt *) parsetree, params, dest);
ExplainQuery((ExplainStmt *) parsetree, queryString, params, dest);
break;
case T_VariableSetStmt:
@ -1079,6 +1091,14 @@ ProcessUtility(Node *parsetree,
ReindexTable(stmt->relation);
break;
case OBJECT_DATABASE:
/*
* This cannot run inside a user transaction block;
* if we were inside a transaction, then its commit-
* and start-transaction-command calls would not have
* the intended effect!
*/
PreventTransactionChain(isTopLevel,
"REINDEX DATABASE");
ReindexDatabase(stmt->name,
stmt->do_system, stmt->do_user);
break;
@ -1166,16 +1186,8 @@ UtilityReturnsTuples(Node *parsetree)
entry = FetchPreparedStatement(stmt->name, false);
if (!entry)
return false; /* not our business to raise error */
switch (ChoosePortalStrategy(entry->stmt_list))
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_RETURNING:
case PORTAL_UTIL_SELECT:
return true;
case PORTAL_MULTI_QUERY:
/* will not return tuples */
break;
}
if (entry->plansource->resultDesc)
return true;
return false;
}
@ -2134,7 +2146,7 @@ GetCommandLogLevel(Node *parsetree)
/* Look through an EXPLAIN ANALYZE to the contained stmt */
if (stmt->analyze)
return GetCommandLogLevel((Node *) stmt->query);
return GetCommandLogLevel(stmt->query);
/* Plain EXPLAIN isn't so interesting */
lev = LOGSTMT_ALL;
}
@ -2245,30 +2257,21 @@ GetCommandLogLevel(Node *parsetree)
PrepareStmt *stmt = (PrepareStmt *) parsetree;
/* Look through a PREPARE to the contained stmt */
return GetCommandLogLevel((Node *) stmt->query);
lev = GetCommandLogLevel(stmt->query);
}
break;
case T_ExecuteStmt:
{
ExecuteStmt *stmt = (ExecuteStmt *) parsetree;
PreparedStatement *pstmt;
ListCell *l;
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;

View File

@ -4,7 +4,7 @@
# Makefile for utils/cache
#
# IDENTIFICATION
# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.20 2007/01/20 17:16:13 petere Exp $
# $PostgreSQL: pgsql/src/backend/utils/cache/Makefile,v 1.21 2007/03/13 00:33:42 tgl Exp $
#
#-------------------------------------------------------------------------
@ -12,7 +12,8 @@ subdir = src/backend/utils/cache
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = catcache.o inval.o relcache.o syscache.o lsyscache.o typcache.o
OBJS = catcache.o inval.o plancache.o relcache.o \
syscache.o lsyscache.o typcache.o
all: SUBSYS.o

862
src/backend/utils/cache/plancache.c vendored Normal file
View File

@ -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;
}

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.174 2007/02/15 23:23:23 alvherre Exp $
* $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.175 2007/03/13 00:33:42 tgl Exp $
*
*
*-------------------------------------------------------------------------
@ -28,6 +28,7 @@
#include "libpq/hba.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "postmaster/postmaster.h"
#include "storage/backendid.h"
@ -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();

View File

@ -1,4 +1,4 @@
$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.9 2006/09/07 22:52:01 tgl Exp $
$PostgreSQL: pgsql/src/backend/utils/mmgr/README,v 1.10 2007/03/13 00:33:42 tgl Exp $
Notes about memory allocation redesign
--------------------------------------
@ -201,15 +201,6 @@ have dangling pointers leading to a crash at top-level commit. An example of
data kept here is pending NOTIFY messages, which are sent at top-level commit,
but only if the generating subtransaction did not abort.
QueryContext --- this is not actually a separate context, but a global
variable pointing to the context that holds the current command's parse tree.
(In simple-Query mode this points to MessageContext; when executing a
prepared statement it will point to the prepared statement's private context.
Note that the plan tree may or may not be in this same context.)
Generally it is not appropriate for any code to use QueryContext as an
allocation target --- from the point of view of any code that would be
referencing the QueryContext variable, it's a read-only context.
PortalContext --- this is not actually a separate context either, but a
global variable pointing to the per-portal context of the currently active
execution portal. This can be used if it's necessary to allocate storage
@ -229,9 +220,7 @@ Contexts for prepared statements and portals
A prepared-statement object has an associated private context, in which
the parse and plan trees for its query are stored. Because these trees
are read-only to the executor, the prepared statement can be re-used many
times without further copying of these trees. QueryContext points at this
private context while executing any portal built from the prepared
statement.
times without further copying of these trees.
An execution-portal object has a private context that is referenced by
PortalContext when the portal is active. In the case of a portal created

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.59 2007/01/05 22:19:47 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/mmgr/mcxt.c,v 1.60 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -46,8 +46,7 @@ MemoryContext MessageContext = NULL;
MemoryContext TopTransactionContext = NULL;
MemoryContext CurTransactionContext = NULL;
/* These two are transient links to contexts owned by other objects: */
MemoryContext QueryContext = NULL;
/* This is a transient link to the active portal's memory context: */
MemoryContext PortalContext = NULL;

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.99 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.100 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -149,9 +149,9 @@ GetPortalByName(const char *name)
* cases should occur in present usages of this function.
*
* Copes if given a list of Querys --- can't happen in a portal, but this
* code also supports prepared statements, which need both cases.
* code also supports plancache.c, which needs both cases.
*
* Note: the reason this is just handed a List is so that prepared statements
* Note: the reason this is just handed a List is so that plancache.c
* can share the code. For use with a portal, use PortalGetPrimaryStmt
* rather than calling this directly.
*/
@ -275,9 +275,17 @@ CreateNewPortal(void)
*
* Notes: commandTag shall be NULL if and only if the original query string
* (before rewriting) was an empty string. Also, the passed commandTag must
* be a pointer to a constant string, since it is not copied. The caller is
* responsible for ensuring that the passed prepStmtName (if any), sourceText
* (if any), and plan trees have adequate lifetime.
* be a pointer to a constant string, since it is not copied. However,
* prepStmtName and sourceText, if provided, are copied into the portal's
* heap context for safekeeping.
*
* If cplan is provided, then it is a cached plan containing the stmts,
* and the caller must have done RevalidateCachedPlan(), causing a refcount
* increment. The refcount will be released when the portal is destroyed.
*
* If cplan is NULL, then it is the caller's responsibility to ensure that
* the passed plan trees have adequate lifetime. Typically this is done by
* copying them into the portal's heap context.
*/
void
PortalDefineQuery(Portal portal,
@ -285,18 +293,35 @@ PortalDefineQuery(Portal portal,
const char *sourceText,
const char *commandTag,
List *stmts,
MemoryContext queryContext)
CachedPlan *cplan)
{
AssertArg(PortalIsValid(portal));
AssertState(portal->queryContext == NULL); /* else defined already */
AssertState(portal->status == PORTAL_NEW);
Assert(commandTag != NULL || stmts == NIL);
portal->prepStmtName = prepStmtName;
portal->sourceText = sourceText;
portal->prepStmtName = prepStmtName ?
MemoryContextStrdup(PortalGetHeapMemory(portal), prepStmtName) : NULL;
portal->sourceText = sourceText ?
MemoryContextStrdup(PortalGetHeapMemory(portal), sourceText) : NULL;
portal->commandTag = commandTag;
portal->stmts = stmts;
portal->queryContext = queryContext;
portal->cplan = cplan;
portal->status = PORTAL_DEFINED;
}
/*
* PortalReleaseCachedPlan
* Release a portal's reference to its cached plan, if any.
*/
static void
PortalReleaseCachedPlan(Portal portal)
{
if (portal->cplan)
{
ReleaseCachedPlan(portal->cplan, false);
portal->cplan = NULL;
}
}
/*
@ -356,6 +381,10 @@ PortalDrop(Portal portal, bool isTopCommit)
if (PointerIsValid(portal->cleanup))
(*portal->cleanup) (portal);
/* drop cached plan reference, if any */
if (portal->cplan)
PortalReleaseCachedPlan(portal);
/*
* Release any resources still attached to the portal. There are several
* cases being covered here:
@ -423,29 +452,6 @@ PortalDrop(Portal portal, bool isTopCommit)
pfree(portal);
}
/*
* DropDependentPortals
* Drop any portals using the specified context as queryContext.
*
* This is normally used to make sure we can safely drop a prepared statement.
*/
void
DropDependentPortals(MemoryContext queryContext)
{
HASH_SEQ_STATUS status;
PortalHashEnt *hentry;
hash_seq_init(&status, PortalHashTable);
while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
{
Portal portal = hentry->portal;
if (portal->queryContext == queryContext)
PortalDrop(portal, false);
}
}
/*
* Pre-commit processing for portals.
@ -485,6 +491,10 @@ CommitHoldablePortals(void)
PortalCreateHoldStore(portal);
PersistHoldablePortal(portal);
/* drop cached plan reference, if any */
if (portal->cplan)
PortalReleaseCachedPlan(portal);
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; the portal will no longer
@ -630,6 +640,10 @@ AtAbort_Portals(void)
portal->cleanup = NULL;
}
/* drop cached plan reference, if any */
if (portal->cplan)
PortalReleaseCachedPlan(portal);
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; they will be gone before we run
@ -769,6 +783,10 @@ AtSubAbort_Portals(SubTransactionId mySubid,
portal->cleanup = NULL;
}
/* drop cached plan reference, if any */
if (portal->cplan)
PortalReleaseCachedPlan(portal);
/*
* Any resources belonging to the portal will be released in the
* upcoming transaction-wide cleanup; they will be gone before we

View File

@ -1,4 +1,4 @@
$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.4 2006/06/16 18:42:23 tgl Exp $
$PostgreSQL: pgsql/src/backend/utils/resowner/README,v 1.5 2007/03/13 00:33:42 tgl Exp $
Notes about resource owners
---------------------------
@ -60,12 +60,13 @@ subtransaction or portal. Therefore, the "release" operation on a child
ResourceOwner transfers lock ownership to the parent instead of actually
releasing the lock, if isCommit is true.
Currently, ResourceOwners contain direct support for recording ownership
of buffer pins, lmgr locks, and catcache, relcache, and tupdesc references.
Other objects can be associated with a ResourceOwner by recording the address
of the owning ResourceOwner in such an object. There is an API for other
modules to get control during ResourceOwner release, so that they can scan
their own data structures to find the objects that need to be deleted.
Currently, ResourceOwners contain direct support for recording ownership of
buffer pins, lmgr locks, and catcache, relcache, plancache, and tupdesc
references. Other objects can be associated with a ResourceOwner by recording
the address of the owning ResourceOwner in such an object. There is an API
for other modules to get control during ResourceOwner release, so that they
can scan their own data structures to find the objects that need to be
deleted.
Whenever we are inside a transaction, the global variable
CurrentResourceOwner shows which resource owner should be assigned

View File

@ -14,7 +14,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.23 2007/01/05 22:19:47 momjian Exp $
* $PostgreSQL: pgsql/src/backend/utils/resowner/resowner.c,v 1.24 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -56,6 +56,11 @@ typedef struct ResourceOwnerData
Relation *relrefs; /* dynamically allocated array */
int maxrelrefs; /* currently allocated array size */
/* We have built-in support for remembering plancache references */
int nplanrefs; /* number of owned plancache pins */
CachedPlan **planrefs; /* dynamically allocated array */
int maxplanrefs; /* currently allocated array size */
/* We have built-in support for remembering tupdesc references */
int ntupdescs; /* number of owned tupdesc references */
TupleDesc *tupdescs; /* dynamically allocated array */
@ -90,6 +95,7 @@ static void ResourceOwnerReleaseInternal(ResourceOwner owner,
bool isCommit,
bool isTopLevel);
static void PrintRelCacheLeakWarning(Relation rel);
static void PrintPlanCacheLeakWarning(CachedPlan *plan);
static void PrintTupleDescLeakWarning(TupleDesc tupdesc);
@ -280,6 +286,13 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
}
/* Ditto for plancache references */
while (owner->nplanrefs > 0)
{
if (isCommit)
PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
}
/* Ditto for tupdesc references */
while (owner->ntupdescs > 0)
{
@ -316,6 +329,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner->ncatrefs == 0);
Assert(owner->ncatlistrefs == 0);
Assert(owner->nrelrefs == 0);
Assert(owner->nplanrefs == 0);
Assert(owner->ntupdescs == 0);
/*
@ -341,6 +355,8 @@ ResourceOwnerDelete(ResourceOwner owner)
pfree(owner->catlistrefs);
if (owner->relrefs)
pfree(owner->relrefs);
if (owner->planrefs)
pfree(owner->planrefs);
if (owner->tupdescs)
pfree(owner->tupdescs);
@ -758,6 +774,86 @@ PrintRelCacheLeakWarning(Relation rel)
RelationGetRelationName(rel));
}
/*
* Make sure there is room for at least one more entry in a ResourceOwner's
* plancache reference array.
*
* This is separate from actually inserting an entry because if we run out
* of memory, it's critical to do so *before* acquiring the resource.
*/
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
int newmax;
if (owner->nplanrefs < owner->maxplanrefs)
return; /* nothing to do */
if (owner->planrefs == NULL)
{
newmax = 16;
owner->planrefs = (CachedPlan **)
MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
owner->maxplanrefs = newmax;
}
else
{
newmax = owner->maxplanrefs * 2;
owner->planrefs = (CachedPlan **)
repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
owner->maxplanrefs = newmax;
}
}
/*
* Remember that a plancache reference is owned by a ResourceOwner
*
* Caller must have previously done ResourceOwnerEnlargePlanCacheRefs()
*/
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
Assert(owner->nplanrefs < owner->maxplanrefs);
owner->planrefs[owner->nplanrefs] = plan;
owner->nplanrefs++;
}
/*
* Forget that a plancache reference is owned by a ResourceOwner
*/
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
CachedPlan **planrefs = owner->planrefs;
int np1 = owner->nplanrefs - 1;
int i;
for (i = np1; i >= 0; i--)
{
if (planrefs[i] == plan)
{
while (i < np1)
{
planrefs[i] = planrefs[i + 1];
i++;
}
owner->nplanrefs = np1;
return;
}
}
elog(ERROR, "plancache reference %p is not owned by resource owner %s",
plan, owner->name);
}
/*
* Debugging subroutine
*/
static void
PrintPlanCacheLeakWarning(CachedPlan *plan)
{
elog(WARNING, "plancache reference leak: plan %p not closed", plan);
}
/*
* Make sure there is room for at least one more entry in a ResourceOwner's
* tupdesc reference array.

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.84 2007/01/05 22:19:51 momjian Exp $
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.85 2007/03/13 00:33:42 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -164,9 +164,9 @@ extern bool IsTransactionBlock(void);
extern bool IsTransactionOrTransactionBlock(void);
extern char TransactionBlockStatusCode(void);
extern void AbortOutOfAnyTransaction(void);
extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
extern bool IsInTransactionChain(void *stmtNode);
extern void PreventTransactionChain(bool isTopLevel, const char *stmtType);
extern void RequireTransactionChain(bool isTopLevel, const char *stmtType);
extern bool IsInTransactionChain(bool isTopLevel);
extern void RegisterXactCallback(XactCallback callback, void *arg);
extern void UnregisterXactCallback(XactCallback callback, void *arg);
extern void RegisterSubXactCallback(SubXactCallback callback, void *arg);

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.31 2007/01/05 22:19:53 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/cluster.h,v 1.32 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,7 +17,7 @@
#include "utils/rel.h"
extern void cluster(ClusterStmt *stmt);
extern void cluster(ClusterStmt *stmt, bool isTopLevel);
extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
bool recheck);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.29 2007/01/05 22:19:53 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/copy.h,v 1.30 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -18,7 +18,7 @@
#include "tcop/dest.h"
extern uint64 DoCopy(const CopyStmt *stmt);
extern uint64 DoCopy(const CopyStmt *stmt, const char *queryString);
extern DestReceiver *CreateCopyDestReceiver(void);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.80 2007/01/23 05:07:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/defrem.h,v 1.81 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -25,7 +25,6 @@ extern void DefineIndex(RangeVar *heapRelation,
char *tableSpaceName,
List *attributeList,
Expr *predicate,
List *rangetable,
List *options,
bool unique,
bool primary,

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994-5, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.29 2007/01/05 22:19:53 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/explain.h,v 1.30 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.21 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/portalcmds.h,v 1.22 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -18,7 +18,8 @@
#include "utils/portal.h"
extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params);
extern void PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
const char *queryString, bool isTopLevel);
extern void PerformPortalFetch(FetchStmt *stmt, DestReceiver *dest,
char *completionTag);

View File

@ -6,7 +6,7 @@
*
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.24 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/include/commands/prepare.h,v 1.25 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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 */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.15 2007/01/05 22:19:54 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/schemacmds.h,v 1.16 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,7 +17,8 @@
#include "nodes/parsenodes.h"
extern void CreateSchemaCommand(CreateSchemaStmt *parsetree);
extern void CreateSchemaCommand(CreateSchemaStmt *parsetree,
const char *queryString);
extern void RemoveSchema(List *names, DropBehavior behavior, bool missing_ok);
extern void RemoveSchemaById(Oid schemaOid);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.69 2007/01/05 22:19:54 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.70 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -110,7 +110,7 @@ extern int vacuum_freeze_min_age;
/* in commands/vacuum.c */
extern void vacuum(VacuumStmt *vacstmt, List *relids);
extern void vacuum(VacuumStmt *vacstmt, List *relids, bool isTopLevel);
extern void vac_open_indexes(Relation relation, LOCKMODE lockmode,
int *nindexes, Relation **Irel);
extern void vac_close_indexes(int nindexes, Relation *Irel, LOCKMODE lockmode);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/commands/view.h,v 1.24 2007/01/05 22:19:54 momjian Exp $
* $PostgreSQL: pgsql/src/include/commands/view.h,v 1.25 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -16,7 +16,7 @@
#include "nodes/parsenodes.h"
extern void DefineView(RangeVar *view, Query *view_parse, bool replace);
extern void DefineView(ViewStmt *stmt, const char *queryString);
extern void RemoveView(const RangeVar *view, DropBehavior behavior);
#endif /* VIEW_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.34 2007/01/05 22:19:55 momjian Exp $
* $PostgreSQL: pgsql/src/include/nodes/params.h,v 1.35 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -82,4 +82,7 @@ typedef struct ParamExecData
/* Functions found in src/backend/nodes/params.c */
extern ParamListInfo copyParamList(ParamListInfo from);
extern void getParamListTypes(ParamListInfo params,
Oid **param_types, int *num_params);
#endif /* PARAMS_H */

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.341 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.342 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -1021,15 +1021,14 @@ typedef struct GrantRoleStmt
*
* We support "COPY relation FROM file", "COPY relation TO file", and
* "COPY (query) TO file". In any given CopyStmt, exactly one of "relation"
* and "query" must be non-NULL. Note: "query" is a SelectStmt before
* parse analysis, and a Query afterwards.
* and "query" must be non-NULL.
* ----------------------
*/
typedef struct CopyStmt
{
NodeTag type;
RangeVar *relation; /* the relation to copy */
Query *query; /* the query to copy */
Node *query; /* the SELECT query to copy */
List *attlist; /* List of column names (as Strings), or NIL
* for all columns */
bool is_from; /* TO or FROM */
@ -1487,8 +1486,6 @@ typedef struct IndexStmt
List *indexParams; /* a list of IndexElem */
List *options; /* options from WITH clause */
Node *whereClause; /* qualification (partial-index predicate) */
List *rangetable; /* range table for qual and/or expressions,
* filled in by transformStmt() */
bool unique; /* is index unique? */
bool primary; /* is index on primary key? */
bool isconstraint; /* is it from a CONSTRAINT clause? */
@ -1713,7 +1710,7 @@ typedef struct ViewStmt
NodeTag type;
RangeVar *view; /* the view to be created */
List *aliases; /* target column names */
Query *query; /* the SQL statement */
Node *query; /* the SELECT query */
bool replace; /* replace an existing view? */
} ViewStmt;
@ -1805,7 +1802,7 @@ typedef struct VacuumStmt
typedef struct ExplainStmt
{
NodeTag type;
Query *query; /* the query */
Node *query; /* the query (as a raw parse tree) */
bool verbose; /* print plan info */
bool analyze; /* get statistics by executing plan */
} ExplainStmt;
@ -1940,9 +1937,8 @@ typedef struct PrepareStmt
{
NodeTag type;
char *name; /* Name of plan, arbitrary */
List *argtypes; /* Types of parameters (TypeNames) */
List *argtype_oids; /* Types of parameters (OIDs) */
Query *query; /* The query itself */
List *argtypes; /* Types of parameters (List of TypeName) */
Node *query; /* The query itself (as a raw parsetree) */
} PrepareStmt;

View File

@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.35 2007/01/05 22:19:56 momjian Exp $
* $PostgreSQL: pgsql/src/include/parser/analyze.h,v 1.36 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,10 @@ extern List *parse_analyze(Node *parseTree, const char *sourceText,
extern List *parse_analyze_varparams(Node *parseTree, const char *sourceText,
Oid **paramTypes, int *numParams);
extern List *parse_sub_analyze(Node *parseTree, ParseState *parentParseState);
extern IndexStmt *analyzeIndexStmt(IndexStmt *stmt, const char *queryString);
extern void analyzeRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
extern List *analyzeCreateSchemaStmt(CreateSchemaStmt *stmt);
extern void CheckSelectLocking(Query *qry);
extern void applyLockingClause(Query *qry, Index rtindex,

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.23 2007/01/05 22:19:57 momjian Exp $
* $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.24 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -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);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.41 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/include/tcop/pquery.h,v 1.42 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -33,7 +33,7 @@ extern void PortalStart(Portal portal, ParamListInfo params,
extern void PortalSetResultFormat(Portal portal, int nFormats,
int16 *formats);
extern bool PortalRun(Portal portal, long count,
extern bool PortalRun(Portal portal, long count, bool isTopLevel,
DestReceiver *dest, DestReceiver *altdest,
char *completionTag);

View File

@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.31 2007/02/20 17:32:17 tgl Exp $
* $PostgreSQL: pgsql/src/include/tcop/utility.h,v 1.32 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -17,8 +17,9 @@
#include "tcop/tcopprot.h"
extern void ProcessUtility(Node *parsetree, ParamListInfo params,
DestReceiver *dest, char *completionTag);
extern void ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag);
extern bool UtilityReturnsTuples(Node *parsetree);

View File

@ -10,7 +10,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.61 2007/01/05 22:19:59 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/memutils.h,v 1.62 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -75,8 +75,7 @@ extern DLLIMPORT MemoryContext MessageContext;
extern DLLIMPORT MemoryContext TopTransactionContext;
extern DLLIMPORT MemoryContext CurTransactionContext;
/* These two are transient links to contexts owned by other objects: */
extern DLLIMPORT MemoryContext QueryContext;
/* This is a transient link to the active portal's memory context: */
extern DLLIMPORT MemoryContext PortalContext;

View File

@ -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 */

View File

@ -39,7 +39,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.73 2007/02/20 17:32:18 tgl Exp $
* $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.74 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -94,7 +94,8 @@ typedef enum PortalStrategy
*/
typedef enum PortalStatus
{
PORTAL_NEW, /* in process of creation */
PORTAL_NEW, /* freshly created */
PORTAL_DEFINED, /* PortalDefineQuery done */
PORTAL_READY, /* PortalStart complete, can run it */
PORTAL_ACTIVE, /* portal is running (can't delete it) */
PORTAL_DONE, /* portal is finished (don't re-run it) */
@ -125,15 +126,7 @@ typedef struct PortalData
const char *sourceText; /* text of query, if known (may be NULL) */
const char *commandTag; /* command tag for original query */
List *stmts; /* PlannedStmts and/or utility statements */
MemoryContext queryContext; /* where the plan trees live */
/*
* Note: queryContext effectively identifies which prepared statement the
* portal depends on, if any. The queryContext is *not* owned by the
* portal and is not to be deleted by portal destruction. (But for a
* cursor it is the same as "heap", and that context is deleted by portal
* destruction.) The plan trees may be in either queryContext or heap.
*/
CachedPlan *cplan; /* CachedPlan, if stmts are from one */
ParamListInfo portalParams; /* params to pass to query */
@ -210,14 +203,13 @@ extern void AtSubCleanup_Portals(SubTransactionId mySubid);
extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent);
extern Portal CreateNewPortal(void);
extern void PortalDrop(Portal portal, bool isTopCommit);
extern void DropDependentPortals(MemoryContext queryContext);
extern Portal GetPortalByName(const char *name);
extern void PortalDefineQuery(Portal portal,
const char *prepStmtName,
const char *sourceText,
const char *commandTag,
List *stmts,
MemoryContext queryContext);
CachedPlan *cplan);
extern Node *PortalListGetPrimaryStmt(List *stmts);
extern void PortalCreateHoldStore(Portal portal);

View File

@ -12,7 +12,7 @@
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.10 2007/01/05 22:19:59 momjian Exp $
* $PostgreSQL: pgsql/src/include/utils/resowner.h,v 1.11 2007/03/13 00:33:43 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -21,6 +21,7 @@
#include "storage/buf.h"
#include "utils/catcache.h"
#include "utils/plancache.h"
/*
@ -106,6 +107,13 @@ extern void ResourceOwnerRememberRelationRef(ResourceOwner owner,
extern void ResourceOwnerForgetRelationRef(ResourceOwner owner,
Relation rel);
/* support for plancache refcount management */
extern void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner);
extern void ResourceOwnerRememberPlanCacheRef(ResourceOwner owner,
CachedPlan *plan);
extern void ResourceOwnerForgetPlanCacheRef(ResourceOwner owner,
CachedPlan *plan);
/* support for tupledesc refcount management */
extern void ResourceOwnerEnlargeTupleDescs(ResourceOwner owner);
extern void ResourceOwnerRememberTupleDesc(ResourceOwner owner,

View File

@ -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)

View File

@ -1188,6 +1188,8 @@ drop rule foorule on foo;
create rule foorule as on insert to foo where f1 < 100
do instead insert into foo2 values (f1);
ERROR: column "f1" does not exist
LINE 2: do instead insert into foo2 values (f1);
^
-- this is the correct way:
create rule foorule as on insert to foo where f1 < 100
do instead insert into foo2 values (new.f1);

View File

@ -1,6 +1,6 @@
# ----------
# The first group of parallel test
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.39 2007/02/09 03:35:35 tgl Exp $
# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.40 2007/03/13 00:33:44 tgl Exp $
# ----------
test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid
@ -69,7 +69,7 @@ test: misc
# ----------
# The fifth group of parallel test
# ----------
test: select_views portals_p2 rules foreign_key cluster dependency guc combocid
test: select_views portals_p2 rules foreign_key cluster dependency guc combocid plancache
# ----------
# The sixth group of parallel test

View File

@ -1,4 +1,4 @@
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.37 2007/02/09 03:35:35 tgl Exp $
# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.38 2007/03/13 00:33:44 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
@ -89,6 +89,7 @@ test: cluster
test: dependency
test: guc
test: combocid
test: plancache
test: limit
test: plpgsql
test: copy2

View File

@ -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;