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