diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-04-12 19:25:20 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-04-12 19:25:31 -0400 |
commit | 0b337904213337db5026ef0a756a447588023935 (patch) | |
tree | b8021cade19c6865d839b712c51688d1f56a3868 /src/backend/commands/createas.c | |
parent | 5003f94f661d3eff0c44774576fe2f1ae202da86 (diff) |
Clean up the mess around EXPLAIN and materialized views.
Revert the matview-related changes in explain.c's API, as per recent
complaint from Robert Haas. The reason for these appears to have been
principally some ill-considered choices around having intorel_startup do
what ought to be parse-time checking, plus a poor arrangement for passing
it the view parsetree it needs to store into pg_rewrite when creating a
materialized view. Do the latter by having parse analysis stick a copy
into the IntoClause, instead of doing it at runtime. (On the whole,
I seriously question the choice to represent CREATE MATERIALIZED VIEW as a
variant of SELECT INTO/CREATE TABLE AS, because that means injecting even
more complexity into what was already a horrid legacy kluge. However,
I didn't go so far as to rethink that choice ... yet.)
I also moved several error checks into matview parse analysis, and
made the check for external Params in a matview more accurate.
In passing, clean things up a bit more around interpretOidsOption(),
and fix things so that we can use that to force no-oids for views,
sequences, etc, thereby eliminating the need to cons up "oids = false"
options when creating them.
catversion bump due to change in IntoClause. (I wonder though if we
really need readfuncs/outfuncs support for IntoClause anymore.)
Diffstat (limited to 'src/backend/commands/createas.c')
-rw-r--r-- | src/backend/commands/createas.c | 142 |
1 files changed, 43 insertions, 99 deletions
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 079fafa06fb..94a5fa755e3 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -1,14 +1,14 @@ /*------------------------------------------------------------------------- * * createas.c - * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO + * 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 that here, too. * * We implement this by diverting the query's normal output to a * specialized DestReceiver type. * - * Formerly, this command was implemented as a variant of SELECT, which led + * Formerly, CTAS was implemented as a variant of SELECT, which led * to assorted legacy behaviors that we still try to preserve, notably that * we must return a tuples-processed count in the completionTag. * @@ -33,7 +33,6 @@ #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" @@ -48,7 +47,6 @@ 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 */ @@ -63,62 +61,6 @@ 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 @@ -128,6 +70,7 @@ 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; @@ -151,8 +94,26 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, return; } + Assert(query->commandType == CMD_SELECT); - query = SetupForCreateTableAs(query, into, queryString, params, dest); + /* + * 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); /* plan the query */ plan = pg_plan_query(query, 0, params); @@ -213,20 +174,20 @@ 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). + * + * Disallow the OIDS option for materialized views. */ - if (interpretOidsOption(intoClause->options, intoClause->relkind)) + if (interpretOidsOption(intoClause->options, + (intoClause->viewQuery == NULL))) flags = EXEC_FLAG_WITH_OIDS; else flags = EXEC_FLAG_WITHOUT_OIDS; - Assert(intoClause->relkind != RELKIND_MATVIEW || - (flags & (EXEC_FLAG_WITH_OIDS | EXEC_FLAG_WITHOUT_OIDS)) == - EXEC_FLAG_WITHOUT_OIDS); - if (intoClause->skipData) flags |= EXEC_FLAG_WITH_NO_DATA; @@ -264,6 +225,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) { DR_intorel *myState = (DR_intorel *) self; IntoClause *into = myState->into; + bool is_matview; + char relkind; CreateStmt *create; Oid intoRelationId; Relation intoRelationDesc; @@ -275,6 +238,10 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) Assert(into != NULL); /* 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; + /* * Create the target relation by faking up a CREATE TABLE parsetree and * passing it to DefineRelation. @@ -352,38 +319,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) if (lc != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - 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"))); - } + errmsg("too many column names were specified"))); /* * Actually create the target table */ - intoRelationId = DefineRelation(create, into->relkind, InvalidOid); + intoRelationId = DefineRelation(create, relkind, InvalidOid); /* * If necessary, create a TOAST table for the target table. Note that @@ -404,9 +345,12 @@ 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) + if (is_matview) { - StoreViewQuery(intoRelationId, myState->viewParse, false); + /* StoreViewQuery scribbles on tree, so make a copy */ + Query *query = (Query *) copyObject(into->viewQuery); + + StoreViewQuery(intoRelationId, query, false); CommandCounterIncrement(); } @@ -415,7 +359,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) */ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); - if (into->relkind == RELKIND_MATVIEW && !into->skipData) + if (is_matview && !into->skipData) /* Make sure the heap looks good even if no rows are written. */ SetMatViewToPopulated(intoRelationDesc); @@ -428,7 +372,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = intoRelationId; - rte->relkind = into->relkind; + rte->relkind = relkind; rte->isResultRel = true; rte->requiredPerms = ACL_INSERT; |