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