From 614c858375645102d2676c851e4c1016e4aefc44 Mon Sep 17 00:00:00 2001 From: Robin Thomas Date: Thu, 8 Sep 2016 12:12:28 -0400 Subject: [PATCH] Forbid EXCLUDE constraints on distributed tables just as we forbid UNIQUE or PRIMARY KEY constraints. Also, properly propagate valid EXCLUDE constraints to worker shard tables. If an EXCLUDE constraint includes the distribution column, the operator must be an equality operator. Tests in regression suite for exclusion constraints that include the partition column, omit it, and include it but with non-equality operator. Regression tests also verify that valid exclusion constraints are propagated to the shard tables. And the tests work in different timezones now. Fixes citusdata/citus#748 and citusdata/citus#778. --- .../commands/create_distributed_table.c | 42 +- .../distributed/master/master_node_protocol.c | 8 +- .../distributed/relay/relay_event_utility.c | 8 +- src/include/distributed/relay_utility.h | 1 - .../regress/expected/multi_create_table.out | 91 +--- .../multi_create_table_constraints.out | 461 ++++++++++++++++++ src/test/regress/multi_schedule | 1 + src/test/regress/output/multi_copy.source | 4 +- src/test/regress/sql/multi_create_table.sql | 64 --- .../sql/multi_create_table_constraints.sql | 238 +++++++++ 10 files changed, 745 insertions(+), 173 deletions(-) create mode 100644 src/test/regress/expected/multi_create_table_constraints.out create mode 100644 src/test/regress/sql/multi_create_table_constraints.sql 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;