* Portions Copyright (c) 1996-2000, PostgreSQL, Inc
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $Id: analyze.c,v 1.168 2000/11/24 20:16:39 petere Exp $
+ * $Id: analyze.c,v 1.169 2000/12/05 19:15:10 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
if (stmt->selectStmt)
{
- List *selectList;
+ ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
Query *selectQuery;
RangeTblEntry *rte;
RangeTblRef *rtr;
* otherwise the behavior of SELECT within INSERT might be different
* from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
* bugs of just that nature...)
+ *
+ * If a non-nil rangetable was passed in, pass it down to the SELECT.
+ * This can only happen if we are inside a CREATE RULE, and in that
+ * case we want the rule's OLD and NEW rtable entries to appear as
+ * part of the SELECT's rtable, not as outer references for it.
*/
- selectList = parse_analyze(stmt->selectStmt, pstate);
- Assert(length(selectList) == 1);
+ sub_pstate->p_rtable = pstate->p_rtable;
+ pstate->p_rtable = NIL;
+ selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
+ release_pstate_resources(sub_pstate);
+ pfree(sub_pstate);
- selectQuery = (Query *) lfirst(selectList);
Assert(IsA(selectQuery, Query));
Assert(selectQuery->commandType == CMD_SELECT);
if (selectQuery->into || selectQuery->isPortal)
foreach(actions, stmt->actions)
{
ParseState *sub_pstate = make_parsestate(pstate->parentParseState);
- Query *sub_qry;
+ Query *sub_qry,
+ *top_subqry;
bool has_old,
has_new;
newrte->checkForRead = false;
/* Transform the rule action statement */
- sub_qry = transformStmt(sub_pstate, lfirst(actions));
+ top_subqry = transformStmt(sub_pstate, lfirst(actions));
+
+ /*
+ * If the action is INSERT...SELECT, OLD/NEW have been pushed
+ * down into the SELECT, and that's what we need to look at.
+ * (Ugly kluge ... try to fix this when we redesign querytrees.)
+ */
+ sub_qry = getInsertSelectQuery(top_subqry, NULL);
/*
* Validate action's use of OLD/NEW, qual too
/*
* For efficiency's sake, add OLD to the rule action's jointree
* only if it was actually referenced in the statement or qual.
- * NEW is not really a relation and should never be added.
+ *
+ * For INSERT, NEW is not really a relation (only a reference to
+ * the to-be-inserted tuple) and should never be added to the
+ * jointree.
+ *
+ * For UPDATE, we treat NEW as being another kind of reference to
+ * OLD, because it represents references to *transformed* tuples
+ * of the existing relation. It would be wrong to enter NEW
+ * separately in the jointree, since that would cause a double
+ * join of the updated relation. It's also wrong to fail to make
+ * a jointree entry if only NEW and not OLD is mentioned.
*/
- if (has_old)
+ if (has_old || (has_new && stmt->event == CMD_UPDATE))
{
+ /* hack so we can use addRTEtoJoinList() */
+ sub_pstate->p_rtable = sub_qry->rtable;
+ sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
addRTEtoJoinList(sub_pstate, oldrte);
sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
}
- lfirst(actions) = sub_qry;
+ lfirst(actions) = top_subqry;
release_pstate_resources(sub_pstate);
pfree(sub_pstate);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.83 2000/11/08 22:09:59 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.84 2000/12/05 19:15:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
bool instead_flag)
{
RewriteInfo *info;
+ Query *sub_action;
+ Query **sub_action_ptr;
int rt_length;
info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
info->rule_action = (Query *) copyObject(rule_action);
info->rule_qual = (Node *) copyObject(rule_qual);
if (info->rule_action == NULL)
- info->nothing = TRUE;
- else
{
- info->nothing = FALSE;
- info->action = info->rule_action->commandType;
- info->current_varno = rt_index;
- rt_length = length(parsetree->rtable);
-
- /* Adjust rule action and qual to offset its varnos */
- info->new_varno = PRS2_NEW_VARNO + rt_length;
- OffsetVarNodes((Node *) info->rule_action, rt_length, 0);
- OffsetVarNodes(info->rule_qual, rt_length, 0);
- /* but its references to *OLD* should point at original rt_index */
- ChangeVarNodes((Node *) info->rule_action,
- PRS2_OLD_VARNO + rt_length, rt_index, 0);
- ChangeVarNodes(info->rule_qual,
- PRS2_OLD_VARNO + rt_length, rt_index, 0);
+ info->nothing = TRUE;
+ return info;
+ }
+ info->nothing = FALSE;
+ info->action = info->rule_action->commandType;
+ info->current_varno = rt_index;
+ rt_length = length(parsetree->rtable);
+ info->new_varno = PRS2_NEW_VARNO + rt_length;
- /*
- * We want the main parsetree's rtable to end up as the concatenation
- * of its original contents plus those of all the relevant rule
- * actions. Also store same into all the rule_action rtables.
- * Some of the entries may be unused after we finish rewriting, but
- * if we tried to clean those out we'd have a much harder job to
- * adjust RT indexes in the query's Vars. It's OK to have unused
- * RT entries, since planner will ignore them.
- *
- * NOTE KLUGY HACK: we assume the parsetree rtable had at least one
- * entry to begin with (OK enough, else where'd the rule come from?).
- * Because of this, if multiple rules nconc() their rtable additions
- * onto parsetree->rtable, they'll all see the same rtable because
- * they all have the same list head pointer.
- */
- parsetree->rtable = nconc(parsetree->rtable,
- info->rule_action->rtable);
- info->rule_action->rtable = parsetree->rtable;
+ /*
+ * Adjust rule action and qual to offset its varnos, so that we can
+ * merge its rtable into the main parsetree's rtable.
+ *
+ * If the rule action is an INSERT...SELECT, the OLD/NEW rtable
+ * entries will be in the SELECT part, and we have to modify that
+ * rather than the top-level INSERT (kluge!).
+ */
+ sub_action = getInsertSelectQuery(info->rule_action, &sub_action_ptr);
- /*
- * Each rule action's jointree should be the main parsetree's jointree
- * plus that rule's jointree, but *without* the original rtindex
- * that we're replacing (if present, which it won't be for INSERT).
- * Note that if the rule refers to OLD, its jointree will add back
- * a reference to rt_index.
- */
+ OffsetVarNodes((Node *) sub_action, rt_length, 0);
+ OffsetVarNodes(info->rule_qual, rt_length, 0);
+ /* but references to *OLD* should point at original rt_index */
+ ChangeVarNodes((Node *) sub_action,
+ PRS2_OLD_VARNO + rt_length, rt_index, 0);
+ ChangeVarNodes(info->rule_qual,
+ PRS2_OLD_VARNO + rt_length, rt_index, 0);
+
+ /*
+ * Update resultRelation too ... perhaps this should be done by
+ * Offset/ChangeVarNodes?
+ */
+ if (sub_action->resultRelation)
+ {
+ int result_reln;
+ int new_result_reln;
+
+ result_reln = sub_action->resultRelation;
+ switch (result_reln)
{
- bool found;
- List *newjointree = adjustJoinTreeList(parsetree,
- rt_index,
- &found);
-
- info->rule_action->jointree->fromlist =
- nconc(newjointree,
- info->rule_action->jointree->fromlist);
+ case PRS2_OLD_VARNO:
+ new_result_reln = rt_index;
+ break;
+ case PRS2_NEW_VARNO:
+ default:
+ new_result_reln = result_reln + rt_length;
+ break;
}
+ sub_action->resultRelation = new_result_reln;
+ }
- /*
- * bug here about replace CURRENT -- sort of replace current is
- * deprecated now so this code shouldn't really need to be so
- * clutzy but.....
- */
- if (info->action != CMD_SELECT)
- { /* i.e update XXXXX */
- int result_reln;
- int new_result_reln;
+ /*
+ * We want the main parsetree's rtable to end up as the concatenation
+ * of its original contents plus those of all the relevant rule
+ * actions. Also store same into all the rule_action rtables.
+ * Some of the entries may be unused after we finish rewriting, but
+ * if we tried to clean those out we'd have a much harder job to
+ * adjust RT indexes in the query's Vars. It's OK to have unused
+ * RT entries, since planner will ignore them.
+ *
+ * NOTE KLUGY HACK: we assume the parsetree rtable had at least one
+ * entry to begin with (OK enough, else where'd the rule come from?).
+ * Because of this, if multiple rules nconc() their rtable additions
+ * onto parsetree->rtable, they'll all see the same rtable because
+ * they all have the same list head pointer.
+ */
+ parsetree->rtable = nconc(parsetree->rtable,
+ sub_action->rtable);
+ sub_action->rtable = parsetree->rtable;
- result_reln = info->rule_action->resultRelation;
- switch (result_reln)
- {
- case PRS2_OLD_VARNO:
- new_result_reln = rt_index;
- break;
- case PRS2_NEW_VARNO: /* XXX */
- default:
- new_result_reln = result_reln + rt_length;
- break;
- }
- info->rule_action->resultRelation = new_result_reln;
- }
+ /*
+ * Each rule action's jointree should be the main parsetree's jointree
+ * plus that rule's jointree, but *without* the original rtindex
+ * that we're replacing (if present, which it won't be for INSERT).
+ * Note that if the rule refers to OLD, its jointree will add back
+ * a reference to rt_index.
+ */
+ {
+ bool found;
+ List *newjointree = adjustJoinTreeList(parsetree,
+ rt_index,
+ &found);
+
+ sub_action->jointree->fromlist =
+ nconc(newjointree, sub_action->jointree->fromlist);
}
+
+ /*
+ * We copy the qualifications of the parsetree to the action and vice
+ * versa. So force hasSubLinks if one of them has it. If this is not
+ * right, the flag will get cleared later, but we mustn't risk having
+ * it not set when it needs to be.
+ */
+ if (parsetree->hasSubLinks)
+ sub_action->hasSubLinks = TRUE;
+ else if (sub_action->hasSubLinks)
+ parsetree->hasSubLinks = TRUE;
+
+ /*
+ * Event Qualification forces copying of parsetree and
+ * splitting into two queries one w/rule_qual, one w/NOT
+ * rule_qual. Also add user query qual onto rule action
+ */
+ AddQual(sub_action, info->rule_qual);
+
+ AddQual(sub_action, parsetree->jointree->quals);
+
+ /*
+ * Rewrite new.attribute w/ right hand side of target-list
+ * entry for appropriate field name in insert/update.
+ *
+ * KLUGE ALERT: since ResolveNew returns a mutated copy, we can't just
+ * apply it to sub_action; we have to remember to update the sublink
+ * inside info->rule_action, too.
+ */
+ if (info->event == CMD_INSERT || info->event == CMD_UPDATE)
+ {
+ sub_action = (Query *) ResolveNew((Node *) sub_action,
+ info->new_varno,
+ 0,
+ parsetree->targetList,
+ info->event,
+ info->current_varno);
+ if (sub_action_ptr)
+ *sub_action_ptr = sub_action;
+ else
+ info->rule_action = sub_action;
+ }
+
return info;
}
}
-
+/*
+ * Modify the given query by adding 'AND NOT rule_qual' to its qualification.
+ * This is used to generate suitable "else clauses" for conditional INSTEAD
+ * rules.
+ *
+ * The rule_qual may contain references to OLD or NEW. OLD references are
+ * replaced by references to the specified rt_index (the relation that the
+ * rule applies to). NEW references are only possible for INSERT and UPDATE
+ * queries on the relation itself, and so they should be replaced by copies
+ * of the related entries in the query's own targetlist.
+ */
static Query *
CopyAndAddQual(Query *parsetree,
- List *actions,
Node *rule_qual,
int rt_index,
CmdType event)
{
Query *new_tree = (Query *) copyObject(parsetree);
- Node *new_qual = NULL;
- Query *rule_action = NULL;
-
- if (actions)
- rule_action = lfirst(actions);
- if (rule_qual != NULL)
- new_qual = (Node *) copyObject(rule_qual);
- if (rule_action != NULL)
- {
- List *rtable;
- int rt_length;
- List *jointreelist;
-
- rtable = new_tree->rtable;
- rt_length = length(rtable);
- rtable = nconc(rtable, copyObject(rule_action->rtable));
- new_tree->rtable = rtable;
- OffsetVarNodes(new_qual, rt_length, 0);
- ChangeVarNodes(new_qual, PRS2_OLD_VARNO + rt_length, rt_index, 0);
- jointreelist = copyObject(rule_action->jointree->fromlist);
- OffsetVarNodes((Node *) jointreelist, rt_length, 0);
- ChangeVarNodes((Node *) jointreelist, PRS2_OLD_VARNO + rt_length,
- rt_index, 0);
- new_tree->jointree->fromlist = nconc(new_tree->jointree->fromlist,
- jointreelist);
- }
- /* XXX -- where current doesn't work for instead nothing.... yet */
+ Node *new_qual = (Node *) copyObject(rule_qual);
+
+ /* Fix references to OLD */
+ ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0);
+ /* Fix references to NEW */
+ if (event == CMD_INSERT || event == CMD_UPDATE)
+ new_qual = ResolveNew(new_qual,
+ PRS2_NEW_VARNO,
+ 0,
+ parsetree->targetList,
+ event,
+ rt_index);
+ /* And attach the fixed qual */
AddNotQual(new_tree, new_qual);
return new_tree;
List *locks,
List **qual_products)
{
- RewriteInfo *info;
List *results = NIL;
List *i;
if (event_qual != NULL && *instead_flag)
{
Query *qual_product;
- RewriteInfo qual_info;
/* ----------
* If there are instead rules with qualifications,
else
qual_product = (Query *) lfirst(*qual_products);
- MemSet(&qual_info, 0, sizeof(qual_info));
- qual_info.event = qual_product->commandType;
- qual_info.current_varno = rt_index;
- qual_info.new_varno = length(qual_product->rtable) + 2;
-
qual_product = CopyAndAddQual(qual_product,
- actions,
event_qual,
rt_index,
event);
- qual_info.rule_action = qual_product;
-
- if (event == CMD_INSERT || event == CMD_UPDATE)
- FixNew(&qual_info, qual_product);
-
*qual_products = makeList1(qual_product);
}
foreach(r, actions)
{
Query *rule_action = lfirst(r);
- Node *rule_qual = copyObject(event_qual);
+ RewriteInfo *info;
if (rule_action->commandType == CMD_NOTHING)
continue;
- /*--------------------------------------------------
- * We copy the qualifications of the parsetree
- * to the action and vice versa. So force
- * hasSubLinks if one of them has it.
- *
- * As of 6.4 only parsetree qualifications can
- * have sublinks. If this changes, we must make
- * this a node lookup at the end of rewriting.
- *
- * Jan
- *--------------------------------------------------
- */
- if (parsetree->hasSubLinks && !rule_action->hasSubLinks)
- {
- rule_action = copyObject(rule_action);
- rule_action->hasSubLinks = TRUE;
- }
- if (!parsetree->hasSubLinks && rule_action->hasSubLinks)
- parsetree->hasSubLinks = TRUE;
-
- /*--------------------------------------------------
- * Step 1:
- * Rewrite current.attribute or current to tuple variable
- * this appears to be done in parser?
- *--------------------------------------------------
- */
- info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
+ info = gatherRewriteMeta(parsetree, rule_action, event_qual,
rt_index, event, *instead_flag);
/* handle escapable cases, or those handled by other code */
continue;
}
- if (info->action == info->event &&
- info->event == CMD_SELECT)
- continue;
-
- /*
- * Event Qualification forces copying of parsetree and
- * splitting into two queries one w/rule_qual, one w/NOT
- * rule_qual. Also add user query qual onto rule action
- */
- AddQual(info->rule_action, info->rule_qual);
-
- AddQual(info->rule_action, parsetree->jointree->quals);
-
- /*--------------------------------------------------
- * Step 2:
- * Rewrite new.attribute w/ right hand side of target-list
- * entry for appropriate field name in insert/update
- *--------------------------------------------------
- */
- if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE))
- FixNew(info, parsetree);
-
- /*--------------------------------------------------
- * Step 3:
- * Simplify? hey, no algorithm for simplification... let
- * the planner do it.
- *--------------------------------------------------
- */
results = lappend(results, info->rule_action);
pfree(info);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.51 2000/11/16 22:30:29 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteManip.c,v 1.52 2000/12/05 19:15:09 tgl Exp $
*
*-------------------------------------------------------------------------
*/
}
+/*
+ * If the given Query is an INSERT ... SELECT construct, extract and
+ * return the sub-Query node that represents the SELECT part. Otherwise
+ * return the given Query.
+ *
+ * If subquery_ptr is not NULL, then *subquery_ptr is set to the location
+ * of the link to the SELECT subquery inside parsetree, or NULL if not an
+ * INSERT ... SELECT.
+ *
+ * This is a hack needed because transformations on INSERT ... SELECTs that
+ * appear in rule actions should be applied to the source SELECT, not to the
+ * INSERT part. Perhaps this can be cleaned up with redesigned querytrees.
+ */
+Query *
+getInsertSelectQuery(Query *parsetree, Query ***subquery_ptr)
+{
+ Query *selectquery;
+ RangeTblEntry *selectrte;
+ RangeTblRef *rtr;
+
+ if (subquery_ptr)
+ *subquery_ptr = NULL;
+
+ if (parsetree == NULL)
+ return parsetree;
+ if (parsetree->commandType != CMD_INSERT)
+ return parsetree;
+ /*
+ * Currently, this is ONLY applied to rule-action queries, and so
+ * we expect to find the *OLD* and *NEW* placeholder entries in the
+ * given query. If they're not there, it must be an INSERT/SELECT
+ * in which they've been pushed down to the SELECT.
+ */
+ if (length(parsetree->rtable) >= 2 &&
+ strcmp(rt_fetch(PRS2_OLD_VARNO, parsetree->rtable)->eref->relname,
+ "*OLD*") == 0 &&
+ strcmp(rt_fetch(PRS2_NEW_VARNO, parsetree->rtable)->eref->relname,
+ "*NEW*") == 0)
+ return parsetree;
+ Assert(parsetree->jointree && IsA(parsetree->jointree, FromExpr));
+ if (length(parsetree->jointree->fromlist) != 1)
+ elog(ERROR, "getInsertSelectQuery: expected to find SELECT subquery");
+ rtr = (RangeTblRef *) lfirst(parsetree->jointree->fromlist);
+ Assert(IsA(rtr, RangeTblRef));
+ selectrte = rt_fetch(rtr->rtindex, parsetree->rtable);
+ selectquery = selectrte->subquery;
+ if (! (selectquery && IsA(selectquery, Query) &&
+ selectquery->commandType == CMD_SELECT))
+ elog(ERROR, "getInsertSelectQuery: expected to find SELECT subquery");
+ if (length(selectquery->rtable) >= 2 &&
+ strcmp(rt_fetch(PRS2_OLD_VARNO, selectquery->rtable)->eref->relname,
+ "*OLD*") == 0 &&
+ strcmp(rt_fetch(PRS2_NEW_VARNO, selectquery->rtable)->eref->relname,
+ "*NEW*") == 0)
+ {
+ if (subquery_ptr)
+ *subquery_ptr = & (selectrte->subquery);
+ return selectquery;
+ }
+ elog(ERROR, "getInsertSelectQuery: can't find rule placeholders");
+ return NULL; /* not reached */
+}
+
+
/*
* Add the given qualifier condition to the query's WHERE clause
*/
return ResolveNew_mutator(node, &context);
}
-/*
- * Alternate interface to ResolveNew: substitute Vars in info->rule_action
- * with targetlist items from the parsetree's targetlist.
- */
-void
-FixNew(RewriteInfo *info, Query *parsetree)
-{
- ResolveNew_context context;
-
- context.target_varno = info->new_varno;
- context.sublevels_up = 0;
- context.targetlist = parsetree->targetList;
- context.event = info->event;
- context.update_varno = info->current_varno;
-
- query_tree_mutator(info->rule_action, ResolveNew_mutator,
- (void *) &context, true);
-}
-
#ifdef NOT_USED