Coverage Report

Created: 2025-06-13 06:06

/src/postgres/src/backend/commands/createas.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * createas.c
4
 *    Execution of CREATE TABLE ... AS, a/k/a SELECT INTO.
5
 *    Since CREATE MATERIALIZED VIEW shares syntax and most behaviors,
6
 *    we implement that here, too.
7
 *
8
 * We implement this by diverting the query's normal output to a
9
 * specialized DestReceiver type.
10
 *
11
 * Formerly, CTAS was implemented as a variant of SELECT, which led
12
 * to assorted legacy behaviors that we still try to preserve, notably that
13
 * we must return a tuples-processed count in the QueryCompletion.  (We no
14
 * longer do that for CTAS ... WITH NO DATA, however.)
15
 *
16
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
17
 * Portions Copyright (c) 1994, Regents of the University of California
18
 *
19
 *
20
 * IDENTIFICATION
21
 *    src/backend/commands/createas.c
22
 *
23
 *-------------------------------------------------------------------------
24
 */
25
#include "postgres.h"
26
27
#include "access/heapam.h"
28
#include "access/reloptions.h"
29
#include "access/tableam.h"
30
#include "access/xact.h"
31
#include "catalog/namespace.h"
32
#include "catalog/toasting.h"
33
#include "commands/createas.h"
34
#include "commands/matview.h"
35
#include "commands/prepare.h"
36
#include "commands/tablecmds.h"
37
#include "commands/view.h"
38
#include "executor/execdesc.h"
39
#include "executor/executor.h"
40
#include "nodes/makefuncs.h"
41
#include "nodes/nodeFuncs.h"
42
#include "nodes/queryjumble.h"
43
#include "parser/analyze.h"
44
#include "rewrite/rewriteHandler.h"
45
#include "tcop/tcopprot.h"
46
#include "utils/builtins.h"
47
#include "utils/lsyscache.h"
48
#include "utils/rls.h"
49
#include "utils/snapmgr.h"
50
51
typedef struct
52
{
53
  DestReceiver pub;     /* publicly-known function pointers */
54
  IntoClause *into;     /* target relation specification */
55
  /* These fields are filled by intorel_startup: */
56
  Relation  rel;      /* relation to write to */
57
  ObjectAddress reladdr;    /* address of rel, for ExecCreateTableAs */
58
  CommandId output_cid;   /* cmin to insert in output tuples */
59
  int     ti_options;   /* table_tuple_insert performance options */
60
  BulkInsertState bistate;  /* bulk insert state */
61
} DR_intorel;
62
63
/* utility functions for CTAS definition creation */
64
static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into);
65
static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into);
66
67
/* DestReceiver routines for collecting data */
68
static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
69
static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self);
70
static void intorel_shutdown(DestReceiver *self);
71
static void intorel_destroy(DestReceiver *self);
72
73
74
/*
75
 * create_ctas_internal
76
 *
77
 * Internal utility used for the creation of the definition of a relation
78
 * created via CREATE TABLE AS or a materialized view.  Caller needs to
79
 * provide a list of attributes (ColumnDef nodes).
80
 */
81
static ObjectAddress
82
create_ctas_internal(List *attrList, IntoClause *into)
83
0
{
84
0
  CreateStmt *create = makeNode(CreateStmt);
85
0
  bool    is_matview;
86
0
  char    relkind;
87
0
  Datum   toast_options;
88
0
  const char *const validnsps[] = HEAP_RELOPT_NAMESPACES;
89
0
  ObjectAddress intoRelationAddr;
90
91
  /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
92
0
  is_matview = (into->viewQuery != NULL);
93
0
  relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION;
94
95
  /*
96
   * Create the target relation by faking up a CREATE TABLE parsetree and
97
   * passing it to DefineRelation.
98
   */
99
0
  create->relation = into->rel;
100
0
  create->tableElts = attrList;
101
0
  create->inhRelations = NIL;
102
0
  create->ofTypename = NULL;
103
0
  create->constraints = NIL;
104
0
  create->options = into->options;
105
0
  create->oncommit = into->onCommit;
106
0
  create->tablespacename = into->tableSpaceName;
107
0
  create->if_not_exists = false;
108
0
  create->accessMethod = into->accessMethod;
109
110
  /*
111
   * Create the relation.  (This will error out if there's an existing view,
112
   * so we don't need more code to complain if "replace" is false.)
113
   */
114
0
  intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
115
116
  /*
117
   * If necessary, create a TOAST table for the target table.  Note that
118
   * NewRelationCreateToastTable ends with CommandCounterIncrement(), so
119
   * that the TOAST table will be visible for insertion.
120
   */
121
0
  CommandCounterIncrement();
122
123
  /* parse and validate reloptions for the toast table */
124
0
  toast_options = transformRelOptions((Datum) 0,
125
0
                    create->options,
126
0
                    "toast",
127
0
                    validnsps,
128
0
                    true, false);
129
130
0
  (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
131
132
0
  NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
133
134
  /* Create the "view" part of a materialized view. */
135
0
  if (is_matview)
136
0
  {
137
    /* StoreViewQuery scribbles on tree, so make a copy */
138
0
    Query    *query = copyObject(into->viewQuery);
139
140
0
    StoreViewQuery(intoRelationAddr.objectId, query, false);
141
0
    CommandCounterIncrement();
142
0
  }
143
144
0
  return intoRelationAddr;
145
0
}
146
147
148
/*
149
 * create_ctas_nodata
150
 *
151
 * Create CTAS or materialized view when WITH NO DATA is used, starting from
152
 * the targetlist of the SELECT or view definition.
153
 */
154
static ObjectAddress
155
create_ctas_nodata(List *tlist, IntoClause *into)
156
0
{
157
0
  List     *attrList;
158
0
  ListCell   *t,
159
0
         *lc;
160
161
  /*
162
   * Build list of ColumnDefs from non-junk elements of the tlist.  If a
163
   * column name list was specified in CREATE TABLE AS, override the column
164
   * names in the query.  (Too few column names are OK, too many are not.)
165
   */
166
0
  attrList = NIL;
167
0
  lc = list_head(into->colNames);
168
0
  foreach(t, tlist)
169
0
  {
170
0
    TargetEntry *tle = (TargetEntry *) lfirst(t);
171
172
0
    if (!tle->resjunk)
173
0
    {
174
0
      ColumnDef  *col;
175
0
      char     *colname;
176
177
0
      if (lc)
178
0
      {
179
0
        colname = strVal(lfirst(lc));
180
0
        lc = lnext(into->colNames, lc);
181
0
      }
182
0
      else
183
0
        colname = tle->resname;
184
185
0
      col = makeColumnDef(colname,
186
0
                exprType((Node *) tle->expr),
187
0
                exprTypmod((Node *) tle->expr),
188
0
                exprCollation((Node *) tle->expr));
189
190
      /*
191
       * It's possible that the column is of a collatable type but the
192
       * collation could not be resolved, so double-check.  (We must
193
       * check this here because DefineRelation would adopt the type's
194
       * default collation rather than complaining.)
195
       */
196
0
      if (!OidIsValid(col->collOid) &&
197
0
        type_is_collatable(col->typeName->typeOid))
198
0
        ereport(ERROR,
199
0
            (errcode(ERRCODE_INDETERMINATE_COLLATION),
200
0
             errmsg("no collation was derived for column \"%s\" with collatable type %s",
201
0
                col->colname,
202
0
                format_type_be(col->typeName->typeOid)),
203
0
             errhint("Use the COLLATE clause to set the collation explicitly.")));
204
205
0
      attrList = lappend(attrList, col);
206
0
    }
207
0
  }
208
209
0
  if (lc != NULL)
210
0
    ereport(ERROR,
211
0
        (errcode(ERRCODE_SYNTAX_ERROR),
212
0
         errmsg("too many column names were specified")));
213
214
  /* Create the relation definition using the ColumnDef list */
215
0
  return create_ctas_internal(attrList, into);
216
0
}
217
218
219
/*
220
 * ExecCreateTableAs -- execute a CREATE TABLE AS command
221
 */
222
ObjectAddress
223
ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
224
          ParamListInfo params, QueryEnvironment *queryEnv,
225
          QueryCompletion *qc)
226
0
{
227
0
  Query    *query = castNode(Query, stmt->query);
228
0
  IntoClause *into = stmt->into;
229
0
  JumbleState *jstate = NULL;
230
0
  bool    is_matview = (into->viewQuery != NULL);
231
0
  bool    do_refresh = false;
232
0
  DestReceiver *dest;
233
0
  ObjectAddress address;
234
235
  /* Check if the relation exists or not */
236
0
  if (CreateTableAsRelExists(stmt))
237
0
    return InvalidObjectAddress;
238
239
  /*
240
   * Create the tuple receiver object and insert info it will need
241
   */
242
0
  dest = CreateIntoRelDestReceiver(into);
243
244
  /* Query contained by CTAS needs to be jumbled if requested */
245
0
  if (IsQueryIdEnabled())
246
0
    jstate = JumbleQuery(query);
247
248
0
  if (post_parse_analyze_hook)
249
0
    (*post_parse_analyze_hook) (pstate, query, jstate);
250
251
  /*
252
   * The contained Query could be a SELECT, or an EXECUTE utility command.
253
   * If the latter, we just pass it off to ExecuteQuery.
254
   */
255
0
  if (query->commandType == CMD_UTILITY &&
256
0
    IsA(query->utilityStmt, ExecuteStmt))
257
0
  {
258
0
    ExecuteStmt *estmt = castNode(ExecuteStmt, query->utilityStmt);
259
260
0
    Assert(!is_matview);  /* excluded by syntax */
261
0
    ExecuteQuery(pstate, estmt, into, params, dest, qc);
262
263
    /* get object address that intorel_startup saved for us */
264
0
    address = ((DR_intorel *) dest)->reladdr;
265
266
0
    return address;
267
0
  }
268
0
  Assert(query->commandType == CMD_SELECT);
269
270
  /*
271
   * For materialized views, always skip data during table creation, and use
272
   * REFRESH instead (see below).
273
   */
274
0
  if (is_matview)
275
0
  {
276
0
    do_refresh = !into->skipData;
277
0
    into->skipData = true;
278
0
  }
279
280
0
  if (into->skipData)
281
0
  {
282
    /*
283
     * If WITH NO DATA was specified, do not go through the rewriter,
284
     * planner and executor.  Just define the relation using a code path
285
     * similar to CREATE VIEW.  This avoids dump/restore problems stemming
286
     * from running the planner before all dependencies are set up.
287
     */
288
0
    address = create_ctas_nodata(query->targetList, into);
289
290
    /*
291
     * For materialized views, reuse the REFRESH logic, which locks down
292
     * security-restricted operations and restricts the search_path.  This
293
     * reduces the chance that a subsequent refresh will fail.
294
     */
295
0
    if (do_refresh)
296
0
      RefreshMatViewByOid(address.objectId, true, false, false,
297
0
                pstate->p_sourcetext, qc);
298
299
0
  }
300
0
  else
301
0
  {
302
0
    List     *rewritten;
303
0
    PlannedStmt *plan;
304
0
    QueryDesc  *queryDesc;
305
306
0
    Assert(!is_matview);
307
308
    /*
309
     * Parse analysis was done already, but we still have to run the rule
310
     * rewriter.  We do not do AcquireRewriteLocks: we assume the query
311
     * either came straight from the parser, or suitable locks were
312
     * acquired by plancache.c.
313
     */
314
0
    rewritten = QueryRewrite(query);
315
316
    /* SELECT should never rewrite to more or less than one SELECT query */
317
0
    if (list_length(rewritten) != 1)
318
0
      elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT");
319
0
    query = linitial_node(Query, rewritten);
320
0
    Assert(query->commandType == CMD_SELECT);
321
322
    /* plan the query */
323
0
    plan = pg_plan_query(query, pstate->p_sourcetext,
324
0
               CURSOR_OPT_PARALLEL_OK, params);
325
326
    /*
327
     * Use a snapshot with an updated command ID to ensure this query sees
328
     * results of any previously executed queries.  (This could only
329
     * matter if the planner executed an allegedly-stable function that
330
     * changed the database contents, but let's do it anyway to be
331
     * parallel to the EXPLAIN code path.)
332
     */
333
0
    PushCopiedSnapshot(GetActiveSnapshot());
334
0
    UpdateActiveSnapshotCommandId();
335
336
    /* Create a QueryDesc, redirecting output to our tuple receiver */
337
0
    queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
338
0
                  GetActiveSnapshot(), InvalidSnapshot,
339
0
                  dest, params, queryEnv, 0);
340
341
    /* call ExecutorStart to prepare the plan for execution */
342
0
    ExecutorStart(queryDesc, GetIntoRelEFlags(into));
343
344
    /* run the plan to completion */
345
0
    ExecutorRun(queryDesc, ForwardScanDirection, 0);
346
347
    /* save the rowcount if we're given a qc to fill */
348
0
    if (qc)
349
0
      SetQueryCompletion(qc, CMDTAG_SELECT, queryDesc->estate->es_processed);
350
351
    /* get object address that intorel_startup saved for us */
352
0
    address = ((DR_intorel *) dest)->reladdr;
353
354
    /* and clean up */
355
0
    ExecutorFinish(queryDesc);
356
0
    ExecutorEnd(queryDesc);
357
358
0
    FreeQueryDesc(queryDesc);
359
360
0
    PopActiveSnapshot();
361
0
  }
362
363
0
  return address;
364
0
}
365
366
/*
367
 * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
368
 *
369
 * This is exported because EXPLAIN and PREPARE need it too.  (Note: those
370
 * callers still need to deal explicitly with the skipData flag; since they
371
 * use different methods for suppressing execution, it doesn't seem worth
372
 * trying to encapsulate that part.)
373
 */
374
int
375
GetIntoRelEFlags(IntoClause *intoClause)
376
0
{
377
0
  int     flags = 0;
378
379
0
  if (intoClause->skipData)
380
0
    flags |= EXEC_FLAG_WITH_NO_DATA;
381
382
0
  return flags;
383
0
}
384
385
/*
386
 * CreateTableAsRelExists --- check existence of relation for CreateTableAsStmt
387
 *
388
 * Utility wrapper checking if the relation pending for creation in this
389
 * CreateTableAsStmt query already exists or not.  Returns true if the
390
 * relation exists, otherwise false.
391
 */
392
bool
393
CreateTableAsRelExists(CreateTableAsStmt *ctas)
394
0
{
395
0
  Oid     nspid;
396
0
  Oid     oldrelid;
397
0
  ObjectAddress address;
398
0
  IntoClause *into = ctas->into;
399
400
0
  nspid = RangeVarGetCreationNamespace(into->rel);
401
402
0
  oldrelid = get_relname_relid(into->rel->relname, nspid);
403
0
  if (OidIsValid(oldrelid))
404
0
  {
405
0
    if (!ctas->if_not_exists)
406
0
      ereport(ERROR,
407
0
          (errcode(ERRCODE_DUPLICATE_TABLE),
408
0
           errmsg("relation \"%s\" already exists",
409
0
              into->rel->relname)));
410
411
    /*
412
     * The relation exists and IF NOT EXISTS has been specified.
413
     *
414
     * If we are in an extension script, insist that the pre-existing
415
     * object be a member of the extension, to avoid security risks.
416
     */
417
0
    ObjectAddressSet(address, RelationRelationId, oldrelid);
418
0
    checkMembershipInCurrentExtension(&address);
419
420
    /* OK to skip */
421
0
    ereport(NOTICE,
422
0
        (errcode(ERRCODE_DUPLICATE_TABLE),
423
0
         errmsg("relation \"%s\" already exists, skipping",
424
0
            into->rel->relname)));
425
0
    return true;
426
0
  }
427
428
  /* Relation does not exist, it can be created */
429
0
  return false;
430
0
}
431
432
/*
433
 * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
434
 *
435
 * intoClause will be NULL if called from CreateDestReceiver(), in which
436
 * case it has to be provided later.  However, it is convenient to allow
437
 * self->into to be filled in immediately for other callers.
438
 */
439
DestReceiver *
440
CreateIntoRelDestReceiver(IntoClause *intoClause)
441
0
{
442
0
  DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel));
443
444
0
  self->pub.receiveSlot = intorel_receive;
445
0
  self->pub.rStartup = intorel_startup;
446
0
  self->pub.rShutdown = intorel_shutdown;
447
0
  self->pub.rDestroy = intorel_destroy;
448
0
  self->pub.mydest = DestIntoRel;
449
0
  self->into = intoClause;
450
  /* other private fields will be set during intorel_startup */
451
452
0
  return (DestReceiver *) self;
453
0
}
454
455
/*
456
 * intorel_startup --- executor startup
457
 */
458
static void
459
intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
460
0
{
461
0
  DR_intorel *myState = (DR_intorel *) self;
462
0
  IntoClause *into = myState->into;
463
0
  bool    is_matview;
464
0
  List     *attrList;
465
0
  ObjectAddress intoRelationAddr;
466
0
  Relation  intoRelationDesc;
467
0
  ListCell   *lc;
468
0
  int     attnum;
469
470
0
  Assert(into != NULL);   /* else somebody forgot to set it */
471
472
  /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
473
0
  is_matview = (into->viewQuery != NULL);
474
475
  /*
476
   * Build column definitions using "pre-cooked" type and collation info. If
477
   * a column name list was specified in CREATE TABLE AS, override the
478
   * column names derived from the query.  (Too few column names are OK, too
479
   * many are not.)
480
   */
481
0
  attrList = NIL;
482
0
  lc = list_head(into->colNames);
483
0
  for (attnum = 0; attnum < typeinfo->natts; attnum++)
484
0
  {
485
0
    Form_pg_attribute attribute = TupleDescAttr(typeinfo, attnum);
486
0
    ColumnDef  *col;
487
0
    char     *colname;
488
489
0
    if (lc)
490
0
    {
491
0
      colname = strVal(lfirst(lc));
492
0
      lc = lnext(into->colNames, lc);
493
0
    }
494
0
    else
495
0
      colname = NameStr(attribute->attname);
496
497
0
    col = makeColumnDef(colname,
498
0
              attribute->atttypid,
499
0
              attribute->atttypmod,
500
0
              attribute->attcollation);
501
502
    /*
503
     * It's possible that the column is of a collatable type but the
504
     * collation could not be resolved, so double-check.  (We must check
505
     * this here because DefineRelation would adopt the type's default
506
     * collation rather than complaining.)
507
     */
508
0
    if (!OidIsValid(col->collOid) &&
509
0
      type_is_collatable(col->typeName->typeOid))
510
0
      ereport(ERROR,
511
0
          (errcode(ERRCODE_INDETERMINATE_COLLATION),
512
0
           errmsg("no collation was derived for column \"%s\" with collatable type %s",
513
0
              col->colname,
514
0
              format_type_be(col->typeName->typeOid)),
515
0
           errhint("Use the COLLATE clause to set the collation explicitly.")));
516
517
0
    attrList = lappend(attrList, col);
518
0
  }
519
520
0
  if (lc != NULL)
521
0
    ereport(ERROR,
522
0
        (errcode(ERRCODE_SYNTAX_ERROR),
523
0
         errmsg("too many column names were specified")));
524
525
  /*
526
   * Actually create the target table
527
   */
528
0
  intoRelationAddr = create_ctas_internal(attrList, into);
529
530
  /*
531
   * Finally we can open the target table
532
   */
533
0
  intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock);
534
535
  /*
536
   * Make sure the constructed table does not have RLS enabled.
537
   *
538
   * check_enable_rls() will ereport(ERROR) itself if the user has requested
539
   * something invalid, and otherwise will return RLS_ENABLED if RLS should
540
   * be enabled here.  We don't actually support that currently, so throw
541
   * our own ereport(ERROR) if that happens.
542
   */
543
0
  if (check_enable_rls(intoRelationAddr.objectId, InvalidOid, false) == RLS_ENABLED)
544
0
    ereport(ERROR,
545
0
        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
546
0
         errmsg("policies not yet implemented for this command")));
547
548
  /*
549
   * Tentatively mark the target as populated, if it's a matview and we're
550
   * going to fill it; otherwise, no change needed.
551
   */
552
0
  if (is_matview && !into->skipData)
553
0
    SetMatViewPopulatedState(intoRelationDesc, true);
554
555
  /*
556
   * Fill private fields of myState for use by later routines
557
   */
558
0
  myState->rel = intoRelationDesc;
559
0
  myState->reladdr = intoRelationAddr;
560
0
  myState->output_cid = GetCurrentCommandId(true);
561
0
  myState->ti_options = TABLE_INSERT_SKIP_FSM;
562
563
  /*
564
   * If WITH NO DATA is specified, there is no need to set up the state for
565
   * bulk inserts as there are no tuples to insert.
566
   */
567
0
  if (!into->skipData)
568
0
    myState->bistate = GetBulkInsertState();
569
0
  else
570
0
    myState->bistate = NULL;
571
572
  /*
573
   * Valid smgr_targblock implies something already wrote to the relation.
574
   * This may be harmless, but this function hasn't planned for it.
575
   */
576
0
  Assert(RelationGetTargetBlock(intoRelationDesc) == InvalidBlockNumber);
577
0
}
578
579
/*
580
 * intorel_receive --- receive one tuple
581
 */
582
static bool
583
intorel_receive(TupleTableSlot *slot, DestReceiver *self)
584
0
{
585
0
  DR_intorel *myState = (DR_intorel *) self;
586
587
  /* Nothing to insert if WITH NO DATA is specified. */
588
0
  if (!myState->into->skipData)
589
0
  {
590
    /*
591
     * Note that the input slot might not be of the type of the target
592
     * relation. That's supported by table_tuple_insert(), but slightly
593
     * less efficient than inserting with the right slot - but the
594
     * alternative would be to copy into a slot of the right type, which
595
     * would not be cheap either. This also doesn't allow accessing per-AM
596
     * data (say a tuple's xmin), but since we don't do that here...
597
     */
598
0
    table_tuple_insert(myState->rel,
599
0
               slot,
600
0
               myState->output_cid,
601
0
               myState->ti_options,
602
0
               myState->bistate);
603
0
  }
604
605
  /* We know this is a newly created relation, so there are no indexes */
606
607
0
  return true;
608
0
}
609
610
/*
611
 * intorel_shutdown --- executor end
612
 */
613
static void
614
intorel_shutdown(DestReceiver *self)
615
0
{
616
0
  DR_intorel *myState = (DR_intorel *) self;
617
0
  IntoClause *into = myState->into;
618
619
0
  if (!into->skipData)
620
0
  {
621
0
    FreeBulkInsertState(myState->bistate);
622
0
    table_finish_bulk_insert(myState->rel, myState->ti_options);
623
0
  }
624
625
  /* close rel, but keep lock until commit */
626
0
  table_close(myState->rel, NoLock);
627
0
  myState->rel = NULL;
628
0
}
629
630
/*
631
 * intorel_destroy --- release DestReceiver object
632
 */
633
static void
634
intorel_destroy(DestReceiver *self)
635
0
{
636
0
  pfree(self);
637
0
}