diff options
Diffstat (limited to 'src/backend/commands/createas.c')
| -rw-r--r-- | src/backend/commands/createas.c | 137 |
1 files changed, 112 insertions, 25 deletions
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 66a49db330..a3ff1d56c8 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -2,6 +2,8 @@ * * createas.c * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO + * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors, + * implement that here, too. * * We implement this by diverting the query's normal output to a * specialized DestReceiver type. @@ -27,8 +29,11 @@ #include "access/xact.h" #include "catalog/toasting.h" #include "commands/createas.h" +#include "commands/matview.h" #include "commands/prepare.h" #include "commands/tablecmds.h" +#include "commands/view.h" +#include "parser/analyze.h" #include "parser/parse_clause.h" #include "rewrite/rewriteHandler.h" #include "storage/smgr.h" @@ -43,6 +48,7 @@ typedef struct { DestReceiver pub; /* publicly-known function pointers */ IntoClause *into; /* target relation specification */ + Query *viewParse; /* the query which defines/populates data */ /* These fields are filled by intorel_startup: */ Relation rel; /* relation to write to */ CommandId output_cid; /* cmin to insert in output tuples */ @@ -57,6 +63,62 @@ static void intorel_destroy(DestReceiver *self); /* + * Common setup needed by both normal execution and EXPLAIN ANALYZE. + */ +Query * +SetupForCreateTableAs(Query *query, IntoClause *into, const char *queryString, + ParamListInfo params, DestReceiver *dest) +{ + List *rewritten; + Query *viewParse = NULL; + + Assert(query->commandType == CMD_SELECT); + + if (into->relkind == RELKIND_MATVIEW) + viewParse = (Query *) parse_analyze((Node *) copyObject(query), + queryString, NULL, 0)->utilityStmt; + + /* + * Parse analysis was done already, but we still have to run the rule + * rewriter. We do not do AcquireRewriteLocks: we assume the query either + * came straight from the parser, or suitable locks were acquired by + * plancache.c. + * + * Because the rewriter and planner tend to scribble on the input, we make + * a preliminary copy of the source querytree. This prevents problems in + * the case that CTAS is in a portal or plpgsql function and is executed + * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + */ + rewritten = QueryRewrite((Query *) copyObject(query)); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT"); + query = (Query *) linitial(rewritten); + + Assert(query->commandType == CMD_SELECT); + + /* Save the query after rewrite but before planning. */ + ((DR_intorel *) dest)->viewParse = viewParse; + ((DR_intorel *) dest)->into = into; + + if (into->relkind == RELKIND_MATVIEW) + { + /* + * A materialized view would either need to save parameters for use in + * maintaining or loading the data or prohibit them entirely. The + * latter seems safer and more sane. + */ + if (params != NULL && params->numParams > 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views may not be defined using bound parameters"))); + } + + return query; +} + +/* * ExecCreateTableAs -- execute a CREATE TABLE AS command */ void @@ -66,7 +128,6 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, Query *query = (Query *) stmt->query; IntoClause *into = stmt->into; DestReceiver *dest; - List *rewritten; PlannedStmt *plan; QueryDesc *queryDesc; ScanDirection dir; @@ -90,26 +151,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, return; } - Assert(query->commandType == CMD_SELECT); - - /* - * Parse analysis was done already, but we still have to run the rule - * rewriter. We do not do AcquireRewriteLocks: we assume the query either - * came straight from the parser, or suitable locks were acquired by - * plancache.c. - * - * Because the rewriter and planner tend to scribble on the input, we make - * a preliminary copy of the source querytree. This prevents problems in - * the case that CTAS is in a portal or plpgsql function and is executed - * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) - */ - rewritten = QueryRewrite((Query *) copyObject(stmt->query)); - /* SELECT should never rewrite to more or less than one SELECT query */ - if (list_length(rewritten) != 1) - elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT"); - query = (Query *) linitial(rewritten); - Assert(query->commandType == CMD_SELECT); + query = SetupForCreateTableAs(query, into, queryString, params, dest); /* plan the query */ plan = pg_plan_query(query, 0, params); @@ -169,15 +212,21 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, int GetIntoRelEFlags(IntoClause *intoClause) { + int flags; /* * We need to tell the executor whether it has to produce OIDs or not, * because it doesn't have enough information to do so itself (since we * can't build the target relation until after ExecutorStart). */ if (interpretOidsOption(intoClause->options)) - return EXEC_FLAG_WITH_OIDS; + flags = EXEC_FLAG_WITH_OIDS; else - return EXEC_FLAG_WITHOUT_OIDS; + flags = EXEC_FLAG_WITHOUT_OIDS; + + if (intoClause->skipData) + flags |= EXEC_FLAG_WITH_NO_DATA; + + return flags; } /* @@ -299,12 +348,38 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) if (lc != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE TABLE AS specifies too many column names"))); + errmsg("too many column names are specified"))); + + /* + * Enforce validations needed for materialized views only. + */ + if (into->relkind == RELKIND_MATVIEW) + { + /* + * Prohibit a data-modifying CTE in the query used to create a + * materialized view. It's not sufficiently clear what the user would + * want to happen if the MV is refreshed or incrementally maintained. + */ + if (myState->viewParse->hasModifyingCTE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use data-modifying statements in WITH"))); + + /* + * Check whether any temporary database objects are used in the + * creation query. It would be hard to refresh data or incrementally + * maintain it if a source disappeared. + */ + if (isQueryUsingTempRelation(myState->viewParse)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use temporary tables or views"))); + } /* * Actually create the target table */ - intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid); + intoRelationId = DefineRelation(create, into->relkind, InvalidOid); /* * If necessary, create a TOAST table for the target table. Note that @@ -324,11 +399,22 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) AlterTableCreateToastTable(intoRelationId, toast_options); + /* Create the "view" part of a materialized view. */ + if (into->relkind == RELKIND_MATVIEW) + { + StoreViewQuery(intoRelationId, myState->viewParse, false); + CommandCounterIncrement(); + } + /* * Finally we can open the target table */ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); + if (into->relkind == RELKIND_MATVIEW && !into->skipData) + /* Make sure the heap looks good even if no rows are written. */ + SetRelationIsScannable(intoRelationDesc); + /* * Check INSERT permission on the constructed table. * @@ -338,7 +424,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = intoRelationId; - rte->relkind = RELKIND_RELATION; + rte->relkind = into->relkind; + rte->isResultRel = true; rte->requiredPerms = ACL_INSERT; for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++) |
