mirror of
https://github.com/postgres/postgres.git
synced 2025-08-14 02:22:38 +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:
@@ -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;
|
||||
|
||||
|
@@ -47,7 +47,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL;
|
||||
#define X_NOWHITESPACE 4
|
||||
|
||||
static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
||||
const char *queryString, DestReceiver *dest, ParamListInfo params);
|
||||
const char *queryString, ParamListInfo params);
|
||||
static void report_triggers(ResultRelInfo *rInfo, bool show_relname,
|
||||
ExplainState *es);
|
||||
static double elapsed_time(instr_time *starttime);
|
||||
@@ -219,7 +219,7 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString,
|
||||
foreach(l, rewritten)
|
||||
{
|
||||
ExplainOneQuery((Query *) lfirst(l), NULL, &es,
|
||||
queryString, None_Receiver, params);
|
||||
queryString, params);
|
||||
|
||||
/* Separate plans with an appropriate separator */
|
||||
if (lnext(l) != NULL)
|
||||
@@ -300,8 +300,7 @@ ExplainResultDesc(ExplainStmt *stmt)
|
||||
*/
|
||||
static void
|
||||
ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
||||
const char *queryString, DestReceiver *dest,
|
||||
ParamListInfo params)
|
||||
const char *queryString, ParamListInfo params)
|
||||
{
|
||||
/* planner will not cope with utility statements */
|
||||
if (query->commandType == CMD_UTILITY)
|
||||
@@ -312,7 +311,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
||||
|
||||
/* if an advisor plugin is present, let it manage things */
|
||||
if (ExplainOneQuery_hook)
|
||||
(*ExplainOneQuery_hook) (query, into, es, queryString, dest, params);
|
||||
(*ExplainOneQuery_hook) (query, into, es, queryString, params);
|
||||
else
|
||||
{
|
||||
PlannedStmt *plan;
|
||||
@@ -321,7 +320,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es,
|
||||
plan = pg_plan_query(query, 0, params);
|
||||
|
||||
/* run it (if needed) and produce output */
|
||||
ExplainOnePlan(plan, into, es, queryString, dest, params);
|
||||
ExplainOnePlan(plan, into, es, queryString, params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,23 +344,19 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
|
||||
|
||||
if (IsA(utilityStmt, CreateTableAsStmt))
|
||||
{
|
||||
DestReceiver *dest;
|
||||
|
||||
/*
|
||||
* We have to rewrite the contained SELECT and then pass it back to
|
||||
* ExplainOneQuery. It's probably not really necessary to copy the
|
||||
* contained parsetree another time, but let's be safe.
|
||||
*/
|
||||
CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt;
|
||||
Query *query = (Query *) ctas->query;
|
||||
|
||||
dest = CreateIntoRelDestReceiver(into);
|
||||
List *rewritten;
|
||||
|
||||
Assert(IsA(ctas->query, Query));
|
||||
|
||||
query = SetupForCreateTableAs(query, ctas->into, queryString, params, dest);
|
||||
|
||||
ExplainOneQuery(query, ctas->into, es, queryString, dest, params);
|
||||
rewritten = QueryRewrite((Query *) copyObject(ctas->query));
|
||||
Assert(list_length(rewritten) == 1);
|
||||
ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es,
|
||||
queryString, params);
|
||||
}
|
||||
else if (IsA(utilityStmt, ExecuteStmt))
|
||||
ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es,
|
||||
@@ -402,8 +397,9 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es,
|
||||
*/
|
||||
void
|
||||
ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
|
||||
const char *queryString, DestReceiver *dest, ParamListInfo params)
|
||||
const char *queryString, ParamListInfo params)
|
||||
{
|
||||
DestReceiver *dest;
|
||||
QueryDesc *queryDesc;
|
||||
instr_time starttime;
|
||||
double totaltime = 0;
|
||||
@@ -427,6 +423,15 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
|
||||
PushCopiedSnapshot(GetActiveSnapshot());
|
||||
UpdateActiveSnapshotCommandId();
|
||||
|
||||
/*
|
||||
* Normally we discard the query's output, but if explaining CREATE TABLE
|
||||
* AS, we'd better use the appropriate tuple receiver.
|
||||
*/
|
||||
if (into)
|
||||
dest = CreateIntoRelDestReceiver(into);
|
||||
else
|
||||
dest = None_Receiver;
|
||||
|
||||
/* Create a QueryDesc for the query */
|
||||
queryDesc = CreateQueryDesc(plannedstmt, queryString,
|
||||
GetActiveSnapshot(), InvalidSnapshot,
|
||||
|
@@ -665,7 +665,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es,
|
||||
PlannedStmt *pstmt = (PlannedStmt *) lfirst(p);
|
||||
|
||||
if (IsA(pstmt, PlannedStmt))
|
||||
ExplainOnePlan(pstmt, into, es, query_string, None_Receiver, paramLI);
|
||||
ExplainOnePlan(pstmt, into, es, query_string, paramLI);
|
||||
else
|
||||
ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI);
|
||||
|
||||
|
@@ -210,7 +210,7 @@ DefineSequence(CreateSeqStmt *seq)
|
||||
stmt->relation = seq->sequence;
|
||||
stmt->inhRelations = NIL;
|
||||
stmt->constraints = NIL;
|
||||
stmt->options = list_make1(defWithOids(false));
|
||||
stmt->options = NIL;
|
||||
stmt->oncommit = ONCOMMIT_NOOP;
|
||||
stmt->tablespacename = NULL;
|
||||
stmt->if_not_exists = false;
|
||||
|
@@ -407,8 +407,6 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid,
|
||||
static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid,
|
||||
Oid oldrelid, void *arg);
|
||||
|
||||
static bool isQueryUsingTempRelation_walker(Node *node, void *context);
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* DefineRelation
|
||||
@@ -560,7 +558,9 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId)
|
||||
*/
|
||||
descriptor = BuildDescForRelation(schema);
|
||||
|
||||
localHasOids = interpretOidsOption(stmt->options, relkind);
|
||||
localHasOids = interpretOidsOption(stmt->options,
|
||||
(relkind == RELKIND_RELATION ||
|
||||
relkind == RELKIND_FOREIGN_TABLE));
|
||||
descriptor->tdhasoid = (localHasOids || parentOidCount > 0);
|
||||
|
||||
/*
|
||||
@@ -10538,51 +10538,3 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid,
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true iff any relation underlying this query is a temporary database
|
||||
* object (table, view, or materialized view).
|
||||
*
|
||||
*/
|
||||
bool
|
||||
isQueryUsingTempRelation(Query *query)
|
||||
{
|
||||
return isQueryUsingTempRelation_walker((Node *) query, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
isQueryUsingTempRelation_walker(Node *node, void *context)
|
||||
{
|
||||
if (node == NULL)
|
||||
return false;
|
||||
|
||||
if (IsA(node, Query))
|
||||
{
|
||||
Query *query = (Query *) node;
|
||||
ListCell *rtable;
|
||||
|
||||
foreach(rtable, query->rtable)
|
||||
{
|
||||
RangeTblEntry *rte = lfirst(rtable);
|
||||
|
||||
if (rte->rtekind == RTE_RELATION)
|
||||
{
|
||||
Relation rel = heap_open(rte->relid, AccessShareLock);
|
||||
char relpersistence = rel->rd_rel->relpersistence;
|
||||
|
||||
heap_close(rel, AccessShareLock);
|
||||
if (relpersistence == RELPERSISTENCE_TEMP)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return query_tree_walker(query,
|
||||
isQueryUsingTempRelation_walker,
|
||||
context,
|
||||
QTW_IGNORE_JOINALIASES);
|
||||
}
|
||||
|
||||
return expression_tree_walker(node,
|
||||
isQueryUsingTempRelation_walker,
|
||||
context);
|
||||
}
|
||||
|
@@ -2032,7 +2032,7 @@ DefineCompositeType(RangeVar *typevar, List *coldeflist)
|
||||
createStmt->tableElts = coldeflist;
|
||||
createStmt->inhRelations = NIL;
|
||||
createStmt->constraints = NIL;
|
||||
createStmt->options = list_make1(defWithOids(false));
|
||||
createStmt->options = NIL;
|
||||
createStmt->oncommit = ONCOMMIT_NOOP;
|
||||
createStmt->tablespacename = NULL;
|
||||
createStmt->if_not_exists = false;
|
||||
|
@@ -208,7 +208,6 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
|
||||
createStmt->inhRelations = NIL;
|
||||
createStmt->constraints = NIL;
|
||||
createStmt->options = options;
|
||||
createStmt->options = lappend(options, defWithOids(false));
|
||||
createStmt->oncommit = ONCOMMIT_NOOP;
|
||||
createStmt->tablespacename = NULL;
|
||||
createStmt->if_not_exists = false;
|
||||
|
Reference in New Issue
Block a user