diff --git a/src/backend/catalog/pg_inherits.c b/src/backend/catalog/pg_inherits.c index e256f6f..fbde33a 100644 --- a/src/backend/catalog/pg_inherits.c +++ b/src/backend/catalog/pg_inherits.c @@ -28,6 +28,7 @@ #include "parser/parse_type.h" #include "storage/lmgr.h" #include "utils/fmgroids.h" +#include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/tqual.h" @@ -190,6 +191,67 @@ find_all_inheritors(Oid parentrelId, LOCKMODE lockmode) return rels_list; } +/* + * find_column_origin + * + * Returns a list of relation OIDs the given column is inherited from. + * In most cases, a column has one origin, unless it (or its parent) + * has multiple ancestors. + * Note that find_column_origin() returns a unique OID for diamond-type + * inheritance trees, because all relations shares the same origin. + */ +List * +find_column_origin(Oid relOid, const char *colName) +{ + Relation inhRel; + SysScanDesc scan; + ScanKeyData skey[1]; + HeapTuple tuple; + List *results = NIL; + + /* + * This relation does not contain the given column, so its + * parent neither. + */ + if (get_attnum(relOid, colName) == InvalidAttrNumber) + return NIL; + + inhRel = heap_open(InheritsRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + Anum_pg_inherits_inhrelid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(relOid)); + + scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, + true, SnapshotNow, 1, skey); + + while (HeapTupleIsValid(tuple = systable_getnext(scan))) + { + Form_pg_inherits inhForm = (Form_pg_inherits) GETSTRUCT(tuple); + List *temp; + + temp = find_column_origin(inhForm->inhparent, colName); + if (temp == NIL) + continue; + + /* + * Add to the queue only those children not already seen. This avoids + * making duplicate entries in case of multiple inheritance paths from + * the same parent. (It'll also keep us from getting into an infinite + * loop, though theoretically there can't be any cycles in the + * inheritance graph anyway.) + */ + results = list_concat_unique_oid(results, temp); + } + systable_endscan(scan); + heap_close(inhRel, AccessShareLock); + + if (results == NIL) + results = list_make1_oid(relOid); + + return results; +} /* * has_subclass - does this relation have any children? diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 10f0a94..4cd988c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2009,6 +2009,17 @@ renameatt(Oid myrelid, errmsg("cannot rename inherited column \"%s\"", oldattname))); + /* + * if the attribute is inherited multiple times, forbid the renaming, + * even if we are already inside a recursive rename, because we + * have no reasonable way to keep its integrity. + */ + if (list_length(find_column_origin(myrelid, oldattname)) > 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + (errmsg("cannot rename multiple inherited column \"%s\"", + oldattname)))); + /* new name should not already exist */ /* this test is deliberately not attisdropped-aware */ @@ -5768,6 +5779,7 @@ ATPrepAlterColumnType(List **wqueue, bool recurse, bool recursing, AlterTableCmd *cmd) { + Oid relOid = RelationGetRelid(rel); char *colName = cmd->name; TypeName *typeName = (TypeName *) cmd->def; HeapTuple tuple; @@ -5803,6 +5815,13 @@ ATPrepAlterColumnType(List **wqueue, errmsg("cannot alter inherited column \"%s\"", colName))); + /* Don't alter multiple inherited columns */ + if (list_length(find_column_origin(relOid, colName)) > 1) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + (errmsg("cannot alter multiple inherited column \"%s\"", + colName)))); + /* Look up the target type */ targettype = typenameTypeId(NULL, typeName, &targettypmod); diff --git a/src/include/catalog/pg_inherits_fn.h b/src/include/catalog/pg_inherits_fn.h index 91baec3..a23f62b 100644 --- a/src/include/catalog/pg_inherits_fn.h +++ b/src/include/catalog/pg_inherits_fn.h @@ -19,6 +19,7 @@ extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode); extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode); +extern List *find_column_origin(Oid relOid, const char *colName); extern bool has_subclass(Oid relationId); extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId); diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 9c83a32..3a049cc 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -1053,3 +1053,81 @@ NOTICE: merging column "a" with inherited definition ERROR: column "a" has a storage parameter conflict DETAIL: MAIN versus EXTENDED DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all; +-- Test for renaming and type changes (simple multiple inheritance) +CREATE TABLE t1 (a int, b int); +CREATE TABLE s1 (b int, c int); +CREATE TABLE ts (d int) INHERITS (t1, s1); +NOTICE: merging multiple inherited definitions of column "b" +ALTER TABLE s1 ALTER b TYPE float; -- to be failed +ERROR: cannot alter multiple inherited column "b" +ALTER TABLE s1 ALTER c TYPE float; +ALTER TABLE ts ALTER c TYPE text; -- to be failed +ERROR: cannot alter inherited column "c" +ALTER TABLE ts ALTER d TYPE text; +ALTER TABLE t1 RENAME a TO aa; +ALTER TABLE t1 RENAME b TO bb; -- to be failed +ERROR: cannot rename multiple inherited column "b" +ALTER TABLE ts RENAME aa TO aaa; -- to be failed +ERROR: cannot rename inherited column "aa" +ALTER TABLE ts RENAME d TO dd; +\d+ ts + Table "public.ts" + Column | Type | Modifiers | Storage | Description +--------+------------------+-----------+----------+------------- + aa | integer | | plain | + b | integer | | plain | + c | double precision | | plain | + dd | text | | extended | +Inherits: t1, + s1 +Has OIDs: no + +DROP TABLE ts; +-- Test for renaming and type changes (diamond inheritance) +CREATE TABLE t2 (x int) INHERITS (t1); +CREATE TABLE t3 (y int) INHERITS (t1); +CREATE TABLE t4 (z int) INHERITS (t2, t3); +NOTICE: merging multiple inherited definitions of column "aa" +NOTICE: merging multiple inherited definitions of column "b" +ALTER TABLE t1 ALTER aa TYPE float; +ALTER TABLE t1 RENAME aa TO aaa; +\d+ t4 + Table "public.t4" + Column | Type | Modifiers | Storage | Description +--------+------------------+-----------+---------+------------- + aaa | double precision | | plain | + b | integer | | plain | + x | integer | | plain | + y | integer | | plain | + z | integer | | plain | +Inherits: t2, + t3 +Has OIDs: no + +CREATE TABLE ts (d int) INHERITS (t2, s1); +NOTICE: merging multiple inherited definitions of column "b" +ALTER TABLE t1 ALTER aaa TYPE text; +ALTER TABLE t1 ALTER b TYPE text; -- to be failed +ERROR: cannot alter multiple inherited column "b" +ALTER TABLE t1 RENAME aaa TO aaaa; +ALTER TABLE t1 RENAME b TO bb; -- to be failed +ERROR: cannot rename multiple inherited column "b" +\d+ ts + Table "public.ts" + Column | Type | Modifiers | Storage | Description +--------+------------------+-----------+----------+------------- + aaaa | text | | extended | + b | integer | | plain | + x | integer | | plain | + c | double precision | | plain | + d | integer | | plain | +Inherits: t2, + s1 +Has OIDs: no + +DROP TABLE t1, s1 CASCADE; +NOTICE: drop cascades to 4 other objects +DETAIL: drop cascades to table t2 +drop cascades to table ts +drop cascades to table t3 +drop cascades to table t4 diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql index 192e860..82ee7d3 100644 --- a/src/test/regress/sql/inherit.sql +++ b/src/test/regress/sql/inherit.sql @@ -334,3 +334,39 @@ CREATE TABLE inh_error1 () INHERITS (t1, t4); CREATE TABLE inh_error2 (LIKE t4 INCLUDING STORAGE) INHERITS (t1); DROP TABLE t1, t2, t3, t4, t12_storage, t12_comments, t1_inh, t13_inh, t13_like, t_all; + +-- Test for renaming and type changes (simple multiple inheritance) +CREATE TABLE t1 (a int, b int); +CREATE TABLE s1 (b int, c int); +CREATE TABLE ts (d int) INHERITS (t1, s1); + +ALTER TABLE s1 ALTER b TYPE float; -- to be failed +ALTER TABLE s1 ALTER c TYPE float; +ALTER TABLE ts ALTER c TYPE text; -- to be failed +ALTER TABLE ts ALTER d TYPE text; + +ALTER TABLE t1 RENAME a TO aa; +ALTER TABLE t1 RENAME b TO bb; -- to be failed +ALTER TABLE ts RENAME aa TO aaa; -- to be failed +ALTER TABLE ts RENAME d TO dd; +\d+ ts + +DROP TABLE ts; + +-- Test for renaming and type changes (diamond inheritance) +CREATE TABLE t2 (x int) INHERITS (t1); +CREATE TABLE t3 (y int) INHERITS (t1); +CREATE TABLE t4 (z int) INHERITS (t2, t3); + +ALTER TABLE t1 ALTER aa TYPE float; +ALTER TABLE t1 RENAME aa TO aaa; +\d+ t4 + +CREATE TABLE ts (d int) INHERITS (t2, s1); +ALTER TABLE t1 ALTER aaa TYPE text; +ALTER TABLE t1 ALTER b TYPE text; -- to be failed +ALTER TABLE t1 RENAME aaa TO aaaa; +ALTER TABLE t1 RENAME b TO bb; -- to be failed +\d+ ts + +DROP TABLE t1, s1 CASCADE;