* 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:
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)