Code review for CREATE OR REPLACE VIEW patch. Do things in a saner order to

result in hopefully-less-confusing error messages when the new definition
isn't compatible with the old; minor other cleanup.
This commit is contained in:
Tom Lane 2008-12-15 21:35:31 +00:00
parent 78b25fd2e9
commit 4da65a23e7
4 changed files with 56 additions and 46 deletions

View File

@ -1,5 +1,5 @@
<!-- <!--
$PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.38 2008/12/06 23:22:46 momjian Exp $ $PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.39 2008/12/15 21:35:31 tgl Exp $
PostgreSQL documentation PostgreSQL documentation
--> -->
@ -38,9 +38,10 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
<para> <para>
<command>CREATE OR REPLACE VIEW</command> is similar, but if a view <command>CREATE OR REPLACE VIEW</command> is similar, but if a view
of the same name already exists, it is replaced. The new query must 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 generate the same columns that were generated by the existing view query
in the same order and with the same data types, but may add additional (that is, the same column names in the same order and with the same data
columns to the end of the list. types), but it may add additional columns to the end of the list. The
calculations giving rise to the output columns may be completely different.
</para> </para>
<para> <para>
@ -77,7 +78,7 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry> <varlistentry>
<term><replaceable class="parameter">name</replaceable></term> <term><replaceable class="parameter">name</replaceable></term>
<listitem> <listitem>
@ -164,7 +165,7 @@ CREATE VIEW comedies AS
</programlisting> </programlisting>
</para> </para>
</refsect1> </refsect1>
<refsect1> <refsect1>
<title>Compatibility</title> <title>Compatibility</title>

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.273 2008/12/13 19:13:44 tgl Exp $ * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.274 2008/12/15 21:35:31 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -3459,9 +3459,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
attrdesc; attrdesc;
HeapTuple reltup; HeapTuple reltup;
FormData_pg_attribute attribute; FormData_pg_attribute attribute;
int i; int newattnum;
int minattnum,
maxatts;
char relkind; char relkind;
HeapTuple typeTuple; HeapTuple typeTuple;
Oid typeOid; Oid typeOid;
@ -3520,6 +3518,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
0, 0, 0); 0, 0, 0);
if (!HeapTupleIsValid(reltup)) if (!HeapTupleIsValid(reltup))
elog(ERROR, "cache lookup failed for relation %u", myrelid); elog(ERROR, "cache lookup failed for relation %u", myrelid);
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind;
/* /*
* this test is deliberately not attisdropped-aware, since if one tries to * this test is deliberately not attisdropped-aware, since if one tries to
@ -3534,15 +3533,12 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
errmsg("column \"%s\" of relation \"%s\" already exists", errmsg("column \"%s\" of relation \"%s\" already exists",
colDef->colname, RelationGetRelationName(rel)))); colDef->colname, RelationGetRelationName(rel))));
minattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts; newattnum = ((Form_pg_class) GETSTRUCT(reltup))->relnatts + 1;
relkind = ((Form_pg_class) GETSTRUCT(reltup))->relkind; if (newattnum > MaxHeapAttributeNumber)
maxatts = minattnum + 1;
if (maxatts > MaxHeapAttributeNumber)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_TOO_MANY_COLUMNS), (errcode(ERRCODE_TOO_MANY_COLUMNS),
errmsg("tables can have at most %d columns", errmsg("tables can have at most %d columns",
MaxHeapAttributeNumber))); MaxHeapAttributeNumber)));
i = minattnum + 1;
typeTuple = typenameType(NULL, colDef->typename, &typmod); typeTuple = typenameType(NULL, colDef->typename, &typmod);
tform = (Form_pg_type) GETSTRUCT(typeTuple); tform = (Form_pg_type) GETSTRUCT(typeTuple);
@ -3551,6 +3547,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
/* make sure datatype is legal for a column */ /* make sure datatype is legal for a column */
CheckAttributeType(colDef->colname, typeOid); CheckAttributeType(colDef->colname, typeOid);
/* construct new attribute's pg_attribute entry */
attribute.attrelid = myrelid; attribute.attrelid = myrelid;
namestrcpy(&(attribute.attname), colDef->colname); namestrcpy(&(attribute.attname), colDef->colname);
attribute.atttypid = typeOid; attribute.atttypid = typeOid;
@ -3558,7 +3555,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
attribute.attlen = tform->typlen; attribute.attlen = tform->typlen;
attribute.attcacheoff = -1; attribute.attcacheoff = -1;
attribute.atttypmod = typmod; attribute.atttypmod = typmod;
attribute.attnum = i; attribute.attnum = newattnum;
attribute.attbyval = tform->typbyval; attribute.attbyval = tform->typbyval;
attribute.attndims = list_length(colDef->typename->arrayBounds); attribute.attndims = list_length(colDef->typename->arrayBounds);
attribute.attstorage = tform->typstorage; attribute.attstorage = tform->typstorage;
@ -3578,7 +3575,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
/* /*
* Update number of attributes in pg_class tuple * Update number of attributes in pg_class tuple
*/ */
((Form_pg_class) GETSTRUCT(reltup))->relnatts = maxatts; ((Form_pg_class) GETSTRUCT(reltup))->relnatts = newattnum;
simple_heap_update(pgclass, &reltup->t_self, reltup); simple_heap_update(pgclass, &reltup->t_self, reltup);
@ -3635,9 +3632,13 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
* returned by AddRelationNewConstraints, so that the right thing happens * returned by AddRelationNewConstraints, so that the right thing happens
* when a datatype's default applies. * when a datatype's default applies.
* *
* We skip this logic completely for views. * We skip this step completely for views. For a view, we can only get
* here from CREATE OR REPLACE VIEW, which historically doesn't set up
* defaults, not even for domain-typed columns. And in any case we mustn't
* invoke Phase 3 on a view, since it has no storage.
*/ */
if (relkind != RELKIND_VIEW) { if (relkind != RELKIND_VIEW)
{
defval = (Expr *) build_column_default(rel, attribute.attnum); defval = (Expr *) build_column_default(rel, attribute.attnum);
if (!defval && GetDomainConstraints(typeOid) != NIL) if (!defval && GetDomainConstraints(typeOid) != NIL)
@ -3680,7 +3681,7 @@ ATExecAddColumn(AlteredTableInfo *tab, Relation rel,
/* /*
* Add needed dependency entries for the new column. * Add needed dependency entries for the new column.
*/ */
add_column_datatype_dependency(myrelid, i, attribute.atttypid); add_column_datatype_dependency(myrelid, newattnum, attribute.atttypid);
} }
/* /*

View File

@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.108 2008/12/06 23:22:46 momjian Exp $ * $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.109 2008/12/15 21:35:31 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
@ -165,6 +165,9 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS,
RelationGetRelationName(rel)); RelationGetRelationName(rel));
/* Also check it's not in use already */
CheckTableNotInUse(rel, "CREATE OR REPLACE VIEW");
/* /*
* Due to the namespace visibility rules for temporary objects, we * Due to the namespace visibility rules for temporary objects, we
* should only end up replacing a temporary view with another * should only end up replacing a temporary view with another
@ -172,30 +175,6 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
*/ */
Assert(relation->istemp == rel->rd_istemp); 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 * Create a tuple descriptor to compare against the existing view, and
* verify that the old column list is an initial prefix of the new * verify that the old column list is an initial prefix of the new
@ -204,6 +183,34 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
descriptor = BuildDescForRelation(attrList); descriptor = BuildDescForRelation(attrList);
checkViewTupleDesc(descriptor, rel->rd_att); checkViewTupleDesc(descriptor, rel->rd_att);
/*
* If new attributes have been added, we must add pg_attribute entries
* for them. It is convenient (although overkill) to use the ALTER
* TABLE ADD COLUMN infrastructure for this.
*/
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 = (Node *) lfirst(c);
atcmds = lappend(atcmds, atcmd);
}
AlterTableInternal(viewOid, atcmds, true);
}
/* /*
* Seems okay, so return the OID of the pre-existing view. * Seems okay, so return the OID of the pre-existing view.
*/ */
@ -238,6 +245,7 @@ DefineVirtualRelation(const RangeVar *relation, List *tlist, bool replace)
* Verify that tupledesc associated with proposed new view definition * Verify that tupledesc associated with proposed new view definition
* matches tupledesc of old view. This is basically a cut-down version * matches tupledesc of old view. This is basically a cut-down version
* of equalTupleDescs(), with code added to generate specific complaints. * of equalTupleDescs(), with code added to generate specific complaints.
* Also, we allow the new tupledesc to have more columns than the old.
*/ */
static void static void
checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc) checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc)

View File

@ -53,7 +53,7 @@ ERROR: cannot drop columns from view
-- should fail -- should fail
CREATE OR REPLACE VIEW viewtest AS CREATE OR REPLACE VIEW viewtest AS
SELECT 1, * FROM viewtest_tbl; SELECT 1, * FROM viewtest_tbl;
ERROR: column "b" of relation "viewtest" already exists ERROR: cannot change name of view column "a"
-- should fail -- should fail
CREATE OR REPLACE VIEW viewtest AS CREATE OR REPLACE VIEW viewtest AS
SELECT a, b::numeric FROM viewtest_tbl; SELECT a, b::numeric FROM viewtest_tbl;