Coverage Report

Created: 2026-09-28 06:55

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