]> granicus.if.org Git - postgresql/blob - src/backend/rewrite/rewriteHandler.c
Yohoo UNIONS of VIEWS.
[postgresql] / src / backend / rewrite / rewriteHandler.c
1 /*-------------------------------------------------------------------------
2  *
3  * rewriteHandler.c--
4  *
5  * Copyright (c) 1994, Regents of the University of California
6  *
7  *
8  * IDENTIFICATION
9  *        $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteHandler.c,v 1.10 1998/01/09 05:48:17 momjian Exp $
10  *
11  *-------------------------------------------------------------------------
12  */
13 #include "postgres.h"
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"
20
21 #include "parser/parsetree.h"   /* for parsetree manipulation */
22 #include "nodes/parsenodes.h"
23
24 #include "rewrite/rewriteSupport.h"
25 #include "rewrite/rewriteHandler.h"
26 #include "rewrite/rewriteManip.h"
27 #include "rewrite/locks.h"
28
29 #include "commands/creatinh.h"
30 #include "access/heapam.h"
31
32 static void
33 ApplyRetrieveRule(Query *parsetree, RewriteRule *rule,
34                                   int rt_index, int relation_level, int *modified);
35 static List *
36 fireRules(Query *parsetree, int rt_index, CmdType event,
37                   bool *instead_flag, List *locks, List **qual_products);
38 static List *deepRewriteQuery(Query *parsetree);
39
40 /*
41  * gatherRewriteMeta -
42  *        Gather meta information about parsetree, and rule. Fix rule body
43  *        and qualifier so that they can be mixed with the parsetree and
44  *        maintain semantic validity
45  */
46 static RewriteInfo *
47 gatherRewriteMeta(Query *parsetree,
48                                   Query *rule_action,
49                                   Node *rule_qual,
50                                   int rt_index,
51                                   CmdType event,
52                                   bool *instead_flag)
53 {
54         RewriteInfo *info;
55         int                     rt_length;
56         int                     result_reln;
57
58         info = (RewriteInfo *) palloc(sizeof(RewriteInfo));
59         info->rt_index = rt_index;
60         info->event = event;
61         info->instead_flag = *instead_flag;
62         info->rule_action = (Query *) copyObject(rule_action);
63         info->rule_qual = (Node *) copyObject(rule_qual);
64         if (info->rule_action == NULL)
65                 info->nothing = TRUE;
66         else
67         {
68                 info->nothing = FALSE;
69                 info->action = info->rule_action->commandType;
70                 info->current_varno = rt_index;
71                 info->rt = parsetree->rtable;
72                 rt_length = length(info->rt);
73                 info->rt = append(info->rt, info->rule_action->rtable);
74
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,
84                                            PRS2_CURRENT_VARNO + rt_length, rt_index);
85
86                 /*
87                  * bug here about replace CURRENT  -- sort of replace current is
88                  * deprecated now so this code shouldn't really need to be so
89                  * clutzy but.....
90                  */
91                 if (info->action != CMD_SELECT)
92                 {                                               /* i.e update XXXXX */
93                         int                     new_result_reln = 0;
94
95                         result_reln = info->rule_action->resultRelation;
96                         switch (result_reln)
97                         {
98                                 case PRS2_CURRENT_VARNO:
99                                         new_result_reln = rt_index;
100                                         break;
101                                 case PRS2_NEW_VARNO:    /* XXX */
102                                 default:
103                                         new_result_reln = result_reln + rt_length;
104                                         break;
105                         }
106                         info->rule_action->resultRelation = new_result_reln;
107                 }
108         }
109         return info;
110 }
111
112 static List *
113 OptimizeRIRRules(List *locks)
114 {
115         List       *attr_level = NIL,
116                            *i;
117         List       *relation_level = NIL;
118
119         foreach(i, locks)
120         {
121                 RewriteRule *rule_lock = lfirst(i);
122
123                 if (rule_lock->attrno == -1)
124                         relation_level = lappend(relation_level, rule_lock);
125                 else
126                         attr_level = lappend(attr_level, rule_lock);
127         }
128         return nconc(relation_level, attr_level);
129 }
130
131 /*
132  * idea is to put instead rules before regular rules so that
133  * excess semantically queasy queries aren't processed
134  */
135 static List *
136 orderRules(List *locks)
137 {
138         List       *regular = NIL,
139                            *i;
140         List       *instead_rules = NIL;
141
142         foreach(i, locks)
143         {
144                 RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
145
146                 if (rule_lock->isInstead)
147                         instead_rules = lappend(instead_rules, rule_lock);
148                 else
149                         regular = lappend(regular, rule_lock);
150         }
151         return nconc(regular, instead_rules);
152 }
153
154 static int
155 AllRetrieve(List *actions)
156 {
157         List       *n;
158
159         foreach(n, actions)
160         {
161                 Query      *pt = lfirst(n);
162
163                 /*
164                  * in the old postgres code, we check whether command_type is a
165                  * consp of '('*'.commandType). but we've never supported
166                  * transitive closures. Hence removed    - ay 10/94.
167                  */
168                 if (pt->commandType != CMD_SELECT)
169                         return false;
170         }
171         return true;
172 }
173
174 static List *
175 FireRetrieveRulesAtQuery(Query *parsetree,
176                                                  int rt_index,
177                                                  Relation relation,
178                                                  bool *instead_flag,
179                                                  int rule_flag)
180 {
181         List       *i,
182                            *locks;
183         RuleLock   *rt_entry_locks = NULL;
184         List       *work = NIL;
185
186         if ((rt_entry_locks = relation->rd_rules) == NULL)
187                 return NIL;
188
189         locks = matchLocks(CMD_SELECT, rt_entry_locks, rt_index, parsetree);
190
191         /* find all retrieve instead */
192         foreach(i, locks)
193         {
194                 RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
195
196                 if (!rule_lock->isInstead)
197                         continue;
198                 work = lappend(work, rule_lock);
199         }
200         if (work != NIL)
201         {
202                 work = OptimizeRIRRules(locks);
203                 foreach(i, work)
204                 {
205                         RewriteRule *rule_lock = lfirst(i);
206                         int                     relation_level;
207                         int                     modified = FALSE;
208
209                         relation_level = (rule_lock->attrno == -1);
210                         if (rule_lock->actions == NIL)
211                         {
212                                 *instead_flag = TRUE;
213                                 return NIL;
214                         }
215                         if (!rule_flag &&
216                                 length(rule_lock->actions) >= 2 &&
217                                 AllRetrieve(rule_lock->actions))
218                         {
219                                 *instead_flag = TRUE;
220                                 return rule_lock->actions;
221                         }
222                         ApplyRetrieveRule(parsetree, rule_lock, rt_index, relation_level,
223                                                           &modified);
224                         if (modified)
225                         {
226                                 *instead_flag = TRUE;
227                                 FixResdomTypes(parsetree->targetList);
228
229                                 return lcons(parsetree, NIL);
230                         }
231                 }
232         }
233         return NIL;
234 }
235
236
237 /* Idea is like this:
238  *
239  * retrieve-instead-retrieve rules have different semantics than update nodes
240  * Separate RIR rules from others.      Pass others to FireRules.
241  * Order RIR rules and process.
242  *
243  * side effect: parsetree's rtable field might be changed
244  */
245 static void
246 ApplyRetrieveRule(Query *parsetree,
247                                   RewriteRule *rule,
248                                   int rt_index,
249                                   int relation_level,
250                                   int *modified)
251 {
252         Query      *rule_action = NULL;
253         Node       *rule_qual;
254         List       *rtable,
255                            *rt;
256         int                     nothing,
257                                 rt_length;
258         int                     badsql = FALSE;
259
260         rule_qual = rule->qual;
261         if (rule->actions)
262         {
263                 if (length(rule->actions) > 1)  /* ??? because we don't handle
264                                                                                  * rules with more than one
265                                                                                  * action? -ay */
266                         return;
267                 rule_action = copyObject(lfirst(rule->actions));
268                 nothing = FALSE;
269         }
270         else
271         {
272                 nothing = TRUE;
273         }
274
275         rtable = copyObject(parsetree->rtable);
276         foreach(rt, rtable)
277         {
278                 RangeTblEntry *rte = lfirst(rt);
279
280                 /*
281                  * this is to prevent add_missing_vars_to_base_rels() from adding
282                  * a bogus entry to the new target list.
283                  */
284                 rte->inFromCl = false;
285         }
286         rt_length = length(rtable);
287         rtable = nconc(rtable, copyObject(rule_action->rtable));
288         parsetree->rtable = rtable;
289
290         rule_action->rtable = rtable;
291         OffsetVarNodes(rule_action->qual, rt_length);
292         OffsetVarNodes((Node *) rule_action->targetList, rt_length);
293         OffsetVarNodes(rule_qual, rt_length);
294         ChangeVarNodes(rule_action->qual,
295                                    PRS2_CURRENT_VARNO + rt_length, rt_index);
296         ChangeVarNodes((Node *) rule_action->targetList,
297                                    PRS2_CURRENT_VARNO + rt_length, rt_index);
298         ChangeVarNodes(rule_qual, PRS2_CURRENT_VARNO + rt_length, rt_index);
299         if (relation_level)
300         {
301                 HandleViewRule(parsetree, rtable, rule_action->targetList, rt_index,
302                                            modified);
303         }
304         else
305         {
306                 HandleRIRAttributeRule(parsetree, rtable, rule_action->targetList,
307                                                            rt_index, rule->attrno, modified, &badsql);
308         }
309         if (*modified && !badsql)
310                 AddQual(parsetree, rule_action->qual);
311 }
312
313 static List *
314 ProcessRetrieveQuery(Query *parsetree,
315                                          List *rtable,
316                                          bool *instead_flag,
317                                          bool rule)
318 {
319         List       *rt;
320         List       *product_queries = NIL;
321         int                     rt_index = 0;
322
323         foreach(rt, rtable)
324         {
325                 RangeTblEntry *rt_entry = lfirst(rt);
326                 Relation        rt_entry_relation = NULL;
327                 List       *result = NIL;
328
329                 rt_index++;
330                 rt_entry_relation = heap_openr(rt_entry->relname);
331
332                 if (rt_entry_relation->rd_rules != NULL)
333                 {
334                         result =
335                                 FireRetrieveRulesAtQuery(parsetree,
336                                                                                  rt_index,
337                                                                                  rt_entry_relation,
338                                                                                  instead_flag,
339                                                                                  rule);
340                 }
341                 heap_close(rt_entry_relation);
342                 if (*instead_flag)
343                         return result;
344         }
345         if (rule)
346                 return NIL;
347
348         foreach(rt, rtable)
349         {
350                 RangeTblEntry *rt_entry = lfirst(rt);
351                 Relation        rt_entry_relation = NULL;
352                 RuleLock   *rt_entry_locks = NULL;
353                 List       *result = NIL;
354                 List       *locks = NIL;
355                 List       *dummy_products;
356
357                 rt_index++;
358                 rt_entry_relation = heap_openr(rt_entry->relname);
359                 rt_entry_locks = rt_entry_relation->rd_rules;
360                 heap_close(rt_entry_relation);
361
362                 if (rt_entry_locks)
363                 {
364                         locks =
365                                 matchLocks(CMD_SELECT, rt_entry_locks, rt_index, parsetree);
366                 }
367                 if (locks != NIL)
368                 {
369                         result = fireRules(parsetree, rt_index, CMD_SELECT,
370                                                            instead_flag, locks, &dummy_products);
371                         if (*instead_flag)
372                                 return lappend(NIL, result);
373                         if (result != NIL)
374                                 product_queries = nconc(product_queries, result);
375                 }
376         }
377         return product_queries;
378 }
379
380 static Query *
381 CopyAndAddQual(Query *parsetree,
382                            List *actions,
383                            Node *rule_qual,
384                            int rt_index,
385                            CmdType event)
386 {
387         Query      *new_tree = (Query *) copyObject(parsetree);
388         Node       *new_qual = NULL;
389         Query      *rule_action = NULL;
390
391         if (actions)
392                 rule_action = lfirst(actions);
393         if (rule_qual != NULL)
394                 new_qual = (Node *) copyObject(rule_qual);
395         if (rule_action != NULL)
396         {
397                 List       *rtable;
398                 int                     rt_length;
399
400                 rtable = new_tree->rtable;
401                 rt_length = length(rtable);
402                 rtable = append(rtable, listCopy(rule_action->rtable));
403                 new_tree->rtable = rtable;
404                 OffsetVarNodes(new_qual, rt_length);
405                 ChangeVarNodes(new_qual, PRS2_CURRENT_VARNO + rt_length, rt_index);
406         }
407         /* XXX -- where current doesn't work for instead nothing.... yet */
408         AddNotQual(new_tree, new_qual);
409
410         return new_tree;
411 }
412
413
414 /*
415  *      fireRules -
416  *         Iterate through rule locks applying rules.  After an instead rule
417  *         rule has been applied, return just new parsetree and let RewriteQuery
418  *         start the process all over again.  The locks are reordered to maintain
419  *         sensible semantics.  remember: reality is for dead birds -- glass
420  *
421  */
422 static List *
423 fireRules(Query *parsetree,
424                   int rt_index,
425                   CmdType event,
426                   bool *instead_flag,
427                   List *locks,
428                   List **qual_products)
429 {
430         RewriteInfo *info;
431         List       *results = NIL;
432         List       *i;
433
434         /* choose rule to fire from list of rules */
435         if (locks == NIL)
436         {
437                 ProcessRetrieveQuery(parsetree,
438                                                          parsetree->rtable,
439                                                          instead_flag, TRUE);
440                 if (*instead_flag)
441                         return lappend(NIL, parsetree);
442                 else
443                         return NIL;
444         }
445
446         locks = orderRules(locks);      /* instead rules first */
447         foreach(i, locks)
448         {
449                 RewriteRule *rule_lock = (RewriteRule *) lfirst(i);
450                 Node       *qual,
451                                    *event_qual;
452                 List       *actions;
453                 List       *r;
454                 bool            orig_instead_flag = *instead_flag;
455
456                 /* multiple rule action time */
457                 *instead_flag = rule_lock->isInstead;
458                 event_qual = rule_lock->qual;
459                 actions = rule_lock->actions;
460                 if (event_qual != NULL && *instead_flag)
461                         *qual_products =
462                                 lappend(*qual_products,
463                                                 CopyAndAddQual(parsetree, actions, event_qual,
464                                                                            rt_index, event));
465                 foreach(r, actions)
466                 {
467                         Query      *rule_action = lfirst(r);
468                         Node       *rule_qual = copyObject(event_qual);
469
470                         /*--------------------------------------------------
471                          * Step 1:
472                          *        Rewrite current.attribute or current to tuple variable
473                          *        this appears to be done in parser?
474                          *--------------------------------------------------
475                          */
476                         info = gatherRewriteMeta(parsetree, rule_action, rule_qual,
477                                                                          rt_index, event, instead_flag);
478
479                         /* handle escapable cases, or those handled by other code */
480                         if (info->nothing)
481                         {
482                                 if (*instead_flag)
483                                         return NIL;
484                                 else
485                                         continue;
486                         }
487
488                         if (info->action == info->event &&
489                                 info->event == CMD_SELECT)
490                                 continue;
491
492                         /*
493                          * Event Qualification forces copying of parsetree --- XXX and
494                          * splitting into two queries one w/rule_qual, one w/NOT
495                          * rule_qual. Also add user query qual onto rule action
496                          */
497                         qual = parsetree->qual;
498                         AddQual(info->rule_action, qual);
499
500                         if (info->rule_qual != NULL)
501                                 AddQual(info->rule_action, info->rule_qual);
502
503                         /*--------------------------------------------------
504                          * Step 2:
505                          *        Rewrite new.attribute w/ right hand side of target-list
506                          *        entry for appropriate field name in insert/update
507                          *--------------------------------------------------
508                          */
509                         if ((info->event == CMD_INSERT) || (info->event == CMD_UPDATE))
510                         {
511                                 FixNew(info, parsetree);
512                         }
513
514                         /*--------------------------------------------------
515                          * Step 3:
516                          *        rewriting due to retrieve rules
517                          *--------------------------------------------------
518                          */
519                         info->rule_action->rtable = info->rt;
520                         ProcessRetrieveQuery(info->rule_action, info->rt,
521                                                                  &orig_instead_flag, TRUE);
522
523                         /*--------------------------------------------------
524                          * Step 4
525                          *        Simplify? hey, no algorithm for simplification... let
526                          *        the planner do it.
527                          *--------------------------------------------------
528                          */
529                         results = lappend(results, info->rule_action);
530
531                         pfree(info);
532                 }
533                 if (*instead_flag)
534                         break;
535         }
536         return results;
537 }
538
539 static List *
540 RewriteQuery(Query *parsetree, bool *instead_flag, List **qual_products)
541 {
542         CmdType         event;
543         List       *product_queries = NIL;
544         int                     result_relation = 0;
545
546         Assert(parsetree != NULL);
547
548         event = parsetree->commandType;
549
550         if (event == CMD_UTILITY)
551                 return NIL;
552
553         /*
554          * only for a delete may the targetlist be NULL
555          */
556         if (event != CMD_DELETE)
557         {
558                 Assert(parsetree->targetList != NULL);
559         }
560
561         result_relation = parsetree->resultRelation;
562
563         if (event != CMD_SELECT)
564         {
565
566                 /*
567                  * the statement is an update, insert or delete
568                  */
569                 RangeTblEntry *rt_entry;
570                 Relation        rt_entry_relation = NULL;
571                 RuleLock   *rt_entry_locks = NULL;
572
573                 rt_entry = rt_fetch(result_relation, parsetree->rtable);
574                 rt_entry_relation = heap_openr(rt_entry->relname);
575                 rt_entry_locks = rt_entry_relation->rd_rules;
576                 heap_close(rt_entry_relation);
577
578                 if (rt_entry_locks != NULL)
579                 {
580                         List       *locks =
581                         matchLocks(event, rt_entry_locks, result_relation, parsetree);
582
583                         product_queries =
584                                 fireRules(parsetree,
585                                                   result_relation,
586                                                   event,
587                                                   instead_flag,
588                                                   locks,
589                                                   qual_products);
590                 }
591                 return product_queries;
592         }
593         else
594         {
595
596                 /*
597                  * the statement is a select
598                  */
599                 Query      *other;
600
601                 /*
602                  *      ApplyRetrieveRule changes the range table
603                  *      XXX Unions are copied again.
604                  */
605                 other = copyObject(parsetree);
606
607                 return
608                         ProcessRetrieveQuery(other, parsetree->rtable,
609                                                                  instead_flag, FALSE);
610         }
611 }
612
613 /*
614  * to avoid infinite recursion, we restrict the number of times a query
615  * can be rewritten. Detecting cycles is left for the reader as an excercise.
616  */
617 #ifndef REWRITE_INVOKE_MAX
618 #define REWRITE_INVOKE_MAX              10
619 #endif
620
621 static int      numQueryRewriteInvoked = 0;
622
623 /*
624  * QueryRewrite -
625  *        rewrite one query via QueryRewrite system, possibly returning 0, or many
626  *        queries
627  */
628 List       *
629 QueryRewrite(Query *parsetree)
630 {
631         numQueryRewriteInvoked = 0;
632
633         /*
634          * take a deep breath and apply all the rewrite rules - ay
635          */
636         return deepRewriteQuery(parsetree);
637 }
638
639 /*
640  * deepRewriteQuery -
641  *        rewrites the query and apply the rules again on the queries rewritten
642  */
643 static List *
644 deepRewriteQuery(Query *parsetree)
645 {
646         List       *n;
647         List       *rewritten = NIL;
648         List       *result = NIL;
649         bool            instead;
650         List       *qual_products = NIL;
651
652         if (++numQueryRewriteInvoked > REWRITE_INVOKE_MAX)
653         {
654                 elog(ERROR, "query rewritten %d times, may contain cycles",
655                          numQueryRewriteInvoked - 1);
656         }
657
658         instead = FALSE;
659         result = RewriteQuery(parsetree, &instead, &qual_products);
660         if (!instead)
661                 rewritten = lcons(parsetree, NIL);
662
663         foreach(n, result)
664         {
665                 Query      *pt = lfirst(n);
666                 List       *newstuff = NIL;
667
668                 newstuff = deepRewriteQuery(pt);
669                 if (newstuff != NIL)
670                         rewritten = nconc(rewritten, newstuff);
671         }
672         if (qual_products != NIL)
673                 rewritten = nconc(rewritten, qual_products);
674
675         return rewritten;
676 }