mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-25 19:07:19 +02:00
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
This commit is contained in:
parent
d06fe6ce2c
commit
e0c05bf4a7
@ -172,7 +172,6 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
|
|||||||
coldef->is_local = true;
|
coldef->is_local = true;
|
||||||
coldef->is_not_null = true;
|
coldef->is_not_null = true;
|
||||||
coldef->is_from_type = false;
|
coldef->is_from_type = false;
|
||||||
coldef->is_from_parent = false;
|
|
||||||
coldef->storage = 0;
|
coldef->storage = 0;
|
||||||
coldef->raw_default = NULL;
|
coldef->raw_default = NULL;
|
||||||
coldef->cooked_default = NULL;
|
coldef->cooked_default = NULL;
|
||||||
|
@ -1862,17 +1862,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
|||||||
errmsg("tables can have at most %d columns",
|
errmsg("tables can have at most %d columns",
|
||||||
MaxHeapAttributeNumber)));
|
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.
|
* Check for duplicate names in the explicit list of attributes.
|
||||||
*
|
*
|
||||||
@ -1886,17 +1875,19 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
|||||||
ListCell *rest = lnext(entry);
|
ListCell *rest = lnext(entry);
|
||||||
ListCell *prev = 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
|
* Typed table column option that does not belong to a column from
|
||||||
* the type. This works because the columns from the type come
|
* 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,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
(errcode(ERRCODE_UNDEFINED_COLUMN),
|
||||||
errmsg("column \"%s\" does not exist",
|
errmsg("column \"%s\" does not exist",
|
||||||
coldef->colname)));
|
coldef->colname)));
|
||||||
|
}
|
||||||
|
|
||||||
while (rest != NULL)
|
while (rest != NULL)
|
||||||
{
|
{
|
||||||
@ -1929,6 +1920,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
|
* 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
|
* list of inherited attributes (inhSchema). Also check to see if we need
|
||||||
@ -2145,7 +2147,6 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
|||||||
def->is_local = false;
|
def->is_local = false;
|
||||||
def->is_not_null = attribute->attnotnull;
|
def->is_not_null = attribute->attnotnull;
|
||||||
def->is_from_type = false;
|
def->is_from_type = false;
|
||||||
def->is_from_parent = true;
|
|
||||||
def->storage = attribute->attstorage;
|
def->storage = attribute->attstorage;
|
||||||
def->raw_default = NULL;
|
def->raw_default = NULL;
|
||||||
def->cooked_default = NULL;
|
def->cooked_default = NULL;
|
||||||
@ -2398,59 +2399,51 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
|
|||||||
/*
|
/*
|
||||||
* Now that we have the column definition list for a partition, we can
|
* Now that we have the column definition list for a partition, we can
|
||||||
* check whether the columns referenced in the column constraint specs
|
* check whether the columns referenced in the column constraint specs
|
||||||
* actually exist. Also, we merge the constraints into the corresponding
|
* actually exist. Also, we merge NOT NULL and defaults into each
|
||||||
* column definitions.
|
* corresponding column definition.
|
||||||
*/
|
*/
|
||||||
if (is_partition && list_length(saved_schema) > 0)
|
if (is_partition)
|
||||||
{
|
{
|
||||||
schema = list_concat(schema, saved_schema);
|
foreach(entry, saved_schema)
|
||||||
|
|
||||||
foreach(entry, schema)
|
|
||||||
{
|
{
|
||||||
ColumnDef *coldef = lfirst(entry);
|
ColumnDef *restdef = lfirst(entry);
|
||||||
ListCell *rest = lnext(entry);
|
bool found = false;
|
||||||
ListCell *prev = entry;
|
ListCell *l;
|
||||||
|
|
||||||
/*
|
foreach(l, schema)
|
||||||
* 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)
|
|
||||||
{
|
{
|
||||||
ColumnDef *restdef = lfirst(rest);
|
ColumnDef *coldef = lfirst(l);
|
||||||
ListCell *next = lnext(rest); /* need to save it in case we
|
|
||||||
* delete it */
|
|
||||||
|
|
||||||
if (strcmp(coldef->colname, restdef->colname) == 0)
|
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
|
* Override the parent's default value for this column
|
||||||
* parent
|
* (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->raw_default = restdef->raw_default;
|
||||||
coldef->cooked_default = restdef->cooked_default;
|
coldef->cooked_default = NULL;
|
||||||
coldef->constraints = restdef->constraints;
|
|
||||||
coldef->is_from_parent = false;
|
|
||||||
list_delete_cell(schema, rest, prev);
|
|
||||||
}
|
}
|
||||||
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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2553,7 +2553,6 @@ _equalColumnDef(const ColumnDef *a, const ColumnDef *b)
|
|||||||
COMPARE_SCALAR_FIELD(is_local);
|
COMPARE_SCALAR_FIELD(is_local);
|
||||||
COMPARE_SCALAR_FIELD(is_not_null);
|
COMPARE_SCALAR_FIELD(is_not_null);
|
||||||
COMPARE_SCALAR_FIELD(is_from_type);
|
COMPARE_SCALAR_FIELD(is_from_type);
|
||||||
COMPARE_SCALAR_FIELD(is_from_parent);
|
|
||||||
COMPARE_SCALAR_FIELD(storage);
|
COMPARE_SCALAR_FIELD(storage);
|
||||||
COMPARE_NODE_FIELD(raw_default);
|
COMPARE_NODE_FIELD(raw_default);
|
||||||
COMPARE_NODE_FIELD(cooked_default);
|
COMPARE_NODE_FIELD(cooked_default);
|
||||||
|
@ -496,7 +496,6 @@ makeColumnDef(const char *colname, Oid typeOid, int32 typmod, Oid collOid)
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = false;
|
n->is_from_type = false;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
|
@ -3389,7 +3389,6 @@ columnDef: ColId Typename create_generic_options ColQualList
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = false;
|
n->is_from_type = false;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
@ -3411,7 +3410,6 @@ columnOptions: ColId ColQualList
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = false;
|
n->is_from_type = false;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
@ -3430,7 +3428,6 @@ columnOptions: ColId ColQualList
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = false;
|
n->is_from_type = false;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
@ -12274,7 +12271,6 @@ TableFuncElement: ColId Typename opt_collate_clause
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = false;
|
n->is_from_type = false;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
|
@ -988,7 +988,6 @@ transformTableLikeClause(CreateStmtContext *cxt, TableLikeClause *table_like_cla
|
|||||||
def->is_local = true;
|
def->is_local = true;
|
||||||
def->is_not_null = attribute->attnotnull;
|
def->is_not_null = attribute->attnotnull;
|
||||||
def->is_from_type = false;
|
def->is_from_type = false;
|
||||||
def->is_from_parent = false;
|
|
||||||
def->storage = 0;
|
def->storage = 0;
|
||||||
def->raw_default = NULL;
|
def->raw_default = NULL;
|
||||||
def->cooked_default = NULL;
|
def->cooked_default = NULL;
|
||||||
@ -1265,7 +1264,6 @@ transformOfType(CreateStmtContext *cxt, TypeName *ofTypename)
|
|||||||
n->is_local = true;
|
n->is_local = true;
|
||||||
n->is_not_null = false;
|
n->is_not_null = false;
|
||||||
n->is_from_type = true;
|
n->is_from_type = true;
|
||||||
n->is_from_parent = false;
|
|
||||||
n->storage = 0;
|
n->storage = 0;
|
||||||
n->raw_default = NULL;
|
n->raw_default = NULL;
|
||||||
n->cooked_default = NULL;
|
n->cooked_default = NULL;
|
||||||
|
@ -650,7 +650,7 @@ typedef struct ColumnDef
|
|||||||
bool is_local; /* column has local (non-inherited) def'n */
|
bool is_local; /* column has local (non-inherited) def'n */
|
||||||
bool is_not_null; /* NOT NULL constraint specified? */
|
bool is_not_null; /* NOT NULL constraint specified? */
|
||||||
bool is_from_type; /* column definition came from table type */
|
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 */
|
char storage; /* attstorage setting, or 0 for default */
|
||||||
Node *raw_default; /* default value (untransformed parse tree) */
|
Node *raw_default; /* default value (untransformed parse tree) */
|
||||||
Node *cooked_default; /* default value (transformed expr tree) */
|
Node *cooked_default; /* default value (transformed expr tree) */
|
||||||
|
@ -722,6 +722,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 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 a level-2 partition
|
||||||
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
|
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
|
-- Partition bound in describe output
|
||||||
\d+ part_b
|
\d+ part_b
|
||||||
Table "public.part_b"
|
Table "public.part_b"
|
||||||
|
@ -654,6 +654,25 @@ CREATE TABLE part_c PARTITION OF parted (b WITH OPTIONS NOT NULL DEFAULT 0) FOR
|
|||||||
-- create a level-2 partition
|
-- create a level-2 partition
|
||||||
CREATE TABLE part_c_1_10 PARTITION OF part_c FOR VALUES FROM (1) TO (10);
|
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
|
-- Partition bound in describe output
|
||||||
\d+ part_b
|
\d+ part_b
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user