1 /*-------------------------------------------------------------------------
4 * routines for defining a rewrite rule
6 * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.109 2006/03/05 15:58:36 momjian Exp $
13 *-------------------------------------------------------------------------
17 #include "access/heapam.h"
18 #include "catalog/dependency.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_expr.h"
25 #include "parser/parse_relation.h"
26 #include "rewrite/rewriteDefine.h"
27 #include "rewrite/rewriteManip.h"
28 #include "rewrite/rewriteSupport.h"
29 #include "storage/smgr.h"
30 #include "utils/acl.h"
31 #include "utils/builtins.h"
32 #include "utils/lsyscache.h"
33 #include "utils/syscache.h"
36 static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
37 static void setRuleCheckAsUser_Expr(Node *node, Oid userid);
38 static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
43 * takes the arguments and inserts them as a row into the system
44 * relation "pg_rewrite"
47 InsertRule(char *rulname,
50 AttrNumber evslot_index,
56 char *evqual = nodeToString(event_qual);
57 char *actiontree = nodeToString((Node *) action);
59 Datum values[Natts_pg_rewrite];
60 char nulls[Natts_pg_rewrite];
61 char replaces[Natts_pg_rewrite];
63 Relation pg_rewrite_desc;
69 bool is_update = false;
72 * Set up *nulls and *values arrays
74 MemSet(nulls, ' ', sizeof(nulls));
77 namestrcpy(&rname, rulname);
78 values[i++] = NameGetDatum(&rname); /* rulename */
79 values[i++] = ObjectIdGetDatum(eventrel_oid); /* ev_class */
80 values[i++] = Int16GetDatum(evslot_index); /* ev_attr */
81 values[i++] = CharGetDatum(evtype + '0'); /* ev_type */
82 values[i++] = BoolGetDatum(evinstead); /* is_instead */
83 values[i++] = DirectFunctionCall1(textin, CStringGetDatum(evqual)); /* ev_qual */
84 values[i++] = DirectFunctionCall1(textin, CStringGetDatum(actiontree)); /* ev_action */
87 * Ready to store new pg_rewrite tuple
89 pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
92 * Check to see if we are replacing an existing tuple
94 oldtup = SearchSysCache(RULERELNAME,
95 ObjectIdGetDatum(eventrel_oid),
96 PointerGetDatum(rulname),
99 if (HeapTupleIsValid(oldtup))
103 (errcode(ERRCODE_DUPLICATE_OBJECT),
104 errmsg("rule \"%s\" for relation \"%s\" already exists",
105 rulname, get_rel_name(eventrel_oid))));
108 * When replacing, we don't need to replace every attribute
110 MemSet(replaces, ' ', sizeof(replaces));
111 replaces[Anum_pg_rewrite_ev_attr - 1] = 'r';
112 replaces[Anum_pg_rewrite_ev_type - 1] = 'r';
113 replaces[Anum_pg_rewrite_is_instead - 1] = 'r';
114 replaces[Anum_pg_rewrite_ev_qual - 1] = 'r';
115 replaces[Anum_pg_rewrite_ev_action - 1] = 'r';
117 tup = heap_modifytuple(oldtup, RelationGetDescr(pg_rewrite_desc),
118 values, nulls, replaces);
120 simple_heap_update(pg_rewrite_desc, &tup->t_self, tup);
122 ReleaseSysCache(oldtup);
124 rewriteObjectId = HeapTupleGetOid(tup);
129 tup = heap_formtuple(pg_rewrite_desc->rd_att, values, nulls);
131 rewriteObjectId = simple_heap_insert(pg_rewrite_desc, tup);
134 /* Need to update indexes in either case */
135 CatalogUpdateIndexes(pg_rewrite_desc, tup);
139 /* If replacing, get rid of old dependencies and make new ones */
141 deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId);
144 * Install dependency on rule's relation to ensure it will go away on
145 * relation deletion. If the rule is ON SELECT, make the dependency
146 * implicit --- this prevents deleting a view's SELECT rule. Other kinds
147 * of rules can be AUTO.
149 myself.classId = RewriteRelationId;
150 myself.objectId = rewriteObjectId;
151 myself.objectSubId = 0;
153 referenced.classId = RelationRelationId;
154 referenced.objectId = eventrel_oid;
155 referenced.objectSubId = 0;
157 recordDependencyOn(&myself, &referenced,
158 (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
161 * Also install dependencies on objects referenced in action and qual.
163 recordDependencyOnExpr(&myself, (Node *) action, NIL,
166 if (event_qual != NULL)
168 /* Find query containing OLD/NEW rtable entries */
169 Query *qry = (Query *) linitial(action);
171 qry = getInsertSelectQuery(qry, NULL);
172 recordDependencyOnExpr(&myself, event_qual, qry->rtable,
176 heap_close(pg_rewrite_desc, RowExclusiveLock);
178 return rewriteObjectId;
182 DefineQueryRewrite(RuleStmt *stmt)
184 RangeVar *event_obj = stmt->relation;
185 Node *event_qual = stmt->whereClause;
186 CmdType event_type = stmt->event;
187 bool is_instead = stmt->instead;
188 bool replace = stmt->replace;
189 List *action = stmt->actions;
190 Relation event_relation;
197 bool RelisBecomingView = false;
200 * If we are installing an ON SELECT rule, we had better grab
201 * AccessExclusiveLock to ensure no SELECTs are currently running on the
202 * event relation. For other types of rules, it might be sufficient to
203 * grab ShareLock to lock out insert/update/delete actions. But for now,
204 * let's just grab AccessExclusiveLock all the time.
206 event_relation = heap_openrv(event_obj, AccessExclusiveLock);
207 ev_relid = RelationGetRelid(event_relation);
210 * Check user has permission to apply rules to this relation.
212 aclresult = pg_class_aclcheck(ev_relid, GetUserId(), ACL_RULE);
213 if (aclresult != ACLCHECK_OK)
214 aclcheck_error(aclresult, ACL_KIND_CLASS,
215 RelationGetRelationName(event_relation));
218 * No rule actions that modify OLD or NEW
222 query = (Query *) lfirst(l);
223 if (query->resultRelation == 0)
225 /* Don't be fooled by INSERT/SELECT */
226 if (query != getInsertSelectQuery(query, NULL))
228 if (query->resultRelation == PRS2_OLD_VARNO)
230 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
231 errmsg("rule actions on OLD are not implemented"),
232 errhint("Use views or triggers instead.")));
233 if (query->resultRelation == PRS2_NEW_VARNO)
235 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
236 errmsg("rule actions on NEW are not implemented"),
237 errhint("Use triggers instead.")));
241 * Rules ON SELECT are restricted to view definitions
243 if (event_type == CMD_SELECT)
249 * So there cannot be INSTEAD NOTHING, ...
251 if (list_length(action) == 0)
253 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
254 errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
255 errhint("Use views instead.")));
258 * ... there cannot be multiple actions, ...
260 if (list_length(action) > 1)
262 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
263 errmsg("multiple actions for rules on SELECT are not implemented")));
266 * ... the one action must be a SELECT, ...
268 query = (Query *) linitial(action);
269 if (!is_instead || query->commandType != CMD_SELECT)
271 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
272 errmsg("rules on SELECT must have action INSTEAD SELECT")));
275 * ... there can be no rule qual, ...
277 if (event_qual != NULL)
279 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
280 errmsg("event qualifications are not implemented for rules on SELECT")));
283 * ... the targetlist of the SELECT action must exactly match the
284 * event relation, ...
287 foreach(tllist, query->targetList)
289 TargetEntry *tle = (TargetEntry *) lfirst(tllist);
291 Form_pg_attribute attr;
297 if (i > event_relation->rd_att->natts)
299 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
300 errmsg("SELECT rule's target list has too many entries")));
302 attr = event_relation->rd_att->attrs[i - 1];
303 attname = NameStr(attr->attname);
306 * Disallow dropped columns in the relation. This won't happen in
307 * the cases we actually care about (namely creating a view via
308 * CREATE TABLE then CREATE RULE). Trying to cope with it is much
309 * more trouble than it's worth, because we'd have to modify the
310 * rule to insert dummy NULLs at the right positions.
312 if (attr->attisdropped)
314 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
315 errmsg("cannot convert relation containing dropped columns to view")));
317 if (strcmp(tle->resname, attname) != 0)
319 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
320 errmsg("SELECT rule's target entry %d has different column name from \"%s\"", i, attname)));
322 if (attr->atttypid != exprType((Node *) tle->expr))
324 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
325 errmsg("SELECT rule's target entry %d has different type from column \"%s\"", i, attname)));
328 * Allow typmods to be different only if one of them is -1, ie,
329 * "unspecified". This is necessary for cases like "numeric",
330 * where the table will have a filled-in default length but the
331 * select rule's expression will probably have typmod = -1.
333 tletypmod = exprTypmod((Node *) tle->expr);
334 if (attr->atttypmod != tletypmod &&
335 attr->atttypmod != -1 && tletypmod != -1)
337 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
338 errmsg("SELECT rule's target entry %d has different size from column \"%s\"", i, attname)));
341 if (i != event_relation->rd_att->natts)
343 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
344 errmsg("SELECT rule's target list has too few entries")));
347 * ... there must not be another ON SELECT rule already ...
349 if (!replace && event_relation->rd_rules != NULL)
351 for (i = 0; i < event_relation->rd_rules->numLocks; i++)
355 rule = event_relation->rd_rules->rules[i];
356 if (rule->event == CMD_SELECT)
358 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
359 errmsg("\"%s\" is already a view",
360 RelationGetRelationName(event_relation))));
365 * ... and finally the rule must be named _RETURN.
367 if (strcmp(stmt->rulename, ViewSelectRuleName) != 0)
370 * In versions before 7.3, the expected name was _RETviewname. For
371 * backwards compatibility with old pg_dump output, accept that
372 * and silently change it to _RETURN. Since this is just a quick
373 * backwards-compatibility hack, limit the number of characters
374 * checked to a few less than NAMEDATALEN; this saves having to
375 * worry about where a multibyte character might have gotten
378 if (strncmp(stmt->rulename, "_RET", 4) != 0 ||
379 strncmp(stmt->rulename + 4, event_obj->relname,
380 NAMEDATALEN - 4 - 4) != 0)
382 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
383 errmsg("view rule for \"%s\" must be named \"%s\"",
384 event_obj->relname, ViewSelectRuleName)));
385 stmt->rulename = pstrdup(ViewSelectRuleName);
389 * Are we converting a relation to a view?
391 * If so, check that the relation is empty because the storage for the
392 * relation is going to be deleted. Also insist that the rel not have
393 * any triggers, indexes, or child tables.
395 if (event_relation->rd_rel->relkind != RELKIND_VIEW)
397 HeapScanDesc scanDesc;
399 scanDesc = heap_beginscan(event_relation, SnapshotNow, 0, NULL);
400 if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
402 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
403 errmsg("could not convert table \"%s\" to a view because it is not empty",
404 event_obj->relname)));
405 heap_endscan(scanDesc);
407 if (event_relation->rd_rel->reltriggers != 0)
409 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
410 errmsg("could not convert table \"%s\" to a view because it has triggers",
412 errhint("In particular, the table may not be involved in any foreign key relationships.")));
414 if (event_relation->rd_rel->relhasindex)
416 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
417 errmsg("could not convert table \"%s\" to a view because it has indexes",
418 event_obj->relname)));
420 if (event_relation->rd_rel->relhassubclass)
422 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
423 errmsg("could not convert table \"%s\" to a view because it has child tables",
424 event_obj->relname)));
426 RelisBecomingView = true;
431 * This rule is allowed - prepare to install it.
436 * We want the rule's table references to be checked as though by the rule
437 * owner, not the user referencing the rule. Therefore, scan through the
438 * rule's rtables and set the checkAsUser field on all rtable entries. We
439 * have to look at event_qual as well, in case it contains sublinks.
443 query = (Query *) lfirst(l);
444 setRuleCheckAsUser_Query(query, GetUserId());
446 setRuleCheckAsUser_Expr(event_qual, GetUserId());
448 /* discard rule if it's null action and not INSTEAD; it's a no-op */
449 if (action != NIL || is_instead)
451 ruleId = InsertRule(stmt->rulename,
461 * Set pg_class 'relhasrules' field TRUE for event relation. If
462 * appropriate, also modify the 'relkind' field to show that the
463 * relation is now a view.
465 * Important side effect: an SI notice is broadcast to force all
466 * backends (including me!) to update relcache entries with the new
469 SetRelationRuleStatus(ev_relid, true, RelisBecomingView);
473 * IF the relation is becoming a view, delete the storage files associated
474 * with it. NB: we had better have AccessExclusiveLock to do this ...
476 * XXX what about getting rid of its TOAST table? For now, we don't.
478 if (RelisBecomingView)
480 RelationOpenSmgr(event_relation);
481 smgrscheduleunlink(event_relation->rd_smgr, event_relation->rd_istemp);
484 /* Close rel, but keep lock till commit... */
485 heap_close(event_relation, NoLock);
489 * setRuleCheckAsUser_Query
490 * Recursively scan a query and set the checkAsUser field to the
491 * given userid in all rtable entries.
493 * Note: for a view (ON SELECT rule), the checkAsUser field of the *OLD*
494 * RTE entry will be overridden when the view rule is expanded, and the
495 * checkAsUser field of the *NEW* entry is irrelevant because that entry's
496 * requiredPerms bits will always be zero. However, for other types of rules
497 * it's important to set these fields to match the rule owner. So we just set
501 setRuleCheckAsUser_Query(Query *qry, Oid userid)
505 /* Set all the RTEs in this query node */
506 foreach(l, qry->rtable)
508 RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
510 if (rte->rtekind == RTE_SUBQUERY)
512 /* Recurse into subquery in FROM */
513 setRuleCheckAsUser_Query(rte->subquery, userid);
516 rte->checkAsUser = userid;
519 /* If there are sublinks, search for them and process their RTEs */
520 /* ignore subqueries in rtable because we already processed them */
521 if (qry->hasSubLinks)
522 query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
523 QTW_IGNORE_RT_SUBQUERIES);
527 * Expression-tree walker to find sublink queries
530 setRuleCheckAsUser_Expr(Node *node, Oid userid)
532 (void) setRuleCheckAsUser_walker(node, &userid);
536 setRuleCheckAsUser_walker(Node *node, Oid *context)
540 if (IsA(node, Query))
542 Query *qry = (Query *) node;
544 setRuleCheckAsUser_Query(qry, *context);
547 return expression_tree_walker(node, setRuleCheckAsUser_walker,
553 * Rename an existing rewrite rule.
555 * This is unused code at the moment.
558 RenameRewriteRule(Oid owningRel, const char *oldName,
561 Relation pg_rewrite_desc;
564 pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
566 ruletup = SearchSysCacheCopy(RULERELNAME,
567 ObjectIdGetDatum(owningRel),
568 PointerGetDatum(oldName),
570 if (!HeapTupleIsValid(ruletup))
572 (errcode(ERRCODE_UNDEFINED_OBJECT),
573 errmsg("rule \"%s\" for relation \"%s\" does not exist",
574 oldName, get_rel_name(owningRel))));
576 /* should not already exist */
577 if (IsDefinedRewriteRule(owningRel, newName))
579 (errcode(ERRCODE_DUPLICATE_OBJECT),
580 errmsg("rule \"%s\" for relation \"%s\" already exists",
581 newName, get_rel_name(owningRel))));
583 namestrcpy(&(((Form_pg_rewrite) GETSTRUCT(ruletup))->rulename), newName);
585 simple_heap_update(pg_rewrite_desc, &ruletup->t_self, ruletup);
587 /* keep system catalog indexes current */
588 CatalogUpdateIndexes(pg_rewrite_desc, ruletup);
590 heap_freetuple(ruletup);
591 heap_close(pg_rewrite_desc, RowExclusiveLock);