diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 2f3ee462361..74cb2e61bcd 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -172,7 +172,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) coldef->is_local = true; coldef->is_not_null = true; coldef->is_from_type = false; - coldef->is_from_parent = false; coldef->storage = 0; coldef->raw_default = NULL; coldef->cooked_default = NULL; diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 971a8721e1c..b4a19771a28 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -1649,17 +1649,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence, errmsg("tables can have at most %d columns", MaxHeapAttributeNumber))); - /* - * In case of a partition, there are no new column definitions, only dummy - * ColumnDefs created for column constraints. We merge them with the - * constraints inherited from the parent. - */ - if (is_partition) - { - saved_schema = schema; - schema = NIL; - } - /* * Check for duplicate names in the explicit list of attributes. * @@ -1673,17 +1662,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence, ListCell *rest = lnext(entry); ListCell *prev = entry; - if (coldef->typeName == NULL) - + if (!is_partition && coldef->typeName == NULL) + { /* * Typed table column option that does not belong to a column from * the type. This works because the columns from the type come - * first in the list. + * first in the list. (We omit this check for partition column + * lists; those are processed separately below.) */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" does not exist", coldef->colname))); + } while (rest != NULL) { @@ -1716,6 +1707,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence, } } + /* + * In case of a partition, there are no new column definitions, only dummy + * ColumnDefs created for column constraints. Set them aside for now and + * process them at the end. + */ + if (is_partition) + { + saved_schema = schema; + schema = NIL; + } + /* * Scan the parents left-to-right, and merge their attributes to form a * list of inherited attributes (inhSchema). Also check to see if we need @@ -1931,7 +1933,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence, def->is_local = false; def->is_not_null = attribute->attnotnull; def->is_from_type = false; - def->is_from_parent = true; def->storage = attribute->attstorage; def->raw_default = NULL; def->cooked_default = NULL; @@ -2184,59 +2185,51 @@ MergeAttributes(List *schema, List *supers, char relpersistence, /* * Now that we have the column definition list for a partition, we can * check whether the columns referenced in the column constraint specs - * actually exist. Also, we merge the constraints into the corresponding - * column definitions. + * actually exist. Also, we merge NOT NULL and defaults into each + * corresponding column definition. */ - if (is_partition && list_length(saved_schema) > 0) + if (is_partition) { - schema = list_concat(schema, saved_schema); - - foreach(entry, schema) + foreach(entry, saved_schema) { - ColumnDef *coldef = lfirst(entry); - ListCell *rest = lnext(entry); - ListCell *prev = entry; + ColumnDef *restdef = lfirst(entry); + bool found = false; + ListCell *l; - /* - * Partition column option that does not belong to a column from - * the parent. This works because the columns from the parent - * come first in the list (see above). - */ - if (coldef->typeName == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" does not exist", - coldef->colname))); - while (rest != NULL) + foreach(l, schema) { - ColumnDef *restdef = lfirst(rest); - ListCell *next = lnext(rest); /* need to save it in case we - * delete it */ + ColumnDef *coldef = lfirst(l); if (strcmp(coldef->colname, restdef->colname) == 0) { + found = true; + coldef->is_not_null |= restdef->is_not_null; + /* - * merge the column options into the column from the - * parent + * Override the parent's default value for this column + * (coldef->cooked_default) with the partition's local + * definition (restdef->raw_default), if there's one. It + * should be physically impossible to get a cooked default + * in the local definition or a raw default in the + * inherited definition, but make sure they're nulls, for + * future-proofing. */ - if (coldef->is_from_parent) + Assert(restdef->cooked_default == NULL); + Assert(coldef->raw_default == NULL); + if (restdef->raw_default) { - coldef->is_not_null = restdef->is_not_null; coldef->raw_default = restdef->raw_default; - coldef->cooked_default = restdef->cooked_default; - coldef->constraints = restdef->constraints; - coldef->is_from_parent = false; - list_delete_cell(schema, rest, prev); + coldef->cooked_default = NULL; } - else - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_COLUMN), - errmsg("column \"%s\" specified more than once", - coldef->colname))); } - prev = rest; - rest = next; } + + /* complain for constraints on columns not in parent */ + if (!found) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" does not exist", + restdef->colname))); } } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 68d38fcba1a..c5bb817ba19 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2539,7 +2539,6 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b) COMPARE_SCALAR_FIELD(is_local); COMPARE_SCALAR_FIELD(is_not_null); COMPARE_SCALAR_FIELD(is_from_type); - COMPARE_SCALAR_FIELD(is_from_parent); COMPARE_SCALAR_FIELD(storage); COMPARE_NODE_FIELD(raw_default); COMPARE_NODE_FIELD(cooked_default); diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c index 0755039da9f..87ffe825304 100644 --- a/src/backend/nodes/makefuncs.c +++ b/src/backend/nodes/makefuncs.c @@ -494,7 +494,6 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid) n->is_local = true; n->is_not_null = false; n->is_from_type = false; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 025e82b30fa..a97f51ce37c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3240,7 +3240,6 @@ columnDef: ColId Typename create_generic_options ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -3262,7 +3261,6 @@ columnOptions: ColId ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -3281,7 +3279,6 @@ columnOptions: ColId ColQualList n->is_local = true; n->is_not_null = false; n->is_from_type = false; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; @@ -11884,7 +11881,6 @@ TableFuncElement: ColId Typename opt_collate_clause n->is_local = true; n->is_not_null = false; n->is_from_type = false; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index b3367f0cd4b..0a03658e663 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -1025,7 +1025,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla def->is_local = true; def->is_not_null = attribute->attnotnull; def->is_from_type = false; - def->is_from_parent = false; def->storage = 0; def->raw_default = NULL; def->cooked_default = NULL; @@ -1293,7 +1292,6 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename) n->is_local = true; n->is_not_null = false; n->is_from_type = true; - n->is_from_parent = false; n->storage = 0; n->raw_default = NULL; n->cooked_default = NULL; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 1f80dfaa85d..57fa4db0d3f 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -642,7 +642,7 @@ typedef struct ColumnDef bool is_local; /* column has local (non-inherited) def'n */ bool is_not_null; /* NOT NULL constraint specified? */ bool is_from_type; /* column definition came from table type */ - bool is_from_parent; /* column def came from partition parent */ + bool is_from_parent; /* XXX unused */ char storage; /* attstorage setting, or 0 for default */ Node *raw_default; /* default value (untransformed parse tree) */ Node *cooked_default; /* default value (transformed expr tree) */ diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out index 481510c2bb1..28c9d2df898 100644 --- a/src/test/regress/expected/create_table.out +++ b/src/test/regress/expected/create_table.out @@ -657,6 +657,32 @@ 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 (a not null, b default 1) for values in (1); +insert into parted_notnull_inh_test (b) values (null); +ERROR: null value in column "b" violates not-null constraint +DETAIL: Failing row contains (1, null). +-- 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 | | not null | 1 + b | integer | | not null | 1 +Partition of: parted_notnull_inh_test FOR VALUES IN (1) + +drop table parted_notnull_inh_test; +-- check for a conflicting COLLATE clause +create table parted_collate_must_match (a text collate "C", b text collate "C") + partition by range (a); +-- on the partition key +create table parted_collate_must_match1 partition of parted_collate_must_match + (a collate "POSIX") for values from ('a') to ('m'); +-- on another column +create table parted_collate_must_match2 partition of parted_collate_must_match + (b collate "POSIX") for values from ('m') to ('z'); +drop table parted_collate_must_match; -- 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 bc6191f4ace..7849139ca15 100644 --- a/src/test/regress/sql/create_table.sql +++ b/src/test/regress/sql/create_table.sql @@ -604,6 +604,25 @@ 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 (a not null, b default 1) for values in (1); +insert into parted_notnull_inh_test (b) values (null); +-- note that while b's default is overriden, a's default is preserved +\d parted_notnull_inh_test1 +drop table parted_notnull_inh_test; + +-- check for a conflicting COLLATE clause +create table parted_collate_must_match (a text collate "C", b text collate "C") + partition by range (a); +-- on the partition key +create table parted_collate_must_match1 partition of parted_collate_must_match + (a collate "POSIX") for values from ('a') to ('m'); +-- on another column +create table parted_collate_must_match2 partition of parted_collate_must_match + (b collate "POSIX") for values from ('m') to ('z'); +drop table parted_collate_must_match; + -- Partition bound in describe output \d+ part_b