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