diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml index 07c8f805b7..7cbbdbe1ce 100644 --- a/doc/src/sgml/ref/create_view.sgml +++ b/doc/src/sgml/ref/create_view.sgml @@ -1,5 +1,5 @@ @@ -37,9 +37,10 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW n CREATE OR REPLACE VIEW 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. diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index d252632ee7..8e12a77d11 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.271 2008/11/19 10:34:51 heikki Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.272 2008/12/06 23:22:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -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,45 +3634,49 @@ 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; + 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"); + 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; + + newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); + newval->attnum = attribute.attnum; + newval->expr = defval; + + 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 (defval) - { - NewColumnValue *newval; - - newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue)); - newval->attnum = attribute.attnum; - newval->expr = defval; - - 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; - /* * Add needed dependency entries for the new column. */ diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 5d207861dc..23a03d28a9 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.107 2008/08/25 22:42:32 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.108 2008/12/06 23:22:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -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, diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 4e0efe84bb..bb3a9142d6 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -19,7 +19,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.17 2008/09/01 20:42:45 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_utilcmd.c,v 2.18 2008/12/06 23:22:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -1721,6 +1721,7 @@ transformAlterTableStmt(AlterTableStmt *stmt, const char *queryString) switch (cmd->subtype) { case AT_AddColumn: + case AT_AddColumnToView: { ColumnDef *def = (ColumnDef *) cmd->def; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 828c959ef2..47e3f37206 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -13,7 +13,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.381 2008/12/04 17:51:27 petere Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.382 2008/12/06 23:22:46 momjian Exp $ * *------------------------------------------------------------------------- */ @@ -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 */ diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out index cbee9dceed..e9b6c83e25 100644 --- a/src/test/regress/expected/create_view.out +++ b/src/test/regress/expected/create_view.out @@ -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 diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql index 30a9bb1152..f8942c93f5 100644 --- a/src/test/regress/sql/create_view.sql +++ b/src/test/regress/sql/create_view.sql @@ -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;