Merge pull request #749 from robin900/forbid-exclusion-constraints

Handle EXCLUDE constraints properly on distributed tables
pull/782/merge
Eren Başak 2016-09-22 11:37:25 +03:00 committed by GitHub
commit 70fd42c41c
10 changed files with 745 additions and 173 deletions

View File

@ -29,6 +29,7 @@
#include "distributed/metadata_cache.h" #include "distributed/metadata_cache.h"
#include "distributed/pg_dist_partition.h" #include "distributed/pg_dist_partition.h"
#include "executor/spi.h" #include "executor/spi.h"
#include "distributed/multi_logical_planner.h"
#include "nodes/execnodes.h" #include "nodes/execnodes.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "nodes/pg_list.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. * 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, * include partition column. This check is important for two reasons. First,
* currently Citus does not enforce uniqueness constraint on multiple shards. * currently Citus does not enforce uniqueness constraint on multiple shards.
* Second, INSERT INTO .. ON CONFLICT (i.e., UPSERT) queries can be executed with no * 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 */ /* extract index key information from the index's pg_index info */
indexInfo = BuildIndexInfo(indexDesc); indexInfo = BuildIndexInfo(indexDesc);
/* only check unique indexes */ /* only check unique indexes and exclusion constraints. */
if (indexInfo->ii_Unique == false) if (indexInfo->ii_Unique == false && indexInfo->ii_ExclusionOps == NULL)
{ {
index_close(indexDesc, NoLock); index_close(indexDesc, NoLock);
continue; continue;
} }
/* /*
* Citus cannot enforce uniqueness constraints with overlapping shards. Thus, * Citus cannot enforce uniqueness/exclusion constraints with overlapping shards.
* emit a warning for unique indexes on append partitioned tables. * Thus, emit a warning for unique indexes and exclusion constraints on
* append partitioned tables.
*/ */
if (distributionMethod == DISTRIBUTE_BY_APPEND) if (distributionMethod == DISTRIBUTE_BY_APPEND)
{ {
ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("table \"%s\" has a unique constraint", errmsg("table \"%s\" has a UNIQUE or EXCLUDE constraint",
distributedRelationName), distributedRelationName),
errdetail("Unique constraints and primary keys on " errdetail("UNIQUE constraints, EXCLUDE constraints, "
"and PRIMARY KEYs on "
"append-partitioned tables cannot be enforced."), "append-partitioned tables cannot be enforced."),
errhint("Consider using hash partitioning."))); errhint("Consider using hash partitioning.")));
} }
@ -236,7 +239,21 @@ master_create_distributed_table(PG_FUNCTION_ARGS)
for (attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++) for (attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++)
{ {
AttrNumber attributeNumber = attributeNumberArray[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; hasDistributionColumn = true;
break; break;
@ -248,9 +265,10 @@ master_create_distributed_table(PG_FUNCTION_ARGS)
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot distribute relation: \"%s\"", errmsg("cannot distribute relation: \"%s\"",
distributedRelationName), distributedRelationName),
errdetail("Distributed relations cannot have " errdetail("Distributed relations cannot have UNIQUE, "
"UNIQUE constraints or PRIMARY KEYs that do not " "EXCLUDE, or PRIMARY KEY constraints that do not "
"include the partition column."))); "include the partition column (with an equality "
"operator if EXCLUDE).")));
} }
index_close(indexDesc, NoLock); index_close(indexDesc, NoLock);

View File

@ -640,15 +640,15 @@ GetTableDDLEvents(Oid relationId)
/* /*
* A primary key index is always created by a constraint statement. * 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 * A unique key index or exclusion index is created by a constraint
* index has a corresponding constraint entry in pg_depend. Any other * if and only if the index has a corresponding constraint entry in pg_depend.
* index form is never associated with a constraint. * Any other index form is never associated with a constraint.
*/ */
if (indexForm->indisprimary) if (indexForm->indisprimary)
{ {
isConstraint = true; isConstraint = true;
} }
else if (indexForm->indisunique) else if (indexForm->indisunique || indexForm->indisexclusion)
{ {
Oid constraintId = get_index_constraint(indexId); Oid constraintId = get_index_constraint(indexId);
isConstraint = OidIsValid(constraintId); isConstraint = OidIsValid(constraintId);

View File

@ -423,7 +423,8 @@ TypeAddIndexConstraint(const AlterTableCmd *command)
{ {
Constraint *constraint = (Constraint *) command->def; Constraint *constraint = (Constraint *) command->def;
if (constraint->contype == CONSTR_PRIMARY || if (constraint->contype == CONSTR_PRIMARY ||
constraint->contype == CONSTR_UNIQUE) constraint->contype == CONSTR_UNIQUE ||
constraint->contype == CONSTR_EXCLUSION)
{ {
return true; return true;
} }
@ -466,7 +467,7 @@ TypeDropIndexConstraint(const AlterTableCmd *command,
* At this stage, our only option is performing a relationId lookup. We * At this stage, our only option is performing a relationId lookup. We
* first find the relationId, and then scan the pg_constraints system * first find the relationId, and then scan the pg_constraints system
* catalog using this relationId. Finally, we check if the passed in * 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); relationId = RangeVarGetRelid(relation, NoLock, failOK);
if (!OidIsValid(relationId)) if (!OidIsValid(relationId))
@ -497,7 +498,8 @@ TypeDropIndexConstraint(const AlterTableCmd *command,
{ {
/* we found the constraint, now check if it is for an index */ /* we found the constraint, now check if it is for an index */
if (constraintForm->contype == CONSTRAINT_PRIMARY || if (constraintForm->contype == CONSTRAINT_PRIMARY ||
constraintForm->contype == CONSTRAINT_UNIQUE) constraintForm->contype == CONSTRAINT_UNIQUE ||
constraintForm->contype == CONSTRAINT_EXCLUSION)
{ {
indexConstraint = true; indexConstraint = true;
} }

View File

@ -23,7 +23,6 @@
#define SHARD_NAME_SEPARATOR '_' #define SHARD_NAME_SEPARATOR '_'
#define INVALID_SHARD_ID 0 #define INVALID_SHARD_ID 0
/* /*
* RelayFileState represents last known states of shards on a given node. We * 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 * currently only have shards in finalized or cached state; and set this state

View File

@ -24,8 +24,8 @@ CREATE TABLE lineitem (
l_comment varchar(44) not null, l_comment varchar(44) not null,
PRIMARY KEY(l_orderkey, l_linenumber) ); PRIMARY KEY(l_orderkey, l_linenumber) );
SELECT master_create_distributed_table('lineitem', 'l_orderkey', 'append'); SELECT master_create_distributed_table('lineitem', 'l_orderkey', 'append');
WARNING: table "lineitem" has a unique constraint WARNING: table "lineitem" has a UNIQUE or EXCLUDE constraint
DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced.
HINT: Consider using hash partitioning. HINT: Consider using hash partitioning.
master_create_distributed_table master_create_distributed_table
--------------------------------- ---------------------------------
@ -47,8 +47,8 @@ CREATE TABLE orders (
o_comment varchar(79) not null, o_comment varchar(79) not null,
PRIMARY KEY(o_orderkey) ); PRIMARY KEY(o_orderkey) );
SELECT master_create_distributed_table('orders', 'o_orderkey', 'append'); SELECT master_create_distributed_table('orders', 'o_orderkey', 'append');
WARNING: table "orders" has a unique constraint WARNING: table "orders" has a UNIQUE or EXCLUDE constraint
DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced.
HINT: Consider using hash partitioning. HINT: Consider using hash partitioning.
master_create_distributed_table master_create_distributed_table
--------------------------------- ---------------------------------
@ -119,86 +119,3 @@ SELECT master_create_distributed_table('supplier', 's_suppkey', 'append');
(1 row) (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;

View File

@ -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;

View File

@ -23,6 +23,7 @@ test: multi_table_ddl
# uploading data to it. # uploading data to it.
# ---------- # ----------
test: multi_create_table test: multi_create_table
test: multi_create_table_constraints
test: multi_master_protocol test: multi_master_protocol
test: multi_load_data test: multi_load_data

View File

@ -458,8 +458,8 @@ CREATE TABLE customer_worker_copy_append (
primary key (c_custkey)); primary key (c_custkey));
CREATE INDEX ON customer_worker_copy_append (c_name); CREATE INDEX ON customer_worker_copy_append (c_name);
SELECT master_create_distributed_table('customer_worker_copy_append', 'c_custkey', 'append'); SELECT master_create_distributed_table('customer_worker_copy_append', 'c_custkey', 'append');
WARNING: table "customer_worker_copy_append" has a unique constraint WARNING: table "customer_worker_copy_append" has a UNIQUE or EXCLUDE constraint
DETAIL: Unique constraints and primary keys on append-partitioned tables cannot be enforced. DETAIL: UNIQUE constraints, EXCLUDE constraints, and PRIMARY KEYs on append-partitioned tables cannot be enforced.
HINT: Consider using hash partitioning. HINT: Consider using hash partitioning.
master_create_distributed_table master_create_distributed_table
--------------------------------- ---------------------------------

View File

@ -2,11 +2,9 @@
-- MULTI_CREATE_TABLE -- MULTI_CREATE_TABLE
-- --
ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 360000; ALTER SEQUENCE pg_catalog.pg_dist_shardid_seq RESTART 360000;
ALTER SEQUENCE pg_catalog.pg_dist_jobid_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 -- Create new table definitions for use in testing in distributed planning and
-- execution functionality. Also create indexes to boost performance. -- execution functionality. Also create indexes to boost performance.
@ -99,65 +97,3 @@ CREATE TABLE supplier
s_comment varchar(101) not null s_comment varchar(101) not null
); );
SELECT master_create_distributed_table('supplier', 's_suppkey', 'append'); 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;

View File

@ -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;