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