1 /*-------------------------------------------------------------------------
5 * Copyright (c) 1994, Regents of the University of California
9 * $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.1.1.1 1996/07/09 06:21:51 scrappy Exp $
11 *-------------------------------------------------------------------------
14 #include "miscadmin.h"
15 #include "utils/palloc.h"
16 #include "utils/elog.h"
17 #include "utils/rel.h"
18 #include "nodes/pg_list.h"
19 #include "nodes/primnodes.h"
21 #include "parser/parsetree.h" /* for parsetree manipulation */
22 #include "nodes/parsenodes.h"
24 #include "rewrite/rewriteSupport.h"
25 #include "rewrite/rewriteHandler.h"
26 #include "rewrite/rewriteManip.h"
27 #include "rewrite/locks.h"
29 #include "commands/creatinh.h"
30 #include "access/heapam.h"
32 static void ApplyRetrieveRule(Query *parsetree, RewriteRule *rule,
33 int rt_index, int relation_level, int *modified);
34 static List *fireRules(Query *parsetree, int rt_index, CmdType event,
35 bool *instead_flag, List *locks, List **qual_products);
36 static List *deepRewriteQuery(Query *parsetree);
40 * Gather meta information about parsetree, and rule. Fix rule body
41 * and qualifier so that they can be mixed with the parsetree and
42 * maintain semantic validity
45 gatherRewriteMeta(Query *parsetree,
56 info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
57 info->rt_index = rt_index;
59 info->instead_flag = *instead_flag;
60 /* info->rule_action = rule_action; this needs to be a copy here, I think! - jolly*/
61 info->rule_action = (Query*)copyObject(rule_action);
62 info->rule_qual = (Node*)copyObject(rule_qual);
63 info->nothing = FALSE;
64 info->action = info->rule_action->commandType;
65 if (info->rule_action == NULL) info->nothing = TRUE;
69 info->current_varno = rt_index;
70 info->rt = parsetree->rtable;
71 rt_length = length(info->rt);
72 info->rt = append(info->rt, info->rule_action->rtable);
75 info->new_varno = PRS2_NEW_VARNO + rt_length;
76 OffsetVarNodes(info->rule_action->qual, rt_length);
77 OffsetVarNodes((Node*)info->rule_action->targetList, rt_length);
78 OffsetVarNodes(info->rule_qual, rt_length);
79 ChangeVarNodes((Node*)info->rule_action->qual,
80 PRS2_CURRENT_VARNO+rt_length, rt_index);
81 ChangeVarNodes((Node*)info->rule_action->targetList,
82 PRS2_CURRENT_VARNO+rt_length, rt_index);
83 ChangeVarNodes(info->rule_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
86 * bug here about replace CURRENT -- sort of
87 * replace current is deprecated now so this code shouldn't really
88 * need to be so clutzy but.....
90 if (info->action != CMD_SELECT) { /* i.e update XXXXX */
91 int new_result_reln = 0;
92 result_reln = info->rule_action->resultRelation;
93 switch (result_reln) {
94 case PRS2_CURRENT_VARNO: new_result_reln = rt_index;
96 case PRS2_NEW_VARNO: /* XXX */
98 new_result_reln = result_reln + rt_length;
101 info->rule_action->resultRelation = new_result_reln;
108 OptimizeRIRRules(List *locks)
110 List *attr_level = NIL, *i;
111 List *relation_level = NIL;
114 RewriteRule *rule_lock = lfirst(i);
116 if (rule_lock->attrno == -1)
117 relation_level = lappend(relation_level, rule_lock);
119 attr_level = lappend(attr_level, rule_lock);
121 return nconc(relation_level, attr_level);
125 * idea is to put instead rules before regular rules so that
126 * excess semantically queasy queries aren't processed
129 orderRules(List *locks)
131 List *regular = NIL, *i;
132 List *instead_rules = NIL;
135 RewriteRule *rule_lock = (RewriteRule *)lfirst(i);
137 if (rule_lock->isInstead)
138 instead_rules = lappend(instead_rules, rule_lock);
140 regular = lappend(regular, rule_lock);
142 return nconc(regular, instead_rules);
146 AllRetrieve(List *actions)
150 foreach(n, actions) {
151 Query *pt = lfirst(n);
154 * in the old postgres code, we check whether command_type is
155 * a consp of '('*'.commandType). but we've never supported transitive
156 * closures. Hence removed - ay 10/94.
158 if (pt->commandType != CMD_SELECT)
165 FireRetrieveRulesAtQuery(Query *parsetree,
172 RuleLock *rt_entry_locks = NULL;
175 if ((rt_entry_locks = relation->rd_rules) == NULL)
178 locks = matchLocks(CMD_SELECT, rt_entry_locks, rt_index, parsetree);
180 /* find all retrieve instead */
182 RewriteRule *rule_lock = (RewriteRule *)lfirst(i);
184 if (!rule_lock->isInstead)
186 work = lappend(work, rule_lock);
189 work = OptimizeRIRRules(locks);
191 RewriteRule *rule_lock = lfirst(i);
193 int modified = FALSE;
195 relation_level = (rule_lock->attrno == -1);
196 if (rule_lock->actions == NIL) {
197 *instead_flag = TRUE;
201 length(rule_lock->actions) >= 2 &&
202 AllRetrieve(rule_lock->actions)) {
203 *instead_flag = TRUE;
204 return rule_lock->actions;
206 ApplyRetrieveRule(parsetree, rule_lock, rt_index, relation_level,
209 *instead_flag = TRUE;
210 FixResdomTypes(parsetree->targetList);
211 return lcons(parsetree,NIL);
219 /* Idea is like this:
221 * retrieve-instead-retrieve rules have different semantics than update nodes
222 * Separate RIR rules from others. Pass others to FireRules.
223 * Order RIR rules and process.
225 * side effect: parsetree's rtable field might be changed
228 ApplyRetrieveRule(Query *parsetree,
234 Query *rule_action = NULL;
237 int nothing, rt_length;
240 rule_qual = rule->qual;
242 if (length(rule->actions) > 1) /* ??? because we don't handle rules
243 with more than one action? -ay */
245 rule_action = copyObject(lfirst(rule->actions));
251 rtable = copyObject(parsetree->rtable);
252 foreach (rt, rtable) {
253 RangeTblEntry *rte = lfirst(rt);
255 * this is to prevent add_missing_vars_to_base_rels() from
256 * adding a bogus entry to the new target list.
258 rte->inFromCl = false;
260 rt_length = length(rtable);
261 rtable = nconc(rtable, copyObject(rule_action->rtable));
262 parsetree->rtable = rtable;
264 rule_action->rtable = rtable;
265 OffsetVarNodes(rule_action->qual, rt_length);
266 OffsetVarNodes((Node*)rule_action->targetList, rt_length);
267 OffsetVarNodes(rule_qual, rt_length);
268 ChangeVarNodes(rule_action->qual,
269 PRS2_CURRENT_VARNO+rt_length, rt_index);
270 ChangeVarNodes((Node*)rule_action->targetList,
271 PRS2_CURRENT_VARNO+rt_length, rt_index);
272 ChangeVarNodes(rule_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
273 if (relation_level) {
274 HandleViewRule(parsetree, rtable, rule_action->targetList, rt_index,
277 HandleRIRAttributeRule(parsetree, rtable, rule_action->targetList,
278 rt_index, rule->attrno, modified, &badsql);
280 if (*modified && !badsql)
281 AddQual(parsetree, rule_action->qual);
285 ProcessRetrieveQuery(Query *parsetree,
291 List *product_queries = NIL;
294 foreach (rt, rtable) {
295 RangeTblEntry *rt_entry = lfirst(rt);
296 Relation rt_entry_relation = NULL;
300 rt_entry_relation = heap_openr(rt_entry->relname);
302 if (rt_entry_relation->rd_rules != NULL) {
304 FireRetrieveRulesAtQuery(parsetree,
310 heap_close(rt_entry_relation);
317 foreach (rt, rtable) {
318 RangeTblEntry *rt_entry = lfirst(rt);
319 Relation rt_entry_relation = NULL;
320 RuleLock *rt_entry_locks = NULL;
323 List *dummy_products;
326 rt_entry_relation = heap_openr(rt_entry->relname);
327 rt_entry_locks = rt_entry_relation->rd_rules;
328 heap_close(rt_entry_relation);
330 if (rt_entry_locks) {
332 matchLocks(CMD_SELECT, rt_entry_locks, rt_index, parsetree);
335 result = fireRules(parsetree, rt_index, CMD_SELECT,
336 instead_flag, locks, &dummy_products);
338 return lappend(NIL, result);
340 product_queries = nconc(product_queries, result);
343 return product_queries;
347 CopyAndAddQual(Query *parsetree,
353 Query *new_tree = (Query *) copyObject(parsetree);
354 Node *new_qual = NULL;
355 Query *rule_action = NULL;
358 rule_action = lfirst(actions);
359 if (rule_qual != NULL)
360 new_qual = (Node *)copyObject(rule_qual);
361 if (rule_action != NULL) {
365 rtable = new_tree->rtable;
366 rt_length = length(rtable);
367 rtable = append(rtable,listCopy(rule_action->rtable));
368 new_tree->rtable = rtable;
369 OffsetVarNodes(new_qual, rt_length);
370 ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO+rt_length, rt_index);
372 /* XXX -- where current doesn't work for instead nothing.... yet*/
373 AddNotQual(new_tree, new_qual);
381 * Iterate through rule locks applying rules. After an instead rule
382 * rule has been applied, return just new parsetree and let RewriteQuery
383 * start the process all over again. The locks are reordered to maintain
384 * sensible semantics. remember: reality is for dead birds -- glass
388 fireRules(Query *parsetree,
393 List **qual_products)
399 /* choose rule to fire from list of rules */
401 (void) ProcessRetrieveQuery(parsetree,
405 return lappend(NIL, parsetree);
410 locks = orderRules(locks); /* instead rules first */
412 RewriteRule *rule_lock = (RewriteRule *)lfirst(i);
413 Node *qual, *event_qual;
416 bool orig_instead_flag = *instead_flag;
418 /* multiple rule action time */
419 *instead_flag = rule_lock->isInstead;
420 event_qual = rule_lock->qual;
421 actions = rule_lock->actions;
422 if (event_qual != NULL && *instead_flag)
424 lappend(*qual_products,
425 CopyAndAddQual(parsetree, actions, event_qual,
427 foreach (r, actions) {
428 Query *rule_action = lfirst(r);
429 Node *rule_qual = copyObject(event_qual);
431 /*--------------------------------------------------
433 * Rewrite current.attribute or current to tuple variable
434 * this appears to be done in parser?
435 *--------------------------------------------------
437 info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
438 rt_index,event,instead_flag);
440 /* handle escapable cases, or those handled by other code */
448 if (info->action == info->event &&
449 info->event == CMD_SELECT)
453 * Event Qualification forces copying of parsetree --- XXX
454 * and splitting into two queries one w/rule_qual, one
455 * w/NOT rule_qual. Also add user query qual onto rule action
457 qual = parsetree->qual;
458 AddQual(info->rule_action, qual);
460 if (info->rule_qual != NULL)
461 AddQual(info->rule_action, info->rule_qual);
463 /*--------------------------------------------------
465 * Rewrite new.attribute w/ right hand side of target-list
466 * entry for appropriate field name in insert/update
467 *--------------------------------------------------
469 if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE)) {
470 FixNew(info, parsetree);
473 /*--------------------------------------------------
475 * rewriting due to retrieve rules
476 *--------------------------------------------------
478 info->rule_action->rtable = info->rt;
479 (void) ProcessRetrieveQuery(info->rule_action, info->rt,
480 &orig_instead_flag, TRUE);
482 /*--------------------------------------------------
484 * Simplify? hey, no algorithm for simplification... let
486 *--------------------------------------------------
488 results = lappend(results, info->rule_action);
492 if (*instead_flag) break;
498 RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
501 List *product_queries = NIL;
502 int result_relation = 0;
504 Assert(parsetree != NULL);
506 event = parsetree->commandType;
508 if (event == CMD_UTILITY)
512 * only for a delete may the targetlist be NULL
514 if (event != CMD_DELETE) {
515 Assert(parsetree->targetList != NULL);
518 result_relation = parsetree->resultRelation;
520 if (event != CMD_SELECT) {
522 * the statement is an update, insert or delete
524 RangeTblEntry *rt_entry;
525 Relation rt_entry_relation = NULL;
526 RuleLock *rt_entry_locks = NULL;
528 rt_entry = rt_fetch(result_relation, parsetree->rtable);
529 rt_entry_relation = heap_openr(rt_entry->relname);
530 rt_entry_locks = rt_entry_relation->rd_rules;
531 heap_close(rt_entry_relation);
533 if (rt_entry_locks != NULL) {
535 matchLocks(event, rt_entry_locks, result_relation, parsetree);
545 return product_queries;
548 * the statement is a select
552 other = copyObject(parsetree); /* ApplyRetrieveRule changes the
555 ProcessRetrieveQuery(other, parsetree->rtable,
556 instead_flag, FALSE);
561 * to avoid infinite recursion, we restrict the number of times a query
562 * can be rewritten. Detecting cycles is left for the reader as an excercise.
564 #ifndef REWRITE_INVOKE_MAX
565 #define REWRITE_INVOKE_MAX 10
568 static int numQueryRewriteInvoked = 0;
572 * rewrite one query via QueryRewrite system, possibly returning 0, or many
576 QueryRewrite(Query *parsetree)
578 numQueryRewriteInvoked = 0;
581 * take a deep breath and apply all the rewrite rules - ay
583 return deepRewriteQuery(parsetree);
588 * rewrites the query and apply the rules again on the queries rewritten
591 deepRewriteQuery(Query *parsetree)
594 List *rewritten = NIL;
597 List *qual_products = NIL;
599 if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX) {
600 elog(WARN, "query rewritten %d times, may contain cycles",
601 numQueryRewriteInvoked-1);
605 result = RewriteQuery(parsetree, &instead, &qual_products);
607 rewritten = lcons(parsetree, NIL);
610 Query *pt = lfirst(n);
611 List *newstuff = NIL;
613 newstuff = deepRewriteQuery(pt);
615 rewritten = nconc(rewritten, newstuff);
617 if (qual_products != NIL)
618 rewritten = nconc(rewritten, qual_products);