1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-14 18:42:34 +03:00

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.)
This commit is contained in:
Tom Lane
2013-04-12 19:25:20 -04:00
parent 5003f94f66
commit 0b33790421
26 changed files with 231 additions and 227 deletions

View File

@ -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 */
@ -62,62 +60,6 @@ static void intorel_shutdown(DestReceiver *self);
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
*/
@ -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;