11/*-------------------------------------------------------------------------
22 *
33 * createas.c
4- * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO
4+ * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO.
55 * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors,
6- * implement that here, too.
6+ * we implement that here, too.
77 *
88 * We implement this by diverting the query's normal output to a
99 * specialized DestReceiver type.
1010 *
11- * Formerly, this command was implemented as a variant of SELECT, which led
11+ * Formerly, CTAS was implemented as a variant of SELECT, which led
1212 * to assorted legacy behaviors that we still try to preserve, notably that
1313 * we must return a tuples-processed count in the completionTag.
1414 *
3333#include "commands/prepare.h"
3434#include "commands/tablecmds.h"
3535#include "commands/view.h"
36- #include "parser/analyze.h"
3736#include "parser/parse_clause.h"
3837#include "rewrite/rewriteHandler.h"
3938#include "storage/smgr.h"
@@ -48,7 +47,6 @@ typedef struct
4847{
4948 DestReceiver pub ; /* publicly-known function pointers */
5049 IntoClause * into ; /* target relation specification */
51- Query * viewParse ; /* the query which defines/populates data */
5250 /* These fields are filled by intorel_startup: */
5351 Relation rel ; /* relation to write to */
5452 CommandId output_cid ; /* cmin to insert in output tuples */
@@ -62,62 +60,6 @@ static void intorel_shutdown(DestReceiver *self);
6260static void intorel_destroy (DestReceiver * self );
6361
6462
65- /*
66- * Common setup needed by both normal execution and EXPLAIN ANALYZE.
67- */
68- Query *
69- SetupForCreateTableAs (Query * query , IntoClause * into , const char * queryString ,
70- ParamListInfo params , DestReceiver * dest )
71- {
72- List * rewritten ;
73- Query * viewParse = NULL ;
74-
75- Assert (query -> commandType == CMD_SELECT );
76-
77- if (into -> relkind == RELKIND_MATVIEW )
78- viewParse = (Query * ) parse_analyze ((Node * ) copyObject (query ),
79- queryString , NULL , 0 )-> utilityStmt ;
80-
81- /*
82- * Parse analysis was done already, but we still have to run the rule
83- * rewriter. We do not do AcquireRewriteLocks: we assume the query either
84- * came straight from the parser, or suitable locks were acquired by
85- * plancache.c.
86- *
87- * Because the rewriter and planner tend to scribble on the input, we make
88- * a preliminary copy of the source querytree. This prevents problems in
89- * the case that CTAS is in a portal or plpgsql function and is executed
90- * repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
91- */
92- rewritten = QueryRewrite ((Query * ) copyObject (query ));
93-
94- /* SELECT should never rewrite to more or less than one SELECT query */
95- if (list_length (rewritten ) != 1 )
96- elog (ERROR , "unexpected rewrite result for CREATE TABLE AS SELECT" );
97- query = (Query * ) linitial (rewritten );
98-
99- Assert (query -> commandType == CMD_SELECT );
100-
101- /* Save the query after rewrite but before planning. */
102- ((DR_intorel * ) dest )-> viewParse = viewParse ;
103- ((DR_intorel * ) dest )-> into = into ;
104-
105- if (into -> relkind == RELKIND_MATVIEW )
106- {
107- /*
108- * A materialized view would either need to save parameters for use in
109- * maintaining or loading the data or prohibit them entirely. The
110- * latter seems safer and more sane.
111- */
112- if (params != NULL && params -> numParams > 0 )
113- ereport (ERROR ,
114- (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
115- errmsg ("materialized views may not be defined using bound parameters" )));
116- }
117-
118- return query ;
119- }
120-
12163/*
12264 * ExecCreateTableAs -- execute a CREATE TABLE AS command
12365 */
@@ -128,6 +70,7 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
12870 Query * query = (Query * ) stmt -> query ;
12971 IntoClause * into = stmt -> into ;
13072 DestReceiver * dest ;
73+ List * rewritten ;
13174 PlannedStmt * plan ;
13275 QueryDesc * queryDesc ;
13376 ScanDirection dir ;
@@ -151,8 +94,26 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
15194
15295 return ;
15396 }
97+ Assert (query -> commandType == CMD_SELECT );
15498
155- query = SetupForCreateTableAs (query , into , queryString , params , dest );
99+ /*
100+ * Parse analysis was done already, but we still have to run the rule
101+ * rewriter. We do not do AcquireRewriteLocks: we assume the query either
102+ * came straight from the parser, or suitable locks were acquired by
103+ * plancache.c.
104+ *
105+ * Because the rewriter and planner tend to scribble on the input, we make
106+ * a preliminary copy of the source querytree. This prevents problems in
107+ * the case that CTAS is in a portal or plpgsql function and is executed
108+ * repeatedly. (See also the same hack in EXPLAIN and PREPARE.)
109+ */
110+ rewritten = QueryRewrite ((Query * ) copyObject (query ));
111+
112+ /* SELECT should never rewrite to more or less than one SELECT query */
113+ if (list_length (rewritten ) != 1 )
114+ elog (ERROR , "unexpected rewrite result for CREATE TABLE AS SELECT" );
115+ query = (Query * ) linitial (rewritten );
116+ Assert (query -> commandType == CMD_SELECT );
156117
157118 /* plan the query */
158119 plan = pg_plan_query (query , 0 , params );
@@ -213,20 +174,20 @@ int
213174GetIntoRelEFlags (IntoClause * intoClause )
214175{
215176 int flags ;
177+
216178 /*
217179 * We need to tell the executor whether it has to produce OIDs or not,
218180 * because it doesn't have enough information to do so itself (since we
219181 * can't build the target relation until after ExecutorStart).
182+ *
183+ * Disallow the OIDS option for materialized views.
220184 */
221- if (interpretOidsOption (intoClause -> options , intoClause -> relkind ))
185+ if (interpretOidsOption (intoClause -> options ,
186+ (intoClause -> viewQuery == NULL )))
222187 flags = EXEC_FLAG_WITH_OIDS ;
223188 else
224189 flags = EXEC_FLAG_WITHOUT_OIDS ;
225190
226- Assert (intoClause -> relkind != RELKIND_MATVIEW ||
227- (flags & (EXEC_FLAG_WITH_OIDS | EXEC_FLAG_WITHOUT_OIDS )) ==
228- EXEC_FLAG_WITHOUT_OIDS );
229-
230191 if (intoClause -> skipData )
231192 flags |= EXEC_FLAG_WITH_NO_DATA ;
232193
@@ -264,6 +225,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
264225{
265226 DR_intorel * myState = (DR_intorel * ) self ;
266227 IntoClause * into = myState -> into ;
228+ bool is_matview ;
229+ char relkind ;
267230 CreateStmt * create ;
268231 Oid intoRelationId ;
269232 Relation intoRelationDesc ;
@@ -275,6 +238,10 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
275238
276239 Assert (into != NULL ); /* else somebody forgot to set it */
277240
241+ /* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
242+ is_matview = (into -> viewQuery != NULL );
243+ relkind = is_matview ? RELKIND_MATVIEW : RELKIND_RELATION ;
244+
278245 /*
279246 * Create the target relation by faking up a CREATE TABLE parsetree and
280247 * passing it to DefineRelation.
@@ -352,38 +319,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
352319 if (lc != NULL )
353320 ereport (ERROR ,
354321 (errcode (ERRCODE_SYNTAX_ERROR ),
355- errmsg ("too many column names are specified" )));
356-
357- /*
358- * Enforce validations needed for materialized views only.
359- */
360- if (into -> relkind == RELKIND_MATVIEW )
361- {
362- /*
363- * Prohibit a data-modifying CTE in the query used to create a
364- * materialized view. It's not sufficiently clear what the user would
365- * want to happen if the MV is refreshed or incrementally maintained.
366- */
367- if (myState -> viewParse -> hasModifyingCTE )
368- ereport (ERROR ,
369- (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
370- errmsg ("materialized views must not use data-modifying statements in WITH" )));
371-
372- /*
373- * Check whether any temporary database objects are used in the
374- * creation query. It would be hard to refresh data or incrementally
375- * maintain it if a source disappeared.
376- */
377- if (isQueryUsingTempRelation (myState -> viewParse ))
378- ereport (ERROR ,
379- (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
380- errmsg ("materialized views must not use temporary tables or views" )));
381- }
322+ errmsg ("too many column names were specified" )));
382323
383324 /*
384325 * Actually create the target table
385326 */
386- intoRelationId = DefineRelation (create , into -> relkind , InvalidOid );
327+ intoRelationId = DefineRelation (create , relkind , InvalidOid );
387328
388329 /*
389330 * If necessary, create a TOAST table for the target table. Note that
@@ -404,9 +345,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
404345 AlterTableCreateToastTable (intoRelationId , toast_options );
405346
406347 /* Create the "view" part of a materialized view. */
407- if (into -> relkind == RELKIND_MATVIEW )
348+ if (is_matview )
408349 {
409- StoreViewQuery (intoRelationId , myState -> viewParse , false);
350+ /* StoreViewQuery scribbles on tree, so make a copy */
351+ Query * query = (Query * ) copyObject (into -> viewQuery );
352+
353+ StoreViewQuery (intoRelationId , query , false);
410354 CommandCounterIncrement ();
411355 }
412356
@@ -415,7 +359,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
415359 */
416360 intoRelationDesc = heap_open (intoRelationId , AccessExclusiveLock );
417361
418- if (into -> relkind == RELKIND_MATVIEW && !into -> skipData )
362+ if (is_matview && !into -> skipData )
419363 /* Make sure the heap looks good even if no rows are written. */
420364 SetMatViewToPopulated (intoRelationDesc );
421365
@@ -428,7 +372,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
428372 rte = makeNode (RangeTblEntry );
429373 rte -> rtekind = RTE_RELATION ;
430374 rte -> relid = intoRelationId ;
431- rte -> relkind = into -> relkind ;
375+ rte -> relkind = relkind ;
432376 rte -> isResultRel = true;
433377 rte -> requiredPerms = ACL_INSERT ;
434378
0 commit comments