Skip to content

Commit 21c9e49

Browse files
committed
Revise attribute handling code on partition creation
The original code to propagate NOT NULL and default expressions specified when creating a partition was mostly copy-pasted from typed-tables creation, but not being a great match it contained some duplicity, inefficiency and bugs. This commit fixes the bug that NOT NULL constraints declared in the parent table would not be honored in the partition. One reported issue that is not fixed is that a DEFAULT declared in the child is not used when inserting through the parent. That would amount to a behavioral change that's better not back-patched. This rewrite makes the code simpler: 1. instead of checking for duplicate column names in its own block, reuse the original one that already did that; 2. instead of concatenating the list of columns from parent and the one declared in the partition and scanning the result to (incorrectly) propagate defaults and not-null constraints, just scan the latter searching the former for a match, and merging sensibly. This works because we know the list in the parent is already correct and there can only be one parent. This rewrite makes ColumnDef->is_from_parent unused, so it's removed on branch master; on released branches, it's kept as an unused field in order not to cause ABI incompatibilities. This commit also adds a test case for creating partitions with collations mismatching that on the parent table, something that is closely related to the code being patched. No code change is introduced though, since that'd be a behavior change that could break some (broken) working applications. Amit Langote wrote a less invasive fix for the original NOT NULL/defaults bug, but while I kept the tests he added, I ended up not using his original code. Ashutosh Bapat reviewed Amit's fix. Amit reviewed mine. Author: Álvaro Herrera, Amit Langote Reviewed-by: Ashutosh Bapat, Amit Langote Reported-by: Jürgen Strobel (bug #15212) Discussion: https://postgr.es/m/152746742177.1291.9847032632907407358@wrigleys.postgresql.org
1 parent 018e5fe commit 21c9e49

File tree

9 files changed

+92
-63
lines changed

9 files changed

+92
-63
lines changed

src/backend/commands/sequence.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
172172
coldef->is_local = true;
173173
coldef->is_not_null = true;
174174
coldef->is_from_type = false;
175-
coldef->is_from_parent = false;
176175
coldef->storage = 0;
177176
coldef->raw_default = NULL;
178177
coldef->cooked_default = NULL;

src/backend/commands/tablecmds.c

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,17 +1649,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
16491649
errmsg("tables can have at most %d columns",
16501650
MaxHeapAttributeNumber)));
16511651

1652-
/*
1653-
* In case of a partition, there are no new column definitions, only dummy
1654-
* ColumnDefs created for column constraints. We merge them with the
1655-
* constraints inherited from the parent.
1656-
*/
1657-
if (is_partition)
1658-
{
1659-
saved_schema = schema;
1660-
schema = NIL;
1661-
}
1662-
16631652
/*
16641653
* Check for duplicate names in the explicit list of attributes.
16651654
*
@@ -1673,17 +1662,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
16731662
ListCell *rest = lnext(entry);
16741663
ListCell *prev = entry;
16751664

1676-
if (coldef->typeName == NULL)
1677-
1665+
if (!is_partition && coldef->typeName == NULL)
1666+
{
16781667
/*
16791668
* Typed table column option that does not belong to a column from
16801669
* the type. This works because the columns from the type come
1681-
* first in the list.
1670+
* first in the list. (We omit this check for partition column
1671+
* lists; those are processed separately below.)
16821672
*/
16831673
ereport(ERROR,
16841674
(errcode(ERRCODE_UNDEFINED_COLUMN),
16851675
errmsg("column \"%s\" does not exist",
16861676
coldef->colname)));
1677+
}
16871678

16881679
while (rest != NULL)
16891680
{
@@ -1716,6 +1707,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
17161707
}
17171708
}
17181709

1710+
/*
1711+
* In case of a partition, there are no new column definitions, only dummy
1712+
* ColumnDefs created for column constraints. Set them aside for now and
1713+
* process them at the end.
1714+
*/
1715+
if (is_partition)
1716+
{
1717+
saved_schema = schema;
1718+
schema = NIL;
1719+
}
1720+
17191721
/*
17201722
* Scan the parents left-to-right, and merge their attributes to form a
17211723
* list of inherited attributes (inhSchema). Also check to see if we need
@@ -1931,7 +1933,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
19311933
def->is_local = false;
19321934
def->is_not_null = attribute->attnotnull;
19331935
def->is_from_type = false;
1934-
def->is_from_parent = true;
19351936
def->storage = attribute->attstorage;
19361937
def->raw_default = NULL;
19371938
def->cooked_default = NULL;
@@ -2184,59 +2185,51 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
21842185
/*
21852186
* Now that we have the column definition list for a partition, we can
21862187
* check whether the columns referenced in the column constraint specs
2187-
* actually exist. Also, we merge the constraints into the corresponding
2188-
* column definitions.
2188+
* actually exist. Also, we merge NOT NULL and defaults into each
2189+
* corresponding column definition.
21892190
*/
2190-
if (is_partition && list_length(saved_schema) > 0)
2191+
if (is_partition)
21912192
{
2192-
schema = list_concat(schema, saved_schema);
2193-
2194-
foreach(entry, schema)
2193+
foreach(entry, saved_schema)
21952194
{
2196-
ColumnDef *coldef = lfirst(entry);
2197-
ListCell *rest = lnext(entry);
2198-
ListCell *prev = entry;
2195+
ColumnDef *restdef = lfirst(entry);
2196+
bool found = false;
2197+
ListCell *l;
21992198

2200-
/*
2201-
* Partition column option that does not belong to a column from
2202-
* the parent. This works because the columns from the parent
2203-
* come first in the list (see above).
2204-
*/
2205-
if (coldef->typeName == NULL)
2206-
ereport(ERROR,
2207-
(errcode(ERRCODE_UNDEFINED_COLUMN),
2208-
errmsg("column \"%s\" does not exist",
2209-
coldef->colname)));
2210-
while (rest != NULL)
2199+
foreach(l, schema)
22112200
{
2212-
ColumnDef *restdef = lfirst(rest);
2213-
ListCell *next = lnext(rest); /* need to save it in case we
2214-
* delete it */
2201+
ColumnDef *coldef = lfirst(l);
22152202

22162203
if (strcmp(coldef->colname, restdef->colname) == 0)
22172204
{
2205+
found = true;
2206+
coldef->is_not_null |= restdef->is_not_null;
2207+
22182208
/*
2219-
* merge the column options into the column from the
2220-
* parent
2209+
* Override the parent's default value for this column
2210+
* (coldef->cooked_default) with the partition's local
2211+
* definition (restdef->raw_default), if there's one. It
2212+
* should be physically impossible to get a cooked default
2213+
* in the local definition or a raw default in the
2214+
* inherited definition, but make sure they're nulls, for
2215+
* future-proofing.
22212216
*/
2222-
if (coldef->is_from_parent)
2217+
Assert(restdef->cooked_default == NULL);
2218+
Assert(coldef->raw_default == NULL);
2219+
if (restdef->raw_default)
22232220
{
2224-
coldef->is_not_null = restdef->is_not_null;
22252221
coldef->raw_default = restdef->raw_default;
2226-
coldef->cooked_default = restdef->cooked_default;
2227-
coldef->constraints = restdef->constraints;
2228-
coldef->is_from_parent = false;
2229-
list_delete_cell(schema, rest, prev);
2222+
coldef->cooked_default = NULL;
22302223
}
2231-
else
2232-
ereport(ERROR,
2233-
(errcode(ERRCODE_DUPLICATE_COLUMN),
2234-
errmsg("column \"%s\" specified more than once",
2235-
coldef->colname)));
22362224
}
2237-
prev = rest;
2238-
rest = next;
22392225
}
2226+
2227+
/* complain for constraints on columns not in parent */
2228+
if (!found)
2229+
ereport(ERROR,
2230+
(errcode(ERRCODE_UNDEFINED_COLUMN),
2231+
errmsg("column \"%s\" does not exist",
2232+
restdef->colname)));
22402233
}
22412234
}
22422235

src/backend/nodes/equalfuncs.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2539,7 +2539,6 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
25392539
COMPARE_SCALAR_FIELD(is_local);
25402540
COMPARE_SCALAR_FIELD(is_not_null);
25412541
COMPARE_SCALAR_FIELD(is_from_type);
2542-
COMPARE_SCALAR_FIELD(is_from_parent);
25432542
COMPARE_SCALAR_FIELD(storage);
25442543
COMPARE_NODE_FIELD(raw_default);
25452544
COMPARE_NODE_FIELD(cooked_default);

src/backend/nodes/makefuncs.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,6 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
494494
n->is_local = true;
495495
n->is_not_null = false;
496496
n->is_from_type = false;
497-
n->is_from_parent = false;
498497
n->storage = 0;
499498
n->raw_default = NULL;
500499
n->cooked_default = NULL;

src/backend/parser/gram.y

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3240,7 +3240,6 @@ columnDef: ColId Typename create_generic_options ColQualList
32403240
n->is_local = true;
32413241
n->is_not_null = false;
32423242
n->is_from_type = false;
3243-
n->is_from_parent = false;
32443243
n->storage = 0;
32453244
n->raw_default = NULL;
32463245
n->cooked_default = NULL;
@@ -3262,7 +3261,6 @@ columnOptions: ColId ColQualList
32623261
n->is_local = true;
32633262
n->is_not_null = false;
32643263
n->is_from_type = false;
3265-
n->is_from_parent = false;
32663264
n->storage = 0;
32673265
n->raw_default = NULL;
32683266
n->cooked_default = NULL;
@@ -3281,7 +3279,6 @@ columnOptions: ColId ColQualList
32813279
n->is_local = true;
32823280
n->is_not_null = false;
32833281
n->is_from_type = false;
3284-
n->is_from_parent = false;
32853282
n->storage = 0;
32863283
n->raw_default = NULL;
32873284
n->cooked_default = NULL;
@@ -11884,7 +11881,6 @@ TableFuncElement: ColId Typename opt_collate_clause
1188411881
n->is_local = true;
1188511882
n->is_not_null = false;
1188611883
n->is_from_type = false;
11887-
n->is_from_parent = false;
1188811884
n->storage = 0;
1188911885
n->raw_default = NULL;
1189011886
n->cooked_default = NULL;

src/backend/parser/parse_utilcmd.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
10251025
def->is_local = true;
10261026
def->is_not_null = attribute->attnotnull;
10271027
def->is_from_type = false;
1028-
def->is_from_parent = false;
10291028
def->storage = 0;
10301029
def->raw_default = NULL;
10311030
def->cooked_default = NULL;
@@ -1293,7 +1292,6 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
12931292
n->is_local = true;
12941293
n->is_not_null = false;
12951294
n->is_from_type = true;
1296-
n->is_from_parent = false;
12971295
n->storage = 0;
12981296
n->raw_default = NULL;
12991297
n->cooked_default = NULL;

src/include/nodes/parsenodes.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ typedef struct ColumnDef
642642
bool is_local; /* column has local (non-inherited) def'n */
643643
bool is_not_null; /* NOT NULL constraint specified? */
644644
bool is_from_type; /* column definition came from table type */
645-
bool is_from_parent; /* column def came from partition parent */
645+
bool is_from_parent; /* XXX unused */
646646
char storage; /* attstorage setting, or 0 for default */
647647
Node *raw_default; /* default value (untransformed parse tree) */
648648
Node *cooked_default; /* default value (transformed expr tree) */

src/test/regress/expected/create_table.out

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,32 @@ ERROR: column "c" named in partition key does not exist
657657
CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
658658
-- create a level-2 partition
659659
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
660+
-- check that NOT NULL and default value are inherited correctly
661+
create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a);
662+
create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (a not null, b default 1) for values in (1);
663+
insert into parted_notnull_inh_test (b) values (null);
664+
ERROR: null value in column "b" violates not-null constraint
665+
DETAIL: Failing row contains (1, null).
666+
-- note that while b's default is overriden, a's default is preserved
667+
\d parted_notnull_inh_test1
668+
Table "public.parted_notnull_inh_test1"
669+
Column | Type | Collation | Nullable | Default
670+
--------+---------+-----------+----------+---------
671+
a | integer | | not null | 1
672+
b | integer | | not null | 1
673+
Partition of: parted_notnull_inh_test FOR VALUES IN (1)
674+
675+
drop table parted_notnull_inh_test;
676+
-- check for a conflicting COLLATE clause
677+
create table parted_collate_must_match (a text collate "C", b text collate "C")
678+
partition by range (a);
679+
-- on the partition key
680+
create table parted_collate_must_match1 partition of parted_collate_must_match
681+
(a collate "POSIX") for values from ('a') to ('m');
682+
-- on another column
683+
create table parted_collate_must_match2 partition of parted_collate_must_match
684+
(b collate "POSIX") for values from ('m') to ('z');
685+
drop table parted_collate_must_match;
660686
-- Partition bound in describe output
661687
\d+ part_b
662688
Table "public.part_b"

src/test/regress/sql/create_table.sql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,25 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR
604604
-- create a level-2 partition
605605
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
606606

607+
-- check that NOT NULL and default value are inherited correctly
608+
create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a);
609+
create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (a not null, b default 1) for values in (1);
610+
insert into parted_notnull_inh_test (b) values (null);
611+
-- note that while b's default is overriden, a's default is preserved
612+
\d parted_notnull_inh_test1
613+
drop table parted_notnull_inh_test;
614+
615+
-- check for a conflicting COLLATE clause
616+
create table parted_collate_must_match (a text collate "C", b text collate "C")
617+
partition by range (a);
618+
-- on the partition key
619+
create table parted_collate_must_match1 partition of parted_collate_must_match
620+
(a collate "POSIX") for values from ('a') to ('m');
621+
-- on another column
622+
create table parted_collate_must_match2 partition of parted_collate_must_match
623+
(b collate "POSIX") for values from ('m') to ('z');
624+
drop table parted_collate_must_match;
625+
607626
-- Partition bound in describe output
608627
\d+ part_b
609628

0 commit comments

Comments
 (0)