* Portions Copyright (c) 1994-5, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.135 2005/04/25 01:30:12 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/explain.c,v 1.136 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
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);
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");
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.41 2005/04/28 21:47:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/portalcmds.c,v 1.42 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* query, so we are not expecting rule rewriting to do anything
* strange.
*/
+ AcquireRewriteLocks(query);
rewritten = QueryRewrite(query);
if (list_length(rewritten) != 1 || !IsA(linitial(rewritten), Query))
elog(ERROR, "unexpected rewrite result");
* Copyright (c) 2002-2005, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.38 2005/05/29 04:23:03 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/prepare.c,v 1.39 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
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. */
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.63 2004/12/31 22:00:23 pgsql Exp $
+ * $PostgreSQL: pgsql/src/backend/optimizer/util/var.c,v 1.64 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
newvar = (Node *) lfirst(l);
attnum++;
/* Ignore dropped columns */
- if (get_rte_attribute_is_dropped(context->root->rtable,
- var->varno,
- attnum))
+ if (IsA(newvar, Const))
continue;
/*
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.108 2005/05/29 17:10:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/parse_relation.c,v 1.109 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* results. If include_dropped is TRUE then empty strings and NULL constants
* (not Vars!) are returned for dropped columns.
*
- * The target RTE is the rtindex'th entry of rtable. (The whole rangetable
- * must be passed since we need it to determine dropped-ness for JOIN columns.)
+ * The target RTE is the rtindex'th entry of rtable.
* sublevels_up is the varlevelsup value to use in the created Vars.
*
* The output lists go into *colnames and *colvars.
varattno = 0;
forboth(colname, rte->eref->colnames, aliasvar, rte->joinaliasvars)
{
+ Node *avar = (Node *) lfirst(aliasvar);
+
varattno++;
/*
* deleted columns in the join; but we have to check
* since this routine is also used by the rewriter,
* and joins found in stored rules might have join
- * columns for since-deleted columns.
+ * columns for since-deleted columns. This will be
+ * signaled by a NULL Const in the alias-vars list.
*/
- if (get_rte_attribute_is_dropped(rtable, rtindex,
- varattno))
+ if (IsA(avar, Const))
{
if (include_dropped)
{
if (colnames)
*colnames = lappend(*colnames,
- makeString(pstrdup("")));
+ makeString(pstrdup("")));
if (colvars)
- {
- /*
- * can't use atttypid here, but it doesn't
- * really matter what type the Const
- * claims to be.
- */
*colvars = lappend(*colvars,
- makeNullConst(INT4OID));
- }
+ copyObject(avar));
}
continue;
}
if (colvars)
{
- Node *avar = (Node *) lfirst(aliasvar);
Var *varnode;
varnode = makeVar(rtindex, varattno,
* Check whether attempted attribute ref is to a dropped column
*/
bool
-get_rte_attribute_is_dropped(List *rtable, int rtindex, AttrNumber attnum)
+get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
{
- RangeTblEntry *rte = rt_fetch(rtindex, rtable);
bool result;
switch (rte->rtekind)
* constructed, but one in a stored rule might contain
* columns that were dropped from the underlying tables,
* if said columns are nowhere explicitly referenced in
- * the rule. So we have to recursively look at the
- * referenced column.
+ * the rule. This will be signaled to us by a NULL Const
+ * in the joinaliasvars list.
*/
Var *aliasvar;
elog(ERROR, "invalid varattno %d", attnum);
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
- /*
- * If the list item isn't a simple Var, then it must
- * represent a merged column, ie a USING column, and so it
- * couldn't possibly be dropped (since it's referenced in
- * the join clause).
- */
- if (!IsA(aliasvar, Var))
- result = false;
- else
- result = get_rte_attribute_is_dropped(rtable,
- aliasvar->varno,
- aliasvar->varattno);
+ result = IsA(aliasvar, Const);
}
break;
case RTE_FUNCTION:
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.152 2005/05/29 18:34:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.153 2005/06/03 23:05:28 tgl Exp $
*
*-------------------------------------------------------------------------
*/
CmdType event; /* type of rule being fired */
} rewrite_event;
+static bool acquireLocksOnSubLinks(Node *node, void *context);
static Query *rewriteRuleAction(Query *parsetree,
Query *rule_action,
Node *rule_qual,
static Query *fireRIRrules(Query *parsetree, List *activeRIRs);
+/*
+ * AcquireRewriteLocks -
+ * Acquire suitable locks on all the relations mentioned in the Query.
+ * These locks will ensure that the relation schemas don't change under us
+ * while we are rewriting and planning the query.
+ *
+ * A secondary purpose of this routine is to fix up JOIN RTE references to
+ * dropped columns (see details below). Because the RTEs are modified in
+ * place, it is generally appropriate for the caller of this routine to have
+ * first done a copyObject() to make a writable copy of the querytree in the
+ * current memory context.
+ *
+ * This processing can, and for efficiency's sake should, be skipped when the
+ * querytree has just been built by the parser: parse analysis already got
+ * all the same locks we'd get here, and the parser will have omitted dropped
+ * columns from JOINs to begin with. But we must do this whenever we are
+ * dealing with a querytree produced earlier than the current command.
+ *
+ * About JOINs and dropped columns: although the parser never includes an
+ * already-dropped column in a JOIN RTE's alias var list, it is possible for
+ * such a list in a stored rule to include references to dropped columns.
+ * (If the column is not explicitly referenced anywhere else in the query,
+ * the dependency mechanism won't consider it used by the rule and so won't
+ * prevent the column drop.) To support get_rte_attribute_is_dropped(),
+ * we replace join alias vars that reference dropped columns with NULL Const
+ * nodes.
+ *
+ * (In PostgreSQL 8.0, we did not do this processing but instead had
+ * get_rte_attribute_is_dropped() recurse to detect dropped columns in joins.
+ * That approach had horrible performance unfortunately; in particular
+ * construction of a nested join was O(N^2) in the nesting depth.)
+ */
+void
+AcquireRewriteLocks(Query *parsetree)
+{
+ ListCell *l;
+ int rt_index;
+
+ /*
+ * First, process RTEs of the current query level.
+ */
+ rt_index = 0;
+ foreach(l, parsetree->rtable)
+ {
+ RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+ Relation rel;
+ LOCKMODE lockmode;
+ List *newaliasvars;
+ ListCell *ll;
+
+ ++rt_index;
+ switch (rte->rtekind)
+ {
+ case RTE_RELATION:
+ /*
+ * Grab the appropriate lock type for the relation, and
+ * do not release it until end of transaction. This protects
+ * the rewriter and planner against schema changes mid-query.
+ *
+ * If the relation is the query's result relation, then we
+ * need RowExclusiveLock. Otherwise, check to see if the
+ * relation is accessed FOR UPDATE/SHARE or not. We can't
+ * just grab AccessShareLock because then the executor
+ * would be trying to upgrade the lock, leading to possible
+ * deadlocks.
+ */
+ if (rt_index == parsetree->resultRelation)
+ lockmode = RowExclusiveLock;
+ else if (list_member_int(parsetree->rowMarks, rt_index))
+ lockmode = RowShareLock;
+ else
+ lockmode = AccessShareLock;
+
+ rel = heap_open(rte->relid, lockmode);
+ heap_close(rel, NoLock);
+ break;
+
+ case RTE_JOIN:
+ /*
+ * Scan the join's alias var list to see if any columns
+ * have been dropped, and if so replace those Vars with
+ * NULL Consts.
+ */
+ newaliasvars = NIL;
+ foreach(ll, rte->joinaliasvars)
+ {
+ Var *aliasvar = (Var *) lfirst(ll);
+
+ /*
+ * If the list item isn't a simple Var, then it must
+ * represent a merged column, ie a USING column, and so it
+ * couldn't possibly be dropped, since it's referenced in
+ * the join clause. (Conceivably it could also be a
+ * NULL constant already? But that's OK too.)
+ */
+ if (IsA(aliasvar, Var))
+ {
+ /*
+ * The elements of an alias list have to refer to
+ * earlier RTEs of the same rtable, because that's
+ * the order the planner builds things in. So we
+ * already processed the referenced RTE, and so it's
+ * safe to use get_rte_attribute_is_dropped on it.
+ * (This might not hold after rewriting or planning,
+ * but it's OK to assume here.)
+ */
+ Assert(aliasvar->varlevelsup == 0);
+ if (aliasvar->varno >= rt_index)
+ elog(ERROR, "unexpected varno %d in JOIN RTE %d",
+ aliasvar->varno, rt_index);
+ if (get_rte_attribute_is_dropped(
+ rt_fetch(aliasvar->varno, parsetree->rtable),
+ aliasvar->varattno))
+ {
+ /*
+ * can't use vartype here, since that might be a
+ * now-dropped type OID, but it doesn't really
+ * matter what type the Const claims to be.
+ */
+ aliasvar = (Var *) makeNullConst(INT4OID);
+ }
+ }
+ newaliasvars = lappend(newaliasvars, aliasvar);
+ }
+ rte->joinaliasvars = newaliasvars;
+ break;
+
+ case RTE_SUBQUERY:
+ /*
+ * The subquery RTE itself is all right, but we have to
+ * recurse to process the represented subquery.
+ */
+ AcquireRewriteLocks(rte->subquery);
+ 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)
+ query_tree_walker(parsetree, acquireLocksOnSubLinks, NULL,
+ QTW_IGNORE_RT_SUBQUERIES);
+}
+
+/*
+ * Walker to find sublink subqueries for AcquireRewriteLocks
+ */
+static bool
+acquireLocksOnSubLinks(Node *node, void *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, SubLink))
+ {
+ SubLink *sub = (SubLink *) node;
+
+ /* Do what we came for */
+ AcquireRewriteLocks((Query *) sub->subselect);
+ /* Fall through to process lefthand args of SubLink */
+ }
+
+ /*
+ * Do NOT recurse into Query nodes, because AcquireRewriteLocks already
+ * processed subselects of subselects for us.
+ */
+ return expression_tree_walker(node, acquireLocksOnSubLinks, context);
+}
+
+
/*
* rewriteRuleAction -
* Rewrite the rule action with appropriate qualifiers (taken from
rule_action = (Query *) copyObject(rule_action);
rule_qual = (Node *) copyObject(rule_qual);
+ /*
+ * Acquire necessary locks and fix any deleted JOIN RTE entries.
+ */
+ AcquireRewriteLocks(rule_action);
+ (void) acquireLocksOnSubLinks(rule_qual, NULL);
+
current_varno = rt_index;
rt_length = list_length(parsetree->rtable);
new_varno = PRS2_NEW_VARNO + rt_length;
}
+/*
+ * ApplyRetrieveRule - expand an ON SELECT rule
+ */
static Query *
ApplyRetrieveRule(Query *parsetree,
RewriteRule *rule,
elog(ERROR, "cannot handle per-attribute ON SELECT rule");
/*
- * Make a modifiable copy of the view query, and recursively expand
- * any view references inside it.
+ * Make a modifiable copy of the view query, and acquire needed locks
+ * on the relations it mentions.
*/
rule_action = copyObject(linitial(rule->actions));
+ AcquireRewriteLocks(rule_action);
+
+ /*
+ * Recursively expand any view references inside the view.
+ */
rule_action = fireRIRrules(rule_action, activeRIRs);
/*
List *locks;
RuleLock *rules;
RewriteRule *rule;
- LOCKMODE lockmode;
int i;
++rt_index;
continue;
/*
- * This may well be the first access to the relation during the
- * current statement (it will be, if this Query was extracted from
- * a rule or somehow got here other than via the parser).
- * Therefore, grab the appropriate lock type for the relation, and
- * do not release it until end of transaction. This protects the
- * rewriter and planner against schema changes mid-query.
- *
- * If the relation is the query's result relation, then
- * RewriteQuery() already got the right lock on it, so we need no
- * additional lock. Otherwise, check to see if the relation is
- * accessed FOR UPDATE/SHARE or not.
+ * We can use NoLock here since either the parser or
+ * AcquireRewriteLocks should have locked the rel already.
*/
- if (rt_index == parsetree->resultRelation)
- lockmode = NoLock;
- else if (list_member_int(parsetree->rowMarks, rt_index))
- lockmode = RowShareLock;
- else
- lockmode = AccessShareLock;
-
- rel = heap_open(rte->relid, lockmode);
+ rel = heap_open(rte->relid, NoLock);
/*
* Collect the RIR rules that we must apply
int rt_index,
CmdType event)
{
- Query *new_tree = (Query *) copyObject(parsetree);
+ /* Don't scribble on the passed qual (it's in the relcache!) */
Node *new_qual = (Node *) copyObject(rule_qual);
+ /*
+ * In case there are subqueries in the qual, acquire necessary locks and
+ * fix any deleted JOIN RTE entries. (This is somewhat redundant with
+ * rewriteRuleAction, but not entirely ... consider restructuring so
+ * that we only need to process the qual this way once.)
+ */
+ (void) acquireLocksOnSubLinks(new_qual, NULL);
+
/* Fix references to OLD */
ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
/* Fix references to NEW */
event,
rt_index);
/* And attach the fixed qual */
- AddInvertedQual(new_tree, new_qual);
+ AddInvertedQual(parsetree, new_qual);
- return new_tree;
+ return parsetree;
}
if (!*instead_flag)
{
if (*qual_product == NULL)
- *qual_product = parsetree;
+ *qual_product = copyObject(parsetree);
*qual_product = CopyAndAddInvertedQual(*qual_product,
event_qual,
rt_index,
Assert(rt_entry->rtekind == RTE_RELATION);
/*
- * This may well be the first access to the result relation during
- * the current statement (it will be, if this Query was extracted
- * from a rule or somehow got here other than via the parser).
- * Therefore, grab the appropriate lock type for a result
- * relation, and do not release it until end of transaction. This
- * protects the rewriter and planner against schema changes
- * mid-query.
+ * We can use NoLock here since either the parser or
+ * AcquireRewriteLocks should have locked the rel already.
*/
- rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock);
+ rt_entry_relation = heap_open(rt_entry->relid, NoLock);
/*
* If it's an INSERT or UPDATE, rewrite the targetlist into
}
}
- heap_close(rt_entry_relation, NoLock); /* keep lock! */
+ heap_close(rt_entry_relation, NoLock);
}
/*
* Rewrite one query via query rewrite system, possibly returning 0
* or many queries.
*
- * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was
- * moved here so that it would be invoked during EXPLAIN.
+ * NOTE: the parsetree must either have come straight from the parser,
+ * or have been scanned by AcquireRewriteLocks to acquire suitable locks.
*/
List *
QueryRewrite(Query *parsetree)
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.446 2005/06/02 21:03:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.447 2005/06/03 23:05:29 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
static int ReadCommand(StringInfo inBuf);
static bool log_after_parse(List *raw_parsetree_list,
const char *query_string, char **prepare_string);
+static List *pg_rewrite_queries(List *querytree_list);
static void start_xact_command(void);
static void finish_xact_command(void);
static void SigHupHandler(SIGNAL_ARGS);
/*
* Perform rewriting of a list of queries produced by parse analysis.
+ *
+ * Note: queries must just have come from the parser, because we do not do
+ * AcquireRewriteLocks() on them.
*/
-List *
+static List *
pg_rewrite_queries(List *querytree_list)
{
List *new_list = NIL;
* back to source text
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.198 2005/05/31 03:03:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.199 2005/06/03 23:05:29 tgl Exp $
*
* This software is copyrighted by Jan Wieck - Hamburg.
*
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "parser/parsetree.h"
+#include "rewrite/rewriteHandler.h"
#include "rewrite/rewriteManip.h"
#include "rewrite/rewriteSupport.h"
#include "utils/array.h"
*/
query = getInsertSelectQuery(query, NULL);
+ /* Must acquire locks right away; see notes in get_query_def() */
+ AcquireRewriteLocks(query);
+
context.buf = buf;
context.namespaces = list_make1(&dpns);
context.varprefix = (list_length(query->rtable) != 1);
deparse_context context;
deparse_namespace dpns;
+ /*
+ * Before we begin to examine the query, acquire locks on referenced
+ * relations, and fix up deleted columns in JOIN RTEs. This ensures
+ * consistent results. Note we assume it's OK to scribble on the
+ * passed querytree!
+ */
+ AcquireRewriteLocks(query);
+
context.buf = buf;
context.namespaces = lcons(&dpns, list_copy(parentnamespace));
context.varprefix = (parentnamespace != NIL ||
Query *query, deparse_context *context)
{
StringInfo buf = context->buf;
+ RangeTblEntry *rte = rt_fetch(varno, query->rtable);
ListCell *col;
AttrNumber attnum;
bool first = true;
foreach(col, alias->colnames)
{
attnum++;
- if (get_rte_attribute_is_dropped(query->rtable, varno, attnum))
+ if (get_rte_attribute_is_dropped(rte, attnum))
continue;
if (first)
{
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.278 2005/04/28 21:47:17 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.279 2005/06/03 23:05:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* those columns are known to be dropped at parse time. Again, however,
* a stored rule might contain entries for columns dropped since the rule
* was created. (This is only possible for columns not actually referenced
- * in the rule.)
+ * in the rule.) When loading a stored rule, we replace the joinaliasvars
+ * items for any such columns with NULL Consts. (We can't simply delete
+ * them from the joinaliasvars list, because that would affect the attnums
+ * of Vars referencing the rest of the list.)
*
* inh is TRUE for relation references that should be expanded to include
* inheritance children, if the rel has any. This *must* be FALSE for
* to the columns of the join result. An alias Var referencing column
* K of the join result can be replaced by the K'th element of
* joinaliasvars --- but to simplify the task of reverse-listing
- * aliases correctly, we do not do that until planning time.
+ * aliases correctly, we do not do that until planning time. In a Query
+ * loaded from a stored rule, it is also possible for joinaliasvars
+ * items to be NULL Consts, denoting columns dropped since the rule was
+ * made.
*/
JoinType jointype; /* type of join */
List *joinaliasvars; /* list of alias-var expansions */
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.29 2004/12/31 22:03:38 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/parser/parsetree.h,v 1.30 2005/06/03 23:05:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
* Check whether an attribute of an RTE has been dropped (note that
* get_rte_attribute_type will fail on such an attr)
*/
-extern bool get_rte_attribute_is_dropped(List *rtable, int rtindex,
- AttrNumber attnum);
+extern bool get_rte_attribute_is_dropped(RangeTblEntry *rte,
+ AttrNumber attnum);
/* ----------------
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteHandler.h,v 1.24 2004/12/31 22:03:41 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteHandler.h,v 1.25 2005/06/03 23:05:30 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "nodes/parsenodes.h"
extern List *QueryRewrite(Query *parsetree);
+extern void AcquireRewriteLocks(Query *parsetree);
extern Node *build_column_default(Relation rel, int attrno);
#endif /* REWRITEHANDLER_H */
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.74 2005/06/02 21:03:25 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/tcopprot.h,v 1.75 2005/06/03 23:05:30 tgl Exp $
*
* OLD COMMENTS
* This file was created so that other c files could get the two
extern List *pg_parse_query(const char *query_string);
extern List *pg_analyze_and_rewrite(Node *parsetree,
Oid *paramTypes, int numParams);
-extern List *pg_rewrite_queries(List *querytree_list);
extern Plan *pg_plan_query(Query *querytree, ParamListInfo boundParams);
extern List *pg_plan_queries(List *querytrees, ParamListInfo boundParams,
bool needSnapshot);