Coverage Report

Created: 2025-07-03 06:49

/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
}