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-2024, 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
2357 peter_e@gmx.net 52 :CBC 7342 : 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 : : {
7943 tgl@sss.pgh.pa.us 60 : 7342 : char *evqual = nodeToString(event_qual);
61 : 7342 : char *actiontree = nodeToString((Node *) action);
62 : : Datum values[Natts_pg_rewrite];
638 peter@eisentraut.org 63 : 7342 : 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;
7895 tgl@sss.pgh.pa.us 71 : 7342 : bool is_update = false;
72 : :
73 : : /*
74 : : * Set up *nulls and *values arrays
75 : : */
8691 76 : 7342 : namestrcpy(&rname, rulname);
4686 77 : 7342 : values[Anum_pg_rewrite_rulename - 1] = NameGetDatum(&rname);
78 : 7342 : values[Anum_pg_rewrite_ev_class - 1] = ObjectIdGetDatum(eventrel_oid);
79 : 7342 : values[Anum_pg_rewrite_ev_type - 1] = CharGetDatum(evtype + '0');
80 : 7342 : values[Anum_pg_rewrite_ev_enabled - 1] = CharGetDatum(RULE_FIRES_ON_ORIGIN);
81 : 7342 : values[Anum_pg_rewrite_is_instead - 1] = BoolGetDatum(evinstead);
82 : 7342 : values[Anum_pg_rewrite_ev_qual - 1] = CStringGetTextDatum(evqual);
83 : 7342 : values[Anum_pg_rewrite_ev_action - 1] = CStringGetTextDatum(actiontree);
84 : :
85 : : /*
86 : : * Ready to store new pg_rewrite tuple
87 : : */
1910 andres@anarazel.de 88 : 7342 : pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
89 : :
90 : : /*
91 : : * Check to see if we are replacing an existing tuple
92 : : */
5173 rhaas@postgresql.org 93 : 7342 : oldtup = SearchSysCache2(RULERELNAME,
94 : : ObjectIdGetDatum(eventrel_oid),
95 : : PointerGetDatum(rulname));
96 : :
7895 tgl@sss.pgh.pa.us 97 [ + + ]: 7342 : if (HeapTupleIsValid(oldtup))
98 : : {
638 peter@eisentraut.org 99 : 121 : bool replaces[Natts_pg_rewrite] = {0};
100 : :
5556 peter_e@gmx.net 101 [ - + ]: 121 : if (!replace)
7569 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 : : */
5642 tgl@sss.pgh.pa.us 110 :CBC 121 : replaces[Anum_pg_rewrite_ev_type - 1] = true;
111 : 121 : replaces[Anum_pg_rewrite_is_instead - 1] = true;
112 : 121 : replaces[Anum_pg_rewrite_ev_qual - 1] = true;
113 : 121 : replaces[Anum_pg_rewrite_ev_action - 1] = true;
114 : :
115 : 121 : tup = heap_modify_tuple(oldtup, RelationGetDescr(pg_rewrite_desc),
116 : : values, nulls, replaces);
117 : :
2630 alvherre@alvh.no-ip. 118 : 121 : CatalogTupleUpdate(pg_rewrite_desc, &tup->t_self, tup);
119 : :
7895 tgl@sss.pgh.pa.us 120 : 121 : ReleaseSysCache(oldtup);
121 : :
1972 andres@anarazel.de 122 : 121 : rewriteObjectId = ((Form_pg_rewrite) GETSTRUCT(tup))->oid;
7895 tgl@sss.pgh.pa.us 123 : 121 : is_update = true;
124 : : }
125 : : else
126 : : {
1972 andres@anarazel.de 127 : 7221 : rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,
128 : : RewriteOidIndexId,
129 : : Anum_pg_rewrite_oid);
130 : 7221 : values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId);
131 : :
5642 tgl@sss.pgh.pa.us 132 : 7221 : tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
133 : :
1972 andres@anarazel.de 134 : 7221 : CatalogTupleInsert(pg_rewrite_desc, tup);
135 : : }
136 : :
137 : :
8691 tgl@sss.pgh.pa.us 138 : 7342 : heap_freetuple(tup);
139 : :
140 : : /* If replacing, get rid of old dependencies and make new ones */
7895 141 [ + + ]: 7342 : if (is_update)
4814 142 : 121 : 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 : : */
6940 150 : 7342 : myself.classId = RewriteRelationId;
7947 151 : 7342 : myself.objectId = rewriteObjectId;
152 : 7342 : myself.objectSubId = 0;
153 : :
6940 154 : 7342 : referenced.classId = RelationRelationId;
7947 155 : 7342 : referenced.objectId = eventrel_oid;
156 : 7342 : referenced.objectSubId = 0;
157 : :
158 [ + + ]: 7342 : 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 : : */
7943 164 : 7342 : recordDependencyOnExpr(&myself, (Node *) action, NIL,
165 : : DEPENDENCY_NORMAL);
166 : :
167 [ + + ]: 7342 : if (event_qual != NULL)
168 : : {
169 : : /* Find query containing OLD/NEW rtable entries */
2561 170 : 124 : Query *qry = linitial_node(Query, action);
171 : :
7943 172 : 124 : qry = getInsertSelectQuery(qry, NULL);
173 : 124 : recordDependencyOnExpr(&myself, event_qual, qry->rtable,
174 : : DEPENDENCY_NORMAL);
175 : : }
176 : :
177 : : /* Post creation hook for new rule */
4057 rhaas@postgresql.org 178 [ + + ]: 7342 : InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
179 : :
1910 andres@anarazel.de 180 : 7342 : table_close(pg_rewrite_desc, RowExclusiveLock);
181 : :
8691 tgl@sss.pgh.pa.us 182 : 7342 : return rewriteObjectId;
183 : : }
184 : :
185 : : /*
186 : : * DefineRule
187 : : * Execute a CREATE RULE command.
188 : : */
189 : : ObjectAddress
6242 190 : 515 : DefineRule(RuleStmt *stmt, const char *queryString)
191 : : {
192 : : List *actions;
193 : : Node *whereClause;
194 : : Oid relId;
195 : :
196 : : /* Parse analysis. */
6140 197 : 515 : transformRuleStmt(stmt, queryString, &actions, &whereClause);
198 : :
199 : : /*
200 : : * Find and lock the relation. Lock level should match
201 : : * DefineQueryRewrite.
202 : : */
4519 rhaas@postgresql.org 203 : 506 : relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
204 : :
205 : : /* ... and execute */
4124 206 : 999 : return DefineQueryRewrite(stmt->rulename,
207 : : relId,
208 : : whereClause,
209 : : stmt->event,
210 : 506 : stmt->instead,
211 : 506 : 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
2357 peter_e@gmx.net 224 : 7355 : 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;
3973 bruce@momjian.us 235 : 7355 : 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 : : */
1910 andres@anarazel.de 248 : 7355 : 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 : : */
5450 tgl@sss.pgh.pa.us 255 [ + + ]: 7355 : if (event_relation->rd_rel->relkind != RELKIND_RELATION &&
4060 kgrittn@postgresql.o 256 [ + + ]: 7058 : event_relation->rd_rel->relkind != RELKIND_MATVIEW &&
2685 rhaas@postgresql.org 257 [ + + ]: 6839 : event_relation->rd_rel->relkind != RELKIND_VIEW &&
258 [ - + ]: 6 : event_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
5450 tgl@sss.pgh.pa.us 259 [ # # ]:UBC 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 : :
5450 tgl@sss.pgh.pa.us 265 [ + + + + ]:CBC 7355 : if (!allowSystemTableMods && IsSystemRelation(event_relation))
266 [ + - ]: 1 : 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 : : */
518 peter@eisentraut.org 274 [ - + ]: 7354 : if (!object_ownercheck(RelationRelationId, event_relid, GetUserId()))
2325 peter_e@gmx.net 275 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(event_relation->rd_rel->relkind),
7562 tgl@sss.pgh.pa.us 276 : 0 : RelationGetRelationName(event_relation));
277 : :
278 : : /*
279 : : * No rule actions that modify OLD or NEW
280 : : */
8689 tgl@sss.pgh.pa.us 281 [ + - + + :CBC 14731 : foreach(l, action)
+ + ]
282 : : {
2561 283 : 7377 : query = lfirst_node(Query, l);
8531 284 [ + + ]: 7377 : if (query->resultRelation == 0)
285 : 6994 : continue;
286 : : /* Don't be fooled by INSERT/SELECT */
287 [ + + ]: 383 : if (query != getInsertSelectQuery(query, NULL))
288 : 29 : continue;
8615 289 [ - + ]: 354 : if (query->resultRelation == PRS2_OLD_VARNO)
7569 tgl@sss.pgh.pa.us 290 [ # # ]:UBC 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.")));
8615 tgl@sss.pgh.pa.us 294 [ - + ]:CBC 354 : if (query->resultRelation == PRS2_NEW_VARNO)
7569 tgl@sss.pgh.pa.us 295 [ # # ]:UBC 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 : :
9357 bruce@momjian.us 301 [ + + ]:CBC 7354 : 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 : : */
499 tgl@sss.pgh.pa.us 308 [ + + ]: 6858 : if (event_relation->rd_rel->relkind != RELKIND_VIEW &&
309 [ + + ]: 228 : event_relation->rd_rel->relkind != RELKIND_MATVIEW)
310 [ + - ]: 9 : 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 : : */
606 319 [ - + ]: 6849 : if (action == NIL)
7569 tgl@sss.pgh.pa.us 320 [ # # ]:UBC 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 : : */
7259 neilc@samurai.com 328 [ - + ]:CBC 6849 : if (list_length(action) > 1)
7569 tgl@sss.pgh.pa.us 329 [ # # ]:UBC 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 : : */
2561 tgl@sss.pgh.pa.us 336 :CBC 6849 : query = linitial_node(Query, action);
6455 337 [ + - ]: 6849 : if (!is_instead ||
2647 338 [ - + ]: 6849 : query->commandType != CMD_SELECT)
7569 tgl@sss.pgh.pa.us 339 [ # # ]:UBC 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 : : */
4797 tgl@sss.pgh.pa.us 346 [ - + ]:CBC 6849 : if (query->hasModifyingCTE)
4797 tgl@sss.pgh.pa.us 347 [ # # ]:UBC 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 : : */
9326 bruce@momjian.us 354 [ - + ]:CBC 6849 : if (event_qual != NULL)
7569 tgl@sss.pgh.pa.us 355 [ # # ]:UBC 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 : : */
6434 tgl@sss.pgh.pa.us 363 :CBC 6849 : checkRuleResultList(query->targetList,
364 : : RelationGetDescr(event_relation),
365 : : true,
3814 kgrittn@postgresql.o 366 : 6849 : event_relation->rd_rel->relkind !=
367 : : RELKIND_MATVIEW);
368 : :
369 : : /*
370 : : * ... there must not be another ON SELECT rule already ...
371 : : */
7895 tgl@sss.pgh.pa.us 372 [ + + - + ]: 6849 : if (!replace && event_relation->rd_rules != NULL)
373 : : {
374 : : int i;
375 : :
9091 bruce@momjian.us 376 [ # # ]:UBC 0 : for (i = 0; i < event_relation->rd_rules->numLocks; i++)
377 : : {
378 : : RewriteRule *rule;
379 : :
9326 380 : 0 : rule = event_relation->rd_rules->rules[i];
381 [ # # ]: 0 : if (rule->event == CMD_SELECT)
7569 tgl@sss.pgh.pa.us 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 : : */
6242 tgl@sss.pgh.pa.us 392 [ - + ]:CBC 6849 : 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 : : */
6242 tgl@sss.pgh.pa.us 403 [ # # ]:UBC 0 : if (strncmp(rulename, "_RET", 4) != 0 ||
6075 404 [ # # ]: 0 : strncmp(rulename + 4, RelationGetRelationName(event_relation),
405 : : NAMEDATALEN - 4 - 4) != 0)
7569 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)));
6242 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 : : */
6402 bruce@momjian.us 424 :CBC 496 : bool haveReturning = false;
425 : :
6434 tgl@sss.pgh.pa.us 426 [ + - + + : 1012 : foreach(l, action)
+ + ]
427 : : {
2561 428 : 519 : query = lfirst_node(Query, l);
429 : :
6434 430 [ + + ]: 519 : if (!query->returningList)
431 : 463 : continue;
432 [ - + ]: 56 : if (haveReturning)
6434 tgl@sss.pgh.pa.us 433 [ # # ]:UBC 0 : ereport(ERROR,
434 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
435 : : errmsg("cannot have multiple RETURNING lists in a rule")));
6434 tgl@sss.pgh.pa.us 436 :CBC 56 : haveReturning = true;
437 [ - + ]: 56 : if (event_qual != NULL)
6434 tgl@sss.pgh.pa.us 438 [ # # ]:UBC 0 : ereport(ERROR,
439 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
440 : : errmsg("RETURNING lists are not supported in conditional rules")));
6434 tgl@sss.pgh.pa.us 441 [ - + ]:CBC 56 : if (!is_instead)
6434 tgl@sss.pgh.pa.us 442 [ # # ]:UBC 0 : ereport(ERROR,
443 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
444 : : errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
6434 tgl@sss.pgh.pa.us 445 :CBC 56 : 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 : : */
545 455 [ - + ]: 493 : if (strcmp(rulename, ViewSelectRuleName) == 0)
545 tgl@sss.pgh.pa.us 456 [ # # ]:UBC 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 */
8615 tgl@sss.pgh.pa.us 468 [ - + - - ]:CBC 7342 : if (action != NIL || is_instead)
469 : : {
4124 rhaas@postgresql.org 470 : 7342 : 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 : : */
4060 tgl@sss.pgh.pa.us 485 : 7342 : SetRelationRuleStatus(event_relid, true);
486 : : }
487 : :
3330 alvherre@alvh.no-ip. 488 : 7342 : ObjectAddressSet(address, RewriteRelationId, ruleId);
489 : :
490 : : /* Close rel, but keep lock till commit... */
1910 andres@anarazel.de 491 : 7342 : table_close(event_relation, NoLock);
492 : :
3330 alvherre@alvh.no-ip. 493 : 7342 : 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
3814 kgrittn@postgresql.o 506 : 6905 : 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 [ + + - + ]: 6905 : Assert(isSelect || !requireColumnNameMatch);
514 : :
6434 tgl@sss.pgh.pa.us 515 : 6905 : i = 0;
516 [ + + + + : 66109 : foreach(tllist, targetList)
+ + ]
517 : : {
518 : 59207 : 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 [ + + ]: 59207 : if (tle->resjunk)
526 : 253 : continue;
527 : 58954 : i++;
528 [ + + ]: 58954 : if (i > resultDesc->natts)
529 [ + - - + ]: 3 : 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 : :
2429 andres@anarazel.de 535 : 58951 : attr = TupleDescAttr(resultDesc, i - 1);
6434 tgl@sss.pgh.pa.us 536 : 58951 : 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 [ - + ]: 58951 : if (attr->attisdropped)
6434 tgl@sss.pgh.pa.us 557 [ # # # # ]:UBC 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 */
3814 kgrittn@postgresql.o 564 [ + + - + ]:CBC 58951 : if (requireColumnNameMatch && strcmp(tle->resname, attname) != 0)
6434 tgl@sss.pgh.pa.us 565 [ # # ]:UBC 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. */
3574 tgl@sss.pgh.pa.us 573 :CBC 58951 : tletypid = exprType((Node *) tle->expr);
574 [ - + ]: 58951 : if (attr->atttypid != tletypid)
6434 tgl@sss.pgh.pa.us 575 [ # # # # :UBC 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 : : */
6434 tgl@sss.pgh.pa.us 596 :CBC 58951 : tletypmod = exprTypmod((Node *) tle->expr);
597 [ - + ]: 58951 : if (attr->atttypmod != tletypmod &&
6434 tgl@sss.pgh.pa.us 598 [ # # # # ]:UBC 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 : :
6434 tgl@sss.pgh.pa.us 617 [ - + ]:CBC 6902 : if (i != resultDesc->natts)
6434 tgl@sss.pgh.pa.us 618 [ # # # # ]:UBC 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")));
6434 tgl@sss.pgh.pa.us 623 :CBC 6902 : }
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
6431 631 : 33926 : setRuleCheckAsUser(Node *node, Oid userid)
632 : : {
633 : 33926 : (void) setRuleCheckAsUser_walker(node, &userid);
634 : 33926 : }
635 : :
636 : : static bool
637 : 160809 : setRuleCheckAsUser_walker(Node *node, Oid *context)
638 : : {
639 [ + + ]: 160809 : if (node == NULL)
640 : 35124 : return false;
641 [ + + ]: 125685 : if (IsA(node, Query))
642 : : {
643 : 17813 : setRuleCheckAsUser_Query((Query *) node, *context);
644 : 17813 : return false;
645 : : }
646 : 107872 : return expression_tree_walker(node, setRuleCheckAsUser_walker,
647 : : (void *) context);
648 : : }
649 : :
650 : : static void
6865 651 : 23982 : setRuleCheckAsUser_Query(Query *qry, Oid userid)
652 : : {
653 : : ListCell *l;
654 : :
655 : : /* Set in all RTEPermissionInfos for this query. */
495 alvherre@alvh.no-ip. 656 [ + + + + : 57876 : foreach(l, qry->rteperminfos)
+ + ]
657 : : {
658 : 33894 : RTEPermissionInfo *perminfo = lfirst_node(RTEPermissionInfo, l);
659 : :
660 : 33894 : perminfo->checkAsUser = userid;
661 : : }
662 : :
663 : : /* Now recurse to any subquery RTEs */
8598 tgl@sss.pgh.pa.us 664 [ + + + + : 80435 : foreach(l, qry->rtable)
+ + ]
665 : : {
666 : 56453 : RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
667 : :
8008 668 [ + + ]: 56453 : if (rte->rtekind == RTE_SUBQUERY)
7271 669 : 6054 : setRuleCheckAsUser_Query(rte->subquery, userid);
670 : : }
671 : :
672 : : /* Recurse into subquery-in-WITH */
5671 673 [ + + + + : 24097 : foreach(l, qry->cteList)
+ + ]
674 : : {
675 : 115 : CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
676 : :
2635 677 : 115 : setRuleCheckAsUser_Query(castNode(Query, cte->ctequery), userid);
678 : : }
679 : :
680 : : /* If there are sublinks, search for them and process their RTEs */
8598 681 [ + + ]: 23982 : if (qry->hasSubLinks)
8592 682 : 1120 : query_tree_walker(qry, setRuleCheckAsUser_walker, (void *) &userid,
683 : : QTW_IGNORE_RC_SUBQUERIES);
8598 684 : 23982 : }
685 : :
686 : :
687 : : /*
688 : : * Change the firing semantics of an existing rule.
689 : : */
690 : : void
6236 JanWieck@Yahoo.com 691 : 23 : EnableDisableRule(Relation rel, const char *rulename,
692 : : char fires_when)
693 : : {
694 : : Relation pg_rewrite_desc;
695 : 23 : Oid owningRel = RelationGetRelid(rel);
696 : : Oid eventRelationOid;
697 : : HeapTuple ruletup;
698 : : Form_pg_rewrite ruleform;
699 : 23 : bool changed = false;
700 : :
701 : : /*
702 : : * Find the rule tuple to change.
703 : : */
1910 andres@anarazel.de 704 : 23 : pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
5173 rhaas@postgresql.org 705 : 23 : ruletup = SearchSysCacheCopy2(RULERELNAME,
706 : : ObjectIdGetDatum(owningRel),
707 : : PointerGetDatum(rulename));
6236 JanWieck@Yahoo.com 708 [ - + ]: 23 : if (!HeapTupleIsValid(ruletup))
6236 JanWieck@Yahoo.com 709 [ # # ]:UBC 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 : :
1972 andres@anarazel.de 714 :CBC 23 : ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
715 : :
716 : : /*
717 : : * Verify that the user has appropriate permissions.
718 : : */
719 : 23 : eventRelationOid = ruleform->ev_class;
6236 JanWieck@Yahoo.com 720 [ - + ]: 23 : Assert(eventRelationOid == owningRel);
518 peter@eisentraut.org 721 [ - + ]: 23 : if (!object_ownercheck(RelationRelationId, eventRelationOid, GetUserId()))
2325 peter_e@gmx.net 722 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(eventRelationOid)),
5995 bruce@momjian.us 723 : 0 : get_rel_name(eventRelationOid));
724 : :
725 : : /*
726 : : * Change ev_enabled if it is different from the desired new state.
727 : : */
1972 andres@anarazel.de 728 [ + - ]:CBC 23 : if (DatumGetChar(ruleform->ev_enabled) !=
729 : : fires_when)
730 : : {
731 : 23 : ruleform->ev_enabled = CharGetDatum(fires_when);
2630 alvherre@alvh.no-ip. 732 : 23 : CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
733 : :
6236 JanWieck@Yahoo.com 734 : 23 : changed = true;
735 : : }
736 : :
1972 andres@anarazel.de 737 [ + + ]: 23 : InvokeObjectPostAlterHook(RewriteRelationId, ruleform->oid, 0);
738 : :
6236 JanWieck@Yahoo.com 739 : 23 : heap_freetuple(ruletup);
1910 andres@anarazel.de 740 : 23 : 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 : : */
6236 JanWieck@Yahoo.com 747 [ + - ]: 23 : if (changed)
748 : 23 : CacheInvalidateRelcache(rel);
749 : 23 : }
750 : :
751 : :
752 : : /*
753 : : * Perform permissions and integrity checks before acquiring a relation lock.
754 : : */
755 : : static void
4083 tgl@sss.pgh.pa.us 756 : 17 : RangeVarCallbackForRenameRule(const RangeVar *rv, Oid relid, Oid oldrelid,
757 : : void *arg)
758 : : {
759 : : HeapTuple tuple;
760 : : Form_pg_class form;
761 : :
762 : 17 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
763 [ - + ]: 17 : if (!HeapTupleIsValid(tuple))
4083 tgl@sss.pgh.pa.us 764 :UBC 0 : return; /* concurrently dropped */
4083 tgl@sss.pgh.pa.us 765 :CBC 17 : form = (Form_pg_class) GETSTRUCT(tuple);
766 : :
767 : : /* only tables and views can have rules */
2560 rhaas@postgresql.org 768 [ + + ]: 17 : if (form->relkind != RELKIND_RELATION &&
769 [ + + ]: 15 : form->relkind != RELKIND_VIEW &&
770 [ - + ]: 3 : form->relkind != RELKIND_PARTITIONED_TABLE)
4083 tgl@sss.pgh.pa.us 771 [ # # ]:UBC 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 : :
3790 rhaas@postgresql.org 776 [ + + + + ]:CBC 17 : if (!allowSystemTableMods && IsSystemClass(relid, form))
4083 tgl@sss.pgh.pa.us 777 [ + - ]: 1 : 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 */
518 peter@eisentraut.org 783 [ - + ]: 16 : if (!object_ownercheck(RelationRelationId, relid, GetUserId()))
2325 peter_e@gmx.net 784 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(get_rel_relkind(relid)), rv->relname);
785 : :
4083 tgl@sss.pgh.pa.us 786 :CBC 16 : ReleaseSysCache(tuple);
787 : : }
788 : :
789 : : /*
790 : : * Rename an existing rewrite rule.
791 : : */
792 : : ObjectAddress
793 : 17 : 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 : 17 : relid = RangeVarGetRelidExtended(relation, AccessExclusiveLock,
809 : : 0,
810 : : RangeVarCallbackForRenameRule,
811 : : NULL);
812 : :
813 : : /* Have lock already, so just need to build relcache entry. */
814 : 16 : targetrel = relation_open(relid, NoLock);
815 : :
816 : : /* Prepare to modify pg_rewrite */
1910 andres@anarazel.de 817 : 16 : pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
818 : :
819 : : /* Fetch the rule's entry (it had better exist) */
5173 rhaas@postgresql.org 820 : 16 : ruletup = SearchSysCacheCopy2(RULERELNAME,
821 : : ObjectIdGetDatum(relid),
822 : : PointerGetDatum(oldName));
8281 tgl@sss.pgh.pa.us 823 [ + + ]: 16 : if (!HeapTupleIsValid(ruletup))
7569 824 [ + - ]: 3 : ereport(ERROR,
825 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
826 : : errmsg("rule \"%s\" for relation \"%s\" does not exist",
827 : : oldName, RelationGetRelationName(targetrel))));
4083 828 : 13 : ruleform = (Form_pg_rewrite) GETSTRUCT(ruletup);
1972 andres@anarazel.de 829 : 13 : ruleOid = ruleform->oid;
830 : :
831 : : /* rule with the new name should not already exist */
4083 tgl@sss.pgh.pa.us 832 [ + + ]: 13 : if (IsDefinedRewriteRule(relid, newName))
7569 833 [ + - ]: 3 : 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 : : */
4083 842 [ + + ]: 10 : if (ruleform->ev_type == CMD_SELECT + '0')
843 [ + - ]: 3 : 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 : 7 : namestrcpy(&(ruleform->rulename), newName);
849 : :
2630 alvherre@alvh.no-ip. 850 : 7 : CatalogTupleUpdate(pg_rewrite_desc, &ruletup->t_self, ruletup);
851 : :
1422 michael@paquier.xyz 852 [ - + ]: 7 : InvokeObjectPostAlterHook(RewriteRelationId, ruleOid, 0);
853 : :
8281 tgl@sss.pgh.pa.us 854 : 7 : heap_freetuple(ruletup);
1910 andres@anarazel.de 855 : 7 : 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 : : */
4083 tgl@sss.pgh.pa.us 862 : 7 : CacheInvalidateRelcache(targetrel);
863 : :
3330 alvherre@alvh.no-ip. 864 : 7 : ObjectAddressSet(address, RewriteRelationId, ruleOid);
865 : :
866 : : /*
867 : : * Close rel, but keep exclusive lock!
868 : : */
4083 tgl@sss.pgh.pa.us 869 : 7 : relation_close(targetrel, NoLock);
870 : :
3330 alvherre@alvh.no-ip. 871 : 7 : return address;
872 : : }
|