/src/postgres/src/backend/commands/view.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * view.c |
4 | | * use rewrite rules to construct views |
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/commands/view.c |
12 | | * |
13 | | *------------------------------------------------------------------------- |
14 | | */ |
15 | | #include "postgres.h" |
16 | | |
17 | | #include "access/relation.h" |
18 | | #include "access/xact.h" |
19 | | #include "catalog/namespace.h" |
20 | | #include "commands/tablecmds.h" |
21 | | #include "commands/view.h" |
22 | | #include "nodes/makefuncs.h" |
23 | | #include "nodes/nodeFuncs.h" |
24 | | #include "parser/analyze.h" |
25 | | #include "parser/parse_relation.h" |
26 | | #include "rewrite/rewriteDefine.h" |
27 | | #include "rewrite/rewriteHandler.h" |
28 | | #include "rewrite/rewriteSupport.h" |
29 | | #include "utils/builtins.h" |
30 | | #include "utils/lsyscache.h" |
31 | | #include "utils/rel.h" |
32 | | |
33 | | static void checkViewColumns(TupleDesc newdesc, TupleDesc olddesc); |
34 | | |
35 | | /*--------------------------------------------------------------------- |
36 | | * DefineVirtualRelation |
37 | | * |
38 | | * Create a view relation and use the rules system to store the query |
39 | | * for the view. |
40 | | * |
41 | | * EventTriggerAlterTableStart must have been called already. |
42 | | *--------------------------------------------------------------------- |
43 | | */ |
44 | | static ObjectAddress |
45 | | DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace, |
46 | | List *options, Query *viewParse) |
47 | 0 | { |
48 | 0 | Oid viewOid; |
49 | 0 | LOCKMODE lockmode; |
50 | 0 | List *attrList; |
51 | 0 | ListCell *t; |
52 | | |
53 | | /* |
54 | | * create a list of ColumnDef nodes based on the names and types of the |
55 | | * (non-junk) targetlist items from the view's SELECT list. |
56 | | */ |
57 | 0 | attrList = NIL; |
58 | 0 | foreach(t, tlist) |
59 | 0 | { |
60 | 0 | TargetEntry *tle = (TargetEntry *) lfirst(t); |
61 | |
|
62 | 0 | if (!tle->resjunk) |
63 | 0 | { |
64 | 0 | ColumnDef *def = makeColumnDef(tle->resname, |
65 | 0 | exprType((Node *) tle->expr), |
66 | 0 | exprTypmod((Node *) tle->expr), |
67 | 0 | exprCollation((Node *) tle->expr)); |
68 | | |
69 | | /* |
70 | | * It's possible that the column is of a collatable type but the |
71 | | * collation could not be resolved, so double-check. |
72 | | */ |
73 | 0 | if (type_is_collatable(exprType((Node *) tle->expr))) |
74 | 0 | { |
75 | 0 | if (!OidIsValid(def->collOid)) |
76 | 0 | ereport(ERROR, |
77 | 0 | (errcode(ERRCODE_INDETERMINATE_COLLATION), |
78 | 0 | errmsg("could not determine which collation to use for view column \"%s\"", |
79 | 0 | def->colname), |
80 | 0 | errhint("Use the COLLATE clause to set the collation explicitly."))); |
81 | 0 | } |
82 | 0 | else |
83 | 0 | Assert(!OidIsValid(def->collOid)); |
84 | | |
85 | 0 | attrList = lappend(attrList, def); |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | /* |
90 | | * Look up, check permissions on, and lock the creation namespace; also |
91 | | * check for a preexisting view with the same name. This will also set |
92 | | * relation->relpersistence to RELPERSISTENCE_TEMP if the selected |
93 | | * namespace is temporary. |
94 | | */ |
95 | 0 | lockmode = replace ? AccessExclusiveLock : NoLock; |
96 | 0 | (void) RangeVarGetAndCheckCreationNamespace(relation, lockmode, &viewOid); |
97 | |
|
98 | 0 | if (OidIsValid(viewOid) && replace) |
99 | 0 | { |
100 | 0 | Relation rel; |
101 | 0 | TupleDesc descriptor; |
102 | 0 | List *atcmds = NIL; |
103 | 0 | AlterTableCmd *atcmd; |
104 | 0 | ObjectAddress address; |
105 | | |
106 | | /* Relation is already locked, but we must build a relcache entry. */ |
107 | 0 | rel = relation_open(viewOid, NoLock); |
108 | | |
109 | | /* Make sure it *is* a view. */ |
110 | 0 | if (rel->rd_rel->relkind != RELKIND_VIEW) |
111 | 0 | ereport(ERROR, |
112 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
113 | 0 | errmsg("\"%s\" is not a view", |
114 | 0 | RelationGetRelationName(rel)))); |
115 | | |
116 | | /* Also check it's not in use already */ |
117 | 0 | CheckTableNotInUse(rel, "CREATE OR REPLACE VIEW"); |
118 | | |
119 | | /* |
120 | | * Due to the namespace visibility rules for temporary objects, we |
121 | | * should only end up replacing a temporary view with another |
122 | | * temporary view, and similarly for permanent views. |
123 | | */ |
124 | 0 | Assert(relation->relpersistence == rel->rd_rel->relpersistence); |
125 | | |
126 | | /* |
127 | | * Create a tuple descriptor to compare against the existing view, and |
128 | | * verify that the old column list is an initial prefix of the new |
129 | | * column list. |
130 | | */ |
131 | 0 | descriptor = BuildDescForRelation(attrList); |
132 | 0 | checkViewColumns(descriptor, rel->rd_att); |
133 | | |
134 | | /* |
135 | | * If new attributes have been added, we must add pg_attribute entries |
136 | | * for them. It is convenient (although overkill) to use the ALTER |
137 | | * TABLE ADD COLUMN infrastructure for this. |
138 | | * |
139 | | * Note that we must do this before updating the query for the view, |
140 | | * since the rules system requires that the correct view columns be in |
141 | | * place when defining the new rules. |
142 | | * |
143 | | * Also note that ALTER TABLE doesn't run parse transformation on |
144 | | * AT_AddColumnToView commands. The ColumnDef we supply must be ready |
145 | | * to execute as-is. |
146 | | */ |
147 | 0 | if (list_length(attrList) > rel->rd_att->natts) |
148 | 0 | { |
149 | 0 | ListCell *c; |
150 | 0 | int skip = rel->rd_att->natts; |
151 | |
|
152 | 0 | foreach(c, attrList) |
153 | 0 | { |
154 | 0 | if (skip > 0) |
155 | 0 | { |
156 | 0 | skip--; |
157 | 0 | continue; |
158 | 0 | } |
159 | 0 | atcmd = makeNode(AlterTableCmd); |
160 | 0 | atcmd->subtype = AT_AddColumnToView; |
161 | 0 | atcmd->def = (Node *) lfirst(c); |
162 | 0 | atcmds = lappend(atcmds, atcmd); |
163 | 0 | } |
164 | | |
165 | | /* EventTriggerAlterTableStart called by ProcessUtilitySlow */ |
166 | 0 | AlterTableInternal(viewOid, atcmds, true); |
167 | | |
168 | | /* Make the new view columns visible */ |
169 | 0 | CommandCounterIncrement(); |
170 | 0 | } |
171 | | |
172 | | /* |
173 | | * Update the query for the view. |
174 | | * |
175 | | * Note that we must do this before updating the view options, because |
176 | | * the new options may not be compatible with the old view query (for |
177 | | * example if we attempt to add the WITH CHECK OPTION, we require that |
178 | | * the new view be automatically updatable, but the old view may not |
179 | | * have been). |
180 | | */ |
181 | 0 | StoreViewQuery(viewOid, viewParse, replace); |
182 | | |
183 | | /* Make the new view query visible */ |
184 | 0 | CommandCounterIncrement(); |
185 | | |
186 | | /* |
187 | | * Update the view's options. |
188 | | * |
189 | | * The new options list replaces the existing options list, even if |
190 | | * it's empty. |
191 | | */ |
192 | 0 | atcmd = makeNode(AlterTableCmd); |
193 | 0 | atcmd->subtype = AT_ReplaceRelOptions; |
194 | 0 | atcmd->def = (Node *) options; |
195 | 0 | atcmds = list_make1(atcmd); |
196 | | |
197 | | /* EventTriggerAlterTableStart called by ProcessUtilitySlow */ |
198 | 0 | AlterTableInternal(viewOid, atcmds, true); |
199 | | |
200 | | /* |
201 | | * There is very little to do here to update the view's dependencies. |
202 | | * Most view-level dependency relationships, such as those on the |
203 | | * owner, schema, and associated composite type, aren't changing. |
204 | | * Because we don't allow changing type or collation of an existing |
205 | | * view column, those dependencies of the existing columns don't |
206 | | * change either, while the AT_AddColumnToView machinery took care of |
207 | | * adding such dependencies for new view columns. The dependencies of |
208 | | * the view's query could have changed arbitrarily, but that was dealt |
209 | | * with inside StoreViewQuery. What remains is only to check that |
210 | | * view replacement is allowed when we're creating an extension. |
211 | | */ |
212 | 0 | ObjectAddressSet(address, RelationRelationId, viewOid); |
213 | |
|
214 | 0 | recordDependencyOnCurrentExtension(&address, true); |
215 | | |
216 | | /* |
217 | | * Seems okay, so return the OID of the pre-existing view. |
218 | | */ |
219 | 0 | relation_close(rel, NoLock); /* keep the lock! */ |
220 | |
|
221 | 0 | return address; |
222 | 0 | } |
223 | 0 | else |
224 | 0 | { |
225 | 0 | CreateStmt *createStmt = makeNode(CreateStmt); |
226 | 0 | ObjectAddress address; |
227 | | |
228 | | /* |
229 | | * Set the parameters for keys/inheritance etc. All of these are |
230 | | * uninteresting for views... |
231 | | */ |
232 | 0 | createStmt->relation = relation; |
233 | 0 | createStmt->tableElts = attrList; |
234 | 0 | createStmt->inhRelations = NIL; |
235 | 0 | createStmt->constraints = NIL; |
236 | 0 | createStmt->options = options; |
237 | 0 | createStmt->oncommit = ONCOMMIT_NOOP; |
238 | 0 | createStmt->tablespacename = NULL; |
239 | 0 | createStmt->if_not_exists = false; |
240 | | |
241 | | /* |
242 | | * Create the relation (this will error out if there's an existing |
243 | | * view, so we don't need more code to complain if "replace" is |
244 | | * false). |
245 | | */ |
246 | 0 | address = DefineRelation(createStmt, RELKIND_VIEW, InvalidOid, NULL, |
247 | 0 | NULL); |
248 | 0 | Assert(address.objectId != InvalidOid); |
249 | | |
250 | | /* Make the new view relation visible */ |
251 | 0 | CommandCounterIncrement(); |
252 | | |
253 | | /* Store the query for the view */ |
254 | 0 | StoreViewQuery(address.objectId, viewParse, replace); |
255 | |
|
256 | 0 | return address; |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | | /* |
261 | | * Verify that the columns associated with proposed new view definition match |
262 | | * the columns of the old view. This is similar to equalRowTypes(), with code |
263 | | * added to generate specific complaints. Also, we allow the new view to have |
264 | | * more columns than the old. |
265 | | */ |
266 | | static void |
267 | | checkViewColumns(TupleDesc newdesc, TupleDesc olddesc) |
268 | 0 | { |
269 | 0 | int i; |
270 | |
|
271 | 0 | if (newdesc->natts < olddesc->natts) |
272 | 0 | ereport(ERROR, |
273 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
274 | 0 | errmsg("cannot drop columns from view"))); |
275 | | |
276 | 0 | for (i = 0; i < olddesc->natts; i++) |
277 | 0 | { |
278 | 0 | Form_pg_attribute newattr = TupleDescAttr(newdesc, i); |
279 | 0 | Form_pg_attribute oldattr = TupleDescAttr(olddesc, i); |
280 | | |
281 | | /* XXX msg not right, but we don't support DROP COL on view anyway */ |
282 | 0 | if (newattr->attisdropped != oldattr->attisdropped) |
283 | 0 | ereport(ERROR, |
284 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
285 | 0 | errmsg("cannot drop columns from view"))); |
286 | | |
287 | 0 | if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0) |
288 | 0 | ereport(ERROR, |
289 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
290 | 0 | errmsg("cannot change name of view column \"%s\" to \"%s\"", |
291 | 0 | NameStr(oldattr->attname), |
292 | 0 | NameStr(newattr->attname)), |
293 | 0 | errhint("Use ALTER VIEW ... RENAME COLUMN ... to change name of view column instead."))); |
294 | | |
295 | | /* |
296 | | * We cannot allow type, typmod, or collation to change, since these |
297 | | * properties may be embedded in Vars of other views/rules referencing |
298 | | * this one. Other column attributes can be ignored. |
299 | | */ |
300 | 0 | if (newattr->atttypid != oldattr->atttypid || |
301 | 0 | newattr->atttypmod != oldattr->atttypmod) |
302 | 0 | ereport(ERROR, |
303 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
304 | 0 | errmsg("cannot change data type of view column \"%s\" from %s to %s", |
305 | 0 | NameStr(oldattr->attname), |
306 | 0 | format_type_with_typemod(oldattr->atttypid, |
307 | 0 | oldattr->atttypmod), |
308 | 0 | format_type_with_typemod(newattr->atttypid, |
309 | 0 | newattr->atttypmod)))); |
310 | | |
311 | | /* |
312 | | * At this point, attcollations should be both valid or both invalid, |
313 | | * so applying get_collation_name unconditionally should be fine. |
314 | | */ |
315 | 0 | if (newattr->attcollation != oldattr->attcollation) |
316 | 0 | ereport(ERROR, |
317 | 0 | (errcode(ERRCODE_INVALID_TABLE_DEFINITION), |
318 | 0 | errmsg("cannot change collation of view column \"%s\" from \"%s\" to \"%s\"", |
319 | 0 | NameStr(oldattr->attname), |
320 | 0 | get_collation_name(oldattr->attcollation), |
321 | 0 | get_collation_name(newattr->attcollation)))); |
322 | 0 | } |
323 | | |
324 | | /* |
325 | | * We ignore the constraint fields. The new view desc can't have any |
326 | | * constraints, and the only ones that could be on the old view are |
327 | | * defaults, which we are happy to leave in place. |
328 | | */ |
329 | 0 | } |
330 | | |
331 | | static void |
332 | | DefineViewRules(Oid viewOid, Query *viewParse, bool replace) |
333 | 0 | { |
334 | | /* |
335 | | * Set up the ON SELECT rule. Since the query has already been through |
336 | | * parse analysis, we use DefineQueryRewrite() directly. |
337 | | */ |
338 | 0 | DefineQueryRewrite(pstrdup(ViewSelectRuleName), |
339 | 0 | viewOid, |
340 | 0 | NULL, |
341 | 0 | CMD_SELECT, |
342 | 0 | true, |
343 | 0 | replace, |
344 | 0 | list_make1(viewParse)); |
345 | | |
346 | | /* |
347 | | * Someday: automatic ON INSERT, etc |
348 | | */ |
349 | 0 | } |
350 | | |
351 | | /* |
352 | | * DefineView |
353 | | * Execute a CREATE VIEW command. |
354 | | */ |
355 | | ObjectAddress |
356 | | DefineView(ViewStmt *stmt, const char *queryString, |
357 | | int stmt_location, int stmt_len) |
358 | 0 | { |
359 | 0 | RawStmt *rawstmt; |
360 | 0 | Query *viewParse; |
361 | 0 | RangeVar *view; |
362 | 0 | ListCell *cell; |
363 | 0 | bool check_option; |
364 | 0 | ObjectAddress address; |
365 | | |
366 | | /* |
367 | | * Run parse analysis to convert the raw parse tree to a Query. Note this |
368 | | * also acquires sufficient locks on the source table(s). |
369 | | */ |
370 | 0 | rawstmt = makeNode(RawStmt); |
371 | 0 | rawstmt->stmt = stmt->query; |
372 | 0 | rawstmt->stmt_location = stmt_location; |
373 | 0 | rawstmt->stmt_len = stmt_len; |
374 | |
|
375 | 0 | viewParse = parse_analyze_fixedparams(rawstmt, queryString, NULL, 0, NULL); |
376 | | |
377 | | /* |
378 | | * The grammar should ensure that the result is a single SELECT Query. |
379 | | * However, it doesn't forbid SELECT INTO, so we have to check for that. |
380 | | */ |
381 | 0 | if (!IsA(viewParse, Query)) |
382 | 0 | elog(ERROR, "unexpected parse analysis result"); |
383 | 0 | if (viewParse->utilityStmt != NULL && |
384 | 0 | IsA(viewParse->utilityStmt, CreateTableAsStmt)) |
385 | 0 | ereport(ERROR, |
386 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
387 | 0 | errmsg("views must not contain SELECT INTO"))); |
388 | 0 | if (viewParse->commandType != CMD_SELECT) |
389 | 0 | elog(ERROR, "unexpected parse analysis result"); |
390 | | |
391 | | /* |
392 | | * Check for unsupported cases. These tests are redundant with ones in |
393 | | * DefineQueryRewrite(), but that function will complain about a bogus ON |
394 | | * SELECT rule, and we'd rather the message complain about a view. |
395 | | */ |
396 | 0 | if (viewParse->hasModifyingCTE) |
397 | 0 | ereport(ERROR, |
398 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
399 | 0 | errmsg("views must not contain data-modifying statements in WITH"))); |
400 | | |
401 | | /* |
402 | | * If the user specified the WITH CHECK OPTION, add it to the list of |
403 | | * reloptions. |
404 | | */ |
405 | 0 | if (stmt->withCheckOption == LOCAL_CHECK_OPTION) |
406 | 0 | stmt->options = lappend(stmt->options, |
407 | 0 | makeDefElem("check_option", |
408 | 0 | (Node *) makeString("local"), -1)); |
409 | 0 | else if (stmt->withCheckOption == CASCADED_CHECK_OPTION) |
410 | 0 | stmt->options = lappend(stmt->options, |
411 | 0 | makeDefElem("check_option", |
412 | 0 | (Node *) makeString("cascaded"), -1)); |
413 | | |
414 | | /* |
415 | | * Check that the view is auto-updatable if WITH CHECK OPTION was |
416 | | * specified. |
417 | | */ |
418 | 0 | check_option = false; |
419 | |
|
420 | 0 | foreach(cell, stmt->options) |
421 | 0 | { |
422 | 0 | DefElem *defel = (DefElem *) lfirst(cell); |
423 | |
|
424 | 0 | if (strcmp(defel->defname, "check_option") == 0) |
425 | 0 | check_option = true; |
426 | 0 | } |
427 | | |
428 | | /* |
429 | | * If the check option is specified, look to see if the view is actually |
430 | | * auto-updatable or not. |
431 | | */ |
432 | 0 | if (check_option) |
433 | 0 | { |
434 | 0 | const char *view_updatable_error = |
435 | 0 | view_query_is_auto_updatable(viewParse, true); |
436 | |
|
437 | 0 | if (view_updatable_error) |
438 | 0 | ereport(ERROR, |
439 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
440 | 0 | errmsg("WITH CHECK OPTION is supported only on automatically updatable views"), |
441 | 0 | errhint("%s", _(view_updatable_error)))); |
442 | 0 | } |
443 | | |
444 | | /* |
445 | | * If a list of column names was given, run through and insert these into |
446 | | * the actual query tree. - thomas 2000-03-08 |
447 | | */ |
448 | 0 | if (stmt->aliases != NIL) |
449 | 0 | { |
450 | 0 | ListCell *alist_item = list_head(stmt->aliases); |
451 | 0 | ListCell *targetList; |
452 | |
|
453 | 0 | foreach(targetList, viewParse->targetList) |
454 | 0 | { |
455 | 0 | TargetEntry *te = lfirst_node(TargetEntry, targetList); |
456 | | |
457 | | /* junk columns don't get aliases */ |
458 | 0 | if (te->resjunk) |
459 | 0 | continue; |
460 | 0 | te->resname = pstrdup(strVal(lfirst(alist_item))); |
461 | 0 | alist_item = lnext(stmt->aliases, alist_item); |
462 | 0 | if (alist_item == NULL) |
463 | 0 | break; /* done assigning aliases */ |
464 | 0 | } |
465 | |
|
466 | 0 | if (alist_item != NULL) |
467 | 0 | ereport(ERROR, |
468 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
469 | 0 | errmsg("CREATE VIEW specifies more column " |
470 | 0 | "names than columns"))); |
471 | 0 | } |
472 | | |
473 | | /* Unlogged views are not sensible. */ |
474 | 0 | if (stmt->view->relpersistence == RELPERSISTENCE_UNLOGGED) |
475 | 0 | ereport(ERROR, |
476 | 0 | (errcode(ERRCODE_SYNTAX_ERROR), |
477 | 0 | errmsg("views cannot be unlogged because they do not have storage"))); |
478 | | |
479 | | /* |
480 | | * If the user didn't explicitly ask for a temporary view, check whether |
481 | | * we need one implicitly. We allow TEMP to be inserted automatically as |
482 | | * long as the CREATE command is consistent with that --- no explicit |
483 | | * schema name. |
484 | | */ |
485 | 0 | view = copyObject(stmt->view); /* don't corrupt original command */ |
486 | 0 | if (view->relpersistence == RELPERSISTENCE_PERMANENT |
487 | 0 | && isQueryUsingTempRelation(viewParse)) |
488 | 0 | { |
489 | 0 | view->relpersistence = RELPERSISTENCE_TEMP; |
490 | 0 | ereport(NOTICE, |
491 | 0 | (errmsg("view \"%s\" will be a temporary view", |
492 | 0 | view->relname))); |
493 | 0 | } |
494 | | |
495 | | /* |
496 | | * Create the view relation |
497 | | * |
498 | | * NOTE: if it already exists and replace is false, the xact will be |
499 | | * aborted. |
500 | | */ |
501 | 0 | address = DefineVirtualRelation(view, viewParse->targetList, |
502 | 0 | stmt->replace, stmt->options, viewParse); |
503 | |
|
504 | 0 | return address; |
505 | 0 | } |
506 | | |
507 | | /* |
508 | | * Use the rules system to store the query for the view. |
509 | | */ |
510 | | void |
511 | | StoreViewQuery(Oid viewOid, Query *viewParse, bool replace) |
512 | 0 | { |
513 | | /* |
514 | | * Now create the rules associated with the view. |
515 | | */ |
516 | 0 | DefineViewRules(viewOid, viewParse, replace); |
517 | 0 | } |