On Thu, Dec 21, 2023 at 4:32 PM Peter Eisentraut <pe...@eisentraut.org> wrote: > > On 19.12.23 11:47, Ashutosh Bapat wrote: > > At this point I am looking for opinions on the above rules and whether > > the implementation is on the right track. > > This looks on the right track to me.
Thanks. > > > 0001 - change to get_partition_ancestors() prologue. Can be reviewed > > and committed independent of other patches. > > I committed that. Thanks. > > > 0004 - An attached partition inherits identity property and uses the > > underlying sequence for direct INSERTs. When inheriting the identity > > property it should also inherit the NOT NULL constraint, but that's a > > TODO in this patch. We expect matching NOT NULL constraints to be > > present in the partition being attached. I am not sure whether we want > > to add NOT NULL constraints automatically for an identity column. We > > require a NOT NULL constraint to be present when adding identity > > property to a column. The behavior in the patch seems to be consistent > > with this. > > I think it makes sense that the NOT NULL constraint must be added > manually before attaching is allowed. > Ok. I have modified the test case to add NOT NULL constraint. Here's complete patch-set. 0001 - fixes unrelated documentation style - can be committed independently OR ignored 0002 - adds an Assert in related code - can be independently committed On Mon, Nov 13, 2023 at 3:51 PM Peter Eisentraut <pe...@eisentraut.org> wrote: > Note, here is a writeup about the behavior of generated columns with > partitioning: > https://www.postgresql.org/docs/devel/ddl-generated-columns.html. It > would be useful if we documented the behavior of identity columns > similarly. (I'm not saying the behavior has to match.) 0003 - addresses this request 0004 - 0011 - each patch contains code changes and SQL testing those changes for ease of review. Each patch has commit message that describes the changes and rationale, if any, behind those changes. 0012 - test changes 0013 - expected output change because of code changes All these patches should be committed as a single commit finally. Please let me know when I can squash those all together. We may commit 0003 separately or along with 0004-0013. 0014 and 0015 - pg_dump/restore and pg_upgrade tests. But these patches are not expected to be committed for the reasons explained in the commit message. Since identity columns of a partitioned table are not marked as such in partitions in the older version, I tested their upgrade from PG 14 through the changes in 0015. pg_dumpall_14.out contains the dump file from PG 14 I used for this testing. -- Best Wishes, Ashutosh Bapat
From fee416ae2141019b7370a2ae31fc3d927d639977 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Tue, 9 Jan 2024 15:42:03 +0530 Subject: [PATCH 03/27] Add Identity Column section under Data Definition chapter This seems to be missing since identity column support was added. Add the same. Ashutosh Bapat --- doc/src/sgml/ddl.sgml | 56 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index bb6ce9291e..b33c35e141 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -404,6 +404,62 @@ CREATE TABLE people ( </para> </sect1> + <sect1 id="ddl-identity-columns"> + <title>Identity Columns</title> + + <indexterm zone="ddl-identity-columns"> + <primary>identity column</primary> + </indexterm> + + <para> + An identity column is a special column that is generated automatically. It + can be used to generate key values. In <productname>PostgreSQL</productname> + each identity column has an associated sequence from which it derives its + values. Such a column is implicitly NOT NULL. An identity column, however, + does not guarantee uniqueness. Uniqueness must be enforced using a + <literal>PRIMARY KEY</literal> or <literal>UNIQUE</literal> constraint or a + <literal>UNIQUE</literal> index. + </para> + + <para> + To create an identity column, use the <literal>GENERATED ... + AS IDENTITY</literal> clause in <command>CREATE TABLE</command>, for example: +<programlisting> +CREATE TABLE people ( + id bigint <emphasis>GENERATED ALWAYS AS IDENTITY</emphasis>, + ..., +); +</programlisting> + See <xref linkend="sql-createtable"/> for more details. The data type of an + identity column must be one of the data types supported by sequences. See + <xref linkend="sql-createsequence"/>. The properties of associated sequence + may be specified when creating an identity column (See <xref + linkend="sql-createtable"/>) or changed afterwards (See <xref + linkend="sql-altertable"/>). + </para> + + <para> + In <command>INSERT</command> or <command>UPDATE</command> commands, the + keyword <literal>DEFAULT</literal> may be used, if necessary, to specify + system generated value. + </para> + + <para> + Identity columns and their properties in a child table are independent of + those in its parent tables. A child table does not inherit identity columns + or their properties automatically from the parent. During insert or update, a + column is treated as an identity column if that column is an identity column + in the table named in the query and corresponding identity properties are + applied. + </para> + + <para> + Partitions inherit indentity columns from the partitioned table. They cannot + have their own identity columns. The properties of a given identity column + are consistent across all the partitions in the partition hierarchy. + </para> + </sect1> + <sect1 id="ddl-constraints"> <title>Constraints</title> -- 2.25.1
From 428d997b9f0172d1cdea02a8946843dcf474c759 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Thu, 7 Dec 2023 11:38:07 +0530 Subject: [PATCH 02/27] Assert that partition inherits from only one parent in MergeAttributes() A partition inherits only from one partitioned table and thus inherits column definition only once. Assert the same in MergeAttributes() and simplify a condition accordingly. Similar definition exists about line 3068 in the same function. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6b0a20010e..18046a0d69 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2712,6 +2712,12 @@ MergeAttributes(List *columns, const List *supers, char relpersistence, int32 deftypmod; Oid defCollId; + /* + * Partitions have only one parent and have no column + * definitions of their own, so conflict should never occur. + */ + Assert(!is_partition); + /* * Yes, try to merge the two column definitions. */ @@ -2783,12 +2789,9 @@ MergeAttributes(List *columns, const List *supers, char relpersistence, /* * In regular inheritance, columns in the parent's primary key - * get an extra not-null constraint. Partitioning doesn't - * need this, because the PK itself is going to be cloned to - * the partition. + * get an extra not-null constraint. */ - if (!is_partition && - bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber, + if (bms_is_member(parent_attno - FirstLowInvalidHeapAttributeNumber, pkattrs)) { CookedConstraint *nn; -- 2.25.1
From 4d2dbe95f3c5267d7ddf7144329c5a98d95842f9 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Wed, 6 Dec 2023 16:08:59 +0530 Subject: [PATCH 05/27] Attached partition inherits identity column A table being attached as a partition inherits identity property from the partitioned table. This should be fine since we expect that the partition table's column has the same type as the partitioned table's corresponding column. If the table being attached is a partitioned table, the indentity properties are propagated down its partition hierarchy. An Identity column in the partitioned table is also marked as NOT NULL. The corresponding column in the partition needs to be marked as NOT NULL for the attach to succeed. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 25 ++++++++++++++++++------- src/test/regress/expected/identity.out | 23 ++++++++++++++++++----- src/test/regress/sql/identity.sql | 9 +++++++++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 06f6301a57..5719df2b76 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -354,7 +354,8 @@ static List *MergeAttributes(List *columns, const List *supers, char relpersiste bool is_partition, List **supconstr, List **supnotnulls); static List *MergeCheckConstraint(List *constraints, const char *name, Node *expr); -static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel); +static void MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, + bool ispartition); static void MergeConstraintsIntoExisting(Relation child_rel, Relation parent_rel); static void StoreCatalogInheritance(Oid relationId, List *supers, bool child_is_partition); @@ -618,7 +619,8 @@ static PartitionSpec *transformPartitionSpec(Relation rel, PartitionSpec *partsp static void ComputePartitionAttrs(ParseState *pstate, Relation rel, List *partParams, AttrNumber *partattrs, List **partexprs, Oid *partopclass, Oid *partcollation, PartitionStrategy strategy); -static void CreateInheritance(Relation child_rel, Relation parent_rel); +static void CreateInheritance(Relation child_rel, Relation parent_rel, + bool ispartition); static void RemoveInheritance(Relation child_rel, Relation parent_rel, bool expect_detached); static void ATInheritAdjustNotNulls(Relation parent_rel, Relation child_rel, @@ -15647,7 +15649,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) errdetail("ROW triggers with transition tables are not supported in inheritance hierarchies."))); /* OK to create inheritance */ - CreateInheritance(child_rel, parent_rel); + CreateInheritance(child_rel, parent_rel, false); /* * If parent_rel has a primary key, then child_rel has not-null @@ -15673,7 +15675,7 @@ ATExecAddInherit(Relation child_rel, RangeVar *parent, LOCKMODE lockmode) * Common to ATExecAddInherit() and ATExecAttachPartition(). */ static void -CreateInheritance(Relation child_rel, Relation parent_rel) +CreateInheritance(Relation child_rel, Relation parent_rel, bool ispartition) { Relation catalogRelation; SysScanDesc scan; @@ -15718,7 +15720,7 @@ CreateInheritance(Relation child_rel, Relation parent_rel) systable_endscan(scan); /* Match up the columns and bump attinhcount as needed */ - MergeAttributesIntoExisting(child_rel, parent_rel); + MergeAttributesIntoExisting(child_rel, parent_rel, ispartition); /* Match up the constraints and bump coninhcount as needed */ MergeConstraintsIntoExisting(child_rel, parent_rel); @@ -15796,7 +15798,8 @@ constraints_equivalent(HeapTuple a, HeapTuple b, TupleDesc tupleDesc) * the child must be as well. Defaults are not compared, however. */ static void -MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) +MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel, + bool ispartition) { Relation attrrel; TupleDesc parent_desc; @@ -15865,6 +15868,14 @@ MergeAttributesIntoExisting(Relation child_rel, Relation parent_rel) (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("column \"%s\" in child table must not be a generated column", parent_attname))); + /* + * Regular inheritance children are independent enough not to + * inherit identity columns. But partitions are integral part of a + * partitioned table and inherit identity column. + */ + if (ispartition) + child_att->attidentity = parent_att->attidentity; + /* * OK, bump the child column's inheritance count. (If we fail * later on, this change will just roll back.) @@ -18696,7 +18707,7 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, cmd->bound, pstate); /* OK to create inheritance. Rest of the checks performed there */ - CreateInheritance(attachrel, rel); + CreateInheritance(attachrel, rel, true); /* Update the pg_class entry. */ StorePartitionBound(attachrel, rel, cmd->bound); diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 20803a1d24..222b3684da 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -542,15 +542,28 @@ DROP TYPE itest_type CASCADE; -- table partitions -- partitions inherit identity column and share sequence CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); +-- new partition CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1'); +-- attached partition +CREATE TABLE pitest1_p2 (f1 date NOT NULL, f2 text, f3 bigint); +INSERT INTO pitest1_p2 VALUES ('2016-08-2', 'before attaching', 100); +ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -- requires NOT NULL constraint +ERROR: column "f3" in child table must be marked NOT NULL +ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET NOT NULL; +ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest1_p2'); +INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-4', 'from pitest1'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; - tableoid | f1 | f2 | f3 -------------+------------+-----------------+---- - pitest1_p1 | 07-02-2016 | from pitest1 | 1 - pitest1_p1 | 07-03-2016 | from pitest1_p1 | 2 -(2 rows) + tableoid | f1 | f2 | f3 +------------+------------+------------------+----- + pitest1_p1 | 07-02-2016 | from pitest1 | 1 + pitest1_p1 | 07-03-2016 | from pitest1_p1 | 2 + pitest1_p2 | 08-02-2016 | before attaching | 100 + pitest1_p2 | 08-03-2016 | from pitest1_p2 | 3 + pitest1_p2 | 08-04-2016 | from pitest1 | 4 +(5 rows) -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index 3660fa6a28..be29cbf119 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -335,9 +335,18 @@ DROP TYPE itest_type CASCADE; -- partitions inherit identity column and share sequence CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); +-- new partition CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1'); +-- attached partition +CREATE TABLE pitest1_p2 (f1 date NOT NULL, f2 text, f3 bigint); +INSERT INTO pitest1_p2 VALUES ('2016-08-2', 'before attaching', 100); +ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); -- requires NOT NULL constraint +ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET NOT NULL; +ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest1_p2'); +INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-4', 'from pitest1'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; -- partition with identity column of its own is not allowed -- 2.25.1
From 18ac85e9467038ec4cce03162f2d91e2efaa6bc4 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Tue, 5 Dec 2023 17:08:11 +0530 Subject: [PATCH 04/27] A newly created partition inherits indentity property The partitions of a partitioned table are integral part of the partitioned table. A partition inherits identity columns from the partitioned table. An indentity column of a partition shares the identity space with the corresponding column of the partitioned table. In other words, the same identity column across all partitions of a partitioned table share the same identity space. This is effected by sharing the same underlying sequence. When INSERTing directly into a partition, sequence associated with the topmost partitioned table is used to calculate the value of the corresponding identity column. In regular inheritance, Identity columns and their properties in a child table are independent of those in its parent tables. A child table does not inherit identity columns or their properties automatically from the parent. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 9 +++++++++ src/backend/rewrite/rewriteHandler.c | 19 ++++++++++++++++++- src/test/regress/expected/identity.out | 15 ++++++++++++++- src/test/regress/sql/identity.sql | 10 +++++++++- 4 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 18046a0d69..06f6301a57 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2855,6 +2855,15 @@ MergeAttributes(List *columns, const List *supers, char relpersistence, def->is_not_null = true; def->storage = attribute->attstorage; def->generated = attribute->attgenerated; + + /* + * Regular inheritance children are independent enough not to + * inherit identity columns. But partitions are integral part + * of a partitioned table and inherit identity column. + */ + if (is_partition) + def->identity = attribute->attidentity; + if (CompressionMethodIsValid(attribute->attcompression)) def->compression = pstrdup(GetCompressionMethodName(attribute->attcompression)); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 41a362310a..d5f1993665 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "catalog/dependency.h" #include "catalog/pg_type.h" +#include "catalog/partition.h" #include "commands/trigger.h" #include "executor/executor.h" #include "foreign/fdwapi.h" @@ -1234,8 +1235,24 @@ build_column_default(Relation rel, int attrno) if (att_tup->attidentity) { NextValueExpr *nve = makeNode(NextValueExpr); + Oid reloid; - nve->seqid = getIdentitySequence(RelationGetRelid(rel), attrno, false); + /* + * The identity sequence is associated with the topmost partitioned + * table. + */ + if (rel->rd_rel->relispartition) + { + List *ancestors = + get_partition_ancestors(RelationGetRelid(rel)); + + reloid = llast_oid(ancestors); + list_free(ancestors); + } + else + reloid = RelationGetRelid(rel); + + nve->seqid = getIdentitySequence(reloid, attrno, false); nve->typeId = att_tup->atttypid; return (Node *) nve; diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 7c6e87e8a5..20803a1d24 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -539,7 +539,20 @@ CREATE TYPE itest_type AS (f1 integer, f2 text, f3 bigint); CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY); -- error ERROR: identity columns are not supported on typed tables DROP TYPE itest_type CASCADE; --- table partitions (currently not supported) +-- table partitions +-- partitions inherit identity column and share sequence +CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); +CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); +INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+---- + pitest1_p1 | 07-02-2016 | from pitest1 | 1 + pitest1_p1 | 07-03-2016 | from pitest1_p1 | 2 +(2 rows) + +-- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index 9b8db2e4a3..3660fa6a28 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -331,8 +331,16 @@ CREATE TABLE itest12 OF itest_type (f1 WITH OPTIONS GENERATED ALWAYS AS IDENTITY DROP TYPE itest_type CASCADE; --- table partitions (currently not supported) +-- table partitions +-- partitions inherit identity column and share sequence +CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); +CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); +INSERT into pitest1_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest1_p1'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; + +-- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -- 2.25.1
From 2fa0310839eae93a1bbf8c9ab3a4cea3dd9d635b Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Tue, 9 Jan 2024 15:40:27 +0530 Subject: [PATCH 01/27] Decorate PostgreSQL with productname tag ... in the section about Generated Columns. Ashutosh Bapat --- doc/src/sgml/ddl.sgml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index 22d04006ad..bb6ce9291e 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -249,7 +249,7 @@ CREATE TABLE products ( storage and is computed when it is read. Thus, a virtual generated column is similar to a view and a stored generated column is similar to a materialized view (except that it is always updated automatically). - PostgreSQL currently implements only stored generated columns. + <productname>PostgreSQL</productname> currently implements only stored generated columns. </para> <para> -- 2.25.1
From d2b9eaf168d7a168671e4f164230eaa8d4b4965d Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Thu, 7 Dec 2023 11:20:07 +0530 Subject: [PATCH 06/27] Support adding indentity column to a partitioned table Adding an identity column to a partitioned table just means propagating it down the partitioning hierarchy. All the partitions share the same underlying sequence. As for the implementation, we need to just disable the code prohibiting this case for partitioned tables. The column definition is transformed only once. The statements related to identity sequence are executed only once before executing any subcommands. Thus only one sequence, associated with the identity column, is created. The transformed column definition and related commands are copied as they are for all the partitions. Hence default expressions of all the partition use the same sequence when rewriting the table. Also the NOT NULL constraints required by the identity column are propagated. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 10 ++++++++-- src/test/regress/expected/identity.out | 22 ++++++++++++++++++++++ src/test/regress/sql/identity.sql | 13 +++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5719df2b76..527a106c48 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -7091,11 +7091,17 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, } /* - * Cannot add identity column if table has children, because identity does - * not inherit. (Adding column and identity separately will work.) + * Regular inheritance children are independent enough not to inherit the + * identity column from parent hence can not recursively add identity + * column if the table has inheritance children. + * + * Partitions, on the other hand, are integral part of a partitioned table + * and inherit indetity column. Hence propagate identity column down the + * partition hierarchy. */ if (colDef->identity && recurse && + rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE && find_inheritance_children(myrelid, NoLock) != NIL) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 222b3684da..93d9a60b03 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -565,6 +565,28 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; pitest1_p2 | 08-04-2016 | from pitest1 | 4 (5 rows) +-- add identity column +CREATE TABLE pitest2 (f1 date NOT NULL, f2 text) PARTITION BY RANGE (f1); +CREATE TABLE pitest2_p1 PARTITION OF pitest2 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +CREATE TABLE pitest2_p2 PARTITION OF pitest2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT into pitest2(f1, f2) VALUES ('2016-07-2', 'from pitest2'); +INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-2', 'from pitest2'); +ALTER TABLE pitest2 ADD COLUMN f3 int GENERATED ALWAYS AS IDENTITY; +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest2_p1'); +INSERT INTO pitest2_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest2_p2'); +INSERT into pitest2(f1, f2) VALUES ('2016-07-4', 'from pitest2'); +INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-4', 'from pitest2'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+---- + pitest2_p1 | 07-02-2016 | from pitest2 | 1 + pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 + pitest2_p1 | 07-04-2016 | from pitest2 | 5 + pitest2_p2 | 08-02-2016 | from pitest2 | 2 + pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 + pitest2_p2 | 08-04-2016 | from pitest2 | 6 +(6 rows) + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index be29cbf119..ba655002c0 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -349,6 +349,19 @@ INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest1_p2'); INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-4', 'from pitest1'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest1; +-- add identity column +CREATE TABLE pitest2 (f1 date NOT NULL, f2 text) PARTITION BY RANGE (f1); +CREATE TABLE pitest2_p1 PARTITION OF pitest2 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +CREATE TABLE pitest2_p2 PARTITION OF pitest2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); +INSERT into pitest2(f1, f2) VALUES ('2016-07-2', 'from pitest2'); +INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-2', 'from pitest2'); +ALTER TABLE pitest2 ADD COLUMN f3 int GENERATED ALWAYS AS IDENTITY; +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-3', 'from pitest2_p1'); +INSERT INTO pitest2_p2 (f1, f2) VALUES ('2016-08-3', 'from pitest2_p2'); +INSERT into pitest2(f1, f2) VALUES ('2016-07-4', 'from pitest2'); +INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-4', 'from pitest2'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( -- 2.25.1
From 106fee38ca410855098ce3acb2d8d9092da13b64 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Mon, 11 Dec 2023 16:41:09 +0530 Subject: [PATCH 07/27] Adding identity to partitioned table adds it to all partitions ... recursively. Additionally do not allow adding identity to only partitioned table or a partition Ashutosh Bapat --- src/backend/commands/tablecmds.c | 47 +++++++++++++++++++++++--- src/test/regress/expected/identity.out | 30 ++++++++++++++++ src/test/regress/sql/identity.sql | 20 +++++++++++ 3 files changed, 93 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 527a106c48..5ae0739377 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -452,7 +452,8 @@ static ObjectAddress ATExecColumnDefault(Relation rel, const char *colName, static ObjectAddress ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, Node *newDefault); static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, - Node *def, LOCKMODE lockmode); + Node *def, LOCKMODE lockmode, + bool recurse, bool recursing); static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode); static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); @@ -4825,7 +4826,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_AddIdentity: ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); - /* This command never recurses */ + /* Set up recursion for phase 2; no other prep needed */ + if (recurse) + cmd->recurse = true; pass = AT_PASS_ADD_OTHERCONSTR; break; case AT_SetIdentity: @@ -5224,7 +5227,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, cur_pass, context); Assert(cmd != NULL); - address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode); + address = ATExecAddIdentity(rel, cmd->name, cmd->def, lockmode, + cmd->recurse, false); break; case AT_SetIdentity: cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, @@ -8105,7 +8109,7 @@ ATExecCookedColumnDefault(Relation rel, AttrNumber attnum, */ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, - Node *def, LOCKMODE lockmode) + Node *def, LOCKMODE lockmode, bool recurse, bool recursing) { Relation attrelation; HeapTuple tuple; @@ -8113,6 +8117,19 @@ ATExecAddIdentity(Relation rel, const char *colName, AttrNumber attnum; ObjectAddress address; ColumnDef *cdef = castNode(ColumnDef, def); + bool ispartitioned; + + ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + if (ispartitioned && !recurse) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add identity to a column of only the partitioned table"), + errhint("Do not specify the ONLY keyword."))); + + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot add identity to a column of a partition")); attrelation = table_open(AttributeRelationId, RowExclusiveLock); @@ -8167,6 +8184,28 @@ ATExecAddIdentity(Relation rel, const char *colName, table_close(attrelation, RowExclusiveLock); + /* + * Recurse to propagate the identity column to partitions. Identity is not + * inherited in regular inheritance children. + */ + if (recurse && ispartitioned) + { + List *children; + ListCell *lc; + + children = find_inheritance_children(RelationGetRelid(rel), + lockmode); + + foreach(lc, children) + { + Relation childrel; + + childrel = table_open(lfirst_oid(lc), NoLock); + ATExecAddIdentity(childrel, colName, def, lockmode, recurse, true); + table_close(childrel, NoLock); + } + } + return address; } diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 93d9a60b03..9de35470af 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -587,6 +587,36 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; pitest2_p2 | 08-04-2016 | from pitest2 | 6 (6 rows) +-- changing a regular column to identity column in a partitioned table +CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); +CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT into pitest3 VALUES ('2016-07-2', 'from pitest3', 1); +INSERT into pitest3_p1 VALUES ('2016-07-3', 'from pitest3_p1', 2); +-- fails, changing only a partition not allowed +ALTER TABLE pitest3_p1 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +ERROR: cannot add identity to a column of a partition +-- fails, changing only the partitioned table not allowed +ALTER TABLE ONLY pitest3 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +ERROR: constraint must be added to child tables too +HINT: Do not specify the ONLY keyword. +ALTER TABLE pitest3 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); +INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+---- + pitest3_p1 | 07-02-2016 | from pitest3 | 1 + pitest3_p1 | 07-03-2016 | from pitest3_p1 | 2 + pitest3_p1 | 07-04-2016 | from pitest3 | 3 + pitest3_p1 | 07-05-2016 | from pitest3_p1 | 4 +(4 rows) + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index ba655002c0..9d1effa059 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -362,6 +362,26 @@ INSERT into pitest2(f1, f2) VALUES ('2016-07-4', 'from pitest2'); INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-4', 'from pitest2'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; +-- changing a regular column to identity column in a partitioned table +CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); +CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +INSERT into pitest3 VALUES ('2016-07-2', 'from pitest3', 1); +INSERT into pitest3_p1 VALUES ('2016-07-3', 'from pitest3_p1', 2); +-- fails, changing only a partition not allowed +ALTER TABLE pitest3_p1 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +-- fails, changing only the partitioned table not allowed +ALTER TABLE ONLY pitest3 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +ALTER TABLE pitest3 + ALTER COLUMN f3 SET NOT NULL, + ALTER COLUMN f3 ADD GENERATED ALWAYS AS IDENTITY (START WITH 3); +INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); +INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( -- 2.25.1
From 06a04b4ce767e2baaebaf112fe82b18d1be5d9a2 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Wed, 3 Jan 2024 10:00:22 +0530 Subject: [PATCH 09/27] Changing Identity column of a partitioned table The change is propagated to all the partitions. Changing the column only in a partitioned table or a partition is not allowed; the change needs to be applied to the whole partition hierarchy. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 48 +++++++++++++++++++++++--- src/test/regress/expected/identity.out | 28 +++++++++++++++ src/test/regress/sql/identity.sql | 11 ++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f0c1a85031..67b38c5101 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -455,7 +455,8 @@ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode, bool recurse, bool recursing); static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, - Node *def, LOCKMODE lockmode); + Node *def, LOCKMODE lockmode, + bool recurse, bool recursing); static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode, bool recurse, bool recursing); @@ -4835,7 +4836,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetIdentity: ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); - /* This command never recurses */ + /* Set up recursion for phase 2; no other prep needed */ + if (recurse) + cmd->recurse = true; /* This should run after AddIdentity, so do it in MISC pass */ pass = AT_PASS_MISC; break; @@ -5238,7 +5241,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, false, lockmode, cur_pass, context); Assert(cmd != NULL); - address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode); + address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode, + cmd->recurse, false); break; case AT_DropIdentity: address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, @@ -8220,7 +8224,8 @@ ATExecAddIdentity(Relation rel, const char *colName, * Return the address of the affected column. */ static ObjectAddress -ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode) +ATExecSetIdentity(Relation rel, const char *colName, Node *def, + LOCKMODE lockmode, bool recurse, bool recursing) { ListCell *option; DefElem *generatedEl = NULL; @@ -8229,6 +8234,19 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod AttrNumber attnum; Relation attrelation; ObjectAddress address; + bool ispartitioned; + + ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + if (ispartitioned && !recurse) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change identity column of only the partitioned table"), + errhint("Do not specify the ONLY keyword."))); + + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot change identity column of a partition")); foreach(option, castNode(List, def)) { @@ -8293,6 +8311,28 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod heap_freetuple(tuple); table_close(attrelation, RowExclusiveLock); + /* + * Recurse to propagate the identity change to partitions. Identity is not + * inherited in regular inheritance children. + */ + if (generatedEl && recurse && ispartitioned) + { + List *children; + ListCell *lc; + + children = find_inheritance_children(RelationGetRelid(rel), + lockmode); + + foreach(lc, children) + { + Relation childrel; + + childrel = table_open(lfirst_oid(lc), NoLock); + ATExecSetIdentity(childrel, colName, def, lockmode, recurse, true); + table_close(childrel, NoLock); + } + } + return address; } diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index d2860b4347..e185e0d4a1 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -587,6 +587,34 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; pitest2_p2 | 08-04-2016 | from pitest2 | 6 (6 rows) +-- SET identity column +ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET GENERATED BY DEFAULT; -- fails +ERROR: cannot change identity column of a partition +ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET INCREMENT BY 2; -- fails +ERROR: cannot change identity column of a partition +ALTER TABLE ONLY pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; -- fails +ERROR: cannot change identity column of only the partitioned table +HINT: Do not specify the ONLY keyword. +ALTER TABLE pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; +INSERT into pitest2(f1, f2, f3) VALUES ('2016-07-5', 'from pitest2', 200); +INSERT INTO pitest2(f1, f2) VALUES ('2016-08-5', 'from pitest2'); +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-6', 'from pitest2_p1'); +INSERT INTO pitest2_p2 (f1, f2, f3) VALUES ('2016-08-6', 'from pitest2_p2', 300); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+------ + pitest2_p1 | 07-02-2016 | from pitest2 | 1 + pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 + pitest2_p1 | 07-04-2016 | from pitest2 | 5 + pitest2_p1 | 07-05-2016 | from pitest2 | 200 + pitest2_p1 | 07-06-2016 | from pitest2_p1 | 1002 + pitest2_p2 | 08-02-2016 | from pitest2 | 2 + pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 + pitest2_p2 | 08-04-2016 | from pitest2 | 6 + pitest2_p2 | 08-05-2016 | from pitest2 | 1000 + pitest2_p2 | 08-06-2016 | from pitest2_p2 | 300 +(10 rows) + -- changing a regular column to identity column in a partitioned table CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index 62323f9cbd..a1862df1d8 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -362,6 +362,17 @@ INSERT into pitest2(f1, f2) VALUES ('2016-07-4', 'from pitest2'); INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-4', 'from pitest2'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; +-- SET identity column +ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET GENERATED BY DEFAULT; -- fails +ALTER TABLE pitest2_p1 ALTER COLUMN f3 SET INCREMENT BY 2; -- fails +ALTER TABLE ONLY pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; -- fails +ALTER TABLE pitest2 ALTER COLUMN f3 SET GENERATED BY DEFAULT SET INCREMENT BY 2 SET START WITH 1000 RESTART; +INSERT into pitest2(f1, f2, f3) VALUES ('2016-07-5', 'from pitest2', 200); +INSERT INTO pitest2(f1, f2) VALUES ('2016-08-5', 'from pitest2'); +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-6', 'from pitest2_p1'); +INSERT INTO pitest2_p2 (f1, f2, f3) VALUES ('2016-08-6', 'from pitest2_p2', 300); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; + -- changing a regular column to identity column in a partitioned table CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- 2.25.1
From 6f5276cef8814300057a370778bb9b8ce6ebde23 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Mon, 18 Dec 2023 16:39:30 +0530 Subject: [PATCH 08/27] DROP IDENTITY on partitioned table recurses to its partitions Since identity property is inherited by partition tables, dropping identity of a column in partitioned table should result in dropping it from all the partitions. I tried to implement the DROP SEQUENCE as a separate step to be executed at the end of ALTER TABLE command. But that does not work since the sequence has a dependency on the column (whose identity is being dropped) and not on the identity property itself. There is no step/ALTER TABLE command to drop that dependecy. Dropping identity property from a column of a partition table is not allwoed. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 73 +++++++++++++++++++++----- src/test/regress/expected/identity.out | 26 +++++++++ src/test/regress/sql/identity.sql | 10 ++++ 3 files changed, 96 insertions(+), 13 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 5ae0739377..f0c1a85031 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -456,7 +456,9 @@ static ObjectAddress ATExecAddIdentity(Relation rel, const char *colName, bool recurse, bool recursing); static ObjectAddress ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmode); -static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); +static ObjectAddress ATExecDropIdentity(Relation rel, const char *colName, + bool missing_ok, LOCKMODE lockmode, + bool recurse, bool recursing); static void ATPrepDropExpression(Relation rel, AlterTableCmd *cmd, bool recurse, bool recursing, LOCKMODE lockmode); static ObjectAddress ATExecDropExpression(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode); static ObjectAddress ATExecSetStatistics(Relation rel, const char *colName, int16 colNum, @@ -4839,7 +4841,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_DropIdentity: ATSimplePermissions(cmd->subtype, rel, ATT_TABLE | ATT_VIEW | ATT_FOREIGN_TABLE); - /* This command never recurses */ + /* Set up recursion for phase 2; no other prep needed */ + if (recurse) + cmd->recurse = true; pass = AT_PASS_DROP; break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ @@ -5237,7 +5241,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, address = ATExecSetIdentity(rel, cmd->name, cmd->def, lockmode); break; case AT_DropIdentity: - address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, lockmode); + address = ATExecDropIdentity(rel, cmd->name, cmd->missing_ok, + lockmode, cmd->recurse, false); break; case AT_DropNotNull: /* ALTER COLUMN DROP NOT NULL */ address = ATExecDropNotNull(rel, cmd->name, cmd->recurse, lockmode); @@ -8297,7 +8302,9 @@ ATExecSetIdentity(Relation rel, const char *colName, Node *def, LOCKMODE lockmod * Return the address of the affected column. */ static ObjectAddress -ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE lockmode) +ATExecDropIdentity(Relation rel, const char *colName, + bool missing_ok, LOCKMODE lockmode, + bool recurse, bool recursing) { HeapTuple tuple; Form_pg_attribute attTup; @@ -8306,6 +8313,19 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE ObjectAddress address; Oid seqid; ObjectAddress seqaddress; + bool ispartitioned; + + ispartitioned = (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + if (ispartitioned && !recurse) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop identity from a column of only the partitioned table"), + errhint("Do not specify the ONLY keyword."))); + + if (rel->rd_rel->relispartition && !recursing) + ereport(ERROR, + errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot drop identity from a column of a partition")); attrelation = table_open(AttributeRelationId, RowExclusiveLock); tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName); @@ -8354,15 +8374,42 @@ ATExecDropIdentity(Relation rel, const char *colName, bool missing_ok, LOCKMODE table_close(attrelation, RowExclusiveLock); - /* drop the internal sequence */ - seqid = getIdentitySequence(RelationGetRelid(rel), attnum, false); - deleteDependencyRecordsForClass(RelationRelationId, seqid, - RelationRelationId, DEPENDENCY_INTERNAL); - CommandCounterIncrement(); - seqaddress.classId = RelationRelationId; - seqaddress.objectId = seqid; - seqaddress.objectSubId = 0; - performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); + /* + * Recurse to drop the identity from column in partitions. Identity is not + * inherited in regular inheritance children so ignore them. + */ + if (recurse && ispartitioned) + { + List *children; + ListCell *lc; + + children = find_inheritance_children(RelationGetRelid(rel), + lockmode); + + foreach(lc, children) + { + Relation childrel; + + childrel = table_open(lfirst_oid(lc), NoLock); + ATExecDropIdentity(childrel, colName, false, lockmode, recurse, + true); + table_close(childrel, NoLock); + } + } + + if (!recursing) + { + /* drop the internal sequence */ + seqid = getIdentitySequence(RelationGetRelid(rel), attnum, false); + deleteDependencyRecordsForClass(RelationRelationId, seqid, + RelationRelationId, + DEPENDENCY_INTERNAL); + CommandCounterIncrement(); + seqaddress.classId = RelationRelationId; + seqaddress.objectId = seqid; + seqaddress.objectSubId = 0; + performDeletion(&seqaddress, DROP_RESTRICT, PERFORM_DELETION_INTERNAL); + } return address; } diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 9de35470af..d2860b4347 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -617,6 +617,32 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; pitest3_p1 | 07-05-2016 | from pitest3_p1 | 4 (4 rows) +-- changing an identity column to a non-identity column in a partitioned table +ALTER TABLE pitest3_p1 ALTER COLUMN f3 DROP IDENTITY; -- fails +ERROR: cannot drop identity from a column of a partition +ALTER TABLE ONLY pitest3 ALTER COLUMN f3 DROP IDENTITY; -- fails +ERROR: cannot drop identity from a column of only the partitioned table +HINT: Do not specify the ONLY keyword. +ALTER TABLE pitest3 ALTER COLUMN f3 DROP IDENTITY; +INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); -- fails +ERROR: null value in column "f3" of relation "pitest3_p1" violates not-null constraint +DETAIL: Failing row contains (07-04-2016, from pitest3, null). +INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); -- fails +ERROR: null value in column "f3" of relation "pitest3_p1" violates not-null constraint +DETAIL: Failing row contains (07-05-2016, from pitest3_p1, null). +INSERT into pitest3(f1, f2, f3) VALUES ('2016-07-6', 'from pitest3', 5); +INSERT into pitest3_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest3_p1', 6); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+---- + pitest3_p1 | 07-02-2016 | from pitest3 | 1 + pitest3_p1 | 07-03-2016 | from pitest3_p1 | 2 + pitest3_p1 | 07-04-2016 | from pitest3 | 3 + pitest3_p1 | 07-05-2016 | from pitest3_p1 | 4 + pitest3_p1 | 07-06-2016 | from pitest3 | 5 + pitest3_p1 | 07-07-2016 | from pitest3_p1 | 6 +(6 rows) + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index 9d1effa059..62323f9cbd 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -382,6 +382,16 @@ INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; +-- changing an identity column to a non-identity column in a partitioned table +ALTER TABLE pitest3_p1 ALTER COLUMN f3 DROP IDENTITY; -- fails +ALTER TABLE ONLY pitest3 ALTER COLUMN f3 DROP IDENTITY; -- fails +ALTER TABLE pitest3 ALTER COLUMN f3 DROP IDENTITY; +INSERT into pitest3(f1, f2) VALUES ('2016-07-4', 'from pitest3'); -- fails +INSERT into pitest3_p1 (f1, f2) VALUES ('2016-07-5', 'from pitest3_p1'); -- fails +INSERT into pitest3(f1, f2, f3) VALUES ('2016-07-6', 'from pitest3', 5); +INSERT into pitest3_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest3_p1', 6); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; + -- partition with identity column of its own is not allowed CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); CREATE TABLE itest_child PARTITION OF itest_parent ( -- 2.25.1
From 1a5517a57ac5f3dd083b4814d403e35f26a542f5 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Tue, 19 Dec 2023 11:38:48 +0530 Subject: [PATCH 10/27] Drop identity property when detaching partition A partition's identity column shares the identity space (i.e. underlying sequence) as the corresponding column of the partitioned table. If a partition is detached it can longer share the identity space as before. Hence the identity columns of the partition being detached loose their identity property. When identity of a column of a regular table is dropped it retains the NOT NULL constarint that came with the identity property. Similarly the columns of the partition being detached retain the NOT NULL constraints that came with identity property, even though the identity property itself is lost. Also note that the sequence associated with the identity property is linked to the partitioned table (and not the partition being detached). That sequence is not dropped as part of detach operation. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 13 +++++++++++ src/test/regress/expected/identity.out | 30 ++++++++++++++++++++++++++ src/test/regress/sql/identity.sql | 10 +++++++++ 3 files changed, 53 insertions(+) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 67b38c5101..19badb3fba 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -19498,6 +19498,7 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, HeapTuple tuple, newtuple; Relation trigrel = NULL; + int attno; if (concurrent) { @@ -19663,6 +19664,18 @@ DetachPartitionFinalize(Relation rel, Relation partRel, bool concurrent, heap_freetuple(newtuple); table_close(classRel, RowExclusiveLock); + /* + * Drop identity property from all identity columns of partition. + */ + for (attno = 0; attno < RelationGetNumberOfAttributes(partRel); attno++) + { + Form_pg_attribute attr = TupleDescAttr(partRel->rd_att, attno); + + if (!attr->attisdropped && attr->attidentity) + ATExecDropIdentity(partRel, NameStr(attr->attname), false, + AccessExclusiveLock, true, true); + } + if (OidIsValid(defaultPartOid)) { /* diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index e185e0d4a1..501fcc22c6 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -615,6 +615,36 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; pitest2_p2 | 08-06-2016 | from pitest2_p2 | 300 (10 rows) +-- detaching a partition removes identity property +ALTER TABLE pitest2 DETACH PARTITION pitest2_p1; +INSERT into pitest2(f1, f2) VALUES ('2016-08-7', 'from pitest2'); +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-7', 'from pitest2_p1'); -- error +ERROR: null value in column "f3" of relation "pitest2_p1" violates not-null constraint +DETAIL: Failing row contains (07-07-2016, from pitest2_p1, null). +INSERT into pitest2_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest2_p1', 2000); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+------ + pitest2_p2 | 08-02-2016 | from pitest2 | 2 + pitest2_p2 | 08-03-2016 | from pitest2_p2 | 4 + pitest2_p2 | 08-04-2016 | from pitest2 | 6 + pitest2_p2 | 08-05-2016 | from pitest2 | 1000 + pitest2_p2 | 08-06-2016 | from pitest2_p2 | 300 + pitest2_p2 | 08-07-2016 | from pitest2 | 1004 +(6 rows) + +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2_p1; + tableoid | f1 | f2 | f3 +------------+------------+-----------------+------ + pitest2_p1 | 07-02-2016 | from pitest2 | 1 + pitest2_p1 | 07-03-2016 | from pitest2_p1 | 3 + pitest2_p1 | 07-04-2016 | from pitest2 | 5 + pitest2_p1 | 07-05-2016 | from pitest2 | 200 + pitest2_p1 | 07-06-2016 | from pitest2_p1 | 1002 + pitest2_p1 | 07-07-2016 | from pitest2_p1 | 2000 +(6 rows) + +DROP TABLE pitest2_p1; -- changing a regular column to identity column in a partitioned table CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index a1862df1d8..0d7ecfb3e0 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -373,6 +373,16 @@ INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-6', 'from pitest2_p1'); INSERT INTO pitest2_p2 (f1, f2, f3) VALUES ('2016-08-6', 'from pitest2_p2', 300); SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; +-- detaching a partition removes identity property +ALTER TABLE pitest2 DETACH PARTITION pitest2_p1; +INSERT into pitest2(f1, f2) VALUES ('2016-08-7', 'from pitest2'); +INSERT into pitest2_p1 (f1, f2) VALUES ('2016-07-7', 'from pitest2_p1'); -- error +INSERT into pitest2_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest2_p1', 2000); +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2; +SELECT tableoid::regclass, f1, f2, f3 FROM pitest2_p1; + +DROP TABLE pitest2_p1; + -- changing a regular column to identity column in a partitioned table CREATE TABLE pitest3 (f1 date NOT NULL, f2 text, f3 int) PARTITION BY RANGE (f1); CREATE TABLE pitest3_p1 PARTITION OF pitest3 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- 2.25.1
From 3ace7b78b04395e01a973e49af1e7b8f29bbafae Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Wed, 20 Dec 2023 14:55:24 +0530 Subject: [PATCH 11/27] Partitions with their own identity columns are not allowed ... for the reasons specified in earlier commit messages. Ashutosh Bapat --- src/backend/commands/tablecmds.c | 12 +++++++++++- src/test/regress/expected/generated.out | 2 +- src/test/regress/expected/identity.out | 20 +++++++++++++++----- src/test/regress/sql/identity.sql | 19 ++++++++++++++----- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 19badb3fba..f6db45522f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -18793,7 +18793,10 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot attach temporary relation of another session as partition"))); - /* Check if there are any columns in attachrel that aren't in the parent */ + /* + * Check if attachrel has any identity columns or any columns that aren't + * in the parent. + */ tupleDesc = RelationGetDescr(attachrel); natts = tupleDesc->natts; for (attno = 1; attno <= natts; attno++) @@ -18805,6 +18808,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, if (attribute->attisdropped) continue; + if (attribute->attidentity) + ereport(ERROR, + errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("table \"%s\" being attached can not contain identity column \"%s\"", + RelationGetRelationName(attachrel), + attributeName)); + /* Try to find the column in parent (matching on column name) */ if (!SearchSysCacheExists2(ATTNAME, ObjectIdGetDatum(RelationGetRelid(rel)), diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out index a2f38d0f50..c9b0856fa2 100644 --- a/src/test/regress/expected/generated.out +++ b/src/test/regress/expected/generated.out @@ -753,7 +753,7 @@ ERROR: column "f3" in child table must be a generated column DROP TABLE gtest_child3; CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS IDENTITY); ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); -- error -ERROR: column "f3" in child table must be a generated column +ERROR: table "gtest_child3" being attached can not contain identity column "f3" DROP TABLE gtest_child3; CREATE TABLE gtest_child3 (f1 date NOT NULL, f2 bigint, f3 bigint GENERATED ALWAYS AS (f2 * 33) STORED); ALTER TABLE gtest_parent ATTACH PARTITION gtest_child3 FOR VALUES FROM ('2016-09-01') TO ('2016-10-01'); diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 501fcc22c6..0b6a4b160e 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -701,13 +701,23 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; pitest3_p1 | 07-07-2016 | from pitest3_p1 | 6 (6 rows) --- partition with identity column of its own is not allowed -CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); -CREATE TABLE itest_child PARTITION OF itest_parent ( +-- partitions with their own identity columns are not allowed, even if the +-- partitioned table does not have an identity column. +CREATE TABLE pitest1_pfail PARTITION OF pitest1 ( f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error +) FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); ERROR: identity columns are not supported on partitions -DROP TABLE itest_parent; +CREATE TABLE pitest_pfail PARTITION OF pitest3 ( + f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); +ERROR: identity columns are not supported on partitions +CREATE TABLE pitest1_pfail (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS IDENTITY); +ALTER TABLE pitest1 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); +ERROR: table "pitest1_pfail" being attached can not contain identity column "f3" +ALTER TABLE pitest3 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); +ERROR: table "pitest1_pfail" being attached can not contain identity column "f3" +DROP TABLE pitest1_pfail; +DROP TABLE pitest3; -- test that sequence of half-dropped serial column is properly ignored CREATE TABLE itest14 (id serial); ALTER TABLE itest14 ALTER id DROP DEFAULT; diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index 0d7ecfb3e0..e0ce2c799b 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -413,13 +413,22 @@ INSERT into pitest3(f1, f2, f3) VALUES ('2016-07-6', 'from pitest3', 5); INSERT into pitest3_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest3_p1', 6); SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; --- partition with identity column of its own is not allowed -CREATE TABLE itest_parent (f1 date NOT NULL, f2 text, f3 bigint) PARTITION BY RANGE (f1); -CREATE TABLE itest_child PARTITION OF itest_parent ( +-- partitions with their own identity columns are not allowed, even if the +-- partitioned table does not have an identity column. +CREATE TABLE pitest1_pfail PARTITION OF pitest1 ( f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY -) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); -- error -DROP TABLE itest_parent; +) FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); +CREATE TABLE pitest_pfail PARTITION OF pitest3 ( + f3 WITH OPTIONS GENERATED ALWAYS AS IDENTITY +) FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); + +CREATE TABLE pitest1_pfail (f1 date NOT NULL, f2 text, f3 bigint GENERATED ALWAYS AS IDENTITY); +ALTER TABLE pitest1 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); +ALTER TABLE pitest3 ATTACH PARTITION pitest1_pfail FOR VALUES FROM ('2016-11-01') TO ('2016-12-01'); + +DROP TABLE pitest1_pfail; +DROP TABLE pitest3; -- test that sequence of half-dropped serial column is properly ignored -- 2.25.1
From ab37ba8f8a517c8aea8c6cb05eb3712b5da15955 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Wed, 3 Jan 2024 11:54:22 +0530 Subject: [PATCH 12/27] Test changing some properties of identity column in partitioned table NULL constraint, default and identity property Ashutosh Bapat --- src/test/regress/expected/identity.out | 15 +++++++++++++++ src/test/regress/sql/identity.sql | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/test/regress/expected/identity.out b/src/test/regress/expected/identity.out index 0b6a4b160e..f03d88035f 100644 --- a/src/test/regress/expected/identity.out +++ b/src/test/regress/expected/identity.out @@ -701,6 +701,21 @@ SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; pitest3_p1 | 07-07-2016 | from pitest3_p1 | 6 (6 rows) +-- Changing NOT NULL constraint of identity columns is not allowed +ALTER TABLE pitest1_p1 ALTER COLUMN f3 DROP NOT NULL; +ERROR: column "f3" of relation "pitest1_p1" is an identity column +ALTER TABLE pitest1 ALTER COLUMN f3 DROP NOT NULL; +ERROR: column "f3" of relation "pitest1" is an identity column +-- Identity columns have their own default +ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET DEFAULT 10000; +ERROR: column "f3" of relation "pitest1_p2" is an identity column +ALTER TABLE pitest1 ALTER COLUMN f3 SET DEFAULT 10000; +ERROR: column "f3" of relation "pitest1" is an identity column +-- Adding identity to an identity column is not allowed +ALTER TABLE pitest1_p2 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; +ERROR: cannot add identity to a column of a partition +ALTER TABLE pitest1 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; +ERROR: column "f3" of relation "pitest1" is already an identity column -- partitions with their own identity columns are not allowed, even if the -- partitioned table does not have an identity column. CREATE TABLE pitest1_pfail PARTITION OF pitest1 ( diff --git a/src/test/regress/sql/identity.sql b/src/test/regress/sql/identity.sql index e0ce2c799b..9f35214751 100644 --- a/src/test/regress/sql/identity.sql +++ b/src/test/regress/sql/identity.sql @@ -413,6 +413,16 @@ INSERT into pitest3(f1, f2, f3) VALUES ('2016-07-6', 'from pitest3', 5); INSERT into pitest3_p1 (f1, f2, f3) VALUES ('2016-07-7', 'from pitest3_p1', 6); SELECT tableoid::regclass, f1, f2, f3 FROM pitest3; +-- Changing NOT NULL constraint of identity columns is not allowed +ALTER TABLE pitest1_p1 ALTER COLUMN f3 DROP NOT NULL; +ALTER TABLE pitest1 ALTER COLUMN f3 DROP NOT NULL; +-- Identity columns have their own default +ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET DEFAULT 10000; +ALTER TABLE pitest1 ALTER COLUMN f3 SET DEFAULT 10000; +-- Adding identity to an identity column is not allowed +ALTER TABLE pitest1_p2 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; +ALTER TABLE pitest1 ALTER COLUMN f3 ADD GENERATED BY DEFAULT AS IDENTITY; + -- partitions with their own identity columns are not allowed, even if the -- partitioned table does not have an identity column. CREATE TABLE pitest1_pfail PARTITION OF pitest1 ( -- 2.25.1
From d5f226c0542c1524ef62fa80ae9fef9d5513e685 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Mon, 8 Jan 2024 16:22:23 +0530 Subject: [PATCH 13/27] Fix output in modules/test_ddl_deparse/ ALTER TABLE ... ALTER COLUMN ... ADD/SET/DROP IDENTITY commands need to recurse into partition hierarchy. Hence they are marked to recurse when ONLY is not specified. Change the expected output for the same. Please note that in case of regular inheritance the commands won't recurse down the inheritance tree whether or not ONLY is specified. This is the same as the earlier behaviour. Ashutosh Bapat --- src/test/modules/test_ddl_deparse/expected/alter_table.out | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out index ecde9d7422..b5e71af9aa 100644 --- a/src/test/modules/test_ddl_deparse/expected/alter_table.out +++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out @@ -74,14 +74,14 @@ ALTER TABLE parent ALTER COLUMN a ADD GENERATED ALWAYS AS IDENTITY; NOTICE: DDL test: type simple, tag CREATE SEQUENCE NOTICE: DDL test: type simple, tag ALTER SEQUENCE NOTICE: DDL test: type alter table, tag ALTER TABLE -NOTICE: subcommand: type ADD IDENTITY desc column a of table parent +NOTICE: subcommand: type ADD IDENTITY (and recurse) desc column a of table parent ALTER TABLE parent ALTER COLUMN a SET GENERATED BY DEFAULT; NOTICE: DDL test: type simple, tag ALTER SEQUENCE NOTICE: DDL test: type alter table, tag ALTER TABLE -NOTICE: subcommand: type SET IDENTITY desc column a of table parent +NOTICE: subcommand: type SET IDENTITY (and recurse) desc column a of table parent ALTER TABLE parent ALTER COLUMN a DROP IDENTITY; NOTICE: DDL test: type alter table, tag ALTER TABLE -NOTICE: subcommand: type DROP IDENTITY desc column a of table parent +NOTICE: subcommand: type DROP IDENTITY (and recurse) desc column a of table parent ALTER TABLE parent ALTER COLUMN a SET STATISTICS 100; NOTICE: DDL test: type alter table, tag ALTER TABLE NOTICE: subcommand: type SET STATS desc column a of table parent -- 2.25.1
From e18135cc29c074c23113f6cb907cbe83fe22e248 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Fri, 5 Jan 2024 19:28:16 +0530 Subject: [PATCH 14/27] Test dump and restore: NOT FOR FINAL COMMIT ... through the changed testcase since it dumps and restores the objects left behind by sql/identity.sql. The additional SQL statements here test the sanity of the restored partitioned tables with identity column in it. I have not seen these kind of tests for checking sanity of other objects so it's quite possible that dump comparison performed by the test is enough. Hence not planning to propose this for final commit. The earlier patches do not change anything in dump restore and yet it works. This is because the identity columns are dumped using ALTER TABLE command, which is constructed only for the table owning the underlying sequence. In case of partitioned tables, only the topmost partitioned table owns the sequence and thus ALTER TABLE command adding identity to the column is run only on the topmost table. The partitions are attached afterwards and inherit the identity column as part of that operation. Ashutosh Bapat --- src/bin/pg_upgrade/t/002_pg_upgrade.pl | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index b0470844de..26ee1fb2af 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -458,4 +458,37 @@ if ($compare_res != 0) print "=== EOF ===\n"; } +# Test restored partitioned table with identity +$newnode->safe_psql('regression', + "INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-5', 'from pitest1'), ('2016-07-5', 'from pitest1');"); +$newnode->safe_psql('regression', + "INSERT INTO pitest1_p1 (f1, f2) VALUES ('2016-07-6', 'from pitest1_p1');"); +$newnode->safe_psql('regression', + "INSERT INTO pitest1_p2 (f1, f2) VALUES ('2016-08-6', 'from pitest1_p2');"); +$result = $newnode->safe_psql('regression', + "SELECT tableoid::regclass, f1, f2, f3 FROM pitest1"); +is($result,"pitest1_p1|2016-07-02|from pitest1|1 +pitest1_p1|2016-07-03|from pitest1_p1|2 +pitest1_p1|2016-07-05|from pitest1|6 +pitest1_p1|2016-07-06|from pitest1_p1|7 +pitest1_p2|2016-08-02|before attaching|100 +pitest1_p2|2016-08-03|from pitest1_p2|3 +pitest1_p2|2016-08-04|from pitest1|4 +pitest1_p2|2016-08-05|from pitest1|5 +pitest1_p2|2016-08-06|from pitest1_p2|8"); +$newnode->safe_psql('regression', + "INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-8', 'from pitest2');"); +$newnode->safe_psql('regression', + "INSERT INTO pitest2_p2 (f1, f2) VALUES ('2016-08-9', 'from pitest2_p2');"); +$result = $newnode->safe_psql('regression', + "SELECT tableoid::regclass, f1, f2, f3 FROM pitest2;"); +is($result, "pitest2_p2|2016-08-02|from pitest2|2 +pitest2_p2|2016-08-03|from pitest2_p2|4 +pitest2_p2|2016-08-04|from pitest2|6 +pitest2_p2|2016-08-05|from pitest2|1000 +pitest2_p2|2016-08-06|from pitest2_p2|300 +pitest2_p2|2016-08-07|from pitest2|1004 +pitest2_p2|2016-08-08|from pitest2|1006 +pitest2_p2|2016-08-09|from pitest2_p2|1008"); + done_testing(); -- 2.25.1
From 304969e542940240e21698b290cc647a38f066fb Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com> Date: Mon, 8 Jan 2024 15:01:06 +0530 Subject: [PATCH 15/27] Test pg_upgrade NOT FOR FINAL COMMIT Old clusters, with versions 10 and above, may have partitioned tables with identity columns. The corresponding columns in partitions of such tables will not be marked as identity columns. Test that such partitioned tables are upgraded correctly. The columns in partitions are also marked as identity columns and use the same underlying sequence as the partitioned table. Test this using following steps 1. On an old version cluster run following commands to create required partitioned tables in "regression" database --- sql CREATE TABLE pitest1 (f1 date NOT NULL, f2 text, f3 bigint generated always as identity) PARTITION BY RANGE (f1); CREATE TABLE pitest1_p1 PARTITION OF pitest1 FOR VALUES FROM ('2016-07-01') TO ('2016-08-01'); INSERT into pitest1(f1, f2) VALUES ('2016-07-2', 'from pitest1'); INSERT into pitest1_p1 (f1, f2, f3) VALUES ('2016-07-3', 'from pitest1_p1', nextval('pitest1_f3_seq')); CREATE TABLE pitest1_p2 (f1 date NOT NULL, f2 text, f3 bigint); INSERT INTO pitest1_p2 VALUES ('2016-08-2', 'before attaching', 100); ALTER TABLE pitest1_p2 ALTER COLUMN f3 SET NOT NULL; ALTER TABLE pitest1 ATTACH PARTITION pitest1_p2 FOR VALUES FROM ('2016-08-01') TO ('2016-09-01'); INSERT INTO pitest1_p2 (f1, f2, f3) VALUES ('2016-08-3', 'from pitest1_p2', nextval('pitest1_f3_seq')); INSERT INTO pitest1 (f1, f2) VALUES ('2016-08-4', 'from pitest1'); --- sql 2. Take dump using pg_dumpall and save it in a file 3. Set environment variables olddump to the path of this file and oldinstall to the binaries of old version. 4. Run modified 002_pg_upgrade. --- src/bin/pg_upgrade/t/002_pg_upgrade.pl | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/bin/pg_upgrade/t/002_pg_upgrade.pl b/src/bin/pg_upgrade/t/002_pg_upgrade.pl index 26ee1fb2af..9b63a7b9dd 100644 --- a/src/bin/pg_upgrade/t/002_pg_upgrade.pl +++ b/src/bin/pg_upgrade/t/002_pg_upgrade.pl @@ -241,8 +241,9 @@ if (defined($ENV{oldinstall})) my %dbnames; do { $dbnames{$_} = 1; } foreach split /\s+/s, $dbnames; - my $adjust_cmds = - adjust_database_contents($oldnode->pg_version, %dbnames); + # Testing a custom dump that doesn't have regression objects + my $adjust_cmds = (); + # adjust_database_contents($oldnode->pg_version, %dbnames); foreach my $updb (keys %$adjust_cmds) { @@ -476,19 +477,5 @@ pitest1_p2|2016-08-03|from pitest1_p2|3 pitest1_p2|2016-08-04|from pitest1|4 pitest1_p2|2016-08-05|from pitest1|5 pitest1_p2|2016-08-06|from pitest1_p2|8"); -$newnode->safe_psql('regression', - "INSERT INTO pitest2 (f1, f2) VALUES ('2016-08-8', 'from pitest2');"); -$newnode->safe_psql('regression', - "INSERT INTO pitest2_p2 (f1, f2) VALUES ('2016-08-9', 'from pitest2_p2');"); -$result = $newnode->safe_psql('regression', - "SELECT tableoid::regclass, f1, f2, f3 FROM pitest2;"); -is($result, "pitest2_p2|2016-08-02|from pitest2|2 -pitest2_p2|2016-08-03|from pitest2_p2|4 -pitest2_p2|2016-08-04|from pitest2|6 -pitest2_p2|2016-08-05|from pitest2|1000 -pitest2_p2|2016-08-06|from pitest2_p2|300 -pitest2_p2|2016-08-07|from pitest2|1004 -pitest2_p2|2016-08-08|from pitest2|1006 -pitest2_p2|2016-08-09|from pitest2_p2|1008"); done_testing(); -- 2.25.1
pg_dumpall_14.out
Description: Binary data