Skip to content

Commit 0b33790

Browse files
committed
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.)
1 parent 5003f94 commit 0b33790

26 files changed

+230
-226
lines changed

src/backend/commands/createas.c

Lines changed: 43 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
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
*
@@ -33,7 +33,6 @@
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);
6260
static 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
213174
GetIntoRelEFlags(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

src/backend/commands/explain.c

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
4747
#define X_NOWHITESPACE 4
4848

4949
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
50-
const char *queryString, DestReceiver *dest, ParamListInfo params);
50+
const char *queryString, ParamListInfo params);
5151
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
5252
ExplainState *es);
5353
static double elapsed_time(instr_time *starttime);
@@ -219,7 +219,7 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
219219
foreach(l, rewritten)
220220
{
221221
ExplainOneQuery((Query *) lfirst(l), NULL, &es,
222-
queryString, None_Receiver, params);
222+
queryString, params);
223223

224224
/* Separate plans with an appropriate separator */
225225
if (lnext(l) != NULL)
@@ -300,8 +300,7 @@ ExplainResultDesc(ExplainStmt *stmt)
300300
*/
301301
static void
302302
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
303-
const char *queryString, DestReceiver *dest,
304-
ParamListInfo params)
303+
const char *queryString, ParamListInfo params)
305304
{
306305
/* planner will not cope with utility statements */
307306
if (query->commandType == CMD_UTILITY)
@@ -312,7 +311,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
312311

313312
/* if an advisor plugin is present, let it manage things */
314313
if (ExplainOneQuery_hook)
315-
(*ExplainOneQuery_hook) (query, into, es, queryString, dest, params);
314+
(*ExplainOneQuery_hook) (query, into, es, queryString, params);
316315
else
317316
{
318317
PlannedStmt *plan;
@@ -321,7 +320,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
321320
plan = pg_plan_query(query, 0, params);
322321

323322
/* run it (if needed) and produce output */
324-
ExplainOnePlan(plan, into, es, queryString, dest, params);
323+
ExplainOnePlan(plan, into, es, queryString, params);
325324
}
326325
}
327326

@@ -345,23 +344,19 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
345344

346345
if (IsA(utilityStmt, CreateTableAsStmt))
347346
{
348-
DestReceiver *dest;
349-
350347
/*
351348
* We have to rewrite the contained SELECT and then pass it back to
352349
* ExplainOneQuery. It's probably not really necessary to copy the
353350
* contained parsetree another time, but let's be safe.
354351
*/
355352
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
356-
Query *query = (Query *) ctas->query;
357-
358-
dest = CreateIntoRelDestReceiver(into);
353+
List *rewritten;
359354

360355
Assert(IsA(ctas->query, Query));
361-
362-
query = SetupForCreateTableAs(query, ctas->into, queryString, params, dest);
363-
364-
ExplainOneQuery(query, ctas->into, es, queryString, dest, params);
356+
rewritten = QueryRewrite((Query *) copyObject(ctas->query));
357+
Assert(list_length(rewritten) == 1);
358+
ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
359+
queryString, params);
365360
}
366361
else if (IsA(utilityStmt, ExecuteStmt))
367362
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
@@ -402,8 +397,9 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
402397
*/
403398
void
404399
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
405-
const char *queryString, DestReceiver *dest, ParamListInfo params)
400+
const char *queryString, ParamListInfo params)
406401
{
402+
DestReceiver *dest;
407403
QueryDesc *queryDesc;
408404
instr_time starttime;
409405
double totaltime = 0;
@@ -427,6 +423,15 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
427423
PushCopiedSnapshot(GetActiveSnapshot());
428424
UpdateActiveSnapshotCommandId();
429425

426+
/*
427+
* Normally we discard the query's output, but if explaining CREATE TABLE
428+
* AS, we'd better use the appropriate tuple receiver.
429+
*/
430+
if (into)
431+
dest = CreateIntoRelDestReceiver(into);
432+
else
433+
dest = None_Receiver;
434+
430435
/* Create a QueryDesc for the query */
431436
queryDesc = CreateQueryDesc(plannedstmt, queryString,
432437
GetActiveSnapshot(), InvalidSnapshot,

src/backend/commands/prepare.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
665665
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
666666

667667
if (IsA(pstmt, PlannedStmt))
668-
ExplainOnePlan(pstmt, into, es, query_string, None_Receiver, paramLI);
668+
ExplainOnePlan(pstmt, into, es, query_string, paramLI);
669669
else
670670
ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
671671

src/backend/commands/sequence.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ DefineSequence(CreateSeqStmt *seq)
210210
stmt->relation = seq->sequence;
211211
stmt->inhRelations = NIL;
212212
stmt->constraints = NIL;
213-
stmt->options = list_make1(defWithOids(false));
213+
stmt->options = NIL;
214214
stmt->oncommit = ONCOMMIT_NOOP;
215215
stmt->tablespacename = NULL;
216216
stmt->if_not_exists = false;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy