]> granicus.if.org Git - postgresql/blob - src/backend/rewrite/rewriteDefine.c
Rule names are now unique per-relation, rather than unique globally.
[postgresql] / src / backend / rewrite / rewriteDefine.c
1 /*-------------------------------------------------------------------------
2  *
3  * rewriteDefine.c
4  *        routines for defining a rewrite rule
5  *
6  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *        $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.67 2002/04/18 20:01:09 tgl Exp $
12  *
13  *-------------------------------------------------------------------------
14  */
15 #include "postgres.h"
16
17 #include "access/heapam.h"
18 #include "catalog/catname.h"
19 #include "catalog/indexing.h"
20 #include "catalog/pg_rewrite.h"
21 #include "commands/view.h"
22 #include "miscadmin.h"
23 #include "optimizer/clauses.h"
24 #include "parser/parse_relation.h"
25 #include "rewrite/rewriteDefine.h"
26 #include "rewrite/rewriteManip.h"
27 #include "rewrite/rewriteSupport.h"
28 #include "storage/smgr.h"
29 #include "utils/acl.h"
30 #include "utils/builtins.h"
31 #include "utils/syscache.h"
32
33
34 static void setRuleCheckAsUser(Query *qry, Oid userid);
35 static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
36
37
38 /*
39  * InsertRule -
40  *        takes the arguments and inserts them as a row into the system
41  *        relation "pg_rewrite"
42  */
43 static Oid
44 InsertRule(char *rulname,
45                    int evtype,
46                    Oid eventrel_oid,
47                    AttrNumber evslot_index,
48                    bool evinstead,
49                    char *evqual,
50                    char *actiontree)
51 {
52         int                     i;
53         Datum           values[Natts_pg_rewrite];
54         char            nulls[Natts_pg_rewrite];
55         NameData        rname;
56         Relation        pg_rewrite_desc;
57         TupleDesc       tupDesc;
58         HeapTuple       tup;
59         Oid                     rewriteObjectId;
60
61         if (IsDefinedRewriteRule(eventrel_oid, rulname))
62                 elog(ERROR, "Attempt to insert rule \"%s\" failed: already exists",
63                          rulname);
64
65         /*
66          * Set up *nulls and *values arrays
67          */
68         MemSet(nulls, ' ', sizeof(nulls));
69
70         i = 0;
71         namestrcpy(&rname, rulname);
72         values[i++] = NameGetDatum(&rname);                             /* rulename */
73         values[i++] = ObjectIdGetDatum(eventrel_oid);   /* ev_class */
74         values[i++] = Int16GetDatum(evslot_index);              /* ev_attr */
75         values[i++] = CharGetDatum(evtype + '0');               /* ev_type */
76         values[i++] = BoolGetDatum(evinstead);                  /* is_instead */
77         values[i++] = DirectFunctionCall1(textin, CStringGetDatum(evqual));     /* ev_qual */
78         values[i++] = DirectFunctionCall1(textin, CStringGetDatum(actiontree)); /* ev_action */
79
80         /*
81          * create a new pg_rewrite tuple
82          */
83         pg_rewrite_desc = heap_openr(RewriteRelationName, RowExclusiveLock);
84
85         tupDesc = pg_rewrite_desc->rd_att;
86
87         tup = heap_formtuple(tupDesc,
88                                                  values,
89                                                  nulls);
90
91         heap_insert(pg_rewrite_desc, tup);
92
93         rewriteObjectId = tup->t_data->t_oid;
94
95         if (RelationGetForm(pg_rewrite_desc)->relhasindex)
96         {
97                 Relation        idescs[Num_pg_rewrite_indices];
98
99                 CatalogOpenIndices(Num_pg_rewrite_indices, Name_pg_rewrite_indices,
100                                                    idescs);
101                 CatalogIndexInsert(idescs, Num_pg_rewrite_indices, pg_rewrite_desc,
102                                                    tup);
103                 CatalogCloseIndices(Num_pg_rewrite_indices, idescs);
104         }
105
106         heap_freetuple(tup);
107
108         heap_close(pg_rewrite_desc, RowExclusiveLock);
109
110         return rewriteObjectId;
111 }
112
113 void
114 DefineQueryRewrite(RuleStmt *stmt)
115 {
116         RangeVar   *event_obj = stmt->relation;
117         Node       *event_qual = stmt->whereClause;
118         CmdType         event_type = stmt->event;
119         bool            is_instead = stmt->instead;
120         List       *action = stmt->actions;
121         Relation        event_relation;
122         Oid                     ev_relid;
123         Oid                     ruleId;
124         int                     event_attno;
125         Oid                     event_attype;
126         char       *actionP,
127                            *event_qualP;
128         List       *l;
129         Query      *query;
130         int32           aclcheck_result;
131         bool            RelisBecomingView = false;
132
133         /*
134          * If we are installing an ON SELECT rule, we had better grab
135          * AccessExclusiveLock to ensure no SELECTs are currently running on
136          * the event relation.  For other types of rules, it might be
137          * sufficient to grab ShareLock to lock out insert/update/delete
138          * actions.  But for now, let's just grab AccessExclusiveLock all the
139          * time.
140          */
141         event_relation = heap_openrv(event_obj, AccessExclusiveLock);
142         ev_relid = RelationGetRelid(event_relation);
143
144         /*
145          * Check user has permission to apply rules to this relation.
146          */
147         aclcheck_result = pg_class_aclcheck(ev_relid, GetUserId(), ACL_RULE);
148         if (aclcheck_result != ACLCHECK_OK)
149                 elog(ERROR, "%s: %s",
150                          RelationGetRelationName(event_relation),
151                          aclcheck_error_strings[aclcheck_result]);
152
153         /*
154          * No rule actions that modify OLD or NEW
155          */
156         foreach(l, action)
157         {
158                 query = (Query *) lfirst(l);
159                 if (query->resultRelation == 0)
160                         continue;
161                 /* Don't be fooled by INSERT/SELECT */
162                 if (query != getInsertSelectQuery(query, NULL))
163                         continue;
164                 if (query->resultRelation == PRS2_OLD_VARNO)
165                         elog(ERROR, "rule actions on OLD currently not supported"
166                                  "\n\tuse views or triggers instead");
167                 if (query->resultRelation == PRS2_NEW_VARNO)
168                         elog(ERROR, "rule actions on NEW currently not supported"
169                                  "\n\tuse triggers instead");
170         }
171
172         /*
173          * Rules ON SELECT are restricted to view definitions
174          */
175         if (event_type == CMD_SELECT)
176         {
177                 List       *tllist;
178                 int                     i;
179                 char       *expected_name;
180
181                 /*
182                  * So there cannot be INSTEAD NOTHING, ...
183                  */
184                 if (length(action) == 0)
185                 {
186                         elog(ERROR, "instead nothing rules on select currently not supported"
187                                  "\n\tuse views instead");
188                 }
189
190                 /*
191                  * ... there cannot be multiple actions, ...
192                  */
193                 if (length(action) > 1)
194                         elog(ERROR, "multiple action rules on select currently not supported");
195
196                 /*
197                  * ... the one action must be a SELECT, ...
198                  */
199                 query = (Query *) lfirst(action);
200                 if (!is_instead || query->commandType != CMD_SELECT)
201                         elog(ERROR, "only instead-select rules currently supported on select");
202
203                 /*
204                  * ... there can be no rule qual, ...
205                  */
206                 if (event_qual != NULL)
207                         elog(ERROR, "event qualifications not supported for rules on select");
208
209                 /*
210                  * ... the targetlist of the SELECT action must exactly match the
211                  * event relation, ...
212                  */
213                 i = 0;
214                 foreach(tllist, query->targetList)
215                 {
216                         TargetEntry *tle = (TargetEntry *) lfirst(tllist);
217                         Resdom     *resdom = tle->resdom;
218                         Form_pg_attribute attr;
219                         char       *attname;
220
221                         if (resdom->resjunk)
222                                 continue;
223                         i++;
224                         if (i > event_relation->rd_att->natts)
225                                 elog(ERROR, "select rule's target list has too many entries");
226
227                         attr = event_relation->rd_att->attrs[i - 1];
228                         attname = NameStr(attr->attname);
229
230                         if (strcmp(resdom->resname, attname) != 0)
231                                 elog(ERROR, "select rule's target entry %d has different column name from %s", i, attname);
232
233                         if (attr->atttypid != resdom->restype)
234                                 elog(ERROR, "select rule's target entry %d has different type from attribute %s", i, attname);
235
236                         /*
237                          * Allow typmods to be different only if one of them is -1,
238                          * ie, "unspecified".  This is necessary for cases like
239                          * "numeric", where the table will have a filled-in default
240                          * length but the select rule's expression will probably have
241                          * typmod = -1.
242                          */
243                         if (attr->atttypmod != resdom->restypmod &&
244                                 attr->atttypmod != -1 && resdom->restypmod != -1)
245                                 elog(ERROR, "select rule's target entry %d has different size from attribute %s", i, attname);
246                 }
247
248                 if (i != event_relation->rd_att->natts)
249                         elog(ERROR, "select rule's target list has too few entries");
250
251                 /*
252                  * ... there must not be another ON SELECT rule already ...
253                  */
254                 if (event_relation->rd_rules != NULL)
255                 {
256                         for (i = 0; i < event_relation->rd_rules->numLocks; i++)
257                         {
258                                 RewriteRule *rule;
259
260                                 rule = event_relation->rd_rules->rules[i];
261                                 if (rule->event == CMD_SELECT)
262                                         elog(ERROR, "\"%s\" is already a view",
263                                                  RelationGetRelationName(event_relation));
264                         }
265                 }
266
267                 /*
268                  * ... and finally the rule must be named _RETviewname.
269                  */
270                 expected_name = MakeRetrieveViewRuleName(event_obj->relname);
271                 if (strcmp(expected_name, stmt->rulename) != 0)
272                 {
273                         elog(ERROR, "view rule for \"%s\" must be named \"%s\"",
274                                  event_obj->relname, expected_name);
275                 }
276                 pfree(expected_name);
277
278                 /*
279                  * Are we converting a relation to a view?
280                  *
281                  * If so, check that the relation is empty because the storage for
282                  * the relation is going to be deleted.
283                  */
284                 if (event_relation->rd_rel->relkind != RELKIND_VIEW)
285                 {
286                         HeapScanDesc scanDesc;
287                         HeapTuple       tuple;
288
289                         scanDesc = heap_beginscan(event_relation, 0, SnapshotNow, 0, NULL);
290                         tuple = heap_getnext(scanDesc, 0);
291                         if (HeapTupleIsValid(tuple))
292                                 elog(ERROR, "Relation \"%s\" is not empty. Cannot convert it to view",
293                                          event_obj->relname);
294
295                         /*
296                          * don't need heap_freetuple because we never got a valid
297                          * tuple
298                          */
299                         heap_endscan(scanDesc);
300
301                         RelisBecomingView = true;
302                 }
303         }
304
305         /*
306          * This rule is allowed - prepare to install it.
307          */
308         event_attno = -1;
309         event_attype = InvalidOid;
310
311         /*
312          * We want the rule's table references to be checked as though by the
313          * rule owner, not the user referencing the rule.  Therefore, scan
314          * through the rule's rtables and set the checkAsUser field on all
315          * rtable entries.
316          */
317         foreach(l, action)
318         {
319                 query = (Query *) lfirst(l);
320                 setRuleCheckAsUser(query, GetUserId());
321         }
322
323         /* discard rule if it's null action and not INSTEAD; it's a no-op */
324         if (action != NIL || is_instead)
325         {
326                 event_qualP = nodeToString(event_qual);
327                 actionP = nodeToString(action);
328
329                 ruleId = InsertRule(stmt->rulename,
330                                                         event_type,
331                                                         ev_relid,
332                                                         event_attno,
333                                                         is_instead,
334                                                         event_qualP,
335                                                         actionP);
336
337                 /*
338                  * Set pg_class 'relhasrules' field TRUE for event relation. If
339                  * appropriate, also modify the 'relkind' field to show that the
340                  * relation is now a view.
341                  *
342                  * Important side effect: an SI notice is broadcast to force all
343                  * backends (including me!) to update relcache entries with the
344                  * new rule.
345                  */
346                 SetRelationRuleStatus(ev_relid, true, RelisBecomingView);
347         }
348
349         /*
350          * IF the relation is becoming a view, delete the storage files
351          * associated with it.  NB: we had better have AccessExclusiveLock to
352          * do this ...
353          */
354         if (RelisBecomingView)
355                 smgrunlink(DEFAULT_SMGR, event_relation);
356
357         /* Close rel, but keep lock till commit... */
358         heap_close(event_relation, NoLock);
359 }
360
361 /*
362  * setRuleCheckAsUser
363  *              Recursively scan a query and set the checkAsUser field to the
364  *              given userid in all rtable entries.
365  *
366  * Note: for a view (ON SELECT rule), the checkAsUser field of the *OLD*
367  * RTE entry will be overridden when the view rule is expanded, and the
368  * checkAsUser field of the *NEW* entry is irrelevant because that entry's
369  * checkFor bits will never be set.  However, for other types of rules it's
370  * important to set these fields to match the rule owner.  So we just set
371  * them always.
372  */
373 static void
374 setRuleCheckAsUser(Query *qry, Oid userid)
375 {
376         List       *l;
377
378         /* Set all the RTEs in this query node */
379         foreach(l, qry->rtable)
380         {
381                 RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
382
383                 if (rte->subquery)
384                 {
385                         /* Recurse into subquery in FROM */
386                         setRuleCheckAsUser(rte->subquery, userid);
387                 }
388                 else
389                         rte->checkAsUser = userid;
390         }
391
392         /* If there are sublinks, search for them and process their RTEs */
393         if (qry->hasSubLinks)
394                 query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
395                                                   false /* already did the ones in rtable */ );
396 }
397
398 /*
399  * Expression-tree walker to find sublink queries
400  */
401 static bool
402 setRuleCheckAsUser_walker(Node *node, Oid *context)
403 {
404         if (node == NULL)
405                 return false;
406         if (IsA(node, Query))
407         {
408                 Query      *qry = (Query *) node;
409
410                 setRuleCheckAsUser(qry, *context);
411                 return false;
412         }
413         return expression_tree_walker(node, setRuleCheckAsUser_walker,
414                                                                   (void *) context);
415 }
416
417
418 /*
419  * Rename an existing rewrite rule.
420  *
421  * There is not currently a user command to invoke this directly
422  * (perhaps there should be).  But we need it anyway to rename the
423  * ON SELECT rule associated with a view, when the view is renamed.
424  */
425 void
426 RenameRewriteRule(Oid owningRel, const char *oldName,
427                                   const char *newName)
428 {
429         Relation        pg_rewrite_desc;
430         HeapTuple       ruletup;
431
432         pg_rewrite_desc = heap_openr(RewriteRelationName, RowExclusiveLock);
433
434         ruletup = SearchSysCacheCopy(RULERELNAME,
435                                                                  ObjectIdGetDatum(owningRel),
436                                                                  PointerGetDatum(oldName),
437                                                                  0, 0);
438         if (!HeapTupleIsValid(ruletup))
439                 elog(ERROR, "RenameRewriteRule: rule \"%s\" does not exist", oldName);
440
441         /* should not already exist */
442         if (IsDefinedRewriteRule(owningRel, newName))
443                 elog(ERROR, "Attempt to rename rule \"%s\" failed: \"%s\" already exists",
444                          oldName, newName);
445
446         namestrcpy(&(((Form_pg_rewrite) GETSTRUCT(ruletup))->rulename), newName);
447
448         simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
449
450         /* keep system catalog indices current */
451         if (RelationGetForm(pg_rewrite_desc)->relhasindex)
452         {
453                 Relation        idescs[Num_pg_rewrite_indices];
454
455                 CatalogOpenIndices(Num_pg_rewrite_indices, Name_pg_rewrite_indices,
456                                                    idescs);
457                 CatalogIndexInsert(idescs, Num_pg_rewrite_indices, pg_rewrite_desc,
458                                                    ruletup);
459                 CatalogCloseIndices(Num_pg_rewrite_indices, idescs);
460         }
461
462         heap_freetuple(ruletup);
463         heap_close(pg_rewrite_desc, RowExclusiveLock);
464 }