Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * rewriteDefine.c
4 : * routines for defining a rewrite rule
5 : *
6 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/rewrite/rewriteDefine.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include "access/heapam.h"
18 : #include "access/htup_details.h"
19 : #include "access/multixact.h"
20 : #include "access/transam.h"
21 : #include "access/xact.h"
22 : #include "catalog/catalog.h"
23 : #include "catalog/dependency.h"
24 : #include "catalog/heap.h"
25 : #include "catalog/indexing.h"
26 : #include "catalog/namespace.h"
27 : #include "catalog/objectaccess.h"
28 : #include "catalog/pg_rewrite.h"
29 : #include "catalog/storage.h"
30 : #include "commands/policy.h"
31 : #include "miscadmin.h"
32 : #include "nodes/nodeFuncs.h"
33 : #include "parser/parse_utilcmd.h"
34 : #include "rewrite/rewriteDefine.h"
35 : #include "rewrite/rewriteManip.h"
36 : #include "rewrite/rewriteSupport.h"
37 : #include "utils/acl.h"
38 : #include "utils/builtins.h"
39 : #include "utils/inval.h"
40 : #include "utils/lsyscache.h"
41 : #include "utils/rel.h"
42 : #include "utils/snapmgr.h"
43 : #include "utils/syscache.h"
44 : #include "utils/tqual.h"
45 :
46 :
47 : static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
48 : bool isSelect, bool requireColumnNameMatch);
49 : static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
50 : static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
51 :
52 :
53 : /*
54 : * InsertRule -
55 : * takes the arguments and inserts them as a row into the system
56 : * relation "pg_rewrite"
57 : */
58 : static Oid
59 523 : InsertRule(char *rulname,
60 : int evtype,
61 : Oid eventrel_oid,
62 : bool evinstead,
63 : Node *event_qual,
64 : List *action,
65 : bool replace)
66 : {
67 523 : char *evqual = nodeToString(event_qual);
68 523 : char *actiontree = nodeToString((Node *) action);
69 : Datum values[Natts_pg_rewrite];
70 : bool nulls[Natts_pg_rewrite];
71 : bool replaces[Natts_pg_rewrite];
72 : NameData rname;
73 : Relation pg_rewrite_desc;
74 : HeapTuple tup,
75 : oldtup;
76 : Oid rewriteObjectId;
77 : ObjectAddress myself,
78 : referenced;
79 523 : bool is_update = false;
80 :
81 : /*
82 : * Set up *nulls and *values arrays
83 : */
84 523 : MemSet(nulls, false, sizeof(nulls));
85 :
86 523 : namestrcpy(&rname, rulname);
87 523 : values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
88 523 : values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
89 523 : values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
90 523 : values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
91 523 : values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
92 523 : values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
93 523 : values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
94 :
95 : /*
96 : * Ready to store new pg_rewrite tuple
97 : */
98 523 : pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
99 :
100 : /*
101 : * Check to see if we are replacing an existing tuple
102 : */
103 523 : oldtup = SearchSysCache2(RULERELNAME,
104 : ObjectIdGetDatum(eventrel_oid),
105 : PointerGetDatum(rulname));
106 :
107 523 : if (HeapTupleIsValid(oldtup))
108 : {
109 22 : if (!replace)
110 0 : ereport(ERROR,
111 : (errcode(ERRCODE_DUPLICATE_OBJECT),
112 : errmsg("rule \"%s\" for relation \"%s\" already exists",
113 : rulname, get_rel_name(eventrel_oid))));
114 :
115 : /*
116 : * When replacing, we don't need to replace every attribute
117 : */
118 22 : MemSet(replaces, false, sizeof(replaces));
119 22 : replaces[Anum_pg_rewrite_ev_type - 1] = true;
120 22 : replaces[Anum_pg_rewrite_is_instead - 1] = true;
121 22 : replaces[Anum_pg_rewrite_ev_qual - 1] = true;
122 22 : replaces[Anum_pg_rewrite_ev_action - 1] = true;
123 :
124 22 : tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
125 : values, nulls, replaces);
126 :
127 22 : CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
128 :
129 22 : ReleaseSysCache(oldtup);
130 :
131 22 : rewriteObjectId = HeapTupleGetOid(tup);
132 22 : is_update = true;
133 : }
134 : else
135 : {
136 501 : tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
137 :
138 501 : rewriteObjectId = CatalogTupleInsert(pg_rewrite_desc, tup);
139 : }
140 :
141 :
142 523 : heap_freetuple(tup);
143 :
144 : /* If replacing, get rid of old dependencies and make new ones */
145 523 : if (is_update)
146 22 : deleteDependencyRecordsFor(RewriteRelationId, rewriteObjectId, false);
147 :
148 : /*
149 : * Install dependency on rule's relation to ensure it will go away on
150 : * relation deletion. If the rule is ON SELECT, make the dependency
151 : * implicit --- this prevents deleting a view's SELECT rule. Other kinds
152 : * of rules can be AUTO.
153 : */
154 523 : myself.classId = RewriteRelationId;
155 523 : myself.objectId = rewriteObjectId;
156 523 : myself.objectSubId = 0;
157 :
158 523 : referenced.classId = RelationRelationId;
159 523 : referenced.objectId = eventrel_oid;
160 523 : referenced.objectSubId = 0;
161 :
162 523 : recordDependencyOn(&myself, &referenced,
163 : (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
164 :
165 : /*
166 : * Also install dependencies on objects referenced in action and qual.
167 : */
168 523 : recordDependencyOnExpr(&myself, (Node *) action, NIL,
169 : DEPENDENCY_NORMAL);
170 :
171 523 : if (event_qual != NULL)
172 : {
173 : /* Find query containing OLD/NEW rtable entries */
174 23 : Query *qry = linitial_node(Query, action);
175 :
176 23 : qry = getInsertSelectQuery(qry, NULL);
177 23 : recordDependencyOnExpr(&myself, event_qual, qry->rtable,
178 : DEPENDENCY_NORMAL);
179 : }
180 :
181 : /* Post creation hook for new rule */
182 523 : InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
183 :
184 523 : heap_close(pg_rewrite_desc, RowExclusiveLock);
185 :
186 523 : return rewriteObjectId;
187 : }
188 :
189 : /*
190 : * DefineRule
191 : * Execute a CREATE RULE command.
192 : */
193 : ObjectAddress
194 101 : DefineRule(RuleStmt *stmt, const char *queryString)
195 : {
196 : List *actions;
197 : Node *whereClause;
198 : Oid relId;
199 :
200 : /* Parse analysis. */
201 101 : transformRuleStmt(stmt, queryString, &actions, &whereClause);
202 :
203 : /*
204 : * Find and lock the relation. Lock level should match
205 : * DefineQueryRewrite.
206 : */
207 99 : relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
208 :
209 : /* ... and execute */
210 297 : return DefineQueryRewrite(stmt->rulename,
211 : relId,
212 : whereClause,
213 : stmt->event,
214 99 : stmt->instead,
215 99 : stmt->replace,
216 : actions);
217 : }
218 :
219 :
220 : /*
221 : * DefineQueryRewrite
222 : * Create a rule
223 : *
224 : * This is essentially the same as DefineRule() except that the rule's
225 : * action and qual have already been passed through parse analysis.
226 : */
227 : ObjectAddress
228 528 : DefineQueryRewrite(char *rulename,
229 : Oid event_relid,
230 : Node *event_qual,
231 : CmdType event_type,
232 : bool is_instead,
233 : bool replace,
234 : List *action)
235 : {
236 : Relation event_relation;
237 : ListCell *l;
238 : Query *query;
239 528 : bool RelisBecomingView = false;
240 528 : Oid ruleId = InvalidOid;
241 : ObjectAddress address;
242 :
243 : /*
244 : * If we are installing an ON SELECT rule, we had better grab
245 : * AccessExclusiveLock to ensure no SELECTs are currently running on the
246 : * event relation. For other types of rules, it would be sufficient to
247 : * grab ShareRowExclusiveLock to lock out insert/update/delete actions and
248 : * to ensure that we lock out current CREATE RULE statements; but because
249 : * of race conditions in access to catalog entries, we can't do that yet.
250 : *
251 : * Note that this lock level should match the one used in DefineRule.
252 : */
253 528 : event_relation = heap_open(event_relid, AccessExclusiveLock);
254 :
255 : /*
256 : * Verify relation is of a type that rules can sensibly be applied to.
257 : * Internal callers can target materialized views, but transformRuleStmt()
258 : * blocks them for users. Don't mention them in the error message.
259 : */
260 989 : if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
261 896 : event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
262 437 : event_relation->rd_rel->relkind != RELKIND_VIEW &&
263 2 : event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
264 0 : ereport(ERROR,
265 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
266 : errmsg("\"%s\" is not a table or view",
267 : RelationGetRelationName(event_relation))));
268 :
269 528 : if (!allowSystemTableMods && IsSystemRelation(event_relation))
270 0 : ereport(ERROR,
271 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
272 : errmsg("permission denied: \"%s\" is a system catalog",
273 : RelationGetRelationName(event_relation))));
274 :
275 : /*
276 : * Check user has permission to apply rules to this relation.
277 : */
278 528 : if (!pg_class_ownercheck(event_relid, GetUserId()))
279 0 : aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
280 0 : RelationGetRelationName(event_relation));
281 :
282 : /*
283 : * No rule actions that modify OLD or NEW
284 : */
285 1062 : foreach(l, action)
286 : {
287 534 : query = lfirst_node(Query, l);
288 534 : if (query->resultRelation == 0)
289 446 : continue;
290 : /* Don't be fooled by INSERT/SELECT */
291 88 : if (query != getInsertSelectQuery(query, NULL))
292 5 : continue;
293 83 : if (query->resultRelation == PRS2_OLD_VARNO)
294 0 : ereport(ERROR,
295 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
296 : errmsg("rule actions on OLD are not implemented"),
297 : errhint("Use views or triggers instead.")));
298 83 : if (query->resultRelation == PRS2_NEW_VARNO)
299 0 : ereport(ERROR,
300 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
301 : errmsg("rule actions on NEW are not implemented"),
302 : errhint("Use triggers instead.")));
303 : }
304 :
305 528 : if (event_type == CMD_SELECT)
306 : {
307 : /*
308 : * Rules ON SELECT are restricted to view definitions
309 : *
310 : * So there cannot be INSTEAD NOTHING, ...
311 : */
312 435 : if (list_length(action) == 0)
313 0 : ereport(ERROR,
314 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
315 : errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
316 : errhint("Use views instead.")));
317 :
318 : /*
319 : * ... there cannot be multiple actions, ...
320 : */
321 435 : if (list_length(action) > 1)
322 0 : ereport(ERROR,
323 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
324 : errmsg("multiple actions for rules on SELECT are not implemented")));
325 :
326 : /*
327 : * ... the one action must be a SELECT, ...
328 : */
329 435 : query = linitial_node(Query, action);
330 870 : if (!is_instead ||
331 435 : query->commandType != CMD_SELECT)
332 0 : ereport(ERROR,
333 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
334 : errmsg("rules on SELECT must have action INSTEAD SELECT")));
335 :
336 : /*
337 : * ... it cannot contain data-modifying WITH ...
338 : */
339 435 : if (query->hasModifyingCTE)
340 0 : ereport(ERROR,
341 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
342 : errmsg("rules on SELECT must not contain data-modifying statements in WITH")));
343 :
344 : /*
345 : * ... there can be no rule qual, ...
346 : */
347 435 : if (event_qual != NULL)
348 0 : ereport(ERROR,
349 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
350 : errmsg("event qualifications are not implemented for rules on SELECT")));
351 :
352 : /*
353 : * ... the targetlist of the SELECT action must exactly match the
354 : * event relation, ...
355 : */
356 435 : checkRuleResultList(query->targetList,
357 : RelationGetDescr(event_relation),
358 : true,
359 435 : event_relation->rd_rel->relkind !=
360 : RELKIND_MATVIEW);
361 :
362 : /*
363 : * ... there must not be another ON SELECT rule already ...
364 : */
365 435 : if (!replace && event_relation->rd_rules != NULL)
366 : {
367 : int i;
368 :
369 0 : for (i = 0; i < event_relation->rd_rules->numLocks; i++)
370 : {
371 : RewriteRule *rule;
372 :
373 0 : rule = event_relation->rd_rules->rules[i];
374 0 : if (rule->event == CMD_SELECT)
375 0 : ereport(ERROR,
376 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
377 : errmsg("\"%s\" is already a view",
378 : RelationGetRelationName(event_relation))));
379 : }
380 : }
381 :
382 : /*
383 : * ... and finally the rule must be named _RETURN.
384 : */
385 435 : if (strcmp(rulename, ViewSelectRuleName) != 0)
386 : {
387 : /*
388 : * In versions before 7.3, the expected name was _RETviewname. For
389 : * backwards compatibility with old pg_dump output, accept that
390 : * and silently change it to _RETURN. Since this is just a quick
391 : * backwards-compatibility hack, limit the number of characters
392 : * checked to a few less than NAMEDATALEN; this saves having to
393 : * worry about where a multibyte character might have gotten
394 : * truncated.
395 : */
396 0 : if (strncmp(rulename, "_RET", 4) != 0 ||
397 0 : strncmp(rulename + 4, RelationGetRelationName(event_relation),
398 : NAMEDATALEN - 4 - 4) != 0)
399 0 : ereport(ERROR,
400 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
401 : errmsg("view rule for \"%s\" must be named \"%s\"",
402 : RelationGetRelationName(event_relation),
403 : ViewSelectRuleName)));
404 0 : rulename = pstrdup(ViewSelectRuleName);
405 : }
406 :
407 : /*
408 : * Are we converting a relation to a view?
409 : *
410 : * If so, check that the relation is empty because the storage for the
411 : * relation is going to be deleted. Also insist that the rel not have
412 : * any triggers, indexes, child tables, policies, or RLS enabled.
413 : * (Note: these tests are too strict, because they will reject
414 : * relations that once had such but don't anymore. But we don't
415 : * really care, because this whole business of converting relations to
416 : * views is just a kluge to allow dump/reload of views that
417 : * participate in circular dependencies.)
418 : */
419 467 : if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
420 32 : event_relation->rd_rel->relkind != RELKIND_MATVIEW)
421 : {
422 : HeapScanDesc scanDesc;
423 : Snapshot snapshot;
424 :
425 6 : if (event_relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
426 1 : ereport(ERROR,
427 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
428 : errmsg("could not convert partitioned table \"%s\" to a view",
429 : RelationGetRelationName(event_relation))));
430 :
431 5 : if (event_relation->rd_rel->relispartition)
432 1 : ereport(ERROR,
433 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
434 : errmsg("could not convert partition \"%s\" to a view",
435 : RelationGetRelationName(event_relation))));
436 :
437 4 : snapshot = RegisterSnapshot(GetLatestSnapshot());
438 4 : scanDesc = heap_beginscan(event_relation, snapshot, 0, NULL);
439 4 : if (heap_getnext(scanDesc, ForwardScanDirection) != NULL)
440 0 : ereport(ERROR,
441 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
442 : errmsg("could not convert table \"%s\" to a view because it is not empty",
443 : RelationGetRelationName(event_relation))));
444 4 : heap_endscan(scanDesc);
445 4 : UnregisterSnapshot(snapshot);
446 :
447 4 : if (event_relation->rd_rel->relhastriggers)
448 0 : ereport(ERROR,
449 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
450 : errmsg("could not convert table \"%s\" to a view because it has triggers",
451 : RelationGetRelationName(event_relation)),
452 : errhint("In particular, the table cannot be involved in any foreign key relationships.")));
453 :
454 4 : if (event_relation->rd_rel->relhasindex)
455 0 : ereport(ERROR,
456 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
457 : errmsg("could not convert table \"%s\" to a view because it has indexes",
458 : RelationGetRelationName(event_relation))));
459 :
460 4 : if (event_relation->rd_rel->relhassubclass)
461 0 : ereport(ERROR,
462 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
463 : errmsg("could not convert table \"%s\" to a view because it has child tables",
464 : RelationGetRelationName(event_relation))));
465 :
466 4 : if (event_relation->rd_rel->relrowsecurity)
467 1 : ereport(ERROR,
468 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
469 : errmsg("could not convert table \"%s\" to a view because it has row security enabled",
470 : RelationGetRelationName(event_relation))));
471 :
472 3 : if (relation_has_policies(event_relation))
473 1 : ereport(ERROR,
474 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
475 : errmsg("could not convert table \"%s\" to a view because it has row security policies",
476 : RelationGetRelationName(event_relation))));
477 :
478 2 : RelisBecomingView = true;
479 : }
480 : }
481 : else
482 : {
483 : /*
484 : * For non-SELECT rules, a RETURNING list can appear in at most one of
485 : * the actions ... and there can't be any RETURNING list at all in a
486 : * conditional or non-INSTEAD rule. (Actually, there can be at most
487 : * one RETURNING list across all rules on the same event, but it seems
488 : * best to enforce that at rule expansion time.) If there is a
489 : * RETURNING list, it must match the event relation.
490 : */
491 93 : bool haveReturning = false;
492 :
493 191 : foreach(l, action)
494 : {
495 99 : query = lfirst_node(Query, l);
496 :
497 99 : if (!query->returningList)
498 83 : continue;
499 16 : if (haveReturning)
500 0 : ereport(ERROR,
501 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
502 : errmsg("cannot have multiple RETURNING lists in a rule")));
503 16 : haveReturning = true;
504 16 : if (event_qual != NULL)
505 0 : ereport(ERROR,
506 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
507 : errmsg("RETURNING lists are not supported in conditional rules")));
508 16 : if (!is_instead)
509 0 : ereport(ERROR,
510 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
511 : errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
512 16 : checkRuleResultList(query->returningList,
513 : RelationGetDescr(event_relation),
514 : false, false);
515 : }
516 : }
517 :
518 : /*
519 : * This rule is allowed - prepare to install it.
520 : */
521 :
522 : /* discard rule if it's null action and not INSTEAD; it's a no-op */
523 523 : if (action != NIL || is_instead)
524 : {
525 523 : ruleId = InsertRule(rulename,
526 : event_type,
527 : event_relid,
528 : is_instead,
529 : event_qual,
530 : action,
531 : replace);
532 :
533 : /*
534 : * Set pg_class 'relhasrules' field TRUE for event relation.
535 : *
536 : * Important side effect: an SI notice is broadcast to force all
537 : * backends (including me!) to update relcache entries with the new
538 : * rule.
539 : */
540 523 : SetRelationRuleStatus(event_relid, true);
541 : }
542 :
543 : /* ---------------------------------------------------------------------
544 : * If the relation is becoming a view:
545 : * - delete the associated storage files
546 : * - get rid of any system attributes in pg_attribute; a view shouldn't
547 : * have any of those
548 : * - remove the toast table; there is no need for it anymore, and its
549 : * presence would make vacuum slightly more complicated
550 : * - set relkind to RELKIND_VIEW, and adjust other pg_class fields
551 : * to be appropriate for a view
552 : *
553 : * NB: we had better have AccessExclusiveLock to do this ...
554 : * ---------------------------------------------------------------------
555 : */
556 523 : if (RelisBecomingView)
557 : {
558 : Relation relationRelation;
559 : Oid toastrelid;
560 : HeapTuple classTup;
561 : Form_pg_class classForm;
562 :
563 2 : relationRelation = heap_open(RelationRelationId, RowExclusiveLock);
564 2 : toastrelid = event_relation->rd_rel->reltoastrelid;
565 :
566 : /* drop storage while table still looks like a table */
567 2 : RelationDropStorage(event_relation);
568 2 : DeleteSystemAttributeTuples(event_relid);
569 :
570 : /*
571 : * Drop the toast table if any. (This won't take care of updating the
572 : * toast fields in the relation's own pg_class entry; we handle that
573 : * below.)
574 : */
575 2 : if (OidIsValid(toastrelid))
576 : {
577 : ObjectAddress toastobject;
578 :
579 : /*
580 : * Delete the dependency of the toast relation on the main
581 : * relation so we can drop the former without dropping the latter.
582 : */
583 1 : deleteDependencyRecordsFor(RelationRelationId, toastrelid,
584 : false);
585 :
586 : /* Make deletion of dependency record visible */
587 1 : CommandCounterIncrement();
588 :
589 : /* Now drop toast table, including its index */
590 1 : toastobject.classId = RelationRelationId;
591 1 : toastobject.objectId = toastrelid;
592 1 : toastobject.objectSubId = 0;
593 1 : performDeletion(&toastobject, DROP_RESTRICT,
594 : PERFORM_DELETION_INTERNAL);
595 : }
596 :
597 : /*
598 : * SetRelationRuleStatus may have updated the pg_class row, so we must
599 : * advance the command counter before trying to update it again.
600 : */
601 2 : CommandCounterIncrement();
602 :
603 : /*
604 : * Fix pg_class entry to look like a normal view's, including setting
605 : * the correct relkind and removal of reltoastrelid of the toast table
606 : * we potentially removed above.
607 : */
608 2 : classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(event_relid));
609 2 : if (!HeapTupleIsValid(classTup))
610 0 : elog(ERROR, "cache lookup failed for relation %u", event_relid);
611 2 : classForm = (Form_pg_class) GETSTRUCT(classTup);
612 :
613 2 : classForm->reltablespace = InvalidOid;
614 2 : classForm->relpages = 0;
615 2 : classForm->reltuples = 0;
616 2 : classForm->relallvisible = 0;
617 2 : classForm->reltoastrelid = InvalidOid;
618 2 : classForm->relhasindex = false;
619 2 : classForm->relkind = RELKIND_VIEW;
620 2 : classForm->relhasoids = false;
621 2 : classForm->relhaspkey = false;
622 2 : classForm->relfrozenxid = InvalidTransactionId;
623 2 : classForm->relminmxid = InvalidMultiXactId;
624 2 : classForm->relreplident = REPLICA_IDENTITY_NOTHING;
625 :
626 2 : CatalogTupleUpdate(relationRelation, &classTup->t_self, classTup);
627 :
628 2 : heap_freetuple(classTup);
629 2 : heap_close(relationRelation, RowExclusiveLock);
630 : }
631 :
632 523 : ObjectAddressSet(address, RewriteRelationId, ruleId);
633 :
634 : /* Close rel, but keep lock till commit... */
635 523 : heap_close(event_relation, NoLock);
636 :
637 523 : return address;
638 : }
639 :
640 : /*
641 : * checkRuleResultList
642 : * Verify that targetList produces output compatible with a tupledesc
643 : *
644 : * The targetList might be either a SELECT targetlist, or a RETURNING list;
645 : * isSelect tells which. This is used for choosing error messages.
646 : *
647 : * A SELECT targetlist may optionally require that column names match.
648 : */
649 : static void
650 451 : checkRuleResultList(List *targetList, TupleDesc resultDesc, bool isSelect,
651 : bool requireColumnNameMatch)
652 : {
653 : ListCell *tllist;
654 : int i;
655 :
656 : /* Only a SELECT may require a column name match. */
657 451 : Assert(isSelect || !requireColumnNameMatch);
658 :
659 451 : i = 0;
660 2467 : foreach(tllist, targetList)
661 : {
662 2017 : TargetEntry *tle = (TargetEntry *) lfirst(tllist);
663 : Oid tletypid;
664 : int32 tletypmod;
665 : Form_pg_attribute attr;
666 : char *attname;
667 :
668 : /* resjunk entries may be ignored */
669 2017 : if (tle->resjunk)
670 12 : continue;
671 2005 : i++;
672 2005 : if (i > resultDesc->natts)
673 1 : ereport(ERROR,
674 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
675 : isSelect ?
676 : errmsg("SELECT rule's target list has too many entries") :
677 : errmsg("RETURNING list has too many entries")));
678 :
679 2004 : attr = TupleDescAttr(resultDesc, i - 1);
680 2004 : attname = NameStr(attr->attname);
681 :
682 : /*
683 : * Disallow dropped columns in the relation. This is not really
684 : * expected to happen when creating an ON SELECT rule. It'd be
685 : * possible if someone tried to convert a relation with dropped
686 : * columns to a view, but the only case we care about supporting
687 : * table-to-view conversion for is pg_dump, and pg_dump won't do that.
688 : *
689 : * Unfortunately, the situation is also possible when adding a rule
690 : * with RETURNING to a regular table, and rejecting that case is
691 : * altogether more annoying. In principle we could support it by
692 : * modifying the targetlist to include dummy NULL columns
693 : * corresponding to the dropped columns in the tupdesc. However,
694 : * places like ruleutils.c would have to be fixed to not process such
695 : * entries, and that would take an uncertain and possibly rather large
696 : * amount of work. (Note we could not dodge that by marking the dummy
697 : * columns resjunk, since it's precisely the non-resjunk tlist columns
698 : * that are expected to correspond to table columns.)
699 : */
700 2004 : if (attr->attisdropped)
701 0 : ereport(ERROR,
702 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
703 : isSelect ?
704 : errmsg("cannot convert relation containing dropped columns to view") :
705 : errmsg("cannot create a RETURNING list for a relation containing dropped columns")));
706 :
707 : /* Check name match if required; no need for two error texts here */
708 2004 : if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
709 0 : ereport(ERROR,
710 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
711 : errmsg("SELECT rule's target entry %d has different column name from column \"%s\"",
712 : i, attname),
713 : errdetail("SELECT target entry is named \"%s\".",
714 : tle->resname)));
715 :
716 : /* Check type match. */
717 2004 : tletypid = exprType((Node *) tle->expr);
718 2004 : if (attr->atttypid != tletypid)
719 0 : ereport(ERROR,
720 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
721 : isSelect ?
722 : errmsg("SELECT rule's target entry %d has different type from column \"%s\"",
723 : i, attname) :
724 : errmsg("RETURNING list's entry %d has different type from column \"%s\"",
725 : i, attname),
726 : isSelect ?
727 : errdetail("SELECT target entry has type %s, but column has type %s.",
728 : format_type_be(tletypid),
729 : format_type_be(attr->atttypid)) :
730 : errdetail("RETURNING list entry has type %s, but column has type %s.",
731 : format_type_be(tletypid),
732 : format_type_be(attr->atttypid))));
733 :
734 : /*
735 : * Allow typmods to be different only if one of them is -1, ie,
736 : * "unspecified". This is necessary for cases like "numeric", where
737 : * the table will have a filled-in default length but the select
738 : * rule's expression will probably have typmod = -1.
739 : */
740 2004 : tletypmod = exprTypmod((Node *) tle->expr);
741 2004 : if (attr->atttypmod != tletypmod &&
742 0 : attr->atttypmod != -1 && tletypmod != -1)
743 0 : ereport(ERROR,
744 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
745 : isSelect ?
746 : errmsg("SELECT rule's target entry %d has different size from column \"%s\"",
747 : i, attname) :
748 : errmsg("RETURNING list's entry %d has different size from column \"%s\"",
749 : i, attname),
750 : isSelect ?
751 : errdetail("SELECT target entry has type %s, but column has type %s.",
752 : format_type_with_typemod(tletypid, tletypmod),
753 : format_type_with_typemod(attr->atttypid,
754 : attr->atttypmod)) :
755 : errdetail("RETURNING list entry has type %s, but column has type %s.",
756 : format_type_with_typemod(tletypid, tletypmod),
757 : format_type_with_typemod(attr->atttypid,
758 : attr->atttypmod))));
759 : }
760 :
761 450 : if (i != resultDesc->natts)
762 0 : ereport(ERROR,
763 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
764 : isSelect ?
765 : errmsg("SELECT rule's target list has too few entries") :
766 : errmsg("RETURNING list has too few entries")));
767 450 : }
768 :
769 : /*
770 : * setRuleCheckAsUser
771 : * Recursively scan a query or expression tree and set the checkAsUser
772 : * field to the given userid in all rtable entries.
773 : *
774 : * Note: for a view (ON SELECT rule), the checkAsUser field of the OLD
775 : * RTE entry will be overridden when the view rule is expanded, and the
776 : * checkAsUser field of the NEW entry is irrelevant because that entry's
777 : * requiredPerms bits will always be zero. However, for other types of rules
778 : * it's important to set these fields to match the rule owner. So we just set
779 : * them always.
780 : */
781 : void
782 1968 : setRuleCheckAsUser(Node *node, Oid userid)
783 : {
784 1968 : (void) setRuleCheckAsUser_walker(node, &userid);
785 1968 : }
786 :
787 : static bool
788 7111 : setRuleCheckAsUser_walker(Node *node, Oid *context)
789 : {
790 7111 : if (node == NULL)
791 1994 : return false;
792 5117 : if (IsA(node, Query))
793 : {
794 1086 : setRuleCheckAsUser_Query((Query *) node, *context);
795 1086 : return false;
796 : }
797 4031 : return expression_tree_walker(node, setRuleCheckAsUser_walker,
798 : (void *) context);
799 : }
800 :
801 : static void
802 1260 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
803 : {
804 : ListCell *l;
805 :
806 : /* Set all the RTEs in this query node */
807 5173 : foreach(l, qry->rtable)
808 : {
809 3913 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
810 :
811 3913 : if (rte->rtekind == RTE_SUBQUERY)
812 : {
813 : /* Recurse into subquery in FROM */
814 166 : setRuleCheckAsUser_Query(rte->subquery, userid);
815 : }
816 : else
817 3747 : rte->checkAsUser = userid;
818 : }
819 :
820 : /* Recurse into subquery-in-WITH */
821 1268 : foreach(l, qry->cteList)
822 : {
823 8 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
824 :
825 8 : setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
826 : }
827 :
828 : /* If there are sublinks, search for them and process their RTEs */
829 1260 : if (qry->hasSubLinks)
830 63 : query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
831 : QTW_IGNORE_RC_SUBQUERIES);
832 1260 : }
833 :
834 :
835 : /*
836 : * Change the firing semantics of an existing rule.
837 : */
838 : void
839 0 : EnableDisableRule(Relation rel, const char *rulename,
840 : char fires_when)
841 : {
842 : Relation pg_rewrite_desc;
843 0 : Oid owningRel = RelationGetRelid(rel);
844 : Oid eventRelationOid;
845 : HeapTuple ruletup;
846 0 : bool changed = false;
847 :
848 : /*
849 : * Find the rule tuple to change.
850 : */
851 0 : pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
852 0 : ruletup = SearchSysCacheCopy2(RULERELNAME,
853 : ObjectIdGetDatum(owningRel),
854 : PointerGetDatum(rulename));
855 0 : if (!HeapTupleIsValid(ruletup))
856 0 : ereport(ERROR,
857 : (errcode(ERRCODE_UNDEFINED_OBJECT),
858 : errmsg("rule \"%s\" for relation \"%s\" does not exist",
859 : rulename, get_rel_name(owningRel))));
860 :
861 : /*
862 : * Verify that the user has appropriate permissions.
863 : */
864 0 : eventRelationOid = ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_class;
865 0 : Assert(eventRelationOid == owningRel);
866 0 : if (!pg_class_ownercheck(eventRelationOid, GetUserId()))
867 0 : aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
868 0 : get_rel_name(eventRelationOid));
869 :
870 : /*
871 : * Change ev_enabled if it is different from the desired new state.
872 : */
873 0 : if (DatumGetChar(((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled) !=
874 : fires_when)
875 : {
876 0 : ((Form_pg_rewrite) GETSTRUCT(ruletup))->ev_enabled =
877 : CharGetDatum(fires_when);
878 0 : CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
879 :
880 0 : changed = true;
881 : }
882 :
883 0 : InvokeObjectPostAlterHook(RewriteRelationId,
884 : HeapTupleGetOid(ruletup), 0);
885 :
886 0 : heap_freetuple(ruletup);
887 0 : heap_close(pg_rewrite_desc, RowExclusiveLock);
888 :
889 : /*
890 : * If we changed anything, broadcast a SI inval message to force each
891 : * backend (including our own!) to rebuild relation's relcache entry.
892 : * Otherwise they will fail to apply the change promptly.
893 : */
894 0 : if (changed)
895 0 : CacheInvalidateRelcache(rel);
896 0 : }
897 :
898 :
899 : /*
900 : * Perform permissions and integrity checks before acquiring a relation lock.
901 : */
902 : static void
903 5 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
904 : void *arg)
905 : {
906 : HeapTuple tuple;
907 : Form_pg_class form;
908 :
909 5 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
910 5 : if (!HeapTupleIsValid(tuple))
911 5 : return; /* concurrently dropped */
912 5 : form = (Form_pg_class) GETSTRUCT(tuple);
913 :
914 : /* only tables and views can have rules */
915 10 : if (form->relkind != RELKIND_RELATION &&
916 6 : form->relkind != RELKIND_VIEW &&
917 1 : form->relkind != RELKIND_PARTITIONED_TABLE)
918 0 : ereport(ERROR,
919 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
920 : errmsg("\"%s\" is not a table or view", rv->relname)));
921 :
922 5 : if (!allowSystemTableMods && IsSystemClass(relid, form))
923 0 : ereport(ERROR,
924 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
925 : errmsg("permission denied: \"%s\" is a system catalog",
926 : rv->relname)));
927 :
928 : /* you must own the table to rename one of its rules */
929 5 : if (!pg_class_ownercheck(relid, GetUserId()))
930 0 : aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, rv->relname);
931 :
932 5 : ReleaseSysCache(tuple);
933 : }
934 :
935 : /*
936 : * Rename an existing rewrite rule.
937 : */
938 : ObjectAddress
939 5 : RenameRewriteRule(RangeVar *relation, const char *oldName,
940 : const char *newName)
941 : {
942 : Oid relid;
943 : Relation targetrel;
944 : Relation pg_rewrite_desc;
945 : HeapTuple ruletup;
946 : Form_pg_rewrite ruleform;
947 : Oid ruleOid;
948 : ObjectAddress address;
949 :
950 : /*
951 : * Look up name, check permissions, and acquire lock (which we will NOT
952 : * release until end of transaction).
953 : */
954 5 : relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
955 : false, false,
956 : RangeVarCallbackForRenameRule,
957 : NULL);
958 :
959 : /* Have lock already, so just need to build relcache entry. */
960 5 : targetrel = relation_open(relid, NoLock);
961 :
962 : /* Prepare to modify pg_rewrite */
963 5 : pg_rewrite_desc = heap_open(RewriteRelationId, RowExclusiveLock);
964 :
965 : /* Fetch the rule's entry (it had better exist) */
966 5 : ruletup = SearchSysCacheCopy2(RULERELNAME,
967 : ObjectIdGetDatum(relid),
968 : PointerGetDatum(oldName));
969 5 : if (!HeapTupleIsValid(ruletup))
970 1 : ereport(ERROR,
971 : (errcode(ERRCODE_UNDEFINED_OBJECT),
972 : errmsg("rule \"%s\" for relation \"%s\" does not exist",
973 : oldName, RelationGetRelationName(targetrel))));
974 4 : ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
975 4 : ruleOid = HeapTupleGetOid(ruletup);
976 :
977 : /* rule with the new name should not already exist */
978 4 : if (IsDefinedRewriteRule(relid, newName))
979 1 : ereport(ERROR,
980 : (errcode(ERRCODE_DUPLICATE_OBJECT),
981 : errmsg("rule \"%s\" for relation \"%s\" already exists",
982 : newName, RelationGetRelationName(targetrel))));
983 :
984 : /*
985 : * We disallow renaming ON SELECT rules, because they should always be
986 : * named "_RETURN".
987 : */
988 3 : if (ruleform->ev_type == CMD_SELECT + '0')
989 1 : ereport(ERROR,
990 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
991 : errmsg("renaming an ON SELECT rule is not allowed")));
992 :
993 : /* OK, do the update */
994 2 : namestrcpy(&(ruleform->rulename), newName);
995 :
996 2 : CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
997 :
998 2 : heap_freetuple(ruletup);
999 2 : heap_close(pg_rewrite_desc, RowExclusiveLock);
1000 :
1001 : /*
1002 : * Invalidate relation's relcache entry so that other backends (and this
1003 : * one too!) are sent SI message to make them rebuild relcache entries.
1004 : * (Ideally this should happen automatically...)
1005 : */
1006 2 : CacheInvalidateRelcache(targetrel);
1007 :
1008 2 : ObjectAddressSet(address, RewriteRelationId, ruleOid);
1009 :
1010 : /*
1011 : * Close rel, but keep exclusive lock!
1012 : */
1013 2 : relation_close(targetrel, NoLock);
1014 :
1015 2 : return address;
1016 : }
|