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