On 1/21/19 3:31 AM, Andreas Karlsson wrote:
Here is a a stab at refactoring this so the object creation does not happen in a callback.
Rebased my patch on top of Andres's pluggable storage patches. Plus some minor style changes.
Andreas
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 2bc8f928ea..7ef3794e08 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -55,16 +55,15 @@ typedef struct { DestReceiver pub; /* publicly-known function pointers */ IntoClause *into; /* target relation specification */ + ObjectAddress reladdr; /* address of rel, for intorel_startup */ /* These fields are filled by intorel_startup: */ Relation rel; /* relation to write to */ - ObjectAddress reladdr; /* address of rel, for ExecCreateTableAs */ CommandId output_cid; /* cmin to insert in output tuples */ int hi_options; /* heap_insert performance options */ BulkInsertState bistate; /* bulk insert state */ } DR_intorel; -/* utility functions for CTAS definition creation */ -static ObjectAddress create_ctas_internal(List *attrList, IntoClause *into); +/* utility function for CTAS definition creation */ static ObjectAddress create_ctas_nodata(List *tlist, IntoClause *into); /* DestReceiver routines for collecting data */ @@ -75,16 +74,18 @@ static void intorel_destroy(DestReceiver *self); /* - * create_ctas_internal + * create_ctas_nodata * - * Internal utility used for the creation of the definition of a relation - * created via CREATE TABLE AS or a materialized view. Caller needs to - * provide a list of attributes (ColumnDef nodes). + * Create CTAS or materialized view without the data, starting from the + * targetlist of the SELECT or view definition. */ static ObjectAddress -create_ctas_internal(List *attrList, IntoClause *into) +create_ctas_nodata(List *tlist, IntoClause *into) { - CreateStmt *create = makeNode(CreateStmt); + List *attrList; + ListCell *t, + *lc; + CreateStmt *create; bool is_matview; char relkind; Datum toast_options; @@ -96,71 +97,6 @@ create_ctas_internal(List *attrList, IntoClause *into) relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; /* - * Create the target relation by faking up a CREATE TABLE parsetree and - * passing it to DefineRelation. - */ - create->relation = into->rel; - create->tableElts = attrList; - create->inhRelations = NIL; - create->ofTypename = NULL; - create->constraints = NIL; - create->options = into->options; - create->oncommit = into->onCommit; - create->tablespacename = into->tableSpaceName; - create->if_not_exists = false; - - /* - * Create the relation. (This will error out if there's an existing view, - * so we don't need more code to complain if "replace" is false.) - */ - intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL); - - /* - * If necessary, create a TOAST table for the target table. Note that - * NewRelationCreateToastTable ends with CommandCounterIncrement(), so - * that the TOAST table will be visible for insertion. - */ - CommandCounterIncrement(); - - /* parse and validate reloptions for the toast table */ - toast_options = transformRelOptions((Datum) 0, - create->options, - "toast", - validnsps, - true, false); - - (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); - - NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); - - /* Create the "view" part of a materialized view. */ - if (is_matview) - { - /* StoreViewQuery scribbles on tree, so make a copy */ - Query *query = (Query *) copyObject(into->viewQuery); - - StoreViewQuery(intoRelationAddr.objectId, query, false); - CommandCounterIncrement(); - } - - return intoRelationAddr; -} - - -/* - * create_ctas_nodata - * - * Create CTAS or materialized view when WITH NO DATA is used, starting from - * the targetlist of the SELECT or view definition. - */ -static ObjectAddress -create_ctas_nodata(List *tlist, IntoClause *into) -{ - List *attrList; - ListCell *t, - *lc; - - /* * Build list of ColumnDefs from non-junk elements of the tlist. If a * column name list was specified in CREATE TABLE AS, override the column * names in the query. (Too few column names are OK, too many are not.) @@ -213,8 +149,56 @@ create_ctas_nodata(List *tlist, IntoClause *into) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("too many column names were specified"))); - /* Create the relation definition using the ColumnDef list */ - return create_ctas_internal(attrList, into); + /* + * Create the target relation by faking up a CREATE TABLE parsetree and + * passing it to DefineRelation. + */ + create = makeNode(CreateStmt); + create->relation = into->rel; + create->tableElts = attrList; + create->inhRelations = NIL; + create->ofTypename = NULL; + create->constraints = NIL; + create->options = into->options; + create->oncommit = into->onCommit; + create->tablespacename = into->tableSpaceName; + create->if_not_exists = false; + + /* + * Create the relation. (This will error out if there's an existing view, + * so we don't need more code to complain if "replace" is false.) + */ + intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL); + + /* + * If necessary, create a TOAST table for the target table. Note that + * NewRelationCreateToastTable ends with CommandCounterIncrement(), so + * that the TOAST table will be visible for insertion. + */ + CommandCounterIncrement(); + + /* parse and validate reloptions for the toast table */ + toast_options = transformRelOptions((Datum) 0, + create->options, + "toast", + validnsps, + true, false); + + (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true); + + NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options); + + /* Create the "view" part of a materialized view. */ + if (is_matview) + { + /* StoreViewQuery scribbles on tree, so make a copy */ + Query *query = (Query *) copyObject(into->viewQuery); + + StoreViewQuery(intoRelationAddr.objectId, query, false); + CommandCounterIncrement(); + } + + return intoRelationAddr; } @@ -257,7 +241,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, /* * Create the tuple receiver object and insert info it will need */ - dest = CreateIntoRelDestReceiver(into); + dest = CreateIntoRelDestReceiver(); /* * The contained Query could be a SELECT, or an EXECUTE utility command. @@ -278,33 +262,25 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, } Assert(query->commandType == CMD_SELECT); - /* - * For materialized views, lock down security-restricted operations and - * arrange to make GUC variable changes local to this command. This is - * not necessary for security, but this keeps the behavior similar to - * REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized - * view not possible to refresh. - */ - if (is_matview) - { - GetUserIdAndSecContext(&save_userid, &save_sec_context); - SetUserIdAndSecContext(save_userid, - save_sec_context | SECURITY_RESTRICTED_OPERATION); - save_nestlevel = NewGUCNestLevel(); - } + DefineIntoRelForDestReceiver(dest, query->targetList, into); - if (into->skipData) + if (!into->skipData) { /* - * If WITH NO DATA was specified, do not go through the rewriter, - * planner and executor. Just define the relation using a code path - * similar to CREATE VIEW. This avoids dump/restore problems stemming - * from running the planner before all dependencies are set up. + * For materialized views, lock down security-restricted operations and + * arrange to make GUC variable changes local to this command. This is + * not necessary for security, but this keeps the behavior similar to + * REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized + * view not possible to refresh. */ - address = create_ctas_nodata(query->targetList, into); - } - else - { + if (is_matview) + { + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(save_userid, + save_sec_context | SECURITY_RESTRICTED_OPERATION); + save_nestlevel = NewGUCNestLevel(); + } + /* * Parse analysis was done already, but we still have to run the rule * rewriter. We do not do AcquireRewriteLocks: we assume the query @@ -367,18 +343,18 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, FreeQueryDesc(queryDesc); PopActiveSnapshot(); - } - if (is_matview) - { - /* Roll back any GUC changes */ - AtEOXact_GUC(false, save_nestlevel); + if (is_matview) + { + /* Roll back any GUC changes */ + AtEOXact_GUC(false, save_nestlevel); - /* Restore userid and security context */ - SetUserIdAndSecContext(save_userid, save_sec_context); + /* Restore userid and security context */ + SetUserIdAndSecContext(save_userid, save_sec_context); + } } - return address; + return ((DR_intorel *) dest)->reladdr; } /* @@ -403,12 +379,11 @@ GetIntoRelEFlags(IntoClause *intoClause) /* * CreateIntoRelDestReceiver -- create a suitable DestReceiver object * - * intoClause will be NULL if called from CreateDestReceiver(), in which - * case it has to be provided later. However, it is convenient to allow - * self->into to be filled in immediately for other callers. + * The private fields are initialized later when CreateIntoRelDestReceiver is + * called to create the receiving relation. */ DestReceiver * -CreateIntoRelDestReceiver(IntoClause *intoClause) +CreateIntoRelDestReceiver(void) { DR_intorel *self = (DR_intorel *) palloc0(sizeof(DR_intorel)); @@ -417,13 +392,25 @@ CreateIntoRelDestReceiver(IntoClause *intoClause) self->pub.rShutdown = intorel_shutdown; self->pub.rDestroy = intorel_destroy; self->pub.mydest = DestIntoRel; - self->into = intoClause; - /* other private fields will be set during intorel_startup */ + /* private fields will be set by DefineIntoRelForDestReceiver */ return (DestReceiver *) self; } /* + * DefineIntoRelForDestReceiver -- create the receveiving relation + */ +void +DefineIntoRelForDestReceiver(DestReceiver *dest, List *targetList, IntoClause *intoClause) +{ + DR_intorel *self = (DR_intorel *) dest; + + self->into = intoClause; + self->reladdr = create_ctas_nodata(targetList, intoClause); + /* other private fields will be set during intorel_startup */ +} + +/* * intorel_startup --- executor startup */ static void @@ -431,77 +418,21 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) { DR_intorel *myState = (DR_intorel *) self; IntoClause *into = myState->into; + ObjectAddress intoRelationAddr = myState->reladdr; bool is_matview; char relkind; - List *attrList; - ObjectAddress intoRelationAddr; Relation intoRelationDesc; RangeTblEntry *rte; - ListCell *lc; int attnum; - Assert(into != NULL); /* else somebody forgot to set it */ + Assert(into != NULL); /* else somebody forgot to set it */ + Assert(intoRelationAddr.classId != InvalidOid); /* else somebody forgot to set it */ /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */ is_matview = (into->viewQuery != NULL); relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION; /* - * Build column definitions using "pre-cooked" type and collation info. If - * a column name list was specified in CREATE TABLE AS, override the - * column names derived from the query. (Too few column names are OK, too - * many are not.) - */ - attrList = NIL; - lc = list_head(into->colNames); - for (attnum = 0; attnum < typeinfo->natts; attnum++) - { - Form_pg_attribute attribute = TupleDescAttr(typeinfo, attnum); - ColumnDef *col; - char *colname; - - if (lc) - { - colname = strVal(lfirst(lc)); - lc = lnext(lc); - } - else - colname = NameStr(attribute->attname); - - col = makeColumnDef(colname, - attribute->atttypid, - attribute->atttypmod, - attribute->attcollation); - - /* - * It's possible that the column is of a collatable type but the - * collation could not be resolved, so double-check. (We must check - * this here because DefineRelation would adopt the type's default - * collation rather than complaining.) - */ - if (!OidIsValid(col->collOid) && - type_is_collatable(col->typeName->typeOid)) - ereport(ERROR, - (errcode(ERRCODE_INDETERMINATE_COLLATION), - errmsg("no collation was derived for column \"%s\" with collatable type %s", - col->colname, - format_type_be(col->typeName->typeOid)), - errhint("Use the COLLATE clause to set the collation explicitly."))); - - attrList = lappend(attrList, col); - } - - if (lc != NULL) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("too many column names were specified"))); - - /* - * Actually create the target table - */ - intoRelationAddr = create_ctas_internal(attrList, into); - - /* * Finally we can open the target table */ intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock); @@ -549,7 +480,6 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) * Fill private fields of myState for use by later routines */ myState->rel = intoRelationDesc; - myState->reladdr = intoRelationAddr; myState->output_cid = GetCurrentCommandId(true); /* diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index ae7f038203..7f443b9423 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -496,11 +496,14 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, UpdateActiveSnapshotCommandId(); /* - * Normally we discard the query's output, but if explaining CREATE TABLE + * Normally we discard the query's output, but if executing CREATE TABLE * AS, we'd better use the appropriate tuple receiver. */ - if (into) - dest = CreateIntoRelDestReceiver(into); + if (into && es->analyze) + { + dest = CreateIntoRelDestReceiver(); + DefineIntoRelForDestReceiver(dest, plannedstmt->planTree->targetlist, into); + } else dest = None_Receiver; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index a98c8362d7..2fd3f6bf60 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -273,6 +273,8 @@ ExecuteQuery(ExecuteStmt *stmt, IntoClause *intoClause, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("prepared statement is not a SELECT"))); + DefineIntoRelForDestReceiver(dest, pstmt->planTree->targetlist, intoClause); + /* Set appropriate eflags */ eflags = GetIntoRelEFlags(intoClause); diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index ee9e349a5b..ff278c2b81 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -139,7 +139,7 @@ CreateDestReceiver(CommandDest dest) return CreateTuplestoreDestReceiver(); case DestIntoRel: - return CreateIntoRelDestReceiver(NULL); + return CreateIntoRelDestReceiver(); case DestCopyOut: return CreateCopyDestReceiver(); diff --git a/src/include/commands/createas.h b/src/include/commands/createas.h index 1f02b149fb..09cbd00255 100644 --- a/src/include/commands/createas.h +++ b/src/include/commands/createas.h @@ -26,6 +26,8 @@ extern ObjectAddress ExecCreateTableAs(CreateTableAsStmt *stmt, const char *quer extern int GetIntoRelEFlags(IntoClause *intoClause); -extern DestReceiver *CreateIntoRelDestReceiver(IntoClause *intoClause); +extern DestReceiver *CreateIntoRelDestReceiver(void); + +extern void DefineIntoRelForDestReceiver(DestReceiver *dest, List *targetList, IntoClause *intoClause); #endif /* CREATEAS_H */ diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out index 46deb55c67..6e12ae641b 100644 --- a/src/test/regress/expected/create_index.out +++ b/src/test/regress/expected/create_index.out @@ -3266,6 +3266,7 @@ SELECT b.relname, matview_col1_idx | i | relfilenode has changed pg_toast_TABLE | t | relfilenode is unchanged pg_toast_TABLE_index | i | relfilenode has changed + reindex_before | r | relfilenode is unchanged table1 | r | relfilenode is unchanged table1_col1_seq | S | relfilenode is unchanged table1_pkey | i | relfilenode has changed @@ -3274,7 +3275,7 @@ SELECT b.relname, table2_col2_idx | i | relfilenode has changed table2_pkey | i | relfilenode has changed view | v | relfilenode is unchanged -(12 rows) +(13 rows) REINDEX SCHEMA schema_to_reindex; BEGIN;