1/*-------------------------------------------------------------------------
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.
8 * We implement this by diverting the query's normal output to a
9 * specialized DestReceiver type.
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.)
16 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
17 * Portions Copyright (c) 1994, Regents of the University of California
21 * src/backend/commands/createas.c
23 *-------------------------------------------------------------------------
55 /* These fields are filled by intorel_startup: */
59 int ti_options;
/* table_tuple_insert performance options */
63/* utility functions for CTAS definition creation */
67/* DestReceiver routines for collecting data */
75 * create_ctas_internal
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).
91 /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
92 is_matview = (into->viewQuery != NULL);
93 relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION;
96 * Create the target relation by faking up a CREATE TABLE parsetree and
97 * passing it to DefineRelation.
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.)
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.
123 /* parse and validate reloptions for the toast table */
134 /* Create the "view" part of a materialized view. */
137 /* StoreViewQuery scribbles on tree, so make a copy */
144 return intoRelationAddr;
151 * Create CTAS or materialized view when WITH NO DATA is used, starting from
152 * the targetlist of the SELECT or view definition.
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.)
183 colname = tle->resname;
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.)
199 (
errcode(ERRCODE_INDETERMINATE_COLLATION),
200 errmsg(
"no collation was derived for column \"%s\" with collatable type %s",
203 errhint(
"Use the COLLATE clause to set the collation explicitly.")));
205 attrList =
lappend(attrList, col);
211 (
errcode(ERRCODE_SYNTAX_ERROR),
212 errmsg(
"too many column names were specified")));
214 /* Create the relation definition using the ColumnDef list */
220 * ExecCreateTableAs -- execute a CREATE TABLE AS command
230 bool is_matview = (into->viewQuery != NULL);
231 bool do_refresh =
false;
235 /* Check if the relation exists or not */
240 * Create the tuple receiver object and insert info it will need
244 /* Query contained by CTAS needs to be jumbled if requested */
249 (*post_parse_analyze_hook) (pstate, query, jstate);
252 * The contained Query could be a SELECT, or an EXECUTE utility command.
253 * If the latter, we just pass it off to ExecuteQuery.
260 Assert(!is_matview);
/* excluded by syntax */
263 /* get object address that intorel_startup saved for us */
271 * For materialized views, always skip data during table creation, and use
272 * REFRESH instead (see below).
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.
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.
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.
316 /* SELECT should never rewrite to more or less than one SELECT query */
318 elog(
ERROR,
"unexpected rewrite result for CREATE TABLE AS SELECT");
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.)
336 /* Create a QueryDesc, redirecting output to our tuple receiver */
339 dest, params, queryEnv, 0);
341 /* call ExecutorStart to prepare the plan for execution */
344 /* run the plan to completion */
347 /* save the rowcount if we're given a qc to fill */
351 /* get object address that intorel_startup saved for us */
367 * GetIntoRelEFlags --- compute executor flags needed for CREATE TABLE AS
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.)
386 * CreateTableAsRelExists --- check existence of relation for CreateTableAsStmt
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.
407 (
errcode(ERRCODE_DUPLICATE_TABLE),
408 errmsg(
"relation \"%s\" already exists",
412 * The relation exists and IF NOT EXISTS has been specified.
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.
422 (
errcode(ERRCODE_DUPLICATE_TABLE),
423 errmsg(
"relation \"%s\" already exists, skipping",
428 /* Relation does not exist, it can be created */
433 * CreateIntoRelDestReceiver -- create a suitable DestReceiver object
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.
449 self->
into = intoClause;
450 /* other private fields will be set during intorel_startup */
456 * intorel_startup --- executor startup
470 Assert(into != NULL);
/* else somebody forgot to set it */
472 /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
473 is_matview = (into->viewQuery != NULL);
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
495 colname =
NameStr(attribute->attname);
499 attribute->atttypmod,
500 attribute->attcollation);
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.)
511 (
errcode(ERRCODE_INDETERMINATE_COLLATION),
512 errmsg(
"no collation was derived for column \"%s\" with collatable type %s",
515 errhint(
"Use the COLLATE clause to set the collation explicitly.")));
517 attrList =
lappend(attrList, col);
522 (
errcode(ERRCODE_SYNTAX_ERROR),
523 errmsg(
"too many column names were specified")));
526 * Actually create the target table
531 * Finally we can open the target table
536 * Make sure the constructed table does not have RLS enabled.
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.
545 (
errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
546 errmsg(
"policies not yet implemented for this command")));
549 * Tentatively mark the target as populated, if it's a matview and we're
550 * going to fill it; otherwise, no change needed.
556 * Fill private fields of myState for use by later routines
558 myState->
rel = intoRelationDesc;
559 myState->
reladdr = intoRelationAddr;
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.
573 * Valid smgr_targblock implies something already wrote to the relation.
574 * This may be harmless, but this function hasn't planned for it.
580 * intorel_receive --- receive one tuple
587 /* Nothing to insert if WITH NO DATA is specified. */
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...
605 /* We know this is a newly created relation, so there are no indexes */
611 * intorel_shutdown --- executor end
625 /* close rel, but keep lock until commit */
631 * intorel_destroy --- release DestReceiver object
void ExecuteQuery(ParseState *pstate, ExecuteStmt *stmt, IntoClause *intoClause, ParamListInfo params, DestReceiver *dest, QueryCompletion *qc)
#define InvalidBlockNumber
#define OidIsValid(objectId)
static void SetQueryCompletion(QueryCompletion *qc, CommandTag commandTag, uint64 nprocessed)
static void intorel_shutdown(DestReceiver *self)
static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into)
bool CreateTableAsRelExists(CreateTableAsStmt *ctas)
static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into)
int GetIntoRelEFlags(IntoClause *intoClause)
static bool intorel_receive(TupleTableSlot *slot, DestReceiver *self)
DestReceiver * CreateIntoRelDestReceiver(IntoClause *intoClause)
static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
ObjectAddress ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt, ParamListInfo params, QueryEnvironment *queryEnv, QueryCompletion *qc)
static void intorel_destroy(DestReceiver *self)
int errhint(const char *fmt,...)
int errcode(int sqlerrcode)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
void ExecutorEnd(QueryDesc *queryDesc)
void ExecutorFinish(QueryDesc *queryDesc)
void ExecutorStart(QueryDesc *queryDesc, int eflags)
void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count)
#define EXEC_FLAG_WITH_NO_DATA
Assert(PointerIsAligned(start, uint64))
BulkInsertState GetBulkInsertState(void)
void FreeBulkInsertState(BulkInsertState bistate)
List * lappend(List *list, void *datum)
#define AccessExclusiveLock
bool type_is_collatable(Oid typid)
Oid get_relname_relid(const char *relname, Oid relnamespace)
ColumnDef * makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
void SetMatViewPopulatedState(Relation relation, bool newstate)
ObjectAddress RefreshMatViewByOid(Oid matviewOid, bool is_create, bool skipData, bool concurrent, const char *queryString, QueryCompletion *qc)
void pfree(void *pointer)
void * palloc0(Size size)
Oid RangeVarGetCreationNamespace(const RangeVar *newRelation)
Oid exprType(const Node *expr)
int32 exprTypmod(const Node *expr)
Oid exprCollation(const Node *expr)
#define IsA(nodeptr, _type_)
#define castNode(_type_, nodeptr)
const ObjectAddress InvalidObjectAddress
#define ObjectAddressSet(addr, class_id, object_id)
#define CURSOR_OPT_PARALLEL_OK
post_parse_analyze_hook_type post_parse_analyze_hook
FormData_pg_attribute * Form_pg_attribute
void checkMembershipInCurrentExtension(const ObjectAddress *object)
static int list_length(const List *l)
#define linitial_node(type, l)
static ListCell * list_head(const List *l)
static ListCell * lnext(const List *l, const ListCell *c)
PlannedStmt * pg_plan_query(Query *querytree, const char *query_string, int cursorOptions, ParamListInfo boundParams)
void FreeQueryDesc(QueryDesc *qdesc)
QueryDesc * CreateQueryDesc(PlannedStmt *plannedstmt, const char *sourceText, Snapshot snapshot, Snapshot crosscheck_snapshot, DestReceiver *dest, ParamListInfo params, QueryEnvironment *queryEnv, int instrument_options)
static bool IsQueryIdEnabled(void)
JumbleState * JumbleQuery(Query *query)
#define RelationGetTargetBlock(relation)
Datum transformRelOptions(Datum oldOptions, List *defList, const char *nameSpace, const char *const validnsps[], bool acceptOidsOff, bool isReset)
bytea * heap_reloptions(char relkind, Datum reloptions, bool validate)
#define HEAP_RELOPT_NAMESPACES
List * QueryRewrite(Query *parsetree)
int check_enable_rls(Oid relid, Oid checkAsUser, bool noError)
void UpdateActiveSnapshotCommandId(void)
void PopActiveSnapshot(void)
void PushCopiedSnapshot(Snapshot snapshot)
Snapshot GetActiveSnapshot(void)
const char * p_sourcetext
void(* rStartup)(DestReceiver *self, int operation, TupleDesc typeinfo)
void(* rShutdown)(DestReceiver *self)
bool(* receiveSlot)(TupleTableSlot *slot, DestReceiver *self)
void(* rDestroy)(DestReceiver *self)
void table_close(Relation relation, LOCKMODE lockmode)
Relation table_open(Oid relationId, LOCKMODE lockmode)
#define TABLE_INSERT_SKIP_FSM
static void table_tuple_insert(Relation rel, TupleTableSlot *slot, CommandId cid, int options, BulkInsertStateData *bistate)
static void table_finish_bulk_insert(Relation rel, int options)
ObjectAddress DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, ObjectAddress *typaddress, const char *queryString)
void NewRelationCreateToastTable(Oid relOid, Datum reloptions)
static FormData_pg_attribute * TupleDescAttr(TupleDesc tupdesc, int i)
void StoreViewQuery(Oid viewOid, Query *viewParse, bool replace)
void CommandCounterIncrement(void)
CommandId GetCurrentCommandId(bool used)