pgsql-fix-inherit-rename.6.patch
application/octect-stream
Filename: pgsql-fix-inherit-rename.6.patch
Type: application/octect-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: context
| File | + | − |
|---|---|---|
| src/backend/catalog/pg_inherits.c | 94 | 0 |
| src/backend/commands/alter.c | 1 | 0 |
| src/backend/commands/tablecmds.c | 94 | 0 |
| src/include/catalog/pg_inherits_fn.h | 4 | 0 |
| src/include/commands/tablecmds.h | 1 | 0 |
| src/test/regress/expected/inherit.out | 103 | 0 |
| src/test/regress/sql/inherit.sql | 46 | 0 |
*** a/src/backend/catalog/pg_inherits.c
--- b/src/backend/catalog/pg_inherits.c
***************
*** 158,164 **** List *
find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
{
List *rels_list;
! ListCell *l;
/*
* We build a list starting with the given rel and adding all direct and
--- 158,212 ----
find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
{
List *rels_list;
!
! find_all_inheritors_with_inhcount(parentrelId, lockmode,
! &rels_list, NULL);
!
! return rels_list;
! }
!
! /*
! * find_all_inheritors_with_inhcount
! *
! * It returns a list of relations OIDs including the given rel plus
! * all relations that inherit from it, directly or indirectly.
! * In addition, it also returns the expected inhcount for them.
! *
! * The expected inhcount means the number of times a certain relation being
! * inherited from any other relations which were inherited from the root
! * of inheritance tree directly or indirectly.
! *
! * It is used to control some of ALTER TABLE operations on columns inherited
! * from a relation. For example, an inherited column must has same name
! * within the series of inheritance. So, we need to prevent renaming when
! * the column to be renamed is delivered from multiple independent relations.
! *
! * The specified lock type is acquired on all child relations (but not on the
! * given rel; caller should already have locked it). If lockmode is NoLock
! * then no locks are acquired, but caller must beware of race conditions
! * against possible DROPs of child relations.
! *
! * T2 For example, T2 and T3 are inherited from T1, T4 is inherited
! * / \ from T2 and T3. And, T5 is inherited from T3 and S1.
! * T1 T4 When we try to rename a column within T1, it needs to rename
! * \ / inherited column in the child relations also.
! * T3-T5 If attinhcount of the column is larger than its expected inhcount,
! * / it means the column is delivered from the different origin, so
! * S1 we have to prevent this operations to keep database integrity.
! *
! * In this example, the expected inhcount is 1 in T2, T3 and T5, and 2 in T4.
! * The column inherited from T1 has attinhcount = 2 in T4, so it does not
! * prevent renaming.
! *
! * If the column inherited from T1 has attinhcount = 2 in T5, it means the
! * column was also inherited from S1, so we can prevent renaming it.
! */
! void
! find_all_inheritors_with_inhcount(Oid parentrelId, LOCKMODE lockmode,
! List **child_oids, List **child_inhs)
! {
! List *rels_oids, *rels_inhs;
! ListCell *cell;
/*
* We build a list starting with the given rel and adding all direct and
***************
*** 166,196 **** find_all_inheritors(Oid parentrelId, LOCKMODE lockmode)
* already-found rels and the agenda of rels yet to be scanned for more
* children. This is a bit tricky but works because the foreach() macro
* doesn't fetch the next list element until the bottom of the loop.
*/
! rels_list = list_make1_oid(parentrelId);
! foreach(l, rels_list)
{
! Oid currentrel = lfirst_oid(l);
! List *currentchildren;
/* Get the direct children of this rel */
! currentchildren = find_inheritance_children(currentrel, lockmode);
! /*
! * 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.)
! */
! rels_list = list_concat_unique_oid(rels_list, currentchildren);
}
! return rels_list;
}
-
/*
* has_subclass - does this relation have any children?
*
--- 214,274 ----
* already-found rels and the agenda of rels yet to be scanned for more
* children. This is a bit tricky but works because the foreach() macro
* doesn't fetch the next list element until the bottom of the loop.
+ *
+ * The root of the inheritance tree always has 0 as an expected inhcount,
+ * because its parent, even if exists, it is never contained within the
+ * inheritance tree originally delivered from the root.
*/
! rels_oids = list_make1_oid(parentrelId);
! rels_inhs = list_make1_int(0);
! foreach (cell, rels_oids)
{
! Oid cur_relid = lfirst_oid(cell);
! Oid temp_relid;
! List *temp;
! ListCell *lt, *lo, *li;
/* Get the direct children of this rel */
! temp = find_inheritance_children(cur_relid, lockmode);
! foreach (lt, temp)
! {
! Oid temp_relid = lfirst_oid(lt);
! bool found = false;
!
! /*
! * 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.)
! */
! forboth (lo, rels_oids, li, rels_inhs)
! {
! if (lfirst_oid(lo) == temp_relid)
! {
! /* increment its expected inhcount */
! lfirst_int(li)++;
! found = true;
! break;
! }
! }
!
! if (!found)
! {
! rels_oids = lappend_oid(rels_oids, temp_relid);
! rels_inhs = lappend_int(rels_inhs, 1);
! }
! }
}
! if (child_oids)
! *child_oids = rels_oids;
! if (child_inhs)
! *child_inhs = rels_inhs;
}
/*
* has_subclass - does this relation have any children?
*
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 125,130 **** ExecRenameStmt(RenameStmt *stmt)
--- 125,131 ----
renameatt(relid,
stmt->subname, /* old att name */
stmt->newname, /* new att name */
+ 0, /* expected inhcount */
interpretInhOption(stmt->relation->inhOpt), /* recursive? */
false); /* recursing already? */
break;
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 1906,1911 **** void
--- 1906,1912 ----
renameatt(Oid myrelid,
const char *oldattname,
const char *newattname,
+ int expected_inhcount,
bool recurse,
bool recursing)
{
***************
*** 1946,1969 **** renameatt(Oid myrelid,
*/
if (recurse)
{
! ListCell *child;
! List *children;
!
! children = find_all_inheritors(myrelid, AccessExclusiveLock);
/*
! * find_all_inheritors does the recursive search of the inheritance
! * hierarchy, so all we have to do is process all of the relids in the
! * list that it returns.
*/
! foreach(child, children)
{
! Oid childrelid = lfirst_oid(child);
! if (childrelid == myrelid)
continue;
/* note we need not recurse again */
! renameatt(childrelid, oldattname, newattname, false, true);
}
}
else
--- 1947,1978 ----
*/
if (recurse)
{
! List *child_oids, *child_inhs;
! ListCell *lo, *li;
/*
! * It is same as find_all_inheritors, except for it returns the
! * list of expected inhcount for each child relations.
! * It is a value to be set on pg_attribute.inhcount when the column
! * to be renamed is not merged with any other column which have
! * different origin.
! * If pg_attribute.inhcount is larger than the expected inhcount,
! * it means the column is merged from different serieses.
*/
! find_all_inheritors_with_inhcount(myrelid, AccessExclusiveLock,
! &child_oids, &child_inhs);
!
! forboth (lo, child_oids, li, child_inhs)
{
! Oid child_relid = lfirst_oid(lo);
! int child_inhcount = lfirst_int(li);
! if (child_relid == myrelid)
continue;
+
/* note we need not recurse again */
! renameatt(child_relid, oldattname, newattname,
! child_inhcount, false, true);
}
}
else
***************
*** 2007,2012 **** renameatt(Oid myrelid,
--- 2016,2032 ----
errmsg("cannot rename inherited column \"%s\"",
oldattname)));
+ /*
+ * if the attribute is multiple inherited, forbid the renaming,
+ * even if we are already inside a recursive rename, because we
+ * have no reasonable way to keep its integrity.
+ */
+ if (attform->attinhcount > expected_inhcount)
+ 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 */
***************
*** 5772,5782 **** ATPrepAlterColumnType(List **wqueue,
colName)));
/* Don't alter inherited columns */
! if (attTup->attinhcount > 0 && !recursing)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("cannot alter inherited column \"%s\"",
! colName)));
/* Look up the target type */
targettype = typenameTypeId(NULL, typeName, &targettypmod);
--- 5792,5820 ----
colName)));
/* Don't alter inherited columns */
! if (!recursing)
! {
! if (attTup->attinhcount > 0)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
! errmsg("cannot alter inherited column \"%s\"",
! colName)));
! }
! else
! {
! /*
! * The TypeName->location is not used in this code path,
! * so we utilize it to store an enpected inhconunt for each
! * child relations.
! */
! int expected_inhcount = typeName->location;
!
! if (attTup->attinhcount > expected_inhcount)
! 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);
***************
*** 5858,5869 **** ATPrepAlterColumnType(List **wqueue,
ReleaseSysCache(tuple);
/*
! * The recursion case is handled by ATSimpleRecursion. However, if we are
! * told not to recurse, there had better not be any child tables; else the
! * alter would put them out of step.
*/
if (recurse)
! ATSimpleRecursion(wqueue, rel, cmd, recurse);
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
ereport(ERROR,
--- 5896,5943 ----
ReleaseSysCache(tuple);
/*
! * The recursion case is not handled by ATSimpleRecursion, because we need
! * to compute an expected inhcount for each child relations. This value is
! * delivered to the recursive invocation to check unexpected type changes.
! * If we are told not to recurse, there had better not be any child tables;
! * else the alter would put them out of step.
*/
if (recurse)
! {
! Relation child_rel;
! List *child_oids;
! List *child_inhs;
! ListCell *lo;
! ListCell *li;
!
! find_all_inheritors_with_inhcount(RelationGetRelid(rel),
! AccessExclusiveLock,
! &child_oids, &child_inhs);
! forboth(lo, child_oids, li, child_inhs)
! {
! AlterTableCmd *child_cmd;
! Relation child_rel;
! Oid child_relid = lfirst_oid(lo);
! int child_inhcount = lfirst_int(li);
!
! if (child_relid == RelationGetRelid(rel))
! continue;
!
! /*
! * The TypeName->location is not used in this code path,
! * so we utilize it to store an expected inhcount for each
! * child relation.
! */
! child_cmd = copyObject(cmd);
! ((TypeName *)child_cmd->def)->location = child_inhcount;
!
! /* find_all_inheritors_with_inhcount already got lock */
! child_rel = relation_open(child_relid, NoLock);
! CheckTableNotInUse(child_rel, "ALTER TABLE");
! ATPrepCmd(wqueue, child_rel, child_cmd, false, true);
! relation_close(child_rel, NoLock);
! }
! }
else if (!recursing &&
find_inheritance_children(RelationGetRelid(rel), NoLock) != NIL)
ereport(ERROR,
*** a/src/include/catalog/pg_inherits_fn.h
--- b/src/include/catalog/pg_inherits_fn.h
***************
*** 19,24 ****
--- 19,28 ----
extern List *find_inheritance_children(Oid parentrelId, LOCKMODE lockmode);
extern List *find_all_inheritors(Oid parentrelId, LOCKMODE lockmode);
+ extern void find_all_inheritors_with_inhcount(Oid parentrelId,
+ LOCKMODE lockmode,
+ List **child_oids,
+ List **child_inhs);
extern bool has_subclass(Oid relationId);
extern bool typeInheritsFrom(Oid subclassTypeId, Oid superclassTypeId);
*** a/src/include/commands/tablecmds.h
--- b/src/include/commands/tablecmds.h
***************
*** 42,47 **** extern void ExecuteTruncate(TruncateStmt *stmt);
--- 42,48 ----
extern void renameatt(Oid myrelid,
const char *oldattname,
const char *newattname,
+ int expected_inhcount,
bool recurse,
bool recursing);
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
***************
*** 1053,1055 **** NOTICE: merging column "a" with inherited definition
--- 1053,1158 ----
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
+
+ WITH RECURSIVE r AS (
+ SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+ SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+ FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+ WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+ JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+ attrelid | attname | attinhcount | expected
+ ----------+---------+-------------+----------
+ t2 | b | 1 | 1
+ t3 | b | 1 | 1
+ t4 | b | 2 | 2
+ t4 | x | 1 | 2
+ t4 | y | 1 | 2
+ t2 | aaaa | 1 | 1
+ t3 | aaaa | 1 | 1
+ t4 | aaaa | 2 | 2
+ ts | b | 2 | 1
+ ts | x | 1 | 1
+ ts | c | 1 | 1
+ ts | aaaa | 1 | 1
+ (12 rows)
+
+ 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
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
***************
*** 334,336 **** CREATE TABLE inh_error1 () INHERITS (t1, t4);
--- 334,382 ----
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
+
+ WITH RECURSIVE r AS (
+ SELECT 't1'::regclass AS inhrelid
+ UNION ALL
+ SELECT c.inhrelid FROM pg_inherits c, r WHERE r.inhrelid = c.inhparent
+ )
+ SELECT attrelid::regclass, attname, attinhcount, r.expected
+ FROM (SELECT inhrelid, count(*) AS expected FROM pg_inherits
+ WHERE inhparent IN (SELECT inhrelid FROM r) GROUP BY inhrelid) r
+ JOIN pg_attribute a ON r.inhrelid = a.attrelid WHERE attislocal = false;
+
+ DROP TABLE t1, s1 CASCADE;