Fix CREATE MATVIEW/CREATE TABLE AS ... WITH NO DATA to not plan the query.
authorTom Lane <[email protected]>
Mon, 27 Jun 2016 19:57:21 +0000 (15:57 -0400)
committerTom Lane <[email protected]>
Mon, 27 Jun 2016 19:57:21 +0000 (15:57 -0400)
Previously, these commands always planned the given query and went through
executor startup before deciding not to actually run the query if WITH NO
DATA is specified.  This behavior is problematic for pg_dump because it
may cause errors to be raised that we would rather not see before a
REFRESH MATERIALIZED VIEW command is issued.  See for example bug #13907
from Marian Krucina.  This change is not sufficient to fix that particular
bug, because we also need to tweak pg_dump to issue the REFRESH later,
but it's a necessary step on the way.

A user-visible side effect of doing things this way is that the returned
command tag for WITH NO DATA cases will now be "CREATE MATERIALIZED VIEW"
or "CREATE TABLE AS", not "SELECT 0".  We could preserve the old behavior
but it would take more code, and arguably that was just an implementation
artifact not intended behavior anyhow.

In 9.5 and HEAD, also get rid of the static variable CreateAsReladdr, which
was trouble waiting to happen; there is not any prohibition on nested
CREATE commands.

Back-patch to 9.3 where CREATE MATERIALIZED VIEW was introduced.

Michael Paquier and Tom Lane

Report: <20160202161407[email protected]>

src/backend/commands/createas.c
src/backend/commands/view.c
src/backend/nodes/makefuncs.c
src/include/nodes/makefuncs.h
src/test/regress/expected/matview.out
src/test/regress/expected/select_into.out
src/test/regress/sql/matview.sql
src/test/regress/sql/select_into.sql

index 65c63411600e4dc25068cc8e856d9e7e6339d815..96886e2a39933f156d1adfdae2e325b950aeab80 100644 (file)
@@ -10,7 +10,8 @@
  *
  * 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.
+ * we must return a tuples-processed count in the completionTag.  (We no
+ * longer do that for CTAS ... WITH NO DATA, however.)
  *
  * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -34,6 +35,8 @@
 #include "commands/tablecmds.h"
 #include "commands/view.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
 #include "parser/parse_clause.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/smgr.h"
@@ -55,12 +58,161 @@ typedef struct
        BulkInsertState bistate;        /* bulk insert state */
 } DR_intorel;
 
+/* utility functions for CTAS definition creation */
+static Oid     create_ctas_internal(List *attrList, IntoClause *into);
+static Oid     create_ctas_nodata(List *tlist, IntoClause *into);
+
+/* DestReceiver routines for collecting data */
 static void intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo);
 static void intorel_receive(TupleTableSlot *slot, DestReceiver *self);
 static void intorel_shutdown(DestReceiver *self);
 static void intorel_destroy(DestReceiver *self);
 
 
+/*
+ * create_ctas_internal
+ *
+ * Internal utility used for the creation of the definition of a relation
+ * created via CREATE TABLE AS or a materialized view.  Caller needs to
+ * provide a list of attributes (ColumnDef nodes).
+ */
+static Oid
+create_ctas_internal(List *attrList, IntoClause *into)
+{
+       CreateStmt *create = makeNode(CreateStmt);
+       bool            is_matview;
+       char            relkind;
+       Datum           toast_options;
+       static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
+       Oid                     intoRelationId;
+
+       /* 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.
+        */
+       create->relation = into->rel;
+       create->tableElts = attrList;
+       create->inhRelations = NIL;
+       create->ofTypename = NULL;
+       create->constraints = NIL;
+       create->options = into->options;
+       create->oncommit = into->onCommit;
+       create->tablespacename = into->tableSpaceName;
+       create->if_not_exists = false;
+
+       /*
+        * Create the relation.  (This will error out if there's an existing view,
+        * so we don't need more code to complain if "replace" is false.)
+        */
+       intoRelationId = DefineRelation(create, relkind, InvalidOid);
+
+       /*
+        * If necessary, create a TOAST table for the target table.  Note that
+        * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
+        * the TOAST table will be visible for insertion.
+        */
+       CommandCounterIncrement();
+
+       /* parse and validate reloptions for the toast table */
+       toast_options = transformRelOptions((Datum) 0,
+                                                                               create->options,
+                                                                               "toast",
+                                                                               validnsps,
+                                                                               true, false);
+
+       (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
+
+       AlterTableCreateToastTable(intoRelationId, toast_options);
+
+       /* Create the "view" part of a materialized view. */
+       if (is_matview)
+       {
+               /* StoreViewQuery scribbles on tree, so make a copy */
+               Query      *query = (Query *) copyObject(into->viewQuery);
+
+               StoreViewQuery(intoRelationId, query, false);
+               CommandCounterIncrement();
+       }
+
+       return intoRelationId;
+}
+
+
+/*
+ * create_ctas_nodata
+ *
+ * Create CTAS or materialized view when WITH NO DATA is used, starting from
+ * the targetlist of the SELECT or view definition.
+ */
+static Oid
+create_ctas_nodata(List *tlist, IntoClause *into)
+{
+       List       *attrList;
+       ListCell   *t,
+                          *lc;
+
+       /*
+        * Build list of ColumnDefs from non-junk elements of the tlist.  If a
+        * column name list was specified in CREATE TABLE AS, override the column
+        * names in the query.  (Too few column names are OK, too many are not.)
+        */
+       attrList = NIL;
+       lc = list_head(into->colNames);
+       foreach(t, tlist)
+       {
+               TargetEntry *tle = (TargetEntry *) lfirst(t);
+
+               if (!tle->resjunk)
+               {
+                       ColumnDef  *col;
+                       char       *colname;
+
+                       if (lc)
+                       {
+                               colname = strVal(lfirst(lc));
+                               lc = lnext(lc);
+                       }
+                       else
+                               colname = tle->resname;
+
+                       col = makeColumnDef(colname,
+                                                               exprType((Node *) tle->expr),
+                                                               exprTypmod((Node *) tle->expr),
+                                                               exprCollation((Node *) tle->expr));
+
+                       /*
+                        * It's possible that the column is of a collatable type but the
+                        * collation could not be resolved, so double-check.  (We must
+                        * check this here because DefineRelation would adopt the type's
+                        * default collation rather than complaining.)
+                        */
+                       if (!OidIsValid(col->collOid) &&
+                               type_is_collatable(col->typeName->typeOid))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INDETERMINATE_COLLATION),
+                                                errmsg("no collation was derived for column \"%s\" with collatable type %s",
+                                                               col->colname,
+                                                               format_type_be(col->typeName->typeOid)),
+                                                errhint("Use the COLLATE clause to set the collation explicitly.")));
+
+                       attrList = lappend(attrList, col);
+               }
+       }
+
+       if (lc != NULL)
+               ereport(ERROR,
+                               (errcode(ERRCODE_SYNTAX_ERROR),
+                                errmsg("too many column names were specified")));
+
+       /* Create the relation definition using the ColumnDef list */
+       return create_ctas_internal(attrList, into);
+}
+
+
 /*
  * ExecCreateTableAs -- execute a CREATE TABLE AS command
  */
@@ -78,7 +230,6 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
        List       *rewritten;
        PlannedStmt *plan;
        QueryDesc  *queryDesc;
-       ScanDirection dir;
 
        /*
         * Create the tuple receiver object and insert info it will need
@@ -117,70 +268,77 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString,
                save_nestlevel = NewGUCNestLevel();
        }
 
-       /*
-        * 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);
+       if (into->skipData)
+       {
+               /*
+                * If WITH NO DATA was specified, do not go through the rewriter,
+                * planner and executor.  Just define the relation using a code path
+                * similar to CREATE VIEW.  This avoids dump/restore problems stemming
+                * from running the planner before all dependencies are set up.
+                */
+               (void) create_ctas_nodata(query->targetList, into);
+       }
+       else
+       {
+               /*
+                * 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));
 
-       /* plan the query */
-       plan = pg_plan_query(query, 0, params);
+               /* SELECT should never rewrite to more or less than one SELECT query */
+               if (list_length(rewritten) != 1)
+                       elog(ERROR, "unexpected rewrite result for %s",
+                                is_matview ? "CREATE MATERIALIZED VIEW" :
+                                "CREATE TABLE AS SELECT");
+               query = (Query *) linitial(rewritten);
+               Assert(query->commandType == CMD_SELECT);
 
-       /*
-        * Use a snapshot with an updated command ID to ensure this query sees
-        * results of any previously executed queries.  (This could only matter if
-        * the planner executed an allegedly-stable function that changed the
-        * database contents, but let's do it anyway to be parallel to the EXPLAIN
-        * code path.)
-        */
-       PushCopiedSnapshot(GetActiveSnapshot());
-       UpdateActiveSnapshotCommandId();
+               /* plan the query */
+               plan = pg_plan_query(query, 0, params);
 
-       /* Create a QueryDesc, redirecting output to our tuple receiver */
-       queryDesc = CreateQueryDesc(plan, queryString,
-                                                               GetActiveSnapshot(), InvalidSnapshot,
-                                                               dest, params, 0);
+               /*
+                * Use a snapshot with an updated command ID to ensure this query sees
+                * results of any previously executed queries.  (This could only
+                * matter if the planner executed an allegedly-stable function that
+                * changed the database contents, but let's do it anyway to be
+                * parallel to the EXPLAIN code path.)
+                */
+               PushCopiedSnapshot(GetActiveSnapshot());
+               UpdateActiveSnapshotCommandId();
 
-       /* call ExecutorStart to prepare the plan for execution */
-       ExecutorStart(queryDesc, GetIntoRelEFlags(into));
+               /* Create a QueryDesc, redirecting output to our tuple receiver */
+               queryDesc = CreateQueryDesc(plan, queryString,
+                                                                       GetActiveSnapshot(), InvalidSnapshot,
+                                                                       dest, params, 0);
 
-       /*
-        * Normally, we run the plan to completion; but if skipData is specified,
-        * just do tuple receiver startup and shutdown.
-        */
-       if (into->skipData)
-               dir = NoMovementScanDirection;
-       else
-               dir = ForwardScanDirection;
+               /* call ExecutorStart to prepare the plan for execution */
+               ExecutorStart(queryDesc, GetIntoRelEFlags(into));
 
-       /* run the plan */
-       ExecutorRun(queryDesc, dir, 0L);
+               /* run the plan to completion */
+               ExecutorRun(queryDesc, ForwardScanDirection, 0L);
 
-       /* save the rowcount if we're given a completionTag to fill */
-       if (completionTag)
-               snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
-                                "SELECT %u", queryDesc->estate->es_processed);
+               /* save the rowcount if we're given a completionTag to fill */
+               if (completionTag)
+                       snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+                                        "SELECT %u", queryDesc->estate->es_processed);
 
-       /* and clean up */
-       ExecutorFinish(queryDesc);
-       ExecutorEnd(queryDesc);
+               /* and clean up */
+               ExecutorFinish(queryDesc);
+               ExecutorEnd(queryDesc);
 
-       FreeQueryDesc(queryDesc);
+               FreeQueryDesc(queryDesc);
 
-       PopActiveSnapshot();
+               PopActiveSnapshot();
+       }
 
        if (is_matview)
        {
@@ -257,14 +415,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
        IntoClause *into = myState->into;
        bool            is_matview;
        char            relkind;
-       CreateStmt *create;
+       List       *attrList;
        Oid                     intoRelationId;
        Relation        intoRelationDesc;
        RangeTblEntry *rte;
-       Datum           toast_options;
        ListCell   *lc;
        int                     attnum;
-       static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
 
        Assert(into != NULL);           /* else somebody forgot to set it */
 
@@ -272,62 +428,32 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
        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.
-        */
-       create = makeNode(CreateStmt);
-       create->relation = into->rel;
-       create->tableElts = NIL;        /* will fill below */
-       create->inhRelations = NIL;
-       create->ofTypename = NULL;
-       create->constraints = NIL;
-       create->options = into->options;
-       create->oncommit = into->onCommit;
-       create->tablespacename = into->tableSpaceName;
-       create->if_not_exists = false;
-
        /*
         * Build column definitions using "pre-cooked" type and collation info. If
         * a column name list was specified in CREATE TABLE AS, override the
         * column names derived from the query.  (Too few column names are OK, too
         * many are not.)
         */
+       attrList = NIL;
        lc = list_head(into->colNames);
        for (attnum = 0; attnum < typeinfo->natts; attnum++)
        {
                Form_pg_attribute attribute = typeinfo->attrs[attnum];
-               ColumnDef  *col = makeNode(ColumnDef);
-               TypeName   *coltype = makeNode(TypeName);
+               ColumnDef  *col;
+               char       *colname;
 
                if (lc)
                {
-                       col->colname = strVal(lfirst(lc));
+                       colname = strVal(lfirst(lc));
                        lc = lnext(lc);
                }
                else
-                       col->colname = NameStr(attribute->attname);
-               col->typeName = coltype;
-               col->inhcount = 0;
-               col->is_local = true;
-               col->is_not_null = false;
-               col->is_from_type = false;
-               col->storage = 0;
-               col->raw_default = NULL;
-               col->cooked_default = NULL;
-               col->collClause = NULL;
-               col->collOid = attribute->attcollation;
-               col->constraints = NIL;
-               col->fdwoptions = NIL;
-
-               coltype->names = NIL;
-               coltype->typeOid = attribute->atttypid;
-               coltype->setof = false;
-               coltype->pct_type = false;
-               coltype->typmods = NIL;
-               coltype->typemod = attribute->atttypmod;
-               coltype->arrayBounds = NIL;
-               coltype->location = -1;
+                       colname = NameStr(attribute->attname);
+
+               col = makeColumnDef(colname,
+                                                       attribute->atttypid,
+                                                       attribute->atttypmod,
+                                                       attribute->attcollation);
 
                /*
                 * It's possible that the column is of a collatable type but the
@@ -336,14 +462,15 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
                 * collation rather than complaining.)
                 */
                if (!OidIsValid(col->collOid) &&
-                       type_is_collatable(coltype->typeOid))
+                       type_is_collatable(col->typeName->typeOid))
                        ereport(ERROR,
                                        (errcode(ERRCODE_INDETERMINATE_COLLATION),
                                         errmsg("no collation was derived for column \"%s\" with collatable type %s",
-                                                       col->colname, format_type_be(coltype->typeOid)),
+                                                       col->colname,
+                                                       format_type_be(col->typeName->typeOid)),
                                         errhint("Use the COLLATE clause to set the collation explicitly.")));
 
-               create->tableElts = lappend(create->tableElts, col);
+               attrList = lappend(attrList, col);
        }
 
        if (lc != NULL)
@@ -354,35 +481,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
        /*
         * Actually create the target table
         */
-       intoRelationId = DefineRelation(create, relkind, InvalidOid);
-
-       /*
-        * If necessary, create a TOAST table for the target table.  Note that
-        * AlterTableCreateToastTable ends with CommandCounterIncrement(), so that
-        * the TOAST table will be visible for insertion.
-        */
-       CommandCounterIncrement();
-
-       /* parse and validate reloptions for the toast table */
-       toast_options = transformRelOptions((Datum) 0,
-                                                                               create->options,
-                                                                               "toast",
-                                                                               validnsps,
-                                                                               true, false);
-
-       (void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
-
-       AlterTableCreateToastTable(intoRelationId, toast_options);
-
-       /* Create the "view" part of a materialized view. */
-       if (is_matview)
-       {
-               /* StoreViewQuery scribbles on tree, so make a copy */
-               Query      *query = (Query *) copyObject(into->viewQuery);
-
-               StoreViewQuery(intoRelationId, query, false);
-               CommandCounterIncrement();
-       }
+       intoRelationId = create_ctas_internal(attrList, into);
 
        /*
         * Finally we can open the target table
index 90ec7ea25815970007b473fbca772faad3aeed81..462d5f8729df9393f0d0e565755203d3a7a5484c 100644 (file)
@@ -63,24 +63,14 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
        attrList = NIL;
        foreach(t, tlist)
        {
-               TargetEntry *tle = lfirst(t);
+               TargetEntry *tle = (TargetEntry *) lfirst(t);
 
                if (!tle->resjunk)
                {
-                       ColumnDef  *def = makeNode(ColumnDef);
-
-                       def->colname = pstrdup(tle->resname);
-                       def->typeName = makeTypeNameFromOid(exprType((Node *) tle->expr),
-                                                                                        exprTypmod((Node *) tle->expr));
-                       def->inhcount = 0;
-                       def->is_local = true;
-                       def->is_not_null = false;
-                       def->is_from_type = false;
-                       def->storage = 0;
-                       def->raw_default = NULL;
-                       def->cooked_default = NULL;
-                       def->collClause = NULL;
-                       def->collOid = exprCollation((Node *) tle->expr);
+                       ColumnDef  *def = makeColumnDef(tle->resname,
+                                                                                       exprType((Node *) tle->expr),
+                                                                                       exprTypmod((Node *) tle->expr),
+                                                                                 exprCollation((Node *) tle->expr));
 
                        /*
                         * It's possible that the column is of a collatable type but the
@@ -97,7 +87,6 @@ DefineVirtualRelation(RangeVar *relation, List *tlist, bool replace,
                        }
                        else
                                Assert(!OidIsValid(def->collOid));
-                       def->constraints = NIL;
 
                        attrList = lappend(attrList, def);
                }
index c487db96d816bc7bc06b98618ef72d88135417d6..0b536a66f819cfabe4fad46b310ad1c545c3f3b5 100644 (file)
@@ -445,6 +445,35 @@ makeTypeNameFromOid(Oid typeOid, int32 typmod)
        return n;
 }
 
+/*
+ * makeColumnDef -
+ *     build a ColumnDef node to represent a simple column definition.
+ *
+ * Type and collation are specified by OID.
+ * Other properties are all basic to start with.
+ */
+ColumnDef *
+makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
+{
+       ColumnDef  *n = makeNode(ColumnDef);
+
+       n->colname = pstrdup(colname);
+       n->typeName = makeTypeNameFromOid(typeOid, typmod);
+       n->inhcount = 0;
+       n->is_local = true;
+       n->is_not_null = false;
+       n->is_from_type = false;
+       n->storage = 0;
+       n->raw_default = NULL;
+       n->cooked_default = NULL;
+       n->collClause = NULL;
+       n->collOid = collOid;
+       n->constraints = NIL;
+       n->fdwoptions = NIL;
+
+       return n;
+}
+
 /*
  * makeFuncExpr -
  *     build an expression tree representing a function call.
index ee0c3657d66cbc89920918318b61dbf70a3dd3bc..610ced94faf76aea116ec9b8e787b82f9e038488 100644 (file)
@@ -72,6 +72,9 @@ extern TypeName *makeTypeName(char *typnam);
 extern TypeName *makeTypeNameFromNameList(List *names);
 extern TypeName *makeTypeNameFromOid(Oid typeOid, int32 typmod);
 
+extern ColumnDef *makeColumnDef(const char *colname,
+                         Oid typeOid, int32 typmod, Oid collOid);
+
 extern FuncExpr *makeFuncExpr(Oid funcid, Oid rettype, List *args,
                         Oid funccollid, Oid inputcollid, CoercionForm fformat);
 
index fb01d9956e733fff25453a3f579fee4f18b5cd46..2284e364ebffcf3f42b84cc5751673fd45840ab6 100644 (file)
@@ -392,26 +392,65 @@ CREATE MATERIALIZED VIEW mv2 AS SELECT * FROM mv1
 DROP MATERIALIZED VIEW mv1 CASCADE;
 NOTICE:  drop cascades to materialized view mv2
 -- make sure that column names are handled correctly
-CREATE TABLE v (i int, j int);
-CREATE MATERIALIZED VIEW mv_v (ii) AS SELECT i, j AS jj FROM v;
-ALTER TABLE v RENAME COLUMN i TO x;
-INSERT INTO v values (1, 2);
-CREATE UNIQUE INDEX mv_v_ii ON mv_v (ii);
-REFRESH MATERIALIZED VIEW mv_v;
-SELECT * FROM v;
+CREATE TABLE mvtest_v (i int, j int);
+CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error
+ERROR:  too many column names were specified
+CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error
+ERROR:  too many column names were specified
+CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok
+ALTER TABLE mvtest_v RENAME COLUMN i TO x;
+INSERT INTO mvtest_v values (1, 2);
+CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii);
+REFRESH MATERIALIZED VIEW mvtest_mv_v;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_2;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_3;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_4;
+SELECT * FROM mvtest_v;
  x | j 
 ---+---
  1 | 2
 (1 row)
 
-SELECT * FROM mv_v;
+SELECT * FROM mvtest_mv_v;
  ii | jj 
 ----+----
   1 |  2
 (1 row)
 
-DROP TABLE v CASCADE;
-NOTICE:  drop cascades to materialized view mv_v
+SELECT * FROM mvtest_mv_v_2;
+ ii | j 
+----+---
+  1 | 2
+(1 row)
+
+SELECT * FROM mvtest_mv_v_3;
+ ii | jj 
+----+----
+  1 |  2
+(1 row)
+
+SELECT * FROM mvtest_mv_v_4;
+ ii | j 
+----+---
+  1 | 2
+(1 row)
+
+DROP TABLE mvtest_v CASCADE;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to materialized view mvtest_mv_v
+drop cascades to materialized view mvtest_mv_v_2
+drop cascades to materialized view mvtest_mv_v_3
+drop cascades to materialized view mvtest_mv_v_4
+-- make sure that create WITH NO DATA does not plan the query (bug #13907)
+create materialized view mvtest_error as select 1/0 as x;  -- fail
+ERROR:  division by zero
+create materialized view mvtest_error as select 1/0 as x with no data;
+refresh materialized view mvtest_error;  -- fail here
+ERROR:  division by zero
+drop materialized view mvtest_error;
 -- make sure that matview rows can be referenced as source rows (bug #9398)
 CREATE TABLE v AS SELECT generate_series(1,10) AS a;
 CREATE MATERIALIZED VIEW mv_v AS SELECT a FROM v WHERE a <= 5;
index 9d3f04758e91c66295b47d4a23624bb963ab748b..6be8129f831c836753e14c6aadac6ba5635a9797 100644 (file)
@@ -50,6 +50,44 @@ DETAIL:  drop cascades to table selinto_schema.tmp1
 drop cascades to table selinto_schema.tmp2
 drop cascades to table selinto_schema.tmp3
 DROP USER selinto_user;
+-- Tests for WITH NO DATA and column name consistency
+CREATE TABLE ctas_base (i int, j int);
+INSERT INTO ctas_base VALUES (1, 2);
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error
+ERROR:  too many column names were specified
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error
+ERROR:  too many column names were specified
+CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+SELECT * FROM ctas_nodata;
+ ii | jj 
+----+----
+  1 |  2
+(1 row)
+
+SELECT * FROM ctas_nodata_2;
+ ii | jj 
+----+----
+(0 rows)
+
+SELECT * FROM ctas_nodata_3;
+ ii | j 
+----+---
+  1 | 2
+(1 row)
+
+SELECT * FROM ctas_nodata_4;
+ ii | j 
+----+---
+(0 rows)
+
+DROP TABLE ctas_base;
+DROP TABLE ctas_nodata;
+DROP TABLE ctas_nodata_2;
+DROP TABLE ctas_nodata_3;
+DROP TABLE ctas_nodata_4;
 --
 -- CREATE TABLE AS/SELECT INTO as last command in a SQL function
 -- have been known to cause problems
index e9af757d609d6bcd777fe07aade45ca0ddd4cc0e..280c4bb400e09f86b4632b7975bf61f5b048b025 100644 (file)
@@ -132,15 +132,32 @@ CREATE MATERIALIZED VIEW mv2 AS SELECT * FROM mv1
 DROP MATERIALIZED VIEW mv1 CASCADE;
 
 -- make sure that column names are handled correctly
-CREATE TABLE v (i int, j int);
-CREATE MATERIALIZED VIEW mv_v (ii) AS SELECT i, j AS jj FROM v;
-ALTER TABLE v RENAME COLUMN i TO x;
-INSERT INTO v values (1, 2);
-CREATE UNIQUE INDEX mv_v_ii ON mv_v (ii);
-REFRESH MATERIALIZED VIEW mv_v;
-SELECT * FROM v;
-SELECT * FROM mv_v;
-DROP TABLE v CASCADE;
+CREATE TABLE mvtest_v (i int, j int);
+CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj, kk) AS SELECT i, j FROM mvtest_v; -- error
+CREATE MATERIALIZED VIEW mvtest_mv_v (ii, jj) AS SELECT i, j FROM mvtest_v; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_2 (ii) AS SELECT i, j FROM mvtest_v; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj, kk) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- error
+CREATE MATERIALIZED VIEW mvtest_mv_v_3 (ii, jj) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok
+CREATE MATERIALIZED VIEW mvtest_mv_v_4 (ii) AS SELECT i, j FROM mvtest_v WITH NO DATA; -- ok
+ALTER TABLE mvtest_v RENAME COLUMN i TO x;
+INSERT INTO mvtest_v values (1, 2);
+CREATE UNIQUE INDEX mvtest_mv_v_ii ON mvtest_mv_v (ii);
+REFRESH MATERIALIZED VIEW mvtest_mv_v;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_2;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_3;
+REFRESH MATERIALIZED VIEW mvtest_mv_v_4;
+SELECT * FROM mvtest_v;
+SELECT * FROM mvtest_mv_v;
+SELECT * FROM mvtest_mv_v_2;
+SELECT * FROM mvtest_mv_v_3;
+SELECT * FROM mvtest_mv_v_4;
+DROP TABLE mvtest_v CASCADE;
+
+-- make sure that create WITH NO DATA does not plan the query (bug #13907)
+create materialized view mvtest_error as select 1/0 as x;  -- fail
+create materialized view mvtest_error as select 1/0 as x with no data;
+refresh materialized view mvtest_error;  -- fail here
+drop materialized view mvtest_error;
 
 -- make sure that matview rows can be referenced as source rows (bug #9398)
 CREATE TABLE v AS SELECT generate_series(1,10) AS a;
index 4d1cc86556b4a9899c84ca93863d695c971f199e..571ebe68a4c993875799166c1866cf3746bbbf34 100644 (file)
@@ -53,6 +53,25 @@ RESET SESSION AUTHORIZATION;
 DROP SCHEMA selinto_schema CASCADE;
 DROP USER selinto_user;
 
+-- Tests for WITH NO DATA and column name consistency
+CREATE TABLE ctas_base (i int, j int);
+INSERT INTO ctas_base VALUES (1, 2);
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base; -- Error
+CREATE TABLE ctas_nodata (ii, jj, kk) AS SELECT i, j FROM ctas_base WITH NO DATA; -- Error
+CREATE TABLE ctas_nodata (ii, jj) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_2 (ii, jj) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+CREATE TABLE ctas_nodata_3 (ii) AS SELECT i, j FROM ctas_base; -- OK
+CREATE TABLE ctas_nodata_4 (ii) AS SELECT i, j FROM ctas_base WITH NO DATA; -- OK
+SELECT * FROM ctas_nodata;
+SELECT * FROM ctas_nodata_2;
+SELECT * FROM ctas_nodata_3;
+SELECT * FROM ctas_nodata_4;
+DROP TABLE ctas_base;
+DROP TABLE ctas_nodata;
+DROP TABLE ctas_nodata_2;
+DROP TABLE ctas_nodata_3;
+DROP TABLE ctas_nodata_4;
+
 --
 -- CREATE TABLE AS/SELECT INTO as last command in a SQL function
 -- have been known to cause problems