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:
parent
f84308f195
commit
b9527e9840
@ -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 &&
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1994-5, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.159 2007/02/23 21:59:44 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.160 2007/03/13 00:33:39 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -26,6 +26,7 @@
|
||||
#include "optimizer/var.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/guc.h"
|
||||
#include "utils/lsyscache.h"
|
||||
@ -41,8 +42,9 @@ typedef struct ExplainState
|
||||
List *rtable; /* range table */
|
||||
} ExplainState;
|
||||
|
||||
static void ExplainOneQuery(Query *query, ExplainStmt *stmt,
|
||||
ParamListInfo params, TupOutputState *tstate);
|
||||
static void ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
|
||||
ExplainStmt *stmt, const char *queryString,
|
||||
ParamListInfo params, TupOutputState *tstate);
|
||||
static double elapsed_time(instr_time *starttime);
|
||||
static void explain_outNode(StringInfo str,
|
||||
Plan *plan, PlanState *planstate,
|
||||
@ -62,62 +64,49 @@ static void show_sort_keys(Plan *sortplan, int nkeys, AttrNumber *keycols,
|
||||
* execute an EXPLAIN command
|
||||
*/
|
||||
void
|
||||
ExplainQuery(ExplainStmt *stmt, ParamListInfo params, DestReceiver *dest)
|
||||
ExplainQuery(ExplainStmt *stmt, const char *queryString,
|
||||
ParamListInfo params, DestReceiver *dest)
|
||||
{
|
||||
Query *query = stmt->query;
|
||||
Oid *param_types;
|
||||
int num_params;
|
||||
TupOutputState *tstate;
|
||||
List *rewritten;
|
||||
ListCell *l;
|
||||
|
||||
/* Convert parameter type data to the form parser wants */
|
||||
getParamListTypes(params, ¶m_types, &num_params);
|
||||
|
||||
/*
|
||||
* Because the planner is not cool about not scribbling on its input, we
|
||||
* Run parse analysis and rewrite. Note this also acquires sufficient
|
||||
* locks on the source table(s).
|
||||
*
|
||||
* Because the parser and planner tend to scribble on their input, we
|
||||
* make a preliminary copy of the source querytree. This prevents
|
||||
* problems in the case that the EXPLAIN is in a portal or plpgsql
|
||||
* function and is executed repeatedly. (See also the same hack in
|
||||
* DECLARE CURSOR and PREPARE.) XXX the planner really shouldn't modify
|
||||
* its input ... FIXME someday.
|
||||
* DECLARE CURSOR and PREPARE.) XXX FIXME someday.
|
||||
*/
|
||||
query = copyObject(query);
|
||||
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
|
||||
queryString, param_types, num_params);
|
||||
|
||||
/* prepare for projection of tuples */
|
||||
tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
|
||||
|
||||
if (query->commandType == CMD_UTILITY)
|
||||
if (rewritten == NIL)
|
||||
{
|
||||
/* Rewriter will not cope with utility statements */
|
||||
if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
|
||||
ExplainOneQuery(query, stmt, params, tstate);
|
||||
else if (query->utilityStmt && IsA(query->utilityStmt, ExecuteStmt))
|
||||
ExplainExecuteQuery(stmt, params, tstate);
|
||||
else
|
||||
do_text_output_oneline(tstate, "Utility statements have no plan structure");
|
||||
/* In the case of an INSTEAD NOTHING, tell at least that */
|
||||
do_text_output_oneline(tstate, "Query rewrites to nothing");
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* Must acquire locks in case we didn't come fresh from the parser.
|
||||
* XXX this also scribbles on query, another reason for copyObject
|
||||
*/
|
||||
AcquireRewriteLocks(query);
|
||||
|
||||
/* Rewrite through rule system */
|
||||
rewritten = QueryRewrite(query);
|
||||
|
||||
if (rewritten == NIL)
|
||||
/* Explain every plan */
|
||||
foreach(l, rewritten)
|
||||
{
|
||||
/* In the case of an INSTEAD NOTHING, tell at least that */
|
||||
do_text_output_oneline(tstate, "Query rewrites to nothing");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Explain every plan */
|
||||
foreach(l, rewritten)
|
||||
{
|
||||
ExplainOneQuery(lfirst(l), stmt, params, tstate);
|
||||
/* put a blank line between plans */
|
||||
if (lnext(l) != NULL)
|
||||
do_text_output_oneline(tstate, "");
|
||||
}
|
||||
ExplainOneQuery((Query *) lfirst(l), false, 0,
|
||||
stmt, queryString, params, tstate);
|
||||
/* put a blank line between plans */
|
||||
if (lnext(l) != NULL)
|
||||
do_text_output_oneline(tstate, "");
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,51 +131,22 @@ ExplainResultDesc(ExplainStmt *stmt)
|
||||
|
||||
/*
|
||||
* ExplainOneQuery -
|
||||
* print out the execution plan for one query
|
||||
* print out the execution plan for one Query
|
||||
*/
|
||||
static void
|
||||
ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
|
||||
TupOutputState *tstate)
|
||||
ExplainOneQuery(Query *query, bool isCursor, int cursorOptions,
|
||||
ExplainStmt *stmt, const char *queryString,
|
||||
ParamListInfo params, TupOutputState *tstate)
|
||||
{
|
||||
PlannedStmt *plan;
|
||||
QueryDesc *queryDesc;
|
||||
bool isCursor = false;
|
||||
int cursorOptions = 0;
|
||||
|
||||
/* planner will not cope with utility statements */
|
||||
if (query->commandType == CMD_UTILITY)
|
||||
{
|
||||
if (query->utilityStmt && IsA(query->utilityStmt, DeclareCursorStmt))
|
||||
{
|
||||
DeclareCursorStmt *dcstmt;
|
||||
List *rewritten;
|
||||
|
||||
dcstmt = (DeclareCursorStmt *) query->utilityStmt;
|
||||
query = (Query *) dcstmt->query;
|
||||
isCursor = true;
|
||||
cursorOptions = dcstmt->options;
|
||||
/* Still need to rewrite cursor command */
|
||||
Assert(query->commandType == CMD_SELECT);
|
||||
/* get locks (we assume ExplainQuery already copied tree) */
|
||||
AcquireRewriteLocks(query);
|
||||
rewritten = QueryRewrite(query);
|
||||
if (list_length(rewritten) != 1)
|
||||
elog(ERROR, "unexpected rewrite result");
|
||||
query = (Query *) linitial(rewritten);
|
||||
Assert(query->commandType == CMD_SELECT);
|
||||
/* do not actually execute the underlying query! */
|
||||
stmt->analyze = false;
|
||||
}
|
||||
else if (query->utilityStmt && IsA(query->utilityStmt, NotifyStmt))
|
||||
{
|
||||
do_text_output_oneline(tstate, "NOTIFY");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
do_text_output_oneline(tstate, "UTILITY");
|
||||
return;
|
||||
}
|
||||
ExplainOneUtility(query->utilityStmt, stmt,
|
||||
queryString, params, tstate);
|
||||
return;
|
||||
}
|
||||
|
||||
/* plan the query */
|
||||
@ -210,6 +170,78 @@ ExplainOneQuery(Query *query, ExplainStmt *stmt, ParamListInfo params,
|
||||
ExplainOnePlan(queryDesc, stmt, tstate);
|
||||
}
|
||||
|
||||
/*
|
||||
* ExplainOneUtility -
|
||||
* print out the execution plan for one utility statement
|
||||
* (In general, utility statements don't have plans, but there are some
|
||||
* we treat as special cases)
|
||||
*
|
||||
* This is exported because it's called back from prepare.c in the
|
||||
* EXPLAIN EXECUTE case
|
||||
*/
|
||||
void
|
||||
ExplainOneUtility(Node *utilityStmt, ExplainStmt *stmt,
|
||||
const char *queryString, ParamListInfo params,
|
||||
TupOutputState *tstate)
|
||||
{
|
||||
if (utilityStmt == NULL)
|
||||
return;
|
||||
|
||||
if (IsA(utilityStmt, DeclareCursorStmt))
|
||||
{
|
||||
DeclareCursorStmt *dcstmt = (DeclareCursorStmt *) utilityStmt;
|
||||
Oid *param_types;
|
||||
int num_params;
|
||||
Query *query;
|
||||
List *rewritten;
|
||||
ExplainStmt newstmt;
|
||||
|
||||
/* Convert parameter type data to the form parser wants */
|
||||
getParamListTypes(params, ¶m_types, &num_params);
|
||||
|
||||
/*
|
||||
* Run parse analysis and rewrite. Note this also acquires sufficient
|
||||
* locks on the source table(s).
|
||||
*
|
||||
* Because the parser and planner tend to scribble on their input, we
|
||||
* make a preliminary copy of the source querytree. This prevents
|
||||
* problems in the case that the DECLARE CURSOR is in a portal or
|
||||
* plpgsql function and is executed repeatedly. (See also the same
|
||||
* hack in COPY and PREPARE.) XXX FIXME someday.
|
||||
*/
|
||||
rewritten = pg_analyze_and_rewrite((Node *) copyObject(dcstmt->query),
|
||||
queryString,
|
||||
param_types, num_params);
|
||||
|
||||
/* We don't expect more or less than one result query */
|
||||
if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
|
||||
elog(ERROR, "unexpected rewrite result");
|
||||
query = (Query *) linitial(rewritten);
|
||||
if (query->commandType != CMD_SELECT)
|
||||
elog(ERROR, "unexpected rewrite result");
|
||||
|
||||
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
|
||||
if (query->into)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
||||
errmsg("DECLARE CURSOR cannot specify INTO")));
|
||||
|
||||
/* do not actually execute the underlying query! */
|
||||
memcpy(&newstmt, stmt, sizeof(ExplainStmt));
|
||||
newstmt.analyze = false;
|
||||
ExplainOneQuery(query, true, dcstmt->options, &newstmt,
|
||||
queryString, params, tstate);
|
||||
}
|
||||
else if (IsA(utilityStmt, ExecuteStmt))
|
||||
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, stmt,
|
||||
queryString, params, tstate);
|
||||
else if (IsA(utilityStmt, NotifyStmt))
|
||||
do_text_output_oneline(tstate, "NOTIFY");
|
||||
else
|
||||
do_text_output_oneline(tstate,
|
||||
"Utility statements have no plan structure");
|
||||
}
|
||||
|
||||
/*
|
||||
* ExplainOnePlan -
|
||||
* given a planned query, execute it if needed, and then print
|
||||
|
@ -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
|
||||
|
@ -14,7 +14,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.61 2007/02/20 17:32:14 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.62 2007/03/13 00:33:39 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -38,8 +38,11 @@
|
||||
* Execute SQL DECLARE CURSOR command.
|
||||
*/
|
||||
void
|
||||
PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
|
||||
PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params,
|
||||
const char *queryString, bool isTopLevel)
|
||||
{
|
||||
Oid *param_types;
|
||||
int num_params;
|
||||
List *rewritten;
|
||||
Query *query;
|
||||
PlannedStmt *plan;
|
||||
@ -61,40 +64,53 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
|
||||
* user-visible effect).
|
||||
*/
|
||||
if (!(stmt->options & CURSOR_OPT_HOLD))
|
||||
RequireTransactionChain((void *) stmt, "DECLARE CURSOR");
|
||||
RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
|
||||
|
||||
/*
|
||||
* Because the planner is not cool about not scribbling on its input, we
|
||||
* Don't allow both SCROLL and NO SCROLL to be specified
|
||||
*/
|
||||
if ((stmt->options & CURSOR_OPT_SCROLL) &&
|
||||
(stmt->options & CURSOR_OPT_NO_SCROLL))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
||||
errmsg("cannot specify both SCROLL and NO SCROLL")));
|
||||
|
||||
/* Convert parameter type data to the form parser wants */
|
||||
getParamListTypes(params, ¶m_types, &num_params);
|
||||
|
||||
/*
|
||||
* Run parse analysis and rewrite. Note this also acquires sufficient
|
||||
* locks on the source table(s).
|
||||
*
|
||||
* Because the parser and planner tend to scribble on their input, we
|
||||
* make a preliminary copy of the source querytree. This prevents
|
||||
* problems in the case that the DECLARE CURSOR is in a portal and is
|
||||
* executed repeatedly. XXX the planner really shouldn't modify its input
|
||||
* ... FIXME someday.
|
||||
* problems in the case that the DECLARE CURSOR is in a portal or plpgsql
|
||||
* function and is executed repeatedly. (See also the same hack in
|
||||
* COPY and PREPARE.) XXX FIXME someday.
|
||||
*/
|
||||
query = copyObject(stmt->query);
|
||||
rewritten = pg_analyze_and_rewrite((Node *) copyObject(stmt->query),
|
||||
queryString, param_types, num_params);
|
||||
|
||||
/*
|
||||
* The query has been through parse analysis, but not rewriting or
|
||||
* planning as yet. Note that the grammar ensured we have a SELECT query,
|
||||
* so we are not expecting rule rewriting to do anything strange.
|
||||
*/
|
||||
AcquireRewriteLocks(query);
|
||||
rewritten = QueryRewrite(query);
|
||||
/* We don't expect more or less than one result query */
|
||||
if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
|
||||
elog(ERROR, "unexpected rewrite result");
|
||||
query = (Query *) linitial(rewritten);
|
||||
if (query->commandType != CMD_SELECT)
|
||||
elog(ERROR, "unexpected rewrite result");
|
||||
|
||||
/* But we must explicitly disallow DECLARE CURSOR ... SELECT INTO */
|
||||
if (query->into)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
|
||||
errmsg("DECLARE CURSOR cannot specify INTO")));
|
||||
|
||||
if (query->rowMarks != NIL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("DECLARE CURSOR ... FOR UPDATE/SHARE is not supported"),
|
||||
errdetail("Cursors must be READ ONLY.")));
|
||||
|
||||
/* plan the query */
|
||||
plan = planner(query, true, stmt->options, params);
|
||||
|
||||
/*
|
||||
@ -106,23 +122,22 @@ PerformCursorOpen(DeclareCursorStmt *stmt, ParamListInfo params)
|
||||
|
||||
plan = copyObject(plan);
|
||||
|
||||
/*
|
||||
* XXX: debug_query_string is wrong here: the user might have submitted
|
||||
* multiple semicolon delimited queries.
|
||||
*/
|
||||
PortalDefineQuery(portal,
|
||||
NULL,
|
||||
debug_query_string ? pstrdup(debug_query_string) : NULL,
|
||||
queryString,
|
||||
"SELECT", /* cursor's query is always a SELECT */
|
||||
list_make1(plan),
|
||||
PortalGetHeapMemory(portal));
|
||||
NULL);
|
||||
|
||||
/*
|
||||
/*----------
|
||||
* Also copy the outer portal's parameter list into the inner portal's
|
||||
* memory context. We want to pass down the parameter values in case we
|
||||
* had a command like DECLARE c CURSOR FOR SELECT ... WHERE foo = $1 This
|
||||
* will have been parsed using the outer parameter set and the parameter
|
||||
* value needs to be preserved for use when the cursor is executed.
|
||||
* had a command like
|
||||
* DECLARE c CURSOR FOR SELECT ... WHERE foo = $1
|
||||
* This will have been parsed using the outer parameter set and the
|
||||
* parameter value needs to be preserved for use when the cursor is
|
||||
* executed.
|
||||
*----------
|
||||
*/
|
||||
params = copyParamList(params);
|
||||
|
||||
@ -314,7 +329,6 @@ PersistHoldablePortal(Portal portal)
|
||||
Snapshot saveActiveSnapshot;
|
||||
ResourceOwner saveResourceOwner;
|
||||
MemoryContext savePortalContext;
|
||||
MemoryContext saveQueryContext;
|
||||
MemoryContext oldcxt;
|
||||
|
||||
/*
|
||||
@ -356,14 +370,12 @@ PersistHoldablePortal(Portal portal)
|
||||
saveActiveSnapshot = ActiveSnapshot;
|
||||
saveResourceOwner = CurrentResourceOwner;
|
||||
savePortalContext = PortalContext;
|
||||
saveQueryContext = QueryContext;
|
||||
PG_TRY();
|
||||
{
|
||||
ActivePortal = portal;
|
||||
ActiveSnapshot = queryDesc->snapshot;
|
||||
CurrentResourceOwner = portal->resowner;
|
||||
PortalContext = PortalGetHeapMemory(portal);
|
||||
QueryContext = portal->queryContext;
|
||||
|
||||
MemoryContextSwitchTo(PortalContext);
|
||||
|
||||
@ -434,7 +446,6 @@ PersistHoldablePortal(Portal portal)
|
||||
ActiveSnapshot = saveActiveSnapshot;
|
||||
CurrentResourceOwner = saveResourceOwner;
|
||||
PortalContext = savePortalContext;
|
||||
QueryContext = saveQueryContext;
|
||||
|
||||
PG_RE_THROW();
|
||||
}
|
||||
@ -449,7 +460,6 @@ PersistHoldablePortal(Portal portal)
|
||||
ActiveSnapshot = saveActiveSnapshot;
|
||||
CurrentResourceOwner = saveResourceOwner;
|
||||
PortalContext = savePortalContext;
|
||||
QueryContext = saveQueryContext;
|
||||
|
||||
/*
|
||||
* We can now release any subsidiary memory of the portal's heap context;
|
||||
|
@ -10,7 +10,7 @@
|
||||
* Copyright (c) 2002-2007, PostgreSQL Global Development Group
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.69 2007/02/20 17:32:14 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.70 2007/03/13 00:33:39 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -22,6 +22,10 @@
|
||||
#include "commands/explain.h"
|
||||
#include "commands/prepare.h"
|
||||
#include "funcapi.h"
|
||||
#include "parser/analyze.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_expr.h"
|
||||
#include "parser/parse_type.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "tcop/pquery.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
@ -39,20 +43,24 @@
|
||||
static HTAB *prepared_queries = NULL;
|
||||
|
||||
static void InitQueryHashTable(void);
|
||||
static ParamListInfo EvaluateParams(EState *estate,
|
||||
List *params, List *argtypes);
|
||||
static Datum build_regtype_array(List *oid_list);
|
||||
static ParamListInfo EvaluateParams(PreparedStatement *pstmt, List *params,
|
||||
const char *queryString, EState *estate);
|
||||
static Datum build_regtype_array(Oid *param_types, int num_params);
|
||||
|
||||
/*
|
||||
* Implements the 'PREPARE' utility statement.
|
||||
*/
|
||||
void
|
||||
PrepareQuery(PrepareStmt *stmt)
|
||||
PrepareQuery(PrepareStmt *stmt, const char *queryString)
|
||||
{
|
||||
const char *commandTag;
|
||||
Oid *argtypes = NULL;
|
||||
int nargs;
|
||||
List *queries;
|
||||
Query *query;
|
||||
const char *commandTag;
|
||||
List *query_list,
|
||||
*plan_list;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Disallow empty-string statement name (conflicts with protocol-level
|
||||
@ -63,7 +71,70 @@ PrepareQuery(PrepareStmt *stmt)
|
||||
(errcode(ERRCODE_INVALID_PSTATEMENT_DEFINITION),
|
||||
errmsg("invalid statement name: must not be empty")));
|
||||
|
||||
switch (stmt->query->commandType)
|
||||
/* Transform list of TypeNames to array of type OIDs */
|
||||
nargs = list_length(stmt->argtypes);
|
||||
|
||||
if (nargs)
|
||||
{
|
||||
ParseState *pstate;
|
||||
ListCell *l;
|
||||
|
||||
/*
|
||||
* typenameTypeId wants a ParseState to carry the source query string.
|
||||
* Is it worth refactoring its API to avoid this?
|
||||
*/
|
||||
pstate = make_parsestate(NULL);
|
||||
pstate->p_sourcetext = queryString;
|
||||
|
||||
argtypes = (Oid *) palloc(nargs * sizeof(Oid));
|
||||
i = 0;
|
||||
|
||||
foreach(l, stmt->argtypes)
|
||||
{
|
||||
TypeName *tn = lfirst(l);
|
||||
Oid toid = typenameTypeId(pstate, tn);
|
||||
|
||||
argtypes[i++] = toid;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Analyze the statement using these parameter types (any parameters
|
||||
* passed in from above us will not be visible to it), allowing
|
||||
* information about unknown parameters to be deduced from context.
|
||||
*
|
||||
* Because parse analysis scribbles on the raw querytree, we must make
|
||||
* a copy to ensure we have a pristine raw tree to cache. FIXME someday.
|
||||
*/
|
||||
queries = parse_analyze_varparams((Node *) copyObject(stmt->query),
|
||||
queryString,
|
||||
&argtypes, &nargs);
|
||||
|
||||
/*
|
||||
* Check that all parameter types were determined.
|
||||
*/
|
||||
for (i = 0; i < nargs; i++)
|
||||
{
|
||||
Oid argtype = argtypes[i];
|
||||
|
||||
if (argtype == InvalidOid || argtype == UNKNOWNOID)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
|
||||
errmsg("could not determine data type of parameter $%d",
|
||||
i + 1)));
|
||||
}
|
||||
|
||||
/*
|
||||
* Shouldn't get any extra statements, since grammar only allows
|
||||
* OptimizableStmt
|
||||
*/
|
||||
if (list_length(queries) != 1)
|
||||
elog(ERROR, "unexpected extra stuff in prepared statement");
|
||||
|
||||
query = (Query *) linitial(queries);
|
||||
Assert(IsA(query, Query));
|
||||
|
||||
switch (query->commandType)
|
||||
{
|
||||
case CMD_SELECT:
|
||||
commandTag = "SELECT";
|
||||
@ -85,38 +156,22 @@ PrepareQuery(PrepareStmt *stmt)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse analysis is already done, but we must still rewrite and plan the
|
||||
* query.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Because the planner is not cool about not scribbling on its input, we
|
||||
* make a preliminary copy of the source querytree. This prevents
|
||||
* problems in the case that the PREPARE is in a portal or plpgsql
|
||||
* function and is executed repeatedly. (See also the same hack in
|
||||
* DECLARE CURSOR and EXPLAIN.) XXX the planner really shouldn't modify
|
||||
* its input ... FIXME someday.
|
||||
*/
|
||||
query = copyObject(stmt->query);
|
||||
|
||||
/* Rewrite the query. The result could be 0, 1, or many queries. */
|
||||
AcquireRewriteLocks(query);
|
||||
query_list = QueryRewrite(query);
|
||||
|
||||
/* Generate plans for queries. Snapshot is already set. */
|
||||
plan_list = pg_plan_queries(query_list, NULL, false);
|
||||
|
||||
/*
|
||||
* Save the results. We don't have the query string for this PREPARE, but
|
||||
* we do have the string we got from the client, so use that.
|
||||
* Save the results.
|
||||
*/
|
||||
StorePreparedStatement(stmt->name,
|
||||
debug_query_string,
|
||||
stmt->query,
|
||||
queryString,
|
||||
commandTag,
|
||||
argtypes,
|
||||
nargs,
|
||||
plan_list,
|
||||
stmt->argtype_oids,
|
||||
true,
|
||||
true);
|
||||
}
|
||||
|
||||
@ -124,13 +179,13 @@ PrepareQuery(PrepareStmt *stmt)
|
||||
* Implements the 'EXECUTE' utility statement.
|
||||
*/
|
||||
void
|
||||
ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
ExecuteQuery(ExecuteStmt *stmt, const char *queryString,
|
||||
ParamListInfo params,
|
||||
DestReceiver *dest, char *completionTag)
|
||||
{
|
||||
PreparedStatement *entry;
|
||||
char *query_string;
|
||||
CachedPlan *cplan;
|
||||
List *plan_list;
|
||||
MemoryContext qcontext;
|
||||
ParamListInfo paramLI = NULL;
|
||||
EState *estate = NULL;
|
||||
Portal portal;
|
||||
@ -138,20 +193,15 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
/* Look it up in the hash table */
|
||||
entry = FetchPreparedStatement(stmt->name, true);
|
||||
|
||||
/*
|
||||
* Punt if not fully planned. (Currently, that only happens for the
|
||||
* protocol-level unnamed statement, which can't be accessed from SQL;
|
||||
* so there's no point in doing more than a quick check here.)
|
||||
*/
|
||||
if (!entry->fully_planned)
|
||||
/* Shouldn't have a non-fully-planned plancache entry */
|
||||
if (!entry->plansource->fully_planned)
|
||||
elog(ERROR, "EXECUTE does not support unplanned prepared statements");
|
||||
|
||||
query_string = entry->query_string;
|
||||
plan_list = entry->stmt_list;
|
||||
qcontext = entry->context;
|
||||
/* Shouldn't get any non-fixed-result cached plan, either */
|
||||
if (!entry->plansource->fixed_result)
|
||||
elog(ERROR, "EXECUTE does not support variable-result cached plans");
|
||||
|
||||
/* Evaluate parameters, if any */
|
||||
if (entry->argtype_list != NIL)
|
||||
if (entry->plansource->num_params > 0)
|
||||
{
|
||||
/*
|
||||
* Need an EState to evaluate parameters; must not delete it till end
|
||||
@ -159,7 +209,8 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
*/
|
||||
estate = CreateExecutorState();
|
||||
estate->es_param_list_info = params;
|
||||
paramLI = EvaluateParams(estate, stmt->params, entry->argtype_list);
|
||||
paramLI = EvaluateParams(entry, stmt->params,
|
||||
queryString, estate);
|
||||
}
|
||||
|
||||
/* Create a new portal to run the query in */
|
||||
@ -168,22 +219,23 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
portal->visible = false;
|
||||
|
||||
/*
|
||||
* For CREATE TABLE / AS EXECUTE, make a copy of the stored query so that
|
||||
* we can modify its destination (yech, but this has always been ugly).
|
||||
* For regular EXECUTE we can just use the stored query where it sits,
|
||||
* since the executor is read-only.
|
||||
* For CREATE TABLE / AS EXECUTE, we must make a copy of the stored query
|
||||
* so that we can modify its destination (yech, but this has always been
|
||||
* ugly). For regular EXECUTE we can just use the cached query, since the
|
||||
* executor is read-only.
|
||||
*/
|
||||
if (stmt->into)
|
||||
{
|
||||
MemoryContext oldContext;
|
||||
PlannedStmt *pstmt;
|
||||
|
||||
qcontext = PortalGetHeapMemory(portal);
|
||||
oldContext = MemoryContextSwitchTo(qcontext);
|
||||
/* Replan if needed, and increment plan refcount transiently */
|
||||
cplan = RevalidateCachedPlan(entry->plansource, true);
|
||||
|
||||
if (query_string)
|
||||
query_string = pstrdup(query_string);
|
||||
plan_list = copyObject(plan_list);
|
||||
/* Copy plan into portal's context, and modify */
|
||||
oldContext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
|
||||
|
||||
plan_list = copyObject(cplan->stmt_list);
|
||||
|
||||
if (list_length(plan_list) != 1)
|
||||
ereport(ERROR,
|
||||
@ -198,21 +250,32 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
pstmt->into = copyObject(stmt->into);
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
/* We no longer need the cached plan refcount ... */
|
||||
ReleaseCachedPlan(cplan, true);
|
||||
/* ... and we don't want the portal to depend on it, either */
|
||||
cplan = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Replan if needed, and increment plan refcount for portal */
|
||||
cplan = RevalidateCachedPlan(entry->plansource, false);
|
||||
plan_list = cplan->stmt_list;
|
||||
}
|
||||
|
||||
PortalDefineQuery(portal,
|
||||
NULL,
|
||||
query_string,
|
||||
entry->commandTag,
|
||||
entry->plansource->query_string,
|
||||
entry->plansource->commandTag,
|
||||
plan_list,
|
||||
qcontext);
|
||||
cplan);
|
||||
|
||||
/*
|
||||
* Run the portal to completion.
|
||||
*/
|
||||
PortalStart(portal, paramLI, ActiveSnapshot);
|
||||
|
||||
(void) PortalRun(portal, FETCH_ALL, dest, dest, completionTag);
|
||||
(void) PortalRun(portal, FETCH_ALL, false, dest, dest, completionTag);
|
||||
|
||||
PortalDrop(portal, false);
|
||||
|
||||
@ -223,42 +286,106 @@ ExecuteQuery(ExecuteStmt *stmt, ParamListInfo params,
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluates a list of parameters, using the given executor state. It
|
||||
* requires a list of the parameter expressions themselves, and a list of
|
||||
* their types. It returns a filled-in ParamListInfo -- this can later
|
||||
* be passed to CreateQueryDesc(), which allows the executor to make use
|
||||
* of the parameters during query execution.
|
||||
* EvaluateParams: evaluate a list of parameters.
|
||||
*
|
||||
* pstmt: statement we are getting parameters for.
|
||||
* params: list of given parameter expressions (raw parser output!)
|
||||
* queryString: source text for error messages.
|
||||
* estate: executor state to use.
|
||||
*
|
||||
* Returns a filled-in ParamListInfo -- this can later be passed to
|
||||
* CreateQueryDesc(), which allows the executor to make use of the parameters
|
||||
* during query execution.
|
||||
*/
|
||||
static ParamListInfo
|
||||
EvaluateParams(EState *estate, List *params, List *argtypes)
|
||||
EvaluateParams(PreparedStatement *pstmt, List *params,
|
||||
const char *queryString, EState *estate)
|
||||
{
|
||||
int nargs = list_length(argtypes);
|
||||
Oid *param_types = pstmt->plansource->param_types;
|
||||
int num_params = pstmt->plansource->num_params;
|
||||
int nparams = list_length(params);
|
||||
ParseState *pstate;
|
||||
ParamListInfo paramLI;
|
||||
List *exprstates;
|
||||
ListCell *le,
|
||||
*la;
|
||||
int i = 0;
|
||||
ListCell *l;
|
||||
int i;
|
||||
|
||||
/* Parser should have caught this error, but check for safety */
|
||||
if (list_length(params) != nargs)
|
||||
elog(ERROR, "wrong number of arguments");
|
||||
if (nparams != num_params)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("wrong number of parameters for prepared statement \"%s\"",
|
||||
pstmt->stmt_name),
|
||||
errdetail("Expected %d parameters but got %d.",
|
||||
num_params, nparams)));
|
||||
|
||||
if (nargs == 0)
|
||||
/* Quick exit if no parameters */
|
||||
if (num_params == 0)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* We have to run parse analysis for the expressions. Since the
|
||||
* parser is not cool about scribbling on its input, copy first.
|
||||
*/
|
||||
params = (List *) copyObject(params);
|
||||
|
||||
pstate = make_parsestate(NULL);
|
||||
pstate->p_sourcetext = queryString;
|
||||
|
||||
i = 0;
|
||||
foreach(l, params)
|
||||
{
|
||||
Node *expr = lfirst(l);
|
||||
Oid expected_type_id = param_types[i];
|
||||
Oid given_type_id;
|
||||
|
||||
expr = transformExpr(pstate, expr);
|
||||
|
||||
/* Cannot contain subselects or aggregates */
|
||||
if (pstate->p_hasSubLinks)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot use subquery in EXECUTE parameter")));
|
||||
if (pstate->p_hasAggs)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_GROUPING_ERROR),
|
||||
errmsg("cannot use aggregate function in EXECUTE parameter")));
|
||||
|
||||
given_type_id = exprType(expr);
|
||||
|
||||
expr = coerce_to_target_type(pstate, expr, given_type_id,
|
||||
expected_type_id, -1,
|
||||
COERCION_ASSIGNMENT,
|
||||
COERCE_IMPLICIT_CAST);
|
||||
|
||||
if (expr == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("parameter $%d of type %s cannot be coerced to the expected type %s",
|
||||
i + 1,
|
||||
format_type_be(given_type_id),
|
||||
format_type_be(expected_type_id)),
|
||||
errhint("You will need to rewrite or cast the expression.")));
|
||||
|
||||
lfirst(l) = expr;
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Prepare the expressions for execution */
|
||||
exprstates = (List *) ExecPrepareExpr((Expr *) params, estate);
|
||||
|
||||
/* sizeof(ParamListInfoData) includes the first array element */
|
||||
paramLI = (ParamListInfo) palloc(sizeof(ParamListInfoData) +
|
||||
(nargs - 1) *sizeof(ParamExternData));
|
||||
paramLI->numParams = nargs;
|
||||
paramLI = (ParamListInfo)
|
||||
palloc(sizeof(ParamListInfoData) +
|
||||
(num_params - 1) *sizeof(ParamExternData));
|
||||
paramLI->numParams = num_params;
|
||||
|
||||
forboth(le, exprstates, la, argtypes)
|
||||
i = 0;
|
||||
foreach(l, exprstates)
|
||||
{
|
||||
ExprState *n = lfirst(le);
|
||||
ExprState *n = lfirst(l);
|
||||
ParamExternData *prm = ¶mLI->params[i];
|
||||
|
||||
prm->ptype = lfirst_oid(la);
|
||||
prm->ptype = param_types[i];
|
||||
prm->pflags = 0;
|
||||
prm->value = ExecEvalExprSwitchContext(n,
|
||||
GetPerTupleExprContext(estate),
|
||||
@ -293,8 +420,9 @@ InitQueryHashTable(void)
|
||||
|
||||
/*
|
||||
* Store all the data pertaining to a query in the hash table using
|
||||
* the specified key. A copy of the data is made in a memory context belonging
|
||||
* to the hash entry, so the caller can dispose of their copy.
|
||||
* the specified key. All the given data is copied into either the hashtable
|
||||
* entry or the underlying plancache entry, so the caller can dispose of its
|
||||
* copy.
|
||||
*
|
||||
* Exception: commandTag is presumed to be a pointer to a constant string,
|
||||
* or possibly NULL, so it need not be copied. Note that commandTag should
|
||||
@ -302,17 +430,16 @@ InitQueryHashTable(void)
|
||||
*/
|
||||
void
|
||||
StorePreparedStatement(const char *stmt_name,
|
||||
Node *raw_parse_tree,
|
||||
const char *query_string,
|
||||
const char *commandTag,
|
||||
Oid *param_types,
|
||||
int num_params,
|
||||
List *stmt_list,
|
||||
List *argtype_list,
|
||||
bool fully_planned,
|
||||
bool from_sql)
|
||||
{
|
||||
PreparedStatement *entry;
|
||||
MemoryContext oldcxt,
|
||||
entrycxt;
|
||||
char *qstring;
|
||||
CachedPlanSource *plansource;
|
||||
bool found;
|
||||
|
||||
/* Initialize the hash table, if necessary */
|
||||
@ -328,24 +455,15 @@ StorePreparedStatement(const char *stmt_name,
|
||||
errmsg("prepared statement \"%s\" already exists",
|
||||
stmt_name)));
|
||||
|
||||
/* Make a permanent memory context for the hashtable entry */
|
||||
entrycxt = AllocSetContextCreate(TopMemoryContext,
|
||||
stmt_name,
|
||||
ALLOCSET_SMALL_MINSIZE,
|
||||
ALLOCSET_SMALL_INITSIZE,
|
||||
ALLOCSET_SMALL_MAXSIZE);
|
||||
|
||||
oldcxt = MemoryContextSwitchTo(entrycxt);
|
||||
|
||||
/*
|
||||
* We need to copy the data so that it is stored in the correct memory
|
||||
* context. Do this before making hashtable entry, so that an
|
||||
* out-of-memory failure only wastes memory and doesn't leave us with an
|
||||
* incomplete (ie corrupt) hashtable entry.
|
||||
*/
|
||||
qstring = query_string ? pstrdup(query_string) : NULL;
|
||||
stmt_list = (List *) copyObject(stmt_list);
|
||||
argtype_list = list_copy(argtype_list);
|
||||
/* Create a plancache entry */
|
||||
plansource = CreateCachedPlan(raw_parse_tree,
|
||||
query_string,
|
||||
commandTag,
|
||||
param_types,
|
||||
num_params,
|
||||
stmt_list,
|
||||
true,
|
||||
true);
|
||||
|
||||
/* Now we can add entry to hash table */
|
||||
entry = (PreparedStatement *) hash_search(prepared_queries,
|
||||
@ -358,22 +476,18 @@ StorePreparedStatement(const char *stmt_name,
|
||||
elog(ERROR, "duplicate prepared statement \"%s\"",
|
||||
stmt_name);
|
||||
|
||||
/* Fill in the hash table entry with copied data */
|
||||
entry->query_string = qstring;
|
||||
entry->commandTag = commandTag;
|
||||
entry->stmt_list = stmt_list;
|
||||
entry->argtype_list = argtype_list;
|
||||
entry->fully_planned = fully_planned;
|
||||
/* Fill in the hash table entry */
|
||||
entry->plansource = plansource;
|
||||
entry->from_sql = from_sql;
|
||||
entry->context = entrycxt;
|
||||
entry->prepare_time = GetCurrentStatementStartTimestamp();
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lookup an existing query in the hash table. If the query does not
|
||||
* actually exist, throw ereport(ERROR) or return NULL per second parameter.
|
||||
*
|
||||
* Note: this does not force the referenced plancache entry to be valid,
|
||||
* since not all callers care.
|
||||
*/
|
||||
PreparedStatement *
|
||||
FetchPreparedStatement(const char *stmt_name, bool throwError)
|
||||
@ -401,20 +515,6 @@ FetchPreparedStatement(const char *stmt_name, bool throwError)
|
||||
return entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up a prepared statement given the name (giving error if not found).
|
||||
* If found, return the list of argument type OIDs.
|
||||
*/
|
||||
List *
|
||||
FetchPreparedStatementParams(const char *stmt_name)
|
||||
{
|
||||
PreparedStatement *entry;
|
||||
|
||||
entry = FetchPreparedStatement(stmt_name, true);
|
||||
|
||||
return entry->argtype_list;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a prepared statement, determine the result tupledesc it will
|
||||
* produce. Returns NULL if the execution will not return tuples.
|
||||
@ -424,85 +524,15 @@ FetchPreparedStatementParams(const char *stmt_name)
|
||||
TupleDesc
|
||||
FetchPreparedStatementResultDesc(PreparedStatement *stmt)
|
||||
{
|
||||
Node *node;
|
||||
Query *query;
|
||||
PlannedStmt *pstmt;
|
||||
|
||||
switch (ChoosePortalStrategy(stmt->stmt_list))
|
||||
{
|
||||
case PORTAL_ONE_SELECT:
|
||||
node = (Node *) linitial(stmt->stmt_list);
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
query = (Query *) node;
|
||||
return ExecCleanTypeFromTL(query->targetList, false);
|
||||
}
|
||||
if (IsA(node, PlannedStmt))
|
||||
{
|
||||
pstmt = (PlannedStmt *) node;
|
||||
return ExecCleanTypeFromTL(pstmt->planTree->targetlist, false);
|
||||
}
|
||||
/* other cases shouldn't happen, but return NULL */
|
||||
break;
|
||||
|
||||
case PORTAL_ONE_RETURNING:
|
||||
node = PortalListGetPrimaryStmt(stmt->stmt_list);
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
query = (Query *) node;
|
||||
Assert(query->returningList);
|
||||
return ExecCleanTypeFromTL(query->returningList, false);
|
||||
}
|
||||
if (IsA(node, PlannedStmt))
|
||||
{
|
||||
pstmt = (PlannedStmt *) node;
|
||||
Assert(pstmt->returningLists);
|
||||
return ExecCleanTypeFromTL((List *) linitial(pstmt->returningLists), false);
|
||||
}
|
||||
/* other cases shouldn't happen, but return NULL */
|
||||
break;
|
||||
|
||||
case PORTAL_UTIL_SELECT:
|
||||
node = (Node *) linitial(stmt->stmt_list);
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
query = (Query *) node;
|
||||
Assert(query->utilityStmt);
|
||||
return UtilityTupleDescriptor(query->utilityStmt);
|
||||
}
|
||||
/* else it's a bare utility statement */
|
||||
return UtilityTupleDescriptor(node);
|
||||
|
||||
case PORTAL_MULTI_QUERY:
|
||||
/* will not return tuples */
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a prepared statement, determine whether it will return tuples.
|
||||
*
|
||||
* Note: this is used rather than just testing the result of
|
||||
* FetchPreparedStatementResultDesc() because that routine can fail if
|
||||
* invoked in an aborted transaction. This one is safe to use in any
|
||||
* context. Be sure to keep the two routines in sync!
|
||||
*/
|
||||
bool
|
||||
PreparedStatementReturnsTuples(PreparedStatement *stmt)
|
||||
{
|
||||
switch (ChoosePortalStrategy(stmt->stmt_list))
|
||||
{
|
||||
case PORTAL_ONE_SELECT:
|
||||
case PORTAL_ONE_RETURNING:
|
||||
case PORTAL_UTIL_SELECT:
|
||||
return true;
|
||||
|
||||
case PORTAL_MULTI_QUERY:
|
||||
/* will not return tuples */
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
/*
|
||||
* Since we don't allow prepared statements' result tupdescs to change,
|
||||
* there's no need for a revalidate call here.
|
||||
*/
|
||||
Assert(stmt->plansource->fixed_result);
|
||||
if (stmt->plansource->resultDesc)
|
||||
return CreateTupleDescCopy(stmt->plansource->resultDesc);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -510,16 +540,32 @@ PreparedStatementReturnsTuples(PreparedStatement *stmt)
|
||||
* targetlist. Returns NIL if the statement doesn't have a determinable
|
||||
* targetlist.
|
||||
*
|
||||
* Note: do not modify the result.
|
||||
* Note: this is pretty ugly, but since it's only used in corner cases like
|
||||
* Describe Statement on an EXECUTE command, we don't worry too much about
|
||||
* efficiency.
|
||||
*/
|
||||
List *
|
||||
FetchPreparedStatementTargetList(PreparedStatement *stmt)
|
||||
{
|
||||
/* no point in looking if it doesn't return tuples */
|
||||
if (ChoosePortalStrategy(stmt->stmt_list) == PORTAL_MULTI_QUERY)
|
||||
List *tlist;
|
||||
CachedPlan *cplan;
|
||||
|
||||
/* No point in looking if it doesn't return tuples */
|
||||
if (stmt->plansource->resultDesc == NULL)
|
||||
return NIL;
|
||||
/* get the primary statement and find out what it returns */
|
||||
return FetchStatementTargetList(PortalListGetPrimaryStmt(stmt->stmt_list));
|
||||
|
||||
/* Make sure the plan is up to date */
|
||||
cplan = RevalidateCachedPlan(stmt->plansource, true);
|
||||
|
||||
/* Get the primary statement and find out what it returns */
|
||||
tlist = FetchStatementTargetList(PortalListGetPrimaryStmt(cplan->stmt_list));
|
||||
|
||||
/* Copy into caller's context so we can release the plancache entry */
|
||||
tlist = (List *) copyObject(tlist);
|
||||
|
||||
ReleaseCachedPlan(cplan, true);
|
||||
|
||||
return tlist;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -547,12 +593,8 @@ DropPreparedStatement(const char *stmt_name, bool showError)
|
||||
|
||||
if (entry)
|
||||
{
|
||||
/* Drop any open portals that depend on this prepared statement */
|
||||
Assert(MemoryContextIsValid(entry->context));
|
||||
DropDependentPortals(entry->context);
|
||||
|
||||
/* Flush the context holding the subsidiary data */
|
||||
MemoryContextDelete(entry->context);
|
||||
/* Release the plancache entry */
|
||||
DropCachedPlan(entry->plansource);
|
||||
|
||||
/* Now we can remove the hash table entry */
|
||||
hash_search(prepared_queries, entry->stmt_name, HASH_REMOVE, NULL);
|
||||
@ -563,34 +605,34 @@ DropPreparedStatement(const char *stmt_name, bool showError)
|
||||
* Implements the 'EXPLAIN EXECUTE' utility statement.
|
||||
*/
|
||||
void
|
||||
ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
|
||||
TupOutputState *tstate)
|
||||
ExplainExecuteQuery(ExecuteStmt *execstmt, ExplainStmt *stmt,
|
||||
const char *queryString,
|
||||
ParamListInfo params, TupOutputState *tstate)
|
||||
{
|
||||
ExecuteStmt *execstmt = (ExecuteStmt *) stmt->query->utilityStmt;
|
||||
PreparedStatement *entry;
|
||||
CachedPlan *cplan;
|
||||
List *plan_list;
|
||||
ListCell *p;
|
||||
ParamListInfo paramLI = NULL;
|
||||
EState *estate = NULL;
|
||||
|
||||
/* explain.c should only call me for EXECUTE stmt */
|
||||
Assert(execstmt && IsA(execstmt, ExecuteStmt));
|
||||
|
||||
/* Look it up in the hash table */
|
||||
entry = FetchPreparedStatement(execstmt->name, true);
|
||||
|
||||
/*
|
||||
* Punt if not fully planned. (Currently, that only happens for the
|
||||
* protocol-level unnamed statement, which can't be accessed from SQL;
|
||||
* so there's no point in doing more than a quick check here.)
|
||||
*/
|
||||
if (!entry->fully_planned)
|
||||
/* Shouldn't have a non-fully-planned plancache entry */
|
||||
if (!entry->plansource->fully_planned)
|
||||
elog(ERROR, "EXPLAIN EXECUTE does not support unplanned prepared statements");
|
||||
/* Shouldn't get any non-fixed-result cached plan, either */
|
||||
if (!entry->plansource->fixed_result)
|
||||
elog(ERROR, "EXPLAIN EXECUTE does not support variable-result cached plans");
|
||||
|
||||
plan_list = entry->stmt_list;
|
||||
/* Replan if needed, and acquire a transient refcount */
|
||||
cplan = RevalidateCachedPlan(entry->plansource, true);
|
||||
|
||||
plan_list = cplan->stmt_list;
|
||||
|
||||
/* Evaluate parameters, if any */
|
||||
if (entry->argtype_list != NIL)
|
||||
if (entry->plansource->num_params)
|
||||
{
|
||||
/*
|
||||
* Need an EState to evaluate parameters; must not delete it till end
|
||||
@ -598,8 +640,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
|
||||
*/
|
||||
estate = CreateExecutorState();
|
||||
estate->es_param_list_info = params;
|
||||
paramLI = EvaluateParams(estate, execstmt->params,
|
||||
entry->argtype_list);
|
||||
paramLI = EvaluateParams(entry, execstmt->params,
|
||||
queryString, estate);
|
||||
}
|
||||
|
||||
/* Explain each query */
|
||||
@ -610,14 +652,7 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
|
||||
|
||||
is_last_query = (lnext(p) == NULL);
|
||||
|
||||
if (!IsA(pstmt, PlannedStmt))
|
||||
{
|
||||
if (IsA(pstmt, NotifyStmt))
|
||||
do_text_output_oneline(tstate, "NOTIFY");
|
||||
else
|
||||
do_text_output_oneline(tstate, "UTILITY");
|
||||
}
|
||||
else
|
||||
if (IsA(pstmt, PlannedStmt))
|
||||
{
|
||||
QueryDesc *qdesc;
|
||||
|
||||
@ -651,6 +686,11 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
|
||||
|
||||
ExplainOnePlan(qdesc, stmt, tstate);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplainOneUtility((Node *) pstmt, stmt, queryString,
|
||||
params, tstate);
|
||||
}
|
||||
|
||||
/* No need for CommandCounterIncrement, as ExplainOnePlan did it */
|
||||
|
||||
@ -661,6 +701,8 @@ ExplainExecuteQuery(ExplainStmt *stmt, ParamListInfo params,
|
||||
|
||||
if (estate)
|
||||
FreeExecutorState(estate);
|
||||
|
||||
ReleaseCachedPlan(cplan, true);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -739,14 +781,15 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
|
||||
values[0] = DirectFunctionCall1(textin,
|
||||
CStringGetDatum(prep_stmt->stmt_name));
|
||||
|
||||
if (prep_stmt->query_string == NULL)
|
||||
if (prep_stmt->plansource->query_string == NULL)
|
||||
nulls[1] = true;
|
||||
else
|
||||
values[1] = DirectFunctionCall1(textin,
|
||||
CStringGetDatum(prep_stmt->query_string));
|
||||
CStringGetDatum(prep_stmt->plansource->query_string));
|
||||
|
||||
values[2] = TimestampTzGetDatum(prep_stmt->prepare_time);
|
||||
values[3] = build_regtype_array(prep_stmt->argtype_list);
|
||||
values[3] = build_regtype_array(prep_stmt->plansource->param_types,
|
||||
prep_stmt->plansource->num_params);
|
||||
values[4] = BoolGetDatum(prep_stmt->from_sql);
|
||||
|
||||
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
|
||||
@ -758,29 +801,23 @@ pg_prepared_statement(PG_FUNCTION_ARGS)
|
||||
}
|
||||
|
||||
/*
|
||||
* This utility function takes a List of Oids, and returns a Datum
|
||||
* pointing to a one-dimensional Postgres array of regtypes. The empty
|
||||
* list is returned as a zero-element array, not NULL.
|
||||
* This utility function takes a C array of Oids, and returns a Datum
|
||||
* pointing to a one-dimensional Postgres array of regtypes. An empty
|
||||
* array is returned as a zero-element array, not NULL.
|
||||
*/
|
||||
static Datum
|
||||
build_regtype_array(List *oid_list)
|
||||
build_regtype_array(Oid *param_types, int num_params)
|
||||
{
|
||||
ListCell *lc;
|
||||
int len;
|
||||
int i;
|
||||
Datum *tmp_ary;
|
||||
ArrayType *result;
|
||||
int i;
|
||||
|
||||
len = list_length(oid_list);
|
||||
tmp_ary = (Datum *) palloc(len * sizeof(Datum));
|
||||
tmp_ary = (Datum *) palloc(num_params * sizeof(Datum));
|
||||
|
||||
i = 0;
|
||||
foreach(lc, oid_list)
|
||||
{
|
||||
tmp_ary[i++] = ObjectIdGetDatum(lfirst_oid(lc));
|
||||
}
|
||||
for (i = 0; i < num_params; i++)
|
||||
tmp_ary[i] = ObjectIdGetDatum(param_types[i]);
|
||||
|
||||
/* XXX: this hardcodes assumptions about the regtype type */
|
||||
result = construct_array(tmp_ary, len, REGTYPEOID, 4, true, 'i');
|
||||
result = construct_array(tmp_ary, num_params, REGTYPEOID, 4, true, 'i');
|
||||
return PointerGetDatum(result);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -8,7 +8,7 @@
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.8 2007/01/05 22:19:30 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/nodes/params.c,v 1.9 2007/03/13 00:33:41 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -60,3 +60,34 @@ copyParamList(ParamListInfo from)
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extract an array of parameter type OIDs from a ParamListInfo.
|
||||
*
|
||||
* The result is allocated in CurrentMemoryContext.
|
||||
*/
|
||||
void
|
||||
getParamListTypes(ParamListInfo params,
|
||||
Oid **param_types, int *num_params)
|
||||
{
|
||||
Oid *ptypes;
|
||||
int i;
|
||||
|
||||
if (params == NULL || params->numParams <= 0)
|
||||
{
|
||||
*param_types = NULL;
|
||||
*num_params = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ptypes = (Oid *) palloc(params->numParams * sizeof(Oid));
|
||||
*param_types = ptypes;
|
||||
*num_params = params->numParams;
|
||||
|
||||
for (i = 0; i < params->numParams; i++)
|
||||
{
|
||||
ParamExternData *prm = ¶ms->params[i];
|
||||
|
||||
ptypes[i] = prm->ptype;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
;
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -8,7 +8,7 @@
|
||||
*
|
||||
*
|
||||
* IDENTIFICATION
|
||||
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.527 2007/03/03 19:32:54 neilc Exp $
|
||||
* $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.528 2007/03/13 00:33:42 tgl Exp $
|
||||
*
|
||||
* NOTES
|
||||
* this is the "main" module of the postgres backend and
|
||||
@ -140,8 +140,9 @@ static bool ignore_till_sync = false;
|
||||
* We keep it separate from the hashtable kept by commands/prepare.c
|
||||
* in order to reduce overhead for short-lived queries.
|
||||
*/
|
||||
static CachedPlanSource *unnamed_stmt_psrc = NULL;
|
||||
/* workspace for building a new unnamed statement in */
|
||||
static MemoryContext unnamed_stmt_context = NULL;
|
||||
static PreparedStatement *unnamed_stmt_pstmt = NULL;
|
||||
|
||||
|
||||
static bool EchoQuery = false; /* default don't echo */
|
||||
@ -173,6 +174,7 @@ static void finish_xact_command(void);
|
||||
static bool IsTransactionExitStmt(Node *parsetree);
|
||||
static bool IsTransactionExitStmtList(List *parseTrees);
|
||||
static bool IsTransactionStmtList(List *parseTrees);
|
||||
static void drop_unnamed_stmt(void);
|
||||
static void SigHupHandler(SIGNAL_ARGS);
|
||||
static void log_disconnections(int code, Datum arg);
|
||||
|
||||
@ -794,21 +796,13 @@ exec_simple_query(const char *query_string)
|
||||
* statement and portal; this ensures we recover any storage used by prior
|
||||
* unnamed operations.)
|
||||
*/
|
||||
unnamed_stmt_pstmt = NULL;
|
||||
if (unnamed_stmt_context)
|
||||
{
|
||||
DropDependentPortals(unnamed_stmt_context);
|
||||
MemoryContextDelete(unnamed_stmt_context);
|
||||
}
|
||||
unnamed_stmt_context = NULL;
|
||||
drop_unnamed_stmt();
|
||||
|
||||
/*
|
||||
* Switch to appropriate context for constructing parsetrees.
|
||||
*/
|
||||
oldcontext = MemoryContextSwitchTo(MessageContext);
|
||||
|
||||
QueryContext = CurrentMemoryContext;
|
||||
|
||||
/*
|
||||
* Do basic parsing of the query or queries (this should be safe even if
|
||||
* we are in aborted transaction state!)
|
||||
@ -906,7 +900,7 @@ exec_simple_query(const char *query_string)
|
||||
query_string,
|
||||
commandTag,
|
||||
plantree_list,
|
||||
MessageContext);
|
||||
NULL);
|
||||
|
||||
/*
|
||||
* Start the portal. No parameters here.
|
||||
@ -950,6 +944,7 @@ exec_simple_query(const char *query_string)
|
||||
*/
|
||||
(void) PortalRun(portal,
|
||||
FETCH_ALL,
|
||||
true, /* top level */
|
||||
receiver,
|
||||
receiver,
|
||||
completionTag);
|
||||
@ -1009,8 +1004,6 @@ exec_simple_query(const char *query_string)
|
||||
if (!parsetree_list)
|
||||
NullCommand(dest);
|
||||
|
||||
QueryContext = NULL;
|
||||
|
||||
/*
|
||||
* Emit duration logging if appropriate.
|
||||
*/
|
||||
@ -1049,10 +1042,10 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
{
|
||||
MemoryContext oldcontext;
|
||||
List *parsetree_list;
|
||||
Node *raw_parse_tree;
|
||||
const char *commandTag;
|
||||
List *querytree_list,
|
||||
*stmt_list,
|
||||
*param_list;
|
||||
*stmt_list;
|
||||
bool is_named;
|
||||
bool fully_planned;
|
||||
bool save_log_statement_stats = log_statement_stats;
|
||||
@ -1088,12 +1081,12 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
* We have two strategies depending on whether the prepared statement is
|
||||
* named or not. For a named prepared statement, we do parsing in
|
||||
* MessageContext and copy the finished trees into the prepared
|
||||
* statement's private context; then the reset of MessageContext releases
|
||||
* statement's plancache entry; then the reset of MessageContext releases
|
||||
* temporary space used by parsing and planning. For an unnamed prepared
|
||||
* statement, we assume the statement isn't going to hang around long, so
|
||||
* getting rid of temp space quickly is probably not worth the costs of
|
||||
* copying parse/plan trees. So in this case, we set up a special context
|
||||
* for the unnamed statement, and do all the parsing work therein.
|
||||
* copying parse/plan trees. So in this case, we create the plancache
|
||||
* entry's context here, and do all the parsing work therein.
|
||||
*/
|
||||
is_named = (stmt_name[0] != '\0');
|
||||
if (is_named)
|
||||
@ -1104,16 +1097,10 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
else
|
||||
{
|
||||
/* Unnamed prepared statement --- release any prior unnamed stmt */
|
||||
unnamed_stmt_pstmt = NULL;
|
||||
if (unnamed_stmt_context)
|
||||
{
|
||||
DropDependentPortals(unnamed_stmt_context);
|
||||
MemoryContextDelete(unnamed_stmt_context);
|
||||
}
|
||||
unnamed_stmt_context = NULL;
|
||||
/* create context for parsing/planning */
|
||||
drop_unnamed_stmt();
|
||||
/* Create context for parsing/planning */
|
||||
unnamed_stmt_context =
|
||||
AllocSetContextCreate(TopMemoryContext,
|
||||
AllocSetContextCreate(CacheMemoryContext,
|
||||
"unnamed prepared statement",
|
||||
ALLOCSET_DEFAULT_MINSIZE,
|
||||
ALLOCSET_DEFAULT_INITSIZE,
|
||||
@ -1121,8 +1108,6 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
oldcontext = MemoryContextSwitchTo(unnamed_stmt_context);
|
||||
}
|
||||
|
||||
QueryContext = CurrentMemoryContext;
|
||||
|
||||
/*
|
||||
* Do basic parsing of the query or queries (this should be safe even if
|
||||
* we are in aborted transaction state!)
|
||||
@ -1141,13 +1126,14 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
|
||||
if (parsetree_list != NIL)
|
||||
{
|
||||
Node *parsetree = (Node *) linitial(parsetree_list);
|
||||
int i;
|
||||
|
||||
raw_parse_tree = (Node *) linitial(parsetree_list);
|
||||
|
||||
/*
|
||||
* Get the command name for possible use in status display.
|
||||
*/
|
||||
commandTag = CreateCommandTag(parsetree);
|
||||
commandTag = CreateCommandTag(raw_parse_tree);
|
||||
|
||||
/*
|
||||
* If we are in an aborted transaction, reject all commands except
|
||||
@ -1158,7 +1144,7 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
* state, but not many...)
|
||||
*/
|
||||
if (IsAbortedTransactionBlockState() &&
|
||||
!IsTransactionExitStmt(parsetree))
|
||||
!IsTransactionExitStmt(raw_parse_tree))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
|
||||
errmsg("current transaction is aborted, "
|
||||
@ -1168,20 +1154,22 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
* OK to analyze, rewrite, and plan this query. Note that the
|
||||
* originally specified parameter set is not required to be complete,
|
||||
* so we have to use parse_analyze_varparams().
|
||||
*
|
||||
* XXX must use copyObject here since parse analysis scribbles on
|
||||
* its input, and we need the unmodified raw parse tree for possible
|
||||
* replanning later.
|
||||
*/
|
||||
if (log_parser_stats)
|
||||
ResetUsage();
|
||||
|
||||
querytree_list = parse_analyze_varparams(parsetree,
|
||||
querytree_list = parse_analyze_varparams(copyObject(raw_parse_tree),
|
||||
query_string,
|
||||
¶mTypes,
|
||||
&numParams);
|
||||
|
||||
/*
|
||||
* Check all parameter types got determined, and convert array
|
||||
* representation to a list for storage.
|
||||
* Check all parameter types got determined.
|
||||
*/
|
||||
param_list = NIL;
|
||||
for (i = 0; i < numParams; i++)
|
||||
{
|
||||
Oid ptype = paramTypes[i];
|
||||
@ -1191,7 +1179,6 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
(errcode(ERRCODE_INDETERMINATE_DATATYPE),
|
||||
errmsg("could not determine data type of parameter $%d",
|
||||
i + 1)));
|
||||
param_list = lappend_oid(param_list, ptype);
|
||||
}
|
||||
|
||||
if (log_parser_stats)
|
||||
@ -1217,9 +1204,9 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
else
|
||||
{
|
||||
/* Empty input string. This is legal. */
|
||||
raw_parse_tree = NULL;
|
||||
commandTag = NULL;
|
||||
stmt_list = NIL;
|
||||
param_list = NIL;
|
||||
fully_planned = true;
|
||||
}
|
||||
|
||||
@ -1232,35 +1219,33 @@ exec_parse_message(const char *query_string, /* string to execute */
|
||||
if (is_named)
|
||||
{
|
||||
StorePreparedStatement(stmt_name,
|
||||
raw_parse_tree,
|
||||
query_string,
|
||||
commandTag,
|
||||
paramTypes,
|
||||
numParams,
|
||||
stmt_list,
|
||||
param_list,
|
||||
fully_planned,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
PreparedStatement *pstmt;
|
||||
|
||||
pstmt = (PreparedStatement *) palloc0(sizeof(PreparedStatement));
|
||||
/* query_string needs to be copied into unnamed_stmt_context */
|
||||
pstmt->query_string = pstrdup(query_string);
|
||||
/* the rest is there already */
|
||||
pstmt->commandTag = commandTag;
|
||||
pstmt->stmt_list = stmt_list;
|
||||
pstmt->argtype_list = param_list;
|
||||
pstmt->fully_planned = fully_planned;
|
||||
pstmt->from_sql = false;
|
||||
pstmt->context = unnamed_stmt_context;
|
||||
/* Now the unnamed statement is complete and valid */
|
||||
unnamed_stmt_pstmt = pstmt;
|
||||
unnamed_stmt_psrc = FastCreateCachedPlan(raw_parse_tree,
|
||||
pstrdup(query_string),
|
||||
commandTag,
|
||||
paramTypes,
|
||||
numParams,
|
||||
stmt_list,
|
||||
fully_planned,
|
||||
true,
|
||||
unnamed_stmt_context);
|
||||
/* context now belongs to the plancache entry */
|
||||
unnamed_stmt_context = NULL;
|
||||
}
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
QueryContext = NULL;
|
||||
|
||||
/*
|
||||
* We do NOT close the open transaction command here; that only happens
|
||||
* when the client sends Sync. Instead, do CommandCounterIncrement just
|
||||
@ -1315,12 +1300,11 @@ exec_bind_message(StringInfo input_message)
|
||||
int numParams;
|
||||
int numRFormats;
|
||||
int16 *rformats = NULL;
|
||||
PreparedStatement *pstmt;
|
||||
CachedPlanSource *psrc;
|
||||
CachedPlan *cplan;
|
||||
Portal portal;
|
||||
ParamListInfo params;
|
||||
List *query_list;
|
||||
List *plan_list;
|
||||
MemoryContext qContext;
|
||||
bool save_log_statement_stats = log_statement_stats;
|
||||
char msec_str[32];
|
||||
|
||||
@ -1335,12 +1319,17 @@ exec_bind_message(StringInfo input_message)
|
||||
|
||||
/* Find prepared statement */
|
||||
if (stmt_name[0] != '\0')
|
||||
{
|
||||
PreparedStatement *pstmt;
|
||||
|
||||
pstmt = FetchPreparedStatement(stmt_name, true);
|
||||
psrc = pstmt->plansource;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* special-case the unnamed statement */
|
||||
pstmt = unnamed_stmt_pstmt;
|
||||
if (!pstmt)
|
||||
psrc = unnamed_stmt_psrc;
|
||||
if (!psrc)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_PSTATEMENT),
|
||||
errmsg("unnamed prepared statement does not exist")));
|
||||
@ -1349,7 +1338,7 @@ exec_bind_message(StringInfo input_message)
|
||||
/*
|
||||
* Report query to various monitoring facilities.
|
||||
*/
|
||||
debug_query_string = pstmt->query_string ? pstmt->query_string : "<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':
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
5
src/backend/utils/cache/Makefile
vendored
5
src/backend/utils/cache/Makefile
vendored
@ -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
862
src/backend/utils/cache/plancache.c
vendored
Normal 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;
|
||||
}
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
105
src/include/utils/plancache.h
Normal file
105
src/include/utils/plancache.h
Normal 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 */
|
@ -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);
|
||||
|
||||
|
@ -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,
|
||||
|
102
src/test/regress/expected/plancache.out
Normal file
102
src/test/regress/expected/plancache.out
Normal 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)
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
53
src/test/regress/sql/plancache.sql
Normal file
53
src/test/regress/sql/plancache.sql
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user