Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e0c05bf

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 d06fe6c commit e0c05bf

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
@@ -1862,17 +1862,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
18621862
errmsg("tables can have at most %d columns",
18631863
MaxHeapAttributeNumber)));
18641864

1865-
/*
1866-
* In case of a partition, there are no new column definitions, only dummy
1867-
* ColumnDefs created for column constraints. We merge them with the
1868-
* constraints inherited from the parent.
1869-
*/
1870-
if (is_partition)
1871-
{
1872-
saved_schema = schema;
1873-
schema = NIL;
1874-
}
1875-
18761865
/*
18771866
* Check for duplicate names in the explicit list of attributes.
18781867
*
@@ -1886,17 +1875,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
18861875
ListCell *rest = lnext(entry);
18871876
ListCell *prev = entry;
18881877

1889-
if (coldef->typeName == NULL)
1890-
1878+
if (!is_partition && coldef->typeName == NULL)
1879+
{
18911880
/*
18921881
* Typed table column option that does not belong to a column from
18931882
* the type. This works because the columns from the type come
1894-
* first in the list.
1883+
* first in the list. (We omit this check for partition column
1884+
* lists; those are processed separately below.)
18951885
*/
18961886
ereport(ERROR,
18971887
(errcode(ERRCODE_UNDEFINED_COLUMN),
18981888
errmsg("column \"%s\" does not exist",
18991889
coldef->colname)));
1890+
}
19001891

19011892
while (rest != NULL)
19021893
{
@@ -1929,6 +1920,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
19291920
}
19301921
}
19311922

1923+
/*
1924+
* In case of a partition, there are no new column definitions, only dummy
1925+
* ColumnDefs created for column constraints. Set them aside for now and
1926+
* process them at the end.
1927+
*/
1928+
if (is_partition)
1929+
{
1930+
saved_schema = schema;
1931+
schema = NIL;
1932+
}
1933+
19321934
/*
19331935
* Scan the parents left-to-right, and merge their attributes to form a
19341936
* list of inherited attributes (inhSchema). Also check to see if we need
@@ -2145,7 +2147,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
21452147
def->is_local = false;
21462148
def->is_not_null = attribute->attnotnull;
21472149
def->is_from_type = false;
2148-
def->is_from_parent = true;
21492150
def->storage = attribute->attstorage;
21502151
def->raw_default = NULL;
21512152
def->cooked_default = NULL;
@@ -2398,59 +2399,51 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
23982399
/*
23992400
* Now that we have the column definition list for a partition, we can
24002401
* check whether the columns referenced in the column constraint specs
2401-
* actually exist. Also, we merge the constraints into the corresponding
2402-
* column definitions.
2402+
* actually exist. Also, we merge NOT NULL and defaults into each
2403+
* corresponding column definition.
24032404
*/
2404-
if (is_partition && list_length(saved_schema) > 0)
2405+
if (is_partition)
24052406
{
2406-
schema = list_concat(schema, saved_schema);
2407-
2408-
foreach(entry, schema)
2407+
foreach(entry, saved_schema)
24092408
{
2410-
ColumnDef *coldef = lfirst(entry);
2411-
ListCell *rest = lnext(entry);
2412-
ListCell *prev = entry;
2409+
ColumnDef *restdef = lfirst(entry);
2410+
bool found = false;
2411+
ListCell *l;
24132412

2414-
/*
2415-
* Partition column option that does not belong to a column from
2416-
* the parent. This works because the columns from the parent
2417-
* come first in the list (see above).
2418-
*/
2419-
if (coldef->typeName == NULL)
2420-
ereport(ERROR,
2421-
(errcode(ERRCODE_UNDEFINED_COLUMN),
2422-
errmsg("column \"%s\" does not exist",
2423-
coldef->colname)));
2424-
while (rest != NULL)
2413+
foreach(l, schema)
24252414
{
2426-
ColumnDef *restdef = lfirst(rest);
2427-
ListCell *next = lnext(rest); /* need to save it in case we
2428-
* delete it */
2415+
ColumnDef *coldef = lfirst(l);
24292416

24302417
if (strcmp(coldef->colname, restdef->colname) == 0)
24312418
{
2419+
found = true;
2420+
coldef->is_not_null |= restdef->is_not_null;
2421+
24322422
/*
2433-
* merge the column options into the column from the
2434-
* parent
2423+
* Override the parent's default value for this column
2424+
* (coldef->cooked_default) with the partition's local
2425+
* definition (restdef->raw_default), if there's one. It
2426+
* should be physically impossible to get a cooked default
2427+
* in the local definition or a raw default in the
2428+
* inherited definition, but make sure they're nulls, for
2429+
* future-proofing.
24352430
*/
2436-
if (coldef->is_from_parent)
2431+
Assert(restdef->cooked_default == NULL);
2432+
Assert(coldef->raw_default == NULL);
2433+
if (restdef->raw_default)
24372434
{
2438-
coldef->is_not_null = restdef->is_not_null;
24392435
coldef->raw_default = restdef->raw_default;
2440-
coldef->cooked_default = restdef->cooked_default;
2441-
coldef->constraints = restdef->constraints;
2442-
coldef->is_from_parent = false;
2443-
list_delete_cell(schema, rest, prev);
2436+
coldef->cooked_default = NULL;
24442437
}
2445-
else
2446-
ereport(ERROR,
2447-
(errcode(ERRCODE_DUPLICATE_COLUMN),
2448-
errmsg("column \"%s\" specified more than once",
2449-
coldef->colname)));
24502438
}
2451-
prev = rest;
2452-
rest = next;
24532439
}
2440+
2441+
/* complain for constraints on columns not in parent */
2442+
if (!found)
2443+
ereport(ERROR,
2444+
(errcode(ERRCODE_UNDEFINED_COLUMN),
2445+
errmsg("column \"%s\" does not exist",
2446+
restdef->colname)));
24542447
}
24552448
}
24562449

src/backend/nodes/equalfuncs.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2553,7 +2553,6 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
25532553
COMPARE_SCALAR_FIELD(is_local);
25542554
COMPARE_SCALAR_FIELD(is_not_null);
25552555
COMPARE_SCALAR_FIELD(is_from_type);
2556-
COMPARE_SCALAR_FIELD(is_from_parent);
25572556
COMPARE_SCALAR_FIELD(storage);
25582557
COMPARE_NODE_FIELD(raw_default);
25592558
COMPARE_NODE_FIELD(cooked_default);

src/backend/nodes/makefuncs.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,6 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
496496
n->is_local = true;
497497
n->is_not_null = false;
498498
n->is_from_type = false;
499-
n->is_from_parent = false;
500499
n->storage = 0;
501500
n->raw_default = NULL;
502501
n->cooked_default = NULL;

src/backend/parser/gram.y

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3389,7 +3389,6 @@ columnDef: ColId Typename create_generic_options ColQualList
33893389
n->is_local = true;
33903390
n->is_not_null = false;
33913391
n->is_from_type = false;
3392-
n->is_from_parent = false;
33933392
n->storage = 0;
33943393
n->raw_default = NULL;
33953394
n->cooked_default = NULL;
@@ -3411,7 +3410,6 @@ columnOptions: ColId ColQualList
34113410
n->is_local = true;
34123411
n->is_not_null = false;
34133412
n->is_from_type = false;
3414-
n->is_from_parent = false;
34153413
n->storage = 0;
34163414
n->raw_default = NULL;
34173415
n->cooked_default = NULL;
@@ -3430,7 +3428,6 @@ columnOptions: ColId ColQualList
34303428
n->is_local = true;
34313429
n->is_not_null = false;
34323430
n->is_from_type = false;
3433-
n->is_from_parent = false;
34343431
n->storage = 0;
34353432
n->raw_default = NULL;
34363433
n->cooked_default = NULL;
@@ -12274,7 +12271,6 @@ TableFuncElement: ColId Typename opt_collate_clause
1227412271
n->is_local = true;
1227512272
n->is_not_null = false;
1227612273
n->is_from_type = false;
12277-
n->is_from_parent = false;
1227812274
n->storage = 0;
1227912275
n->raw_default = NULL;
1228012276
n->cooked_default = NULL;

src/backend/parser/parse_utilcmd.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -988,7 +988,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
988988
def->is_local = true;
989989
def->is_not_null = attribute->attnotnull;
990990
def->is_from_type = false;
991-
def->is_from_parent = false;
992991
def->storage = 0;
993992
def->raw_default = NULL;
994993
def->cooked_default = NULL;
@@ -1265,7 +1264,6 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
12651264
n->is_local = true;
12661265
n->is_not_null = false;
12671266
n->is_from_type = true;
1268-
n->is_from_parent = false;
12691267
n->storage = 0;
12701268
n->raw_default = NULL;
12711269
n->cooked_default = NULL;

src/include/nodes/parsenodes.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ typedef struct ColumnDef
650650
bool is_local; /* column has local (non-inherited) def'n */
651651
bool is_not_null; /* NOT NULL constraint specified? */
652652
bool is_from_type; /* column definition came from table type */
653-
bool is_from_parent; /* column def came from partition parent */
653+
bool is_from_parent; /* XXX unused */
654654
char storage; /* attstorage setting, or 0 for default */
655655
Node *raw_default; /* default value (untransformed parse tree) */
656656
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
@@ -722,6 +722,32 @@ ERROR: column "c" named in partition key does not exist
722722
CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR VALUES IN ('c') PARTITION BY RANGE ((b));
723723
-- create a level-2 partition
724724
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
725+
-- check that NOT NULL and default value are inherited correctly
726+
create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a);
727+
create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (a not null, b default 1) for values in (1);
728+
insert into parted_notnull_inh_test (b) values (null);
729+
ERROR: null value in column "b" violates not-null constraint
730+
DETAIL: Failing row contains (1, null).
731+
-- note that while b's default is overriden, a's default is preserved
732+
\d parted_notnull_inh_test1
733+
Table "public.parted_notnull_inh_test1"
734+
Column | Type | Collation | Nullable | Default
735+
--------+---------+-----------+----------+---------
736+
a | integer | | not null | 1
737+
b | integer | | not null | 1
738+
Partition of: parted_notnull_inh_test FOR VALUES IN (1)
739+
740+
drop table parted_notnull_inh_test;
741+
-- check for a conflicting COLLATE clause
742+
create table parted_collate_must_match (a text collate "C", b text collate "C")
743+
partition by range (a);
744+
-- on the partition key
745+
create table parted_collate_must_match1 partition of parted_collate_must_match
746+
(a collate "POSIX") for values from ('a') to ('m');
747+
-- on another column
748+
create table parted_collate_must_match2 partition of parted_collate_must_match
749+
(b collate "POSIX") for values from ('m') to ('z');
750+
drop table parted_collate_must_match;
725751
-- Partition bound in describe output
726752
\d+ part_b
727753
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
@@ -654,6 +654,25 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR
654654
-- create a level-2 partition
655655
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
656656

657+
-- check that NOT NULL and default value are inherited correctly
658+
create table parted_notnull_inh_test (a int default 1, b int not null default 0) partition by list (a);
659+
create table parted_notnull_inh_test1 partition of parted_notnull_inh_test (a not null, b default 1) for values in (1);
660+
insert into parted_notnull_inh_test (b) values (null);
661+
-- note that while b's default is overriden, a's default is preserved
662+
\d parted_notnull_inh_test1
663+
drop table parted_notnull_inh_test;
664+
665+
-- check for a conflicting COLLATE clause
666+
create table parted_collate_must_match (a text collate "C", b text collate "C")
667+
partition by range (a);
668+
-- on the partition key
669+
create table parted_collate_must_match1 partition of parted_collate_must_match
670+
(a collate "POSIX") for values from ('a') to ('m');
671+
-- on another column
672+
create table parted_collate_must_match2 partition of parted_collate_must_match
673+
(b collate "POSIX") for values from ('m') to ('z');
674+
drop table parted_collate_must_match;
675+
657676
-- Partition bound in describe output
658677
\d+ part_b
659678

0 commit comments

Comments
 (0)