diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index 8ce82d0bb..39ac7cecf 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -29,6 +29,7 @@ #include "distributed/metadata_cache.h" #include "distributed/pg_dist_partition.h" #include "executor/spi.h" +#include "distributed/multi_logical_planner.h" #include "nodes/execnodes.h" #include "nodes/nodeFuncs.h" #include "nodes/pg_list.h" @@ -186,10 +187,10 @@ master_create_distributed_table(PG_FUNCTION_ARGS) } /* - * Do not allow UNIQUE constraint and/or PRIMARY KEY on append partitioned tables, + * Forbid UNIQUE, PRIMARY KEY, or EXCLUDE constraints on append partitioned tables, * since currently there is no way of enforcing uniqueness for overlapping shards. * - * Similarly, do not allow UNIQUE constraint and/or PRIMARY KEY if it does not + * Similarly, do not allow such constraints it they do not * include partition column. This check is important for two reasons. First, * currently Citus does not enforce uniqueness constraint on multiple shards. * Second, INSERT INTO .. ON CONFLICT (i.e., UPSERT) queries can be executed with no @@ -209,23 +210,25 @@ master_create_distributed_table(PG_FUNCTION_ARGS) /* extract index key information from the index's pg_index info */ indexInfo = BuildIndexInfo(indexDesc); - /* only check unique indexes */ - if (indexInfo->ii_Unique == false) + /* only check unique indexes and exclusion constraints. */ + if (indexInfo->ii_Unique == false && indexInfo->ii_ExclusionOps == NULL) { index_close(indexDesc, NoLock); continue; } /* - * Citus cannot enforce uniqueness constraints with overlapping shards. Thus, - * emit a warning for unique indexes on append partitioned tables. + * Citus cannot enforce uniqueness/exclusion constraints with overlapping shards. + * Thus, emit a warning for unique indexes and exclusion constraints on + * append partitioned tables. */ if (distributionMethod == DISTRIBUTE_BY_APPEND) { ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("table \"%s\" has a unique constraint", + errmsg("table \"%s\" has a UNIQUE or EXCLUDE constraint", distributedRelationName), - errdetail("Unique constraints and primary keys on " + errdetail("UNIQUE constraints, EXCLUDE constraints, " + "and PRIMARY KEYs on " "append-partitioned tables cannot be enforced."), errhint("Consider using hash partitioning."))); } @@ -236,7 +239,21 @@ master_create_distributed_table(PG_FUNCTION_ARGS) for (attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) { AttrNumber attributeNumber = attributeNumberArray[attributeIndex]; - if (distributionColumn->varattno == attributeNumber) + bool uniqueConstraint = false; + bool exclusionConstraintWithEquality = false; + + if (distributionColumn->varattno != attributeNumber) + { + continue; + } + + uniqueConstraint = indexInfo->ii_Unique; + exclusionConstraintWithEquality = (indexInfo->ii_ExclusionOps != NULL && + OperatorImplementsEquality( + indexInfo->ii_ExclusionOps[ + attributeIndex])); + + if (uniqueConstraint || exclusionConstraintWithEquality) { hasDistributionColumn = true; break; @@ -248,9 +265,10 @@ master_create_distributed_table(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot distribute relation: \"%s\"", distributedRelationName), - errdetail("Distributed relations cannot have " - "UNIQUE constraints or PRIMARY KEYs that do not " - "include the partition column."))); + errdetail("Distributed relations cannot have UNIQUE, " + "EXCLUDE, or PRIMARY KEY constraints that do not " + "include the partition column (with an equality " + "operator if EXCLUDE)."))); } index_close(indexDesc, NoLock); diff --git a/src/backend/distributed/master/master_node_protocol.c b/src/backend/distributed/master/master_node_protocol.c index e3835b20c..f2c52c6b5 100644 --- a/src/backend/distributed/master/master_node_protocol.c +++ b/src/backend/distributed/master/master_node_protocol.c @@ -640,15 +640,15 @@ GetTableDDLEvents(Oid relationId) /* * A primary key index is always created by a constraint statement. - * A unique key index is created by a constraint if and only if the - * index has a corresponding constraint entry in pg_depend. Any other - * index form is never associated with a constraint. + * A unique key index or exclusion index is created by a constraint + * if and only if the index has a corresponding constraint entry in pg_depend. + * Any other index form is never associated with a constraint. */ if (indexForm->indisprimary) { isConstraint = true; } - else if (indexForm->indisunique) + else if (indexForm->indisunique || indexForm->indisexclusion) { Oid constraintId = get_index_constraint(indexId); isConstraint = OidIsValid(constraintId); diff --git a/src/backend/distributed/relay/relay_event_utility.c b/src/backend/distributed/relay/relay_event_utility.c index 76b5f0177..3dda412f8 100644 --- a/src/backend/distributed/relay/relay_event_utility.c +++ b/src/backend/distributed/relay/relay_event_utility.c @@ -423,7 +423,8 @@ TypeAddIndexConstraint(const AlterTableCmd *command) { Constraint *constraint = (Constraint *) command->def; if (constraint->contype == CONSTR_PRIMARY || - constraint->contype == CONSTR_UNIQUE) + constraint->contype == CONSTR_UNIQUE || + constraint->contype == CONSTR_EXCLUSION) { return true; } @@ -466,7 +467,7 @@ TypeDropIndexConstraint(const AlterTableCmd *command, * At this stage, our only option is performing a relationId lookup. We * first find the relationId, and then scan the pg_constraints system * catalog using this relationId. Finally, we check if the passed in - * constraint is for a primary key or unique index. + * constraint is for a primary key, unique, or exclusion index. */ relationId = RangeVarGetRelid(relation, NoLock, failOK); if (!OidIsValid(relationId)) @@ -497,7 +498,8 @@ TypeDropIndexConstraint(const AlterTableCmd *command, { /* we found the constraint, now check if it is for an index */ if (constraintForm->contype == CONSTRAINT_PRIMARY || - constraintForm->contype == CONSTRAINT_UNIQUE) + constraintForm->contype == CONSTRAINT_UNIQUE || + constraintForm->contype == CONSTRAINT_EXCLUSION) { indexConstraint = true; } diff --git a/src/include/distributed/relay_utility.h b/src/include/distributed/relay_utility.h index 1f6f77da0..bb504bfa0 100644 --- a/src/include/distributed/relay_utility.h +++ b/src/include/distributed/relay_utility.h @@ -23,7 +23,6 @@ #define SHARD_NAME_SEPARATOR '_' #define INVALID_SHARD_ID 0 - /* * RelayFileState represents last known states of shards on a given node. We * currently only have shards in finalized or cached state; and set this state diff --git a/src/test/regress/expected/multi_create_table.out b/src/test/regress/expected/multi_create_table.out index a41f7af07..a9446f9be 100644 --- a/src/test/regress/expected/multi_create_table.out +++ b/src/test/regress/expected/multi_create_table.out @@ -24,8 +24,8 @@ CREATE TABLE lineitem ( l_comment varchar(44) not null, PRIMARY KEY(l_orderkey, l_linenumber) ); SELECT master_create_distributed_table('lineitem', 'l_orderkey', 'append'); -WARNING: table "lineitem" has a unique constraint -DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. +WARNING: table "lineitem" has a UNIQUE or EXCLUDE constraint +DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced. HINT: Consider using hash partitioning. master_create_distributed_table --------------------------------- @@ -47,8 +47,8 @@ CREATE TABLE orders ( o_comment varchar(79) not null, PRIMARY KEY(o_orderkey) ); SELECT master_create_distributed_table('orders', 'o_orderkey', 'append'); -WARNING: table "orders" has a unique constraint -DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. +WARNING: table "orders" has a UNIQUE or EXCLUDE constraint +DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced. HINT: Consider using hash partitioning. master_create_distributed_table --------------------------------- @@ -119,86 +119,3 @@ SELECT master_create_distributed_table('supplier', 's_suppkey', 'append'); (1 row) --- now test that Citus cannot distribute unique constraints that do not include --- the partition column -CREATE TABLE primary_key_on_non_part_col -( - partition_col integer, - other_col integer PRIMARY KEY -); -SELECT master_create_distributed_table('primary_key_on_non_part_col', 'partition_col', 'hash'); -ERROR: cannot distribute relation: "primary_key_on_non_part_col" -DETAIL: Distributed relations cannot have UNIQUE constraints or PRIMARY KEYs that do not include the partition column. -CREATE TABLE unique_const_on_non_part_col -( - partition_col integer, - other_col integer UNIQUE -); -SELECT master_create_distributed_table('primary_key_on_non_part_col', 'partition_col', 'hash'); -ERROR: cannot distribute relation: "primary_key_on_non_part_col" -DETAIL: Distributed relations cannot have UNIQUE constraints or PRIMARY KEYs that do not include the partition column. --- now show that Citus can distribute unique constrints that include --- the partition column -CREATE TABLE primary_key_on_part_col -( - partition_col integer PRIMARY KEY, - other_col integer -); -SELECT master_create_distributed_table('primary_key_on_part_col', 'partition_col', 'hash'); - master_create_distributed_table ---------------------------------- - -(1 row) - -CREATE TABLE unique_const_on_part_col -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_on_part_col', 'partition_col', 'hash'); - master_create_distributed_table ---------------------------------- - -(1 row) - -CREATE TABLE unique_const_on_two_columns -( - partition_col integer, - other_col integer, - UNIQUE (partition_col, other_col) -); -SELECT master_create_distributed_table('unique_const_on_two_columns', 'partition_col', 'hash'); - master_create_distributed_table ---------------------------------- - -(1 row) - -CREATE TABLE unique_const_append_partitioned_tables -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_append_partitioned_tables', 'partition_col', 'append'); -WARNING: table "unique_const_append_partitioned_tables" has a unique constraint -DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. -HINT: Consider using hash partitioning. - master_create_distributed_table ---------------------------------- - -(1 row) - -CREATE TABLE unique_const_range_partitioned_tables -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_range_partitioned_tables', 'partition_col', 'range'); - master_create_distributed_table ---------------------------------- - -(1 row) - --- drop unnecessary tables -DROP TABLE primary_key_on_non_part_col, unique_const_on_non_part_col CASCADE; -DROP TABLE primary_key_on_part_col, unique_const_on_part_col, unique_const_on_two_columns CASCADE; -DROP TABLE unique_const_range_partitioned_tables CASCADE; diff --git a/src/test/regress/expected/multi_create_table_constraints.out b/src/test/regress/expected/multi_create_table_constraints.out new file mode 100644 index 000000000..3fdaebc17 --- /dev/null +++ b/src/test/regress/expected/multi_create_table_constraints.out @@ -0,0 +1,461 @@ +-- +-- MULTI_CREATE_TABLE_CONSTRAINTS +-- +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 365000; +ALTER SEQUENCE pg_catalog.pg_dist_jobid_seq RESTART 365000; +-- test that Citus forbids unique and EXCLUDE constraints on append-partitioned tables. +CREATE TABLE uniq_cns_append_tables +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uniq_cns_append_tables', 'partition_col', 'append'); +WARNING: table "uniq_cns_append_tables" has a UNIQUE or EXCLUDE constraint +DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced. +HINT: Consider using hash partitioning. + master_create_distributed_table +--------------------------------- + +(1 row) + +CREATE TABLE excl_cns_append_tables +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('excl_cns_append_tables', 'partition_col', 'append'); +WARNING: table "excl_cns_append_tables" has a UNIQUE or EXCLUDE constraint +DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced. +HINT: Consider using hash partitioning. + master_create_distributed_table +--------------------------------- + +(1 row) + +-- test that Citus cannot distribute unique constraints that do not include +-- the partition column on hash-partitioned tables. +CREATE TABLE pk_on_non_part_col +( + partition_col integer, + other_col integer PRIMARY KEY +); +SELECT master_create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); +ERROR: cannot distribute relation: "pk_on_non_part_col" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +CREATE TABLE uq_on_non_part_col +( + partition_col integer, + other_col integer UNIQUE +); +SELECT master_create_distributed_table('uq_on_non_part_col', 'partition_col', 'hash'); +ERROR: cannot distribute relation: "uq_on_non_part_col" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +CREATE TABLE ex_on_non_part_col +( + partition_col integer, + other_col integer, + EXCLUDE (other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); +ERROR: cannot distribute relation: "ex_on_non_part_col" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +-- now show that Citus can distribute unique and EXCLUDE constraints that +-- include the partition column for hash-partitioned tables. +-- However, EXCLUDE constraints must include the partition column with +-- an equality operator. +-- These tests are for UNNAMED constraints. +CREATE TABLE pk_on_part_col +( + partition_col integer PRIMARY KEY, + other_col integer +); +SELECT master_create_distributed_table('pk_on_part_col', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +CREATE TABLE uq_part_col +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_part_col', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +CREATE TABLE uq_two_columns +( + partition_col integer, + other_col integer, + UNIQUE (partition_col, other_col) +); +SELECT master_create_distributed_table('uq_two_columns', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('uq_two_columns', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); +INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); +ERROR: duplicate key value violates unique constraint "uq_two_columns_partition_col_other_col_key_365000" +DETAIL: Key (partition_col, other_col)=(1, 1) already exists. +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_on_part_col +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('ex_on_part_col', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_on_part_col', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,2); +ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_partition_col_excl_365004" +DETAIL: Key (partition_col)=(1) conflicts with existing key (partition_col)=(1). +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_on_two_columns +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =, other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_two_columns', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_on_two_columns', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); +ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_partition_col_other_col_excl_365008" +DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_wrong_operator +( + partition_col tsrange, + other_col tsrange, + EXCLUDE USING gist (other_col WITH =, partition_col WITH &&) +); +SELECT master_create_distributed_table('ex_wrong_operator', 'partition_col', 'hash'); +ERROR: cannot distribute relation: "ex_wrong_operator" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +CREATE TABLE ex_overlaps +( + partition_col tsrange, + other_col tsrange, + EXCLUDE USING gist (other_col WITH &&, partition_col WITH =) +); +SELECT master_create_distributed_table('ex_overlaps', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_overlaps', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); +INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); +ERROR: conflicting key value violates exclusion constraint "ex_overlaps_other_col_partition_col_excl_365015" +DETAIL: Key (other_col, partition_col)=(["2016-01-15 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]) conflicts with existing key (other_col, partition_col)=(["2016-01-01 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]). +CONTEXT: while executing command on localhost:57638 +-- now show that Citus can distribute unique and EXCLUDE constraints that +-- include the partition column, for hash-partitioned tables. +-- However, EXCLUDE constraints must include the partition column with +-- an equality operator. +-- These tests are for NAMED constraints. +CREATE TABLE pk_on_part_col_named +( + partition_col integer CONSTRAINT pk_on_part_col_named_pk PRIMARY KEY, + other_col integer +); +SELECT master_create_distributed_table('pk_on_part_col_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +CREATE TABLE uq_part_col_named +( + partition_col integer CONSTRAINT uq_part_col_named_uniq UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_part_col_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +CREATE TABLE uq_two_columns_named +( + partition_col integer, + other_col integer, + CONSTRAINT uq_two_columns_named_uniq UNIQUE (partition_col, other_col) +); +SELECT master_create_distributed_table('uq_two_columns_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('uq_two_columns_named', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); +INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); +ERROR: duplicate key value violates unique constraint "uq_two_columns_named_uniq_365016" +DETAIL: Key (partition_col, other_col)=(1, 1) already exists. +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_on_part_col_named +( + partition_col integer, + other_col integer, + CONSTRAINT ex_on_part_col_named_exclude EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('ex_on_part_col_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_on_part_col_named', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,2); +ERROR: conflicting key value violates exclusion constraint "ex_on_part_col_named_exclude_365020" +DETAIL: Key (partition_col)=(1) conflicts with existing key (partition_col)=(1). +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_on_two_columns_named +( + partition_col integer, + other_col integer, + CONSTRAINT ex_on_two_columns_named_exclude EXCLUDE (partition_col WITH =, other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_two_columns_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_on_two_columns_named', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); +ERROR: conflicting key value violates exclusion constraint "ex_on_two_columns_named_exclude_365024" +DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_multiple_excludes +( + partition_col integer, + other_col integer, + other_other_col integer, + CONSTRAINT ex_multiple_excludes_excl1 EXCLUDE (partition_col WITH =, other_col WITH =), + CONSTRAINT ex_multiple_excludes_excl2 EXCLUDE (partition_col WITH =, other_other_col WITH =) +); +SELECT master_create_distributed_table('ex_multiple_excludes', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_multiple_excludes', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,1); +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,2); +ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl1_365028" +DETAIL: Key (partition_col, other_col)=(1, 1) conflicts with existing key (partition_col, other_col)=(1, 1). +CONTEXT: while executing command on localhost:57637 +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,2,1); +ERROR: conflicting key value violates exclusion constraint "ex_multiple_excludes_excl2_365028" +DETAIL: Key (partition_col, other_other_col)=(1, 1) conflicts with existing key (partition_col, other_other_col)=(1, 1). +CONTEXT: while executing command on localhost:57637 +CREATE TABLE ex_wrong_operator_named +( + partition_col tsrange, + other_col tsrange, + CONSTRAINT ex_wrong_operator_named_exclude EXCLUDE USING gist (other_col WITH =, partition_col WITH &&) +); +SELECT master_create_distributed_table('ex_wrong_operator_named', 'partition_col', 'hash'); +ERROR: cannot distribute relation: "ex_wrong_operator_named" +DETAIL: Distributed relations cannot have UNIQUE, EXCLUDE, or PRIMARY KEY constraints that do not include the partition column (with an equality operator if EXCLUDE). +CREATE TABLE ex_overlaps_named +( + partition_col tsrange, + other_col tsrange, + CONSTRAINT ex_overlaps_operator_named_exclude EXCLUDE USING gist (other_col WITH &&, partition_col WITH =) +); +SELECT master_create_distributed_table('ex_overlaps_named', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('ex_overlaps_named', '4', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); +INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); +ERROR: conflicting key value violates exclusion constraint "ex_overlaps_operator_named_exclude_365035" +DETAIL: Key (other_col, partition_col)=(["2016-01-15 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]) conflicts with existing key (other_col, partition_col)=(["2016-01-01 00:00:00","2016-02-01 00:00:00"], ["2016-01-01 00:00:00","2016-02-01 00:00:00"]). +CONTEXT: while executing command on localhost:57638 +-- now show that Citus allows unique constraints on range-partitioned tables. +CREATE TABLE uq_range_tables +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_range_tables', 'partition_col', 'range'); + master_create_distributed_table +--------------------------------- + +(1 row) + +-- show that CHECK constraints are distributed. +CREATE TABLE check_example +( + partition_col integer UNIQUE, + other_col integer CHECK (other_col >= 100), + other_other_col integer CHECK (abs(other_other_col) >= 100) +); +SELECT master_create_distributed_table('check_example', 'partition_col', 'hash'); + master_create_distributed_table +--------------------------------- + +(1 row) + +SELECT master_create_worker_shards('check_example', '2', '2'); + master_create_worker_shards +----------------------------- + +(1 row) + +\c - - - :worker_1_port +\d check_example* + Table "public.check_example_365036" + Column | Type | Modifiers +-----------------+---------+----------- + partition_col | integer | + other_col | integer | + other_other_col | integer | +Indexes: + "check_example_partition_col_key_365036" UNIQUE CONSTRAINT, btree (partition_col) +Check constraints: + "check_example_other_col_check" CHECK (other_col >= 100) + "check_example_other_other_col_check" CHECK (abs(other_other_col) >= 100) + + Table "public.check_example_365037" + Column | Type | Modifiers +-----------------+---------+----------- + partition_col | integer | + other_col | integer | + other_other_col | integer | +Indexes: + "check_example_partition_col_key_365037" UNIQUE CONSTRAINT, btree (partition_col) +Check constraints: + "check_example_other_col_check" CHECK (other_col >= 100) + "check_example_other_other_col_check" CHECK (abs(other_other_col) >= 100) + +Index "public.check_example_partition_col_key_365036" + Column | Type | Definition +---------------+---------+--------------- + partition_col | integer | partition_col +unique, btree, for table "public.check_example_365036" + +Index "public.check_example_partition_col_key_365037" + Column | Type | Definition +---------------+---------+--------------- + partition_col | integer | partition_col +unique, btree, for table "public.check_example_365037" + +\c - - - :worker_2_port +\d check_example* + Table "public.check_example_365036" + Column | Type | Modifiers +-----------------+---------+----------- + partition_col | integer | + other_col | integer | + other_other_col | integer | +Indexes: + "check_example_partition_col_key_365036" UNIQUE CONSTRAINT, btree (partition_col) +Check constraints: + "check_example_other_col_check" CHECK (other_col >= 100) + "check_example_other_other_col_check" CHECK (abs(other_other_col) >= 100) + + Table "public.check_example_365037" + Column | Type | Modifiers +-----------------+---------+----------- + partition_col | integer | + other_col | integer | + other_other_col | integer | +Indexes: + "check_example_partition_col_key_365037" UNIQUE CONSTRAINT, btree (partition_col) +Check constraints: + "check_example_other_col_check" CHECK (other_col >= 100) + "check_example_other_other_col_check" CHECK (abs(other_other_col) >= 100) + +Index "public.check_example_partition_col_key_365036" + Column | Type | Definition +---------------+---------+--------------- + partition_col | integer | partition_col +unique, btree, for table "public.check_example_365036" + +Index "public.check_example_partition_col_key_365037" + Column | Type | Definition +---------------+---------+--------------- + partition_col | integer | partition_col +unique, btree, for table "public.check_example_365037" + +\c - - - :master_port +-- drop unnecessary tables +DROP TABLE pk_on_non_part_col, uq_on_non_part_col CASCADE; +DROP TABLE pk_on_part_col, uq_part_col, uq_two_columns CASCADE; +DROP TABLE ex_on_part_col, ex_on_two_columns, ex_multiple_excludes, ex_overlaps CASCADE; +DROP TABLE ex_on_part_col_named, ex_on_two_columns_named, ex_overlaps_named CASCADE; +DROP TABLE uq_range_tables, check_example CASCADE; diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index c97da7190..4f868e506 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -23,6 +23,7 @@ test: multi_table_ddl # uploading data to it. # ---------- test: multi_create_table +test: multi_create_table_constraints test: multi_master_protocol test: multi_load_data diff --git a/src/test/regress/output/multi_copy.source b/src/test/regress/output/multi_copy.source index 4fd55ffdc..64880627b 100644 --- a/src/test/regress/output/multi_copy.source +++ b/src/test/regress/output/multi_copy.source @@ -458,8 +458,8 @@ CREATE TABLE customer_worker_copy_append ( primary key (c_custkey)); CREATE INDEX ON customer_worker_copy_append (c_name); SELECT master_create_distributed_table('customer_worker_copy_append', 'c_custkey', 'append'); -WARNING: table "customer_worker_copy_append" has a unique constraint -DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. +WARNING: table "customer_worker_copy_append" has a UNIQUE or EXCLUDE constraint +DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced. HINT: Consider using hash partitioning. master_create_distributed_table --------------------------------- diff --git a/src/test/regress/sql/multi_create_table.sql b/src/test/regress/sql/multi_create_table.sql index 0662c9744..c4b7f2a12 100644 --- a/src/test/regress/sql/multi_create_table.sql +++ b/src/test/regress/sql/multi_create_table.sql @@ -2,11 +2,9 @@ -- MULTI_CREATE_TABLE -- - ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 360000; ALTER SEQUENCE pg_catalog.pg_dist_jobid_seq RESTART 360000; - -- Create new table definitions for use in testing in distributed planning and -- execution functionality. Also create indexes to boost performance. @@ -99,65 +97,3 @@ CREATE TABLE supplier s_comment varchar(101) not null ); SELECT master_create_distributed_table('supplier', 's_suppkey', 'append'); - - --- now test that Citus cannot distribute unique constraints that do not include --- the partition column -CREATE TABLE primary_key_on_non_part_col -( - partition_col integer, - other_col integer PRIMARY KEY -); -SELECT master_create_distributed_table('primary_key_on_non_part_col', 'partition_col', 'hash'); - -CREATE TABLE unique_const_on_non_part_col -( - partition_col integer, - other_col integer UNIQUE -); -SELECT master_create_distributed_table('primary_key_on_non_part_col', 'partition_col', 'hash'); - --- now show that Citus can distribute unique constrints that include --- the partition column -CREATE TABLE primary_key_on_part_col -( - partition_col integer PRIMARY KEY, - other_col integer -); -SELECT master_create_distributed_table('primary_key_on_part_col', 'partition_col', 'hash'); - -CREATE TABLE unique_const_on_part_col -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_on_part_col', 'partition_col', 'hash'); - -CREATE TABLE unique_const_on_two_columns -( - partition_col integer, - other_col integer, - UNIQUE (partition_col, other_col) -); -SELECT master_create_distributed_table('unique_const_on_two_columns', 'partition_col', 'hash'); - -CREATE TABLE unique_const_append_partitioned_tables -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_append_partitioned_tables', 'partition_col', 'append'); - -CREATE TABLE unique_const_range_partitioned_tables -( - partition_col integer UNIQUE, - other_col integer -); -SELECT master_create_distributed_table('unique_const_range_partitioned_tables', 'partition_col', 'range'); - --- drop unnecessary tables -DROP TABLE primary_key_on_non_part_col, unique_const_on_non_part_col CASCADE; -DROP TABLE primary_key_on_part_col, unique_const_on_part_col, unique_const_on_two_columns CASCADE; -DROP TABLE unique_const_range_partitioned_tables CASCADE; - - diff --git a/src/test/regress/sql/multi_create_table_constraints.sql b/src/test/regress/sql/multi_create_table_constraints.sql new file mode 100644 index 000000000..a467fd3f1 --- /dev/null +++ b/src/test/regress/sql/multi_create_table_constraints.sql @@ -0,0 +1,238 @@ +-- +-- MULTI_CREATE_TABLE_CONSTRAINTS +-- + +ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 365000; +ALTER SEQUENCE pg_catalog.pg_dist_jobid_seq RESTART 365000; + +-- test that Citus forbids unique and EXCLUDE constraints on append-partitioned tables. + +CREATE TABLE uniq_cns_append_tables +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uniq_cns_append_tables', 'partition_col', 'append'); + +CREATE TABLE excl_cns_append_tables +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('excl_cns_append_tables', 'partition_col', 'append'); + +-- test that Citus cannot distribute unique constraints that do not include +-- the partition column on hash-partitioned tables. + +CREATE TABLE pk_on_non_part_col +( + partition_col integer, + other_col integer PRIMARY KEY +); +SELECT master_create_distributed_table('pk_on_non_part_col', 'partition_col', 'hash'); + +CREATE TABLE uq_on_non_part_col +( + partition_col integer, + other_col integer UNIQUE +); +SELECT master_create_distributed_table('uq_on_non_part_col', 'partition_col', 'hash'); + +CREATE TABLE ex_on_non_part_col +( + partition_col integer, + other_col integer, + EXCLUDE (other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_non_part_col', 'partition_col', 'hash'); + +-- now show that Citus can distribute unique and EXCLUDE constraints that +-- include the partition column for hash-partitioned tables. +-- However, EXCLUDE constraints must include the partition column with +-- an equality operator. +-- These tests are for UNNAMED constraints. + +CREATE TABLE pk_on_part_col +( + partition_col integer PRIMARY KEY, + other_col integer +); +SELECT master_create_distributed_table('pk_on_part_col', 'partition_col', 'hash'); + +CREATE TABLE uq_part_col +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_part_col', 'partition_col', 'hash'); + +CREATE TABLE uq_two_columns +( + partition_col integer, + other_col integer, + UNIQUE (partition_col, other_col) +); +SELECT master_create_distributed_table('uq_two_columns', 'partition_col', 'hash'); +SELECT master_create_worker_shards('uq_two_columns', '4', '2'); +INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); +INSERT INTO uq_two_columns (partition_col, other_col) VALUES (1,1); + +CREATE TABLE ex_on_part_col +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('ex_on_part_col', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_on_part_col', '4', '2'); +INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_part_col (partition_col, other_col) VALUES (1,2); + +CREATE TABLE ex_on_two_columns +( + partition_col integer, + other_col integer, + EXCLUDE (partition_col WITH =, other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_two_columns', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_on_two_columns', '4', '2'); +INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_two_columns (partition_col, other_col) VALUES (1,1); + +CREATE TABLE ex_wrong_operator +( + partition_col tsrange, + other_col tsrange, + EXCLUDE USING gist (other_col WITH =, partition_col WITH &&) +); +SELECT master_create_distributed_table('ex_wrong_operator', 'partition_col', 'hash'); + +CREATE TABLE ex_overlaps +( + partition_col tsrange, + other_col tsrange, + EXCLUDE USING gist (other_col WITH &&, partition_col WITH =) +); +SELECT master_create_distributed_table('ex_overlaps', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_overlaps', '4', '2'); +INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); +INSERT INTO ex_overlaps (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); + +-- now show that Citus can distribute unique and EXCLUDE constraints that +-- include the partition column, for hash-partitioned tables. +-- However, EXCLUDE constraints must include the partition column with +-- an equality operator. +-- These tests are for NAMED constraints. + +CREATE TABLE pk_on_part_col_named +( + partition_col integer CONSTRAINT pk_on_part_col_named_pk PRIMARY KEY, + other_col integer +); +SELECT master_create_distributed_table('pk_on_part_col_named', 'partition_col', 'hash'); + +CREATE TABLE uq_part_col_named +( + partition_col integer CONSTRAINT uq_part_col_named_uniq UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_part_col_named', 'partition_col', 'hash'); + +CREATE TABLE uq_two_columns_named +( + partition_col integer, + other_col integer, + CONSTRAINT uq_two_columns_named_uniq UNIQUE (partition_col, other_col) +); +SELECT master_create_distributed_table('uq_two_columns_named', 'partition_col', 'hash'); +SELECT master_create_worker_shards('uq_two_columns_named', '4', '2'); +INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); +INSERT INTO uq_two_columns_named (partition_col, other_col) VALUES (1,1); + +CREATE TABLE ex_on_part_col_named +( + partition_col integer, + other_col integer, + CONSTRAINT ex_on_part_col_named_exclude EXCLUDE (partition_col WITH =) +); +SELECT master_create_distributed_table('ex_on_part_col_named', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_on_part_col_named', '4', '2'); +INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_part_col_named (partition_col, other_col) VALUES (1,2); + +CREATE TABLE ex_on_two_columns_named +( + partition_col integer, + other_col integer, + CONSTRAINT ex_on_two_columns_named_exclude EXCLUDE (partition_col WITH =, other_col WITH =) +); +SELECT master_create_distributed_table('ex_on_two_columns_named', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_on_two_columns_named', '4', '2'); +INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); +INSERT INTO ex_on_two_columns_named (partition_col, other_col) VALUES (1,1); + +CREATE TABLE ex_multiple_excludes +( + partition_col integer, + other_col integer, + other_other_col integer, + CONSTRAINT ex_multiple_excludes_excl1 EXCLUDE (partition_col WITH =, other_col WITH =), + CONSTRAINT ex_multiple_excludes_excl2 EXCLUDE (partition_col WITH =, other_other_col WITH =) +); +SELECT master_create_distributed_table('ex_multiple_excludes', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_multiple_excludes', '4', '2'); +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,1); +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,1,2); +INSERT INTO ex_multiple_excludes (partition_col, other_col, other_other_col) VALUES (1,2,1); + +CREATE TABLE ex_wrong_operator_named +( + partition_col tsrange, + other_col tsrange, + CONSTRAINT ex_wrong_operator_named_exclude EXCLUDE USING gist (other_col WITH =, partition_col WITH &&) +); +SELECT master_create_distributed_table('ex_wrong_operator_named', 'partition_col', 'hash'); + +CREATE TABLE ex_overlaps_named +( + partition_col tsrange, + other_col tsrange, + CONSTRAINT ex_overlaps_operator_named_exclude EXCLUDE USING gist (other_col WITH &&, partition_col WITH =) +); +SELECT master_create_distributed_table('ex_overlaps_named', 'partition_col', 'hash'); +SELECT master_create_worker_shards('ex_overlaps_named', '4', '2'); +INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-01 00:00:00, 2016-02-01 00:00:00]'); +INSERT INTO ex_overlaps_named (partition_col, other_col) VALUES ('[2016-01-01 00:00:00, 2016-02-01 00:00:00]', '[2016-01-15 00:00:00, 2016-02-01 00:00:00]'); + +-- now show that Citus allows unique constraints on range-partitioned tables. + +CREATE TABLE uq_range_tables +( + partition_col integer UNIQUE, + other_col integer +); +SELECT master_create_distributed_table('uq_range_tables', 'partition_col', 'range'); + +-- show that CHECK constraints are distributed. +CREATE TABLE check_example +( + partition_col integer UNIQUE, + other_col integer CHECK (other_col >= 100), + other_other_col integer CHECK (abs(other_other_col) >= 100) +); +SELECT master_create_distributed_table('check_example', 'partition_col', 'hash'); +SELECT master_create_worker_shards('check_example', '2', '2'); + +\c - - - :worker_1_port +\d check_example* +\c - - - :worker_2_port +\d check_example* +\c - - - :master_port + +-- drop unnecessary tables +DROP TABLE pk_on_non_part_col, uq_on_non_part_col CASCADE; +DROP TABLE pk_on_part_col, uq_part_col, uq_two_columns CASCADE; +DROP TABLE ex_on_part_col, ex_on_two_columns, ex_multiple_excludes, ex_overlaps CASCADE; +DROP TABLE ex_on_part_col_named, ex_on_two_columns_named, ex_overlaps_named CASCADE; +DROP TABLE uq_range_tables, check_example CASCADE;