X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Frewrite%2FrewriteHandler.c;h=a1d9122f828e9146974fa1571ed77e3dbc78078a;hb=455891bf96f6ee3dda9150fe83af2be5e86a572b;hp=a8997d5a8ab33089bbd09a8726b9179eeffdf5d6;hpb=4140c2f30e2814527f0975876956f446e326ae70;p=postgresql diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index a8997d5a8a..a1d9122f82 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1,2010 +1,745 @@ /*------------------------------------------------------------------------- * - * rewriteHandler.c-- - * - * Copyright (c) 1994, Regents of the University of California + * rewriteHandler.c + * Primary module of query rewriter. * + * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.27 1998/12/14 00:02:16 thomas Exp $ + * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.122 2003/07/03 16:34:25 tgl Exp $ * *------------------------------------------------------------------------- */ -#include #include "postgres.h" + +#include "access/heapam.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_type.h" #include "miscadmin.h" -#include "utils/palloc.h" -#include "utils/elog.h" -#include "utils/rel.h" -#include "nodes/pg_list.h" -#include "nodes/primnodes.h" -#include "nodes/relation.h" - -#include "parser/parsetree.h" /* for parsetree manipulation */ -#include "parser/parse_relation.h" -#include "nodes/parsenodes.h" - -#include "rewrite/rewriteSupport.h" +#include "nodes/makefuncs.h" +#include "optimizer/clauses.h" +#include "optimizer/prep.h" +#include "optimizer/var.h" +#include "parser/analyze.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "parser/parse_oper.h" +#include "parser/parse_type.h" +#include "parser/parsetree.h" #include "rewrite/rewriteHandler.h" #include "rewrite/rewriteManip.h" -#include "rewrite/locks.h" - -#include "commands/creatinh.h" -#include "access/heapam.h" - +#include "utils/builtins.h" #include "utils/lsyscache.h" -#include "utils/syscache.h" -#include "utils/acl.h" -#include "catalog/pg_shadow.h" -#include "catalog/pg_type.h" -static RewriteInfo *gatherRewriteMeta(Query *parsetree, +/* We use a list of these to detect recursion in RewriteQuery */ +typedef struct rewrite_event { + Oid relation; /* OID of relation having rules */ + CmdType event; /* type of rule being fired */ +} rewrite_event; + +static Query *rewriteRuleAction(Query *parsetree, Query *rule_action, Node *rule_qual, int rt_index, - CmdType event, - bool *instead_flag); -static bool rangeTableEntry_used(Node *node, int rt_index, int sublevels_up); -static bool attribute_used(Node *node, int rt_index, int attno, int sublevels_up); -static void modifyAggregUplevel(Node *node); -static void modifyAggregChangeVarnodes(Node **nodePtr, int rt_index, int new_index, int sublevels_up); -static void modifyAggregDropQual(Node **nodePtr, Node *orignode, Expr *expr); -static SubLink *modifyAggregMakeSublink(Expr *origexp, Query *parsetree); -static void modifyAggregQual(Node **nodePtr, Query *parsetree); - - -static Query *fireRIRrules(Query *parsetree); + CmdType event); +static List *adjustJoinTreeList(Query *parsetree, bool removert, int rt_index); +static void rewriteTargetList(Query *parsetree, Relation target_relation); +static TargetEntry *process_matched_tle(TargetEntry *src_tle, + TargetEntry *prior_tle); +static void markQueryForUpdate(Query *qry, bool skipOldNew); +static List *matchLocks(CmdType event, RuleLock *rulelocks, + int varno, Query *parsetree); +static Query *fireRIRrules(Query *parsetree, List *activeRIRs); /* - * gatherRewriteMeta - - * Gather meta information about parsetree, and rule. Fix rule body - * and qualifier so that they can be mixed with the parsetree and - * maintain semantic validity + * rewriteRuleAction - + * Rewrite the rule action with appropriate qualifiers (taken from + * the triggering query). */ -static RewriteInfo * -gatherRewriteMeta(Query *parsetree, +static Query * +rewriteRuleAction(Query *parsetree, Query *rule_action, Node *rule_qual, int rt_index, - CmdType event, - bool *instead_flag) + CmdType event) { - RewriteInfo *info; + int current_varno, + new_varno; + List *main_rtable; int rt_length; - int result_reln; - - info = (RewriteInfo *) palloc(sizeof(RewriteInfo)); - info->rt_index = rt_index; - info->event = event; - info->instead_flag = *instead_flag; - info->rule_action = (Query *) copyObject(rule_action); - info->rule_qual = (Node *) copyObject(rule_qual); - if (info->rule_action == NULL) - info->nothing = TRUE; - else + Query *sub_action; + Query **sub_action_ptr; + List *rt; + + /* + * Make modifiable copies of rule action and qual (what we're passed + * are the stored versions in the relcache; don't touch 'em!). + */ + rule_action = (Query *) copyObject(rule_action); + rule_qual = (Node *) copyObject(rule_qual); + + current_varno = rt_index; + rt_length = length(parsetree->rtable); + new_varno = PRS2_NEW_VARNO + rt_length; + + /* + * Adjust rule action and qual to offset its varnos, so that we can + * merge its rtable with 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(rule_action, &sub_action_ptr); + + OffsetVarNodes((Node *) sub_action, rt_length, 0); + OffsetVarNodes(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(rule_qual, + PRS2_OLD_VARNO + rt_length, rt_index, 0); + + /* + * Generate expanded rtable consisting of main parsetree's rtable plus + * rule action's rtable; this becomes the complete rtable for the rule + * action. Some of the entries may be unused after we finish + * rewriting, but if we tried to remove them 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: because planner will destructively alter rtable, we must ensure + * that rule action's rtable is separate and shares no substructure + * with the main rtable. Hence do a deep copy here. + * + * Also, we must disable write-access checking in all the RT entries + * copied from the main query. This is safe since in fact the rule action + * won't write on them, and it's necessary because the rule action may + * have a different commandType than the main query, causing + * ExecCheckRTEPerms() to make an inappropriate check. The read-access + * checks can be left enabled, although they're probably redundant. + */ + main_rtable = (List *) copyObject(parsetree->rtable); + + foreach(rt, main_rtable) { - info->nothing = FALSE; - info->action = info->rule_action->commandType; - info->current_varno = rt_index; - info->rt = parsetree->rtable; - rt_length = length(info->rt); - info->rt = append(info->rt, info->rule_action->rtable); - - info->new_varno = PRS2_NEW_VARNO + rt_length; - OffsetVarNodes(info->rule_action->qual, rt_length, 0); - OffsetVarNodes((Node *) info->rule_action->targetList, rt_length, 0); - OffsetVarNodes(info->rule_qual, rt_length, 0); - ChangeVarNodes((Node *) info->rule_action->qual, - PRS2_CURRENT_VARNO + rt_length, rt_index, 0); - ChangeVarNodes((Node *) info->rule_action->targetList, - PRS2_CURRENT_VARNO + rt_length, rt_index, 0); - ChangeVarNodes(info->rule_qual, - PRS2_CURRENT_VARNO + rt_length, rt_index, 0); + RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt); - /* - * 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 new_result_reln = 0; + rte->checkForWrite = false; + } - result_reln = info->rule_action->resultRelation; - switch (result_reln) - { - case PRS2_CURRENT_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; - } + sub_action->rtable = nconc(main_rtable, sub_action->rtable); + + /* + * Each rule action's jointree should be the main parsetree's jointree + * plus that rule's jointree, but usually *without* the original + * rtindex that we're replacing (if present, which it won't be for + * INSERT). Note that if the rule action refers to OLD, its jointree + * will add a reference to rt_index. If the rule action doesn't refer + * to OLD, but either the rule_qual or the user query quals do, then + * we need to keep the original rtindex in the jointree to provide + * data for the quals. We don't want the original rtindex to be + * joined twice, however, so avoid keeping it if the rule action + * mentions it. + * + * As above, the action's jointree must not share substructure with the + * main parsetree's. + */ + if (sub_action->jointree != NULL) + { + bool keeporig; + List *newjointree; + + keeporig = (!rangeTableEntry_used((Node *) sub_action->jointree, + rt_index, 0)) && + (rangeTableEntry_used(rule_qual, rt_index, 0) || + rangeTableEntry_used(parsetree->jointree->quals, rt_index, 0)); + newjointree = adjustJoinTreeList(parsetree, !keeporig, rt_index); + 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. (XXX this should probably be + * handled by AddQual and friends, not here...) + */ + 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, 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 rule_action, too. + */ + if (event == CMD_INSERT || event == CMD_UPDATE) + { + sub_action = (Query *) ResolveNew((Node *) sub_action, + new_varno, + 0, + parsetree->targetList, + event, + current_varno); + if (sub_action_ptr) + *sub_action_ptr = sub_action; + else + rule_action = sub_action; } - return info; -} + return rule_action; +} /* - * rangeTableEntry_used - - * we need to process a RTE for RIR rules only if it is - * referenced somewhere in var nodes of the query. + * Copy the query's jointree list, and optionally attempt to remove any + * occurrence of the given rt_index as a top-level join item (we do not look + * for it within join items; this is OK because we are only expecting to find + * it as an UPDATE or DELETE target relation, which will be at the top level + * of the join). Returns modified jointree list --- this is a separate copy + * sharing no nodes with the original. */ -static bool -rangeTableEntry_used(Node *node, int rt_index, int sublevels_up) +static List * +adjustJoinTreeList(Query *parsetree, bool removert, int rt_index) { - if (node == NULL) - return FALSE; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; + List *newjointree = copyObject(parsetree->jointree->fromlist); + List *jjt; - return rangeTableEntry_used( - (Node *)(tle->expr), - rt_index, - sublevels_up); - } - break; + if (removert) + { + foreach(jjt, newjointree) + { + RangeTblRef *rtr = lfirst(jjt); - case T_Aggreg: + if (IsA(rtr, RangeTblRef) && + rtr->rtindex == rt_index) { - Aggreg *agg = (Aggreg *)node; - - return rangeTableEntry_used( - (Node *)(agg->target), - rt_index, - sublevels_up); + newjointree = lremove(rtr, newjointree); + /* foreach is safe because we exit loop after lremove... */ + break; } - break; + } + } + return newjointree; +} - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - return rangeTableEntry_used( - (Node *)(grp->entry), - rt_index, - sublevels_up); - } - break; +/* + * rewriteTargetList - rewrite INSERT/UPDATE targetlist into standard form + * + * This has the following responsibilities: + * + * 1. For an INSERT, add tlist entries to compute default values for any + * attributes that have defaults and are not assigned to in the given tlist. + * (We do not insert anything for default-less attributes, however. The + * planner will later insert NULLs for them, but there's no reason to slow + * down rewriter processing with extra tlist nodes.) Also, for both INSERT + * and UPDATE, replace explicit DEFAULT specifications with column default + * expressions. + * + * 2. Merge multiple entries for the same target attribute, or declare error + * if we can't. Presently, multiple entries are only allowed for UPDATE of + * an array field, for example "UPDATE table SET foo[2] = 42, foo[4] = 43". + * We can merge such operations into a single assignment op. Essentially, + * the expression we want to produce in this case is like + * foo = array_set(array_set(foo, 2, 42), 4, 43) + * + * 3. Sort the tlist into standard order: non-junk fields in order by resno, + * then junk fields (these in no particular order). + * + * We must do items 1 and 2 before firing rewrite rules, else rewritten + * references to NEW.foo will produce wrong or incomplete results. Item 3 + * is not needed for rewriting, but will be needed by the planner, and we + * can do it essentially for free while handling items 1 and 2. + */ +static void +rewriteTargetList(Query *parsetree, Relation target_relation) +{ + CmdType commandType = parsetree->commandType; + List *tlist = parsetree->targetList; + List *new_tlist = NIL; + int attrno, + numattrs; + List *temp; - case T_Expr: - { - Expr *exp = (Expr *)node; + /* + * Scan the tuple description in the relation's relcache entry to make + * sure we have all the user attributes in the right order. + */ + numattrs = RelationGetNumberOfAttributes(target_relation); - return rangeTableEntry_used( - (Node *)(exp->args), - rt_index, - sublevels_up); - } - break; + for (attrno = 1; attrno <= numattrs; attrno++) + { + Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1]; + TargetEntry *new_tle = NULL; - case T_Iter: - { - Iter *iter = (Iter *)node; + /* We can ignore deleted attributes */ + if (att_tup->attisdropped) + continue; - return rangeTableEntry_used( - (Node *)(iter->iterexpr), - rt_index, - sublevels_up); - } - break; + /* + * Look for targetlist entries matching this attr. We match by + * resno, but the resname should match too. + * + * Junk attributes are not candidates to be matched. + */ + foreach(temp, tlist) + { + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); + Resdom *resdom = old_tle->resdom; - case T_ArrayRef: + if (!resdom->resjunk && resdom->resno == attrno) { - ArrayRef *ref = (ArrayRef *)node; - - if (rangeTableEntry_used( - (Node *)(ref->refupperindexpr), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(ref->reflowerindexpr), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(ref->refexpr), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(ref->refassgnexpr), - rt_index, - sublevels_up)) - return TRUE; - - return FALSE; + Assert(strcmp(resdom->resname, + NameStr(att_tup->attname)) == 0); + new_tle = process_matched_tle(old_tle, new_tle); + /* keep scanning to detect multiple assignments to attr */ } - break; + } - case T_Var: - { - Var *var = (Var *)node; + /* + * Handle the two cases where we need to insert a default expression: + * it's an INSERT and there's no tlist entry for the column, or the + * tlist entry is a DEFAULT placeholder node. + */ + if ((new_tle == NULL && commandType == CMD_INSERT) || + (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault))) + { + Node *new_expr; + + new_expr = build_column_default(target_relation, attrno); - if (var->varlevelsup == sublevels_up) - return var->varno == rt_index; + /* + * If there is no default (ie, default is effectively NULL), + * we can omit the tlist entry in the INSERT case, since the + * planner can insert a NULL for itself, and there's no point + * in spending any more rewriter cycles on the entry. But in the + * UPDATE case we've got to explicitly set the column to NULL. + */ + if (!new_expr) + { + if (commandType == CMD_INSERT) + new_tle = NULL; else - return FALSE; + { + new_expr = (Node *) makeConst(att_tup->atttypid, + att_tup->attlen, + (Datum) 0, + true, /* isnull */ + att_tup->attbyval); + /* this is to catch a NOT NULL domain constraint */ + new_expr = coerce_to_domain(new_expr, + InvalidOid, + att_tup->atttypid, + COERCE_IMPLICIT_CAST); + } } - break; - case T_Param: - return FALSE; + if (new_expr) + new_tle = makeTargetEntry(makeResdom(attrno, + att_tup->atttypid, + att_tup->atttypmod, + pstrdup(NameStr(att_tup->attname)), + false), + (Expr *) new_expr); + } - case T_Const: - return FALSE; + if (new_tle) + new_tlist = lappend(new_tlist, new_tle); + } - case T_List: - { - List *l; - - foreach (l, (List *)node) { - if (rangeTableEntry_used( - (Node *)lfirst(l), - rt_index, - sublevels_up)) - return TRUE; - } - return FALSE; - } - break; + /* + * Copy all resjunk tlist entries to the end of the new tlist, and + * assign them resnos above the last real resno. + * + * Typical junk entries include ORDER BY or GROUP BY expressions (are + * these actually possible in an INSERT or UPDATE?), system attribute + * references, etc. + */ + foreach(temp, tlist) + { + TargetEntry *old_tle = (TargetEntry *) lfirst(temp); + Resdom *resdom = old_tle->resdom; - case T_SubLink: + if (resdom->resjunk) + { + /* Get the resno right, but don't copy unnecessarily */ + if (resdom->resno != attrno) { - SubLink *sub = (SubLink *)node; + resdom = (Resdom *) copyObject((Node *) resdom); + resdom->resno = attrno; + old_tle = makeTargetEntry(resdom, old_tle->expr); + } + new_tlist = lappend(new_tlist, old_tle); + attrno++; + } + else + { + /* Let's just make sure we processed all the non-junk items */ + if (resdom->resno < 1 || resdom->resno > numattrs) + elog(ERROR, "rewriteTargetList: bogus resno %d in targetlist", + resdom->resno); + } + } - if (rangeTableEntry_used( - (Node *)(sub->lefthand), - rt_index, - sublevels_up)) - return TRUE; + parsetree->targetList = new_tlist; +} - if (rangeTableEntry_used( - (Node *)(sub->subselect), - rt_index, - sublevels_up + 1)) - return TRUE; - return FALSE; - } - break; +/* + * Convert a matched TLE from the original tlist into a correct new TLE. + * + * This routine detects and handles multiple assignments to the same target + * attribute. + */ +static TargetEntry * +process_matched_tle(TargetEntry *src_tle, + TargetEntry *prior_tle) +{ + Resdom *resdom = src_tle->resdom; + Node *priorbottom; + ArrayRef *newexpr; - case T_CaseExpr: - { - CaseExpr *exp = (CaseExpr *)node; + if (prior_tle == NULL) + { + /* + * Normal case where this is the first assignment to the + * attribute. + */ + return src_tle; + } - if (rangeTableEntry_used( - (Node *)(exp->args), - rt_index, - sublevels_up)) - return TRUE; + /* + * Multiple assignments to same attribute. Allow only if all are + * array-assign operators with same bottom array object. + */ + if (src_tle->expr == NULL || !IsA(src_tle->expr, ArrayRef) || + ((ArrayRef *) src_tle->expr)->refassgnexpr == NULL || + prior_tle->expr == NULL || !IsA(prior_tle->expr, ArrayRef) || + ((ArrayRef *) prior_tle->expr)->refassgnexpr == NULL || + ((ArrayRef *) src_tle->expr)->refrestype != + ((ArrayRef *) prior_tle->expr)->refrestype) + elog(ERROR, "Multiple assignments to same attribute \"%s\"", + resdom->resname); - if (rangeTableEntry_used( - (Node *)(exp->defresult), - rt_index, - sublevels_up)) - return TRUE; + /* + * Prior TLE could be a nest of ArrayRefs if we do this more than + * once. + */ + priorbottom = (Node *) ((ArrayRef *) prior_tle->expr)->refexpr; + while (priorbottom != NULL && IsA(priorbottom, ArrayRef) && + ((ArrayRef *) priorbottom)->refassgnexpr != NULL) + priorbottom = (Node *) ((ArrayRef *) priorbottom)->refexpr; + if (!equal(priorbottom, ((ArrayRef *) src_tle->expr)->refexpr)) + elog(ERROR, "Multiple assignments to same attribute \"%s\"", + resdom->resname); - return FALSE; - } - break; + /* + * Looks OK to nest 'em. + */ + newexpr = makeNode(ArrayRef); + memcpy(newexpr, src_tle->expr, sizeof(ArrayRef)); + newexpr->refexpr = prior_tle->expr; - case T_CaseWhen: - { - CaseWhen *when = (CaseWhen *)node; + return makeTargetEntry(resdom, (Expr *) newexpr); +} - if (rangeTableEntry_used( - (Node *)(when->expr), - rt_index, - sublevels_up)) - return TRUE; - if (rangeTableEntry_used( - (Node *)(when->result), - rt_index, - sublevels_up)) - return TRUE; +/* + * Make an expression tree for the default value for a column. + * + * If there is no default, return a NULL instead. + */ +Node * +build_column_default(Relation rel, int attrno) +{ + TupleDesc rd_att = rel->rd_att; + Form_pg_attribute att_tup = rd_att->attrs[attrno - 1]; + Oid atttype = att_tup->atttypid; + int32 atttypmod = att_tup->atttypmod; + Node *expr = NULL; + Oid exprtype; - return FALSE; - } - break; + /* + * Scan to see if relation has a default for this column. + */ + if (rd_att->constr && rd_att->constr->num_defval > 0) + { + AttrDefault *defval = rd_att->constr->defval; + int ndef = rd_att->constr->num_defval; - case T_Query: + while (--ndef >= 0) + { + if (attrno == defval[ndef].adnum) { - Query *qry = (Query *)node; - - if (rangeTableEntry_used( - (Node *)(qry->targetList), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(qry->qual), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(qry->havingQual), - rt_index, - sublevels_up)) - return TRUE; - - if (rangeTableEntry_used( - (Node *)(qry->groupClause), - rt_index, - sublevels_up)) - return TRUE; - - return FALSE; + /* + * Found it, convert string representation to node tree. + */ + expr = stringToNode(defval[ndef].adbin); + break; } - break; + } + } - default: - elog(NOTICE, "unknown node tag %d in rangeTableEntry_used()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; + if (expr == NULL) + { + /* + * No per-column default, so look for a default for the type + * itself. + */ + if (att_tup->attisset) + { + /* + * Set attributes are represented as OIDs no matter what the + * set element type is, and the element type's default is + * irrelevant too. + */ + } + else + expr = get_typdefault(atttype); + } + if (expr == NULL) + return NULL; /* No default anywhere */ - } + /* + * Make sure the value is coerced to the target column type (might not + * be right type yet if it's not a constant!) This should match the + * parser's processing of non-defaulted expressions --- see + * updateTargetListEntry(). + */ + exprtype = exprType(expr); - return FALSE; + expr = coerce_to_target_type(NULL, /* no UNKNOWN params here */ + expr, exprtype, + atttype, atttypmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST); + /* + * This really shouldn't fail; should have checked the default's + * type when it was created ... + */ + if (expr == NULL) + elog(ERROR, "Column \"%s\" is of type %s" + " but default expression is of type %s" + "\n\tYou will need to rewrite or cast the expression", + NameStr(att_tup->attname), + format_type_be(atttype), + format_type_be(exprtype)); + + return expr; } /* - * attribute_used - - * Check if a specific attribute number of a RTE is used - * somewhere in the query + * matchLocks - + * match the list of locks and returns the matching rules */ -static bool -attribute_used(Node *node, int rt_index, int attno, int sublevels_up) +static List * +matchLocks(CmdType event, + RuleLock *rulelocks, + int varno, + Query *parsetree) { - if (node == NULL) - return FALSE; + List *matching_locks = NIL; + int nlocks; + int i; - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - return attribute_used( - (Node *)(tle->expr), - rt_index, - attno, - sublevels_up); - } - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - - return attribute_used( - (Node *)(agg->target), - rt_index, - attno, - sublevels_up); - } - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; + if (rulelocks == NULL) + return NIL; - return attribute_used( - (Node *)(grp->entry), - rt_index, - attno, - sublevels_up); - } - break; + if (parsetree->commandType != CMD_SELECT) + { + if (parsetree->resultRelation != varno) + return NIL; + } - case T_Expr: - { - Expr *exp = (Expr *)node; + nlocks = rulelocks->numLocks; - return attribute_used( - (Node *)(exp->args), - rt_index, - attno, - sublevels_up); - } - break; + for (i = 0; i < nlocks; i++) + { + RewriteRule *oneLock = rulelocks->rules[i]; - case T_Iter: - { - Iter *iter = (Iter *)node; + if (oneLock->event == event) + { + if (parsetree->commandType != CMD_SELECT || + (oneLock->attrno == -1 ? + rangeTableEntry_used((Node *) parsetree, varno, 0) : + attribute_used((Node *) parsetree, + varno, oneLock->attrno, 0))) + matching_locks = lappend(matching_locks, oneLock); + } + } - return attribute_used( - (Node *)(iter->iterexpr), - rt_index, - attno, - sublevels_up); - } - break; + return matching_locks; +} - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - if (attribute_used( - (Node *)(ref->refupperindexpr), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(ref->reflowerindexpr), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(ref->refexpr), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(ref->refassgnexpr), - rt_index, - attno, - sublevels_up)) - return TRUE; - - return FALSE; - } - break; - case T_Var: - { - Var *var = (Var *)node; +static Query * +ApplyRetrieveRule(Query *parsetree, + RewriteRule *rule, + int rt_index, + bool relation_level, + Relation relation, + bool relIsUsed, + List *activeRIRs) +{ + Query *rule_action; + RangeTblEntry *rte, + *subrte; - if (var->varlevelsup == sublevels_up) - return var->varno == rt_index; - else - return FALSE; - } - break; + if (length(rule->actions) != 1) + elog(ERROR, "ApplyRetrieveRule: expected just one rule action"); + if (rule->qual != NULL) + elog(ERROR, "ApplyRetrieveRule: can't handle qualified ON SELECT rule"); + if (!relation_level) + elog(ERROR, "ApplyRetrieveRule: can't handle per-attribute ON SELECT rule"); - case T_Param: - return FALSE; + /* + * Make a modifiable copy of the view query, and recursively expand + * any view references inside it. + */ + rule_action = copyObject(lfirst(rule->actions)); - case T_Const: - return FALSE; + rule_action = fireRIRrules(rule_action, activeRIRs); - case T_List: - { - List *l; - - foreach (l, (List *)node) { - if (attribute_used( - (Node *)lfirst(l), - rt_index, - attno, - sublevels_up)) - return TRUE; - } - return FALSE; - } - break; + /* + * VIEWs are really easy --- just plug the view query in as a + * subselect, replacing the relation's original RTE. + */ + rte = rt_fetch(rt_index, parsetree->rtable); - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - - if (attribute_used( - (Node *)(sub->lefthand), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(sub->subselect), - rt_index, - attno, - sublevels_up + 1)) - return TRUE; - - return FALSE; - } - break; + rte->rtekind = RTE_SUBQUERY; + rte->relid = InvalidOid; + rte->subquery = rule_action; + rte->inh = false; /* must not be set for a subquery */ - case T_Query: - { - Query *qry = (Query *)node; - - if (attribute_used( - (Node *)(qry->targetList), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(qry->qual), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(qry->havingQual), - rt_index, - attno, - sublevels_up)) - return TRUE; - - if (attribute_used( - (Node *)(qry->groupClause), - rt_index, - attno, - sublevels_up)) - return TRUE; - - return FALSE; - } - break; + /* + * We move the view's permission check data down to its rangetable. + * The checks will actually be done against the *OLD* entry therein. + */ + subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable); + Assert(subrte->relid == relation->rd_id); + subrte->checkForRead = rte->checkForRead; + subrte->checkForWrite = rte->checkForWrite; + subrte->checkAsUser = rte->checkAsUser; - default: - elog(NOTICE, "unknown node tag %d in attribute_used()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; + rte->checkForRead = false; /* no permission check on subquery itself */ + rte->checkForWrite = false; + rte->checkAsUser = InvalidOid; + /* + * FOR UPDATE of view? + */ + if (intMember(rt_index, parsetree->rowMarks)) + { + /* + * Remove the view from the list of rels that will actually be + * marked FOR UPDATE by the executor. It will still be access- + * checked for write access, though. + */ + parsetree->rowMarks = lremovei(rt_index, parsetree->rowMarks); + /* + * Set up the view's referenced tables as if FOR UPDATE. + */ + markQueryForUpdate(rule_action, true); } - return FALSE; + return parsetree; } - /* - * modifyAggregUplevel - - * In the newly created sublink for an aggregate column used in - * the qualification, we must adjust the varlevelsup in all the - * var nodes. + * Recursively mark all relations used by a view as FOR UPDATE. + * + * This may generate an invalid query, eg if some sub-query uses an + * aggregate. We leave it to the planner to detect that. + * + * NB: this must agree with the parser's transformForUpdate() routine. */ static void -modifyAggregUplevel(Node *node) +markQueryForUpdate(Query *qry, bool skipOldNew) { - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - modifyAggregUplevel( - (Node *)(tle->expr)); - } - break; + Index rti = 0; + List *l; - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; + foreach(l, qry->rtable) + { + RangeTblEntry *rte = (RangeTblEntry *) lfirst(l); - modifyAggregUplevel( - (Node *)(agg->target)); - } - break; + rti++; - case T_Expr: - { - Expr *exp = (Expr *)node; + /* Ignore OLD and NEW entries if we are at top level of view */ + if (skipOldNew && + (rti == PRS2_OLD_VARNO || rti == PRS2_NEW_VARNO)) + continue; - modifyAggregUplevel( - (Node *)(exp->args)); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - modifyAggregUplevel( - (Node *)(iter->iterexpr)); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - modifyAggregUplevel( - (Node *)(ref->refupperindexpr)); - modifyAggregUplevel( - (Node *)(ref->reflowerindexpr)); - modifyAggregUplevel( - (Node *)(ref->refexpr)); - modifyAggregUplevel( - (Node *)(ref->refassgnexpr)); - } - break; - - case T_Var: - { - Var *var = (Var *)node; - - var->varlevelsup++; - } - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) - modifyAggregUplevel( - (Node *)lfirst(l)); - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - - modifyAggregUplevel( - (Node *)(sub->lefthand)); - - modifyAggregUplevel( - (Node *)(sub->oper)); - - modifyAggregUplevel( - (Node *)(sub->subselect)); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - - modifyAggregUplevel( - (Node *)(qry->targetList)); - - modifyAggregUplevel( - (Node *)(qry->qual)); - - modifyAggregUplevel( - (Node *)(qry->havingQual)); - - modifyAggregUplevel( - (Node *)(qry->groupClause)); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in modifyAggregUplevel()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - - - } -} - - -/* - * modifyAggregChangeVarnodes - - * Change the var nodes in a sublink created for an aggregate column - * used in the qualification that is subject of the aggregate - * function to point to the correct local RTE. - */ -static void -modifyAggregChangeVarnodes(Node **nodePtr, int rt_index, int new_index, int sublevels_up) -{ - Node *node = *nodePtr; - - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(tle->expr)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(agg->target)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(grp->entry)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_Expr: - { - Expr *exp = (Expr *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(exp->args)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(iter->iterexpr)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(ref->refupperindexpr)), - rt_index, - new_index, - sublevels_up); - modifyAggregChangeVarnodes( - (Node **)(&(ref->reflowerindexpr)), - rt_index, - new_index, - sublevels_up); - modifyAggregChangeVarnodes( - (Node **)(&(ref->refexpr)), - rt_index, - new_index, - sublevels_up); - modifyAggregChangeVarnodes( - (Node **)(&(ref->refassgnexpr)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_Var: - { - Var *var = (Var *)node; - - if (var->varlevelsup == sublevels_up && - var->varno == rt_index) { - var = copyObject(var); - var->varno = new_index; - var->varnoold = new_index; - var->varlevelsup = 0; - - *nodePtr = (Node *)var; - } - } - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) - modifyAggregChangeVarnodes( - (Node **)(&lfirst(l)), - rt_index, - new_index, - sublevels_up); - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(sub->lefthand)), - rt_index, - new_index, - sublevels_up); - - modifyAggregChangeVarnodes( - (Node **)(&(sub->oper)), - rt_index, - new_index, - sublevels_up); - - modifyAggregChangeVarnodes( - (Node **)(&(sub->subselect)), - rt_index, - new_index, - sublevels_up + 1); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - - modifyAggregChangeVarnodes( - (Node **)(&(qry->targetList)), - rt_index, - new_index, - sublevels_up); - - modifyAggregChangeVarnodes( - (Node **)(&(qry->qual)), - rt_index, - new_index, - sublevels_up); - - modifyAggregChangeVarnodes( - (Node **)(&(qry->havingQual)), - rt_index, - new_index, - sublevels_up); - - modifyAggregChangeVarnodes( - (Node **)(&(qry->groupClause)), - rt_index, - new_index, - sublevels_up); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in modifyAggregChangeVarnodes()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - - - } -} - - -/* - * modifyAggregDropQual - - * remove the pure aggreg clase from a qualification - */ -static void -modifyAggregDropQual(Node **nodePtr, Node *orignode, Expr *expr) -{ - Node *node = *nodePtr; - - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_Var: - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - Aggreg *oagg = (Aggreg *)orignode; - - modifyAggregDropQual( - (Node **)(&(agg->target)), - (Node *)(oagg->target), - expr); - } - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_GroupClause: - break; - - case T_Expr: - { - Expr *this_expr = (Expr *)node; - Expr *orig_expr = (Expr *)orignode; - - if (orig_expr == expr) { - Const *ctrue; - - if (expr->typeOid != BOOLOID) - elog(ERROR, - "aggregate expression in qualification isn't of type bool"); - ctrue = makeNode(Const); - ctrue->consttype = BOOLOID; - ctrue->constlen = 1; - ctrue->constisnull = FALSE; - ctrue->constvalue = (Datum)TRUE; - ctrue->constbyval = TRUE; - - *nodePtr = (Node *)ctrue; - } - else - modifyAggregDropQual( - (Node **)(&(this_expr->args)), - (Node *)(orig_expr->args), - expr); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - Iter *oiter = (Iter *)orignode; - - modifyAggregDropQual( - (Node **)(&(iter->iterexpr)), - (Node *)(oiter->iterexpr), - expr); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - ArrayRef *oref = (ArrayRef *)orignode; - - modifyAggregDropQual( - (Node **)(&(ref->refupperindexpr)), - (Node *)(oref->refupperindexpr), - expr); - modifyAggregDropQual( - (Node **)(&(ref->reflowerindexpr)), - (Node *)(oref->reflowerindexpr), - expr); - modifyAggregDropQual( - (Node **)(&(ref->refexpr)), - (Node *)(oref->refexpr), - expr); - modifyAggregDropQual( - (Node **)(&(ref->refassgnexpr)), - (Node *)(oref->refassgnexpr), - expr); - } - break; - - case T_List: - { - List *l; - List *ol = (List *)orignode; - int li = 0; - - foreach (l, (List *)node) { - modifyAggregDropQual( - (Node **)(&(lfirst(l))), - (Node *)nth(li, ol), - expr); - li++; - } - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - SubLink *osub = (SubLink *)orignode; - - modifyAggregDropQual( - (Node **)(&(sub->subselect)), - (Node *)(osub->subselect), - expr); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - Query *oqry = (Query *)orignode; - - modifyAggregDropQual( - (Node **)(&(qry->qual)), - (Node *)(oqry->qual), - expr); - - modifyAggregDropQual( - (Node **)(&(qry->havingQual)), - (Node *)(oqry->havingQual), - expr); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in modifyAggregDropQual()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - - - } -} - - -/* - * modifyAggregMakeSublink - - * Create a sublink node for a qualification expression that - * uses an aggregate column of a view - */ -static SubLink * -modifyAggregMakeSublink(Expr *origexp, Query *parsetree) -{ - SubLink *sublink; - Query *subquery; - Node *subqual; - RangeTblEntry *rte; - Aggreg *aggreg; - Var *target; - TargetEntry *tle; - Resdom *resdom; - Expr *exp = copyObject(origexp); - - if (nodeTag(nth(0, exp->args)) == T_Aggreg) - { - if (nodeTag(nth(1, exp->args)) == T_Aggreg) - elog(ERROR, "rewrite: comparision of 2 aggregate columns not supported"); - else - elog(ERROR, "rewrite: aggregate column of view must be at rigth side in qual"); + if (rte->rtekind == RTE_RELATION) + { + if (!intMember(rti, qry->rowMarks)) + qry->rowMarks = lappendi(qry->rowMarks, rti); + rte->checkForWrite = true; + } + else if (rte->rtekind == RTE_SUBQUERY) + { + /* FOR UPDATE of subquery is propagated to subquery's rels */ + markQueryForUpdate(rte->subquery, false); + } } - - aggreg = (Aggreg *)nth(1, exp->args); - target = (Var *)(aggreg->target); - rte = (RangeTblEntry *)nth(target->varno - 1, parsetree->rtable); - tle = makeNode(TargetEntry); - resdom = makeNode(Resdom); - - aggreg->usenulls = TRUE; - - resdom->resno = 1; - resdom->restype = ((Oper *)(exp->oper))->opresulttype; - resdom->restypmod = -1; - resdom->resname = pstrdup(""); - resdom->reskey = 0; - resdom->reskeyop = 0; - resdom->resjunk = 0; - - tle->resdom = resdom; - tle->expr = (Node *)aggreg; - - subqual = copyObject(parsetree->qual); - modifyAggregDropQual((Node **)&subqual, (Node *)parsetree->qual, origexp); - - sublink = makeNode(SubLink); - sublink->subLinkType = EXPR_SUBLINK; - sublink->useor = FALSE; - sublink->lefthand = lappend(NIL, copyObject(lfirst(exp->args))); - sublink->oper = lappend(NIL, copyObject(exp)); - sublink->subselect = NULL; - - subquery = makeNode(Query); - sublink->subselect = (Node *)subquery; - - subquery->commandType = CMD_SELECT; - subquery->utilityStmt = NULL; - subquery->resultRelation = 0; - subquery->into = NULL; - subquery->isPortal = FALSE; - subquery->isBinary = FALSE; - subquery->unionall = FALSE; - subquery->uniqueFlag = NULL; - subquery->sortClause = NULL; - subquery->rtable = lappend(NIL, rte); - subquery->targetList = lappend(NIL, tle); - subquery->qual = subqual; - subquery->groupClause = NIL; - subquery->havingQual = NULL; - subquery->hasAggs = TRUE; - subquery->hasSubLinks = FALSE; - subquery->unionClause = NULL; - - - modifyAggregUplevel((Node *)sublink); - - modifyAggregChangeVarnodes((Node **)&(sublink->lefthand), target->varno, - 1, target->varlevelsup); - modifyAggregChangeVarnodes((Node **)&(sublink->oper), target->varno, - 1, target->varlevelsup); - modifyAggregChangeVarnodes((Node **)&(sublink->subselect), target->varno, - 1, target->varlevelsup); - - return sublink; } /* - * modifyAggregQual - - * Search for qualification expressions that contain aggregate - * functions and substiture them by sublinks. These expressions - * originally come from qualifications that use aggregate columns - * of a view. - */ -static void -modifyAggregQual(Node **nodePtr, Query *parsetree) -{ - Node *node = *nodePtr; - - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_Var: - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - - modifyAggregQual( - (Node **)(&(grp->entry)), - parsetree); - } - break; - - case T_Expr: - { - Expr *exp = (Expr *)node; - SubLink *sub; - - - if (length(exp->args) != 2) { - modifyAggregQual( - (Node **)(&(exp->args)), - parsetree); - break; - } - - if (nodeTag(nth(0, exp->args)) != T_Aggreg && - nodeTag(nth(1, exp->args)) != T_Aggreg) { - - modifyAggregQual( - (Node **)(&(exp->args)), - parsetree); - break; - } - - sub = modifyAggregMakeSublink(exp, - parsetree); - - *nodePtr = (Node *)sub; - parsetree->hasSubLinks = TRUE; - } - break; - - case T_CaseExpr: - { - /* We're calling recursively, - * and this routine knows how to handle lists - * so let it do the work to handle the WHEN clauses... */ - modifyAggregQual( - (Node **)(&(((CaseExpr *)node)->args)), - parsetree); - - modifyAggregQual( - (Node **)(&(((CaseExpr *)node)->defresult)), - parsetree); - } - break; - - case T_CaseWhen: - { - modifyAggregQual( - (Node **)(&(((CaseWhen *)node)->expr)), - parsetree); - - modifyAggregQual( - (Node **)(&(((CaseWhen *)node)->result)), - parsetree); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - modifyAggregQual( - (Node **)(&(iter->iterexpr)), - parsetree); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - modifyAggregQual( - (Node **)(&(ref->refupperindexpr)), - parsetree); - modifyAggregQual( - (Node **)(&(ref->reflowerindexpr)), - parsetree); - modifyAggregQual( - (Node **)(&(ref->refexpr)), - parsetree); - modifyAggregQual( - (Node **)(&(ref->refassgnexpr)), - parsetree); - } - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) - modifyAggregQual( - (Node **)(&(lfirst(l))), - parsetree); - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - - modifyAggregQual( - (Node **)(&(sub->subselect)), - (Query *)(sub->subselect)); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - - modifyAggregQual( - (Node **)(&(qry->qual)), - parsetree); - - modifyAggregQual( - (Node **)(&(qry->havingQual)), - parsetree); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in modifyAggregQual()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - - - } -} - - -static Node * -FindMatchingTLEntry(List *tlist, char *e_attname) -{ - List *i; - - foreach(i, tlist) - { - TargetEntry *tle = lfirst(i); - char *resname; - - resname = tle->resdom->resname; - if (!strcmp(e_attname, resname)) - return (tle->expr); - } - return NULL; -} - - -static Node * -make_null(Oid type) -{ - Const *c = makeNode(Const); - - c->consttype = type; - c->constlen = get_typlen(type); - c->constvalue = PointerGetDatum(NULL); - c->constisnull = true; - c->constbyval = get_typbyval(type); - return (Node *) c; -} - - -static void -apply_RIR_adjust_sublevel(Node *node, int sublevels_up) -{ - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - apply_RIR_adjust_sublevel( - (Node *)(tle->expr), - sublevels_up); - } - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - - apply_RIR_adjust_sublevel( - (Node *)(agg->target), - sublevels_up); - } - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - - apply_RIR_adjust_sublevel( - (Node *)(grp->entry), - sublevels_up); - } - break; - - case T_Expr: - { - Expr *exp = (Expr *)node; - - apply_RIR_adjust_sublevel( - (Node *)(exp->args), - sublevels_up); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - apply_RIR_adjust_sublevel( - (Node *)(iter->iterexpr), - sublevels_up); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - apply_RIR_adjust_sublevel( - (Node *)(ref->refupperindexpr), - sublevels_up); - - apply_RIR_adjust_sublevel( - (Node *)(ref->reflowerindexpr), - sublevels_up); - - apply_RIR_adjust_sublevel( - (Node *)(ref->refexpr), - sublevels_up); - - apply_RIR_adjust_sublevel( - (Node *)(ref->refassgnexpr), - sublevels_up); - } - break; - - case T_Var: - { - Var *var = (Var *)node; - - var->varlevelsup = sublevels_up; - } - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) { - apply_RIR_adjust_sublevel( - (Node *)lfirst(l), - sublevels_up); - } - } - break; - - case T_CaseExpr: - { - CaseExpr *exp = (CaseExpr *)node; - - apply_RIR_adjust_sublevel( - (Node *)(exp->args), - sublevels_up); - - apply_RIR_adjust_sublevel( - (Node *)(exp->defresult), - sublevels_up); - } - break; - - case T_CaseWhen: - { - CaseWhen *exp = (CaseWhen *)node; - - apply_RIR_adjust_sublevel( - (Node *)(exp->expr), - sublevels_up); - - apply_RIR_adjust_sublevel( - (Node *)(exp->result), - sublevels_up); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in attribute_used()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - - - } -} - - -static void -apply_RIR_view(Node **nodePtr, int rt_index, RangeTblEntry *rte, List *tlist, int *modified, int sublevels_up) -{ - Node *node = *nodePtr; - - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - apply_RIR_view( - (Node **)(&(tle->expr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - - apply_RIR_view( - (Node **)(&(agg->target)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - - apply_RIR_view( - (Node **)(&(grp->entry)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_Expr: - { - Expr *exp = (Expr *)node; - - apply_RIR_view( - (Node **)(&(exp->args)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - apply_RIR_view( - (Node **)(&(iter->iterexpr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - apply_RIR_view( - (Node **)(&(ref->refupperindexpr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - apply_RIR_view( - (Node **)(&(ref->reflowerindexpr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - apply_RIR_view( - (Node **)(&(ref->refexpr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - apply_RIR_view( - (Node **)(&(ref->refassgnexpr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_Var: - { - Var *var = (Var *)node; - - if (var->varlevelsup == sublevels_up && - var->varno == rt_index) { - Node *exp; - - if (var->varattno < 0) - elog(ERROR, "system column %s not available - %s is a view", get_attname(rte->relid, var->varattno), rte->relname); - exp = FindMatchingTLEntry( - tlist, - get_attname(rte->relid, - var->varattno)); - - if (exp == NULL) { - *nodePtr = make_null(var->vartype); - return; - } - - exp = copyObject(exp); - if (var->varlevelsup > 0) - apply_RIR_adjust_sublevel(exp, var->varlevelsup); - *nodePtr = exp; - *modified = TRUE; - } - } - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) - apply_RIR_view( - (Node **)(&(lfirst(l))), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - - apply_RIR_view( - (Node **)(&(sub->lefthand)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(sub->subselect)), - rt_index, - rte, - tlist, - modified, - sublevels_up + 1); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - - apply_RIR_view( - (Node **)(&(qry->targetList)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(qry->qual)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(qry->havingQual)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(qry->groupClause)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_CaseExpr: - { - CaseExpr *exp = (CaseExpr *)node; - - apply_RIR_view( - (Node **)(&(exp->args)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(exp->defresult)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - case T_CaseWhen: - { - CaseWhen *exp = (CaseWhen *)node; - - apply_RIR_view( - (Node **)(&(exp->expr)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - - apply_RIR_view( - (Node **)(&(exp->result)), - rt_index, - rte, - tlist, - modified, - sublevels_up); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in apply_RIR_view()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - } -} - - -static void -ApplyRetrieveRule(Query *parsetree, - RewriteRule *rule, - int rt_index, - int relation_level, - Relation relation, - int *modified) -{ - Query *rule_action = NULL; - Node *rule_qual; - List *rtable, - *rt; - int nothing, - rt_length; - int badsql = FALSE; - - rule_qual = rule->qual; - if (rule->actions) - { - if (length(rule->actions) > 1) /* ??? because we don't handle - * rules with more than one - * action? -ay */ - - return; - rule_action = copyObject(lfirst(rule->actions)); - nothing = FALSE; - } - else - nothing = TRUE; - - rtable = copyObject(parsetree->rtable); - foreach(rt, rtable) - { - RangeTblEntry *rte = lfirst(rt); - - /* - * this is to prevent add_missing_vars_to_base_rels() from adding - * a bogus entry to the new target list. - */ - rte->inFromCl = false; - } - rt_length = length(rtable); - - rtable = nconc(rtable, copyObject(rule_action->rtable)); - parsetree->rtable = rtable; - - rule_action->rtable = rtable; - OffsetVarNodes((Node *) rule_qual, rt_length, 0); - OffsetVarNodes((Node *) rule_action, rt_length, 0); - - ChangeVarNodes((Node *) rule_qual, - PRS2_CURRENT_VARNO + rt_length, rt_index, 0); - ChangeVarNodes((Node *) rule_action, - PRS2_CURRENT_VARNO + rt_length, rt_index, 0); - - if (relation_level) - { - apply_RIR_view((Node **) &parsetree, rt_index, - (RangeTblEntry *)nth(rt_index - 1, rtable), - rule_action->targetList, modified, 0); - apply_RIR_view((Node **) &rule_action, rt_index, - (RangeTblEntry *)nth(rt_index - 1, rtable), - rule_action->targetList, modified, 0); - } - else - { - HandleRIRAttributeRule(parsetree, rtable, rule_action->targetList, - rt_index, rule->attrno, modified, &badsql); - } - if (*modified && !badsql) { - AddQual(parsetree, rule_action->qual); - /* This will only work if the query made to the view defined by the following - * groupClause groups by the same attributes or does not use group at all! */ - if (parsetree->groupClause == NULL) - parsetree->groupClause=rule_action->groupClause; - AddHavingQual(parsetree, rule_action->havingQual); - parsetree->hasAggs = (rule_action->hasAggs || parsetree->hasAggs); - parsetree->hasSubLinks = (rule_action->hasSubLinks || parsetree->hasSubLinks); - } -} - - -static void -fireRIRonSubselect(Node *node) -{ - if (node == NULL) - return; - - switch(nodeTag(node)) { - case T_TargetEntry: - { - TargetEntry *tle = (TargetEntry *)node; - - fireRIRonSubselect( - (Node *)(tle->expr)); - } - break; - - case T_Aggreg: - { - Aggreg *agg = (Aggreg *)node; - - fireRIRonSubselect( - (Node *)(agg->target)); - } - break; - - case T_GroupClause: - { - GroupClause *grp = (GroupClause *)node; - - fireRIRonSubselect( - (Node *)(grp->entry)); - } - break; - - case T_Expr: - { - Expr *exp = (Expr *)node; - - fireRIRonSubselect( - (Node *)(exp->args)); - } - break; - - case T_Iter: - { - Iter *iter = (Iter *)node; - - fireRIRonSubselect( - (Node *)(iter->iterexpr)); - } - break; - - case T_ArrayRef: - { - ArrayRef *ref = (ArrayRef *)node; - - fireRIRonSubselect( - (Node *)(ref->refupperindexpr)); - fireRIRonSubselect( - (Node *)(ref->reflowerindexpr)); - fireRIRonSubselect( - (Node *)(ref->refexpr)); - fireRIRonSubselect( - (Node *)(ref->refassgnexpr)); - } - break; - - case T_Var: - break; - - case T_Param: - break; - - case T_Const: - break; - - case T_List: - { - List *l; - - foreach (l, (List *)node) - fireRIRonSubselect( - (Node *)(lfirst(l))); - } - break; - - case T_SubLink: - { - SubLink *sub = (SubLink *)node; - Query *qry; - - fireRIRonSubselect( - (Node *)(sub->lefthand)); - - qry = fireRIRrules((Query *)(sub->subselect)); - - fireRIRonSubselect( - (Node *)qry); - - sub->subselect = (Node *) qry; - } - break; - - case T_CaseExpr: - { - CaseExpr *exp = (CaseExpr *)node; - - fireRIRonSubselect( - (Node *)(exp->args)); - - fireRIRonSubselect( - (Node *)(exp->defresult)); - } - break; - - case T_CaseWhen: - { - CaseWhen *exp = (CaseWhen *)node; - - fireRIRonSubselect( - (Node *)(exp->expr)); - - fireRIRonSubselect( - (Node *)(exp->result)); - } - break; - - case T_Query: - { - Query *qry = (Query *)node; - - fireRIRonSubselect( - (Node *)(qry->targetList)); - - fireRIRonSubselect( - (Node *)(qry->qual)); - - fireRIRonSubselect( - (Node *)(qry->havingQual)); - - fireRIRonSubselect( - (Node *)(qry->groupClause)); - } - break; - - default: - elog(NOTICE, "unknown node tag %d in fireRIRonSubselect()", nodeTag(node)); - elog(NOTICE, "Node is: %s", nodeToString(node)); - break; - + * fireRIRonSubLink - + * Apply fireRIRrules() to each SubLink (subselect in expression) found + * in the given tree. + * + * NOTE: although this has the form of a walker, we cheat and modify the + * SubLink nodes in-place. It is caller's responsibility to ensure that + * no unwanted side-effects occur! + * + * This is unlike most of the other routines that recurse into subselects, + * because we must take control at the SubLink node in order to replace + * the SubLink's subselect link with the possibly-rewritten subquery. + */ +static bool +fireRIRonSubLink(Node *node, List *activeRIRs) +{ + if (node == NULL) + return false; + if (IsA(node, SubLink)) + { + SubLink *sub = (SubLink *) node; + /* Do what we came for */ + sub->subselect = (Node *) fireRIRrules((Query *) sub->subselect, + activeRIRs); + /* Fall through to process lefthand args of SubLink */ } + + /* + * Do NOT recurse into Query nodes, because fireRIRrules already + * processed subselects of subselects for us. + */ + return expression_tree_walker(node, fireRIRonSubLink, + (void *) activeRIRs); } @@ -2013,609 +748,571 @@ fireRIRonSubselect(Node *node) * Apply all RIR rules on each rangetable entry in a query */ static Query * -fireRIRrules(Query *parsetree) +fireRIRrules(Query *parsetree, List *activeRIRs) { - int rt_index; - RangeTblEntry *rte; - Relation rel; - List *locks; - RuleLock *rules; - RewriteRule *rule; - RewriteRule RIRonly; - int modified; - int i; - List *l; + int rt_index; + /* + * don't try to convert this into a foreach loop, because rtable list + * can get changed each time through... + */ rt_index = 0; - while(rt_index < length(parsetree->rtable)) { + while (rt_index < length(parsetree->rtable)) + { + RangeTblEntry *rte; + Relation rel; + List *locks; + RuleLock *rules; + RewriteRule *rule; + LOCKMODE lockmode; + bool relIsUsed; + int i; + ++rt_index; - if (!rangeTableEntry_used((Node *)parsetree, rt_index, 0)) - continue; - - rte = nth(rt_index - 1, parsetree->rtable); - rel = heap_openr(rte->relname); - if (rel->rd_rules == NULL) { - heap_close(rel); + rte = rt_fetch(rt_index, parsetree->rtable); + + /* + * A subquery RTE can't have associated rules, so there's nothing + * to do to this level of the query, but we must recurse into the + * subquery to expand any rule references in it. + */ + if (rte->rtekind == RTE_SUBQUERY) + { + rte->subquery = fireRIRrules(rte->subquery, activeRIRs); continue; } - rules = rel->rd_rules; - locks = NIL; + /* + * Joins and other non-relation RTEs can be ignored completely. + */ + if (rte->rtekind != RTE_RELATION) + continue; + + /* + * If the table is not referenced in the query, then we ignore it. + * This prevents infinite expansion loop due to new rtable entries + * inserted by expansion of a rule. A table is referenced if it is + * part of the join set (a source table), or is referenced by any + * Var nodes, or is the result table. + */ + relIsUsed = rangeTableEntry_used((Node *) parsetree, rt_index, 0); + + if (!relIsUsed && rt_index != parsetree->resultRelation) + 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 or not. + */ + if (rt_index == parsetree->resultRelation) + lockmode = NoLock; + else if (intMember(rt_index, parsetree->rowMarks)) + lockmode = RowShareLock; + else + lockmode = AccessShareLock; + + rel = heap_open(rte->relid, lockmode); /* * Collect the RIR rules that we must apply */ - for (i = 0; i < rules->numLocks; i++) { + rules = rel->rd_rules; + if (rules == NULL) + { + heap_close(rel, NoLock); + continue; + } + locks = NIL; + for (i = 0; i < rules->numLocks; i++) + { rule = rules->rules[i]; if (rule->event != CMD_SELECT) continue; - - if (rule->attrno > 0 && - !attribute_used((Node *)parsetree, - rt_index, - rule->attrno, 0)) - continue; + + if (rule->attrno > 0) + { + /* per-attr rule; do we need it? */ + if (!attribute_used((Node *) parsetree, rt_index, + rule->attrno, 0)) + continue; + } locks = lappend(locks, rule); } /* - * Check permissions + * If we found any, apply them --- but first check for recursion! */ - checkLockPerms(locks, parsetree, rt_index); + if (locks != NIL) + { + List *newActiveRIRs; + List *l; - /* - * Now apply them - */ - foreach (l, locks) { - rule = lfirst(l); - - RIRonly.event = rule->event; - RIRonly.attrno = rule->attrno; - RIRonly.qual = rule->qual; - RIRonly.actions = rule->actions; - - ApplyRetrieveRule(parsetree, - &RIRonly, - rt_index, - RIRonly.attrno == -1, - rel, - &modified); + if (oidMember(RelationGetRelid(rel), activeRIRs)) + elog(ERROR, "Infinite recursion detected in rules for relation %s", + RelationGetRelationName(rel)); + newActiveRIRs = lconso(RelationGetRelid(rel), activeRIRs); + + foreach(l, locks) + { + rule = lfirst(l); + + parsetree = ApplyRetrieveRule(parsetree, + rule, + rt_index, + rule->attrno == -1, + rel, + relIsUsed, + newActiveRIRs); + } } - heap_close(rel); + heap_close(rel, NoLock); } - fireRIRonSubselect((Node *) parsetree); - modifyAggregQual((Node **) &(parsetree->qual), parsetree); + /* + * Recurse into sublink subqueries, too. But we already did the ones + * in the rtable. + */ + if (parsetree->hasSubLinks) + query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, + QTW_IGNORE_RT_SUBQUERIES); + + /* + * If the query was marked having aggregates, check if this is still + * true after rewriting. Ditto for sublinks. Note there should be no + * aggs in the qual at this point. (Does this code still do anything + * useful? The view-becomes-subselect-in-FROM approach doesn't look + * like it could remove aggs or sublinks...) + */ + if (parsetree->hasAggs) + { + parsetree->hasAggs = checkExprHasAggs((Node *) parsetree); + if (parsetree->hasAggs) + if (checkExprHasAggs((Node *) parsetree->jointree)) + elog(ERROR, "fireRIRrules: failed to remove aggs from qual"); + } + if (parsetree->hasSubLinks) + parsetree->hasSubLinks = checkExprHasSubLink((Node *) parsetree); return parsetree; } /* - * idea is to fire regular rules first, then qualified instead - * rules and unqualified instead rules last. Any lemming is counted for. + * Modify the given query by adding 'AND rule_qual IS NOT TRUE' to its + * qualification. This is used to generate suitable "else clauses" for + * conditional INSTEAD rules. (Unfortunately we must use "x IS NOT TRUE", + * not just "NOT x" which the planner is much smarter about, else we will + * do the wrong thing when the qual evaluates to NULL.) + * + * 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 List * -orderRules(List *locks) -{ - List *regular = NIL; - List *instead_rules = NIL; - List *instead_qualified = NIL; - List *i; - - foreach(i, locks) - { - RewriteRule *rule_lock = (RewriteRule *) lfirst(i); - - if (rule_lock->isInstead) - { - if (rule_lock->qual == NULL) - instead_rules = lappend(instead_rules, rule_lock); - else - instead_qualified = lappend(instead_qualified, rule_lock); - } - else - regular = lappend(regular, rule_lock); - } - regular = nconc(regular, instead_qualified); - return nconc(regular, instead_rules); -} - - - static Query * -CopyAndAddQual(Query *parsetree, - List *actions, - Node *rule_qual, - int rt_index, - CmdType event) +CopyAndAddInvertedQual(Query *parsetree, + 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; - - rtable = new_tree->rtable; - rt_length = length(rtable); - rtable = append(rtable, listCopy(rule_action->rtable)); - new_tree->rtable = rtable; - OffsetVarNodes(new_qual, rt_length, 0); - ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO + rt_length, rt_index, 0); - } - /* XXX -- where current doesn't work for instead nothing.... yet */ - AddNotQual(new_tree, new_qual); + 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 */ + AddInvertedQual(new_tree, new_qual); return new_tree; } - /* * fireRules - * Iterate through rule locks applying rules. - * All rules create their own parsetrees. Instead rules - * with rule qualification save the original parsetree - * and add their negated qualification to it. Real instead - * rules finally throw away the original parsetree. * - * remember: reality is for dead birds -- glass + * Input arguments: + * parsetree - original query + * rt_index - RT index of result relation in original query + * event - type of rule event + * locks - list of rules to fire + * Output arguments: + * *instead_flag - set TRUE if any unqualified INSTEAD rule is found + * (must be initialized to FALSE) + * *qual_product - filled with modified original query if any qualified + * INSTEAD rule is found (must be initialized to NULL) + * Return value: + * list of rule actions adjusted for use with this query * + * Qualified INSTEAD rules generate their action with the qualification + * condition added. They also generate a modified version of the original + * query with the negated qualification added, so that it will run only for + * rows that the qualified action doesn't act on. (If there are multiple + * qualified INSTEAD rules, we AND all the negated quals onto a single + * modified original query.) We won't execute the original, unmodified + * query if we find either qualified or unqualified INSTEAD rules. If + * we find both, the modified original query is discarded too. */ static List * fireRules(Query *parsetree, int rt_index, CmdType event, - bool *instead_flag, List *locks, - List **qual_products) + bool *instead_flag, + Query **qual_product) { - RewriteInfo *info; List *results = NIL; List *i; - /* choose rule to fire from list of rules */ - if (locks == NIL) - { - return NIL; - } - - locks = orderRules(locks); /* real instead rules last */ foreach(i, locks) { RewriteRule *rule_lock = (RewriteRule *) lfirst(i); - Node *qual, - *event_qual; - List *actions; + Node *event_qual = rule_lock->qual; + List *actions = rule_lock->actions; + QuerySource qsrc; List *r; - /* - * Instead rules change the resultRelation of the query. So the - * permission checks on the initial resultRelation would never be - * done (this is normally done in the executor deep down). So we - * must do it here. The result relations resulting from earlier - * rewrites are already checked against the rules eventrelation - * owner (during matchLocks) and have the skipAcl flag set. - */ - if (rule_lock->isInstead && - parsetree->commandType != CMD_SELECT) + /* Determine correct QuerySource value for actions */ + if (rule_lock->isInstead) { - RangeTblEntry *rte; - int32 acl_rc; - int32 reqperm; - - switch (parsetree->commandType) - { - case CMD_INSERT: - reqperm = ACL_AP; - break; - default: - reqperm = ACL_WR; - break; - } - - rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1, - parsetree->rtable); - if (!rte->skipAcl) + if (event_qual != NULL) + qsrc = QSRC_QUAL_INSTEAD_RULE; + else { - acl_rc = pg_aclcheck(rte->relname, - GetPgUserName(), reqperm); - if (acl_rc != ACLCHECK_OK) - { - elog(ERROR, "%s: %s", - rte->relname, - aclcheck_error_strings[acl_rc]); - } + qsrc = QSRC_INSTEAD_RULE; + *instead_flag = true; /* report unqualified INSTEAD */ } } + else + qsrc = QSRC_NON_INSTEAD_RULE; - /* multiple rule action time */ - *instead_flag = rule_lock->isInstead; - event_qual = rule_lock->qual; - actions = rule_lock->actions; - if (event_qual != NULL && *instead_flag) + if (qsrc == QSRC_QUAL_INSTEAD_RULE) { - Query *qual_product; - RewriteInfo qual_info; - - /* ---------- - * If there are instead rules with qualifications, - * the original query is still performed. But all - * the negated rule qualifications of the instead - * rules are added so it does it's actions only - * in cases where the rule quals of all instead - * rules are false. Think of it as the default - * action in a case. We save this in *qual_products - * so deepRewriteQuery() can add it to the query - * list after we mangled it up enough. - * ---------- + /* + * If there are INSTEAD rules with qualifications, the + * original query is still performed. But all the negated rule + * qualifications of the INSTEAD rules are added so it does + * its actions only in cases where the rule quals of all + * INSTEAD rules are false. Think of it as the default action + * in a case. We save this in *qual_product so + * RewriteQuery() can add it to the query list after we + * mangled it up enough. + * + * If we have already found an unqualified INSTEAD rule, + * then *qual_product won't be used, so don't bother building it. */ - if (*qual_products == NIL) - qual_product = parsetree; - else - qual_product = (Query *) nth(0, *qual_products); - - qual_info.event = qual_product->commandType; - 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 = lappend(NIL, qual_product); + if (! *instead_flag) + { + if (*qual_product == NULL) + *qual_product = parsetree; + *qual_product = CopyAndAddInvertedQual(*qual_product, + event_qual, + rt_index, + event); + } } + /* Now process the rule's actions and add them to the result list */ foreach(r, actions) { Query *rule_action = lfirst(r); - Node *rule_qual = copyObject(event_qual); 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, - rt_index, event, instead_flag); - - /* handle escapable cases, or those handled by other code */ - if (info->nothing) - { - if (*instead_flag) - return NIL; - else - 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 - */ - qual = parsetree->qual; - AddQual(info->rule_action, qual); - - if (info->rule_qual != NULL) - AddQual(info->rule_action, info->rule_qual); + rule_action = rewriteRuleAction(parsetree, rule_action, + event_qual, rt_index, event); - /*-------------------------------------------------- - * 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: - * rewriting due to retrieve rules - *-------------------------------------------------- - */ - info->rule_action->rtable = info->rt; - /* - ProcessRetrieveQuery(info->rule_action, info->rt, - &orig_instead_flag, TRUE); - */ - - /*-------------------------------------------------- - * Step 4 - * Simplify? hey, no algorithm for simplification... let - * the planner do it. - *-------------------------------------------------- - */ - results = lappend(results, info->rule_action); + rule_action->querySource = qsrc; + rule_action->canSetTag = false; /* might change later */ - pfree(info); + results = lappend(results, rule_action); } - - /* ---------- - * If this was an unqualified instead rule, - * throw away an eventually saved 'default' parsetree - * ---------- - */ - if (event_qual == NULL && *instead_flag) - *qual_products = NIL; } + return results; } - +/* + * RewriteQuery - + * rewrites the query and apply the rules again on the queries rewritten + * + * rewrite_events is a list of open query-rewrite actions, so we can detect + * infinite recursion. + */ static List * -RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products) +RewriteQuery(Query *parsetree, List *rewrite_events) { - CmdType event; - List *product_queries = NIL; - int result_relation = 0; - RangeTblEntry *rt_entry; - Relation rt_entry_relation = NULL; - RuleLock *rt_entry_locks = NULL; - - Assert(parsetree != NULL); - - event = parsetree->commandType; - - /* - * SELECT rules are handled later when we have all the - * queries that should get executed - */ - if (event == CMD_SELECT) - return NIL; - - /* - * Utilities aren't rewritten at all - why is this here? - */ - if (event == CMD_UTILITY) - return NIL; - - /* - * only for a delete may the targetlist be NULL - */ - if (event != CMD_DELETE) - Assert(parsetree->targetList != NULL); - - result_relation = parsetree->resultRelation; + CmdType event = parsetree->commandType; + bool instead = false; + Query *qual_product = NULL; + List *rewritten = NIL; /* - * the statement is an update, insert or delete - fire rules - * on it. + * If the statement is an update, insert or delete - fire rules on it. + * + * SELECT rules are handled later when we have all the queries that + * should get executed. Also, utilities aren't rewritten at all + * (do we still need that check?) */ - rt_entry = rt_fetch(result_relation, parsetree->rtable); - rt_entry_relation = heap_openr(rt_entry->relname); - rt_entry_locks = rt_entry_relation->rd_rules; - heap_close(rt_entry_relation); - - if (rt_entry_locks != NULL) + if (event != CMD_SELECT && event != CMD_UTILITY) { - List *locks = - matchLocks(event, rt_entry_locks, result_relation, parsetree); - - product_queries = - fireRules(parsetree, - result_relation, - event, - instead_flag, - locks, - qual_products); - } + int result_relation; + RangeTblEntry *rt_entry; + Relation rt_entry_relation; + List *locks; - return product_queries; + result_relation = parsetree->resultRelation; + Assert(result_relation != 0); + rt_entry = rt_fetch(result_relation, parsetree->rtable); + 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. + */ + rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock); + /* + * If it's an INSERT or UPDATE, rewrite the targetlist into standard + * form. This will be needed by the planner anyway, and doing it now + * ensures that any references to NEW.field will behave sanely. + */ + if (event == CMD_INSERT || event == CMD_UPDATE) + rewriteTargetList(parsetree, rt_entry_relation); -/* - * to avoid infinite recursion, we restrict the number of times a query - * can be rewritten. Detecting cycles is left for the reader as an excercise. - */ -#ifndef REWRITE_INVOKE_MAX -#define REWRITE_INVOKE_MAX 10 -#endif + /* + * Collect and apply the appropriate rules. + */ + locks = matchLocks(event, rt_entry_relation->rd_rules, + result_relation, parsetree); -static int numQueryRewriteInvoked = 0; + if (locks != NIL) + { + List *product_queries; -/* - * deepRewriteQuery - - * rewrites the query and apply the rules again on the queries rewritten - */ -static List * -deepRewriteQuery(Query *parsetree) -{ - List *n; - List *rewritten = NIL; - List *result = NIL; - bool instead; - List *qual_products = NIL; + product_queries = fireRules(parsetree, + result_relation, + event, + locks, + &instead, + &qual_product); + /* + * If we got any product queries, recursively rewrite them + * --- but first check for recursion! + */ + if (product_queries != NIL) + { + List *n; + rewrite_event *rev; + foreach(n, rewrite_events) + { + rev = (rewrite_event *) lfirst(n); + if (rev->relation == RelationGetRelid(rt_entry_relation) && + rev->event == event) + elog(ERROR, "Infinite recursion detected in rules for relation %s", + RelationGetRelationName(rt_entry_relation)); + } - if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX) - { - elog(ERROR, "query rewritten %d times, may contain cycles", - numQueryRewriteInvoked - 1); - } + rev = (rewrite_event *) palloc(sizeof(rewrite_event)); + rev->relation = RelationGetRelid(rt_entry_relation); + rev->event = event; + rewrite_events = lcons(rev, rewrite_events); - instead = FALSE; - result = RewriteQuery(parsetree, &instead, &qual_products); + foreach(n, product_queries) + { + Query *pt = (Query *) lfirst(n); + List *newstuff; - foreach(n, result) - { - Query *pt = lfirst(n); - List *newstuff = NIL; + newstuff = RewriteQuery(pt, rewrite_events); + rewritten = nconc(rewritten, newstuff); + } + } + } - newstuff = deepRewriteQuery(pt); - if (newstuff != NIL) - rewritten = nconc(rewritten, newstuff); + heap_close(rt_entry_relation, NoLock); /* keep lock! */ } - /* ---------- - * qual_products are the original query with the negated - * rule qualification of an instead rule - * ---------- - */ - if (qual_products != NIL) - rewritten = nconc(rewritten, qual_products); - - /* ---------- - * The original query is appended last if not instead - * because update and delete rule actions might not do - * anything if they are invoked after the update or - * delete is performed. The command counter increment - * between the query execution makes the deleted (and - * maybe the updated) tuples disappear so the scans - * for them in the rule actions cannot find them. - * ---------- - */ - if (!instead) - rewritten = lappend(rewritten, parsetree); - - return rewritten; -} - - -/* - * QueryOneRewrite - - * rewrite one query - */ -static List * -QueryRewriteOne(Query *parsetree) -{ - numQueryRewriteInvoked = 0; - /* - * take a deep breath and apply all the rewrite rules - ay - */ - return deepRewriteQuery(parsetree); -} - - -/* ---------- - * RewritePreprocessQuery - - * adjust details in the parsetree, the rule system - * depends on - * ---------- - */ -static void -RewritePreprocessQuery(Query *parsetree) -{ - /* ---------- - * if the query has a resultRelation, reassign the - * result domain numbers to the attribute numbers in the - * target relation. FixNew() depends on it when replacing - * *new* references in a rule action by the expressions - * from the rewritten query. - * ---------- + * For INSERTs, the original query is done first; for UPDATE/DELETE, + * it is done last. This is needed because update and delete rule + * actions might not do anything if they are invoked after the update + * or delete is performed. The command counter increment between the + * query executions makes the deleted (and maybe the updated) tuples + * disappear so the scans for them in the rule actions cannot find + * them. + * + * If we found any unqualified INSTEAD, the original query is not + * done at all, in any form. Otherwise, we add the modified form + * if qualified INSTEADs were found, else the unmodified form. */ - if (parsetree->resultRelation > 0) + if (!instead) { - RangeTblEntry *rte; - Relation rd; - List *tl; - TargetEntry *tle; - int resdomno; - - rte = (RangeTblEntry *) nth(parsetree->resultRelation - 1, - parsetree->rtable); - rd = heap_openr(rte->relname); - - foreach(tl, parsetree->targetList) + if (parsetree->commandType == CMD_INSERT) { - tle = (TargetEntry *) lfirst(tl); - resdomno = attnameAttNum(rd, tle->resdom->resname); - tle->resdom->resno = resdomno; + if (qual_product != NULL) + rewritten = lcons(qual_product, rewritten); + else + rewritten = lcons(parsetree, rewritten); + } + else + { + if (qual_product != NULL) + rewritten = lappend(rewritten, qual_product); + else + rewritten = lappend(rewritten, parsetree); } - - heap_close(rd); } + + return rewritten; } /* * QueryRewrite - - * rewrite one query via query rewrite system, possibly returning 0 - * or many queries + * Primary entry point to the query rewriter. + * 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. */ List * QueryRewrite(Query *parsetree) { - List *querylist; - List *results = NIL; - List *l; - Query *query; + List *querylist; + List *results = NIL; + List *l; + CmdType origCmdType; + bool foundOriginalQuery; + Query *lastInstead; /* * Step 1 * - * There still seems something broken with the resdom numbers - * so we reassign them first. + * Apply all non-SELECT rules possibly getting 0 or many queries */ - RewritePreprocessQuery(parsetree); + querylist = RewriteQuery(parsetree, NIL); /* * Step 2 * - * Apply all non-SELECT rules possibly getting 0 or many queries + * Apply all the RIR rules on each query */ - querylist = QueryRewriteOne(parsetree); + foreach(l, querylist) + { + Query *query = (Query *) lfirst(l); + + query = fireRIRrules(query, NIL); + + /* + * If the query target was rewritten as a view, complain. + */ + if (query->resultRelation) + { + RangeTblEntry *rte = rt_fetch(query->resultRelation, + query->rtable); + + if (rte->rtekind == RTE_SUBQUERY) + { + switch (query->commandType) + { + case CMD_INSERT: + elog(ERROR, "Cannot insert into a view" + "\n\tYou need an unconditional ON INSERT DO INSTEAD rule"); + break; + case CMD_UPDATE: + elog(ERROR, "Cannot update a view" + "\n\tYou need an unconditional ON UPDATE DO INSTEAD rule"); + break; + case CMD_DELETE: + elog(ERROR, "Cannot delete from a view" + "\n\tYou need an unconditional ON DELETE DO INSTEAD rule"); + break; + default: + elog(ERROR, "QueryRewrite: unexpected commandType %d", + (int) query->commandType); + break; + } + } + } + + results = lappend(results, query); + } /* * Step 3 * - * Apply all the RIR rules on each query + * Determine which, if any, of the resulting queries is supposed to set + * the command-result tag; and update the canSetTag fields accordingly. + * + * If the original query is still in the list, it sets the command tag. + * Otherwise, the last INSTEAD query of the same kind as the original + * is allowed to set the tag. (Note these rules can leave us with no + * query setting the tag. The tcop code has to cope with this by + * setting up a default tag based on the original un-rewritten query.) + * + * The Asserts verify that at most one query in the result list is marked + * canSetTag. If we aren't checking asserts, we can fall out of the loop + * as soon as we find the original query. */ - foreach (l, querylist) { - query = (Query *)lfirst(l); - results = lappend(results, fireRIRrules(query)); + origCmdType = parsetree->commandType; + foundOriginalQuery = false; + lastInstead = NULL; + + foreach(l, results) + { + Query *query = (Query *) lfirst(l); + + if (query->querySource == QSRC_ORIGINAL) + { + Assert(query->canSetTag); + Assert(!foundOriginalQuery); + foundOriginalQuery = true; +#ifndef USE_ASSERT_CHECKING + break; +#endif + } + else + { + Assert(!query->canSetTag); + if (query->commandType == origCmdType && + (query->querySource == QSRC_INSTEAD_RULE || + query->querySource == QSRC_QUAL_INSTEAD_RULE)) + lastInstead = query; + } } + if (!foundOriginalQuery && lastInstead != NULL) + lastInstead->canSetTag = true; + return results; } - -