Allow CREATE OR REPLACE VIEW to add columns to the _end_ of the view.
authorBruce Momjian <bruce@momjian.us>
Sat, 6 Dec 2008 23:22:46 +0000 (23:22 +0000)
committerBruce Momjian <bruce@momjian.us>
Sat, 6 Dec 2008 23:22:46 +0000 (23:22 +0000)
Robert Haas

doc/src/sgml/ref/create_view.sgml
src/backend/commands/tablecmds.c
src/backend/commands/view.c
src/backend/parser/parse_utilcmd.c
src/include/nodes/parsenodes.h
src/test/regress/expected/create_view.out
src/test/regress/sql/create_view.sql

index a0ed9a8efdd0e9b8f79d0661dbfc9a30622cd425..df01488c0a8ea3f9cf7c8c2134052f7309945335 100644 (file)
@@ -37,9 +37,10 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
 
   <para>
    <command>CREATE OR REPLACE VIEW</command> is similar, but if a view
-   of the same name already exists, it is replaced.  You can only replace
-   a view with a new query that generates the identical set of columns
-   (i.e., same column names and data types).
+   of the same name already exists, it is replaced.  The new query must
+   generate all of the same columns that were generated by the original query
+   in the same order and with the same data types, but may add additional
+   columns to the end of the list.
   </para>
 
   <para>
index 423808d93daaaebbb6b19ff57904ab957c1754a1..dcf85637af8eedbb0ac1fb87e5b6cb1eb68a96fb 100644 (file)
@@ -2334,6 +2334,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
                        ATPrepAddColumn(wqueue, rel, recurse, cmd);
                        pass = AT_PASS_ADD_COL;
                        break;
+               case AT_AddColumnToView:        /* add column via CREATE OR REPLACE VIEW */
+                       ATSimplePermissions(rel, true);
+                       /* Performs own recursion */
+                       ATPrepAddColumn(wqueue, rel, recurse, cmd);
+                       pass = AT_PASS_ADD_COL;
+                       break;
                case AT_ColumnDefault:  /* ALTER COLUMN DEFAULT */
 
                        /*
@@ -2555,6 +2561,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
        switch (cmd->subtype)
        {
                case AT_AddColumn:              /* ADD COLUMN */
+               case AT_AddColumnToView: /* add column via CREATE OR REPLACE VIEW */
                        ATExecAddColumn(tab, rel, (ColumnDef *) cmd->def);
                        break;
                case AT_ColumnDefault:  /* ALTER COLUMN DEFAULT */
@@ -3455,6 +3462,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
        int                     i;
        int                     minattnum,
                                maxatts;
+       char            relkind;
        HeapTuple       typeTuple;
        Oid                     typeOid;
        int32           typmod;
@@ -3527,6 +3535,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
                                                colDef->colname, RelationGetRelationName(rel))));
 
        minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts;
+       relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
        maxatts = minattnum + 1;
        if (maxatts > MaxHeapAttributeNumber)
                ereport(ERROR,
@@ -3625,44 +3634,48 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
         * Note: we use build_column_default, and not just the cooked default
         * returned by AddRelationNewConstraints, so that the right thing happens
         * when a datatype's default applies.
+        *
+        * We skip this logic completely for views.
         */
-       defval = (Expr *) build_column_default(rel, attribute.attnum);
+       if (relkind != RELKIND_VIEW) {
+               defval = (Expr *) build_column_default(rel, attribute.attnum);
 
-       if (!defval && GetDomainConstraints(typeOid) != NIL)
-       {
-               Oid                     baseTypeId;
-               int32           baseTypeMod;
-
-               baseTypeMod = typmod;
-               baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
-               defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod);
-               defval = (Expr *) coerce_to_target_type(NULL,
-                                                                                               (Node *) defval,
-                                                                                               baseTypeId,
-                                                                                               typeOid,
-                                                                                               typmod,
-                                                                                               COERCION_ASSIGNMENT,
-                                                                                               COERCE_IMPLICIT_CAST,
-                                                                                               -1);
-               if (defval == NULL)             /* should not happen */
-                       elog(ERROR, "failed to coerce base type to domain");
-       }
+               if (!defval && GetDomainConstraints(typeOid) != NIL)
+               {
+                       Oid                     baseTypeId;
+                       int32           baseTypeMod;
+
+                       baseTypeMod = typmod;
+                       baseTypeId = getBaseTypeAndTypmod(typeOid, &baseTypeMod);
+                       defval = (Expr *) makeNullConst(baseTypeId, baseTypeMod);
+                       defval = (Expr *) coerce_to_target_type(NULL,
+                                                                                                       (Node *) defval,
+                                                                                                       baseTypeId,
+                                                                                                       typeOid,
+                                                                                                       typmod,
+                                                                                                       COERCION_ASSIGNMENT,
+                                                                                                       COERCE_IMPLICIT_CAST,
+                                                                                                       -1);
+                       if (defval == NULL)             /* should not happen */
+                               elog(ERROR, "failed to coerce base type to domain");
+               }
 
-       if (defval)
-       {
-               NewColumnValue *newval;
+               if (defval)
+               {
+                       NewColumnValue *newval;
 
-               newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
-               newval->attnum = attribute.attnum;
-               newval->expr = defval;
+                       newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
+                       newval->attnum = attribute.attnum;
+                       newval->expr = defval;
 
-               tab->newvals = lappend(tab->newvals, newval);
-       }
+                       tab->newvals = lappend(tab->newvals, newval);
+               }
 
-       /*
-        * If the new column is NOT NULL, tell Phase 3 it needs to test that.
-        */
-       tab->new_notnull |= colDef->is_not_null;
+               /*
+                * If the new column is NOT NULL, tell Phase 3 it needs to test that.
+                */
+               tab->new_notnull |= colDef->is_not_null;
+       }
 
        /*
         * Add needed dependency entries for the new column.
index d527d9ef24ac6b29a921a0acd90ea8bde9b2a6b4..d985ac046456406eebcd8918906500db00eea3f3 100644 (file)
@@ -172,9 +172,34 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
                 */
                Assert(relation->istemp == rel->rd_istemp);
 
+               /*
+                * If new attributes have been added, we must modify the pre-existing
+                * view.
+                */
+               if (list_length(attrList) > rel->rd_att->natts) {
+                       List            *atcmds = NIL;
+                       ListCell        *c;
+                       int                     skip = rel->rd_att->natts;
+
+                       foreach(c, attrList) {
+                               AlterTableCmd *atcmd;
+
+                               if (skip > 0) {
+                                       --skip;
+                                       continue;
+                               }
+                               atcmd = makeNode(AlterTableCmd);
+                               atcmd->subtype = AT_AddColumnToView;
+                               atcmd->def = lfirst(c);
+                               atcmds = lappend(atcmds, atcmd);
+                       }
+                       AlterTableInternal(viewOid, atcmds, true);
+               }
+
                /*
                 * Create a tuple descriptor to compare against the existing view, and
-                * verify it matches.
+                * verify that the old column list is an initial prefix of the new
+                * column list.
                 */
                descriptor = BuildDescForRelation(attrList);
                checkViewTupleDesc(descriptor, rel->rd_att);
@@ -219,13 +244,13 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
 {
        int                     i;
 
-       if (newdesc->natts != olddesc->natts)
+       if (newdesc->natts < olddesc->natts)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                                errmsg("cannot change number of columns in view")));
+                                errmsg("cannot drop columns from view")));
        /* we can ignore tdhasoid */
 
-       for (i = 0; i < newdesc->natts; i++)
+       for (i = 0; i < olddesc->natts; i++)
        {
                Form_pg_attribute newattr = newdesc->attrs[i];
                Form_pg_attribute oldattr = olddesc->attrs[i];
@@ -234,7 +259,7 @@ checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)
                if (newattr->attisdropped != oldattr->attisdropped)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
-                                        errmsg("cannot change number of columns in view")));
+                                        errmsg("cannot drop columns from view")));
 
                if (strcmp(NameStr(newattr->attname), NameStr(oldattr->attname)) != 0)
                        ereport(ERROR,
index ecc40f72c4d216767e548fc00f73140a1f5a4f62..95e26f7de8cdc7cb2f2132cd4b5153dea8a972db 100644 (file)
@@ -1721,6 +1721,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString)
                switch (cmd->subtype)
                {
                        case AT_AddColumn:
+                       case AT_AddColumnToView:
                                {
                                        ColumnDef  *def = (ColumnDef *) cmd->def;
 
index 4ca7653d7ee0b64d59fc115d019c4da8e3d6fe8d..a9ec8e1712e14a062c174dc15d962f0b853a9076 100644 (file)
@@ -989,6 +989,7 @@ typedef struct AlterTableStmt
 typedef enum AlterTableType
 {
        AT_AddColumn,                           /* add column */
+       AT_AddColumnToView,                     /* implicitly via CREATE OR REPLACE VIEW */
        AT_ColumnDefault,                       /* alter column default */
        AT_DropNotNull,                         /* alter column drop not null */
        AT_SetNotNull,                          /* alter column set not null */
index cbee9dceed88f57108bcc441331caede784164e9..e9b6c83e25d883affc095a332b2a83f5ce231029 100644 (file)
@@ -49,15 +49,18 @@ SELECT * FROM viewtest;
 -- should fail
 CREATE OR REPLACE VIEW viewtest AS
        SELECT a FROM viewtest_tbl WHERE a <> 20;
-ERROR:  cannot change number of columns in view
+ERROR:  cannot drop columns from view
 -- should fail
 CREATE OR REPLACE VIEW viewtest AS
        SELECT 1, * FROM viewtest_tbl;
-ERROR:  cannot change number of columns in view
+ERROR:  column "b" of relation "viewtest" already exists
 -- should fail
 CREATE OR REPLACE VIEW viewtest AS
        SELECT a, b::numeric FROM viewtest_tbl;
 ERROR:  cannot change data type of view column "b"
+-- should work 
+CREATE OR REPLACE VIEW viewtest AS
+       SELECT a, b, 0 AS c FROM viewtest_tbl;
 DROP VIEW viewtest;
 DROP TABLE viewtest_tbl;
 -- tests for temporary views
index 30a9bb1152da9731c2bb2a1af1d4b76439404bee..f8942c93f5ce44b6f43dae451127db8125b16a64 100644 (file)
@@ -61,6 +61,10 @@ CREATE OR REPLACE VIEW viewtest AS
 CREATE OR REPLACE VIEW viewtest AS
        SELECT a, b::numeric FROM viewtest_tbl;
 
+-- should work 
+CREATE OR REPLACE VIEW viewtest AS
+       SELECT a, b, 0 AS c FROM viewtest_tbl;
+
 DROP VIEW viewtest;
 DROP TABLE viewtest_tbl;