summaryrefslogtreecommitdiff
path: root/src/backend/commands/createas.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/createas.c')
-rw-r--r--src/backend/commands/createas.c137
1 files changed, 112 insertions, 25 deletions
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 66a49db330..a3ff1d56c8 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -2,6 +2,8 @@
*
* createas.c
* 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 this by diverting the query's normal output to a
* specialized DestReceiver type.
@@ -27,8 +29,11 @@
#include "access/xact.h"
#include "catalog/toasting.h"
#include "commands/createas.h"
+#include "commands/matview.h"
#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"
@@ -43,6 +48,7 @@ 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 */
@@ -57,6 +63,62 @@ 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
@@ -66,7 +128,6 @@ 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;
@@ -90,26 +151,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
return;
}
- Assert(query->commandType == CMD_SELECT);
-
- /*
- * 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(stmt->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);
+ query = SetupForCreateTableAs(query, into, queryString, params, dest);
/* plan the query */
plan = pg_plan_query(query, 0, params);
@@ -169,15 +212,21 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
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).
*/
if (interpretOidsOption(intoClause->options))
- return EXEC_FLAG_WITH_OIDS;
+ flags = EXEC_FLAG_WITH_OIDS;
else
- return EXEC_FLAG_WITHOUT_OIDS;
+ flags = EXEC_FLAG_WITHOUT_OIDS;
+
+ if (intoClause->skipData)
+ flags |= EXEC_FLAG_WITH_NO_DATA;
+
+ return flags;
}
/*
@@ -299,12 +348,38 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
if (lc != NULL)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("CREATE TABLE AS specifies too many column names")));
+ 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")));
+ }
/*
* Actually create the target table
*/
- intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid);
+ intoRelationId = DefineRelation(create, into->relkind, InvalidOid);
/*
* If necessary, create a TOAST table for the target table. Note that
@@ -324,11 +399,22 @@ 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)
+ {
+ StoreViewQuery(intoRelationId, myState->viewParse, false);
+ CommandCounterIncrement();
+ }
+
/*
* Finally we can open the target table
*/
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
+ if (into->relkind == RELKIND_MATVIEW && !into->skipData)
+ /* Make sure the heap looks good even if no rows are written. */
+ SetRelationIsScannable(intoRelationDesc);
+
/*
* Check INSERT permission on the constructed table.
*
@@ -338,7 +424,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = intoRelationId;
- rte->relkind = RELKIND_RELATION;
+ rte->relkind = into->relkind;
+ rte->isResultRel = true;
rte->requiredPerms = ACL_INSERT;
for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++)