Thanks Ashutosh.

On 2018/07/10 22:50, Ashutosh Bapat wrote:
> I didn't see any hackers thread linked to this CF entry. Hence sending this 
> mail through CF app.

Hmm, yes.  I hadn't posted the patch to -hackers.

> The patch looks good to me. It applies cleanly, compiles cleanly and make 
> check passes.
> 
> I think you could avoid condition
> +                       /* Do not override parent's NOT NULL constraint. */
> +                       if (restdef->is_not_null)
> +                           coldef->is_not_null = restdef->is_not_null;
> 
> by rewriting this line as
> coldef->is_not_null = coldef->is_not_null || restdef->is_not_null;

Agreed, done like that.

> The comment looks a bit odd either way since we are changing 
> coldef->is_not_null based on restdef->is_not_null. That's because of the 
> nature of boolean variables. May be a bit of explanation is needed.

I have modified the comments around this code in the updated patch.

> On a side note, I see
> coldef->constraints = restdef->constraints;
> Shouldn't we merge the constraints instead of just overwriting those?

Actually, I checked that coldef->constraints is always NIL in this case.
coldef (ColumnDef) is constructed from a member in the parent's TupleDesc
earlier in that function and any constraints that may be present in the
parent's definition of the column are saved in a separate variable that's
returned to the caller as one list containing "old"/inherited constraints.
 So, no constraints are getting lost here.

Attached is an updated patch.  I've updated some nearby comments as the
code around here seems pretty confusing, which I thought after having
returned to it after a while.

I have attached both a patch for PG10 and PG11/HEAD branches, which are
actually not all that different from each other.

Thanks,
Amit
From 1158793b899bbc1f52e37b2255bd74121a293495 Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Wed, 6 Jun 2018 16:46:46 +0900
Subject: [PATCH v2] Fix bug regarding partition column option inheritance

It appears that the coding pattern in MergeAttributes causes the
NOT NULL constraint and default value of a column from not being
properly inherited from the parent's definition.
---
 src/backend/commands/tablecmds.c           | 49 +++++++++++++++++++++++++++---
 src/test/regress/expected/create_table.out | 13 ++++++++
 src/test/regress/sql/create_table.sql      |  7 +++++
 3 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 81c3845095..b7a6c1792e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2181,6 +2181,15 @@ MergeAttributes(List *schema, List *supers, char 
relpersistence,
         */
        if (is_partition && list_length(saved_schema) > 0)
        {
+               /*
+                * Each member in 'saved_schema' contains a ColumnDef containing
+                * partition-specific options for the column.  Below, we merge 
the
+                * information from each into the ColumnDef of the same name 
found in
+                * the original 'schema' list before deleting it from the list. 
 So,
+                * once we've finished processing all the columns from the 
original
+                * 'schema' list, there shouldn't be any ColumnDefs left that 
came
+                * from the 'saved_schema' list.
+                */
                schema = list_concat(schema, saved_schema);
 
                foreach(entry, schema)
@@ -2208,14 +2217,44 @@ MergeAttributes(List *schema, List *supers, char 
relpersistence,
                                if (strcmp(coldef->colname, restdef->colname) 
== 0)
                                {
                                        /*
-                                        * merge the column options into the 
column from the
-                                        * parent
+                                        * Merge the column options specified 
for the partition
+                                        * into the column definition inherited 
from the parent.
                                         */
                                        if (coldef->is_from_parent)
                                        {
-                                               coldef->is_not_null = 
restdef->is_not_null;
-                                               coldef->raw_default = 
restdef->raw_default;
-                                               coldef->cooked_default = 
restdef->cooked_default;
+                                               /*
+                                                * Combine using OR so that the 
NOT NULL constraint
+                                                * in the parent's definition 
doesn't get lost.
+                                                */
+                                               coldef->is_not_null = 
(coldef->is_not_null ||
+                                                                               
           restdef->is_not_null);
+
+                                               /*
+                                                * If the partition's 
definition specifies a default,
+                                                * it's in 
restdef->raw_default, which if non-NULL,
+                                                * overrides the parent's 
default that's in
+                                                * coldef->cooked_default.
+                                                */
+                                               if (restdef->raw_default)
+                                               {
+                                                       coldef->raw_default = 
restdef->raw_default;
+                                                       coldef->cooked_default 
= NULL;
+                                               }
+                                               /*
+                                                * Don't accidentally lose the 
parent's default by
+                                                * overwriting with NULL.
+                                                */
+                                               else if 
(restdef->cooked_default)
+                                               {
+                                                       coldef->raw_default = 
NULL;
+                                                       coldef->cooked_default 
= restdef->cooked_default;
+                                               }
+
+                                               /*
+                                                * Parent's constraints, if 
any, have been saved in
+                                                * 'constraints', which are 
passed back to the caller,
+                                                * so it's okay to overwrite 
the variable like this.
+                                                */
                                                coldef->constraints = 
restdef->constraints;
                                                coldef->is_from_parent = false;
                                                list_delete_cell(schema, rest, 
prev);
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index 0026aa11dd..4846a6fc98 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -662,6 +662,19 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) 
FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 
0) partition by list (a);
+create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (b 
default 1) for values in (1);
+-- note that while b's default is overriden, a's default is preserved
+\d parted_notnull_inh_test1
+      Table "public.parted_notnull_inh_test1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 1
+ b      | integer |           | not null | 1
+Partition of: parted_notnull_inh_test FOR VALUES IN (1)
+
+drop table parted_notnull_inh_test;
 -- Partition bound in describe output
 \d+ part_b
                                    Table "public.part_b"
diff --git a/src/test/regress/sql/create_table.sql 
b/src/test/regress/sql/create_table.sql
index ad83614137..7442c4f7d3 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -609,6 +609,13 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS 
NOT NULL DEFAULT 0) FOR
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 
0) partition by list (a);
+create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (b 
default 1) for values in (1);
+-- note that while b's default is overriden, a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+
 -- Partition bound in describe output
 \d+ part_b
 
-- 
2.11.0

From 6ab930fa7e8f6f5e548b783451f2ab7d4515909b Mon Sep 17 00:00:00 2001
From: amit <amitlangot...@gmail.com>
Date: Wed, 6 Jun 2018 16:46:46 +0900
Subject: [PATCH v2] Fix bug regarding partition column option inheritance

It appears that the coding pattern in MergeAttributes causes the
NOT NULL constraint and default value of a column from not being
properly inherited from the parent's definition.
---
 src/backend/commands/tablecmds.c           | 49 +++++++++++++++++++++++++++---
 src/test/regress/expected/create_table.out | 13 ++++++++
 src/test/regress/sql/create_table.sql      |  7 +++++
 3 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7c0cf0d7ee..9955e81d14 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -2394,6 +2394,15 @@ MergeAttributes(List *schema, List *supers, char 
relpersistence,
         */
        if (is_partition && list_length(saved_schema) > 0)
        {
+               /*
+                * Each member in 'saved_schema' contains a ColumnDef containing
+                * partition-specific options for the column.  Below, we merge 
the
+                * information from each into the ColumnDef of the same name 
found in
+                * the original 'schema' list before deleting it from the list. 
 So,
+                * once we've finished processing all the columns from the 
original
+                * 'schema' list, there shouldn't be any ColumnDefs left that 
came
+                * from the 'saved_schema' list.
+                */
                schema = list_concat(schema, saved_schema);
 
                foreach(entry, schema)
@@ -2421,14 +2430,44 @@ MergeAttributes(List *schema, List *supers, char 
relpersistence,
                                if (strcmp(coldef->colname, restdef->colname) 
== 0)
                                {
                                        /*
-                                        * merge the column options into the 
column from the
-                                        * parent
+                                        * Merge the column options specified 
for the partition
+                                        * into the column definition inherited 
from the parent.
                                         */
                                        if (coldef->is_from_parent)
                                        {
-                                               coldef->is_not_null = 
restdef->is_not_null;
-                                               coldef->raw_default = 
restdef->raw_default;
-                                               coldef->cooked_default = 
restdef->cooked_default;
+                                               /*
+                                                * Combine using OR so that the 
NOT NULL constraint
+                                                * in the parent's definition 
doesn't get lost.
+                                                */
+                                               coldef->is_not_null = 
(coldef->is_not_null ||
+                                                                               
           restdef->is_not_null);
+
+                                               /*
+                                                * If the partition's 
definition specifies a default,
+                                                * it's in 
restdef->raw_default, which if non-NULL,
+                                                * overrides the parent's 
default that's in
+                                                * coldef->cooked_default.
+                                                */
+                                               if (restdef->raw_default)
+                                               {
+                                                       coldef->raw_default = 
restdef->raw_default;
+                                                       coldef->cooked_default 
= NULL;
+                                               }
+                                               /*
+                                                * Don't accidentally lose the 
parent's default by
+                                                * overwriting with NULL.
+                                                */
+                                               else if 
(restdef->cooked_default)
+                                               {
+                                                       coldef->raw_default = 
NULL;
+                                                       coldef->cooked_default 
= restdef->cooked_default;
+                                               }
+
+                                               /*
+                                                * Parent's constraints, if 
any, have been saved in
+                                                * 'constraints', which are 
passed back to the caller,
+                                                * so it's okay to overwrite 
the variable like this.
+                                                */
                                                coldef->constraints = 
restdef->constraints;
                                                coldef->is_from_parent = false;
                                                list_delete_cell(schema, rest, 
prev);
diff --git a/src/test/regress/expected/create_table.out 
b/src/test/regress/expected/create_table.out
index 8fdbca1345..5750344e73 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -727,6 +727,19 @@ ERROR:  column "c" named in partition key does not exist
 CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) 
FOR VALUES IN ('c') PARTITION BY RANGE ((b));
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 
0) partition by list (a);
+create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (b 
default 1) for values in (1);
+-- note that while b's default is overriden, a's default is preserved
+\d parted_notnull_inh_test1
+      Table "public.parted_notnull_inh_test1"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 1
+ b      | integer |           | not null | 1
+Partition of: parted_notnull_inh_test FOR VALUES IN (1)
+
+drop table parted_notnull_inh_test;
 -- Partition bound in describe output
 \d+ part_b
                                    Table "public.part_b"
diff --git a/src/test/regress/sql/create_table.sql 
b/src/test/regress/sql/create_table.sql
index 78944950fe..5a64ecc480 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -659,6 +659,13 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS 
NOT NULL DEFAULT 0) FOR
 -- create a level-2 partition
 CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
 
+-- check that NOT NULL and default value are inherited correctly
+create table parted_notnull_inh_test (a int default 1, b int not null default 
0) partition by list (a);
+create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (b 
default 1) for values in (1);
+-- note that while b's default is overriden, a's default is preserved
+\d parted_notnull_inh_test1
+drop table parted_notnull_inh_test;
+
 -- Partition bound in describe output
 \d+ part_b
 
-- 
2.11.0

Reply via email to