From 2fa4e38841b575d4adb6aa09c72a2ee7ec42a28c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Mon, 4 Jun 2018 16:53:18 +0300 Subject: [PATCH 01/10] FK from dist to ref can be added with alter table --- .../distributed/ddl/foreign_constraint.c | 195 ++-- .../distributed/executor/multi_utility.c | 42 +- src/include/distributed/multi_utility.h | 1 + .../foreign_key_to_reference_table.out | 991 ++++++++++++++++++ .../regress/expected/multi_foreign_key.out | 253 +++-- src/test/regress/multi_schedule | 2 +- .../sql/foreign_key_to_reference_table.sql | 498 +++++++++ src/test/regress/sql/multi_foreign_key.sql | 53 +- 8 files changed, 1846 insertions(+), 189 deletions(-) create mode 100644 src/test/regress/expected/foreign_key_to_reference_table.out create mode 100644 src/test/regress/sql/foreign_key_to_reference_table.sql diff --git a/src/backend/distributed/ddl/foreign_constraint.c b/src/backend/distributed/ddl/foreign_constraint.c index b83d1e660..aa9248d22 100644 --- a/src/backend/distributed/ddl/foreign_constraint.c +++ b/src/backend/distributed/ddl/foreign_constraint.c @@ -34,12 +34,16 @@ * environment. * * To support foreign constraints, we require that; - * - Referencing and referenced tables are hash distributed. - * - Referencing and referenced tables are co-located. - * - Foreign constraint is defined over distribution column. - * - ON DELETE/UPDATE SET NULL, ON DELETE/UPDATE SET DEFAULT and ON UPDATE CASCADE options - * are not used. - * - Replication factors of referencing and referenced table are 1. + * - If referencing and referenced tables are hash-distributed + * - Referencing and referenced tables are co-located. + * - Foreign constraint is defined over distribution column. + * - ON DELETE/UPDATE SET NULL, ON DELETE/UPDATE SET DEFAULT and ON UPDATE CASCADE options + * are not used. + * - Replication factors of referencing and referenced table are 1. + * - If referenced table is a reference table + * - ON DELETE/UPDATE SET NULL, ON DELETE/UPDATE SET DEFAULT and ON UPDATE CASCADE options + * are not used on the distribution key of the referencing column. + * - If referencing table is a reference table, error out */ void ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, @@ -66,6 +70,8 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, int attrIdx = 0; bool foreignConstraintOnPartitionColumn = false; bool selfReferencingTable = false; + bool referencedTableIsAReferenceTable = false; + bool referencingColumnsIncludeDistKey = false; pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, @@ -85,55 +91,26 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, continue; } + /* + * We should make this check in this loop because the error message will only + * be given if the table has a foreign constraint and the table is a reference + * table. + */ + if (distributionMethod == DISTRIBUTE_BY_NONE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create foreign key constraint because " + "reference tables are not supported as the " + "referencing table of a foreign constraint"), + errdetail("Reference tables are only supported as the " + "referenced table of a foreign key when the " + "referencing table is a hash distributed " + "table"))); + } + referencedTableId = constraintForm->confrelid; selfReferencingTable = referencingTableId == referencedTableId; - /* - * We do not support foreign keys for reference tables. Here we skip the second - * part of check if the table is a self referencing table because; - * - PartitionMethod only works for distributed tables and this table may not be - * distributed yet. - * - Since referencing and referenced tables are same, it is OK to not checking - * distribution method twice. - */ - if (distributionMethod == DISTRIBUTE_BY_NONE || - (!selfReferencingTable && - PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE)) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create foreign key constraint from or to " - "reference tables"))); - } - - /* - * ON DELETE SET NULL and ON DELETE SET DEFAULT is not supported. Because we do - * not want to set partition column to NULL or default value. - */ - if (constraintForm->confdeltype == FKCONSTR_ACTION_SETNULL || - constraintForm->confdeltype == FKCONSTR_ACTION_SETDEFAULT) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create foreign key constraint"), - errdetail("SET NULL or SET DEFAULT is not supported" - " in ON DELETE operation."))); - } - - /* - * ON UPDATE SET NULL, ON UPDATE SET DEFAULT and UPDATE CASCADE is not supported. - * Because we do not want to set partition column to NULL or default value. Also - * cascading update operation would require re-partitioning. Updating partition - * column value is not allowed anyway even outside of foreign key concept. - */ - if (constraintForm->confupdtype == FKCONSTR_ACTION_SETNULL || - constraintForm->confupdtype == FKCONSTR_ACTION_SETDEFAULT || - constraintForm->confupdtype == FKCONSTR_ACTION_CASCADE) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create foreign key constraint"), - errdetail("SET NULL, SET DEFAULT or CASCADE is not" - " supported in ON UPDATE operation."))); - } - /* * Some checks are not meaningful if foreign key references the table itself. * Therefore we will skip those checks. @@ -148,28 +125,43 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, "table."))); } - /* to enforce foreign constraints, tables must be co-located */ - referencedTableColocationId = TableColocationId(referencedTableId); - if (colocationId == INVALID_COLOCATION_ID || - colocationId != referencedTableColocationId) + /* + * PartitionMethod errors out when it is called for non-distributed + * tables. This is why we make this check under !selfReferencingTable + * and after !IsDistributedTable(referencedTableId). + */ + if (PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot create foreign key constraint"), - errdetail("Foreign key constraint can only be created" - " on co-located tables."))); + referencedTableIsAReferenceTable = true; } /* - * Partition column must exist in both referencing and referenced side of the - * foreign key constraint. They also must be in same ordinal. + * To enforce foreign constraints, tables must be co-located unless a + * reference table is referenced. */ + referencedTableColocationId = TableColocationId(referencedTableId); + if (colocationId == INVALID_COLOCATION_ID || + (colocationId != referencedTableColocationId && + !referencedTableIsAReferenceTable)) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create foreign key constraint since " + "relations are not colocated or not referencing " + "a reference table"), + errdetail( + "A distributed table can only have foreign keys " + "if it is referencing another colocated hash " + "distributed table or a reference table"))); + } + referencedTablePartitionColumn = DistPartitionKey(referencedTableId); } else { /* - * Partition column must exist in both referencing and referenced side of the - * foreign key constraint. They also must be in same ordinal. + * If the referenced table is not a reference table, the distribution + * column in referencing table should be the distribution column in + * referenced table as well. */ referencedTablePartitionColumn = distributionColumn; } @@ -198,28 +190,83 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, AttrNumber referencedAttrNo = DatumGetInt16(referencedColumnArray[attrIdx]); if (distributionColumn->varattno == referencingAttrNo && - referencedTablePartitionColumn->varattno == referencedAttrNo) + (!referencedTableIsAReferenceTable && + referencedTablePartitionColumn->varattno == referencedAttrNo)) { foreignConstraintOnPartitionColumn = true; } + + if (distributionColumn->varattno == referencingAttrNo) + { + referencingColumnsIncludeDistKey = true; + } } - if (!foreignConstraintOnPartitionColumn) + + /* + * If columns in the foreign key includes the distribution key from the + * referencing side, we do not allow update/delete operations through + * foreign key constraints (e.g. ... ON UPDATE SET NULL) + */ + + if (referencingColumnsIncludeDistKey) + { + /* + * ON DELETE SET NULL and ON DELETE SET DEFAULT is not supported. Because we do + * not want to set partition column to NULL or default value. + */ + if (constraintForm->confdeltype == FKCONSTR_ACTION_SETNULL || + constraintForm->confdeltype == FKCONSTR_ACTION_SETDEFAULT) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create foreign key constraint"), + errdetail("SET NULL or SET DEFAULT is not supported" + " in ON DELETE operation when distribution " + "key is included in the foreign key constraint"))); + } + + /* + * ON UPDATE SET NULL, ON UPDATE SET DEFAULT and UPDATE CASCADE is not supported. + * Because we do not want to set partition column to NULL or default value. Also + * cascading update operation would require re-partitioning. Updating partition + * column value is not allowed anyway even outside of foreign key concept. + */ + if (constraintForm->confupdtype == FKCONSTR_ACTION_SETNULL || + constraintForm->confupdtype == FKCONSTR_ACTION_SETDEFAULT || + constraintForm->confupdtype == FKCONSTR_ACTION_CASCADE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create foreign key constraint"), + errdetail("SET NULL, SET DEFAULT or CASCADE is not " + "supported in ON UPDATE operation when " + "distribution key included in the foreign " + "constraint."))); + } + } + + /* + * if tables are hash-distributed and colocated, we need to make sure that + * the distribution key is included in foreign constraint. + */ + if (!referencedTableIsAReferenceTable && !foreignConstraintOnPartitionColumn) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), - errdetail("Partition column must exist both " - "referencing and referenced side of the " - "foreign constraint statement and it must " - "be in the same ordinal in both sides."))); + errdetail("Foreign keys are supported in two cases, " + "either in between two colocated tables including " + "partition column in the same ordinal in the both " + "tables or from distributed to reference tables"))); } /* * We do not allow to create foreign constraints if shard replication factor is * greater than 1. Because in our current design, multiple replicas may cause - * locking problems and inconsistent shard contents. We don't check the referenced - * table, since referenced and referencing tables should be co-located and - * colocation check has been done above. + * locking problems and inconsistent shard contents. + * + * Note that we allow referenced table to be a reference table (e.g., not a + * single replicated table). This is allowed since (a) we are sure that + * placements always be in the same state (b) executors are aware of reference + * tables and handle concurrency related issues accordingly. */ if (IsDistributedTable(referencingTableId)) { @@ -231,6 +278,8 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, } else { + Assert(distributionMethod == DISTRIBUTE_BY_HASH); + /* check whether creating single replicated table with foreign constraint */ if (ShardReplicationFactor > 1) { diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index 6c8e658d8..007f1090f 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -1380,6 +1380,17 @@ PlanAlterTableStmt(AlterTableStmt *alterTableStatement, const char *alterTableCo /* if foreign key related, use specialized task list function ... */ ddlJob->taskList = InterShardDDLTaskList(leftRelationId, rightRelationId, alterTableCommand); + + /* + * We need to execute the ddls working with reference tables on the + * right side sequentially, because parallel ddl operations + * relating to one and only shard of a reference table on a worker + * may cause self-deadlocks. + */ + if (PartitionMethod(rightRelationId) == DISTRIBUTE_BY_NONE) + { + ddlJob->executeSequentially = true; + } } else { @@ -2885,13 +2896,14 @@ ExecuteDistributedDDLJob(DDLJob *ddlJob) SendCommandToWorkers(WORKERS_WITH_METADATA, (char *) ddlJob->commandString); } - if (MultiShardConnectionType == PARALLEL_CONNECTION) + if (MultiShardConnectionType == SEQUENTIAL_CONNECTION || + ddlJob->executeSequentially) { - ExecuteModifyTasksWithoutResults(ddlJob->taskList); + ExecuteModifyTasksSequentiallyWithoutResults(ddlJob->taskList, CMD_UTILITY); } else { - ExecuteModifyTasksSequentiallyWithoutResults(ddlJob->taskList, CMD_UTILITY); + ExecuteModifyTasksWithoutResults(ddlJob->taskList); } } else @@ -3102,6 +3114,7 @@ InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId, char *leftSchemaName = get_namespace_name(leftSchemaId); char *escapedLeftSchemaName = quote_literal_cstr(leftSchemaName); + char rightPartitionMethod = PartitionMethod(rightRelationId); List *rightShardList = LoadShardIntervalList(rightRelationId); ListCell *rightShardCell = NULL; Oid rightSchemaId = get_rel_namespace(rightRelationId); @@ -3112,6 +3125,29 @@ InterShardDDLTaskList(Oid leftRelationId, Oid rightRelationId, uint64 jobId = INVALID_JOB_ID; int taskId = 1; + /* + * If the rightPartitionMethod is a reference table, we need to make sure + * that the tasks are created in a way that the right shard stays the same + * since we only have one placement per worker. This hack is first implemented + * for foreign constraint support from distributed tables to reference tables. + */ + if (rightPartitionMethod == DISTRIBUTE_BY_NONE) + { + ShardInterval *rightShardInterval = NULL; + int rightShardCount = list_length(rightShardList); + int leftShardCount = list_length(leftShardList); + int shardCounter = 0; + + Assert(rightShardCount == 1); + + rightShardInterval = (ShardInterval *) linitial(rightShardList); + for (shardCounter = rightShardCount; shardCounter < leftShardCount; + shardCounter++) + { + rightShardList = lappend(rightShardList, rightShardInterval); + } + } + /* lock metadata before getting placement lists */ LockShardListMetadata(leftShardList, ShareLock); diff --git a/src/include/distributed/multi_utility.h b/src/include/distributed/multi_utility.h index a0f044244..45c38252b 100644 --- a/src/include/distributed/multi_utility.h +++ b/src/include/distributed/multi_utility.h @@ -25,6 +25,7 @@ typedef struct DDLJob { Oid targetRelationId; /* oid of the target distributed relation */ bool concurrentIndexCmd; /* related to a CONCURRENTLY index command? */ + bool executeSequentially; const char *commandString; /* initial (coordinator) DDL command string */ List *taskList; /* worker DDL tasks to execute */ } DDLJob; diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out new file mode 100644 index 000000000..72e593647 --- /dev/null +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -0,0 +1,991 @@ +-- +-- FOREIGN_KEY_TO_REFERENCE_TABLE +-- +CREATE SCHEMA fkey_reference_table; +SET search_path TO 'fkey_reference_table'; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 8; +SET citus.next_shard_id TO 7000000; +CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); +SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE TYPE") + (localhost,57638,t,"CREATE TYPE") +(2 rows) + +CREATE VIEW table_fkeys_in_workers AS +SELECT +(json_populate_record(NULL::foreign_details, + json_array_elements_text((run_command_on_workers( $$ + SELECT + COALESCE(json_agg(row_to_json(d)), '[]'::json) + FROM + ( + SELECT + distinct name, + relid::regclass::text, + refd_relid::regclass::text + FROM + table_fkey_cols + ) + d $$ )).RESULT::json )::json )).* ; +CREATE TABLE referenced_table(id int UNIQUE, test_column int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +-- we still do not support update/delete operations through foreign constraints if the foreign key includes the distribution column +-- All should fail +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +-- try with multiple columns including the distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +-- all of the above is supported if the foreign key does not include distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000043 | fkey_reference_table.referencing_table_7000043 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000044 | fkey_reference_table.referencing_table_7000044 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000045 | fkey_reference_table.referencing_table_7000045 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000046 | fkey_reference_table.referencing_table_7000046 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000047 | fkey_reference_table.referencing_table_7000047 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000048 | fkey_reference_table.referencing_table_7000048 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000049 | fkey_reference_table.referencing_table_7000049 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000050 | fkey_reference_table.referencing_table_7000050 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000051 | fkey_reference_table.referencing_table_7000051 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000052 | fkey_reference_table.referencing_table_7000052 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000053 | fkey_reference_table.referencing_table_7000053 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000054 | fkey_reference_table.referencing_table_7000054 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000055 | fkey_reference_table.referencing_table_7000055 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000056 | fkey_reference_table.referencing_table_7000056 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000057 | fkey_reference_table.referencing_table_7000057 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000058 | fkey_reference_table.referencing_table_7000058 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000059 | fkey_reference_table.referencing_table_7000059 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000060 | fkey_reference_table.referencing_table_7000060 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000061 | fkey_reference_table.referencing_table_7000061 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000062 | fkey_reference_table.referencing_table_7000062 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000063 | fkey_reference_table.referencing_table_7000063 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000064 | fkey_reference_table.referencing_table_7000064 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000065 | fkey_reference_table.referencing_table_7000065 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000066 | fkey_reference_table.referencing_table_7000066 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000067 | fkey_reference_table.referencing_table_7000067 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000068 | fkey_reference_table.referencing_table_7000068 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000069 | fkey_reference_table.referencing_table_7000069 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000070 | fkey_reference_table.referencing_table_7000070 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000071 | fkey_reference_table.referencing_table_7000071 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000072 | fkey_reference_table.referencing_table_7000072 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000073 | fkey_reference_table.referencing_table_7000073 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000074 | fkey_reference_table.referencing_table_7000074 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000075 | fkey_reference_table.referencing_table_7000075 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000076 | fkey_reference_table.referencing_table_7000076 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000077 | fkey_reference_table.referencing_table_7000077 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000078 | fkey_reference_table.referencing_table_7000078 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000079 | fkey_reference_table.referencing_table_7000079 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000080 | fkey_reference_table.referencing_table_7000080 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000081 | fkey_reference_table.referencing_table_7000081 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000082 | fkey_reference_table.referencing_table_7000082 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +-- foreign keys are only supported when the replication factor = 1 +SET citus.shard_replication_factor TO 2; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint +DETAIL: Citus Community Edition currently supports foreign key constraints only for "citus.shard_replication_factor = 1". +HINT: Please change "citus.shard_replication_factor to 1". To learn more about using foreign keys with other replication factors, please contact us at https://citusdata.com/about/contact_us. +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +SET citus.shard_replication_factor TO 1; +-- foreign keys are supported either in between distributed tables including the +-- distribution column or from distributed tables to reference tables. +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'append'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'range'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- test foreign constraint with correct conditions +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(id); +-- test inserts +-- test insert to referencing table while there is NO corresponding value in referenced table +INSERT INTO referencing_table VALUES(1, 1); +ERROR: insert or update on table "referencing_table_7000092" violates foreign key constraint "fkey_ref_7000092" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_7000091". +CONTEXT: while executing command on localhost:57637 +-- test insert to referencing while there is corresponding value in referenced table +INSERT INTO referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_table SELECT x, x from generate_series(1,500) as f(x); +-- test deletes +-- test delete from referenced table while there is corresponding value in referencing table +DELETE FROM referenced_table WHERE id > 3; +ERROR: update or delete on table "referenced_table_7000091" violates foreign key constraint "fkey_ref_7000094" on table "referencing_table_7000094" +DETAIL: Key (id)=(4) is still referenced from table "referencing_table_7000094". +CONTEXT: while executing command on localhost:57637 +-- test delete from referenced table while there is NO corresponding value in referencing table +DELETE FROM referenced_table WHERE id = 501; +-- test cascading truncate +-- will fail for now +TRUNCATE referenced_table CASCADE; +NOTICE: truncate cascades to table "referencing_table" +ERROR: canceling the transaction since it was involved in a distributed deadlock +SELECT count(*) FROM referencing_table; + count +------- + 500 +(1 row) + +-- drop table for next tests +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- self referencing foreign key on reference tables are not allowed +-- TODO try create_reference_table with already created foreign key. +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referencing_table'); + create_reference_table +------------------------ + +(1 row) + +-- self referencing foreign key +ALTER TABLE referenced_table ADD CONSTRAINT fkey_ref FOREIGN KEY (test_column) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table +-- foreign Keys from reference table to reference table are not allowed +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table +DROP TABLE referenced_table; +DROP TABLE referencing_table; +-- cascades on delete with different schemas +CREATE SCHEMA referenced_schema; +CREATE SCHEMA referencing_schema; +CREATE TABLE referenced_schema.referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_schema.referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_schema.referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_schema.referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_schema.referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_schema.referenced_table(id) ON DELETE CASCADE; +INSERT INTO referenced_schema.referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_schema.referencing_table SELECT x, x from generate_series(1,1000) as f(x); +DELETE FROM referenced_schema.referenced_table WHERE id > 800; +SELECT count(*) FROM referencing_schema.referencing_table; + count +------- + 800 +(1 row) + +DROP SCHEMA referenced_schema CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table referenced_schema.referenced_table +drop cascades to constraint fkey_ref on table referencing_schema.referencing_table +DROP SCHEMA referencing_schema CASCADE; +NOTICE: drop cascades to table referencing_schema.referencing_table +-- on delete set update cascades properly +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT 1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; +INSERT INTO referenced_table SELECT x, x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE test_column > 800; +SELECT count(*) FROM referencing_table WHERE ref_id = 1; + count +------- + 201 +(1 row) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- foreign key as composite key +CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int); +SELECT run_command_on_workers($$CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int)$$) ORDER BY 1; + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE TYPE") + (localhost,57638,t,"CREATE TYPE") +(2 rows) + +CREATE TABLE referenced_table(test_column composite, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, referencing_composite composite); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (referencing_composite) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table SELECT (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE (test_column).key1 > 900; +SELECT count(*) FROM referencing_table; + count +------- + 899 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referenced column +-- in the foreign constraint. We'll first show that and insert on non-serial +-- column successfully inserts into the serial and referenced column. +-- Accordingly, the inserts into the referencing table which references to the +-- serial column will be successful. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE test_column2 > 10; +SELECT count(*) FROM referencing_table; + count +------- + 10 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referencing column +-- in the foreign constraint. We'll first show that the values that exist +-- in the referenced tables are successfully generated by the serial column +-- and inserted to the distributed table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column int PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table SELECT x,x FROM generate_series(1,1000) AS f(x); +-- Success for existing inserts +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); +ERROR: insert or update on table "referencing_table_7000144" violates foreign key constraint "fkey_ref_7000144" +DETAIL: Key (ref_id)=(1006) is not present in table "referenced_table_7000138". +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referencing column +-- and referenced columns in a foreign constraint. We'll first show that the +-- the inserts into referenced column will successfully generate and insert +-- data into serial column. Then, we will be successfully insert the same amount +-- of data into referencing table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +-- Success for existing values +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); +ERROR: insert or update on table "referencing_table_7000151" violates foreign key constraint "fkey_ref_7000151" +DETAIL: Key (ref_id)=(1003) is not present in table "referenced_table_7000147". +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we use a volatile function in the referencing +-- column in a foreign constraint. We show that if the data exists in the +-- referenced table, we can successfully use volatile functions with +-- foreign constraints. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x,(random()*1000)::int FROM generate_series(0,1000) AS f(x); +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we show that Citus currently does not support +-- VALIDATE command. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT NOT VALID; +-- Even if the foreign constraint is added with "NOT VALID", +-- we make sure that it is still applied to the upcoming inserts. +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); +-- we expect this to fail because of the foreign constraint. +INSERT INTO referencing_table SELECT x, x FROM generate_series(1000,1001) AS f(x); +ERROR: insert or update on table "referencing_table_7000171" violates foreign key constraint "fkey_ref_7000171" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000165". +-- currently not supported +ALTER TABLE referencing_table VALIDATE CONSTRAINT fkey_ref; +ERROR: alter table command is currently unsupported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT, ADD|DROP CONSTRAINT, SET (), RESET (), ATTACH|DETACH PARTITION and TYPE subcommands are supported. +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following tests, we create a foreign constraint with +-- ON UPDATE CASCADE and see if it works properly with cascading upsert +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE; +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table VALUES (1,2), (2,3), (3,4), (4,5) +ON CONFLICT (test_column) +DO UPDATE + SET test_column = -1 * EXCLUDED.test_column; +SELECT * FROM referencing_table WHERE ref_id < 0 ORDER BY 1; + id | ref_id +----+-------- + 1 | -1 + 2 | -2 + 3 | -3 + 4 | -4 +(4 rows) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- Chained references +-- In the following test, we create foreign keys from one column in a distributed +-- table to two reference tables. We expect to see that even if a data exist in +-- one reference table, it is not going to be inserted in to referencing table +-- because of lack of the key in the other table. Data can only be inserted into +-- referencing table if it exists in both referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------+------------------------------------------------+------------------------------------------------ + fkey_ref_7000185 | fkey_reference_table.referencing_table_7000185 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000186 | fkey_reference_table.referencing_table_7000186 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000187 | fkey_reference_table.referencing_table_7000187 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000188 | fkey_reference_table.referencing_table_7000188 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000189 | fkey_reference_table.referencing_table_7000189 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000190 | fkey_reference_table.referencing_table_7000190 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000191 | fkey_reference_table.referencing_table_7000191 | fkey_reference_table.referenced_table_7000183 + fkey_ref_7000192 | fkey_reference_table.referencing_table_7000192 | fkey_reference_table.referenced_table_7000183 + foreign_key_2_7000185 | fkey_reference_table.referencing_table_7000185 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000186 | fkey_reference_table.referencing_table_7000186 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000187 | fkey_reference_table.referencing_table_7000187 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000188 | fkey_reference_table.referencing_table_7000188 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000189 | fkey_reference_table.referencing_table_7000189 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000190 | fkey_reference_table.referencing_table_7000190 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000191 | fkey_reference_table.referencing_table_7000191 | fkey_reference_table.referenced_table2_7000184 + foreign_key_2_7000192 | fkey_reference_table.referencing_table_7000192 | fkey_reference_table.referenced_table2_7000184 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "foreign_key_2_7000192" +DETAIL: Key (id)=(9) is not present in table "referenced_table2_7000184". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "foreign_key_2_7000192" +DETAIL: Key (id)=(9) is not present in table "referenced_table2_7000184". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "fkey_ref_7000192" +DETAIL: Key (id)=(1023) is not present in table "referenced_table_7000183". +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(600,900) AS f(x); +SELECT count(*) FROM referencing_table; + count +------- + 301 +(1 row) + +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; + count +------- + 201 +(1 row) + +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + count +------- + 101 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we create foreign keys from two columns in a distributed +-- table to two reference tables separately. We expect to see that even if a data +-- exist in one reference table for one column, it is not going to be inserted in +-- to referencing table because the other constraint doesn't hold. Data can only +-- be inserted into referencing table if both columns exist in respective columns +-- in referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------+------------------------------------------------+------------------------------------------------ + fkey_ref_7000195 | fkey_reference_table.referencing_table_7000195 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000196 | fkey_reference_table.referencing_table_7000196 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000197 | fkey_reference_table.referencing_table_7000197 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000198 | fkey_reference_table.referencing_table_7000198 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000199 | fkey_reference_table.referencing_table_7000199 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000200 | fkey_reference_table.referencing_table_7000200 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000201 | fkey_reference_table.referencing_table_7000201 | fkey_reference_table.referenced_table_7000193 + fkey_ref_7000202 | fkey_reference_table.referencing_table_7000202 | fkey_reference_table.referenced_table_7000193 + foreign_key_2_7000195 | fkey_reference_table.referencing_table_7000195 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000196 | fkey_reference_table.referencing_table_7000196 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000197 | fkey_reference_table.referencing_table_7000197 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000198 | fkey_reference_table.referencing_table_7000198 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000199 | fkey_reference_table.referencing_table_7000199 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000200 | fkey_reference_table.referencing_table_7000200 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000201 | fkey_reference_table.referencing_table_7000201 | fkey_reference_table.referenced_table2_7000194 + foreign_key_2_7000202 | fkey_reference_table.referencing_table_7000202 | fkey_reference_table.referenced_table2_7000194 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "foreign_key_2_7000197" +DETAIL: Key (ref_id)=(5) is not present in table "referenced_table2_7000194". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "foreign_key_2_7000197" +DETAIL: Key (ref_id)=(5) is not present in table "referenced_table2_7000194". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "fkey_ref_7000197" +DETAIL: Key (id)=(1015) is not present in table "referenced_table_7000193". +-- should success +INSERT INTO referencing_table SELECT x, x+501 FROM generate_series(0,1000) AS f(x); +SELECT count(*) FROM referencing_table; + count +------- + 1001 +(1 row) + +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; + count +------- + 301 +(1 row) + +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + count +------- + 0 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- two distributed tables are referencing to one reference table and +-- in the same time the distributed table 2 is referencing to +-- distributed table 1. Thus, we have a triangular +-- distributed table 1 has a foreign key from the distribution column to reference table +-- distributed table 2 has a foreign key from a non-distribution column to reference table +-- distributed table 2 has a foreign key to distributed table 1 on the distribution column +-- We show that inserts into distributed table 2 will fail if the data does not exist in distributed table 1 +-- Delete from reference table cascades to both of the distributed tables properly +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int); +CREATE TABLE referencing_table2(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('referencing_table2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; +SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref_to_dist FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE; +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + name | relid | refd_relid +--------------------------+-------------------------------------------------+------------------------------------------------ + fkey_ref_7000204 | fkey_reference_table.referencing_table_7000204 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000205 | fkey_reference_table.referencing_table_7000205 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000206 | fkey_reference_table.referencing_table_7000206 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000207 | fkey_reference_table.referencing_table_7000207 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000208 | fkey_reference_table.referencing_table_7000208 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000209 | fkey_reference_table.referencing_table_7000209 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000210 | fkey_reference_table.referencing_table_7000210 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000211 | fkey_reference_table.referencing_table_7000211 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000212 | fkey_reference_table.referencing_table2_7000212 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000213 | fkey_reference_table.referencing_table2_7000213 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000214 | fkey_reference_table.referencing_table2_7000214 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000215 | fkey_reference_table.referencing_table2_7000215 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000216 | fkey_reference_table.referencing_table2_7000216 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000217 | fkey_reference_table.referencing_table2_7000217 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000218 | fkey_reference_table.referencing_table2_7000218 | fkey_reference_table.referenced_table_7000203 + fkey_ref_7000219 | fkey_reference_table.referencing_table2_7000219 | fkey_reference_table.referenced_table_7000203 + fkey_ref_to_dist_7000212 | fkey_reference_table.referencing_table2_7000212 | fkey_reference_table.referencing_table_7000204 + fkey_ref_to_dist_7000213 | fkey_reference_table.referencing_table2_7000213 | fkey_reference_table.referencing_table_7000205 + fkey_ref_to_dist_7000214 | fkey_reference_table.referencing_table2_7000214 | fkey_reference_table.referencing_table_7000206 + fkey_ref_to_dist_7000215 | fkey_reference_table.referencing_table2_7000215 | fkey_reference_table.referencing_table_7000207 + fkey_ref_to_dist_7000216 | fkey_reference_table.referencing_table2_7000216 | fkey_reference_table.referencing_table_7000208 + fkey_ref_to_dist_7000217 | fkey_reference_table.referencing_table2_7000217 | fkey_reference_table.referencing_table_7000209 + fkey_ref_to_dist_7000218 | fkey_reference_table.referencing_table2_7000218 | fkey_reference_table.referencing_table_7000210 + fkey_ref_to_dist_7000219 | fkey_reference_table.referencing_table2_7000219 | fkey_reference_table.referencing_table_7000211 +(24 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,100) AS f(x); +ERROR: insert or update on table "referencing_table2_7000215" violates foreign key constraint "fkey_ref_to_dist_7000215" +DETAIL: Key (id)=(0) is not present in table "referencing_table_7000207". +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(200,500) AS f(x); +ERROR: insert or update on table "referencing_table2_7000215" violates foreign key constraint "fkey_ref_to_dist_7000215" +DETAIL: Key (id)=(407) is not present in table "referencing_table_7000207". +-- should success +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,300) AS f(x); +DELETE FROM referenced_table WHERE test_column < 200; +SELECT count(*) FROM referencing_table; + count +------- + 201 +(1 row) + +SELECT count(*) FROM referencing_table2; + count +------- + 101 +(1 row) + +DELETE FROM referencing_table WHERE id > 200; +SELECT count(*) FROM referencing_table2; + count +------- + 1 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint fkey_ref on table referencing_table2 +drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref_to_dist on table referencing_table2 +DROP TABLE referencing_table2 CASCADE; +-- In this test we have a chained relationship in form of +-- distributed table (referencing_referencing_table) has a foreign key with two columns +-- to another distributed table (referencing_table) +-- referencing_table has another foreign key with 2 columns to referenced_table. +-- We will show that a cascading delete on referenced_table reaches to referencing_referencing_table. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column, test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, ref_id2 int, PRIMARY KEY(id, ref_id)); +CREATE TABLE referencing_referencing_table(id int, ref_id int, FOREIGN KEY (id, ref_id) REFERENCES referencing_table(id, ref_id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('referencing_referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id, ref_id2) REFERENCES referenced_table(test_column, test_column2) ON DELETE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.referencing%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------------------+------------------------------------------------------------+------------------------------------------------ + fkey_ref_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000227 | fkey_reference_table.referencing_table_7000227 | fkey_reference_table.referenced_table_7000220 + fkey_ref_7000228 | fkey_reference_table.referencing_table_7000228 | fkey_reference_table.referenced_table_7000220 + referencing_referencing_table_id_fkey_7000229 | fkey_reference_table.referencing_referencing_table_7000229 | fkey_reference_table.referencing_table_7000221 + referencing_referencing_table_id_fkey_7000230 | fkey_reference_table.referencing_referencing_table_7000230 | fkey_reference_table.referencing_table_7000222 + referencing_referencing_table_id_fkey_7000231 | fkey_reference_table.referencing_referencing_table_7000231 | fkey_reference_table.referencing_table_7000223 + referencing_referencing_table_id_fkey_7000232 | fkey_reference_table.referencing_referencing_table_7000232 | fkey_reference_table.referencing_table_7000224 + referencing_referencing_table_id_fkey_7000233 | fkey_reference_table.referencing_referencing_table_7000233 | fkey_reference_table.referencing_table_7000225 + referencing_referencing_table_id_fkey_7000234 | fkey_reference_table.referencing_referencing_table_7000234 | fkey_reference_table.referencing_table_7000226 + referencing_referencing_table_id_fkey_7000235 | fkey_reference_table.referencing_referencing_table_7000235 | fkey_reference_table.referencing_table_7000227 + referencing_referencing_table_id_fkey_7000236 | fkey_reference_table.referencing_referencing_table_7000236 | fkey_reference_table.referencing_table_7000228 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x+1, x+2 FROM generate_series(1,999) AS f(x); +INSERT INTO referencing_referencing_table SELECT x, x+1 FROM generate_series(1,999) AS f(x); +DELETE FROM referenced_table WHERE test_column > 800; +SELECT max(ref_id) FROM referencing_referencing_table; + max +----- + 800 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint referencing_referencing_table_id_fkey on table referencing_referencing_table +DROP TABLE referencing_referencing_table; +DROP SCHEMA fkey_reference_table CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to type foreign_details +drop cascades to view table_fkeys_in_workers +drop cascades to type composite +SET search_path TO DEFAULT; diff --git a/src/test/regress/expected/multi_foreign_key.out b/src/test/regress/expected/multi_foreign_key.out index 0e81ce217..5eaf5ecc0 100644 --- a/src/test/regress/expected/multi_foreign_key.out +++ b/src/test/regress/expected/multi_foreign_key.out @@ -16,47 +16,66 @@ SELECT create_distributed_table('referenced_table', 'id', 'hash'); CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation. +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation. +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET DEFAULT); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE CASCADE); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; +-- self referencing table with replication factor > 1 +CREATE TABLE self_referencing_table(id int, ref_id int, PRIMARY KEY (id, ref_id), FOREIGN KEY(id,ref_id) REFERENCES self_referencing_table(id, ref_id)); +SELECT create_distributed_table('self_referencing_table', 'id', 'hash'); +ERROR: cannot create foreign key constraint +DETAIL: Citus Community Edition currently supports foreign key constraints only for "citus.shard_replication_factor = 1". +HINT: Please change "citus.shard_replication_factor to 1". To learn more about using foreign keys with other replication factors, please contact us at https://citusdata.com/about/contact_us. +DROP TABLE self_referencing_table; +CREATE TABLE self_referencing_table(id int, ref_id int, PRIMARY KEY (id, ref_id)); +SELECT create_distributed_table('self_referencing_table', 'id', 'hash'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE self_referencing_table ADD CONSTRAINT fkey FOREIGN KEY(id,ref_id) REFERENCES self_referencing_table(id, ref_id); +ERROR: cannot create foreign key constraint +DETAIL: Citus Community Edition currently supports foreign key constraints only for "citus.shard_replication_factor = 1". +HINT: Please change "citus.shard_replication_factor to 1". To learn more about using foreign keys with other replication factors, please contact us at https://citusdata.com/about/contact_us. +DROP TABLE self_referencing_table; -- test foreign constraint creation on NOT co-located tables SET citus.shard_count TO 8; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id)); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); -ERROR: cannot create foreign key constraint -DETAIL: Foreign key constraint can only be created on co-located tables. +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table DROP TABLE referencing_table; SET citus.shard_count TO 32; -- test foreign constraint creation on non-partition columns CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id)); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: Partition column must exist both referencing and referenced side of the foreign constraint statement and it must be in the same ordinal in both sides. +DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables DROP TABLE referencing_table; -- test foreign constraint creation while column list are in incorrect order CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column)); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); ERROR: cannot create foreign key constraint -DETAIL: Partition column must exist both referencing and referenced side of the foreign constraint statement and it must be in the same ordinal in both sides. +DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables DROP TABLE referencing_table; -- test foreign constraint with replication factor > 1 CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id)); @@ -66,9 +85,38 @@ DETAIL: Citus Community Edition currently supports foreign key constraints only HINT: Please change "citus.shard_replication_factor to 1". To learn more about using foreign keys with other replication factors, please contact us at https://citusdata.com/about/contact_us. DROP TABLE referencing_table; DROP TABLE referenced_table; --- test foreign constraint with correct conditions +-- test foreign constraint creation on append and range distributed tables +-- foreign keys are supported either in between distributed tables including the +-- distribution column or from distributed tables to reference tables. SET citus.shard_replication_factor TO 1; CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'hash'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'id', 'append'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +DROP TABLE referencing_table; +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'range'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int,FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'id', 'range'); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- test foreign constraint with correct conditions +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id)); SELECT create_distributed_table('referenced_table', 'id', 'hash'); create_distributed_table @@ -85,8 +133,8 @@ SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); -- test inserts -- test insert to referencing table while there is NO corresponding value in referenced table INSERT INTO referencing_table VALUES(1, 1); -ERROR: insert or update on table "referencing_table_1350065" violates foreign key constraint "referencing_table_ref_id_fkey_1350065" -DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350033". +ERROR: insert or update on table "referencing_table_1350129" violates foreign key constraint "referencing_table_ref_id_fkey_1350129" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350097". CONTEXT: while executing command on localhost:57638 -- test insert to referencing while there is corresponding value in referenced table INSERT INTO referenced_table VALUES(1, 1); @@ -94,8 +142,8 @@ INSERT INTO referencing_table VALUES(1, 1); -- test deletes -- test delete from referenced table while there is corresponding value in referencing table DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350033" violates foreign key constraint "referencing_table_ref_id_fkey_1350065" on table "referencing_table_1350065" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350065". +ERROR: update or delete on table "referenced_table_1350097" violates foreign key constraint "referencing_table_ref_id_fkey_1350129" on table "referencing_table_1350129" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350129". CONTEXT: while executing command on localhost:57638 -- test delete from referenced table while there is NO corresponding value in referencing table DELETE FROM referencing_table WHERE ref_id = 1; @@ -189,8 +237,8 @@ SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350161" violates foreign key constraint "referencing_table_ref_id_fkey_1350193" on table "referencing_table_1350193" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350193". +ERROR: update or delete on table "referenced_table_1350225" violates foreign key constraint "referencing_table_ref_id_fkey_1350257" on table "referencing_table_1350257" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350257". CONTEXT: while executing command on localhost:57638 BEGIN; DELETE FROM referenced_table WHERE id = 1; @@ -227,8 +275,8 @@ INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); BEGIN; DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350225" violates foreign key constraint "referencing_table_ref_id_fkey_1350257" on table "referencing_table_1350257" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350257". +ERROR: update or delete on table "referenced_table_1350289" violates foreign key constraint "referencing_table_ref_id_fkey_1350321" on table "referencing_table_1350321" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350321". CONTEXT: while executing command on localhost:57638 DELETE FROM referencing_table WHERE ref_id = 1; ERROR: current transaction is aborted, commands ignored until end of transaction block @@ -265,8 +313,8 @@ SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); UPDATE referenced_table SET test_column = 10 WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350289" violates foreign key constraint "referencing_table_ref_id_fkey_1350321" on table "referencing_table_1350321" -DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350321". +ERROR: update or delete on table "referenced_table_1350353" violates foreign key constraint "referencing_table_ref_id_fkey_1350385" on table "referencing_table_1350385" +DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350385". CONTEXT: while executing command on localhost:57638 BEGIN; UPDATE referenced_table SET test_column = 10 WHERE id = 1; @@ -305,8 +353,8 @@ INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); BEGIN; UPDATE referenced_table SET test_column = 20 WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350353" violates foreign key constraint "referencing_table_ref_id_fkey_1350385" on table "referencing_table_1350385" -DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350385". +ERROR: update or delete on table "referenced_table_1350417" violates foreign key constraint "referencing_table_ref_id_fkey_1350449" on table "referencing_table_1350449" +DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350449". CONTEXT: while executing command on localhost:57638 UPDATE referencing_table SET id = 20 WHERE ref_id = 1; ERROR: current transaction is aborted, commands ignored until end of transaction block @@ -366,7 +414,7 @@ SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); (1 row) INSERT INTO referencing_table VALUES(null, 2); -ERROR: insert or update on table "referencing_table_1350536" violates foreign key constraint "referencing_table_ref_id_fkey_1350536" +ERROR: insert or update on table "referencing_table_1350600" violates foreign key constraint "referencing_table_ref_id_fkey_1350600" DETAIL: MATCH FULL does not allow mixing of null and nonnull key values. CONTEXT: while executing command on localhost:57637 SELECT * FROM referencing_table; @@ -378,29 +426,18 @@ DROP TABLE referencing_table; DROP TABLE referenced_table; -- Similar tests, but this time we push foreign key constraints created by ALTER TABLE queries -- create tables +SET citus.shard_count TO 4; CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); -SELECT master_create_distributed_table('referenced_table', 'id', 'hash'); - master_create_distributed_table ---------------------------------- - -(1 row) - -SELECT master_create_worker_shards('referenced_table', 4, 1); - master_create_worker_shards ------------------------------ +SELECT create_distributed_table('referenced_table', 'id', 'hash'); + create_distributed_table +-------------------------- (1 row) CREATE TABLE referencing_table(id int, ref_id int); -SELECT master_create_distributed_table('referencing_table', 'ref_id', 'hash'); - master_create_distributed_table ---------------------------------- - -(1 row) - -SELECT master_create_worker_shards('referencing_table', 4, 1); - master_create_worker_shards ------------------------------ +SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); + create_distributed_table +-------------------------- (1 row) @@ -415,19 +452,19 @@ ABORT; -- test foreign constraint creation with not supported parameters ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL; ERROR: cannot create foreign key constraint -DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation. +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; ERROR: cannot create foreign key constraint -DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation. +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL; ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET DEFAULT; ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE CASCADE; ERROR: cannot create foreign key constraint -DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation. +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. -- test foreign constraint creation with multiple subcommands ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id), ADD CONSTRAINT test_constraint FOREIGN KEY(id) REFERENCES referenced_table(test_column); @@ -437,9 +474,25 @@ HINT: You can issue each subcommand separately ALTER TABLE referencing_table ADD FOREIGN KEY(ref_id) REFERENCES referenced_table(id); ERROR: cannot create constraint without a name on a distributed table -- test foreign constraint creation on NOT co-located tables +DROP TABLE referencing_table; +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'hash'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'hash', colocate_with => 'none'); + create_distributed_table +-------------------------- + +(1 row) + ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id); -ERROR: cannot create foreign key constraint -DETAIL: Foreign key constraint can only be created on co-located tables. +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table -- create co-located tables DROP TABLE referencing_table; DROP TABLE referenced_table; @@ -463,38 +516,38 @@ ERROR: number of referencing and referenced columns for foreign key disagree -- test foreign constraint creation on non-partition columns ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(id) REFERENCES referenced_table(id); ERROR: cannot create foreign key constraint -DETAIL: Partition column must exist both referencing and referenced side of the foreign constraint statement and it must be in the same ordinal in both sides. +DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables -- test foreign constraint creation while column list are in incorrect order ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column); ERROR: cannot create foreign key constraint -DETAIL: Partition column must exist both referencing and referenced side of the foreign constraint statement and it must be in the same ordinal in both sides. +DETAIL: Foreign keys are supported in two cases, either in between two colocated tables including partition column in the same ordinal in the both tables or from distributed to reference tables -- test foreign constraint creation while column list are not in same length ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id, test_column); ERROR: number of referencing and referenced columns for foreign key disagree -- test foreign constraint creation while existing tables does not satisfy the constraint INSERT INTO referencing_table VALUES(1, 1); ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id); -ERROR: insert or update on table "referencing_table_1350585" violates foreign key constraint "test_constraint_1350585" -DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350553". -CONTEXT: while executing command on localhost:57638 +ERROR: insert or update on table "referencing_table_1350628" violates foreign key constraint "test_constraint_1350628" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350624". +CONTEXT: while executing command on localhost:57637 -- test foreign constraint with correct conditions DELETE FROM referencing_table WHERE ref_id = 1; ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id); -- test inserts -- test insert to referencing table while there is NO corresponding value in referenced table INSERT INTO referencing_table VALUES(1, 1); -ERROR: insert or update on table "referencing_table_1350585" violates foreign key constraint "test_constraint_1350585" -DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350553". -CONTEXT: while executing command on localhost:57638 +ERROR: insert or update on table "referencing_table_1350628" violates foreign key constraint "test_constraint_1350628" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_1350624". +CONTEXT: while executing command on localhost:57637 -- test insert to referencing while there is corresponding value in referenced table INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); -- test deletes -- test delete from referenced table while there is corresponding value in referencing table DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350553" violates foreign key constraint "test_constraint_1350585" on table "referencing_table_1350585" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350585". -CONTEXT: while executing command on localhost:57638 +ERROR: update or delete on table "referenced_table_1350624" violates foreign key constraint "test_constraint_1350628" on table "referencing_table_1350628" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350628". +CONTEXT: while executing command on localhost:57637 -- test delete from referenced table while there is NO corresponding value in referencing table DELETE FROM referencing_table WHERE ref_id = 1; DELETE FROM referenced_table WHERE id = 1; @@ -522,9 +575,9 @@ ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350553" violates foreign key constraint "test_constraint_1350585" on table "referencing_table_1350585" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350585". -CONTEXT: while executing command on localhost:57638 +ERROR: update or delete on table "referenced_table_1350624" violates foreign key constraint "test_constraint_1350628" on table "referencing_table_1350628" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350628". +CONTEXT: while executing command on localhost:57637 BEGIN; DELETE FROM referenced_table WHERE id = 1; DELETE FROM referencing_table WHERE ref_id = 1; @@ -546,9 +599,9 @@ INSERT INTO referenced_table VALUES(1, 1); INSERT INTO referencing_table VALUES(1, 1); BEGIN; DELETE FROM referenced_table WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350553" violates foreign key constraint "test_constraint_1350585" on table "referencing_table_1350585" -DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350585". -CONTEXT: while executing command on localhost:57638 +ERROR: update or delete on table "referenced_table_1350624" violates foreign key constraint "test_constraint_1350628" on table "referencing_table_1350628" +DETAIL: Key (id)=(1) is still referenced from table "referencing_table_1350628". +CONTEXT: while executing command on localhost:57637 DELETE FROM referencing_table WHERE ref_id = 1; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; @@ -568,9 +621,9 @@ ALTER TABLE referencing_table DROP CONSTRAINT test_constraint; -- test ON UPDATE NO ACTION + DEFERABLE + INITIALLY DEFERRED ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id, id) REFERENCES referenced_table(id, test_column) ON UPDATE NO ACTION DEFERRABLE INITIALLY DEFERRED; UPDATE referenced_table SET test_column = 10 WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350553" violates foreign key constraint "test_constraint_1350585" on table "referencing_table_1350585" -DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350585". -CONTEXT: while executing command on localhost:57638 +ERROR: update or delete on table "referenced_table_1350624" violates foreign key constraint "test_constraint_1350628" on table "referencing_table_1350628" +DETAIL: Key (id, test_column)=(1, 1) is still referenced from table "referencing_table_1350628". +CONTEXT: while executing command on localhost:57637 BEGIN; UPDATE referenced_table SET test_column = 10 WHERE id = 1; UPDATE referencing_table SET id = 10 WHERE ref_id = 1; @@ -592,9 +645,9 @@ ALTER TABLE referencing_table DROP CONSTRAINT test_constraint; ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id, id) REFERENCES referenced_table(id, test_column) ON UPDATE RESTRICT; BEGIN; UPDATE referenced_table SET test_column = 20 WHERE id = 1; -ERROR: update or delete on table "referenced_table_1350553" violates foreign key constraint "test_constraint_1350585" on table "referencing_table_1350585" -DETAIL: Key (id, test_column)=(1, 10) is still referenced from table "referencing_table_1350585". -CONTEXT: while executing command on localhost:57638 +ERROR: update or delete on table "referenced_table_1350624" violates foreign key constraint "test_constraint_1350628" on table "referencing_table_1350628" +DETAIL: Key (id, test_column)=(1, 10) is still referenced from table "referencing_table_1350628". +CONTEXT: while executing command on localhost:57637 UPDATE referencing_table SET id = 20 WHERE ref_id = 1; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; @@ -626,9 +679,9 @@ ALTER TABLE referencing_table DROP CONSTRAINT test_constraint; -- test MATCH FULL ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id, id) REFERENCES referenced_table(id, test_column) MATCH FULL; INSERT INTO referencing_table VALUES(null, 2); -ERROR: insert or update on table "referencing_table_1350608" violates foreign key constraint "test_constraint_1350608" +ERROR: insert or update on table "referencing_table_1350631" violates foreign key constraint "test_constraint_1350631" DETAIL: MATCH FULL does not allow mixing of null and nonnull key values. -CONTEXT: while executing command on localhost:57637 +CONTEXT: while executing command on localhost:57638 SELECT * FROM referencing_table; id | ref_id ----+-------- @@ -658,9 +711,9 @@ ALTER TABLE cyclic_reference_table1 ADD CONSTRAINT cyclic_constraint1 FOREIGN KE ALTER TABLE cyclic_reference_table2 ADD CONSTRAINT cyclic_constraint2 FOREIGN KEY(id, table1_id) REFERENCES cyclic_reference_table1(table2_id, id) DEFERRABLE INITIALLY DEFERRED; -- test insertion to a table which has cyclic foreign constraints, we expect that to fail INSERT INTO cyclic_reference_table1 VALUES(1, 1); -ERROR: insert or update on table "cyclic_reference_table1_1350617" violates foreign key constraint "cyclic_constraint1_1350617" -DETAIL: Key (id, table2_id)=(1, 1) is not present in table "cyclic_reference_table2_1350649". -CONTEXT: while executing command on localhost:57638 +ERROR: insert or update on table "cyclic_reference_table1_1350632" violates foreign key constraint "cyclic_constraint1_1350632" +DETAIL: Key (id, table2_id)=(1, 1) is not present in table "cyclic_reference_table2_1350636". +CONTEXT: while executing command on localhost:57637 -- proper insertion to table with cyclic dependency BEGIN; INSERT INTO cyclic_reference_table1 VALUES(1, 1); @@ -740,9 +793,9 @@ SELECT create_distributed_table('self_referencing_table1', 'id', 'hash'); INSERT INTO self_referencing_table1 VALUES(1, 1, 1); -- we expect this query to fail INSERT INTO self_referencing_table1 VALUES(1, 2, 3); -ERROR: insert or update on table "self_referencing_table1_1350681" violates foreign key constraint "self_referencing_table1_id_fkey_1350681" -DETAIL: Key (id, other_column_ref)=(1, 3) is not present in table "self_referencing_table1_1350681". -CONTEXT: while executing command on localhost:57638 +ERROR: insert or update on table "self_referencing_table1_1350640" violates foreign key constraint "self_referencing_table1_id_fkey_1350640" +DETAIL: Key (id, other_column_ref)=(1, 3) is not present in table "self_referencing_table1_1350640". +CONTEXT: while executing command on localhost:57637 -- verify that rows are actually inserted SELECT * FROM self_referencing_table1; id | other_column | other_column_ref @@ -765,9 +818,9 @@ ALTER TABLE self_referencing_table2 ADD CONSTRAINT self_referencing_fk_constrain INSERT INTO self_referencing_table2 VALUES(1, 1, 1); -- we expect this query to fail INSERT INTO self_referencing_table2 VALUES(1, 2, 3); -ERROR: insert or update on table "self_referencing_table2_1350713" violates foreign key constraint "self_referencing_fk_constraint_1350713" -DETAIL: Key (id, other_column_ref)=(1, 3) is not present in table "self_referencing_table2_1350713". -CONTEXT: while executing command on localhost:57638 +ERROR: insert or update on table "self_referencing_table2_1350644" violates foreign key constraint "self_referencing_fk_constraint_1350644" +DETAIL: Key (id, other_column_ref)=(1, 3) is not present in table "self_referencing_table2_1350644". +CONTEXT: while executing command on localhost:57637 -- verify that rows are actually inserted SELECT * FROM self_referencing_table2; id | other_column | other_column_ref @@ -788,8 +841,9 @@ SELECT create_distributed_table('referenced_by_reference_table', 'id'); CREATE TABLE reference_table(id int, referencing_column int REFERENCES referenced_by_reference_table(id)); SELECT create_reference_table('reference_table'); -ERROR: cannot create foreign key constraint from or to reference tables --- test foreign key creation on CREATE TABLE to reference table +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table +-- test foreign key creation on CREATE TABLE from + to reference table DROP TABLE reference_table; CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); @@ -798,22 +852,18 @@ SELECT create_reference_table('reference_table'); (1 row) -CREATE TABLE references_to_reference_table(id int, referencing_column int REFERENCES reference_table(id)); -SELECT create_distributed_table('references_to_reference_table', 'referencing_column'); -ERROR: cannot create foreign key constraint from or to reference tables --- test foreign key creation on CREATE TABLE from + to reference table CREATE TABLE reference_table_second(id int, referencing_column int REFERENCES reference_table(id)); SELECT create_reference_table('reference_table_second'); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on CREATE TABLE from reference table to local table CREATE TABLE referenced_local_table(id int PRIMARY KEY, other_column int); DROP TABLE reference_table CASCADE; -NOTICE: drop cascades to 2 other objects -DETAIL: drop cascades to constraint references_to_reference_table_referencing_column_fkey on table references_to_reference_table -drop cascades to constraint reference_table_second_referencing_column_fkey on table reference_table_second +NOTICE: drop cascades to constraint reference_table_second_referencing_column_fkey on table reference_table_second CREATE TABLE reference_table(id int, referencing_column int REFERENCES referenced_local_table(id)); SELECT create_reference_table('reference_table'); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on CREATE TABLE on self referencing reference table CREATE TABLE self_referencing_reference_table( id int, @@ -823,7 +873,8 @@ CREATE TABLE self_referencing_reference_table( FOREIGN KEY(id, other_column_ref) REFERENCES self_referencing_reference_table(id, other_column) ); SELECT create_reference_table('self_referencing_reference_table'); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on ALTER TABLE from reference table DROP TABLE reference_table; CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); @@ -834,9 +885,9 @@ SELECT create_reference_table('reference_table'); (1 row) ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_by_reference_table(id); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on ALTER TABLE to reference table -DROP TABLE references_to_reference_table; CREATE TABLE references_to_reference_table(id int, referencing_column int); SELECT create_distributed_table('references_to_reference_table', 'referencing_column'); create_distributed_table @@ -845,7 +896,6 @@ SELECT create_distributed_table('references_to_reference_table', 'referencing_co (1 row) ALTER TABLE references_to_reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES reference_table(id); -ERROR: cannot create foreign key constraint from or to reference tables -- test foreign key creation on ALTER TABLE from + to reference table DROP TABLE reference_table_second; CREATE TABLE reference_table_second(id int, referencing_column int); @@ -856,9 +906,11 @@ SELECT create_reference_table('reference_table_second'); (1 row) ALTER TABLE reference_table_second ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES reference_table(id); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on ALTER TABLE from reference table to local table -DROP TABLE reference_table; +DROP TABLE reference_table CASCADE; +NOTICE: drop cascades to constraint fk on table references_to_reference_table CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); create_reference_table @@ -883,6 +935,7 @@ SELECT create_reference_table('self_referencing_reference_table'); (1 row) ALTER TABLE self_referencing_reference_table ADD CONSTRAINT fk FOREIGN KEY(id, other_column_ref) REFERENCES self_referencing_reference_table(id, other_column); -ERROR: cannot create foreign key constraint from or to reference tables +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- we no longer need those tables DROP TABLE referenced_by_reference_table, references_to_reference_table, reference_table, reference_table_second, referenced_local_table, self_referencing_reference_table; diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 16bbe4c67..828c15a08 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -164,7 +164,7 @@ test: multi_modifications test: multi_distribution_metadata test: multi_generate_ddl_commands multi_create_shards multi_prune_shard_list multi_repair_shards test: multi_upsert multi_simple_queries multi_create_insert_proxy multi_data_types -test: multi_utilities +test: multi_utilities foreign_key_to_reference_table test: multi_modifying_xacts test: multi_repartition_udt multi_repartitioned_subquery_udf multi_subtransactions test: multi_transaction_recovery diff --git a/src/test/regress/sql/foreign_key_to_reference_table.sql b/src/test/regress/sql/foreign_key_to_reference_table.sql new file mode 100644 index 000000000..323be6889 --- /dev/null +++ b/src/test/regress/sql/foreign_key_to_reference_table.sql @@ -0,0 +1,498 @@ +-- +-- FOREIGN_KEY_TO_REFERENCE_TABLE +-- + +CREATE SCHEMA fkey_reference_table; +SET search_path TO 'fkey_reference_table'; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 8; +SET citus.next_shard_id TO 7000000; + +CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); +SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); + +CREATE VIEW table_fkeys_in_workers AS +SELECT +(json_populate_record(NULL::foreign_details, + json_array_elements_text((run_command_on_workers( $$ + SELECT + COALESCE(json_agg(row_to_json(d)), '[]'::json) + FROM + ( + SELECT + distinct name, + relid::regclass::text, + refd_relid::regclass::text + FROM + table_fkey_cols + ) + d $$ )).RESULT::json )::json )).* ; + +CREATE TABLE referenced_table(id int UNIQUE, test_column int); +SELECT create_reference_table('referenced_table'); + +-- we still do not support update/delete operations through foreign constraints if the foreign key includes the distribution column +-- All should fail +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +DROP TABLE referencing_table; + +-- try with multiple columns including the distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_reference_table('referenced_table'); + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE; +DROP TABLE referencing_table; + +-- all of the above is supported if the foreign key does not include distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +SELECT create_reference_table('referenced_table'); + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +-- foreign keys are only supported when the replication factor = 1 +SET citus.shard_replication_factor TO 2; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; +SET citus.shard_replication_factor TO 1; + +-- foreign keys are supported either in between distributed tables including the +-- distribution column or from distributed tables to reference tables. +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'append'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'range'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +-- test foreign constraint with correct conditions +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(id); + + +-- test inserts +-- test insert to referencing table while there is NO corresponding value in referenced table +INSERT INTO referencing_table VALUES(1, 1); + +-- test insert to referencing while there is corresponding value in referenced table +INSERT INTO referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_table SELECT x, x from generate_series(1,500) as f(x); + + +-- test deletes +-- test delete from referenced table while there is corresponding value in referencing table +DELETE FROM referenced_table WHERE id > 3; + +-- test delete from referenced table while there is NO corresponding value in referencing table +DELETE FROM referenced_table WHERE id = 501; + +-- test cascading truncate +-- will fail for now +TRUNCATE referenced_table CASCADE; +SELECT count(*) FROM referencing_table; + +-- drop table for next tests +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +-- self referencing foreign key on reference tables are not allowed +-- TODO try create_reference_table with already created foreign key. +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_reference_table('referencing_table'); +-- self referencing foreign key +ALTER TABLE referenced_table ADD CONSTRAINT fkey_ref FOREIGN KEY (test_column) REFERENCES referenced_table(id); +-- foreign Keys from reference table to reference table are not allowed +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; + +DROP TABLE referenced_table; +DROP TABLE referencing_table; + +-- cascades on delete with different schemas +CREATE SCHEMA referenced_schema; +CREATE SCHEMA referencing_schema; +CREATE TABLE referenced_schema.referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_schema.referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_schema.referenced_table'); +SELECT create_distributed_table('referencing_schema.referencing_table', 'id'); +ALTER TABLE referencing_schema.referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_schema.referenced_table(id) ON DELETE CASCADE; + +INSERT INTO referenced_schema.referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_schema.referencing_table SELECT x, x from generate_series(1,1000) as f(x); + +DELETE FROM referenced_schema.referenced_table WHERE id > 800; +SELECT count(*) FROM referencing_schema.referencing_table; + +DROP SCHEMA referenced_schema CASCADE; +DROP SCHEMA referencing_schema CASCADE; + +-- on delete set update cascades properly +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT 1); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; + +INSERT INTO referenced_table SELECT x, x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); + +DELETE FROM referenced_table WHERE test_column > 800; +SELECT count(*) FROM referencing_table WHERE ref_id = 1; + +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +-- foreign key as composite key +CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int); +SELECT run_command_on_workers($$CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int)$$) ORDER BY 1; + +CREATE TABLE referenced_table(test_column composite, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, referencing_composite composite); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (referencing_composite) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + +INSERT INTO referenced_table SELECT (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); + +DELETE FROM referenced_table WHERE (test_column).key1 > 900; +SELECT count(*) FROM referencing_table; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + +-- In the following test, we'll use a SERIAL column as the referenced column +-- in the foreign constraint. We'll first show that and insert on non-serial +-- column successfully inserts into the serial and referenced column. +-- Accordingly, the inserts into the referencing table which references to the +-- serial column will be successful. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); + +DELETE FROM referenced_table WHERE test_column2 > 10; +SELECT count(*) FROM referencing_table; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + +-- In the following test, we'll use a SERIAL column as the referencing column +-- in the foreign constraint. We'll first show that the values that exist +-- in the referenced tables are successfully generated by the serial column +-- and inserted to the distributed table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column int PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + +INSERT INTO referenced_table SELECT x,x FROM generate_series(1,1000) AS f(x); +-- Success for existing inserts +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + +-- In the following test, we'll use a SERIAL column as the referencing column +-- and referenced columns in a foreign constraint. We'll first show that the +-- the inserts into referenced column will successfully generate and insert +-- data into serial column. Then, we will be successfully insert the same amount +-- of data into referencing table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +-- Success for existing values +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; +-- In the following test, we use a volatile function in the referencing +-- column in a foreign constraint. We show that if the data exists in the +-- referenced table, we can successfully use volatile functions with +-- foreign constraints. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; + +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x,(random()*1000)::int FROM generate_series(0,1000) AS f(x); + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + +-- In the following test, we show that Citus currently does not support +-- VALIDATE command. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT NOT VALID; + +-- Even if the foreign constraint is added with "NOT VALID", +-- we make sure that it is still applied to the upcoming inserts. +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); +-- we expect this to fail because of the foreign constraint. +INSERT INTO referencing_table SELECT x, x FROM generate_series(1000,1001) AS f(x); + +-- currently not supported +ALTER TABLE referencing_table VALIDATE CONSTRAINT fkey_ref; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + +-- In the following tests, we create a foreign constraint with +-- ON UPDATE CASCADE and see if it works properly with cascading upsert +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE; + +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); + +INSERT INTO referenced_table VALUES (1,2), (2,3), (3,4), (4,5) +ON CONFLICT (test_column) +DO UPDATE + SET test_column = -1 * EXCLUDED.test_column; + +SELECT * FROM referencing_table WHERE ref_id < 0 ORDER BY 1; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; + + +-- Chained references +-- In the following test, we create foreign keys from one column in a distributed +-- table to two reference tables. We expect to see that even if a data exist in +-- one reference table, it is not going to be inserted in to referencing table +-- because of lack of the key in the other table. Data can only be inserted into +-- referencing table if it exists in both referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_reference_table('referenced_table2'); +SELECT create_distributed_table('referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(600,900) AS f(x); + +SELECT count(*) FROM referencing_table; +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referenced_table2 CASCADE; +DROP TABLE referencing_table CASCADE; + + +-- In the following test, we create foreign keys from two columns in a distributed +-- table to two reference tables separately. We expect to see that even if a data +-- exist in one reference table for one column, it is not going to be inserted in +-- to referencing table because the other constraint doesn't hold. Data can only +-- be inserted into referencing table if both columns exist in respective columns +-- in referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_reference_table('referenced_table2'); +SELECT create_distributed_table('referencing_table', 'id'); + +BEGIN; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +-- should success +INSERT INTO referencing_table SELECT x, x+501 FROM generate_series(0,1000) AS f(x); + +SELECT count(*) FROM referencing_table; +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referenced_table2 CASCADE; +DROP TABLE referencing_table CASCADE; + + +-- two distributed tables are referencing to one reference table and +-- in the same time the distributed table 2 is referencing to +-- distributed table 1. Thus, we have a triangular +-- distributed table 1 has a foreign key from the distribution column to reference table +-- distributed table 2 has a foreign key from a non-distribution column to reference table +-- distributed table 2 has a foreign key to distributed table 1 on the distribution column +-- We show that inserts into distributed table 2 will fail if the data does not exist in distributed table 1 +-- Delete from reference table cascades to both of the distributed tables properly +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int); +CREATE TABLE referencing_table2(id int, ref_id int); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +SELECT create_distributed_table('referencing_table2', 'id'); +BEGIN; +SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref_to_dist FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE; +COMMIT; + +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,100) AS f(x); +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(200,500) AS f(x); +-- should success +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,300) AS f(x); + +DELETE FROM referenced_table WHERE test_column < 200; +SELECT count(*) FROM referencing_table; +SELECT count(*) FROM referencing_table2; +DELETE FROM referencing_table WHERE id > 200; +SELECT count(*) FROM referencing_table2; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; +DROP TABLE referencing_table2 CASCADE; + + +-- In this test we have a chained relationship in form of +-- distributed table (referencing_referencing_table) has a foreign key with two columns +-- to another distributed table (referencing_table) +-- referencing_table has another foreign key with 2 columns to referenced_table. +-- We will show that a cascading delete on referenced_table reaches to referencing_referencing_table. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column, test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, ref_id2 int, PRIMARY KEY(id, ref_id)); +CREATE TABLE referencing_referencing_table(id int, ref_id int, FOREIGN KEY (id, ref_id) REFERENCES referencing_table(id, ref_id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); +SELECT create_distributed_table('referencing_referencing_table', 'id'); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id, ref_id2) REFERENCES referenced_table(test_column, test_column2) ON DELETE CASCADE; + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.referencing%' ORDER BY 1,2,3; + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x+1, x+2 FROM generate_series(1,999) AS f(x); +INSERT INTO referencing_referencing_table SELECT x, x+1 FROM generate_series(1,999) AS f(x); + +DELETE FROM referenced_table WHERE test_column > 800; +SELECT max(ref_id) FROM referencing_referencing_table; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; +DROP TABLE referencing_referencing_table; + +DROP SCHEMA fkey_reference_table CASCADE; +SET search_path TO DEFAULT; diff --git a/src/test/regress/sql/multi_foreign_key.sql b/src/test/regress/sql/multi_foreign_key.sql index 8b5ce89ad..82ff4a57e 100644 --- a/src/test/regress/sql/multi_foreign_key.sql +++ b/src/test/regress/sql/multi_foreign_key.sql @@ -32,6 +32,16 @@ CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCE SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); DROP TABLE referencing_table; +-- self referencing table with replication factor > 1 +CREATE TABLE self_referencing_table(id int, ref_id int, PRIMARY KEY (id, ref_id), FOREIGN KEY(id,ref_id) REFERENCES self_referencing_table(id, ref_id)); +SELECT create_distributed_table('self_referencing_table', 'id', 'hash'); +DROP TABLE self_referencing_table; + +CREATE TABLE self_referencing_table(id int, ref_id int, PRIMARY KEY (id, ref_id)); +SELECT create_distributed_table('self_referencing_table', 'id', 'hash'); +ALTER TABLE self_referencing_table ADD CONSTRAINT fkey FOREIGN KEY(id,ref_id) REFERENCES self_referencing_table(id, ref_id); +DROP TABLE self_referencing_table; + -- test foreign constraint creation on NOT co-located tables SET citus.shard_count TO 8; CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id)); @@ -55,9 +65,27 @@ SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); DROP TABLE referencing_table; DROP TABLE referenced_table; --- test foreign constraint with correct conditions +-- test foreign constraint creation on append and range distributed tables +-- foreign keys are supported either in between distributed tables including the +-- distribution column or from distributed tables to reference tables. SET citus.shard_replication_factor TO 1; CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'hash'); + +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'id', 'append'); +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'range'); +CREATE TABLE referencing_table(id int, ref_id int,FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'id', 'range'); +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +-- test foreign constraint with correct conditions +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id)); SELECT create_distributed_table('referenced_table', 'id', 'hash'); SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); @@ -211,13 +239,12 @@ DROP TABLE referenced_table; -- Similar tests, but this time we push foreign key constraints created by ALTER TABLE queries -- create tables +SET citus.shard_count TO 4; CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); -SELECT master_create_distributed_table('referenced_table', 'id', 'hash'); -SELECT master_create_worker_shards('referenced_table', 4, 1); +SELECT create_distributed_table('referenced_table', 'id', 'hash'); CREATE TABLE referencing_table(id int, ref_id int); -SELECT master_create_distributed_table('referencing_table', 'ref_id', 'hash'); -SELECT master_create_worker_shards('referencing_table', 4, 1); +SELECT create_distributed_table('referencing_table', 'ref_id', 'hash'); -- verify that we skip foreign key validation when propagation is turned off -- not skipping validation would result in a distributed query, which emits debug messages @@ -243,6 +270,13 @@ ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) ALTER TABLE referencing_table ADD FOREIGN KEY(ref_id) REFERENCES referenced_table(id); -- test foreign constraint creation on NOT co-located tables +DROP TABLE referencing_table; +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_distributed_table('referenced_table', 'id', 'hash'); + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'hash', colocate_with => 'none'); ALTER TABLE referencing_table ADD CONSTRAINT test_constraint FOREIGN KEY(ref_id) REFERENCES referenced_table(id); -- create co-located tables @@ -468,15 +502,11 @@ SELECT create_distributed_table('referenced_by_reference_table', 'id'); CREATE TABLE reference_table(id int, referencing_column int REFERENCES referenced_by_reference_table(id)); SELECT create_reference_table('reference_table'); --- test foreign key creation on CREATE TABLE to reference table +-- test foreign key creation on CREATE TABLE from + to reference table DROP TABLE reference_table; CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); -CREATE TABLE references_to_reference_table(id int, referencing_column int REFERENCES reference_table(id)); -SELECT create_distributed_table('references_to_reference_table', 'referencing_column'); - --- test foreign key creation on CREATE TABLE from + to reference table CREATE TABLE reference_table_second(id int, referencing_column int REFERENCES reference_table(id)); SELECT create_reference_table('reference_table_second'); @@ -503,7 +533,6 @@ SELECT create_reference_table('reference_table'); ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_by_reference_table(id); -- test foreign key creation on ALTER TABLE to reference table -DROP TABLE references_to_reference_table; CREATE TABLE references_to_reference_table(id int, referencing_column int); SELECT create_distributed_table('references_to_reference_table', 'referencing_column'); ALTER TABLE references_to_reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES reference_table(id); @@ -515,7 +544,7 @@ SELECT create_reference_table('reference_table_second'); ALTER TABLE reference_table_second ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES reference_table(id); -- test foreign key creation on ALTER TABLE from reference table to local table -DROP TABLE reference_table; +DROP TABLE reference_table CASCADE; CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); SELECT create_reference_table('reference_table'); ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_local_table(id); From 45f8017f4287a4af6741f076e95302958066861a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Tue, 19 Jun 2018 16:29:36 +0300 Subject: [PATCH 02/10] create_distributed_table with fk to ref table is implemented --- src/backend/distributed/master/master_stage_protocol.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/backend/distributed/master/master_stage_protocol.c b/src/backend/distributed/master/master_stage_protocol.c index e904d76a5..5b3239d62 100644 --- a/src/backend/distributed/master/master_stage_protocol.c +++ b/src/backend/distributed/master/master_stage_protocol.c @@ -668,6 +668,13 @@ WorkerCreateShard(Oid relationId, int shardIndex, uint64 shardId, List *ddlComma { referencedShardId = shardId; } + else if (PartitionMethod(referencedRelationId) == DISTRIBUTE_BY_NONE) + { + List *shardList = LoadShardList(referencedRelationId); + uint64 *shardIdPointer = (uint64 *) linitial(shardList); + + referencedShardId = (*shardIdPointer); + } else { referencedShardId = ColocatedShardIdInRelation(referencedRelationId, From 2c5d59f3a8ca147d4566d8bde42280ebf9c01d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Tue, 19 Jun 2018 18:40:35 +0300 Subject: [PATCH 03/10] create_distributed_table in transaction is fixed --- .../commands/create_distributed_table.c | 61 +++++++++++++------ .../distributed/ddl/foreign_constraint.c | 56 +++++++++++++++++ .../master/master_stage_protocol.c | 5 +- src/include/distributed/foreign_constraint.h | 1 + 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index 4a4d7de93..b557cbdd4 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -100,6 +100,7 @@ static bool LocalTableEmpty(Oid tableId); static void CopyLocalDataIntoShards(Oid relationId); static List * TupleDescColumnNameList(TupleDesc tupleDescriptor); static bool RelationUsesIdentityColumns(TupleDesc relationDesc); +static bool CanUseExclusiveConnections(Oid relationId, bool localTableEmpty); /* exports for SQL callable functions */ PG_FUNCTION_INFO_V1(master_create_distributed_table); @@ -484,26 +485,8 @@ CreateHashDistributedTableShards(Oid relationId, Oid colocatedTableId, */ if (RegularTable(relationId)) { - if (!localTableEmpty && MultiShardConnectionType == SEQUENTIAL_CONNECTION) - { - char *relationName = get_rel_name(relationId); - - ereport(ERROR, (errmsg("cannot distribute \"%s\" in sequential mode " - "because it is not empty", relationName), - errhint("If you have manually set " - "citus.multi_shard_modify_mode to 'sequential', " - "try with 'parallel' option. If that is not the " - "case, try distributing local tables when they " - "are empty."))); - } - else if (MultiShardConnectionType == SEQUENTIAL_CONNECTION) - { - useExclusiveConnection = false; - } - else if (!localTableEmpty || IsTransactionBlock()) - { - useExclusiveConnection = true; - } + useExclusiveConnection = CanUseExclusiveConnections(relationId, + localTableEmpty); } if (colocatedTableId != InvalidOid) @@ -1107,6 +1090,44 @@ LocalTableEmpty(Oid tableId) } +/* + * CanUseExclusiveConnections checks if we can open parallel connections + * while creating shards. We simply error out if we need to execute + * sequentially but there is data in the table, since we cannot copy the + * data to shards sequentially. + */ +static bool +CanUseExclusiveConnections(Oid relationId, bool localTableEmpty) +{ + bool hasForeignKeyToReferenceTable = HasForeignKeyToReferenceTable(relationId); + bool shouldRunSequential = MultiShardConnectionType == SEQUENTIAL_CONNECTION || + hasForeignKeyToReferenceTable; + + if (!localTableEmpty && shouldRunSequential) + { + char *relationName = get_rel_name(relationId); + + ereport(ERROR, (errmsg("cannot distribute \"%s\" in sequential mode " + "because it is not empty", relationName), + errhint("If you have manually set " + "citus.multi_shard_modify_mode to 'sequential', " + "try with 'parallel' option. If that is not the " + "case, try distributing local tables when they " + "are empty."))); + } + else if (shouldRunSequential) + { + return false; + } + else if (!localTableEmpty || IsTransactionBlock()) + { + return true; + } + + return false; +} + + /* * CreateTruncateTrigger creates a truncate trigger on table identified by relationId * and assigns citus_truncate_trigger() as handler. diff --git a/src/backend/distributed/ddl/foreign_constraint.c b/src/backend/distributed/ddl/foreign_constraint.c index aa9248d22..84caa3b76 100644 --- a/src/backend/distributed/ddl/foreign_constraint.c +++ b/src/backend/distributed/ddl/foreign_constraint.c @@ -370,6 +370,62 @@ GetTableForeignConstraintCommands(Oid relationId) } +/* + * HasForeignKeyToReferenceTable function scans the pgConstraint table to + * fetch all of the constraints on the given relationId and see if at least one + * of them is a foreign key referencing to a reference table. + */ +bool +HasForeignKeyToReferenceTable(Oid relationId) +{ + Relation pgConstraint = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + HeapTuple heapTuple = NULL; + bool hasForeignKeyToReferenceTable = false; + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, + relationId); + scanDescriptor = systable_beginscan(pgConstraint, ConstraintRelidIndexId, true, NULL, + scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Oid referencedTableId = InvalidOid; + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); + + if (constraintForm->contype != CONSTRAINT_FOREIGN) + { + heapTuple = systable_getnext(scanDescriptor); + continue; + } + + referencedTableId = constraintForm->confrelid; + + if (!IsDistributedTable(referencedTableId)) + { + continue; + } + + if (PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE) + { + hasForeignKeyToReferenceTable = true; + break; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + /* clean up scan and close system catalog */ + systable_endscan(scanDescriptor); + heap_close(pgConstraint, NoLock); + return hasForeignKeyToReferenceTable; +} + + /* * TableReferenced function checks whether given table is referenced by another table * via foreign constraints. If it is referenced, this function returns true. To check diff --git a/src/backend/distributed/master/master_stage_protocol.c b/src/backend/distributed/master/master_stage_protocol.c index 5b3239d62..6129931cb 100644 --- a/src/backend/distributed/master/master_stage_protocol.c +++ b/src/backend/distributed/master/master_stage_protocol.c @@ -662,7 +662,10 @@ WorkerCreateShard(Oid relationId, int shardIndex, uint64 shardId, List *ddlComma * In case of self referencing shards, relation itself might not be distributed * already. Therefore we cannot use ColocatedShardIdInRelation which assumes * given relation is distributed. Besides, since we know foreign key references - * itself, referencedShardId is actual shardId anyway. + * itself, referencedShardId is actual shardId anyway. Also, if the referenced + * relation is a reference table, we cannot use ColocatedShardIdInRelation since + * reference tables only have one shard. Instead, we fetch the one and only shard + * from shardlist and use it. */ if (relationId == referencedRelationId) { diff --git a/src/include/distributed/foreign_constraint.h b/src/include/distributed/foreign_constraint.h index 5261f46c6..b85a6891e 100644 --- a/src/include/distributed/foreign_constraint.h +++ b/src/include/distributed/foreign_constraint.h @@ -19,6 +19,7 @@ extern void ErrorIfUnsupportedForeignConstraint(Relation relation, char Var *distributionColumn, uint32 colocationId); extern List * GetTableForeignConstraintCommands(Oid relationId); +extern bool HasForeignKeyToReferenceTable(Oid relationId); extern bool TableReferenced(Oid relationId); #endif From e37f76c276aa634c1f081f9abeb073b959b53b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Wed, 20 Jun 2018 13:01:31 +0300 Subject: [PATCH 04/10] tests are added --- .../foreign_key_to_reference_table.out | 634 ++++++++++++++---- .../sql/foreign_key_to_reference_table.sql | 192 +++++- 2 files changed, 693 insertions(+), 133 deletions(-) diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out index 72e593647..b9f9e3de7 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table.out +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -6,6 +6,7 @@ SET search_path TO 'fkey_reference_table'; SET citus.shard_replication_factor TO 1; SET citus.shard_count TO 8; SET citus.next_shard_id TO 7000000; +SET citus.next_placement_id TO 7000000; CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); run_command_on_workers @@ -50,6 +51,11 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERE ERROR: cannot create foreign key constraint DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); create_distributed_table @@ -61,6 +67,11 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERE ERROR: cannot create foreign key constraint DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); create_distributed_table @@ -72,6 +83,12 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERE ERROR: cannot create foreign key constraint DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +ROLLBACK; -- try with multiple columns including the distribution column DROP TABLE referenced_table; CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id, test_column)); @@ -92,6 +109,11 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) RE ERROR: cannot create foreign key constraint DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); create_distributed_table @@ -103,6 +125,12 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) RE ERROR: cannot create foreign key constraint DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +ROLLBACK; -- all of the above is supported if the foreign key does not include distribution column DROP TABLE referenced_table; CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); @@ -134,25 +162,24 @@ SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' A (8 rows) DROP TABLE referencing_table; -CREATE TABLE referencing_table(id int, ref_id int); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL); SELECT create_distributed_table('referencing_table', 'ref_id'); create_distributed_table -------------------------- (1 row) -ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; - name | relid | refd_relid -------------------+------------------------------------------------+----------------------------------------------- - fkey_ref_7000051 | fkey_reference_table.referencing_table_7000051 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000052 | fkey_reference_table.referencing_table_7000052 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000053 | fkey_reference_table.referencing_table_7000053 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000054 | fkey_reference_table.referencing_table_7000054 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000055 | fkey_reference_table.referencing_table_7000055 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000056 | fkey_reference_table.referencing_table_7000056 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000057 | fkey_reference_table.referencing_table_7000057 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000058 | fkey_reference_table.referencing_table_7000058 | fkey_reference_table.referenced_table_7000042 + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000051 | fkey_reference_table.referencing_table_7000051 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000052 | fkey_reference_table.referencing_table_7000052 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000053 | fkey_reference_table.referencing_table_7000053 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000054 | fkey_reference_table.referencing_table_7000054 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000055 | fkey_reference_table.referencing_table_7000055 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000056 | fkey_reference_table.referencing_table_7000056 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000057 | fkey_reference_table.referencing_table_7000057 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000058 | fkey_reference_table.referencing_table_7000058 | fkey_reference_table.referenced_table_7000042 (8 rows) DROP TABLE referencing_table; @@ -163,7 +190,7 @@ SELECT create_distributed_table('referencing_table', 'ref_id'); (1 row) -ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid ------------------+------------------------------------------------+----------------------------------------------- @@ -177,6 +204,51 @@ SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' A fkey_ref_7000066 | fkey_reference_table.referencing_table_7000066 | fkey_reference_table.referenced_table_7000042 (8 rows) +DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); + SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000067 | fkey_reference_table.referencing_table_7000067 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000068 | fkey_reference_table.referencing_table_7000068 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000069 | fkey_reference_table.referencing_table_7000069 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000070 | fkey_reference_table.referencing_table_7000070 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000071 | fkey_reference_table.referencing_table_7000071 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000072 | fkey_reference_table.referencing_table_7000072 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000073 | fkey_reference_table.referencing_table_7000073 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000074 | fkey_reference_table.referencing_table_7000074 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000075 | fkey_reference_table.referencing_table_7000075 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000076 | fkey_reference_table.referencing_table_7000076 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000077 | fkey_reference_table.referencing_table_7000077 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000078 | fkey_reference_table.referencing_table_7000078 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000079 | fkey_reference_table.referencing_table_7000079 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000080 | fkey_reference_table.referencing_table_7000080 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000081 | fkey_reference_table.referencing_table_7000081 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000082 | fkey_reference_table.referencing_table_7000082 | fkey_reference_table.referenced_table_7000042 +(8 rows) + DROP TABLE referencing_table; CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); @@ -189,14 +261,14 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid ------------------+------------------------------------------------+----------------------------------------------- - fkey_ref_7000067 | fkey_reference_table.referencing_table_7000067 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000068 | fkey_reference_table.referencing_table_7000068 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000069 | fkey_reference_table.referencing_table_7000069 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000070 | fkey_reference_table.referencing_table_7000070 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000071 | fkey_reference_table.referencing_table_7000071 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000072 | fkey_reference_table.referencing_table_7000072 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000073 | fkey_reference_table.referencing_table_7000073 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000074 | fkey_reference_table.referencing_table_7000074 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000083 | fkey_reference_table.referencing_table_7000083 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000084 | fkey_reference_table.referencing_table_7000084 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000085 | fkey_reference_table.referencing_table_7000085 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000086 | fkey_reference_table.referencing_table_7000086 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000087 | fkey_reference_table.referencing_table_7000087 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000088 | fkey_reference_table.referencing_table_7000088 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000089 | fkey_reference_table.referencing_table_7000089 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000090 | fkey_reference_table.referencing_table_7000090 | fkey_reference_table.referenced_table_7000042 (8 rows) DROP TABLE referencing_table; @@ -211,14 +283,14 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid ------------------+------------------------------------------------+----------------------------------------------- - fkey_ref_7000075 | fkey_reference_table.referencing_table_7000075 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000076 | fkey_reference_table.referencing_table_7000076 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000077 | fkey_reference_table.referencing_table_7000077 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000078 | fkey_reference_table.referencing_table_7000078 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000079 | fkey_reference_table.referencing_table_7000079 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000080 | fkey_reference_table.referencing_table_7000080 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000081 | fkey_reference_table.referencing_table_7000081 | fkey_reference_table.referenced_table_7000042 - fkey_ref_7000082 | fkey_reference_table.referencing_table_7000082 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000091 | fkey_reference_table.referencing_table_7000091 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000092 | fkey_reference_table.referencing_table_7000092 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000093 | fkey_reference_table.referencing_table_7000093 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000094 | fkey_reference_table.referencing_table_7000094 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000095 | fkey_reference_table.referencing_table_7000095 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000096 | fkey_reference_table.referencing_table_7000096 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000097 | fkey_reference_table.referencing_table_7000097 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000098 | fkey_reference_table.referencing_table_7000098 | fkey_reference_table.referenced_table_7000042 (8 rows) DROP TABLE referencing_table; @@ -242,6 +314,59 @@ SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' A DROP TABLE referencing_table; SET citus.shard_replication_factor TO 1; +-- simple create_distributed_table should work in/out transactions on tables with foreign key to reference tables +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000107 | fkey_reference_table.referencing_table_7000107 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000108 | fkey_reference_table.referencing_table_7000108 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000109 | fkey_reference_table.referencing_table_7000109 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000110 | fkey_reference_table.referencing_table_7000110 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000111 | fkey_reference_table.referencing_table_7000111 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000112 | fkey_reference_table.referencing_table_7000112 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000113 | fkey_reference_table.referencing_table_7000113 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000114 | fkey_reference_table.referencing_table_7000114 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +BEGIN; + CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); + SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); + SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000116 | fkey_reference_table.referencing_table_7000116 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000117 | fkey_reference_table.referencing_table_7000117 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000118 | fkey_reference_table.referencing_table_7000118 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000119 | fkey_reference_table.referencing_table_7000119 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000120 | fkey_reference_table.referencing_table_7000120 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000121 | fkey_reference_table.referencing_table_7000121 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000122 | fkey_reference_table.referencing_table_7000122 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000123 | fkey_reference_table.referencing_table_7000123 | fkey_reference_table.referenced_table_7000115 +(8 rows) + +DROP TABLE referencing_table; -- foreign keys are supported either in between distributed tables including the -- distribution column or from distributed tables to reference tables. CREATE TABLE referencing_table(id int, ref_id int); @@ -296,8 +421,8 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFER -- test inserts -- test insert to referencing table while there is NO corresponding value in referenced table INSERT INTO referencing_table VALUES(1, 1); -ERROR: insert or update on table "referencing_table_7000092" violates foreign key constraint "fkey_ref_7000092" -DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_7000091". +ERROR: insert or update on table "referencing_table_7000125" violates foreign key constraint "fkey_ref_7000125" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_7000124". CONTEXT: while executing command on localhost:57637 -- test insert to referencing while there is corresponding value in referenced table INSERT INTO referenced_table SELECT x, x from generate_series(1,1000) as f(x); @@ -305,8 +430,8 @@ INSERT INTO referencing_table SELECT x, x from generate_series(1,500) as f(x); -- test deletes -- test delete from referenced table while there is corresponding value in referencing table DELETE FROM referenced_table WHERE id > 3; -ERROR: update or delete on table "referenced_table_7000091" violates foreign key constraint "fkey_ref_7000094" on table "referencing_table_7000094" -DETAIL: Key (id)=(4) is still referenced from table "referencing_table_7000094". +ERROR: update or delete on table "referenced_table_7000124" violates foreign key constraint "fkey_ref_7000127" on table "referencing_table_7000127" +DETAIL: Key (id)=(4) is still referenced from table "referencing_table_7000127". CONTEXT: while executing command on localhost:57637 -- test delete from referenced table while there is NO corresponding value in referencing table DELETE FROM referenced_table WHERE id = 501; @@ -503,8 +628,8 @@ INSERT INTO referenced_table SELECT x,x FROM generate_series(1,1000) AS f(x); INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); -- Fails for non existing value inserts (serial is already incremented) INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); -ERROR: insert or update on table "referencing_table_7000144" violates foreign key constraint "fkey_ref_7000144" -DETAIL: Key (ref_id)=(1006) is not present in table "referenced_table_7000138". +ERROR: insert or update on table "referencing_table_7000172" violates foreign key constraint "fkey_ref_7000172" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000171". DROP TABLE referenced_table CASCADE; NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referencing_table CASCADE; @@ -534,8 +659,8 @@ INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); -- Fails for non existing value inserts (serial is already incremented) INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); -ERROR: insert or update on table "referencing_table_7000151" violates foreign key constraint "fkey_ref_7000151" -DETAIL: Key (ref_id)=(1003) is not present in table "referenced_table_7000147". +ERROR: insert or update on table "referencing_table_7000181" violates foreign key constraint "fkey_ref_7000181" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000180". DROP TABLE referenced_table CASCADE; NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referencing_table CASCADE; @@ -586,8 +711,8 @@ INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); -- we expect this to fail because of the foreign constraint. INSERT INTO referencing_table SELECT x, x FROM generate_series(1000,1001) AS f(x); -ERROR: insert or update on table "referencing_table_7000171" violates foreign key constraint "fkey_ref_7000171" -DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000165". +ERROR: insert or update on table "referencing_table_7000204" violates foreign key constraint "fkey_ref_7000204" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000198". -- currently not supported ALTER TABLE referencing_table VALIDATE CONSTRAINT fkey_ref; ERROR: alter table command is currently unsupported @@ -630,6 +755,29 @@ SELECT * FROM referencing_table WHERE ref_id < 0 ORDER BY 1; DROP TABLE referenced_table CASCADE; NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referencing_table CASCADE; +-- create_distributed_table should fail for tables with data if fkey exists to reference table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE); +INSERT INTO referenced_table VALUES (1,1), (2,2), (3,3); +INSERT INTO referencing_table VALUES (1,1), (2,2), (3,3); +SELECT create_reference_table('referenced_table'); +NOTICE: Copying data from local table... + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); +ERROR: cannot distribute "referencing_table" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. +BEGIN; + SELECT create_distributed_table('referencing_table', 'id'); +ERROR: cannot distribute "referencing_table" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. +COMMIT; +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_ref_id_fkey on table referencing_table +DROP TABLE referencing_table CASCADE; -- Chained references -- In the following test, we create foreign keys from one column in a distributed -- table to two reference tables. We expect to see that even if a data exist in @@ -663,38 +811,38 @@ ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFE SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid -----------------------+------------------------------------------------+------------------------------------------------ - fkey_ref_7000185 | fkey_reference_table.referencing_table_7000185 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000186 | fkey_reference_table.referencing_table_7000186 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000187 | fkey_reference_table.referencing_table_7000187 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000188 | fkey_reference_table.referencing_table_7000188 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000189 | fkey_reference_table.referencing_table_7000189 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000190 | fkey_reference_table.referencing_table_7000190 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000191 | fkey_reference_table.referencing_table_7000191 | fkey_reference_table.referenced_table_7000183 - fkey_ref_7000192 | fkey_reference_table.referencing_table_7000192 | fkey_reference_table.referenced_table_7000183 - foreign_key_2_7000185 | fkey_reference_table.referencing_table_7000185 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000186 | fkey_reference_table.referencing_table_7000186 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000187 | fkey_reference_table.referencing_table_7000187 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000188 | fkey_reference_table.referencing_table_7000188 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000189 | fkey_reference_table.referencing_table_7000189 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000190 | fkey_reference_table.referencing_table_7000190 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000191 | fkey_reference_table.referencing_table_7000191 | fkey_reference_table.referenced_table2_7000184 - foreign_key_2_7000192 | fkey_reference_table.referencing_table_7000192 | fkey_reference_table.referenced_table2_7000184 + fkey_ref_7000219 | fkey_reference_table.referencing_table_7000219 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000220 | fkey_reference_table.referencing_table_7000220 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table_7000217 + foreign_key_2_7000219 | fkey_reference_table.referencing_table_7000219 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000220 | fkey_reference_table.referencing_table_7000220 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table2_7000218 (16 rows) INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); -ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "foreign_key_2_7000192" -DETAIL: Key (id)=(9) is not present in table "referenced_table2_7000184". +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "foreign_key_2_7000220" +DETAIL: Key (id)=(5) is not present in table "referenced_table2_7000218". -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); -ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "foreign_key_2_7000192" -DETAIL: Key (id)=(9) is not present in table "referenced_table2_7000184". +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "foreign_key_2_7000220" +DETAIL: Key (id)=(5) is not present in table "referenced_table2_7000218". -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); -ERROR: insert or update on table "referencing_table_7000192" violates foreign key constraint "fkey_ref_7000192" -DETAIL: Key (id)=(1023) is not present in table "referenced_table_7000183". +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "fkey_ref_7000220" +DETAIL: Key (id)=(1005) is not present in table "referenced_table_7000217". -- should success INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(600,900) AS f(x); SELECT count(*) FROM referencing_table; @@ -722,6 +870,39 @@ NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referenced_table2 CASCADE; NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 16 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey1 on table referencing_table +DROP TABLE referencing_table CASCADE; -- In the following test, we create foreign keys from two columns in a distributed -- table to two reference tables separately. We expect to see that even if a data -- exist in one reference table for one column, it is not going to be inserted in @@ -751,44 +932,44 @@ SELECT create_distributed_table('referencing_table', 'id'); (1 row) BEGIN; -ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; -ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; + ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; COMMIT; SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid -----------------------+------------------------------------------------+------------------------------------------------ - fkey_ref_7000195 | fkey_reference_table.referencing_table_7000195 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000196 | fkey_reference_table.referencing_table_7000196 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000197 | fkey_reference_table.referencing_table_7000197 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000198 | fkey_reference_table.referencing_table_7000198 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000199 | fkey_reference_table.referencing_table_7000199 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000200 | fkey_reference_table.referencing_table_7000200 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000201 | fkey_reference_table.referencing_table_7000201 | fkey_reference_table.referenced_table_7000193 - fkey_ref_7000202 | fkey_reference_table.referencing_table_7000202 | fkey_reference_table.referenced_table_7000193 - foreign_key_2_7000195 | fkey_reference_table.referencing_table_7000195 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000196 | fkey_reference_table.referencing_table_7000196 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000197 | fkey_reference_table.referencing_table_7000197 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000198 | fkey_reference_table.referencing_table_7000198 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000199 | fkey_reference_table.referencing_table_7000199 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000200 | fkey_reference_table.referencing_table_7000200 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000201 | fkey_reference_table.referencing_table_7000201 | fkey_reference_table.referenced_table2_7000194 - foreign_key_2_7000202 | fkey_reference_table.referencing_table_7000202 | fkey_reference_table.referenced_table2_7000194 + fkey_ref_7000239 | fkey_reference_table.referencing_table_7000239 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000240 | fkey_reference_table.referencing_table_7000240 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000241 | fkey_reference_table.referencing_table_7000241 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000242 | fkey_reference_table.referencing_table_7000242 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000243 | fkey_reference_table.referencing_table_7000243 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000244 | fkey_reference_table.referencing_table_7000244 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000245 | fkey_reference_table.referencing_table_7000245 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000246 | fkey_reference_table.referencing_table_7000246 | fkey_reference_table.referenced_table_7000237 + foreign_key_2_7000239 | fkey_reference_table.referencing_table_7000239 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000240 | fkey_reference_table.referencing_table_7000240 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000241 | fkey_reference_table.referencing_table_7000241 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000242 | fkey_reference_table.referencing_table_7000242 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000243 | fkey_reference_table.referencing_table_7000243 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000244 | fkey_reference_table.referencing_table_7000244 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000245 | fkey_reference_table.referencing_table_7000245 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000246 | fkey_reference_table.referencing_table_7000246 | fkey_reference_table.referenced_table2_7000238 (16 rows) INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); -ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "foreign_key_2_7000197" -DETAIL: Key (ref_id)=(5) is not present in table "referenced_table2_7000194". +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "foreign_key_2_7000245" +DETAIL: Key (ref_id)=(3) is not present in table "referenced_table2_7000238". -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); -ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "foreign_key_2_7000197" -DETAIL: Key (ref_id)=(5) is not present in table "referenced_table2_7000194". +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "foreign_key_2_7000245" +DETAIL: Key (ref_id)=(3) is not present in table "referenced_table2_7000238". -- should fail INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); -ERROR: insert or update on table "referencing_table_7000197" violates foreign key constraint "fkey_ref_7000197" -DETAIL: Key (id)=(1015) is not present in table "referenced_table_7000193". +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "fkey_ref_7000245" +DETAIL: Key (id)=(1002) is not present in table "referenced_table_7000237". -- should success INSERT INTO referencing_table SELECT x, x+501 FROM generate_series(0,1000) AS f(x); SELECT count(*) FROM referencing_table; @@ -816,6 +997,42 @@ NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referenced_table2 CASCADE; NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created when create_distributed_table is used for 1 foreign key and alter table for the other +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); +BEGIN; + SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 16 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; -- two distributed tables are referencing to one reference table and -- in the same time the distributed table 2 is referencing to -- distributed table 1. Thus, we have a triangular @@ -851,46 +1068,46 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCE ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE; ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref_to_dist FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE; COMMIT; -SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; name | relid | refd_relid --------------------------+-------------------------------------------------+------------------------------------------------ - fkey_ref_7000204 | fkey_reference_table.referencing_table_7000204 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000205 | fkey_reference_table.referencing_table_7000205 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000206 | fkey_reference_table.referencing_table_7000206 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000207 | fkey_reference_table.referencing_table_7000207 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000208 | fkey_reference_table.referencing_table_7000208 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000209 | fkey_reference_table.referencing_table_7000209 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000210 | fkey_reference_table.referencing_table_7000210 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000211 | fkey_reference_table.referencing_table_7000211 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000212 | fkey_reference_table.referencing_table2_7000212 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000213 | fkey_reference_table.referencing_table2_7000213 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000214 | fkey_reference_table.referencing_table2_7000214 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000215 | fkey_reference_table.referencing_table2_7000215 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000216 | fkey_reference_table.referencing_table2_7000216 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000217 | fkey_reference_table.referencing_table2_7000217 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000218 | fkey_reference_table.referencing_table2_7000218 | fkey_reference_table.referenced_table_7000203 - fkey_ref_7000219 | fkey_reference_table.referencing_table2_7000219 | fkey_reference_table.referenced_table_7000203 - fkey_ref_to_dist_7000212 | fkey_reference_table.referencing_table2_7000212 | fkey_reference_table.referencing_table_7000204 - fkey_ref_to_dist_7000213 | fkey_reference_table.referencing_table2_7000213 | fkey_reference_table.referencing_table_7000205 - fkey_ref_to_dist_7000214 | fkey_reference_table.referencing_table2_7000214 | fkey_reference_table.referencing_table_7000206 - fkey_ref_to_dist_7000215 | fkey_reference_table.referencing_table2_7000215 | fkey_reference_table.referencing_table_7000207 - fkey_ref_to_dist_7000216 | fkey_reference_table.referencing_table2_7000216 | fkey_reference_table.referencing_table_7000208 - fkey_ref_to_dist_7000217 | fkey_reference_table.referencing_table2_7000217 | fkey_reference_table.referencing_table_7000209 - fkey_ref_to_dist_7000218 | fkey_reference_table.referencing_table2_7000218 | fkey_reference_table.referencing_table_7000210 - fkey_ref_to_dist_7000219 | fkey_reference_table.referencing_table2_7000219 | fkey_reference_table.referencing_table_7000211 + fkey_ref_7000258 | fkey_reference_table.referencing_table_7000258 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000259 | fkey_reference_table.referencing_table_7000259 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000260 | fkey_reference_table.referencing_table_7000260 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000261 | fkey_reference_table.referencing_table_7000261 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000262 | fkey_reference_table.referencing_table_7000262 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000263 | fkey_reference_table.referencing_table_7000263 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000264 | fkey_reference_table.referencing_table_7000264 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000265 | fkey_reference_table.referencing_table_7000265 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000266 | fkey_reference_table.referencing_table2_7000266 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000267 | fkey_reference_table.referencing_table2_7000267 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000268 | fkey_reference_table.referencing_table2_7000268 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000269 | fkey_reference_table.referencing_table2_7000269 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000270 | fkey_reference_table.referencing_table2_7000270 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000271 | fkey_reference_table.referencing_table2_7000271 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000272 | fkey_reference_table.referencing_table2_7000272 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000273 | fkey_reference_table.referencing_table2_7000273 | fkey_reference_table.referenced_table_7000257 + fkey_ref_to_dist_7000266 | fkey_reference_table.referencing_table2_7000266 | fkey_reference_table.referencing_table_7000258 + fkey_ref_to_dist_7000267 | fkey_reference_table.referencing_table2_7000267 | fkey_reference_table.referencing_table_7000259 + fkey_ref_to_dist_7000268 | fkey_reference_table.referencing_table2_7000268 | fkey_reference_table.referencing_table_7000260 + fkey_ref_to_dist_7000269 | fkey_reference_table.referencing_table2_7000269 | fkey_reference_table.referencing_table_7000261 + fkey_ref_to_dist_7000270 | fkey_reference_table.referencing_table2_7000270 | fkey_reference_table.referencing_table_7000262 + fkey_ref_to_dist_7000271 | fkey_reference_table.referencing_table2_7000271 | fkey_reference_table.referencing_table_7000263 + fkey_ref_to_dist_7000272 | fkey_reference_table.referencing_table2_7000272 | fkey_reference_table.referencing_table_7000264 + fkey_ref_to_dist_7000273 | fkey_reference_table.referencing_table2_7000273 | fkey_reference_table.referencing_table_7000265 (24 rows) INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); -- should fail INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,100) AS f(x); -ERROR: insert or update on table "referencing_table2_7000215" violates foreign key constraint "fkey_ref_to_dist_7000215" -DETAIL: Key (id)=(0) is not present in table "referencing_table_7000207". +ERROR: insert or update on table "referencing_table2_7000272" violates foreign key constraint "fkey_ref_to_dist_7000272" +DETAIL: Key (id)=(2) is not present in table "referencing_table_7000264". -- should success INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); -- should fail INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(200,500) AS f(x); -ERROR: insert or update on table "referencing_table2_7000215" violates foreign key constraint "fkey_ref_to_dist_7000215" -DETAIL: Key (id)=(407) is not present in table "referencing_table_7000207". +ERROR: insert or update on table "referencing_table2_7000272" violates foreign key constraint "fkey_ref_to_dist_7000272" +DETAIL: Key (id)=(404) is not present in table "referencing_table_7000264". -- should success INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,300) AS f(x); DELETE FROM referenced_table WHERE test_column < 200; @@ -920,6 +1137,44 @@ drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referencing_table CASCADE; NOTICE: drop cascades to constraint fkey_ref_to_dist on table referencing_table2 DROP TABLE referencing_table2 CASCADE; +-- Check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); +CREATE TABLE referencing_table2(id int, ref_id int, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 24 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint referencing_table2_ref_id_fkey on table referencing_table2 +drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint referencing_table2_id_fkey on table referencing_table2 +DROP TABLE referencing_table2 CASCADE; -- In this test we have a chained relationship in form of -- distributed table (referencing_referencing_table) has a foreign key with two columns -- to another distributed table (referencing_table) @@ -950,22 +1205,22 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id, ref_i SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.referencing%' ORDER BY 1,2,3; name | relid | refd_relid -----------------------------------------------+------------------------------------------------------------+------------------------------------------------ - fkey_ref_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000227 | fkey_reference_table.referencing_table_7000227 | fkey_reference_table.referenced_table_7000220 - fkey_ref_7000228 | fkey_reference_table.referencing_table_7000228 | fkey_reference_table.referenced_table_7000220 - referencing_referencing_table_id_fkey_7000229 | fkey_reference_table.referencing_referencing_table_7000229 | fkey_reference_table.referencing_table_7000221 - referencing_referencing_table_id_fkey_7000230 | fkey_reference_table.referencing_referencing_table_7000230 | fkey_reference_table.referencing_table_7000222 - referencing_referencing_table_id_fkey_7000231 | fkey_reference_table.referencing_referencing_table_7000231 | fkey_reference_table.referencing_table_7000223 - referencing_referencing_table_id_fkey_7000232 | fkey_reference_table.referencing_referencing_table_7000232 | fkey_reference_table.referencing_table_7000224 - referencing_referencing_table_id_fkey_7000233 | fkey_reference_table.referencing_referencing_table_7000233 | fkey_reference_table.referencing_table_7000225 - referencing_referencing_table_id_fkey_7000234 | fkey_reference_table.referencing_referencing_table_7000234 | fkey_reference_table.referencing_table_7000226 - referencing_referencing_table_id_fkey_7000235 | fkey_reference_table.referencing_referencing_table_7000235 | fkey_reference_table.referencing_table_7000227 - referencing_referencing_table_id_fkey_7000236 | fkey_reference_table.referencing_referencing_table_7000236 | fkey_reference_table.referencing_table_7000228 + fkey_ref_7000292 | fkey_reference_table.referencing_table_7000292 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000293 | fkey_reference_table.referencing_table_7000293 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000294 | fkey_reference_table.referencing_table_7000294 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000295 | fkey_reference_table.referencing_table_7000295 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000296 | fkey_reference_table.referencing_table_7000296 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000297 | fkey_reference_table.referencing_table_7000297 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000298 | fkey_reference_table.referencing_table_7000298 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000299 | fkey_reference_table.referencing_table_7000299 | fkey_reference_table.referenced_table_7000291 + referencing_referencing_table_id_fkey_7000300 | fkey_reference_table.referencing_referencing_table_7000300 | fkey_reference_table.referencing_table_7000292 + referencing_referencing_table_id_fkey_7000301 | fkey_reference_table.referencing_referencing_table_7000301 | fkey_reference_table.referencing_table_7000293 + referencing_referencing_table_id_fkey_7000302 | fkey_reference_table.referencing_referencing_table_7000302 | fkey_reference_table.referencing_table_7000294 + referencing_referencing_table_id_fkey_7000303 | fkey_reference_table.referencing_referencing_table_7000303 | fkey_reference_table.referencing_table_7000295 + referencing_referencing_table_id_fkey_7000304 | fkey_reference_table.referencing_referencing_table_7000304 | fkey_reference_table.referencing_table_7000296 + referencing_referencing_table_id_fkey_7000305 | fkey_reference_table.referencing_referencing_table_7000305 | fkey_reference_table.referencing_table_7000297 + referencing_referencing_table_id_fkey_7000306 | fkey_reference_table.referencing_referencing_table_7000306 | fkey_reference_table.referencing_table_7000298 + referencing_referencing_table_id_fkey_7000307 | fkey_reference_table.referencing_referencing_table_7000307 | fkey_reference_table.referencing_table_7000299 (16 rows) INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(1,1000) AS f(x); @@ -983,6 +1238,125 @@ NOTICE: drop cascades to constraint fkey_ref on table referencing_table DROP TABLE referencing_table CASCADE; NOTICE: drop cascades to constraint referencing_referencing_table_id_fkey on table referencing_referencing_table DROP TABLE referencing_referencing_table; +-- test if create_distributed_table works in transactions with some edge cases +-- the following checks if create_distributed_table works on foreign keys when +-- one of them is a self-referencing table of multiple distributed tables +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES test_table_1(id)); + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES test_table_2(id)); + SELECT create_distributed_table('test_table_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + DROP TABLE test_table_1 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint test_table_2_id_fkey on table test_table_2 +drop cascades to constraint test_table_3_value_1_fkey on table test_table_3 +ROLLBACK; +-- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); +ERROR: relation "fkey_reference_table.test_table_1_7000325" does not exist +CONTEXT: while executing command on localhost:57637 + DROP TABLE test_table_1, test_table_2; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- the order of create_reference_table and create_distributed_table is changed +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY); + SELECT create_reference_table('test_table_2'); + create_reference_table +------------------------ + +(1 row) + + ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); +ERROR: relation "fkey_reference_table.test_table_2_7000342" does not exist +CONTEXT: while executing command on localhost:57637 + DROP TABLE test_table_2 CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- make sure that we fail if we need parallel data load +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + SELECT create_reference_table('test_table_1'); +NOTICE: Copying data from local table... + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); +ERROR: cannot distribute "test_table_2" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- make sure that other DDLs/DMLs also work fine +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE INDEX i1 ON test_table_1(id); + ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); +ERROR: cannot establish a new connection for placement 7000388, since DDL has been executed on a connection that is in use + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; DROP SCHEMA fkey_reference_table CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to type foreign_details diff --git a/src/test/regress/sql/foreign_key_to_reference_table.sql b/src/test/regress/sql/foreign_key_to_reference_table.sql index 323be6889..723ba6fb1 100644 --- a/src/test/regress/sql/foreign_key_to_reference_table.sql +++ b/src/test/regress/sql/foreign_key_to_reference_table.sql @@ -7,6 +7,7 @@ SET search_path TO 'fkey_reference_table'; SET citus.shard_replication_factor TO 1; SET citus.shard_count TO 8; SET citus.next_shard_id TO 7000000; +SET citus.next_placement_id TO 7000000; CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); @@ -38,16 +39,29 @@ SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL; DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL); +SELECT create_distributed_table('referencing_table', 'ref_id'); +DROP TABLE referencing_table; + CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +DROP TABLE referencing_table; + CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL; DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ROLLBACK; + -- try with multiple columns including the distribution column DROP TABLE referenced_table; CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id, test_column)); @@ -58,11 +72,20 @@ SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT; DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +DROP TABLE referencing_table; + CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE; DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ROLLBACK; + -- all of the above is supported if the foreign key does not include distribution column DROP TABLE referenced_table; CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); @@ -74,12 +97,24 @@ ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL); +SELECT create_distributed_table('referencing_table', 'ref_id'); +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); + SELECT create_distributed_table('referencing_table', 'ref_id'); +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + CREATE TABLE referencing_table(id int, ref_id int); SELECT create_distributed_table('referencing_table', 'ref_id'); ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; @@ -107,6 +142,22 @@ SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' A DROP TABLE referencing_table; SET citus.shard_replication_factor TO 1; +-- simple create_distributed_table should work in/out transactions on tables with foreign key to reference tables +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'ref_id'); +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; +DROP TABLE referenced_table; + +BEGIN; + CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); + SELECT create_reference_table('referenced_table'); + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); + SELECT create_distributed_table('referencing_table', 'ref_id'); +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; +DROP TABLE referencing_table; + -- foreign keys are supported either in between distributed tables including the -- distribution column or from distributed tables to reference tables. CREATE TABLE referencing_table(id int, ref_id int); @@ -340,6 +391,20 @@ SELECT * FROM referencing_table WHERE ref_id < 0 ORDER BY 1; DROP TABLE referenced_table CASCADE; DROP TABLE referencing_table CASCADE; +-- create_distributed_table should fail for tables with data if fkey exists to reference table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE); +INSERT INTO referenced_table VALUES (1,1), (2,2), (3,3); +INSERT INTO referencing_table VALUES (1,1), (2,2), (3,3); +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); + +BEGIN; + SELECT create_distributed_table('referencing_table', 'id'); +COMMIT; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; -- Chained references -- In the following test, we create foreign keys from one column in a distributed @@ -380,6 +445,19 @@ DROP TABLE referenced_table CASCADE; DROP TABLE referenced_table2 CASCADE; DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); +SELECT create_reference_table('referenced_table2'); +SELECT create_distributed_table('referencing_table', 'id'); + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referenced_table2 CASCADE; +DROP TABLE referencing_table CASCADE; -- In the following test, we create foreign keys from two columns in a distributed -- table to two reference tables separately. We expect to see that even if a data @@ -396,8 +474,8 @@ SELECT create_reference_table('referenced_table2'); SELECT create_distributed_table('referencing_table', 'id'); BEGIN; -ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; -ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; + ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; COMMIT; SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; @@ -423,6 +501,24 @@ DROP TABLE referenced_table CASCADE; DROP TABLE referenced_table2 CASCADE; DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created when create_distributed_table is used for 1 foreign key and alter table for the other +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); + +BEGIN; + SELECT create_reference_table('referenced_table'); + SELECT create_reference_table('referenced_table2'); + SELECT create_distributed_table('referencing_table', 'id'); + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referenced_table2 CASCADE; +DROP TABLE referencing_table CASCADE; + -- two distributed tables are referencing to one reference table and -- in the same time the distributed table 2 is referencing to @@ -445,7 +541,7 @@ ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFE ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref_to_dist FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE; COMMIT; -SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); -- should fail @@ -467,6 +563,23 @@ DROP TABLE referenced_table CASCADE; DROP TABLE referencing_table CASCADE; DROP TABLE referencing_table2 CASCADE; +-- Check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); +CREATE TABLE referencing_table2(id int, ref_id int, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + SELECT create_distributed_table('referencing_table', 'id'); + SELECT create_distributed_table('referencing_table2', 'id'); +COMMIT; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table CASCADE; +DROP TABLE referencing_table2 CASCADE; + -- In this test we have a chained relationship in form of -- distributed table (referencing_referencing_table) has a foreign key with two columns @@ -494,5 +607,78 @@ DROP TABLE referenced_table CASCADE; DROP TABLE referencing_table CASCADE; DROP TABLE referencing_referencing_table; +-- test if create_distributed_table works in transactions with some edge cases +-- the following checks if create_distributed_table works on foreign keys when +-- one of them is a self-referencing table of multiple distributed tables +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES test_table_1(id)); + SELECT create_distributed_table('test_table_2', 'id'); + + CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES test_table_2(id)); + SELECT create_distributed_table('test_table_3', 'id'); + + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + DROP TABLE test_table_1, test_table_2; +COMMIT; + +-- the order of create_reference_table and create_distributed_table is changed +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_1', 'id'); + + CREATE TABLE test_table_2(id int PRIMARY KEY); + SELECT create_reference_table('test_table_2'); + + ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); + + DROP TABLE test_table_2 CASCADE; +ROLLBACK; + +-- make sure that we fail if we need parallel data load +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + DROP TABLE test_table_2, test_table_1; +COMMIT; + +-- make sure that other DDLs/DMLs also work fine +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + CREATE INDEX i1 ON test_table_1(id); + ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); + + DROP TABLE test_table_2, test_table_1; +COMMIT; + DROP SCHEMA fkey_reference_table CASCADE; SET search_path TO DEFAULT; From 4db72c99f6027c47139ee111ef01cd684e742976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Thu, 7 Jun 2018 14:41:52 +0300 Subject: [PATCH 05/10] Specific DDLs are sequentialized when there is FK -[x] drop constraint -[x] drop column -[x] alter column type -[x] truncate are sequentialized if there is a foreign constraint from a distributed table to a reference table on the affected relations by the above commands. --- .../distributed/ddl/foreign_constraint.c | 185 ++++++- .../distributed/executor/multi_executor.c | 2 - .../distributed/executor/multi_utility.c | 97 +++- .../master/master_modify_multiple_shards.c | 38 ++ src/include/distributed/foreign_constraint.h | 4 + .../foreign_key_to_reference_table.out | 459 +++++++++++++++++- .../regress/expected/multi_foreign_key.out | 3 +- .../expected/relation_access_tracking.out | 2 +- .../expected/relation_access_tracking_0.out | 2 +- .../expected/sequential_modifications.out | 1 - .../sql/foreign_key_to_reference_table.sql | 232 ++++++++- .../regress/sql/relation_access_tracking.sql | 2 +- 12 files changed, 994 insertions(+), 33 deletions(-) diff --git a/src/backend/distributed/ddl/foreign_constraint.c b/src/backend/distributed/ddl/foreign_constraint.c index 84caa3b76..5d9cdbe91 100644 --- a/src/backend/distributed/ddl/foreign_constraint.c +++ b/src/backend/distributed/ddl/foreign_constraint.c @@ -22,12 +22,75 @@ #include "distributed/master_protocol.h" #include "distributed/multi_join_order.h" #include "utils/fmgroids.h" +#include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/relcache.h" #include "utils/ruleutils.h" #include "utils/syscache.h" +static bool HeapTupleOfForeignConstraintIncludesColumn(HeapTuple heapTuple, Oid + relationId, int pgConstraintKey, + char *columnName); + +/* + * ConstraintIsAForeignKeyToReferenceTable function scans the pgConstraint to + * fetch all of the constraints on the given relationId and see if at least one + * of them is a foreign key referencing to a reference table. + */ +bool +ConstraintIsAForeignKeyToReferenceTable(char *constraintName, Oid relationId) +{ + Relation pgConstraint = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + HeapTuple heapTuple = NULL; + bool foreignKeyToReferenceTable = false; + + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(CONSTRAINT_FOREIGN)); + scanDescriptor = systable_beginscan(pgConstraint, InvalidOid, false, + NULL, scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Oid referencedTableId = InvalidOid; + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); + char *constraintName = (constraintForm->conname).data; + + if (strncmp(constraintName, constraintName, NAMEDATALEN) != 0 || + constraintForm->conrelid != relationId) + { + heapTuple = systable_getnext(scanDescriptor); + continue; + } + + referencedTableId = constraintForm->confrelid; + + Assert(IsDistributedTable(referencedTableId)); + + if (PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE) + { + foreignKeyToReferenceTable = true; + break; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + /* clean up scan and close system catalog */ + systable_endscan(scanDescriptor); + heap_close(pgConstraint, AccessShareLock); + + return foreignKeyToReferenceTable; +} + + /* * ErrorIfUnsupportedForeignConstraint runs checks related to foreign constraints and * errors out if it is not possible to create one of the foreign constraint in distributed @@ -60,11 +123,11 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, uint32 referencedTableColocationId = INVALID_COLOCATION_ID; Var *referencedTablePartitionColumn = NULL; - Datum referencingColumnsDatum; - Datum *referencingColumnArray; + Datum referencingColumnsDatum = 0; + Datum *referencingColumnArray = NULL; int referencingColumnCount = 0; - Datum referencedColumnsDatum; - Datum *referencedColumnArray; + Datum referencedColumnsDatum = 0; + Datum *referencedColumnArray = NULL; int referencedColumnCount = 0; bool isNull = false; int attrIdx = 0; @@ -309,6 +372,87 @@ ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, } +/* + * ColumnAppearsInForeignKeyToReferenceTable checks if there is foreign constraint + * from/to a reference table on the given column. We iterate pgConstraint to fetch + * the constraint on the given relationId and find if any of the constraints + * includes the given column. + */ +bool +ColumnAppearsInForeignKeyToReferenceTable(char *columnName, Oid relationId) +{ + Relation pgConstraint = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + HeapTuple heapTuple = NULL; + bool foreignKeyToReferenceTableIncludesGivenColumn = false; + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(CONSTRAINT_FOREIGN)); + + scanDescriptor = systable_beginscan(pgConstraint, InvalidOid, false, + NULL, scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Oid referencedTableId = InvalidOid; + Oid referencingTableId = InvalidOid; + int pgConstraintKey = 0; + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); + + referencedTableId = constraintForm->confrelid; + referencingTableId = constraintForm->conrelid; + + if (referencedTableId == relationId) + { + pgConstraintKey = Anum_pg_constraint_confkey; + } + else if (referencingTableId == relationId) + { + pgConstraintKey = Anum_pg_constraint_conkey; + } + else + { + /* + * If the constraint is not from/to the given relation, we should simply + * skip. + */ + heapTuple = systable_getnext(scanDescriptor); + continue; + } + + /* + * We check if the referenced table is a reference table. There cannot be + * any foreign constraint from a distributed table to a local table. + */ + Assert(IsDistributedTable(referencedTableId)); + if (PartitionMethod(referencedTableId) != DISTRIBUTE_BY_NONE) + { + heapTuple = systable_getnext(scanDescriptor); + continue; + } + + if (HeapTupleOfForeignConstraintIncludesColumn(heapTuple, relationId, + pgConstraintKey, columnName)) + { + foreignKeyToReferenceTableIncludesGivenColumn = true; + break; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + /* clean up scan and close system catalog */ + systable_endscan(scanDescriptor); + heap_close(pgConstraint, AccessShareLock); + return foreignKeyToReferenceTableIncludesGivenColumn; +} + + /* * GetTableForeignConstraints takes in a relationId, and returns the list of foreign * constraint commands needed to reconstruct foreign constraints of that table. @@ -472,3 +616,36 @@ TableReferenced(Oid relationId) return false; } + + +/* + * HeapTupleOfForeignConstraintIncludesColumn fetches the columns from the foreign + * constraint and checks if the given column name matches one of them. + */ +static bool +HeapTupleOfForeignConstraintIncludesColumn(HeapTuple heapTuple, Oid relationId, + int pgConstraintKey, char *columnName) +{ + Datum columnsDatum = 0; + Datum *columnArray = NULL; + int columnCount = 0; + int attrIdx = 0; + bool isNull = false; + + columnsDatum = SysCacheGetAttr(CONSTROID, heapTuple, pgConstraintKey, &isNull); + deconstruct_array(DatumGetArrayTypeP(columnsDatum), INT2OID, 2, true, + 's', &columnArray, NULL, &columnCount); + + for (attrIdx = 0; attrIdx < columnCount; ++attrIdx) + { + AttrNumber attrNo = DatumGetInt16(columnArray[attrIdx]); + + char *colName = get_relid_attribute_name(relationId, attrNo); + if (strncmp(colName, columnName, NAMEDATALEN) == 0) + { + return true; + } + } + + return false; +} diff --git a/src/backend/distributed/executor/multi_executor.c b/src/backend/distributed/executor/multi_executor.c index b506ea955..f088dc8a4 100644 --- a/src/backend/distributed/executor/multi_executor.c +++ b/src/backend/distributed/executor/multi_executor.c @@ -310,8 +310,6 @@ ExecutePlanIntoDestReceiver(PlannedStmt *queryPlan, ParamListInfo params, void SetLocalMultiShardModifyModeToSequential() { - WarnNoTransactionChain(true, "SET LOCAL"); - set_config_option("citus.multi_shard_modify_mode", "sequential", (superuser() ? PGC_SUSET : PGC_USERSET), PGC_S_SESSION, GUC_ACTION_LOCAL, true, 0, false); diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index 007f1090f..0beb9dabb 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -164,6 +164,12 @@ static void PostProcessUtility(Node *parsetree); static List * CollectGrantTableIdList(GrantStmt *grantStmt); static void ProcessDropTableStmt(DropStmt *dropTableStatement); +/* + * We need to run some of the commands sequentially if there is a foreign constraint + * from/to reference table. + */ +static bool ShouldExecuteAlterTableSequentially(Oid relationId, AlterTableCmd *command); + /* * multi_ProcessUtility9x is the 9.x-compatible wrapper for Citus' main utility @@ -1247,6 +1253,7 @@ PlanAlterTableStmt(AlterTableStmt *alterTableStatement, const char *alterTableCo bool isDistributedRelation = false; List *commandList = NIL; ListCell *commandCell = NULL; + bool executeSequentially = false; /* first check whether a distributed relation is affected */ if (alterTableStatement->relation == NULL) @@ -1295,8 +1302,8 @@ PlanAlterTableStmt(AlterTableStmt *alterTableStatement, const char *alterTableCo } /* - * We check if there is a ADD FOREIGN CONSTRAINT command in sub commands list. - * If there is we assign referenced releation id to rightRelationId and we also + * We check if there is a ADD/DROP FOREIGN CONSTRAINT command in sub commands list. + * If there is we assign referenced relation id to rightRelationId and we also * set skip_validation to true to prevent PostgreSQL to verify validity of the * foreign constraint in master. Validity will be checked in workers anyway. */ @@ -1368,28 +1375,27 @@ PlanAlterTableStmt(AlterTableStmt *alterTableStatement, const char *alterTableCo rightRelationId = RangeVarGetRelid(partitionCommand->name, NoLock, false); } #endif + executeSequentially |= ShouldExecuteAlterTableSequentially(leftRelationId, + command); } ddlJob = palloc0(sizeof(DDLJob)); ddlJob->targetRelationId = leftRelationId; ddlJob->concurrentIndexCmd = false; ddlJob->commandString = alterTableCommand; + ddlJob->executeSequentially = executeSequentially; if (rightRelationId) { - /* if foreign key related, use specialized task list function ... */ - ddlJob->taskList = InterShardDDLTaskList(leftRelationId, rightRelationId, - alterTableCommand); - - /* - * We need to execute the ddls working with reference tables on the - * right side sequentially, because parallel ddl operations - * relating to one and only shard of a reference table on a worker - * may cause self-deadlocks. - */ - if (PartitionMethod(rightRelationId) == DISTRIBUTE_BY_NONE) + if (!IsDistributedTable(rightRelationId)) { - ddlJob->executeSequentially = true; + ddlJob->taskList = NIL; + } + else + { + /* if foreign key related, use specialized task list function ... */ + ddlJob->taskList = InterShardDDLTaskList(leftRelationId, rightRelationId, + alterTableCommand); } } else @@ -3796,3 +3802,66 @@ ProcessDropTableStmt(DropStmt *dropTableStatement) } } } + + +/* + * ShouldExecuteAlterTableSequentially checks if the given ALTER TABLE + * statements should be executed sequentially when there is a foreign + * constraint from a distributed table to a reference table. + * In case of a column related ALTER TABLE operation, we check explicitly + * if there is a foreign constraint on this column from/to a reference table. + * Additionally, if the command is run inside a transaction block, we call + * SetLocalMultiShardModifyModeToSequential so that the further commands + * in the same transaction uses the same connections and does not error out. + */ +static bool +ShouldExecuteAlterTableSequentially(Oid relationId, AlterTableCmd *command) +{ + bool executeSequentially = false; + AlterTableType alterTableType = command->subtype; + if (alterTableType == AT_DropConstraint) + { + char *constraintName = command->name; + if (ConstraintIsAForeignKeyToReferenceTable(constraintName, relationId)) + { + executeSequentially = true; + } + } + else if (alterTableType == AT_DropColumn || alterTableType == AT_AlterColumnType) + { + char *affectedColumnName = command->name; + + if (ColumnAppearsInForeignKeyToReferenceTable(affectedColumnName, + relationId)) + { + if (IsTransactionBlock() && alterTableType == AT_AlterColumnType) + { + SetLocalMultiShardModifyModeToSequential(); + } + + executeSequentially = true; + } + } + else if (alterTableType == AT_AddConstraint) + { + /* + * We need to execute the ddls working with reference tables on the + * right side sequentially, because parallel ddl operations + * relating to one and only shard of a reference table on a worker + * may cause self-deadlocks. + */ + Constraint *constraint = (Constraint *) command->def; + if (constraint->contype == CONSTR_FOREIGN) + { + Oid rightRelationId = RangeVarGetRelid(constraint->pktable, NoLock, + false); + if (IsDistributedTable(rightRelationId) && + PartitionMethod(rightRelationId) == DISTRIBUTE_BY_NONE) + { + executeSequentially = true; + } + } + } + + return executeSequentially; +} diff --git a/src/backend/distributed/master/master_modify_multiple_shards.c b/src/backend/distributed/master/master_modify_multiple_shards.c index b7b774594..4456ab64d 100644 --- a/src/backend/distributed/master/master_modify_multiple_shards.c +++ b/src/backend/distributed/master/master_modify_multiple_shards.c @@ -26,6 +26,7 @@ #include "commands/event_trigger.h" #include "distributed/citus_clauses.h" #include "distributed/citus_ruleutils.h" +#include "distributed/foreign_constraint.h" #include "distributed/listutils.h" #include "distributed/master_metadata_utility.h" #include "distributed/master_protocol.h" @@ -57,6 +58,7 @@ static List * ModifyMultipleShardsTaskList(Query *query, List *shardIntervalList, TaskType taskType); +static bool ShouldExecuteTruncateStmtSequential(TruncateStmt *command); PG_FUNCTION_INFO_V1(master_modify_multiple_shards); @@ -134,6 +136,11 @@ master_modify_multiple_shards(PG_FUNCTION_ARGS) } EnsureTablePermissions(relationId, ACL_TRUNCATE); + + if (ShouldExecuteTruncateStmtSequential(truncateStatement)) + { + SetLocalMultiShardModifyModeToSequential(); + } } else { @@ -243,3 +250,34 @@ ModifyMultipleShardsTaskList(Query *query, List *shardIntervalList, TaskType tas return taskList; } + + +/* + * ShouldExecuteTruncateStmtSequential decides if the TRUNCATE stmt needs + * to run sequential. If so, it calls SetLocalMultiShardModifyModeToSequential(). + * + * If a reference table which has a foreign key from a distributed table is truncated + * we need to execute the command sequentially to avoid self-deadlock. + */ +static bool +ShouldExecuteTruncateStmtSequential(TruncateStmt *command) +{ + List *relationList = command->relations; + ListCell *relationCell = NULL; + bool failOK = false; + + foreach(relationCell, relationList) + { + RangeVar *rangeVar = (RangeVar *) lfirst(relationCell); + Oid relationId = RangeVarGetRelid(rangeVar, NoLock, failOK); + + if (IsDistributedTable(relationId) && + PartitionMethod(relationId) == DISTRIBUTE_BY_NONE && + TableReferenced(relationId)) + { + return true; + } + } + + return false; +} diff --git a/src/include/distributed/foreign_constraint.h b/src/include/distributed/foreign_constraint.h index b85a6891e..f12921157 100644 --- a/src/include/distributed/foreign_constraint.h +++ b/src/include/distributed/foreign_constraint.h @@ -14,10 +14,14 @@ #include "utils/relcache.h" #include "nodes/primnodes.h" +extern bool ConstraintIsAForeignKeyToReferenceTable(char *constraintName, + Oid leftRelationId); extern void ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, Var *distributionColumn, uint32 colocationId); +extern bool ColumnAppearsInForeignKeyToReferenceTable(char *columnName, Oid + relationId); extern List * GetTableForeignConstraintCommands(Oid relationId); extern bool HasForeignKeyToReferenceTable(Oid relationId); extern bool TableReferenced(Oid relationId); diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out index b9f9e3de7..a463e7bff 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table.out +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -436,14 +436,12 @@ CONTEXT: while executing command on localhost:57637 -- test delete from referenced table while there is NO corresponding value in referencing table DELETE FROM referenced_table WHERE id = 501; -- test cascading truncate --- will fail for now TRUNCATE referenced_table CASCADE; NOTICE: truncate cascades to table "referencing_table" -ERROR: canceling the transaction since it was involved in a distributed deadlock SELECT count(*) FROM referencing_table; count ------- - 500 + 0 (1 row) -- drop table for next tests @@ -1286,8 +1284,7 @@ BEGIN; (1 row) ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); -ERROR: relation "fkey_reference_table.test_table_1_7000325" does not exist -CONTEXT: while executing command on localhost:57637 +ERROR: cannot perform query with placements that were modified over multiple connections DROP TABLE test_table_1, test_table_2; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; @@ -1309,8 +1306,7 @@ BEGIN; (1 row) ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); -ERROR: relation "fkey_reference_table.test_table_2_7000342" does not exist -CONTEXT: while executing command on localhost:57637 +ERROR: cannot perform query with placements that were modified over multiple connections DROP TABLE test_table_2 CASCADE; ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK; @@ -1357,6 +1353,455 @@ ERROR: cannot establish a new connection for placement 7000388, since DDL has b DROP TABLE test_table_2, test_table_1; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; +-- The following tests check if the DDLs affecting foreign keys work as expected +-- check if we can drop the foreign constraint +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 8 +(1 row) + +ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT foreign_key FOREIGN KEY(value_1) REFERENCES test_table_1(id); +ERROR: cannot perform query with placements that were modified over multiple connections + ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +ERROR: table "test_table_1" does not exist +-- check if we can drop the primary key which cascades to the foreign key +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the primary key which cascades to the foreign key in a transaction block +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_2 DROP COLUMN value_1; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 DROP COLUMN value_1; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can alter the column type which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1,1), (2,2), (3,3); +INSERT INTO test_table_2 VALUES (1,1), (2,2), (3,3); +-- should succeed +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; +ALTER TABLE test_table_1 ALTER COLUMN id SET DATA TYPE bigint; +-- should fail since there is a bigint out of integer range > (2^32 - 1) +INSERT INTO test_table_1 VALUES (2147483648,4); +INSERT INTO test_table_2 VALUES (4,2147483648); +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE int; +ERROR: integer out of range +CONTEXT: while executing command on localhost:57637 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 8 +(1 row) + +DROP TABLE test_table_1 CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +DROP TABLE test_table_2; +-- check if we can alter the column type and drop it which foreign key is referencing to in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +BEGIN; + TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table in a transaction after inserts +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + INSERT INTO test_table_1 VALUES (1),(2),(3); + INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); + TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referencing table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_2 CASCADE; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +SELECT * FROM test_table_1; + id +---- + 1 + 2 + 3 +(3 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referencing table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +BEGIN; + TRUNCATE test_table_2 CASCADE; +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +SELECT * FROM test_table_1; + id +---- + 1 + 2 + 3 +(3 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs +-- in transaction since the upcoming DDLs need to run sequentially. +-- FIXME: fails for now +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('test_table_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; + ALTER TABLE test_table_2 ADD CONSTRAINT fkey FOREIGN KEY (value_1) REFERENCES test_table_1(id); + ALTER TABLE test_table_3 ADD COLUMN test_column int; +ERROR: cannot establish a new connection for placement 7000556, since DDL has been executed on a connection that is in use + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block + ALTER TABLE test_table_1 ADD COLUMN id int; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +DROP TABLE test_table_1, test_table_2, test_table_3; DROP SCHEMA fkey_reference_table CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to type foreign_details diff --git a/src/test/regress/expected/multi_foreign_key.out b/src/test/regress/expected/multi_foreign_key.out index 5eaf5ecc0..94e5bdb6f 100644 --- a/src/test/regress/expected/multi_foreign_key.out +++ b/src/test/regress/expected/multi_foreign_key.out @@ -919,7 +919,8 @@ SELECT create_reference_table('reference_table'); (1 row) ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_local_table(id); -ERROR: relation referenced_local_table is not distributed +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table -- test foreign key creation on ALTER TABLE on self referencing reference table DROP TABLE self_referencing_reference_table; CREATE TABLE self_referencing_reference_table( diff --git a/src/test/regress/expected/relation_access_tracking.out b/src/test/regress/expected/relation_access_tracking.out index 304af1c54..d9a05ac04 100644 --- a/src/test/regress/expected/relation_access_tracking.out +++ b/src/test/regress/expected/relation_access_tracking.out @@ -576,7 +576,7 @@ BEGIN; 101 (1 row) - SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1'); + SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1') ORDER BY 1,2; table_name | select_access | dml_access | ddl_access ------------+-----------------+--------------+-------------- table_1 | parallel_access | not_accessed | not_accessed diff --git a/src/test/regress/expected/relation_access_tracking_0.out b/src/test/regress/expected/relation_access_tracking_0.out index c6edb7993..1d5f5e6af 100644 --- a/src/test/regress/expected/relation_access_tracking_0.out +++ b/src/test/regress/expected/relation_access_tracking_0.out @@ -576,7 +576,7 @@ BEGIN; 101 (1 row) - SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1'); + SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1') ORDER BY 1,2; table_name | select_access | dml_access | ddl_access ------------+-----------------+--------------+-------------- table_1 | parallel_access | not_accessed | not_accessed diff --git a/src/test/regress/expected/sequential_modifications.out b/src/test/regress/expected/sequential_modifications.out index 9170313d4..7cc555fed 100644 --- a/src/test/regress/expected/sequential_modifications.out +++ b/src/test/regress/expected/sequential_modifications.out @@ -74,7 +74,6 @@ SELECT create_distributed_table('test_table', 'a'); -- not useful if not in transaction SELECT set_local_multi_shard_modify_mode_to_sequential(); -WARNING: SET LOCAL can only be used in transaction blocks set_local_multi_shard_modify_mode_to_sequential ------------------------------------------------- diff --git a/src/test/regress/sql/foreign_key_to_reference_table.sql b/src/test/regress/sql/foreign_key_to_reference_table.sql index 723ba6fb1..c9f5f39b6 100644 --- a/src/test/regress/sql/foreign_key_to_reference_table.sql +++ b/src/test/regress/sql/foreign_key_to_reference_table.sql @@ -198,7 +198,6 @@ DELETE FROM referenced_table WHERE id > 3; DELETE FROM referenced_table WHERE id = 501; -- test cascading truncate --- will fail for now TRUNCATE referenced_table CASCADE; SELECT count(*) FROM referencing_table; @@ -680,5 +679,236 @@ BEGIN; DROP TABLE test_table_2, test_table_1; COMMIT; +-- The following tests check if the DDLs affecting foreign keys work as expected +-- check if we can drop the foreign constraint +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + +ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 ADD CONSTRAINT foreign_key FOREIGN KEY(value_1) REFERENCES test_table_1(id); + ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +COMMIT; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the primary key which cascades to the foreign key +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the primary key which cascades to the foreign key in a transaction block +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +COMMIT; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the column which foreign key is referencing from +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +ALTER TABLE test_table_2 DROP COLUMN value_1; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +BEGIN; + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 DROP COLUMN value_1; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the column which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +BEGIN; + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can alter the column type which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); +INSERT INTO test_table_1 VALUES (1,1), (2,2), (3,3); +INSERT INTO test_table_2 VALUES (1,1), (2,2), (3,3); + +-- should succeed +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; +ALTER TABLE test_table_1 ALTER COLUMN id SET DATA TYPE bigint; + +-- should fail since there is a bigint out of integer range > (2^32 - 1) +INSERT INTO test_table_1 VALUES (2147483648,4); +INSERT INTO test_table_2 VALUES (4,2147483648); +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE int; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1 CASCADE; +DROP TABLE test_table_2; + +-- check if we can alter the column type and drop it which foreign key is referencing to in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +BEGIN; + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +COMMIT; + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; +DROP TABLE test_table_1, test_table_2; + +-- check if we can TRUNCATE the referenced table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_1 CASCADE; + +SELECT * FROM test_table_2; +DROP TABLE test_table_1, test_table_2; + +-- check if we can TRUNCATE the referenced table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); + +BEGIN; + TRUNCATE test_table_1 CASCADE; +COMMIT; +SELECT * FROM test_table_2; +DROP TABLE test_table_1, test_table_2; + +-- check if we can TRUNCATE the referenced table in a transaction after inserts +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + +BEGIN; + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + INSERT INTO test_table_1 VALUES (1),(2),(3); + INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); + TRUNCATE test_table_1 CASCADE; +COMMIT; + +SELECT * FROM test_table_2; +DROP TABLE test_table_1, test_table_2; + +-- check if we can TRUNCATE the referencing table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_2 CASCADE; + +SELECT * FROM test_table_2; +SELECT * FROM test_table_1; +DROP TABLE test_table_1, test_table_2; + +-- check if we can TRUNCATE the referencing table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +BEGIN; + TRUNCATE test_table_2 CASCADE; +COMMIT; + +SELECT * FROM test_table_2; +SELECT * FROM test_table_1; +DROP TABLE test_table_1, test_table_2; + +-- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs +-- in transaction since the upcoming DDLs need to run sequentially. +-- FIXME: fails for now +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); +SELECT create_reference_table('test_table_1'); +SELECT create_distributed_table('test_table_2', 'id'); +SELECT create_distributed_table('test_table_3', 'id'); +BEGIN; + ALTER TABLE test_table_2 ADD CONSTRAINT fkey FOREIGN KEY (value_1) REFERENCES test_table_1(id); + ALTER TABLE test_table_3 ADD COLUMN test_column int; + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; + ALTER TABLE test_table_1 ADD COLUMN id int; +COMMIT; +DROP TABLE test_table_1, test_table_2, test_table_3; + DROP SCHEMA fkey_reference_table CASCADE; SET search_path TO DEFAULT; diff --git a/src/test/regress/sql/relation_access_tracking.sql b/src/test/regress/sql/relation_access_tracking.sql index 5f27e7a8a..914e16a9a 100644 --- a/src/test/regress/sql/relation_access_tracking.sql +++ b/src/test/regress/sql/relation_access_tracking.sql @@ -348,7 +348,7 @@ ROLLBACK; -- reference table join with a distributed table BEGIN; SELECT count(*) FROM table_1 JOIN table_6 USING(key); - SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1'); + SELECT * FROM relation_acesses WHERE table_name IN ('table_6', 'table_1') ORDER BY 1,2; ROLLBACK; -- TRUNCATE should be DDL From 89a8d6ab95b133aebf3adcf6c20543ab985e922c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Thu, 28 Jun 2018 10:25:39 +0300 Subject: [PATCH 06/10] FK from dist to ref is tested for partitioning, MX --- .../foreign_key_to_reference_table.out | 55 +- .../foreign_key_to_reference_table_0.out | 1882 +++++++++++++++++ .../mx_foreign_key_to_reference_table.out | 90 + src/test/regress/multi_mx_schedule | 2 +- .../sql/foreign_key_to_reference_table.sql | 37 +- .../sql/mx_foreign_key_to_reference_table.sql | 45 + 6 files changed, 2107 insertions(+), 4 deletions(-) create mode 100644 src/test/regress/expected/foreign_key_to_reference_table_0.out create mode 100644 src/test/regress/expected/mx_foreign_key_to_reference_table.out create mode 100644 src/test/regress/sql/mx_foreign_key_to_reference_table.sql diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out index a463e7bff..093359329 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table.out +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -1581,9 +1581,9 @@ INSERT INTO test_table_2 VALUES (1,1), (2,2), (3,3); -- should succeed ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; ALTER TABLE test_table_1 ALTER COLUMN id SET DATA TYPE bigint; --- should fail since there is a bigint out of integer range > (2^32 - 1) INSERT INTO test_table_1 VALUES (2147483648,4); INSERT INTO test_table_2 VALUES (4,2147483648); +-- should fail since there is a bigint out of integer range > (2^32 - 1) ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE int; ERROR: integer out of range CONTEXT: while executing command on localhost:57637 @@ -1802,6 +1802,59 @@ ERROR: current transaction is aborted, commands ignored until end of transactio ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; DROP TABLE test_table_1, test_table_2, test_table_3; +-- NOTE: Postgres does not support foreign keys on partitioned tables currently. +-- However, we can create foreign keys to/from the partitions themselves. +-- The following tests chech if we create the foreign constraints in partitions properly. +CREATE TABLE referenced_table(id int PRIMARY KEY, test_column int); +CREATE TABLE referencing_table(id int, value_1 int) PARTITION BY RANGE (value_1); +CREATE TABLE referencing_table_0 PARTITION OF referencing_table FOR VALUES FROM (0) TO (2); +CREATE TABLE referencing_table_2 PARTITION OF referencing_table FOR VALUES FROM (2) TO (4); +CREATE TABLE referencing_table_4 PARTITION OF referencing_table FOR VALUES FROM (4) TO (6); +-- partitioned tables are not supported as reference tables +select create_reference_table('referencing_table'); +ERROR: distributing partitioned tables in only supported for hash-distributed tables +-- partitioned tables are supported as hash distributed table +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +-- add foreign constraints in between partitions +ALTER TABLE referencing_table_0 ADD CONSTRAINT pkey PRIMARY KEY (id); +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey FOREIGN KEY (id) REFERENCES referencing_table_0; +-- add foreign constraint from a partition to reference table +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey_to_ref FOREIGN KEY (value_1) REFERENCES referenced_table; +-- should fail since the data will flow to partitioning_test_4 and it has a foreign constraint to partitioning_test_0 on id column +INSERT INTO referencing_table VALUES (0, 5); +ERROR: insert or update on table "referencing_table_4_7000533" violates foreign key constraint "fkey_7000533" +DETAIL: Key (id)=(0) is not present in table "referencing_table_0_7000517". +CONTEXT: while executing command on localhost:57638 +-- should succeed on partitioning_test_0 +INSERT INTO referencing_table VALUES (0, 1); +SELECT * FROM referencing_table; + id | value_1 +----+--------- + 0 | 1 +(1 row) + +-- should fail since partitioning_test_4 has foreign constraint to referenced_table on value_1 column +INSERT INTO referencing_table VALUES (0, 5); +ERROR: insert or update on table "referencing_table_4_7000533" violates foreign key constraint "fkey_to_ref_7000533" +DETAIL: Key (value_1)=(5) is not present in table "referenced_table_7000505". +CONTEXT: while executing command on localhost:57638 +INSERT INTO referenced_table VALUES(5,5); +-- should succeed since both of the foreign constraints are positive +INSERT INTO referencing_table VALUES (0, 5); +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_to_ref on table referencing_table_4 +DROP TABLE referencing_table; DROP SCHEMA fkey_reference_table CASCADE; NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to type foreign_details diff --git a/src/test/regress/expected/foreign_key_to_reference_table_0.out b/src/test/regress/expected/foreign_key_to_reference_table_0.out new file mode 100644 index 000000000..991c48a1f --- /dev/null +++ b/src/test/regress/expected/foreign_key_to_reference_table_0.out @@ -0,0 +1,1882 @@ +-- +-- FOREIGN_KEY_TO_REFERENCE_TABLE +-- +CREATE SCHEMA fkey_reference_table; +SET search_path TO 'fkey_reference_table'; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 8; +SET citus.next_shard_id TO 7000000; +SET citus.next_placement_id TO 7000000; +CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); +SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE TYPE") + (localhost,57638,t,"CREATE TYPE") +(2 rows) + +CREATE VIEW table_fkeys_in_workers AS +SELECT +(json_populate_record(NULL::foreign_details, + json_array_elements_text((run_command_on_workers( $$ + SELECT + COALESCE(json_agg(row_to_json(d)), '[]'::json) + FROM + ( + SELECT + distinct name, + relid::regclass::text, + refd_relid::regclass::text + FROM + table_fkey_cols + ) + d $$ )).RESULT::json )::json )).* ; +CREATE TABLE referenced_table(id int UNIQUE, test_column int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +-- we still do not support update/delete operations through foreign constraints if the foreign key includes the distribution column +-- All should fail +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET NULL); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL or SET DEFAULT is not supported in ON DELETE operation when distribution key is included in the foreign key constraint +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(ref_id) REFERENCES referenced_table(id) ON UPDATE SET NULL); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +ROLLBACK; +-- try with multiple columns including the distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id, test_column)); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE SET DEFAULT); +SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE; +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id, ref_id) REFERENCES referenced_table(id, test_column) ON UPDATE CASCADE); + SELECT create_distributed_table('referencing_table', 'ref_id'); +ERROR: cannot create foreign key constraint +DETAIL: SET NULL, SET DEFAULT or CASCADE is not supported in ON UPDATE operation when distribution key included in the foreign constraint. +ROLLBACK; +-- all of the above is supported if the foreign key does not include distribution column +DROP TABLE referenced_table; +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000043 | fkey_reference_table.referencing_table_7000043 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000044 | fkey_reference_table.referencing_table_7000044 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000045 | fkey_reference_table.referencing_table_7000045 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000046 | fkey_reference_table.referencing_table_7000046 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000047 | fkey_reference_table.referencing_table_7000047 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000048 | fkey_reference_table.referencing_table_7000048 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000049 | fkey_reference_table.referencing_table_7000049 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000050 | fkey_reference_table.referencing_table_7000050 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET NULL); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000051 | fkey_reference_table.referencing_table_7000051 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000052 | fkey_reference_table.referencing_table_7000052 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000053 | fkey_reference_table.referencing_table_7000053 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000054 | fkey_reference_table.referencing_table_7000054 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000055 | fkey_reference_table.referencing_table_7000055 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000056 | fkey_reference_table.referencing_table_7000056 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000057 | fkey_reference_table.referencing_table_7000057 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000058 | fkey_reference_table.referencing_table_7000058 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000059 | fkey_reference_table.referencing_table_7000059 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000060 | fkey_reference_table.referencing_table_7000060 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000061 | fkey_reference_table.referencing_table_7000061 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000062 | fkey_reference_table.referencing_table_7000062 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000063 | fkey_reference_table.referencing_table_7000063 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000064 | fkey_reference_table.referencing_table_7000064 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000065 | fkey_reference_table.referencing_table_7000065 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000066 | fkey_reference_table.referencing_table_7000066 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +BEGIN; + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY(id) REFERENCES referenced_table(id) ON DELETE SET DEFAULT); + SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000067 | fkey_reference_table.referencing_table_7000067 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000068 | fkey_reference_table.referencing_table_7000068 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000069 | fkey_reference_table.referencing_table_7000069 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000070 | fkey_reference_table.referencing_table_7000070 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000071 | fkey_reference_table.referencing_table_7000071 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000072 | fkey_reference_table.referencing_table_7000072 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000073 | fkey_reference_table.referencing_table_7000073 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000074 | fkey_reference_table.referencing_table_7000074 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET NULL; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000075 | fkey_reference_table.referencing_table_7000075 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000076 | fkey_reference_table.referencing_table_7000076 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000077 | fkey_reference_table.referencing_table_7000077 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000078 | fkey_reference_table.referencing_table_7000078 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000079 | fkey_reference_table.referencing_table_7000079 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000080 | fkey_reference_table.referencing_table_7000080 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000081 | fkey_reference_table.referencing_table_7000081 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000082 | fkey_reference_table.referencing_table_7000082 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE SET DEFAULT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000083 | fkey_reference_table.referencing_table_7000083 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000084 | fkey_reference_table.referencing_table_7000084 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000085 | fkey_reference_table.referencing_table_7000085 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000086 | fkey_reference_table.referencing_table_7000086 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000087 | fkey_reference_table.referencing_table_7000087 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000088 | fkey_reference_table.referencing_table_7000088 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000089 | fkey_reference_table.referencing_table_7000089 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000090 | fkey_reference_table.referencing_table_7000090 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------------------+------------------------------------------------+----------------------------------------------- + fkey_ref_7000091 | fkey_reference_table.referencing_table_7000091 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000092 | fkey_reference_table.referencing_table_7000092 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000093 | fkey_reference_table.referencing_table_7000093 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000094 | fkey_reference_table.referencing_table_7000094 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000095 | fkey_reference_table.referencing_table_7000095 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000096 | fkey_reference_table.referencing_table_7000096 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000097 | fkey_reference_table.referencing_table_7000097 | fkey_reference_table.referenced_table_7000042 + fkey_ref_7000098 | fkey_reference_table.referencing_table_7000098 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +-- foreign keys are only supported when the replication factor = 1 +SET citus.shard_replication_factor TO 2; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint +DETAIL: Citus Community Edition currently supports foreign key constraints only for "citus.shard_replication_factor = 1". +HINT: Please change "citus.shard_replication_factor to 1". To learn more about using foreign keys with other replication factors, please contact us at https://citusdata.com/about/contact_us. +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +SET citus.shard_replication_factor TO 1; +-- simple create_distributed_table should work in/out transactions on tables with foreign key to reference tables +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); +SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000107 | fkey_reference_table.referencing_table_7000107 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000108 | fkey_reference_table.referencing_table_7000108 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000109 | fkey_reference_table.referencing_table_7000109 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000110 | fkey_reference_table.referencing_table_7000110 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000111 | fkey_reference_table.referencing_table_7000111 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000112 | fkey_reference_table.referencing_table_7000112 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000113 | fkey_reference_table.referencing_table_7000113 | fkey_reference_table.referenced_table_7000042 + referencing_table_id_fkey_7000114 | fkey_reference_table.referencing_table_7000114 | fkey_reference_table.referenced_table_7000042 +(8 rows) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +BEGIN; + CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); + SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(id)); + SELECT create_distributed_table('referencing_table', 'ref_id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------+------------------------------------------------+----------------------------------------------- + referencing_table_id_fkey_7000116 | fkey_reference_table.referencing_table_7000116 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000117 | fkey_reference_table.referencing_table_7000117 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000118 | fkey_reference_table.referencing_table_7000118 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000119 | fkey_reference_table.referencing_table_7000119 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000120 | fkey_reference_table.referencing_table_7000120 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000121 | fkey_reference_table.referencing_table_7000121 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000122 | fkey_reference_table.referencing_table_7000122 | fkey_reference_table.referenced_table_7000115 + referencing_table_id_fkey_7000123 | fkey_reference_table.referencing_table_7000123 | fkey_reference_table.referenced_table_7000115 +(8 rows) + +DROP TABLE referencing_table; +-- foreign keys are supported either in between distributed tables including the +-- distribution column or from distributed tables to reference tables. +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'append'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_distributed_table('referencing_table', 'ref_id', 'range'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint since relations are not colocated or not referencing a reference table +DETAIL: A distributed table can only have foreign keys if it is referencing another colocated hash distributed table or a reference table +SELECT * FROM table_fkeys_in_workers WHERE name LIKE 'fkey_ref%' ORDER BY 1,2,3; + name | relid | refd_relid +------+-------+------------ +(0 rows) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- test foreign constraint with correct conditions +CREATE TABLE referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(id); +-- test inserts +-- test insert to referencing table while there is NO corresponding value in referenced table +INSERT INTO referencing_table VALUES(1, 1); +ERROR: insert or update on table "referencing_table_7000125" violates foreign key constraint "fkey_ref_7000125" +DETAIL: Key (ref_id)=(1) is not present in table "referenced_table_7000124". +CONTEXT: while executing command on localhost:57637 +-- test insert to referencing while there is corresponding value in referenced table +INSERT INTO referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_table SELECT x, x from generate_series(1,500) as f(x); +-- test deletes +-- test delete from referenced table while there is corresponding value in referencing table +DELETE FROM referenced_table WHERE id > 3; +ERROR: update or delete on table "referenced_table_7000124" violates foreign key constraint "fkey_ref_7000127" on table "referencing_table_7000127" +DETAIL: Key (id)=(4) is still referenced from table "referencing_table_7000127". +CONTEXT: while executing command on localhost:57637 +-- test delete from referenced table while there is NO corresponding value in referencing table +DELETE FROM referenced_table WHERE id = 501; +-- test cascading truncate +TRUNCATE referenced_table CASCADE; +NOTICE: truncate cascades to table "referencing_table" +SELECT count(*) FROM referencing_table; + count +------- + 0 +(1 row) + +-- drop table for next tests +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- self referencing foreign key on reference tables are not allowed +-- TODO try create_reference_table with already created foreign key. +CREATE TABLE referenced_table(id int, test_column int, PRIMARY KEY(id)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referencing_table'); + create_reference_table +------------------------ + +(1 row) + +-- self referencing foreign key +ALTER TABLE referenced_table ADD CONSTRAINT fkey_ref FOREIGN KEY (test_column) REFERENCES referenced_table(id); +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table +-- foreign Keys from reference table to reference table are not allowed +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY(id) REFERENCES referenced_table(id) ON UPDATE CASCADE; +ERROR: cannot create foreign key constraint because reference tables are not supported as the referencing table of a foreign constraint +DETAIL: Reference tables are only supported as the referenced table of a foreign key when the referencing table is a hash distributed table +DROP TABLE referenced_table; +DROP TABLE referencing_table; +-- cascades on delete with different schemas +CREATE SCHEMA referenced_schema; +CREATE SCHEMA referencing_schema; +CREATE TABLE referenced_schema.referenced_table(id int UNIQUE, test_column int, PRIMARY KEY(id, test_column)); +CREATE TABLE referencing_schema.referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_schema.referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_schema.referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_schema.referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_schema.referenced_table(id) ON DELETE CASCADE; +INSERT INTO referenced_schema.referenced_table SELECT x, x from generate_series(1,1000) as f(x); +INSERT INTO referencing_schema.referencing_table SELECT x, x from generate_series(1,1000) as f(x); +DELETE FROM referenced_schema.referenced_table WHERE id > 800; +SELECT count(*) FROM referencing_schema.referencing_table; + count +------- + 800 +(1 row) + +DROP SCHEMA referenced_schema CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table referenced_schema.referenced_table +drop cascades to constraint fkey_ref on table referencing_schema.referencing_table +DROP SCHEMA referencing_schema CASCADE; +NOTICE: drop cascades to table referencing_schema.referencing_table +-- on delete set update cascades properly +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT 1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; +INSERT INTO referenced_table SELECT x, x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE test_column > 800; +SELECT count(*) FROM referencing_table WHERE ref_id = 1; + count +------- + 201 +(1 row) + +DROP TABLE referencing_table; +DROP TABLE referenced_table; +-- foreign key as composite key +CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int); +SELECT run_command_on_workers($$CREATE TYPE fkey_reference_table.composite AS (key1 int, key2 int)$$) ORDER BY 1; + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE TYPE") + (localhost,57638,t,"CREATE TYPE") +(2 rows) + +CREATE TABLE referenced_table(test_column composite, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, referencing_composite composite); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (referencing_composite) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table SELECT (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, (x+1, x+1)::composite FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE (test_column).key1 > 900; +SELECT count(*) FROM referencing_table; + count +------- + 899 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referenced column +-- in the foreign constraint. We'll first show that and insert on non-serial +-- column successfully inserts into the serial and referenced column. +-- Accordingly, the inserts into the referencing table which references to the +-- serial column will be successful. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(1,1000) AS f(x); +DELETE FROM referenced_table WHERE test_column2 > 10; +SELECT count(*) FROM referencing_table; + count +------- + 10 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referencing column +-- in the foreign constraint. We'll first show that the values that exist +-- in the referenced tables are successfully generated by the serial column +-- and inserted to the distributed table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column int PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table SELECT x,x FROM generate_series(1,1000) AS f(x); +-- Success for existing inserts +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); +ERROR: insert or update on table "referencing_table_7000172" violates foreign key constraint "fkey_ref_7000172" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000171". +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we'll use a SERIAL column as the referencing column +-- and referenced columns in a foreign constraint. We'll first show that the +-- the inserts into referenced column will successfully generate and insert +-- data into serial column. Then, we will be successfully insert the same amount +-- of data into referencing table. However, if the values that are generated +-- by serial column do not exist on the referenced table, the query fails. +CREATE TABLE referenced_table(test_column SERIAL PRIMARY KEY, test_column2 int); +CREATE TABLE referencing_table(id int, ref_id SERIAL); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +INSERT INTO referenced_table(test_column2) SELECT x FROM generate_series(1,1000) AS f(x); +-- Success for existing values +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,1000) AS f(x); +-- Fails for non existing value inserts (serial is already incremented) +INSERT INTO referencing_table(id) SELECT x FROM generate_series(1,10) AS f(x); +ERROR: insert or update on table "referencing_table_7000181" violates foreign key constraint "fkey_ref_7000181" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000180". +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we use a volatile function in the referencing +-- column in a foreign constraint. We show that if the data exists in the +-- referenced table, we can successfully use volatile functions with +-- foreign constraints. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT; +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x,(random()*1000)::int FROM generate_series(0,1000) AS f(x); +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we show that Citus currently does not support +-- VALIDATE command. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON DELETE SET DEFAULT NOT VALID; +-- Even if the foreign constraint is added with "NOT VALID", +-- we make sure that it is still applied to the upcoming inserts. +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); +-- we expect this to fail because of the foreign constraint. +INSERT INTO referencing_table SELECT x, x FROM generate_series(1000,1001) AS f(x); +ERROR: insert or update on table "referencing_table_7000204" violates foreign key constraint "fkey_ref_7000204" +DETAIL: Key (ref_id)=(1001) is not present in table "referenced_table_7000198". +-- currently not supported +ALTER TABLE referencing_table VALIDATE CONSTRAINT fkey_ref; +ERROR: alter table command is currently unsupported +DETAIL: Only ADD|DROP COLUMN, SET|DROP NOT NULL, SET|DROP DEFAULT, ADD|DROP CONSTRAINT, SET (), RESET (), ATTACH|DETACH PARTITION and TYPE subcommands are supported. +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following tests, we create a foreign constraint with +-- ON UPDATE CASCADE and see if it works properly with cascading upsert +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE; +INSERT INTO referenced_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table VALUES (1,2), (2,3), (3,4), (4,5) +ON CONFLICT (test_column) +DO UPDATE + SET test_column = -1 * EXCLUDED.test_column; +SELECT * FROM referencing_table WHERE ref_id < 0 ORDER BY 1; + id | ref_id +----+-------- + 1 | -1 + 2 | -2 + 3 | -3 + 4 | -4 +(4 rows) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +-- create_distributed_table should fail for tables with data if fkey exists to reference table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int, ref_id int DEFAULT -1, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column) ON UPDATE CASCADE); +INSERT INTO referenced_table VALUES (1,1), (2,2), (3,3); +INSERT INTO referencing_table VALUES (1,1), (2,2), (3,3); +SELECT create_reference_table('referenced_table'); +NOTICE: Copying data from local table... + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); +ERROR: cannot distribute "referencing_table" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. +BEGIN; + SELECT create_distributed_table('referencing_table', 'id'); +ERROR: cannot distribute "referencing_table" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. +COMMIT; +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_ref_id_fkey on table referencing_table +DROP TABLE referencing_table CASCADE; +-- Chained references +-- In the following test, we create foreign keys from one column in a distributed +-- table to two reference tables. We expect to see that even if a data exist in +-- one reference table, it is not going to be inserted in to referencing table +-- because of lack of the key in the other table. Data can only be inserted into +-- referencing table if it exists in both referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------+------------------------------------------------+------------------------------------------------ + fkey_ref_7000219 | fkey_reference_table.referencing_table_7000219 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000220 | fkey_reference_table.referencing_table_7000220 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table_7000217 + fkey_ref_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table_7000217 + foreign_key_2_7000219 | fkey_reference_table.referencing_table_7000219 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000220 | fkey_reference_table.referencing_table_7000220 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000221 | fkey_reference_table.referencing_table_7000221 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000222 | fkey_reference_table.referencing_table_7000222 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000223 | fkey_reference_table.referencing_table_7000223 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000224 | fkey_reference_table.referencing_table_7000224 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000225 | fkey_reference_table.referencing_table_7000225 | fkey_reference_table.referenced_table2_7000218 + foreign_key_2_7000226 | fkey_reference_table.referencing_table_7000226 | fkey_reference_table.referenced_table2_7000218 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "foreign_key_2_7000220" +DETAIL: Key (id)=(5) is not present in table "referenced_table2_7000218". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "foreign_key_2_7000220" +DETAIL: Key (id)=(5) is not present in table "referenced_table2_7000218". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +ERROR: insert or update on table "referencing_table_7000220" violates foreign key constraint "fkey_ref_7000220" +DETAIL: Key (id)=(1005) is not present in table "referenced_table_7000217". +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(600,900) AS f(x); +SELECT count(*) FROM referencing_table; + count +------- + 301 +(1 row) + +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; + count +------- + 201 +(1 row) + +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + count +------- + 101 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 16 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey1 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- In the following test, we create foreign keys from two columns in a distributed +-- table to two reference tables separately. We expect to see that even if a data +-- exist in one reference table for one column, it is not going to be inserted in +-- to referencing table because the other constraint doesn't hold. Data can only +-- be inserted into referencing table if both columns exist in respective columns +-- in referenced tables. +-- Additionally, delete or update in one referenced table should cascade properly. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; + ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------+------------------------------------------------+------------------------------------------------ + fkey_ref_7000239 | fkey_reference_table.referencing_table_7000239 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000240 | fkey_reference_table.referencing_table_7000240 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000241 | fkey_reference_table.referencing_table_7000241 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000242 | fkey_reference_table.referencing_table_7000242 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000243 | fkey_reference_table.referencing_table_7000243 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000244 | fkey_reference_table.referencing_table_7000244 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000245 | fkey_reference_table.referencing_table_7000245 | fkey_reference_table.referenced_table_7000237 + fkey_ref_7000246 | fkey_reference_table.referencing_table_7000246 | fkey_reference_table.referenced_table_7000237 + foreign_key_2_7000239 | fkey_reference_table.referencing_table_7000239 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000240 | fkey_reference_table.referencing_table_7000240 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000241 | fkey_reference_table.referencing_table_7000241 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000242 | fkey_reference_table.referencing_table_7000242 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000243 | fkey_reference_table.referencing_table_7000243 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000244 | fkey_reference_table.referencing_table_7000244 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000245 | fkey_reference_table.referencing_table_7000245 | fkey_reference_table.referenced_table2_7000238 + foreign_key_2_7000246 | fkey_reference_table.referencing_table_7000246 | fkey_reference_table.referenced_table2_7000238 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +INSERT INTO referenced_table2 SELECT x, x+1 FROM generate_series(500,1500) AS f(x); +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,1500) AS f(x); +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "foreign_key_2_7000245" +DETAIL: Key (ref_id)=(3) is not present in table "referenced_table2_7000238". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "foreign_key_2_7000245" +DETAIL: Key (ref_id)=(3) is not present in table "referenced_table2_7000238". +-- should fail +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(1000,1400) AS f(x); +ERROR: insert or update on table "referencing_table_7000245" violates foreign key constraint "fkey_ref_7000245" +DETAIL: Key (id)=(1002) is not present in table "referenced_table_7000237". +-- should success +INSERT INTO referencing_table SELECT x, x+501 FROM generate_series(0,1000) AS f(x); +SELECT count(*) FROM referencing_table; + count +------- + 1001 +(1 row) + +DELETE FROM referenced_table WHERE test_column < 700; +SELECT count(*) FROM referencing_table; + count +------- + 301 +(1 row) + +DELETE FROM referenced_table2 WHERE test_column2 > 800; +SELECT count(*) FROM referencing_table; + count +------- + 0 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- check if the above fkeys are created when create_distributed_table is used for 1 foreign key and alter table for the other +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); +BEGIN; + SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (ref_id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 16 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referenced_table2 CASCADE; +NOTICE: drop cascades to constraint foreign_key_2 on table referencing_table +DROP TABLE referencing_table CASCADE; +-- two distributed tables are referencing to one reference table and +-- in the same time the distributed table 2 is referencing to +-- distributed table 1. Thus, we have a triangular +-- distributed table 1 has a foreign key from the distribution column to reference table +-- distributed table 2 has a foreign key from a non-distribution column to reference table +-- distributed table 2 has a foreign key to distributed table 1 on the distribution column +-- We show that inserts into distributed table 2 will fail if the data does not exist in distributed table 1 +-- Delete from reference table cascades to both of the distributed tables properly +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int); +CREATE TABLE referencing_table2(id int, ref_id int); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('referencing_table2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; +SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE; +ALTER TABLE referencing_table2 ADD CONSTRAINT fkey_ref_to_dist FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE; +COMMIT; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1,2,3; + name | relid | refd_relid +--------------------------+-------------------------------------------------+------------------------------------------------ + fkey_ref_7000258 | fkey_reference_table.referencing_table_7000258 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000259 | fkey_reference_table.referencing_table_7000259 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000260 | fkey_reference_table.referencing_table_7000260 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000261 | fkey_reference_table.referencing_table_7000261 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000262 | fkey_reference_table.referencing_table_7000262 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000263 | fkey_reference_table.referencing_table_7000263 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000264 | fkey_reference_table.referencing_table_7000264 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000265 | fkey_reference_table.referencing_table_7000265 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000266 | fkey_reference_table.referencing_table2_7000266 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000267 | fkey_reference_table.referencing_table2_7000267 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000268 | fkey_reference_table.referencing_table2_7000268 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000269 | fkey_reference_table.referencing_table2_7000269 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000270 | fkey_reference_table.referencing_table2_7000270 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000271 | fkey_reference_table.referencing_table2_7000271 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000272 | fkey_reference_table.referencing_table2_7000272 | fkey_reference_table.referenced_table_7000257 + fkey_ref_7000273 | fkey_reference_table.referencing_table2_7000273 | fkey_reference_table.referenced_table_7000257 + fkey_ref_to_dist_7000266 | fkey_reference_table.referencing_table2_7000266 | fkey_reference_table.referencing_table_7000258 + fkey_ref_to_dist_7000267 | fkey_reference_table.referencing_table2_7000267 | fkey_reference_table.referencing_table_7000259 + fkey_ref_to_dist_7000268 | fkey_reference_table.referencing_table2_7000268 | fkey_reference_table.referencing_table_7000260 + fkey_ref_to_dist_7000269 | fkey_reference_table.referencing_table2_7000269 | fkey_reference_table.referencing_table_7000261 + fkey_ref_to_dist_7000270 | fkey_reference_table.referencing_table2_7000270 | fkey_reference_table.referencing_table_7000262 + fkey_ref_to_dist_7000271 | fkey_reference_table.referencing_table2_7000271 | fkey_reference_table.referencing_table_7000263 + fkey_ref_to_dist_7000272 | fkey_reference_table.referencing_table2_7000272 | fkey_reference_table.referencing_table_7000264 + fkey_ref_to_dist_7000273 | fkey_reference_table.referencing_table2_7000273 | fkey_reference_table.referencing_table_7000265 +(24 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(0,1000) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,100) AS f(x); +ERROR: insert or update on table "referencing_table2_7000272" violates foreign key constraint "fkey_ref_to_dist_7000272" +DETAIL: Key (id)=(2) is not present in table "referencing_table_7000264". +-- should success +INSERT INTO referencing_table SELECT x, x+1 FROM generate_series(0,400) AS f(x); +-- should fail +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(200,500) AS f(x); +ERROR: insert or update on table "referencing_table2_7000272" violates foreign key constraint "fkey_ref_to_dist_7000272" +DETAIL: Key (id)=(404) is not present in table "referencing_table_7000264". +-- should success +INSERT INTO referencing_table2 SELECT x, x+1 FROM generate_series(0,300) AS f(x); +DELETE FROM referenced_table WHERE test_column < 200; +SELECT count(*) FROM referencing_table; + count +------- + 201 +(1 row) + +SELECT count(*) FROM referencing_table2; + count +------- + 101 +(1 row) + +DELETE FROM referencing_table WHERE id > 200; +SELECT count(*) FROM referencing_table2; + count +------- + 1 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint fkey_ref on table referencing_table2 +drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref_to_dist on table referencing_table2 +DROP TABLE referencing_table2 CASCADE; +-- Check if the above fkeys are created with create_distributed_table +CREATE TABLE referenced_table(test_column int, test_column2 int UNIQUE, PRIMARY KEY(test_column)); +CREATE TABLE referencing_table(id int PRIMARY KEY, ref_id int, FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE); +CREATE TABLE referencing_table2(id int, ref_id int, FOREIGN KEY (ref_id) REFERENCES referenced_table(test_column2) ON DELETE CASCADE, FOREIGN KEY (id) REFERENCES referencing_table(id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT create_distributed_table('referencing_table2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 24 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint referencing_table2_ref_id_fkey on table referencing_table2 +drop cascades to constraint referencing_table_id_fkey on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint referencing_table2_id_fkey on table referencing_table2 +DROP TABLE referencing_table2 CASCADE; +-- In this test we have a chained relationship in form of +-- distributed table (referencing_referencing_table) has a foreign key with two columns +-- to another distributed table (referencing_table) +-- referencing_table has another foreign key with 2 columns to referenced_table. +-- We will show that a cascading delete on referenced_table reaches to referencing_referencing_table. +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column, test_column2)); +CREATE TABLE referencing_table(id int, ref_id int, ref_id2 int, PRIMARY KEY(id, ref_id)); +CREATE TABLE referencing_referencing_table(id int, ref_id int, FOREIGN KEY (id, ref_id) REFERENCES referencing_table(id, ref_id) ON DELETE CASCADE); +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('referencing_referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (ref_id, ref_id2) REFERENCES referenced_table(test_column, test_column2) ON DELETE CASCADE; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.referencing%' ORDER BY 1,2,3; + name | relid | refd_relid +-----------------------------------------------+------------------------------------------------------------+------------------------------------------------ + fkey_ref_7000292 | fkey_reference_table.referencing_table_7000292 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000293 | fkey_reference_table.referencing_table_7000293 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000294 | fkey_reference_table.referencing_table_7000294 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000295 | fkey_reference_table.referencing_table_7000295 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000296 | fkey_reference_table.referencing_table_7000296 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000297 | fkey_reference_table.referencing_table_7000297 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000298 | fkey_reference_table.referencing_table_7000298 | fkey_reference_table.referenced_table_7000291 + fkey_ref_7000299 | fkey_reference_table.referencing_table_7000299 | fkey_reference_table.referenced_table_7000291 + referencing_referencing_table_id_fkey_7000300 | fkey_reference_table.referencing_referencing_table_7000300 | fkey_reference_table.referencing_table_7000292 + referencing_referencing_table_id_fkey_7000301 | fkey_reference_table.referencing_referencing_table_7000301 | fkey_reference_table.referencing_table_7000293 + referencing_referencing_table_id_fkey_7000302 | fkey_reference_table.referencing_referencing_table_7000302 | fkey_reference_table.referencing_table_7000294 + referencing_referencing_table_id_fkey_7000303 | fkey_reference_table.referencing_referencing_table_7000303 | fkey_reference_table.referencing_table_7000295 + referencing_referencing_table_id_fkey_7000304 | fkey_reference_table.referencing_referencing_table_7000304 | fkey_reference_table.referencing_table_7000296 + referencing_referencing_table_id_fkey_7000305 | fkey_reference_table.referencing_referencing_table_7000305 | fkey_reference_table.referencing_table_7000297 + referencing_referencing_table_id_fkey_7000306 | fkey_reference_table.referencing_referencing_table_7000306 | fkey_reference_table.referencing_table_7000298 + referencing_referencing_table_id_fkey_7000307 | fkey_reference_table.referencing_referencing_table_7000307 | fkey_reference_table.referencing_table_7000299 +(16 rows) + +INSERT INTO referenced_table SELECT x, x+1 FROM generate_series(1,1000) AS f(x); +INSERT INTO referencing_table SELECT x, x+1, x+2 FROM generate_series(1,999) AS f(x); +INSERT INTO referencing_referencing_table SELECT x, x+1 FROM generate_series(1,999) AS f(x); +DELETE FROM referenced_table WHERE test_column > 800; +SELECT max(ref_id) FROM referencing_referencing_table; + max +----- + 800 +(1 row) + +DROP TABLE referenced_table CASCADE; +NOTICE: drop cascades to constraint fkey_ref on table referencing_table +DROP TABLE referencing_table CASCADE; +NOTICE: drop cascades to constraint referencing_referencing_table_id_fkey on table referencing_referencing_table +DROP TABLE referencing_referencing_table; +-- test if create_distributed_table works in transactions with some edge cases +-- the following checks if create_distributed_table works on foreign keys when +-- one of them is a self-referencing table of multiple distributed tables +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES test_table_1(id)); + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES test_table_2(id)); + SELECT create_distributed_table('test_table_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + DROP TABLE test_table_1 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint test_table_2_id_fkey on table test_table_2 +drop cascades to constraint test_table_3_value_1_fkey on table test_table_3 +ROLLBACK; +-- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); +ERROR: cannot perform query with placements that were modified over multiple connections + DROP TABLE test_table_1, test_table_2; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- the order of create_reference_table and create_distributed_table is changed +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY); + SELECT create_reference_table('test_table_2'); + create_reference_table +------------------------ + +(1 row) + + ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); +ERROR: cannot perform query with placements that were modified over multiple connections + DROP TABLE test_table_2 CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- make sure that we fail if we need parallel data load +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + SELECT create_reference_table('test_table_1'); +NOTICE: Copying data from local table... + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); +ERROR: cannot distribute "test_table_2" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- make sure that other DDLs/DMLs also work fine +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE INDEX i1 ON test_table_1(id); + ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); +ERROR: cannot establish a new connection for placement 7000388, since DDL has been executed on a connection that is in use + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- The following tests check if the DDLs affecting foreign keys work as expected +-- check if we can drop the foreign constraint +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 8 +(1 row) + +ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT +-- FIXME: fails for now +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT foreign_key FOREIGN KEY(value_1) REFERENCES test_table_1(id); +ERROR: cannot perform query with placements that were modified over multiple connections + ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +ERROR: table "test_table_1" does not exist +-- check if we can drop the primary key which cascades to the foreign key +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the primary key which cascades to the foreign key in a transaction block +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_1 DROP CONSTRAINT test_table_1_pkey CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_2 DROP COLUMN value_1; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 DROP COLUMN value_1; +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can drop the column which foreign key is referencing from in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can alter the column type which foreign key is referencing to +CREATE TABLE test_table_1(id int PRIMARY KEY, id2 int); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1,1), (2,2), (3,3); +INSERT INTO test_table_2 VALUES (1,1), (2,2), (3,3); +-- should succeed +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; +ALTER TABLE test_table_1 ALTER COLUMN id SET DATA TYPE bigint; +INSERT INTO test_table_1 VALUES (2147483648,4); +INSERT INTO test_table_2 VALUES (4,2147483648); +-- should fail since there is a bigint out of integer range > (2^32 - 1) +ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE int; +ERROR: integer out of range +CONTEXT: while executing command on localhost:57637 +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 8 +(1 row) + +DROP TABLE test_table_1 CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +DROP TABLE test_table_2; +-- check if we can alter the column type and drop it which foreign key is referencing to in a transaction block +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +NOTICE: drop cascades to constraint test_table_2_value_1_fkey on table test_table_2 +COMMIT; +SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; + count +------- + 0 +(1 row) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +BEGIN; + TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referenced table in a transaction after inserts +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +BEGIN; + SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + INSERT INTO test_table_1 VALUES (1),(2),(3); + INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); + TRUNCATE test_table_1 CASCADE; +NOTICE: truncate cascades to table "test_table_2" +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referencing table +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +TRUNCATE test_table_2 CASCADE; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +SELECT * FROM test_table_1; + id +---- + 1 + 2 + 3 +(3 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we can TRUNCATE the referencing table in a transaction +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +INSERT INTO test_table_1 VALUES (1),(2),(3); +INSERT INTO test_table_2 VALUES (1,1),(2,2),(3,3); +BEGIN; + TRUNCATE test_table_2 CASCADE; +COMMIT; +SELECT * FROM test_table_2; + id | value_1 +----+--------- +(0 rows) + +SELECT * FROM test_table_1; + id +---- + 1 + 2 + 3 +(3 rows) + +DROP TABLE test_table_1, test_table_2; +-- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs +-- in transaction since the upcoming DDLs need to run sequentially. +-- FIXME: fails for now +CREATE TABLE test_table_1(id int PRIMARY KEY); +CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); +SELECT create_reference_table('test_table_1'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('test_table_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_table('test_table_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +BEGIN; + ALTER TABLE test_table_2 ADD CONSTRAINT fkey FOREIGN KEY (value_1) REFERENCES test_table_1(id); + ALTER TABLE test_table_3 ADD COLUMN test_column int; +ERROR: cannot establish a new connection for placement 7000556, since DDL has been executed on a connection that is in use + ALTER TABLE test_table_1 DROP COLUMN id CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block + ALTER TABLE test_table_1 ADD COLUMN id int; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +DROP TABLE test_table_1, test_table_2, test_table_3; +-- NOTE: Postgres does not support foreign keys on partitioned tables currently. +-- However, we can create foreign keys to/from the partitions themselves. +-- The following tests chech if we create the foreign constraints in partitions properly. +CREATE TABLE referenced_table(id int PRIMARY KEY, test_column int); +CREATE TABLE referencing_table(id int, value_1 int) PARTITION BY RANGE (value_1); +ERROR: syntax error at or near "PARTITION" +LINE 1: ...EATE TABLE referencing_table(id int, value_1 int) PARTITION ... + ^ +CREATE TABLE referencing_table_0 PARTITION OF referencing_table FOR VALUES FROM (0) TO (2); +ERROR: syntax error at or near "PARTITION" +LINE 1: CREATE TABLE referencing_table_0 PARTITION OF referencing_ta... + ^ +CREATE TABLE referencing_table_2 PARTITION OF referencing_table FOR VALUES FROM (2) TO (4); +ERROR: syntax error at or near "PARTITION" +LINE 1: CREATE TABLE referencing_table_2 PARTITION OF referencing_ta... + ^ +CREATE TABLE referencing_table_4 PARTITION OF referencing_table FOR VALUES FROM (4) TO (6); +ERROR: syntax error at or near "PARTITION" +LINE 1: CREATE TABLE referencing_table_4 PARTITION OF referencing_ta... + ^ +-- partitioned tables are not supported as reference tables +select create_reference_table('referencing_table'); +ERROR: relation "referencing_table" does not exist +LINE 1: select create_reference_table('referencing_table'); + ^ +-- partitioned tables are supported as hash distributed table +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); +ERROR: relation "referencing_table" does not exist +LINE 1: SELECT create_distributed_table('referencing_table', 'id'); + ^ +-- add foreign constraints in between partitions +ALTER TABLE referencing_table_0 ADD CONSTRAINT pkey PRIMARY KEY (id); +ERROR: relation "referencing_table_0" does not exist +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey FOREIGN KEY (id) REFERENCES referencing_table_0; +ERROR: relation "referencing_table_4" does not exist +-- add foreign constraint from a partition to reference table +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey_to_ref FOREIGN KEY (value_1) REFERENCES referenced_table; +ERROR: relation "referencing_table_4" does not exist +-- should fail since the data will flow to partitioning_test_4 and it has a foreign constraint to partitioning_test_0 on id column +INSERT INTO referencing_table VALUES (0, 5); +ERROR: relation "referencing_table" does not exist +LINE 1: INSERT INTO referencing_table VALUES (0, 5); + ^ +-- should succeed on partitioning_test_0 +INSERT INTO referencing_table VALUES (0, 1); +ERROR: relation "referencing_table" does not exist +LINE 1: INSERT INTO referencing_table VALUES (0, 1); + ^ +SELECT * FROM referencing_table; +ERROR: relation "referencing_table" does not exist +LINE 1: SELECT * FROM referencing_table; + ^ +-- should fail since partitioning_test_4 has foreign constraint to referenced_table on value_1 column +INSERT INTO referencing_table VALUES (0, 5); +ERROR: relation "referencing_table" does not exist +LINE 1: INSERT INTO referencing_table VALUES (0, 5); + ^ +INSERT INTO referenced_table VALUES(5,5); +-- should succeed since both of the foreign constraints are positive +INSERT INTO referencing_table VALUES (0, 5); +ERROR: relation "referencing_table" does not exist +LINE 1: INSERT INTO referencing_table VALUES (0, 5); + ^ +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table; +ERROR: table "referencing_table" does not exist +DROP SCHEMA fkey_reference_table CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to type foreign_details +drop cascades to view table_fkeys_in_workers +drop cascades to type composite +SET search_path TO DEFAULT; diff --git a/src/test/regress/expected/mx_foreign_key_to_reference_table.out b/src/test/regress/expected/mx_foreign_key_to_reference_table.out new file mode 100644 index 000000000..d0d3058f4 --- /dev/null +++ b/src/test/regress/expected/mx_foreign_key_to_reference_table.out @@ -0,0 +1,90 @@ +CREATE SCHEMA fkey_reference_table; +SET search_path TO 'fkey_reference_table'; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 8; +SET citus.next_shard_id TO 7000000; +SET citus.next_placement_id TO 7000000; +SET citus.replication_model TO streaming; +-- Setup the view so that we can check if the foreign keys are created properly +CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); +SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); + run_command_on_workers +----------------------------------- + (localhost,57637,t,"CREATE TYPE") + (localhost,57638,t,"CREATE TYPE") +(2 rows) + +CREATE VIEW table_fkeys_in_workers AS +SELECT +(json_populate_record(NULL::foreign_details, + json_array_elements_text((run_command_on_workers( $$ + SELECT + COALESCE(json_agg(row_to_json(d)), '[]'::json) + FROM + ( + SELECT + distinct name, + relid::regclass::text, + refd_relid::regclass::text + FROM + table_fkey_cols + ) + d $$ )).RESULT::json )::json )).* ; +-- Check if MX can create foreign keys properly on foreign keys from distributed to reference tables +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; +SELECT create_reference_table('referenced_table'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_reference_table('referenced_table2'); + create_reference_table +------------------------ + +(1 row) + +SELECT create_distributed_table('referencing_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SET search_path TO 'fkey_reference_table'; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1, 2; + name | relid | refd_relid +-----------------------+------------------------------------------------+------------------------------------------------ + fkey_ref | fkey_reference_table.referencing_table | fkey_reference_table.referenced_table + fkey_ref | fkey_reference_table.referencing_table | fkey_reference_table.referenced_table + fkey_ref_7000002 | fkey_reference_table.referencing_table_7000002 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000003 | fkey_reference_table.referencing_table_7000003 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000004 | fkey_reference_table.referencing_table_7000004 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000005 | fkey_reference_table.referencing_table_7000005 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000006 | fkey_reference_table.referencing_table_7000006 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000007 | fkey_reference_table.referencing_table_7000007 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000008 | fkey_reference_table.referencing_table_7000008 | fkey_reference_table.referenced_table_7000000 + fkey_ref_7000009 | fkey_reference_table.referencing_table_7000009 | fkey_reference_table.referenced_table_7000000 + foreign_key_2 | fkey_reference_table.referencing_table | fkey_reference_table.referenced_table2 + foreign_key_2 | fkey_reference_table.referencing_table | fkey_reference_table.referenced_table2 + foreign_key_2_7000002 | fkey_reference_table.referencing_table_7000002 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000003 | fkey_reference_table.referencing_table_7000003 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000004 | fkey_reference_table.referencing_table_7000004 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000005 | fkey_reference_table.referencing_table_7000005 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000006 | fkey_reference_table.referencing_table_7000006 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000007 | fkey_reference_table.referencing_table_7000007 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000008 | fkey_reference_table.referencing_table_7000008 | fkey_reference_table.referenced_table2_7000001 + foreign_key_2_7000009 | fkey_reference_table.referencing_table_7000009 | fkey_reference_table.referenced_table2_7000001 +(20 rows) + +DROP SCHEMA fkey_reference_table CASCADE; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to type foreign_details +drop cascades to view table_fkeys_in_workers +drop cascades to table referenced_table +drop cascades to table referenced_table2 +drop cascades to table referencing_table +SET search_path TO DEFAULT; diff --git a/src/test/regress/multi_mx_schedule b/src/test/regress/multi_mx_schedule index a746e7d58..11be43432 100644 --- a/src/test/regress/multi_mx_schedule +++ b/src/test/regress/multi_mx_schedule @@ -25,7 +25,7 @@ test: multi_mx_tpch_query12 multi_mx_tpch_query14 multi_mx_tpch_query19 test: multi_mx_tpch_query3 multi_mx_tpch_query6 multi_mx_tpch_query7 test: multi_mx_tpch_query7_nested multi_mx_ddl test: recursive_dml_queries_mx -test: multi_mx_repartition_udt_prepare +test: multi_mx_repartition_udt_prepare mx_foreign_key_to_reference_table test: multi_mx_repartition_join_w1 multi_mx_repartition_join_w2 multi_mx_repartition_udt_w1 multi_mx_repartition_udt_w2 test: multi_mx_metadata test: multi_mx_modifications diff --git a/src/test/regress/sql/foreign_key_to_reference_table.sql b/src/test/regress/sql/foreign_key_to_reference_table.sql index c9f5f39b6..f79ef0469 100644 --- a/src/test/regress/sql/foreign_key_to_reference_table.sql +++ b/src/test/regress/sql/foreign_key_to_reference_table.sql @@ -795,11 +795,10 @@ INSERT INTO test_table_2 VALUES (1,1), (2,2), (3,3); ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE bigint; ALTER TABLE test_table_1 ALTER COLUMN id SET DATA TYPE bigint; --- should fail since there is a bigint out of integer range > (2^32 - 1) INSERT INTO test_table_1 VALUES (2147483648,4); INSERT INTO test_table_2 VALUES (4,2147483648); +-- should fail since there is a bigint out of integer range > (2^32 - 1) ALTER TABLE test_table_2 ALTER COLUMN value_1 SET DATA TYPE int; - SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%'; DROP TABLE test_table_1 CASCADE; DROP TABLE test_table_2; @@ -909,6 +908,40 @@ BEGIN; ALTER TABLE test_table_1 ADD COLUMN id int; COMMIT; DROP TABLE test_table_1, test_table_2, test_table_3; +-- NOTE: Postgres does not support foreign keys on partitioned tables currently. +-- However, we can create foreign keys to/from the partitions themselves. +-- The following tests chech if we create the foreign constraints in partitions properly. +CREATE TABLE referenced_table(id int PRIMARY KEY, test_column int); +CREATE TABLE referencing_table(id int, value_1 int) PARTITION BY RANGE (value_1); +CREATE TABLE referencing_table_0 PARTITION OF referencing_table FOR VALUES FROM (0) TO (2); +CREATE TABLE referencing_table_2 PARTITION OF referencing_table FOR VALUES FROM (2) TO (4); +CREATE TABLE referencing_table_4 PARTITION OF referencing_table FOR VALUES FROM (4) TO (6); + +-- partitioned tables are not supported as reference tables +select create_reference_table('referencing_table'); + +-- partitioned tables are supported as hash distributed table +SELECT create_reference_table('referenced_table'); +SELECT create_distributed_table('referencing_table', 'id'); + +-- add foreign constraints in between partitions +ALTER TABLE referencing_table_0 ADD CONSTRAINT pkey PRIMARY KEY (id); +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey FOREIGN KEY (id) REFERENCES referencing_table_0; +-- add foreign constraint from a partition to reference table +ALTER TABLE referencing_table_4 ADD CONSTRAINT fkey_to_ref FOREIGN KEY (value_1) REFERENCES referenced_table; +-- should fail since the data will flow to partitioning_test_4 and it has a foreign constraint to partitioning_test_0 on id column +INSERT INTO referencing_table VALUES (0, 5); +-- should succeed on partitioning_test_0 +INSERT INTO referencing_table VALUES (0, 1); +SELECT * FROM referencing_table; +-- should fail since partitioning_test_4 has foreign constraint to referenced_table on value_1 column +INSERT INTO referencing_table VALUES (0, 5); +INSERT INTO referenced_table VALUES(5,5); +-- should succeed since both of the foreign constraints are positive +INSERT INTO referencing_table VALUES (0, 5); + +DROP TABLE referenced_table CASCADE; +DROP TABLE referencing_table; DROP SCHEMA fkey_reference_table CASCADE; SET search_path TO DEFAULT; diff --git a/src/test/regress/sql/mx_foreign_key_to_reference_table.sql b/src/test/regress/sql/mx_foreign_key_to_reference_table.sql new file mode 100644 index 000000000..8b19a2efe --- /dev/null +++ b/src/test/regress/sql/mx_foreign_key_to_reference_table.sql @@ -0,0 +1,45 @@ +CREATE SCHEMA fkey_reference_table; +SET search_path TO 'fkey_reference_table'; +SET citus.shard_replication_factor TO 1; +SET citus.shard_count TO 8; +SET citus.next_shard_id TO 7000000; +SET citus.next_placement_id TO 7000000; +SET citus.replication_model TO streaming; + +-- Setup the view so that we can check if the foreign keys are created properly +CREATE TYPE foreign_details AS (name text, relid text, refd_relid text); +SELECT run_command_on_workers($$CREATE TYPE foreign_details AS (name text, relid text, refd_relid text)$$); + +CREATE VIEW table_fkeys_in_workers AS +SELECT +(json_populate_record(NULL::foreign_details, + json_array_elements_text((run_command_on_workers( $$ + SELECT + COALESCE(json_agg(row_to_json(d)), '[]'::json) + FROM + ( + SELECT + distinct name, + relid::regclass::text, + refd_relid::regclass::text + FROM + table_fkey_cols + ) + d $$ )).RESULT::json )::json )).* ; + +-- Check if MX can create foreign keys properly on foreign keys from distributed to reference tables +CREATE TABLE referenced_table(test_column int, test_column2 int, PRIMARY KEY(test_column)); +CREATE TABLE referenced_table2(test_column int, test_column2 int, PRIMARY KEY(test_column2)); +CREATE TABLE referencing_table(id int, ref_id int); +ALTER TABLE referencing_table ADD CONSTRAINT fkey_ref FOREIGN KEY (id) REFERENCES referenced_table(test_column) ON DELETE CASCADE; +ALTER TABLE referencing_table ADD CONSTRAINT foreign_key_2 FOREIGN KEY (id) REFERENCES referenced_table2(test_column2) ON DELETE CASCADE; + +SELECT create_reference_table('referenced_table'); +SELECT create_reference_table('referenced_table2'); +SELECT create_distributed_table('referencing_table', 'id'); + +SET search_path TO 'fkey_reference_table'; +SELECT * FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_table.%' AND refd_relid LIKE 'fkey_reference_table.%' ORDER BY 1, 2; + +DROP SCHEMA fkey_reference_table CASCADE; +SET search_path TO DEFAULT; From 6be6911ed90c28a67791ee041c3c173f7c320fa4 Mon Sep 17 00:00:00 2001 From: velioglu Date: Tue, 12 Jun 2018 12:44:35 +0300 Subject: [PATCH 07/10] Create foreign key relation graph and functions to query on it --- Makefile | 2 + .../commands/create_distributed_table.c | 5 + .../distributed/ddl/foreign_constraint.c | 94 +++ .../distributed/executor/multi_utility.c | 189 +++++- .../test/foreign_key_relationship_query.c | 123 ++++ .../utils/foreign_key_relationship.c | 445 +++++++++++++ .../distributed/utils/metadata_cache.c | 109 ++- src/include/distributed/foreign_constraint.h | 3 + .../distributed/foreign_key_relationship.h | 24 + src/include/distributed/metadata_cache.h | 14 + .../multi_foreign_key_relation_graph.out | 620 ++++++++++++++++++ src/test/regress/multi_schedule | 2 +- .../sql/multi_foreign_key_relation_graph.sql | 224 +++++++ 13 files changed, 1839 insertions(+), 15 deletions(-) create mode 100644 src/backend/distributed/test/foreign_key_relationship_query.c create mode 100644 src/backend/distributed/utils/foreign_key_relationship.c create mode 100644 src/include/distributed/foreign_key_relationship.h create mode 100644 src/test/regress/expected/multi_foreign_key_relation_graph.out create mode 100644 src/test/regress/sql/multi_foreign_key_relation_graph.sql diff --git a/Makefile b/Makefile index 8ff43160c..c251f6ce3 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,7 @@ OBJS = src/backend/distributed/shared_library_init.o \ src/backend/distributed/test/distributed_deadlock_detection.o \ src/backend/distributed/test/distribution_metadata.o \ src/backend/distributed/test/fake_fdw.o \ + src/backend/distributed/test/foreign_key_relationship_query.o \ src/backend/distributed/test/generate_ddl_commands.o \ src/backend/distributed/test/metadata_sync.o \ src/backend/distributed/test/partitioning_utils.o \ @@ -90,6 +91,7 @@ OBJS = src/backend/distributed/shared_library_init.o \ src/backend/distributed/utils/colocation_utils.o \ src/backend/distributed/utils/distribution_column.o \ src/backend/distributed/utils/errormessage.o \ + src/backend/distributed/utils/foreign_key_relationship.o \ src/backend/distributed/utils/hash_helpers.o \ src/backend/distributed/utils/listutils.o \ src/backend/distributed/utils/maintenanced.o \ diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index b557cbdd4..b0d006926 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -759,6 +759,11 @@ EnsureRelationCanBeDistributed(Oid relationId, Var *distributionColumn, ErrorIfUnsupportedConstraint(relation, distributionMethod, distributionColumn, colocationId); + if (TableReferenced(relationId) || TableReferencing(relationId)) + { + InvalidateForeignKeyGraph(); + } + relation_close(relation, NoLock); } diff --git a/src/backend/distributed/ddl/foreign_constraint.c b/src/backend/distributed/ddl/foreign_constraint.c index 5d9cdbe91..e6398b47d 100644 --- a/src/backend/distributed/ddl/foreign_constraint.c +++ b/src/backend/distributed/ddl/foreign_constraint.c @@ -649,3 +649,97 @@ HeapTupleOfForeignConstraintIncludesColumn(HeapTuple heapTuple, Oid relationId, return false; } + + +/* + * TableReferencing function checks whether given table is referencing by another table + * via foreign constraints. If it is referencing, this function returns true. To check + * that, this function searches given relation at pg_constraints system catalog. However + * since there is no index for the column we searched, this function performs sequential + * search, therefore call this function with caution. + */ +bool +TableReferencing(Oid relationId) +{ + Relation pgConstraint = NULL; + HeapTuple heapTuple = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + Oid scanIndexId = InvalidOid; + bool useIndex = false; + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, + relationId); + scanDescriptor = systable_beginscan(pgConstraint, scanIndexId, useIndex, NULL, + scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); + + if (constraintForm->contype == CONSTRAINT_FOREIGN) + { + systable_endscan(scanDescriptor); + heap_close(pgConstraint, NoLock); + + return true; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + systable_endscan(scanDescriptor); + heap_close(pgConstraint, NoLock); + + return false; +} + + +/* + * ConstraintIsAForeignKey returns true if the given constraint name + * is a foreign key to defined on the relation. + */ +bool +ConstraintIsAForeignKey(char *constraintNameInput, Oid relationId) +{ + Relation pgConstraint = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + HeapTuple heapTuple = NULL; + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(CONSTRAINT_FOREIGN)); + scanDescriptor = systable_beginscan(pgConstraint, InvalidOid, false, + NULL, scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); + char *constraintName = (constraintForm->conname).data; + + if (strncmp(constraintName, constraintNameInput, NAMEDATALEN) == 0 && + constraintForm->conrelid == relationId) + { + systable_endscan(scanDescriptor); + heap_close(pgConstraint, AccessShareLock); + + return true; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + /* clean up scan and close system catalog */ + systable_endscan(scanDescriptor); + heap_close(pgConstraint, AccessShareLock); + + return false; +} diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index 0beb9dabb..ff8e5d878 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -75,6 +75,7 @@ #include "utils/builtins.h" #include "utils/elog.h" #include "utils/errcodes.h" +#include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/hsearch.h" #include "utils/inval.h" @@ -86,6 +87,10 @@ bool EnableDDLPropagation = true; /* ddl propagation is enabled */ + +static bool shouldInvalidateForeignKeyGraph = false; + + /* * This struct defines the state for the callback for drop statements. * It is copied as it is from commands/tablecmds.c in Postgres source. @@ -162,7 +167,11 @@ static void CheckCopyPermissions(CopyStmt *copyStatement); static List * CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist); static void PostProcessUtility(Node *parsetree); static List * CollectGrantTableIdList(GrantStmt *grantStmt); +static char * GetSchemaNameFromDropObject(ListCell *dropSchemaCell); static void ProcessDropTableStmt(DropStmt *dropTableStatement); +static void ProcessDropSchemaStmt(DropStmt *dropSchemaStatement); +static void InvalidateForeignKeyGraphForDDL(void); + /* * We need to run some of the commands sequentially if there is a foreign constraint @@ -371,6 +380,11 @@ multi_ProcessUtility(PlannedStmt *pstmt, { ProcessDropTableStmt(dropStatement); } + + if (dropStatement->removeType == OBJECT_SCHEMA) + { + ProcessDropSchemaStmt(dropStatement); + } } if (IsA(parsetree, AlterTableStmt)) @@ -530,6 +544,16 @@ multi_ProcessUtility(PlannedStmt *pstmt, SetUserIdAndSecContext(savedUserId, savedSecurityContext); } + /* + * Re-forming the foreign key graph relies on the command being executed + * on the local table first. However, in order to decide whether the + * command leads to an invalidation, we need to check before the command + * is being executed since we read pg_constraint table. Thus, we maintain a + * local flag and do the invalidation after multi_ProcessUtility, + * before ExecuteDistributedDDLJob(). + */ + InvalidateForeignKeyGraphForDDL(); + /* after local command has completed, finish by executing worker DDLJobs, if any */ if (ddlJobs != NIL) { @@ -556,8 +580,27 @@ multi_ProcessUtility(PlannedStmt *pstmt, if (alterTableType == AT_AddConstraint) { + LOCKMODE lockmode = NoLock; + Oid relationId = InvalidOid; + Constraint *constraint = NULL; + Assert(list_length(commandList) == 1); + ErrorIfUnsupportedAlterAddConstraintStmt(alterTableStatement); + + lockmode = AlterTableGetLockLevel(alterTableStatement->cmds); + relationId = AlterTableLookupRelation(alterTableStatement, lockmode); + + if (!OidIsValid(relationId)) + { + continue; + } + + constraint = (Constraint *) command->def; + if (ConstraintIsAForeignKey(constraint->conname, relationId)) + { + InvalidateForeignKeyGraph(); + } } } } @@ -617,6 +660,22 @@ multi_ProcessUtility(PlannedStmt *pstmt, } +/* + * InvalidateForeignKeyGraphForDDL simply keeps track of whether + * the foreign key graph should be invalidated due to a DDL. + */ +static void +InvalidateForeignKeyGraphForDDL(void) +{ + if (shouldInvalidateForeignKeyGraph) + { + InvalidateForeignKeyGraph(); + + shouldInvalidateForeignKeyGraph = false; + } +} + + /* * IsCitusExtensionStmt returns whether a given utility is a CREATE or ALTER * EXTENSION statement which references the citus extension. This function @@ -2244,8 +2303,25 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) } #endif - case AT_SetNotNull: case AT_DropConstraint: + { + LOCKMODE lockmode = AlterTableGetLockLevel(alterTableStatement->cmds); + Oid relationId = AlterTableLookupRelation(alterTableStatement, lockmode); + + if (!OidIsValid(relationId)) + { + return; + } + + if (ConstraintIsAForeignKey(command->name, relationId)) + { + shouldInvalidateForeignKeyGraph = true; + } + + break; + } + + case AT_SetNotNull: case AT_EnableTrigAll: case AT_DisableTrigAll: case AT_ReplicaIdentity: @@ -3747,6 +3823,99 @@ RoleSpecString(RoleSpec *spec) } +/* + * ProcessDropSchemaStmt invalidates the foreign key cache if any table created + * under dropped schema involved in any foreign key relationship. + */ +static void +ProcessDropSchemaStmt(DropStmt *dropStatement) +{ + Relation pgClass = NULL; + HeapTuple heapTuple = NULL; + SysScanDesc scanDescriptor = NULL; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + Oid scanIndexId = InvalidOid; + bool useIndex = false; + ListCell *dropSchemaCell; + + if (dropStatement->behavior != DROP_CASCADE) + { + return; + } + + foreach(dropSchemaCell, dropStatement->objects) + { + char *schemaString = GetSchemaNameFromDropObject(dropSchemaCell); + Oid namespaceOid = get_namespace_oid(schemaString, true); + + if (namespaceOid == InvalidOid) + { + continue; + } + + pgClass = heap_open(RelationRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_class_relnamespace, BTEqualStrategyNumber, + F_OIDEQ, namespaceOid); + scanDescriptor = systable_beginscan(pgClass, scanIndexId, useIndex, NULL, + scanKeyCount, scanKey); + + heapTuple = systable_getnext(scanDescriptor); + while (HeapTupleIsValid(heapTuple)) + { + Form_pg_class relationForm = (Form_pg_class) GETSTRUCT(heapTuple); + char *relationName = NameStr(relationForm->relname); + Oid relationId = get_relname_relid(relationName, namespaceOid); + + /* we're not interested in non-valid, non-distributed relations */ + if (relationId == InvalidOid || !IsDistributedTable(relationId)) + { + heapTuple = systable_getnext(scanDescriptor); + continue; + } + + /* invalidate foreign key cache if the table involved in any foreign key */ + if (TableReferenced(relationId) || TableReferencing(relationId)) + { + shouldInvalidateForeignKeyGraph = true; + + systable_endscan(scanDescriptor); + heap_close(pgClass, NoLock); + return; + } + + heapTuple = systable_getnext(scanDescriptor); + } + + systable_endscan(scanDescriptor); + heap_close(pgClass, NoLock); + } +} + + +/* + * GetSchemaNameFromDropObject gets the name of the drop schema from given + * list cell. This function is defined due to API change between PG 9.6 and + * PG 10. + */ +static char * +GetSchemaNameFromDropObject(ListCell *dropSchemaCell) +{ + char *schemaString = NULL; + +#if (PG_VERSION_NUM >= 100000) + Value *schemaValue = (Value *) lfirst(dropSchemaCell); + schemaString = strVal(schemaValue); +#else + List *schemaNameList = (List *) lfirst(dropSchemaCell); + schemaString = NameListToString(schemaNameList); +#endif + + return schemaString; +} + + /* * ProcessDropTableStmt processes DROP TABLE commands for partitioned tables. * If we are trying to DROP partitioned tables, we first need to go to MX nodes @@ -3774,10 +3943,20 @@ ProcessDropTableStmt(DropStmt *dropTableStatement) Oid relationId = RangeVarGetRelid(tableRangeVar, AccessShareLock, missingOK); - if (relationId == InvalidOid || - !IsDistributedTable(relationId) || - !ShouldSyncTableMetadata(relationId) || - !PartitionedTable(relationId)) + /* we're not interested in non-valid, non-distributed relations */ + if (relationId == InvalidOid || !IsDistributedTable(relationId)) + { + continue; + } + + /* invalidate foreign key cache if the table involved in any foreign key */ + if ((TableReferenced(relationId) || TableReferencing(relationId))) + { + shouldInvalidateForeignKeyGraph = true; + } + + /* we're only interested in partitioned and mx tables */ + if (!ShouldSyncTableMetadata(relationId) || !PartitionedTable(relationId)) { continue; } diff --git a/src/backend/distributed/test/foreign_key_relationship_query.c b/src/backend/distributed/test/foreign_key_relationship_query.c new file mode 100644 index 000000000..5e9a6a712 --- /dev/null +++ b/src/backend/distributed/test/foreign_key_relationship_query.c @@ -0,0 +1,123 @@ +/*------------------------------------------------------------------------- + * + * foreign_key_relationship_query.c + * + * This file contains UDFs for getting foreign constraint relationship between + * distributed tables. + * + * Copyright (c) 2018, Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "fmgr.h" +#include "funcapi.h" + +#include "distributed/metadata_cache.h" + + +/* these functions are only exported in the regression tests */ +PG_FUNCTION_INFO_V1(get_referencing_relation_id_list); +PG_FUNCTION_INFO_V1(get_referenced_relation_id_list); + +/* + * get_referencing_relation_id_list returns the list of table oids that is referencing + * by given oid recursively. It uses the list cached in the distributed table cache + * entry. + */ +Datum +get_referencing_relation_id_list(PG_FUNCTION_ARGS) +{ + FuncCallContext *functionContext = NULL; + ListCell *foreignRelationCell = NULL; + + CheckCitusVersion(ERROR); + + /* for the first we call this UDF, we need to populate the result to return set */ + if (SRF_IS_FIRSTCALL()) + { + Oid relationId = PG_GETARG_OID(0); + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + List *refList = cacheEntry->referencingRelationsViaForeignKey; + + /* create a function context for cross-call persistence */ + functionContext = SRF_FIRSTCALL_INIT(); + + foreignRelationCell = list_head(refList); + functionContext->user_fctx = foreignRelationCell; + } + + /* + * On every call to this function, we get the current position in the + * statement list. We then iterate to the next position in the list and + * return the current statement, if we have not yet reached the end of + * list. + */ + functionContext = SRF_PERCALL_SETUP(); + + foreignRelationCell = (ListCell *) functionContext->user_fctx; + if (foreignRelationCell != NULL) + { + Oid refId = lfirst_oid(foreignRelationCell); + + functionContext->user_fctx = lnext(foreignRelationCell); + + SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId)); + } + else + { + SRF_RETURN_DONE(functionContext); + } +} + + +/* + * get_referenced_relation_id_list returns the list of table oids that is referenced + * by given oid recursively. It uses the list cached in the distributed table cache + * entry. + */ +Datum +get_referenced_relation_id_list(PG_FUNCTION_ARGS) +{ + FuncCallContext *functionContext = NULL; + ListCell *foreignRelationCell = NULL; + + CheckCitusVersion(ERROR); + + /* for the first we call this UDF, we need to populate the result to return set */ + if (SRF_IS_FIRSTCALL()) + { + Oid relationId = PG_GETARG_OID(0); + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + List *refList = cacheEntry->referencedRelationsViaForeignKey; + + /* create a function context for cross-call persistence */ + functionContext = SRF_FIRSTCALL_INIT(); + + foreignRelationCell = list_head(refList); + functionContext->user_fctx = foreignRelationCell; + } + + /* + * On every call to this function, we get the current position in the + * statement list. We then iterate to the next position in the list and + * return the current statement, if we have not yet reached the end of + * list. + */ + functionContext = SRF_PERCALL_SETUP(); + + foreignRelationCell = (ListCell *) functionContext->user_fctx; + if (foreignRelationCell != NULL) + { + Oid refId = lfirst_oid(foreignRelationCell); + + functionContext->user_fctx = lnext(foreignRelationCell); + + SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId)); + } + else + { + SRF_RETURN_DONE(functionContext); + } +} diff --git a/src/backend/distributed/utils/foreign_key_relationship.c b/src/backend/distributed/utils/foreign_key_relationship.c new file mode 100644 index 000000000..6ddb58f09 --- /dev/null +++ b/src/backend/distributed/utils/foreign_key_relationship.c @@ -0,0 +1,445 @@ +/*------------------------------------------------------------------------- + * + * foreign_key_relationship.c + * This file contains functions for creating foreign key relationship graph + * between distributed tables. Created relationship graph will be hold by + * a static variable defined in this file until an invalidation comes in. + * + * Copyright (c) 2018, Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/stratnum.h" +#include "catalog/pg_constraint.h" +#include "distributed/foreign_key_relationship.h" +#include "distributed/hash_helpers.h" +#include "distributed/listutils.h" +#include "distributed/version_compat.h" +#include "nodes/pg_list.h" +#include "storage/lockdefs.h" +#include "utils/fmgroids.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" + + +/* + * ForeignConstraintRelationshipGraph holds the graph data structure for foreign constraint relationship + * between relations. We will only have single static instance of that struct and it + * will be invalidated after change on any foreign constraint. + */ +typedef struct ForeignConstraintRelationshipGraph +{ + HTAB *nodeMap; + bool isValid; +}ForeignConstraintRelationshipGraph; + +/* + * ForeignConstraintRelationshipNode holds the data for each node of the ForeignConstraintRelationshipGraph + * For each node we have relation id, which is the Oid of that relation, visiting + * information for that node in the latest DFS and the list of adjacency nodes. + * Note that we also hold back adjacency nodes for getting referenced node over + * that one. + */ +typedef struct ForeignConstraintRelationshipNode +{ + Oid relationId; + bool visited; + List *adjacencyList; + List *backAdjacencyList; +}ForeignConstraintRelationshipNode; + + +/* + * ForeignConstraintRelationshipEdge will only be used while creating the ForeignConstraintRelationshipGraph. + * It won't show edge information on the graph, yet will be used in the pre-processing + * phase. + */ +typedef struct ForeignConstraintRelationshipEdge +{ + Oid referencingRelationOID; + Oid referencedRelationOID; +}ForeignConstraintRelationshipEdge; + + +static ForeignConstraintRelationshipGraph *fConstraintRelationshipGraph = NULL; + +static void CreateForeignConstraintRelationshipGraph(void); +static void PopulateAdjacencyLists(void); +static int CompareForeignConstraintRelationshipEdges(const void *leftElement, const + void *rightElement); +static void AddForeignConstraintRelationshipEdge(Oid referencingOid, Oid referencedOid); +static ForeignConstraintRelationshipNode * CreateOrFindNode(HTAB *adjacencyLists, Oid + relid); +static void GetConnectedListHelper(ForeignConstraintRelationshipNode *node, + List **adjacentNodeList, bool + isReferencing); +static List * GetForeignConstraintRelationshipHelper(Oid relationId, bool isReferencing); + + +/* + * ReferencedRelationIdList is a wrapper function around GetForeignConstraintRelationshipHelper + * to get list of relation IDs which are referenced by the given relation id. + * + * Note that, if relation A is referenced by relation B and relation B is referenced + * by relation C, then the result list for relation A consists of the relation + * IDs of relation B and relation C. + */ +List * +ReferencedRelationIdList(Oid relationId) +{ + return GetForeignConstraintRelationshipHelper(relationId, false); +} + + +/* + * ReferencingRelationIdList is a wrapper function around GetForeignConstraintRelationshipHelper + * to get list of relation IDs which are referencing by the given relation id. + * + * Note that, if relation A is referenced by relation B and relation B is referenced + * by relation C, then the result list for relation C consists of the relation + * IDs of relation A and relation B. + */ +List * +ReferencingRelationIdList(Oid relationId) +{ + return GetForeignConstraintRelationshipHelper(relationId, true); +} + + +/* + * GetForeignConstraintRelationshipHelper returns the list of oids referenced or + * referencing given relation id. It is a helper function for providing results + * to public functions ReferencedRelationIdList and ReferencingRelationIdList. + */ +static List * +GetForeignConstraintRelationshipHelper(Oid relationId, bool isReferencing) +{ + List *foreignConstraintList = NIL; + List *foreignNodeList = NIL; + ListCell *nodeCell = NULL; + bool isFound = false; + ForeignConstraintRelationshipNode *relationNode = NULL; + + CreateForeignConstraintRelationshipGraph(); + + relationNode = (ForeignConstraintRelationshipNode *) hash_search( + fConstraintRelationshipGraph->nodeMap, &relationId, + HASH_FIND, &isFound); + + if (!isFound) + { + /* + * If there is no node with the given relation id, that means given table + * is not referencing and is not referenced by any table + */ + return NIL; + } + + GetConnectedListHelper(relationNode, &foreignNodeList, isReferencing); + + /* + * We need only their OIDs, we get back node list to make their visited + * variable to false for using them iteratively. + */ + foreach(nodeCell, foreignNodeList) + { + ForeignConstraintRelationshipNode *currentNode = + (ForeignConstraintRelationshipNode *) lfirst(nodeCell); + + foreignConstraintList = lappend_oid(foreignConstraintList, + currentNode->relationId); + currentNode->visited = false; + } + + /* set to false separately, since we don't add itself to foreign node list */ + relationNode->visited = false; + + return foreignConstraintList; +} + + +/* + * CreateForeignConstraintRelationshipGraph creates the foreign constraint relation graph using + * foreign constraint provided by pg_constraint metadata table. + */ +static void +CreateForeignConstraintRelationshipGraph() +{ + MemoryContext oldContext; + MemoryContext fConstraintRelationshipMemoryContext = NULL; + HASHCTL info; + uint32 hashFlags = 0; + + /* if we have already created the graph, use it */ + if (IsForeignConstraintRelationshipGraphValid()) + { + return; + } + + ClearForeignConstraintRelationshipGraphContext(); + + fConstraintRelationshipMemoryContext = AllocSetContextCreateExtended( + CacheMemoryContext, + "Forign Constraint Relationship Graph Context", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + oldContext = MemoryContextSwitchTo(fConstraintRelationshipMemoryContext); + + fConstraintRelationshipGraph = (ForeignConstraintRelationshipGraph *) palloc( + sizeof(ForeignConstraintRelationshipGraph)); + fConstraintRelationshipGraph->isValid = false; + + /* create (oid) -> [ForeignConstraintRelationshipNode] hash */ + memset(&info, 0, sizeof(info)); + info.keysize = sizeof(Oid); + info.entrysize = sizeof(ForeignConstraintRelationshipNode); + info.hash = oid_hash; + info.hcxt = CurrentMemoryContext; + hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + fConstraintRelationshipGraph->nodeMap = hash_create( + "foreign key relationship map (oid)", + 32, &info, hashFlags); + + PopulateAdjacencyLists(); + + fConstraintRelationshipGraph->isValid = true; + MemoryContextSwitchTo(oldContext); +} + + +/* + * IsForeignConstraintGraphValid check whether there is a valid graph. + */ +bool +IsForeignConstraintRelationshipGraphValid() +{ + if (fConstraintRelationshipGraph != NULL && fConstraintRelationshipGraph->isValid) + { + return true; + } + + return false; +} + + +/* + * SetForeignConstraintGraphInvalid sets the validity of the graph to false. + */ +void +SetForeignConstraintRelationshipGraphInvalid() +{ + if (fConstraintRelationshipGraph != NULL) + { + fConstraintRelationshipGraph->isValid = false; + } +} + + +/* + * GetConnectedListHelper is the function for getting nodes connected (or connecting) to + * the given relation. adjacentNodeList holds the result for recursive calls and + * by changing isReferencing caller function can select connected or connecting + * adjacency list. + * + */ +static void +GetConnectedListHelper(ForeignConstraintRelationshipNode *node, List **adjacentNodeList, + bool isReferencing) +{ + ListCell *nodeCell = NULL; + List *neighbourList = NIL; + + node->visited = true; + + if (isReferencing) + { + neighbourList = node->backAdjacencyList; + } + else + { + neighbourList = node->adjacencyList; + } + + foreach(nodeCell, neighbourList) + { + ForeignConstraintRelationshipNode *neighborNode = + (ForeignConstraintRelationshipNode *) lfirst(nodeCell); + if (neighborNode->visited == false) + { + *adjacentNodeList = lappend(*adjacentNodeList, neighborNode); + GetConnectedListHelper(neighborNode, adjacentNodeList, isReferencing); + } + } +} + + +/* + * PopulateAdjacencyLists gets foreign constraint relationship information from pg_constraint + * metadata table and populates them to the foreign constraint relation graph. + */ +static void +PopulateAdjacencyLists(void) +{ + SysScanDesc scanDescriptor; + HeapTuple tuple; + Relation pgConstraint; + ScanKeyData scanKey[1]; + int scanKeyCount = 1; + + Oid prevReferencingOid = InvalidOid; + Oid prevReferencedOid = InvalidOid; + List *frelEdgeList = NIL; + ListCell *frelEdgeCell = NULL; + + pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ, + CharGetDatum(CONSTRAINT_FOREIGN)); + scanDescriptor = systable_beginscan(pgConstraint, InvalidOid, false, + NULL, scanKeyCount, scanKey); + + while (HeapTupleIsValid(tuple = systable_getnext(scanDescriptor))) + { + Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(tuple); + ForeignConstraintRelationshipEdge *currentFConstraintRelationshipEdge = NULL; + + currentFConstraintRelationshipEdge = palloc( + sizeof(ForeignConstraintRelationshipEdge)); + currentFConstraintRelationshipEdge->referencingRelationOID = + constraintForm->conrelid; + currentFConstraintRelationshipEdge->referencedRelationOID = + constraintForm->confrelid; + + frelEdgeList = lappend(frelEdgeList, currentFConstraintRelationshipEdge); + } + + /* + * Since there is no index on columns we are planning to sort tuples + * sorting tuples manually instead of using scan keys + */ + frelEdgeList = SortList(frelEdgeList, CompareForeignConstraintRelationshipEdges); + + foreach(frelEdgeCell, frelEdgeList) + { + ForeignConstraintRelationshipEdge *currentFConstraintRelationshipEdge = + (ForeignConstraintRelationshipEdge *) lfirst(frelEdgeCell); + + /* we just saw this edge, no need to add it twice */ + if (currentFConstraintRelationshipEdge->referencingRelationOID == + prevReferencingOid && + currentFConstraintRelationshipEdge->referencedRelationOID == + prevReferencedOid) + { + continue; + } + + AddForeignConstraintRelationshipEdge( + currentFConstraintRelationshipEdge->referencingRelationOID, + currentFConstraintRelationshipEdge-> + referencedRelationOID); + + prevReferencingOid = currentFConstraintRelationshipEdge->referencingRelationOID; + prevReferencedOid = currentFConstraintRelationshipEdge->referencedRelationOID; + } + + systable_endscan(scanDescriptor); + heap_close(pgConstraint, AccessShareLock); +} + + +/* + * CompareForeignConstraintRelationshipEdges is a helper function to compare two + * ForeignConstraintRelationshipEdge using referencing and referenced ids respectively. + */ +static int +CompareForeignConstraintRelationshipEdges(const void *leftElement, const + void *rightElement) +{ + const ForeignConstraintRelationshipEdge *leftEdge = *((const + ForeignConstraintRelationshipEdge + **) leftElement); + const ForeignConstraintRelationshipEdge *rightEdge = *((const + ForeignConstraintRelationshipEdge + **) rightElement); + + int referencingDiff = leftEdge->referencingRelationOID - + rightEdge->referencingRelationOID; + int referencedDiff = leftEdge->referencedRelationOID - + rightEdge->referencedRelationOID; + + if (referencingDiff != 0) + { + return referencingDiff; + } + + return referencedDiff; +} + + +/* + * AddForeignConstraintRelationshipEdge adds edge between the nodes having given OIDs + * by adding referenced node to the adjacency list referencing node and adding + * referencing node to the back adjacency list of referenced node. + */ +static void +AddForeignConstraintRelationshipEdge(Oid referencingOid, Oid referencedOid) +{ + ForeignConstraintRelationshipNode *referencingNode = CreateOrFindNode( + fConstraintRelationshipGraph->nodeMap, referencingOid); + ForeignConstraintRelationshipNode *referencedNode = CreateOrFindNode( + fConstraintRelationshipGraph->nodeMap, referencedOid); + + referencingNode->adjacencyList = lappend(referencingNode->adjacencyList, + referencedNode); + referencedNode->backAdjacencyList = lappend(referencedNode->backAdjacencyList, + referencingNode); +} + + +/* + * CreateOrFindNode either gets or adds new node to the foreign constraint relation graph + */ +static ForeignConstraintRelationshipNode * +CreateOrFindNode(HTAB *adjacencyLists, Oid relid) +{ + bool found = false; + ForeignConstraintRelationshipNode *node = + (ForeignConstraintRelationshipNode *) hash_search(adjacencyLists, + &relid, HASH_ENTER, + &found); + + if (!found) + { + node->adjacencyList = NIL; + node->backAdjacencyList = NIL; + node->visited = false; + } + + return node; +} + + +/* + * ClearForeignConstraintRelationshipGraphContext clear all the allocated memory obtained + * for foreign constraint relationship graph. Since all the variables of relationship + * graph was obtained within the same context, destroying hash map is enough as + * it deletes the context. + */ +void +ClearForeignConstraintRelationshipGraphContext() +{ + if (fConstraintRelationshipGraph == NULL) + { + return; + } + + hash_destroy(fConstraintRelationshipGraph->nodeMap); + fConstraintRelationshipGraph = NULL; +} diff --git a/src/backend/distributed/utils/metadata_cache.c b/src/backend/distributed/utils/metadata_cache.c index 999efa28f..bb27aad66 100644 --- a/src/backend/distributed/utils/metadata_cache.c +++ b/src/backend/distributed/utils/metadata_cache.c @@ -29,6 +29,7 @@ #include "distributed/colocation_utils.h" #include "distributed/connection_management.h" #include "distributed/citus_ruleutils.h" +#include "distributed/foreign_key_relationship.h" #include "distributed/master_metadata_utility.h" #include "distributed/metadata_cache.h" #include "distributed/multi_logical_optimizer.h" @@ -44,6 +45,8 @@ #include "distributed/worker_protocol.h" #include "executor/executor.h" #include "nodes/makefuncs.h" +#include "nodes/memnodes.h" +#include "nodes/pg_list.h" #include "parser/parse_func.h" #include "parser/parse_type.h" #include "storage/lmgr.h" @@ -55,6 +58,7 @@ #include "utils/inval.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" +#include "utils/palloc.h" #include "utils/rel.h" #include "utils/relfilenodemap.h" #include "utils/relmapper.h" @@ -182,11 +186,13 @@ static bool HasOverlappingShardInterval(ShardInterval **shardIntervalArray, static void InitializeCaches(void); static void InitializeDistTableCache(void); static void InitializeWorkerNodeCache(void); +static void RegisterForeignKeyGraphCacheCallbacks(void); static void RegisterWorkerNodeCacheCallbacks(void); static void RegisterLocalGroupIdCacheCallbacks(void); static uint32 WorkerNodeHashCode(const void *key, Size keySize); static void ResetDistTableCacheEntry(DistTableCacheEntry *cacheEntry); static void CreateDistTableCache(void); +static void InvalidateForeignRelationGraphCacheCallback(Datum argument, Oid relationId); static void InvalidateDistRelationCacheCallback(Datum argument, Oid relationId); static void InvalidateNodeRelationCacheCallback(Datum argument, Oid relationId); static void InvalidateLocalGroupIdRelationCacheCallback(Datum argument, Oid relationId); @@ -204,6 +210,7 @@ static ShardPlacement * ResolveGroupShardPlacement( GroupShardPlacement *groupShardPlacement, ShardCacheEntry *shardEntry); static WorkerNode * LookupNodeForGroup(uint32 groupid); static Oid LookupEnumValueId(Oid typeId, char *valueName); +static void InvalidateEntireDistCache(void); /* exports for SQL callable functions */ @@ -981,6 +988,15 @@ BuildDistTableCacheEntry(DistTableCacheEntry *cacheEntry) cacheEntry->hashFunction = NULL; } + oldContext = MemoryContextSwitchTo(CacheMemoryContext); + + cacheEntry->referencedRelationsViaForeignKey = ReferencedRelationIdList( + cacheEntry->relationId); + cacheEntry->referencingRelationsViaForeignKey = ReferencingRelationIdList( + cacheEntry->relationId); + + MemoryContextSwitchTo(oldContext); + heap_close(pgDistPartition, NoLock); } @@ -2510,6 +2526,7 @@ InitializeCaches(void) } InitializeDistTableCache(); + RegisterForeignKeyGraphCacheCallbacks(); RegisterWorkerNodeCacheCallbacks(); RegisterLocalGroupIdCacheCallbacks(); } @@ -2701,6 +2718,19 @@ InitializeWorkerNodeCache(void) } +/* + * RegisterForeignKeyGraphCacheCallbacks registers callbacks required for + * the foreign key graph cache. + */ +static void +RegisterForeignKeyGraphCacheCallbacks(void) +{ + /* Watch for invalidation events. */ + CacheRegisterRelcacheCallback(InvalidateForeignRelationGraphCacheCallback, + (Datum) 0); +} + + /* * RegisterWorkerNodeCacheCallbacks registers the callbacks required for the * worker node cache. It's separate from InitializeWorkerNodeCache so the @@ -2905,6 +2935,16 @@ ResetDistTableCacheEntry(DistTableCacheEntry *cacheEntry) pfree(cacheEntry->arrayOfPlacementArrays); cacheEntry->arrayOfPlacementArrays = NULL; } + if (cacheEntry->referencedRelationsViaForeignKey) + { + list_free(cacheEntry->referencedRelationsViaForeignKey); + cacheEntry->referencedRelationsViaForeignKey = NIL; + } + if (cacheEntry->referencingRelationsViaForeignKey) + { + list_free(cacheEntry->referencingRelationsViaForeignKey); + cacheEntry->referencingRelationsViaForeignKey = NIL; + } cacheEntry->shardIntervalArrayLength = 0; cacheEntry->hasUninitializedShardInterval = false; @@ -2913,6 +2953,46 @@ ResetDistTableCacheEntry(DistTableCacheEntry *cacheEntry) } +/* + * InvalidateForeignRelationGraphCacheCallback invalidates the foreign key relation + * graph and entire distributed cache entries. + */ +static void +InvalidateForeignRelationGraphCacheCallback(Datum argument, Oid relationId) +{ + /* when invalidation happens simply set the LocalGroupId to the default value */ + if (relationId == MetadataCache.distColocationRelationId) + { + SetForeignConstraintRelationshipGraphInvalid(); + InvalidateEntireDistCache(); + } +} + + +/* + * InvalidateForeignKeyGraph is used to invalidate the cached foreign key + * graph (see ForeignKeyRelationGraph @ utils/foreign_key_relationship.c). + * + * To invalidate the foreign key graph, we hack around relcache invalidation + * callbacks. Given that there is no metadata table associated with the foreign + * key graph cache, we use pg_dist_colocation, which is never invalidated for + * other purposes. + * + * We acknowledge that it is not a very intiutive way of implementing this cache + * invalidation, but, seems acceptable for now. If this becomes problematic, we + * could try using a magic oid where we're sure that no relation would ever use + * that oid. + */ +void +InvalidateForeignKeyGraph(void) +{ + CitusInvalidateRelcacheByRelid(DistColocationRelationId()); + + /* bump command counter, to force invalidation to take effect */ + CommandCounterIncrement(); +} + + /* * InvalidateDistRelationCacheCallback flushes cache entries when a relation * is updated (or flushes the entire cache). @@ -2923,21 +3003,14 @@ InvalidateDistRelationCacheCallback(Datum argument, Oid relationId) /* invalidate either entire cache or a specific entry */ if (relationId == InvalidOid) { - DistTableCacheEntry *cacheEntry = NULL; - HASH_SEQ_STATUS status; - - hash_seq_init(&status, DistTableCacheHash); - - while ((cacheEntry = (DistTableCacheEntry *) hash_seq_search(&status)) != NULL) - { - cacheEntry->isValid = false; - } + InvalidateEntireDistCache(); } else { void *hashKey = (void *) &relationId; bool foundInCache = false; + DistTableCacheEntry *cacheEntry = hash_search(DistTableCacheHash, hashKey, HASH_FIND, &foundInCache); if (foundInCache) @@ -2958,6 +3031,24 @@ InvalidateDistRelationCacheCallback(Datum argument, Oid relationId) } +/* + * InvalidateEntireDistCache makes entire cache entries invalid. + */ +static void +InvalidateEntireDistCache() +{ + DistTableCacheEntry *cacheEntry = NULL; + HASH_SEQ_STATUS status; + + hash_seq_init(&status, DistTableCacheHash); + + while ((cacheEntry = (DistTableCacheEntry *) hash_seq_search(&status)) != NULL) + { + cacheEntry->isValid = false; + } +} + + /* * FlushDistTableCache flushes the entire distributed relation cache, frees * all entries, and recreates the cache. diff --git a/src/include/distributed/foreign_constraint.h b/src/include/distributed/foreign_constraint.h index f12921157..732f22bf3 100644 --- a/src/include/distributed/foreign_constraint.h +++ b/src/include/distributed/foreign_constraint.h @@ -12,6 +12,7 @@ #include "postgres.h" #include "postgres_ext.h" #include "utils/relcache.h" +#include "utils/hsearch.h" #include "nodes/primnodes.h" extern bool ConstraintIsAForeignKeyToReferenceTable(char *constraintName, @@ -25,5 +26,7 @@ extern bool ColumnAppearsInForeignKeyToReferenceTable(char *columnName, Oid extern List * GetTableForeignConstraintCommands(Oid relationId); extern bool HasForeignKeyToReferenceTable(Oid relationId); extern bool TableReferenced(Oid relationId); +extern bool TableReferencing(Oid relationId); +extern bool ConstraintIsAForeignKey(char *constraintName, Oid relationId); #endif diff --git a/src/include/distributed/foreign_key_relationship.h b/src/include/distributed/foreign_key_relationship.h new file mode 100644 index 000000000..2e1ebc9cc --- /dev/null +++ b/src/include/distributed/foreign_key_relationship.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * foreign_key_relationship.h + * + * Copyright (c) 2018, Citus Data, Inc. + * + *------------------------------------------------------------------------- + */ + +#ifndef FOREIGN_KEY_RELATIONSHIP_H +#define FOREIGN_KEY_RELATIONSHIP_H + +#include "postgres.h" +#include "postgres_ext.h" +#include "utils/relcache.h" +#include "utils/hsearch.h" +#include "nodes/primnodes.h" + +extern List * ReferencedRelationIdList(Oid relationId); +extern List * ReferencingRelationIdList(Oid relationId); +extern void SetForeignConstraintRelationshipGraphInvalid(void); +extern bool IsForeignConstraintRelationshipGraphValid(void); +extern void ClearForeignConstraintRelationshipGraphContext(void); + +#endif diff --git a/src/include/distributed/metadata_cache.h b/src/include/distributed/metadata_cache.h index c30aece12..a37229e15 100644 --- a/src/include/distributed/metadata_cache.h +++ b/src/include/distributed/metadata_cache.h @@ -69,6 +69,19 @@ typedef struct FmgrInfo *shardIntervalCompareFunction; FmgrInfo *hashFunction; /* NULL if table is not distributed by hash */ + /* + * The following two lists consists of relationIds that this distributed + * relation has a foreign key to (e.g., referencedRelationsViaForeignKey) or + * other relations has a foreign key to to this relation (e.g., + * referencingRelationsViaForeignKey). + * + * Note that we're keeping all transitive foreign key references as well + * such that if relation A refers to B, and B refers to C, we keep A and B + * in C's referencingRelationsViaForeignKey. + */ + List *referencedRelationsViaForeignKey; + List *referencingRelationsViaForeignKey; + /* pg_dist_placement metadata */ GroupShardPlacement **arrayOfPlacementArrays; int *arrayOfPlacementArrayLengths; @@ -89,6 +102,7 @@ extern List * DistTableOidList(void); extern List * ShardPlacementList(uint64 shardId); extern void CitusInvalidateRelcacheByRelid(Oid relationId); extern void CitusInvalidateRelcacheByShardId(int64 shardId); +extern void InvalidateForeignKeyGraph(void); extern void FlushDistTableCache(void); extern void InvalidateMetadataSystemCache(void); extern Datum DistNodeMetadata(void); diff --git a/src/test/regress/expected/multi_foreign_key_relation_graph.out b/src/test/regress/expected/multi_foreign_key_relation_graph.out new file mode 100644 index 000000000..dc6b785b1 --- /dev/null +++ b/src/test/regress/expected/multi_foreign_key_relation_graph.out @@ -0,0 +1,620 @@ +SET citus.next_shard_id TO 3000000; +SET citus.shard_replication_factor TO 1; +CREATE SCHEMA fkey_graph; +SET search_path TO 'fkey_graph'; +CREATE FUNCTION get_referencing_relation_id_list(Oid) + RETURNS SETOF Oid + LANGUAGE C STABLE STRICT + AS 'citus', $$get_referencing_relation_id_list$$; +CREATE FUNCTION get_referenced_relation_id_list(Oid) + RETURNS SETOF Oid + LANGUAGE C STABLE STRICT + AS 'citus', $$get_referenced_relation_id_list$$; +-- Simple case with distributed tables +CREATE TABLE dtt1(id int PRIMARY KEY); +SELECT create_distributed_table('dtt1','id'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE dtt2(id int PRIMARY KEY REFERENCES dtt1(id)); +SELECT create_distributed_table('dtt2','id'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE dtt3(id int PRIMARY KEY REFERENCES dtt2(id)); +SELECT create_distributed_table('dtt3','id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt1'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt2'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 +(1 row) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 + dtt2 +(2 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt1'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + dtt2 + dtt3 +(2 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt2'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + dtt3 +(1 row) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + +CREATE TABLE dtt4(id int PRIMARY KEY); +SELECT create_distributed_table('dtt4', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + +ALTER TABLE dtt4 ADD CONSTRAINT dtt4_fkey FOREIGN KEY (id) REFERENCES dtt3(id); +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 + dtt2 + dtt3 +(3 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt1'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt2'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 +(1 row) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 + dtt2 +(2 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt1'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + dtt2 + dtt3 + dtt4 +(3 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt2'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + dtt3 + dtt4 +(2 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + dtt4 +(1 row) + +ALTER TABLE dtt4 DROP CONSTRAINT dtt4_fkey; +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + dtt1 + dtt2 +(2 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + +-- some tests within transction blocks to make sure that +-- cache invalidation works fine +CREATE TABLE test_1 (id int UNIQUE); +CREATE TABLE test_2 (id int UNIQUE); +CREATE TABLE test_3 (id int UNIQUE); +CREATE TABLE test_4 (id int UNIQUE); +CREATE TABLE test_5 (id int UNIQUE); +SELECT create_distributed_Table('test_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_Table('test_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_Table('test_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_Table('test_4', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +SELECT create_distributed_Table('test_5', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE VIEW referential_integrity_summary AS + WITH RECURSIVE referential_integrity_summary(n, table_name, referencing_relations, referenced_relations) AS + ( + SELECT 0,'0','{}'::regclass[],'{}'::regclass[] + UNION ALL + SELECT + n + 1, + 'test_' || n + 1|| '' as table_name, + (SELECT array_agg(get_referencing_relation_id_list::regclass ORDER BY 1) FROM get_referencing_relation_id_list(('test_' || (n +1) ) ::regclass)) as referencing_relations, + (SELECT array_agg(get_referenced_relation_id_list::regclass ORDER BY 1) FROM get_referenced_relation_id_list(('test_' || (n +1) ) ::regclass)) as referenced_by_relations + FROM referential_integrity_summary, pg_class + WHERE + pg_class.relname = ('test_' || (n +1)) + AND n < 5 + ) + SELECT * FROM referential_integrity_summary WHERE n != 0 ORDER BY 1; +-- make sure that invalidation through ALTER TABLE works fine +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} + 3 | test_3 | | + 4 | test_4 | | + 5 | test_5 | | +(5 rows) + + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2,test_3} | + 2 | test_2 | {test_3} | {test_1} + 3 | test_3 | | {test_2,test_1} + 4 | test_4 | | + 5 | test_5 | | +(5 rows) + + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+------------------------+------------------------ + 1 | test_1 | {test_2,test_3,test_4} | + 2 | test_2 | {test_3,test_4} | {test_1} + 3 | test_3 | {test_4} | {test_2,test_1} + 4 | test_4 | | {test_3,test_2,test_1} + 5 | test_5 | | +(5 rows) + + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-------------------------------+------------------------------- + 1 | test_1 | {test_2,test_3,test_4,test_5} | + 2 | test_2 | {test_3,test_4,test_5} | {test_1} + 3 | test_3 | {test_4,test_5} | {test_2,test_1} + 4 | test_4 | {test_5} | {test_3,test_2,test_1} + 5 | test_5 | | {test_4,test_3,test_2,test_1} +(5 rows) + +ROLLBACK; +-- similar test, but slightly different order of creating foreign keys +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} + 3 | test_3 | | + 4 | test_4 | | + 5 | test_5 | | +(5 rows) + + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} + 3 | test_3 | {test_4} | + 4 | test_4 | | {test_3} + 5 | test_5 | | +(5 rows) + + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} + 3 | test_3 | {test_4,test_5} | + 4 | test_4 | {test_5} | {test_3} + 5 | test_5 | | {test_4,test_3} +(5 rows) + + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-------------------------------+------------------------------- + 1 | test_1 | {test_2,test_3,test_4,test_5} | + 2 | test_2 | {test_3,test_4,test_5} | {test_1} + 3 | test_3 | {test_4,test_5} | {test_2,test_1} + 4 | test_4 | {test_5} | {test_3,test_2,test_1} + 5 | test_5 | | {test_4,test_3,test_2,test_1} +(5 rows) + +ROLLBACK; +-- make sure that DROP CONSTRAINT works invalidates the cache correctly +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-------------------------------+------------------------------- + 1 | test_1 | {test_2,test_3,test_4,test_5} | + 2 | test_2 | {test_3,test_4,test_5} | {test_1} + 3 | test_3 | {test_4,test_5} | {test_2,test_1} + 4 | test_4 | {test_5} | {test_3,test_2,test_1} + 5 | test_5 | | {test_4,test_3,test_2,test_1} +(5 rows) + + ALTER TABLE test_3 DROP CONSTRAINT fkey_1; + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} + 3 | test_3 | {test_4,test_5} | + 4 | test_4 | {test_5} | {test_3} + 5 | test_5 | | {test_4,test_3} +(5 rows) + +ROLLBACK; +-- make sure that CREATE TABLE invalidates the cache correctly +DROP TABLE test_1, test_2, test_3, test_4, test_5 CASCADE; +BEGIN; + CREATE TABLE test_1 (id int UNIQUE); + SELECT create_distributed_Table('test_1', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_2 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_1(id)); + SELECT create_distributed_Table('test_2', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} +(2 rows) + + CREATE TABLE test_3 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_2(id)); + SELECT create_distributed_Table('test_3', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2,test_3} | + 2 | test_2 | {test_3} | {test_1} + 3 | test_3 | | {test_2,test_1} +(3 rows) + + CREATE TABLE test_4 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_3(id)); + SELECT create_distributed_Table('test_4', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+------------------------+------------------------ + 1 | test_1 | {test_2,test_3,test_4} | + 2 | test_2 | {test_3,test_4} | {test_1} + 3 | test_3 | {test_4} | {test_2,test_1} + 4 | test_4 | | {test_3,test_2,test_1} +(4 rows) + + CREATE TABLE test_5 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_4(id)); + SELECT create_distributed_Table('test_5', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-------------------------------+------------------------------- + 1 | test_1 | {test_2,test_3,test_4,test_5} | + 2 | test_2 | {test_3,test_4,test_5} | {test_1} + 3 | test_3 | {test_4,test_5} | {test_2,test_1} + 4 | test_4 | {test_5} | {test_3,test_2,test_1} + 5 | test_5 | | {test_4,test_3,test_2,test_1} +(5 rows) + +COMMIT; +-- DROP TABLE works expected +-- re-create the constraints +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-------------------------------+------------------------------- + 1 | test_1 | {test_2,test_3,test_4,test_5} | + 2 | test_2 | {test_3,test_4,test_5} | {test_1} + 3 | test_3 | {test_4,test_5} | {test_2,test_1} + 4 | test_4 | {test_5} | {test_3,test_2,test_1} + 5 | test_5 | | {test_4,test_3,test_2,test_1} +(5 rows) + + DROP TABLE test_3 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to constraint test_4_id_fkey on table test_4 +drop cascades to constraint fkey_1 on table test_4 + SELECT * FROM referential_integrity_summary; + n | table_name | referencing_relations | referenced_relations +---+------------+-----------------------+---------------------- + 1 | test_1 | {test_2} | + 2 | test_2 | | {test_1} +(2 rows) + +ROLLBACK; +-- Test schemas +BEGIN; + CREATE SCHEMA fkey_intermediate_schema_1; + CREATE SCHEMA fkey_intermediate_schema_2; + SET search_path TO fkey_graph, fkey_intermediate_schema_1, fkey_intermediate_schema_2; + CREATE TABLE fkey_intermediate_schema_1.test_6(id int PRIMARY KEY); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_6', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE fkey_intermediate_schema_2.test_7(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_1.test_6(id)); + SELECT create_distributed_table('fkey_intermediate_schema_2.test_7','id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE fkey_intermediate_schema_1.test_8(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_2.test_7(id)); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_8', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + test_7 + test_8 +(2 rows) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + test_8 +(1 row) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + test_6 +(1 row) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + test_6 + test_7 +(2 rows) + + DROP SCHEMA fkey_intermediate_schema_2 CASCADE; +NOTICE: drop cascades to 2 other objects +DETAIL: drop cascades to table test_7 +drop cascades to constraint test_8_id_fkey on table test_8 + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + +ROLLBACK; +BEGIN; + CREATE SCHEMA fkey_intermediate_schema_1; + CREATE SCHEMA fkey_intermediate_schema_2; + SET search_path TO fkey_graph, fkey_intermediate_schema_1, fkey_intermediate_schema_2; + CREATE TABLE fkey_intermediate_schema_1.test_6(id int PRIMARY KEY); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_6', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE fkey_intermediate_schema_2.test_7(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_1.test_6(id)); + SELECT create_distributed_table('fkey_intermediate_schema_2.test_7','id'); + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE fkey_intermediate_schema_1.test_8(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_2.test_7(id)); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_8', 'id'); + create_distributed_table +-------------------------- + +(1 row) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + test_7 + test_8 +(2 rows) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- + test_8 +(1 row) + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + test_6 +(1 row) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- + test_6 + test_7 +(2 rows) + + DROP SCHEMA fkey_intermediate_schema_1 CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table test_6 +drop cascades to constraint test_7_id_fkey on table test_7 +drop cascades to table test_8 + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referencing_relation_id_list +---------------------------------- +(0 rows) + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + get_referenced_relation_id_list +--------------------------------- +(0 rows) + + +ROLLBACK; +SET search_path TO public; +DROP SCHEMA fkey_graph CASCADE; +NOTICE: drop cascades to 12 other objects +DETAIL: drop cascades to function fkey_graph.get_referencing_relation_id_list(oid) +drop cascades to function fkey_graph.get_referenced_relation_id_list(oid) +drop cascades to table fkey_graph.dtt1 +drop cascades to table fkey_graph.dtt2 +drop cascades to table fkey_graph.dtt3 +drop cascades to table fkey_graph.dtt4 +drop cascades to view fkey_graph.referential_integrity_summary +drop cascades to table fkey_graph.test_1 +drop cascades to table fkey_graph.test_2 +drop cascades to table fkey_graph.test_3 +drop cascades to table fkey_graph.test_4 +drop cascades to table fkey_graph.test_5 diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 828c15a08..8d05d0fde 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -228,7 +228,7 @@ test: multi_citus_tools # ---------- # multi_foreign_key tests foreign key push down on distributed tables # ---------- -test: multi_foreign_key +test: multi_foreign_key multi_foreign_key_relation_graph # ---------- # multi_upgrade_reference_table tests for upgrade_reference_table UDF diff --git a/src/test/regress/sql/multi_foreign_key_relation_graph.sql b/src/test/regress/sql/multi_foreign_key_relation_graph.sql new file mode 100644 index 000000000..baaf844d5 --- /dev/null +++ b/src/test/regress/sql/multi_foreign_key_relation_graph.sql @@ -0,0 +1,224 @@ +SET citus.next_shard_id TO 3000000; +SET citus.shard_replication_factor TO 1; + +CREATE SCHEMA fkey_graph; +SET search_path TO 'fkey_graph'; + +CREATE FUNCTION get_referencing_relation_id_list(Oid) + RETURNS SETOF Oid + LANGUAGE C STABLE STRICT + AS 'citus', $$get_referencing_relation_id_list$$; + +CREATE FUNCTION get_referenced_relation_id_list(Oid) + RETURNS SETOF Oid + LANGUAGE C STABLE STRICT + AS 'citus', $$get_referenced_relation_id_list$$; + +-- Simple case with distributed tables +CREATE TABLE dtt1(id int PRIMARY KEY); +SELECT create_distributed_table('dtt1','id'); + +CREATE TABLE dtt2(id int PRIMARY KEY REFERENCES dtt1(id)); +SELECT create_distributed_table('dtt2','id'); + +CREATE TABLE dtt3(id int PRIMARY KEY REFERENCES dtt2(id)); +SELECT create_distributed_table('dtt3','id'); + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt1'::regclass) ORDER BY 1; +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt2'::regclass) ORDER BY 1; +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt1'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt2'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + + +CREATE TABLE dtt4(id int PRIMARY KEY); +SELECT create_distributed_table('dtt4', 'id'); + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + +ALTER TABLE dtt4 ADD CONSTRAINT dtt4_fkey FOREIGN KEY (id) REFERENCES dtt3(id); + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt1'::regclass) ORDER BY 1; +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt2'::regclass) ORDER BY 1; +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; + +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt1'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt2'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + +ALTER TABLE dtt4 DROP CONSTRAINT dtt4_fkey; + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt3'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt3'::regclass) ORDER BY 1; + +SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('dtt4'::regclass) ORDER BY 1; +SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('dtt4'::regclass) ORDER BY 1; + +-- some tests within transction blocks to make sure that +-- cache invalidation works fine +CREATE TABLE test_1 (id int UNIQUE); +CREATE TABLE test_2 (id int UNIQUE); +CREATE TABLE test_3 (id int UNIQUE); +CREATE TABLE test_4 (id int UNIQUE); +CREATE TABLE test_5 (id int UNIQUE); + +SELECT create_distributed_Table('test_1', 'id'); +SELECT create_distributed_Table('test_2', 'id'); +SELECT create_distributed_Table('test_3', 'id'); +SELECT create_distributed_Table('test_4', 'id'); +SELECT create_distributed_Table('test_5', 'id'); + +CREATE VIEW referential_integrity_summary AS + WITH RECURSIVE referential_integrity_summary(n, table_name, referencing_relations, referenced_relations) AS + ( + SELECT 0,'0','{}'::regclass[],'{}'::regclass[] + UNION ALL + SELECT + n + 1, + 'test_' || n + 1|| '' as table_name, + (SELECT array_agg(get_referencing_relation_id_list::regclass ORDER BY 1) FROM get_referencing_relation_id_list(('test_' || (n +1) ) ::regclass)) as referencing_relations, + (SELECT array_agg(get_referenced_relation_id_list::regclass ORDER BY 1) FROM get_referenced_relation_id_list(('test_' || (n +1) ) ::regclass)) as referenced_by_relations + FROM referential_integrity_summary, pg_class + WHERE + pg_class.relname = ('test_' || (n +1)) + AND n < 5 + ) + SELECT * FROM referential_integrity_summary WHERE n != 0 ORDER BY 1; + +-- make sure that invalidation through ALTER TABLE works fine +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; +ROLLBACK; + +-- similar test, but slightly different order of creating foreign keys +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + SELECT * FROM referential_integrity_summary; +ROLLBACK; + +-- make sure that DROP CONSTRAINT works invalidates the cache correctly +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + ALTER TABLE test_3 DROP CONSTRAINT fkey_1; + SELECT * FROM referential_integrity_summary; +ROLLBACK; + +-- make sure that CREATE TABLE invalidates the cache correctly +DROP TABLE test_1, test_2, test_3, test_4, test_5 CASCADE; + +BEGIN; + CREATE TABLE test_1 (id int UNIQUE); + SELECT create_distributed_Table('test_1', 'id'); + CREATE TABLE test_2 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_1(id)); + SELECT create_distributed_Table('test_2', 'id'); + SELECT * FROM referential_integrity_summary; + CREATE TABLE test_3 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_2(id)); + SELECT create_distributed_Table('test_3', 'id'); + SELECT * FROM referential_integrity_summary; + CREATE TABLE test_4 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_3(id)); + SELECT create_distributed_Table('test_4', 'id'); + SELECT * FROM referential_integrity_summary; + CREATE TABLE test_5 (id int UNIQUE, FOREIGN KEY(id) REFERENCES test_4(id)); + SELECT create_distributed_Table('test_5', 'id'); + SELECT * FROM referential_integrity_summary; +COMMIT; + +-- DROP TABLE works expected +-- re-create the constraints +BEGIN; + ALTER TABLE test_2 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_1(id); + ALTER TABLE test_3 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_2(id); + ALTER TABLE test_4 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_3(id); + ALTER TABLE test_5 ADD CONSTRAINT fkey_1 FOREIGN KEY(id) REFERENCES test_4(id); + SELECT * FROM referential_integrity_summary; + + DROP TABLE test_3 CASCADE; + SELECT * FROM referential_integrity_summary; +ROLLBACK; + +-- Test schemas +BEGIN; + CREATE SCHEMA fkey_intermediate_schema_1; + CREATE SCHEMA fkey_intermediate_schema_2; + SET search_path TO fkey_graph, fkey_intermediate_schema_1, fkey_intermediate_schema_2; + + CREATE TABLE fkey_intermediate_schema_1.test_6(id int PRIMARY KEY); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_6', 'id'); + + CREATE TABLE fkey_intermediate_schema_2.test_7(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_1.test_6(id)); + SELECT create_distributed_table('fkey_intermediate_schema_2.test_7','id'); + + CREATE TABLE fkey_intermediate_schema_1.test_8(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_2.test_7(id)); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_8', 'id'); + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; + + DROP SCHEMA fkey_intermediate_schema_2 CASCADE; + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; +ROLLBACK; + +BEGIN; + CREATE SCHEMA fkey_intermediate_schema_1; + CREATE SCHEMA fkey_intermediate_schema_2; + SET search_path TO fkey_graph, fkey_intermediate_schema_1, fkey_intermediate_schema_2; + + CREATE TABLE fkey_intermediate_schema_1.test_6(id int PRIMARY KEY); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_6', 'id'); + + CREATE TABLE fkey_intermediate_schema_2.test_7(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_1.test_6(id)); + SELECT create_distributed_table('fkey_intermediate_schema_2.test_7','id'); + + CREATE TABLE fkey_intermediate_schema_1.test_8(id int PRIMARY KEY REFERENCES fkey_intermediate_schema_2.test_7(id)); + SELECT create_distributed_table('fkey_intermediate_schema_1.test_8', 'id'); + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_8'::regclass) ORDER BY 1; + + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_6'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_8'::regclass) ORDER BY 1; + + DROP SCHEMA fkey_intermediate_schema_1 CASCADE; + + SELECT get_referencing_relation_id_list::regclass FROM get_referencing_relation_id_list('test_7'::regclass) ORDER BY 1; + SELECT get_referenced_relation_id_list::regclass FROM get_referenced_relation_id_list('test_7'::regclass) ORDER BY 1; + +ROLLBACK; + +SET search_path TO public; +DROP SCHEMA fkey_graph CASCADE; From d83be3a33f7590ac5f3b6bb94333441dc1b008f6 Mon Sep 17 00:00:00 2001 From: Onder Kalaci Date: Wed, 27 Jun 2018 11:36:58 +0300 Subject: [PATCH 08/10] Enforce foreign key restrictions inside transaction blocks When a hash distributed table have a foreign key to a reference table, there are few restrictions we have to apply in order to prevent distributed deadlocks or reading wrong results. The necessity to apply the restrictions arise from cascading nature of foreign keys. When a foreign key on a reference table cascades to a distributed table, a single operation over a single connection can acquire locks on multiple shards of the distributed table. Thus, any parallel operation on that distributed table, in the same transaction should not open parallel connections to the shards. Otherwise, we'd either end-up with a self-distributed deadlock or read wrong results. As briefly described above, the restrictions that we apply is done by tracking the distributed/reference relation accesses inside transaction blocks, and act accordingly when necessary. The two main rules are as follows: - Whenever a parallel distributed relation access conflicts with a consecutive reference relation access, Citus errors out - Whenever a reference relation access is followed by a conflicting parallel relation access, the execution mode is switched to sequential mode. There are also some other notes to mention: - If the user does SET LOCAL citus.multi_shard_modify_mode TO 'sequential';, all the queries should simply work with using one connection per worker and sequentially executing the commands. That's obviously a slower approach than Citus' usual parallel execution. However, we've at least have a way to run all commands successfully. - If an unrelated parallel query executed on any distributed table, we cannot switch to sequential mode. Because, the essense of sequential mode is using one connection per worker. However, in the presence of a parallel connection, the connection manager picks those connections to execute the commands. That contradicts with our purpose, thus we error out. - COPY to a distributed table cannot be executed in sequential mode. Thus, if we switch to sequential mode and COPY is executed, the operation fails and there is currently no way of implementing that. Note that, when the local table is not empty and create_distributed_table is used, citus uses COPY internally. Thus, in those cases, create_distributed_table() will also fail. - There is a GUC called citus.enforce_foreign_key_restrictions to disable all the checks. We added that GUC since the restrictions we apply is sometimes a bit more restrictive than its necessary. The user might want to relax those. Similarly, if you don't have CASCADEing reference tables, you might consider disabling all the checks. --- .../commands/create_distributed_table.c | 21 + src/backend/distributed/commands/multi_copy.c | 3 + .../distributed/executor/multi_utility.c | 25 + src/backend/distributed/shared_library_init.c | 15 + .../transaction/relation_access_tracking.c | 514 +++++++- .../distributed/utils/metadata_cache.c | 2 +- .../distributed/relation_access_tracking.h | 6 + .../foreign_key_restriction_enforcement.out | 1145 +++++++++++++++++ .../foreign_key_to_reference_table.out | 23 +- .../foreign_key_to_reference_table_0.out | 23 +- src/test/regress/multi_schedule | 2 +- .../foreign_key_restriction_enforcement.sql | 577 +++++++++ .../sql/foreign_key_to_reference_table.sql | 5 - 13 files changed, 2327 insertions(+), 34 deletions(-) create mode 100644 src/test/regress/expected/foreign_key_restriction_enforcement.out create mode 100644 src/test/regress/sql/foreign_key_restriction_enforcement.sql diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index b0d006926..17f741190 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -47,6 +47,7 @@ #include "distributed/pg_dist_colocation.h" #include "distributed/pg_dist_partition.h" #include "distributed/reference_table_utils.h" +#include "distributed/relation_access_tracking.h" #include "distributed/remote_commands.h" #include "distributed/worker_protocol.h" #include "distributed/worker_transaction.h" @@ -1120,6 +1121,26 @@ CanUseExclusiveConnections(Oid relationId, bool localTableEmpty) "case, try distributing local tables when they " "are empty."))); } + else if (shouldRunSequential && ParallelQueryExecutedInTransaction()) + { + char *relationName = get_rel_name(relationId); + + /* + * If there has already been a parallel query executed, the sequential mode + * would still use the already opened parallel connections to the workers, + * thus contradicting our purpose of using sequential mode. + */ + ereport(ERROR, (errmsg("cannot distribute relation \"%s\" in this " + "transaction because it has a foreign key to " + "a reference table", relationName), + errdetail("If a hash distributed table has a foreign key " + "to a reference table, it has to be created " + "in sequential mode before any parallel commands " + "have been executed in the same transaction"), + errhint("Try re-running the transaction with " + "\"SET LOCAL citus.multi_shard_modify_mode TO " + "\'sequential\';\""))); + } else if (shouldRunSequential) { return false; diff --git a/src/backend/distributed/commands/multi_copy.c b/src/backend/distributed/commands/multi_copy.c index aa283e013..195d6b157 100644 --- a/src/backend/distributed/commands/multi_copy.c +++ b/src/backend/distributed/commands/multi_copy.c @@ -2324,6 +2324,9 @@ CitusCopyDestReceiverReceive(TupleTableSlot *slot, DestReceiver *dest) /* mark as multi shard to skip doing the same thing over and over */ copyDest->multiShardCopy = true; + /* error out of conflicting COPY */ + CheckConflictingParallelCopyAccesses(relationId); + /* when we see multiple shard connections, we mark COPY as parallel modify */ RecordParallelModifyAccess(relationId); } diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index ff8e5d878..209b0458b 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -49,6 +49,7 @@ #include "distributed/multi_shard_transaction.h" #include "distributed/multi_utility.h" /* IWYU pragma: keep */ #include "distributed/pg_dist_partition.h" +#include "distributed/relation_access_tracking.h" #include "distributed/resource_lock.h" #include "distributed/transaction_management.h" #include "distributed/transmit.h" @@ -4042,5 +4043,29 @@ ShouldExecuteAlterTableSequentially(Oid relationId, AlterTableCmd *command) } } + /* + * If there has already been a parallel query executed, the sequential mode + * would still use the already opened parallel connections to the workers for + * the distributed tables, thus contradicting our purpose of using + * sequential mode. + */ + if (executeSequentially && IsDistributedTable(relationId) && + PartitionMethod(relationId) != DISTRIBUTE_BY_NONE && + ParallelQueryExecutedInTransaction()) + { + char *relationName = get_rel_name(relationId); + + ereport(ERROR, (errmsg("cannot modify table \"%s\" because there " + "was a parallel operation on a distributed table" + "in the transaction", relationName), + errdetail("When there is a foreign key to a reference " + "table, Citus needs to perform all operations " + "over a single connection per node to ensure " + "consistency."), + errhint("Try re-running the transaction with " + "\"SET LOCAL citus.multi_shard_modify_mode TO " + "\'sequential\';\""))); + } + return executeSequentially; } diff --git a/src/backend/distributed/shared_library_init.c b/src/backend/distributed/shared_library_init.c index c0f1474c3..ba493581b 100644 --- a/src/backend/distributed/shared_library_init.c +++ b/src/backend/distributed/shared_library_init.c @@ -38,6 +38,7 @@ #include "distributed/multi_utility.h" #include "distributed/pg_dist_partition.h" #include "distributed/placement_connection.h" +#include "distributed/relation_access_tracking.h" #include "distributed/query_pushdown_planning.h" #include "distributed/query_stats.h" #include "distributed/remote_commands.h" @@ -401,6 +402,20 @@ RegisterCitusConfigVariables(void) GUC_NO_SHOW_ALL, NULL, NULL, NULL); + DefineCustomBoolVariable( + "citus.enforce_foreign_key_restrictions", + gettext_noop("Enforce restrictions while querying distributed/reference " + "tables with foreign keys"), + gettext_noop("When enabled, cascading modifications from reference tables " + "to distributed tables are traced and acted accordingly " + "to avoid creating distributed deadlocks and ensure correctness."), + &EnforceForeignKeyRestrictions, + true, + PGC_USERSET, + GUC_NO_SHOW_ALL, + NULL, NULL, NULL); + + DefineCustomBoolVariable( "citus.subquery_pushdown", gettext_noop("Enables supported subquery pushdown to workers."), diff --git a/src/backend/distributed/transaction/relation_access_tracking.c b/src/backend/distributed/transaction/relation_access_tracking.c index 3c0d495a5..25dcab3c0 100644 --- a/src/backend/distributed/transaction/relation_access_tracking.c +++ b/src/backend/distributed/transaction/relation_access_tracking.c @@ -20,14 +20,27 @@ #include "access/xact.h" #include "distributed/colocation_utils.h" #include "distributed/hash_helpers.h" +#include "distributed/multi_executor.h" #include "distributed/multi_join_order.h" #include "distributed/multi_partitioning_utils.h" #include "distributed/metadata_cache.h" #include "distributed/relation_access_tracking.h" #include "utils/hsearch.h" +#include "utils/lsyscache.h" +/* Config variables managed via guc.c */ +bool EnforceForeignKeyRestrictions = true; + #define PARALLEL_MODE_FLAG_OFFSET 3 +#define PARALLEL_ACCESS_MASK (int) (0 | \ + (1 << (PLACEMENT_ACCESS_SELECT + \ + PARALLEL_MODE_FLAG_OFFSET)) | \ + (1 << (PLACEMENT_ACCESS_DML + \ + PARALLEL_MODE_FLAG_OFFSET)) | \ + (1 << (PLACEMENT_ACCESS_DDL + \ + PARALLEL_MODE_FLAG_OFFSET))) + /* * Hash table mapping relations to the @@ -66,6 +79,7 @@ typedef struct RelationAccessHashEntry static HTAB *RelationAccessHash; +/* functions related to access recording */ static void RecordRelationAccess(Oid relationId, ShardPlacementAccessType accessType); static void RecordPlacementAccessToCache(Oid relationId, ShardPlacementAccessType accessType); @@ -76,6 +90,26 @@ static void RecordParallelRelationAccess(Oid relationId, ShardPlacementAccessTyp static void RecordParallelRelationAccessToCache(Oid relationId, ShardPlacementAccessType placementAccess); +/* functions related to access conflict checks */ +static char * PlacementAccessTypeToText(ShardPlacementAccessType accessType); +static void CheckConflictingRelationAccesses(Oid relationId, + ShardPlacementAccessType accessType); +static bool HoldsConflictingLockWithReferencingRelations(Oid relationId, + ShardPlacementAccessType + placementAccess, + Oid *conflictingRelationId, + ShardPlacementAccessType * + conflictingAccessMode); +static void CheckConflictingParallelRelationAccesses(Oid relationId, + ShardPlacementAccessType + accessType); +static bool HoldsConflictingLockWithReferencedRelations(Oid relationId, + ShardPlacementAccessType + placementAccess, + Oid *conflictingRelationId, + ShardPlacementAccessType * + conflictingAccessMode); + /* * Empty RelationAccessHash, without destroying the hash table itself. @@ -131,6 +165,40 @@ AssociatePlacementAccessWithRelation(ShardPlacement *placement, } +/* + * PlacementAccessTypeToText converts ShardPlacementAccessType to + * text representation. + */ +static char * +PlacementAccessTypeToText(ShardPlacementAccessType accessType) +{ + switch (accessType) + { + case PLACEMENT_ACCESS_SELECT: + { + return "SELECT"; + break; + } + + case PLACEMENT_ACCESS_DML: + { + return "DML"; + } + + case PLACEMENT_ACCESS_DDL: + { + return "DDL"; + } + + default: + { + return "None"; + break; + } + } +} + + /* * RecordRelationAccess associates the access to the distributed relation. The * function takes partitioned relations into account as well. @@ -142,6 +210,9 @@ AssociatePlacementAccessWithRelation(ShardPlacement *placement, static void RecordRelationAccess(Oid relationId, ShardPlacementAccessType accessType) { + /* make sure that this is not a conflicting access */ + CheckConflictingRelationAccesses(relationId, accessType); + /* * If a relation is partitioned, record accesses to all of its partitions as well. * We prefer to use PartitionedTableNoLock() because at this point the necessary @@ -374,6 +445,9 @@ RecordParallelRelationAccess(Oid relationId, ShardPlacementAccessType placementA return; } + /* act accordingly if it's a conflicting access */ + CheckConflictingParallelRelationAccesses(relationId, placementAccess); + /* * If a relation is partitioned, record accesses to all of its partitions as well. * We prefer to use PartitionedTableNoLock() because at this point the necessary @@ -434,6 +508,40 @@ RecordParallelRelationAccessToCache(Oid relationId, } +/* + * ParallelQueryExecutedInTransaction returns true if any parallel query + * is executed in the current transaction. + */ +bool +ParallelQueryExecutedInTransaction(void) +{ + HASH_SEQ_STATUS status; + RelationAccessHashEntry *hashEntry; + + if (!ShouldRecordRelationAccess() || RelationAccessHash == NULL) + { + return false; + } + + hash_seq_init(&status, RelationAccessHash); + + hashEntry = (RelationAccessHashEntry *) hash_seq_search(&status); + while (hashEntry != NULL) + { + int relationAccessMode = hashEntry->relationAccessMode; + if ((relationAccessMode & PARALLEL_ACCESS_MASK)) + { + hash_seq_term(&status); + return true; + } + + hashEntry = (RelationAccessHashEntry *) hash_seq_search(&status); + } + + return false; +} + + /* * GetRelationSelectAccessMode is a wrapper around GetRelationAccessMode. */ @@ -524,10 +632,414 @@ GetRelationAccessMode(Oid relationId, ShardPlacementAccessType accessType) bool ShouldRecordRelationAccess() { - if (IsTransactionBlock() || InCoordinatedTransaction()) + if (EnforceForeignKeyRestrictions && + (IsTransactionBlock() || InCoordinatedTransaction())) { return true; } return false; } + + +/* + * CheckConflictingRelationAccesses is mostly a wrapper around + * HoldsConflictingLockWithReferencingRelations(). We're only interested in accesses + * to reference tables that are referenced via a foreign constraint by a + * hash distributed tables. + */ +static void +CheckConflictingRelationAccesses(Oid relationId, ShardPlacementAccessType accessType) +{ + DistTableCacheEntry *cacheEntry = NULL; + Oid conflictingReferencingRelationId = InvalidOid; + ShardPlacementAccessType conflictingAccessType = PLACEMENT_ACCESS_SELECT; + + if (!EnforceForeignKeyRestrictions || !IsDistributedTable(relationId)) + { + return; + } + + cacheEntry = DistributedTableCacheEntry(relationId); + + if (!(cacheEntry->partitionMethod == DISTRIBUTE_BY_NONE && + cacheEntry->referencingRelationsViaForeignKey != NIL)) + { + return; + } + + if (HoldsConflictingLockWithReferencingRelations(relationId, accessType, + &conflictingReferencingRelationId, + &conflictingAccessType)) + { + char *relationName = get_rel_name(relationId); + char *conflictingRelationName = get_rel_name(conflictingReferencingRelationId); + + char *accessTypeText = PlacementAccessTypeToText(accessType); + char *conflictingAccessTypeText = + PlacementAccessTypeToText(conflictingAccessType); + + ereport(ERROR, (errmsg("cannot execute %s on reference relation \"%s\" because " + "there was a parallel %s access to distributed relation " + "\"%s\" in the same transaction", + accessTypeText, relationName, conflictingAccessTypeText, + conflictingRelationName), + errhint("Try re-running the transaction with " + "\"SET LOCAL citus.multi_shard_modify_mode TO " + "\'sequential\';\""))); + } + else if (cacheEntry->referencingRelationsViaForeignKey != NIL && + accessType > PLACEMENT_ACCESS_SELECT) + { + char *relationName = get_rel_name(relationId); + + if (ParallelQueryExecutedInTransaction()) + { + /* + * If there has already been a parallel query executed, the sequential mode + * would still use the already opened parallel connections to the workers, + * thus contradicting our purpose of using sequential mode. + */ + ereport(ERROR, (errmsg("cannot modify reference table \"%s\" because there " + "was a parallel operation on a distributed table", + relationName), + errdetail("When there is a foreign key to a reference " + "table, Citus needs to perform all operations " + "over a single connection per node to ensure " + "consistency."), + errhint("Try re-running the transaction with " + "\"SET LOCAL citus.multi_shard_modify_mode TO " + "\'sequential\';\""))); + } + else if (MultiShardConnectionType == PARALLEL_CONNECTION) + { + /* + * We can still continue with multi-shard queries in sequential mode, so + * set it. + */ + ereport(DEBUG1, (errmsg("switching to sequential query execution mode"), + errdetail( + "Reference relation \"%s\" is modified, which might lead " + "to data inconsistencies or distributed deadlocks via " + "parallel accesses to hash distributed relations due to " + "foreign keys. Any parallel modification to " + "those hash distributed relations in the same " + "transaction can only be executed in sequential query " + "execution mode", relationName))); + + /* + * Switching to sequential mode is admittedly confusing and, could be useless + * and less performant in some cases. However, if we do not switch to + * sequential mode at this point, we'd loose the opportunity to do so + * later when a parallel query is executed on the hash distributed relations + * that are referencing this reference table. + */ + SetLocalMultiShardModifyModeToSequential(); + } + } +} + + +/* + * CheckConflictingParallelRelationAccesses is mostly a wrapper around + * HoldsConflictingLockWithReferencedRelations(). We're only interested in parallel + * accesses to distributed tables that refers reference tables via foreign constraint. + * + */ +static void +CheckConflictingParallelRelationAccesses(Oid relationId, ShardPlacementAccessType + accessType) +{ + DistTableCacheEntry *cacheEntry = NULL; + Oid conflictingReferencingRelationId = InvalidOid; + ShardPlacementAccessType conflictingAccessType = PLACEMENT_ACCESS_SELECT; + + if (!EnforceForeignKeyRestrictions || !IsDistributedTable(relationId)) + { + return; + } + + cacheEntry = DistributedTableCacheEntry(relationId); + if (!(cacheEntry->partitionMethod == DISTRIBUTE_BY_HASH && + cacheEntry->referencedRelationsViaForeignKey != NIL)) + { + return; + } + + if (MultiShardConnectionType == PARALLEL_CONNECTION && + HoldsConflictingLockWithReferencedRelations(relationId, accessType, + &conflictingReferencingRelationId, + &conflictingAccessType)) + { + char *relationName = get_rel_name(relationId); + char *conflictingRelationName = get_rel_name(conflictingReferencingRelationId); + + char *accessTypeText = PlacementAccessTypeToText(accessType); + char *conflictingAccessTypeText = + PlacementAccessTypeToText(conflictingAccessType); + + if (ParallelQueryExecutedInTransaction()) + { + /* + * If there has already been a parallel query executed, the sequential mode + * would still use the already opened parallel connections to the workers, + * thus contradicting our purpose of using sequential mode. + */ + ereport(ERROR, (errmsg("cannot execute parallel %s on relation \"%s\" " + "after %s command on reference relation " + "\"%s\" because there is a foreign key between " + "them and \"%s\" has been accessed in this transaction", + accessTypeText, relationName, + conflictingAccessTypeText, conflictingRelationName, + conflictingRelationName), + errdetail("When there is a foreign key to a reference " + "table, Citus needs to perform all operations " + "over a single connection per node to ensure " + "consistency."), + errhint("Try re-running the transaction with " + "\"SET LOCAL citus.multi_shard_modify_mode TO " + "\'sequential\';\""))); + } + else + { + ereport(DEBUG1, (errmsg("switching to sequential query execution mode"), + errdetail("cannot execute parallel %s on relation \"%s\" " + "after %s command on reference relation " + "\"%s\" because there is a foreign key between " + "them and \"%s\" has been accessed in this transaction", + accessTypeText, relationName, + conflictingAccessTypeText, conflictingRelationName, + conflictingRelationName))); + + SetLocalMultiShardModifyModeToSequential(); + } + } +} + + +/* + * CheckConflictingParallelCopyAccesses is mostly a wrapper around + * HoldsConflictingLockWithReferencedRelations(). We're only interested in parallel + * accesses to distributed tables that refers reference tables via foreign constraint. + * Since COPY cannot be used in sequential mode, we're erroring out. + */ +void +CheckConflictingParallelCopyAccesses(Oid relationId) +{ + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + Oid conflictingReferencingRelationId = InvalidOid; + ShardPlacementAccessType conflictingAccessType = PLACEMENT_ACCESS_SELECT; + + if (!(cacheEntry->partitionMethod == DISTRIBUTE_BY_HASH && + cacheEntry->referencedRelationsViaForeignKey != NIL)) + { + return; + } + + + if (HoldsConflictingLockWithReferencedRelations(relationId, PLACEMENT_ACCESS_DML, + &conflictingReferencingRelationId, + &conflictingAccessType)) + { + char *relationName = get_rel_name(relationId); + char *conflictingRelationName = get_rel_name(conflictingReferencingRelationId); + + char *conflictingAccessTypeText = + PlacementAccessTypeToText(conflictingAccessType); + + ereport(ERROR, (errmsg("cannot execute parallel COPY on relation \"%s\" " + "after %s command on reference relation " + "\"%s\" because there is a foreign key between " + "them and \"%s\" has been modified in this transaction", + relationName, conflictingAccessTypeText, + conflictingRelationName, conflictingRelationName), + errdetail("COPY to a distributed table uses a separate set of " + "connections which will not be able to see the " + "uncommitted changes to the reference table."), + errhint("Perform the COPY in a separate transaction."))); + } +} + + +/* + * HoldsConflictingLockWithReferencedRelations returns true if the input relationId is a + * hash distributed table and it holds any conflicting locks with the reference tables that + * the distributed table has a foreign key to the reference table. + */ +static bool +HoldsConflictingLockWithReferencedRelations(Oid relationId, ShardPlacementAccessType + placementAccess, + Oid *conflictingRelationId, + + ShardPlacementAccessType * + conflictingAccessMode) +{ + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + ListCell *referencedRelationCell = NULL; + + foreach(referencedRelationCell, cacheEntry->referencedRelationsViaForeignKey) + { + Oid referencedRelation = lfirst_oid(referencedRelationCell); + RelationAccessMode selectMode = RELATION_NOT_ACCESSED; + RelationAccessMode dmlMode = RELATION_NOT_ACCESSED; + RelationAccessMode ddlMode = RELATION_NOT_ACCESSED; + + /* we're only interested in foreign keys to reference tables */ + if (PartitionMethod(referencedRelation) != DISTRIBUTE_BY_NONE) + { + continue; + } + + /* + * A select on a reference table could conflict with a DDL + * on a distributed table. + */ + selectMode = GetRelationSelectAccessMode(referencedRelation); + if (placementAccess == PLACEMENT_ACCESS_DDL && + selectMode != RELATION_NOT_ACCESSED) + { + *conflictingRelationId = referencedRelation; + *conflictingAccessMode = PLACEMENT_ACCESS_SELECT; + + return true; + } + + /* + * Both DML and DDL operations on a reference table conflicts with + * any parallel operation on distributed tables. + */ + dmlMode = GetRelationDMLAccessMode(referencedRelation); + if (dmlMode != RELATION_NOT_ACCESSED) + { + *conflictingRelationId = referencedRelation; + *conflictingAccessMode = PLACEMENT_ACCESS_DML; + + return true; + } + + ddlMode = GetRelationDDLAccessMode(referencedRelation); + if (ddlMode != RELATION_NOT_ACCESSED) + { + *conflictingRelationId = referencedRelation; + *conflictingAccessMode = PLACEMENT_ACCESS_DDL; + + return true; + } + } + + return false; +} + + +/* + * HoldsConflictingLockWithReferencingRelations returns true when the input relationId is a + * reference table and it holds any conflicting locks with the distributed tables where + * the distributed table has a foreign key to the reference table. + * + * If returns true, the referencing relation and conflictingAccessMode are also set. + */ +static bool +HoldsConflictingLockWithReferencingRelations(Oid relationId, ShardPlacementAccessType + placementAccess, Oid *conflictingRelationId, + ShardPlacementAccessType * + conflictingAccessMode) +{ + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + ListCell *referencingRelationCell = NULL; + bool holdsConflictingLocks = false; + + Assert(PartitionMethod(relationId) == DISTRIBUTE_BY_NONE); + + foreach(referencingRelationCell, cacheEntry->referencingRelationsViaForeignKey) + { + Oid referencingRelation = lfirst_oid(referencingRelationCell); + + /* + * We're only interested in foreign keys to reference tables from + * hash distributed tables. + */ + if (!IsDistributedTable(referencingRelation) || + PartitionMethod(referencingRelation) != DISTRIBUTE_BY_HASH) + { + continue; + } + + /* + * Rules that we apply: + * - SELECT on a reference might table conflict with + * a previous parallel DDL on a distributed table + * - DML on a reference table might conflict with + * a previous parallel DML or DDL on a distributed + * table + * - DDL on a reference table might conflict with + * a parellel SELECT, DML or DDL on a distributed + * table + */ + if (placementAccess == PLACEMENT_ACCESS_SELECT) + { + RelationAccessMode ddlMode = GetRelationDDLAccessMode(referencingRelation); + + if (ddlMode == RELATION_PARALLEL_ACCESSED) + { + /* SELECT on a distributed table conflicts with DDL / TRUNCATE */ + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_DDL; + } + } + else if (placementAccess == PLACEMENT_ACCESS_DML) + { + RelationAccessMode ddlMode = RELATION_NOT_ACCESSED; + RelationAccessMode dmlMode = GetRelationDMLAccessMode(referencingRelation); + + if (dmlMode == RELATION_PARALLEL_ACCESSED) + { + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_DML; + } + + ddlMode = GetRelationDDLAccessMode(referencingRelation); + if (ddlMode == RELATION_PARALLEL_ACCESSED) + { + /* SELECT on a distributed table conflicts with DDL / TRUNCATE */ + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_DDL; + } + } + else if (placementAccess == PLACEMENT_ACCESS_DDL) + { + RelationAccessMode selectMode = RELATION_NOT_ACCESSED; + RelationAccessMode ddlMode = RELATION_NOT_ACCESSED; + RelationAccessMode dmlMode = RELATION_NOT_ACCESSED; + + selectMode = GetRelationSelectAccessMode(referencingRelation); + if (selectMode == RELATION_PARALLEL_ACCESSED) + { + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_SELECT; + } + + dmlMode = GetRelationDMLAccessMode(referencingRelation); + if (dmlMode == RELATION_PARALLEL_ACCESSED) + { + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_DML; + } + + ddlMode = GetRelationDDLAccessMode(referencingRelation); + if (ddlMode == RELATION_PARALLEL_ACCESSED) + { + holdsConflictingLocks = true; + *conflictingAccessMode = PLACEMENT_ACCESS_DDL; + } + } + + if (holdsConflictingLocks) + { + *conflictingRelationId = referencingRelation; + + return true; + } + } + + return false; +} diff --git a/src/backend/distributed/utils/metadata_cache.c b/src/backend/distributed/utils/metadata_cache.c index bb27aad66..5cdfc64ba 100644 --- a/src/backend/distributed/utils/metadata_cache.c +++ b/src/backend/distributed/utils/metadata_cache.c @@ -2988,7 +2988,7 @@ InvalidateForeignKeyGraph(void) { CitusInvalidateRelcacheByRelid(DistColocationRelationId()); - /* bump command counter, to force invalidation to take effect */ + /* bump command counter to force invalidation to take effect */ CommandCounterIncrement(); } diff --git a/src/include/distributed/relation_access_tracking.h b/src/include/distributed/relation_access_tracking.h index 9c6eba62a..e3c330322 100644 --- a/src/include/distributed/relation_access_tracking.h +++ b/src/include/distributed/relation_access_tracking.h @@ -13,6 +13,10 @@ #include "distributed/multi_physical_planner.h" /* access Task struct */ #include "distributed/placement_connection.h" +/* Config variables managed via guc.c */ +extern bool EnforceForeignKeyRestrictions; + + /* forward declare, to avoid dependency on ShardPlacement definition */ struct ShardPlacement; @@ -37,6 +41,8 @@ extern RelationAccessMode GetRelationDDLAccessMode(Oid relationId); extern RelationAccessMode GetRelationDMLAccessMode(Oid relationId); extern RelationAccessMode GetRelationSelectAccessMode(Oid relationId); extern bool ShouldRecordRelationAccess(void); +extern void CheckConflictingParallelCopyAccesses(Oid relationId); +extern bool ParallelQueryExecutedInTransaction(void); #endif /* RELATION_ACCESS_TRACKING_H_ */ diff --git a/src/test/regress/expected/foreign_key_restriction_enforcement.out b/src/test/regress/expected/foreign_key_restriction_enforcement.out new file mode 100644 index 000000000..bcc7df753 --- /dev/null +++ b/src/test/regress/expected/foreign_key_restriction_enforcement.out @@ -0,0 +1,1145 @@ +-- +-- Tests multiple commands in transactions where +-- there is foreign key relation between reference +-- tables and distributed tables +-- +CREATE SCHEMA test_fkey_to_ref_in_tx; +SET search_path TO 'test_fkey_to_ref_in_tx'; +SET citus.next_shard_id TO 2380000; +SET citus.next_placement_id TO 2380000; +SET citus.shard_replication_factor TO 1; +CREATE TABLE referece_table(id int PRIMARY KEY); +SELECT create_reference_table('referece_table'); + create_reference_table +------------------------ + +(1 row) + +CREATE TABLE on_update_fkey_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('on_update_fkey_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +CREATE TABLE unrelated_dist_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('unrelated_dist_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +ALTER TABLE on_update_fkey_table ADD CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referece_table(id) ON UPDATE CASCADE; +INSERT INTO referece_table SELECT i FROM generate_series(0, 100) i; +INSERT INTO on_update_fkey_table SELECT i, i % 100 FROM generate_series(0, 1000) i; +INSERT INTO unrelated_dist_table SELECT i, i % 100 FROM generate_series(0, 1000) i; +-- in order to see when the mode automatically swithces to sequential execution +SET client_min_messages TO DEBUG1; +-- case 1.1: SELECT to a reference table is followed by a parallel SELECT to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + SELECT count(*) FROM on_update_fkey_table; + count +------- + 1001 +(1 row) + +ROLLBACK; +-- case 1.2: SELECT to a reference table is followed by a multiple router SELECTs to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE id = 15; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE id = 16; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE id = 17; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE id = 18; + count +------- + 1 +(1 row) + + +ROLLBACK; +-- case 1.3: SELECT to a reference table is followed by a multi-shard UPDATE to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; +ROLLBACK; +-- case 1.4: SELECT to a reference table is followed by a multiple sing-shard UPDATE to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 15; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 16; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 17; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 18; +ROLLBACK; +-- case 1.5: SELECT to a reference table is followed by a DDL that touches fkey column +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE bigint; +DEBUG: rewriting table "on_update_fkey_table" +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +DEBUG: validating foreign key constraint "fkey" +ROLLBACK; +-- case 1.6: SELECT to a reference table is followed by an unrelated DDL +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + ALTER TABLE on_update_fkey_table ADD COLUMN X INT; +DEBUG: switching to sequential query execution mode +DETAIL: cannot execute parallel DDL on relation "on_update_fkey_table" after SELECT command on reference relation "referece_table" because there is a foreign key between them and "referece_table" has been accessed in this transaction +ROLLBACK; +-- case 1.7.1: SELECT to a reference table is followed by a DDL that is on +-- the foreign key column +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + ALTER TABLE on_update_fkey_table DROP COLUMN value_1 CASCADE; +ROLLBACK; +-- case 1.7.2: SELECT to a reference table is followed by a DDL that is on +-- the foreign key column after a parallel query has been executed +BEGIN; + SELECT count(*) FROM unrelated_dist_table; + count +------- + 1001 +(1 row) + + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + ALTER TABLE on_update_fkey_table DROP COLUMN value_1 CASCADE; +ERROR: cannot modify table "on_update_fkey_table" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 1.7.3: SELECT to a reference table is followed by a DDL that is not on +-- the foreign key column, and a parallel query has already been executed +BEGIN; + SELECT count(*) FROM unrelated_dist_table; + count +------- + 1001 +(1 row) + + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + ALTER TABLE on_update_fkey_table ADD COLUMN X INT; +ERROR: cannot execute parallel DDL on relation "on_update_fkey_table" after SELECT command on reference relation "referece_table" because there is a foreign key between them and "referece_table" has been accessed in this transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 1.8: SELECT to a reference table is followed by a COPY +BEGIN; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + + COPY on_update_fkey_table FROM STDIN WITH CSV; +ROLLBACK; +-- case 2.1: UPDATE to a reference table is followed by a multi-shard SELECT +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 0 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101; + count +------- + 10 +(1 row) + +ROLLBACK; +-- case 2.2: UPDATE to a reference table is followed by multiple router SELECT +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 99; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 199; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 299; + count +------- + 1 +(1 row) + + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 399; + count +------- + 1 +(1 row) + +ROLLBACK; +-- case 2.3: UPDATE to a reference table is followed by a multi-shard UPDATE +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + UPDATE on_update_fkey_table SET value_1 = 15; +ROLLBACK; +-- case 2.4: UPDATE to a reference table is followed by multiple router UPDATEs +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 1; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 2; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 3; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 4; +ROLLBACK; +-- case 2.5: UPDATE to a reference table is followed by a DDL that touches fkey column +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE bigint; +DEBUG: rewriting table "on_update_fkey_table" +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +DEBUG: validating foreign key constraint "fkey" +ROLLBACK; +-- case 2.6: UPDATE to a reference table is followed by an unrelated DDL +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + ALTER TABLE on_update_fkey_table ADD COLUMN value_1_X INT; +ROLLBACK; +-- case 2.7: UPDATE to a reference table is followed by COPY +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + COPY on_update_fkey_table FROM STDIN WITH CSV; +ERROR: cannot execute parallel COPY on relation "on_update_fkey_table" after DML command on reference relation "referece_table" because there is a foreign key between them and "referece_table" has been modified in this transaction +DETAIL: COPY to a distributed table uses a separate set of connections which will not be able to see the uncommitted changes to the reference table. +HINT: Perform the COPY in a separate transaction. +CONTEXT: COPY on_update_fkey_table, line 2: "1002,99" +ROLLBACK; +-- case 2.8: UPDATE to a reference table is followed by TRUNCATE +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + TRUNCATE on_update_fkey_table; +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +ROLLBACK; +-- case 3.1: an unrelated DDL to a reference table is followed by a real-time SELECT +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + SELECT count(*) FROM on_update_fkey_table; + count +------- + 1001 +(1 row) + +ROLLBACK; +-- case 3.2: DDL that touches fkey column to a reference table is followed by a real-time SELECT +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE int; + SELECT count(*) FROM on_update_fkey_table; + count +------- + 1001 +(1 row) + +ROLLBACK; +-- case 3.3: DDL to a reference table followed by a multi shard UPDATE +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; +ROLLBACK; +-- case 3.4: DDL to a reference table followed by multiple router UPDATEs +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 1; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 2; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 3; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 4; +ROLLBACK; +-- case 3.5: DDL to reference table followed by a DDL to dist table +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +DEBUG: rewriting table "referece_table" +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: validating foreign key constraint "fkey" + CREATE INDEX fkey_test_index_1 ON on_update_fkey_table(value_1); +DEBUG: building index "fkey_test_index_1" on table "on_update_fkey_table" +ROLLBACK; +-- case 4.6: DDL to reference table followed by a DDL to dist table, both touching fkey columns +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +DEBUG: rewriting table "referece_table" +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: validating foreign key constraint "fkey" + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; +DEBUG: rewriting table "on_update_fkey_table" +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +DEBUG: validating foreign key constraint "fkey" +ROLLBACK; +-- case 3.7: DDL to a reference table is followed by COPY +BEGIN; + ALTER TABLE referece_table ADD COLUMN X int; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + COPY on_update_fkey_table FROM STDIN WITH CSV; +ERROR: cannot execute parallel COPY on relation "on_update_fkey_table" after DDL command on reference relation "referece_table" because there is a foreign key between them and "referece_table" has been modified in this transaction +DETAIL: COPY to a distributed table uses a separate set of connections which will not be able to see the uncommitted changes to the reference table. +HINT: Perform the COPY in a separate transaction. +CONTEXT: COPY on_update_fkey_table, line 2: "1002,99" +ROLLBACK; +-- case 3.8: DDL to a reference table is followed by TRUNCATE +BEGIN; + ALTER TABLE referece_table ADD COLUMN X int; +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "referece_table" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode + TRUNCATE on_update_fkey_table; +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +ROLLBACK; +-- case 3.9: DDL to a reference table is followed by TRUNCATE +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +DEBUG: rewriting table "referece_table" +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: validating foreign key constraint "fkey" + TRUNCATE on_update_fkey_table; +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +ROLLBACK; +----- +--- Now, start testing the other way araound +----- +-- case 4.1: SELECT to a dist table is follwed by a SELECT to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 10 +(1 row) + + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + +ROLLBACK; +-- case 4.2: SELECT to a dist table is follwed by a DML to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 10 +(1 row) + + UPDATE referece_table SET id = 101 WHERE id = 99; +ERROR: cannot modify reference table "referece_table" because there was a parallel operation on a distributed table +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 4.3: SELECT to a dist table is follwed by an unrelated DDL to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 10 +(1 row) + + ALTER TABLE referece_table ADD COLUMN X INT; +ERROR: cannot execute DDL on reference relation "referece_table" because there was a parallel SELECT access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 4.4: SELECT to a dist table is follwed by a DDL to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 10 +(1 row) + + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +DEBUG: rewriting table "referece_table" +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: validating foreign key constraint "fkey" +ERROR: cannot execute DDL on reference relation "referece_table" because there was a parallel SELECT access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 4.5: SELECT to a dist table is follwed by a TRUNCATE +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + count +------- + 10 +(1 row) + + TRUNCATE referece_table CASCADE; +NOTICE: truncate cascades to table "on_update_fkey_table" +ERROR: cannot execute DDL on reference relation "referece_table" because there was a parallel SELECT access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 4.6: Router SELECT to a dist table is followed by a TRUNCATE +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE id = 9; + count +------- + 1 +(1 row) + + TRUNCATE referece_table CASCADE; +NOTICE: truncate cascades to table "on_update_fkey_table" +DEBUG: truncate cascades to table "on_update_fkey_table_2380002" +DETAIL: NOTICE from localhost:57638 +DEBUG: truncate cascades to table "on_update_fkey_table_2380004" +DETAIL: NOTICE from localhost:57638 +DEBUG: truncate cascades to table "on_update_fkey_table_2380001" +DETAIL: NOTICE from localhost:57637 +DEBUG: truncate cascades to table "on_update_fkey_table_2380003" +DETAIL: NOTICE from localhost:57637 +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +ROLLBACK; +-- case 5.1: Parallel UPDATE on distributed table follow by a SELECT +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + SELECT count(*) FROM referece_table; + count +------- + 101 +(1 row) + +ROLLBACK; +-- case 5.2: Parallel UPDATE on distributed table follow by a UPDATE +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + UPDATE referece_table SET id = 160 WHERE id = 15; +ERROR: cannot execute DML on reference relation "referece_table" because there was a parallel DML access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 5.3: Parallel UPDATE on distributed table follow by an unrelated DDL on reference table +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + ALTER TABLE referece_table ADD COLUMN X INT; +ERROR: cannot execute DDL on reference relation "referece_table" because there was a parallel DML access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 5.4: Parallel UPDATE on distributed table follow by a related DDL on reference table +-- FIXME: Can we do better? +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +DEBUG: rewriting table "referece_table" +DEBUG: building index "referece_table_pkey" on table "referece_table" +DEBUG: validating foreign key constraint "fkey" +ERROR: cannot perform DDL on placement 2380001, which has been read over multiple connections +ROLLBACK; +-- case 6:1: Unrelated parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + SELECT count(*) FROM referece_table; +ERROR: cannot execute SELECT on reference relation "referece_table" because there was a parallel DDL access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 6:2: Related parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; +DEBUG: rewriting table "on_update_fkey_table" +DEBUG: building index "on_update_fkey_table_pkey" on table "on_update_fkey_table" +DEBUG: validating foreign key constraint "fkey" + UPDATE referece_table SET id = 160 WHERE id = 15; +ROLLBACK; +-- case 6:3: Unrelated parallel DDL on distributed table followed by UPDATE on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + SELECT count(*) FROM referece_table; +ERROR: cannot execute SELECT on reference relation "referece_table" because there was a parallel DDL access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 6:4: Related parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + UPDATE referece_table SET id = 160 WHERE id = 15; +ERROR: cannot execute SELECT on reference relation "referece_table" because there was a parallel DDL access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 6:5: Unrelated parallel DDL on distributed table followed by unrelated DDL on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + ALTER TABLE referece_table ADD COLUMN X int; +ERROR: cannot execute DDL on reference relation "referece_table" because there was a parallel DDL access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- case 6:6: Unrelated parallel DDL on distributed table followed by related DDL on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; +ERROR: cannot modify table "on_update_fkey_table" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- some more extensive tests +-- UPDATE on dist table is followed by DELETE to reference table +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; + DELETE FROM referece_table WHERE id = 99; +ERROR: cannot execute DML on reference relation "referece_table" because there was a parallel DML access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- an unrelated update followed by update on dist table and update +-- on reference table +BEGIN; + UPDATE unrelated_dist_table SET value_1 = 15; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; + UPDATE referece_table SET id = 101 WHERE id = 99; +ERROR: cannot execute DML on reference relation "referece_table" because there was a parallel DML access to distributed relation "on_update_fkey_table" in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" +ROLLBACK; +-- an unrelated update followed by update on the reference table and update +-- on the cascading distributed table +-- note that the UPDATE on the reference table will try to set the execution +-- mode to sequential, which will fail since there is an already opened +-- parallel connections +BEGIN; + UPDATE unrelated_dist_table SET value_1 = 15; + UPDATE referece_table SET id = 101 WHERE id = 99; +ERROR: cannot modify reference table "referece_table" because there was a parallel operation on a distributed table +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; +-- the fails since we're trying to switch sequential mode after +-- already executed a parallel query +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE tt4(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES tt4(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "tt4_pkey" for table "tt4" +DEBUG: building index "tt4_pkey" on table "tt4" + SELECT create_distributed_table('tt4', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES tt4(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +ERROR: cannot distribute relation "test_table_2" in this transaction because it has a foreign key to a reference table +DETAIL: If a hash distributed table has a foreign key to a reference table, it has to be created in sequential mode before any parallel commands have been executed in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_1 CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- same test with the above, but this time using +-- sequential mode, succeeds +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE tt4(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES tt4(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "tt4_pkey" for table "tt4" +DEBUG: building index "tt4_pkey" on table "tt4" + SELECT create_distributed_table('tt4', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES tt4(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; +-- another test with ALTER TABLE fails since we're already opened +-- parallel connection via create_distributed_table(), later +-- adding foreign key to reference table fails +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_1, test_table_2; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- same test with the above on sequential mode should work fine +BEGIN; + + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1, test_table_2; +COMMIT; +-- similar test with the above, but this time the order of +-- create_distributed_table and create_reference_table is +-- changed +BEGIN; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_1 CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- same test in sequential mode should succeed +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; +-- again a very similar test, but this time +-- a parallel SELECT is already executed before +-- setting the mode to sequential should fail +BEGIN; + SELECT count(*) FROM on_update_fkey_table; + count +------- + 1001 +(1 row) + + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +ERROR: cannot distribute relation "test_table_2" in this transaction because it has a foreign key to a reference table +DETAIL: If a hash distributed table has a foreign key to a reference table, it has to be created in sequential mode before any parallel commands have been executed in the same transaction +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" + CREATE TABLE test_table_1(id int PRIMARY KEY); +ERROR: current transaction is aborted, commands ignored until end of transaction block + SELECT create_reference_table('test_table_1'); +ERROR: current transaction is aborted, commands ignored until end of transaction block + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); +ERROR: current transaction is aborted, commands ignored until end of transaction block + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_1 CASCADE; +ERROR: current transaction is aborted, commands ignored until end of transaction block +ROLLBACK; +-- make sure that we cannot create hash distributed tables with +-- foreign keys to reference tables when they have data in it +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "test_table_1" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +NOTICE: Copying data from local table... +DEBUG: Copied 101 rows + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +ERROR: cannot distribute "test_table_2" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- the same test with above in sequential mode would still not work +-- since COPY cannot be executed in sequential mode +BEGIN; + + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +NOTICE: Copying data from local table... +DEBUG: Copied 101 rows + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +ERROR: cannot distribute "test_table_2" in sequential mode because it is not empty +HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', try with 'parallel' option. If that is not the case, try distributing local tables when they are empty. + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; +ERROR: current transaction is aborted, commands ignored until end of transaction block + DROP TABLE test_table_2, test_table_1; +ERROR: current transaction is aborted, commands ignored until end of transaction block +COMMIT; +-- we should be able to execute and DML/DDL/SELECT after we've +-- switched to sequential via create_distributed_table +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_1_pkey" for table "test_table_1" +DEBUG: building index "test_table_1_pkey" on table "test_table_1" + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); +DEBUG: CREATE TABLE / PRIMARY KEY will create implicit index "test_table_2_pkey" for table "test_table_2" +DEBUG: building index "test_table_2_pkey" on table "test_table_2" + SELECT create_reference_table('test_table_1'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: switching to sequential query execution mode +DETAIL: Reference relation "test_table_1" is modified, which might lead to data inconsistencies or distributed deadlocks via parallel accesses to hash distributed relations due to foreign keys. Any parallel modification to those hash distributed relations in the same transaction can only be executed in sequential query execution mode +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_reference_table +------------------------ + +(1 row) + + SELECT create_distributed_table('test_table_2', 'id'); +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57637 +DEBUG: schema "test_fkey_to_ref_in_tx" already exists, skipping +DETAIL: NOTICE from localhost:57638 + create_distributed_table +-------------------------- + +(1 row) + + -- and maybe some other test + CREATE INDEX i1 ON test_table_1(id); +DEBUG: building index "i1" on table "test_table_1" + ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); +DEBUG: verifying table "test_table_2" + SELECT count(*) FROM test_table_2; + count +------- + 0 +(1 row) + + SELECT count(*) FROM test_table_1; + count +------- + 0 +(1 row) + + UPDATE test_table_2 SET value_1 = 15; + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_2, test_table_1; +COMMIT; +RESET client_min_messages; +DROP SCHEMA test_fkey_to_ref_in_tx CASCADE; +NOTICE: drop cascades to 3 other objects +DETAIL: drop cascades to table referece_table +drop cascades to table on_update_fkey_table +drop cascades to table unrelated_dist_table +SET search_path TO public; diff --git a/src/test/regress/expected/foreign_key_to_reference_table.out b/src/test/regress/expected/foreign_key_to_reference_table.out index 093359329..bb3bb4860 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table.out +++ b/src/test/regress/expected/foreign_key_to_reference_table.out @@ -1267,7 +1267,6 @@ DETAIL: drop cascades to constraint test_table_2_id_fkey on table test_table_2 drop cascades to constraint test_table_3_value_1_fkey on table test_table_3 ROLLBACK; -- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); @@ -1284,12 +1283,13 @@ BEGIN; (1 row) ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" DROP TABLE test_table_1, test_table_2; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- the order of create_reference_table and create_distributed_table is changed --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_table_1', 'id'); @@ -1306,7 +1306,9 @@ BEGIN; (1 row) ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_1" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" DROP TABLE test_table_2 CASCADE; ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK; @@ -1331,7 +1333,6 @@ HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', t ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- make sure that other DDLs/DMLs also work fine --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); @@ -1349,9 +1350,7 @@ BEGIN; CREATE INDEX i1 ON test_table_1(id); ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); -ERROR: cannot establish a new connection for placement 7000388, since DDL has been executed on a connection that is in use DROP TABLE test_table_2, test_table_1; -ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- The following tests check if the DDLs affecting foreign keys work as expected -- check if we can drop the foreign constraint @@ -1384,7 +1383,6 @@ SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_tab DROP TABLE test_table_1, test_table_2; -- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); @@ -1401,7 +1399,9 @@ BEGIN; (1 row) ALTER TABLE test_table_2 ADD CONSTRAINT foreign_key FOREIGN KEY(value_1) REFERENCES test_table_1(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; @@ -1770,7 +1770,6 @@ SELECT * FROM test_table_1; DROP TABLE test_table_1, test_table_2; -- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs -- in transaction since the upcoming DDLs need to run sequentially. --- FIXME: fails for now CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); @@ -1795,11 +1794,9 @@ SELECT create_distributed_table('test_table_3', 'id'); BEGIN; ALTER TABLE test_table_2 ADD CONSTRAINT fkey FOREIGN KEY (value_1) REFERENCES test_table_1(id); ALTER TABLE test_table_3 ADD COLUMN test_column int; -ERROR: cannot establish a new connection for placement 7000556, since DDL has been executed on a connection that is in use ALTER TABLE test_table_1 DROP COLUMN id CASCADE; -ERROR: current transaction is aborted, commands ignored until end of transaction block +NOTICE: drop cascades to constraint fkey on table test_table_2 ALTER TABLE test_table_1 ADD COLUMN id int; -ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; DROP TABLE test_table_1, test_table_2, test_table_3; -- NOTE: Postgres does not support foreign keys on partitioned tables currently. diff --git a/src/test/regress/expected/foreign_key_to_reference_table_0.out b/src/test/regress/expected/foreign_key_to_reference_table_0.out index 991c48a1f..d752edfd3 100644 --- a/src/test/regress/expected/foreign_key_to_reference_table_0.out +++ b/src/test/regress/expected/foreign_key_to_reference_table_0.out @@ -1267,7 +1267,6 @@ DETAIL: drop cascades to constraint test_table_2_id_fkey on table test_table_2 drop cascades to constraint test_table_3_value_1_fkey on table test_table_3 ROLLBACK; -- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); @@ -1284,12 +1283,13 @@ BEGIN; (1 row) ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" DROP TABLE test_table_1, test_table_2; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- the order of create_reference_table and create_distributed_table is changed --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_table_1', 'id'); @@ -1306,7 +1306,9 @@ BEGIN; (1 row) ALTER TABLE test_table_1 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_2(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_1" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" DROP TABLE test_table_2 CASCADE; ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK; @@ -1331,7 +1333,6 @@ HINT: If you have manually set citus.multi_shard_modify_mode to 'sequential', t ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- make sure that other DDLs/DMLs also work fine --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); @@ -1349,9 +1350,7 @@ BEGIN; CREATE INDEX i1 ON test_table_1(id); ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); -ERROR: cannot establish a new connection for placement 7000388, since DDL has been executed on a connection that is in use DROP TABLE test_table_2, test_table_1; -ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; -- The following tests check if the DDLs affecting foreign keys work as expected -- check if we can drop the foreign constraint @@ -1384,7 +1383,6 @@ SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_tab DROP TABLE test_table_1, test_table_2; -- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); @@ -1401,7 +1399,9 @@ BEGIN; (1 row) ALTER TABLE test_table_2 ADD CONSTRAINT foreign_key FOREIGN KEY(value_1) REFERENCES test_table_1(id); -ERROR: cannot perform query with placements that were modified over multiple connections +ERROR: cannot modify table "test_table_2" because there was a parallel operation on a distributed tablein the transaction +DETAIL: When there is a foreign key to a reference table, Citus needs to perform all operations over a single connection per node to ensure consistency. +HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';" ALTER TABLE test_table_2 DROP CONSTRAINT test_table_2_value_1_fkey; ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; @@ -1770,7 +1770,6 @@ SELECT * FROM test_table_1; DROP TABLE test_table_1, test_table_2; -- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs -- in transaction since the upcoming DDLs need to run sequentially. --- FIXME: fails for now CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); @@ -1795,11 +1794,9 @@ SELECT create_distributed_table('test_table_3', 'id'); BEGIN; ALTER TABLE test_table_2 ADD CONSTRAINT fkey FOREIGN KEY (value_1) REFERENCES test_table_1(id); ALTER TABLE test_table_3 ADD COLUMN test_column int; -ERROR: cannot establish a new connection for placement 7000556, since DDL has been executed on a connection that is in use ALTER TABLE test_table_1 DROP COLUMN id CASCADE; -ERROR: current transaction is aborted, commands ignored until end of transaction block +NOTICE: drop cascades to constraint fkey on table test_table_2 ALTER TABLE test_table_1 ADD COLUMN id int; -ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; DROP TABLE test_table_1, test_table_2, test_table_3; -- NOTE: Postgres does not support foreign keys on partitioned tables currently. diff --git a/src/test/regress/multi_schedule b/src/test/regress/multi_schedule index 8d05d0fde..9b902a91d 100644 --- a/src/test/regress/multi_schedule +++ b/src/test/regress/multi_schedule @@ -69,7 +69,7 @@ test: multi_null_minmax_value_pruning test: multi_query_directory_cleanup test: multi_task_assignment_policy multi_cross_shard test: multi_utility_statements -test: multi_dropped_column_aliases +test: multi_dropped_column_aliases foreign_key_restriction_enforcement test: multi_binary_master_copy_format # ---------- diff --git a/src/test/regress/sql/foreign_key_restriction_enforcement.sql b/src/test/regress/sql/foreign_key_restriction_enforcement.sql new file mode 100644 index 000000000..20d13b282 --- /dev/null +++ b/src/test/regress/sql/foreign_key_restriction_enforcement.sql @@ -0,0 +1,577 @@ +-- +-- Tests multiple commands in transactions where +-- there is foreign key relation between reference +-- tables and distributed tables +-- + +CREATE SCHEMA test_fkey_to_ref_in_tx; +SET search_path TO 'test_fkey_to_ref_in_tx'; + +SET citus.next_shard_id TO 2380000; +SET citus.next_placement_id TO 2380000; + +SET citus.shard_replication_factor TO 1; + +CREATE TABLE referece_table(id int PRIMARY KEY); +SELECT create_reference_table('referece_table'); + +CREATE TABLE on_update_fkey_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('on_update_fkey_table', 'id'); + +CREATE TABLE unrelated_dist_table(id int PRIMARY KEY, value_1 int); +SELECT create_distributed_table('unrelated_dist_table', 'id'); + +ALTER TABLE on_update_fkey_table ADD CONSTRAINT fkey FOREIGN KEY(value_1) REFERENCES referece_table(id) ON UPDATE CASCADE; + +INSERT INTO referece_table SELECT i FROM generate_series(0, 100) i; + +INSERT INTO on_update_fkey_table SELECT i, i % 100 FROM generate_series(0, 1000) i; +INSERT INTO unrelated_dist_table SELECT i, i % 100 FROM generate_series(0, 1000) i; + +-- in order to see when the mode automatically swithces to sequential execution +SET client_min_messages TO DEBUG1; + +-- case 1.1: SELECT to a reference table is followed by a parallel SELECT to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + SELECT count(*) FROM on_update_fkey_table; +ROLLBACK; + +-- case 1.2: SELECT to a reference table is followed by a multiple router SELECTs to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + SELECT count(*) FROM on_update_fkey_table WHERE id = 15; + SELECT count(*) FROM on_update_fkey_table WHERE id = 16; + SELECT count(*) FROM on_update_fkey_table WHERE id = 17; + SELECT count(*) FROM on_update_fkey_table WHERE id = 18; + +ROLLBACK; + +-- case 1.3: SELECT to a reference table is followed by a multi-shard UPDATE to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; +ROLLBACK; + +-- case 1.4: SELECT to a reference table is followed by a multiple sing-shard UPDATE to a distributed table +BEGIN; + SELECT count(*) FROM referece_table; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 15; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 16; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 17; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE id = 18; +ROLLBACK; + +-- case 1.5: SELECT to a reference table is followed by a DDL that touches fkey column +BEGIN; + SELECT count(*) FROM referece_table; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE bigint; +ROLLBACK; + +-- case 1.6: SELECT to a reference table is followed by an unrelated DDL +BEGIN; + SELECT count(*) FROM referece_table; + ALTER TABLE on_update_fkey_table ADD COLUMN X INT; +ROLLBACK; + +-- case 1.7.1: SELECT to a reference table is followed by a DDL that is on +-- the foreign key column +BEGIN; + SELECT count(*) FROM referece_table; + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + ALTER TABLE on_update_fkey_table DROP COLUMN value_1 CASCADE; +ROLLBACK; + +-- case 1.7.2: SELECT to a reference table is followed by a DDL that is on +-- the foreign key column after a parallel query has been executed +BEGIN; + SELECT count(*) FROM unrelated_dist_table; + SELECT count(*) FROM referece_table; + + ALTER TABLE on_update_fkey_table DROP COLUMN value_1 CASCADE; +ROLLBACK; + +-- case 1.7.3: SELECT to a reference table is followed by a DDL that is not on +-- the foreign key column, and a parallel query has already been executed +BEGIN; + SELECT count(*) FROM unrelated_dist_table; + SELECT count(*) FROM referece_table; + ALTER TABLE on_update_fkey_table ADD COLUMN X INT; +ROLLBACK; + +-- case 1.8: SELECT to a reference table is followed by a COPY +BEGIN; + SELECT count(*) FROM referece_table; + COPY on_update_fkey_table FROM STDIN WITH CSV; +1001,99 +1002,99 +1003,99 +1004,99 +1005,99 +\. +ROLLBACK; + +-- case 2.1: UPDATE to a reference table is followed by a multi-shard SELECT +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101; +ROLLBACK; + +-- case 2.2: UPDATE to a reference table is followed by multiple router SELECT +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 99; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 199; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 299; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 101 AND id = 399; +ROLLBACK; + + +-- case 2.3: UPDATE to a reference table is followed by a multi-shard UPDATE +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + UPDATE on_update_fkey_table SET value_1 = 15; +ROLLBACK; + +-- case 2.4: UPDATE to a reference table is followed by multiple router UPDATEs +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 1; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 2; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 3; + UPDATE on_update_fkey_table SET value_1 = 101 WHERE id = 4; +ROLLBACK; + +-- case 2.5: UPDATE to a reference table is followed by a DDL that touches fkey column +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE bigint; +ROLLBACK; + +-- case 2.6: UPDATE to a reference table is followed by an unrelated DDL +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + ALTER TABLE on_update_fkey_table ADD COLUMN value_1_X INT; +ROLLBACK; + +-- case 2.7: UPDATE to a reference table is followed by COPY +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + COPY on_update_fkey_table FROM STDIN WITH CSV; +1001,99 +1002,99 +1003,99 +1004,99 +1005,99 +\. +ROLLBACK; + + +-- case 2.8: UPDATE to a reference table is followed by TRUNCATE +BEGIN; + UPDATE referece_table SET id = 101 WHERE id = 99; + TRUNCATE on_update_fkey_table; +ROLLBACK; + +-- case 3.1: an unrelated DDL to a reference table is followed by a real-time SELECT +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; + SELECT count(*) FROM on_update_fkey_table; +ROLLBACK; + +-- case 3.2: DDL that touches fkey column to a reference table is followed by a real-time SELECT +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE int; + SELECT count(*) FROM on_update_fkey_table; +ROLLBACK; + + +-- case 3.3: DDL to a reference table followed by a multi shard UPDATE +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; +ROLLBACK; + +-- case 3.4: DDL to a reference table followed by multiple router UPDATEs +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DEFAULT 1001; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 1; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 2; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 3; + UPDATE on_update_fkey_table SET value_1 = 98 WHERE id = 4; +ROLLBACK; + + +-- case 3.5: DDL to reference table followed by a DDL to dist table +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; + CREATE INDEX fkey_test_index_1 ON on_update_fkey_table(value_1); +ROLLBACK; + +-- case 4.6: DDL to reference table followed by a DDL to dist table, both touching fkey columns +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; +ROLLBACK; + +-- case 3.7: DDL to a reference table is followed by COPY +BEGIN; + ALTER TABLE referece_table ADD COLUMN X int; + COPY on_update_fkey_table FROM STDIN WITH CSV; +1001,99 +1002,99 +1003,99 +1004,99 +1005,99 +\. +ROLLBACK; + +-- case 3.8: DDL to a reference table is followed by TRUNCATE +BEGIN; + ALTER TABLE referece_table ADD COLUMN X int; + TRUNCATE on_update_fkey_table; +ROLLBACK; + +-- case 3.9: DDL to a reference table is followed by TRUNCATE +BEGIN; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; + TRUNCATE on_update_fkey_table; +ROLLBACK; + + +----- +--- Now, start testing the other way araound +----- + +-- case 4.1: SELECT to a dist table is follwed by a SELECT to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + SELECT count(*) FROM referece_table; +ROLLBACK; + +-- case 4.2: SELECT to a dist table is follwed by a DML to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + UPDATE referece_table SET id = 101 WHERE id = 99; +ROLLBACK; + +-- case 4.3: SELECT to a dist table is follwed by an unrelated DDL to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + ALTER TABLE referece_table ADD COLUMN X INT; +ROLLBACK; + +-- case 4.4: SELECT to a dist table is follwed by a DDL to a reference table +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +ROLLBACK; + +-- case 4.5: SELECT to a dist table is follwed by a TRUNCATE +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE value_1 = 99; + TRUNCATE referece_table CASCADE; +ROLLBACK; + +-- case 4.6: Router SELECT to a dist table is followed by a TRUNCATE +BEGIN; + SELECT count(*) FROM on_update_fkey_table WHERE id = 9; + TRUNCATE referece_table CASCADE; +ROLLBACK; + +-- case 5.1: Parallel UPDATE on distributed table follow by a SELECT +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + SELECT count(*) FROM referece_table; +ROLLBACK; + +-- case 5.2: Parallel UPDATE on distributed table follow by a UPDATE +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + UPDATE referece_table SET id = 160 WHERE id = 15; +ROLLBACK; + +-- case 5.3: Parallel UPDATE on distributed table follow by an unrelated DDL on reference table +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + ALTER TABLE referece_table ADD COLUMN X INT; +ROLLBACK; + +-- case 5.4: Parallel UPDATE on distributed table follow by a related DDL on reference table +-- FIXME: Can we do better? +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 16 WHERE value_1 = 15; + ALTER TABLE referece_table ALTER COLUMN id SET DATA TYPE smallint; +ROLLBACK; + +-- case 6:1: Unrelated parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + SELECT count(*) FROM referece_table; +ROLLBACK; + +-- case 6:2: Related parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; + UPDATE referece_table SET id = 160 WHERE id = 15; +ROLLBACK; + +-- case 6:3: Unrelated parallel DDL on distributed table followed by UPDATE on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + SELECT count(*) FROM referece_table; +ROLLBACK; + +-- case 6:4: Related parallel DDL on distributed table followed by SELECT on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + UPDATE referece_table SET id = 160 WHERE id = 15; +ROLLBACK; + +-- case 6:5: Unrelated parallel DDL on distributed table followed by unrelated DDL on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + ALTER TABLE referece_table ADD COLUMN X int; +ROLLBACK; + +-- case 6:6: Unrelated parallel DDL on distributed table followed by related DDL on ref. table +BEGIN; + ALTER TABLE on_update_fkey_table ADD COLUMN X int; + ALTER TABLE on_update_fkey_table ALTER COLUMN value_1 SET DATA TYPE smallint; +ROLLBACK; + + +-- some more extensive tests + +-- UPDATE on dist table is followed by DELETE to reference table +BEGIN; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; + DELETE FROM referece_table WHERE id = 99; +ROLLBACK; + +-- an unrelated update followed by update on dist table and update +-- on reference table +BEGIN; + UPDATE unrelated_dist_table SET value_1 = 15; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; + UPDATE referece_table SET id = 101 WHERE id = 99; +ROLLBACK; + +-- an unrelated update followed by update on the reference table and update +-- on the cascading distributed table +-- note that the UPDATE on the reference table will try to set the execution +-- mode to sequential, which will fail since there is an already opened +-- parallel connections +BEGIN; + UPDATE unrelated_dist_table SET value_1 = 15; + UPDATE referece_table SET id = 101 WHERE id = 99; + UPDATE on_update_fkey_table SET value_1 = 5 WHERE id != 11; +ROLLBACK; + +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + SELECT create_distributed_table('test_table_2', 'id'); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- the fails since we're trying to switch sequential mode after +-- already executed a parallel query +BEGIN; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE tt4(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES tt4(id)); + SELECT create_distributed_table('tt4', 'id'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES tt4(id)); + SELECT create_distributed_table('test_table_2', 'id'); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- same test with the above, but this time using +-- sequential mode, succeeds +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE tt4(id int PRIMARY KEY, value_1 int, FOREIGN KEY(id) REFERENCES tt4(id)); + SELECT create_distributed_table('tt4', 'id'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id), FOREIGN KEY(id) REFERENCES tt4(id)); + SELECT create_distributed_table('test_table_2', 'id'); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- another test with ALTER TABLE fails since we're already opened +-- parallel connection via create_distributed_table(), later +-- adding foreign key to reference table fails +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1, test_table_2; +COMMIT; + +-- same test with the above on sequential mode should work fine +BEGIN; + + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1, test_table_2; +COMMIT; + + +-- similar test with the above, but this time the order of +-- create_distributed_table and create_reference_table is +-- changed +BEGIN; + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- same test in sequential mode should succeed +BEGIN; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- again a very similar test, but this time +-- a parallel SELECT is already executed before +-- setting the mode to sequential should fail +BEGIN; + SELECT count(*) FROM on_update_fkey_table; + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); + SELECT create_distributed_table('test_table_2', 'id'); + + CREATE TABLE test_table_1(id int PRIMARY KEY); + SELECT create_reference_table('test_table_1'); + + ALTER TABLE test_table_2 ADD CONSTRAINT c_check FOREIGN KEY (value_1) REFERENCES test_table_1(id); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_1 CASCADE; +ROLLBACK; + +-- make sure that we cannot create hash distributed tables with +-- foreign keys to reference tables when they have data in it +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_2, test_table_1; +COMMIT; + + +-- the same test with above in sequential mode would still not work +-- since COPY cannot be executed in sequential mode +BEGIN; + + SET LOCAL citus.multi_shard_modify_mode TO 'sequential'; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + INSERT INTO test_table_1 SELECT i FROM generate_series(0,100) i; + + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + INSERT INTO test_table_2 SELECT i, i FROM generate_series(0,100) i; + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_2, test_table_1; +COMMIT; + +-- we should be able to execute and DML/DDL/SELECT after we've +-- switched to sequential via create_distributed_table +BEGIN; + + CREATE TABLE test_table_1(id int PRIMARY KEY); + CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); + + SELECT create_reference_table('test_table_1'); + SELECT create_distributed_table('test_table_2', 'id'); + + -- and maybe some other test + CREATE INDEX i1 ON test_table_1(id); + ALTER TABLE test_table_2 ADD CONSTRAINT check_val CHECK (id > 0); + SELECT count(*) FROM test_table_2; + SELECT count(*) FROM test_table_1; + UPDATE test_table_2 SET value_1 = 15; + + -- make sure that the output isn't too verbose + SET LOCAL client_min_messages TO ERROR; + DROP TABLE test_table_2, test_table_1; +COMMIT; + +RESET client_min_messages; + +DROP SCHEMA test_fkey_to_ref_in_tx CASCADE; + +SET search_path TO public; diff --git a/src/test/regress/sql/foreign_key_to_reference_table.sql b/src/test/regress/sql/foreign_key_to_reference_table.sql index f79ef0469..e55b558d8 100644 --- a/src/test/regress/sql/foreign_key_to_reference_table.sql +++ b/src/test/regress/sql/foreign_key_to_reference_table.sql @@ -623,7 +623,6 @@ BEGIN; ROLLBACK; -- create_reference_table, create_distributed_table and ALTER TABLE in the same transaction --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); SELECT create_reference_table('test_table_1'); @@ -637,7 +636,6 @@ BEGIN; COMMIT; -- the order of create_reference_table and create_distributed_table is changed --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY, value_1 int); SELECT create_distributed_table('test_table_1', 'id'); @@ -665,7 +663,6 @@ BEGIN; COMMIT; -- make sure that other DDLs/DMLs also work fine --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int, FOREIGN KEY(value_1) REFERENCES test_table_1(id)); @@ -694,7 +691,6 @@ SELECT count(*) FROM table_fkeys_in_workers WHERE relid LIKE 'fkey_reference_tab DROP TABLE test_table_1, test_table_2; -- check if we can drop the foreign constraint in a transaction right after ADD CONSTRAINT --- FIXME: fails for now BEGIN; CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); @@ -894,7 +890,6 @@ DROP TABLE test_table_1, test_table_2; -- check if we successfuly set multi_shard_modify_mode to sequential after sequentially running DDLs -- in transaction since the upcoming DDLs need to run sequentially. --- FIXME: fails for now CREATE TABLE test_table_1(id int PRIMARY KEY); CREATE TABLE test_table_2(id int PRIMARY KEY, value_1 int); CREATE TABLE test_table_3(id int PRIMARY KEY, value_1 int); From 35eac2318d680841ed503bf89720b02b1bb48a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Fri, 29 Jun 2018 17:55:07 +0300 Subject: [PATCH 09/10] lock referenced reference table metadata is added For certain operations in enterprise, we need to lock the referenced reference table shard distribution metadata --- .../distributed/master/master_repair_shards.c | 11 +++- src/backend/distributed/utils/resource_lock.c | 56 +++++++++++++++++++ src/include/distributed/resource_lock.h | 4 ++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/src/backend/distributed/master/master_repair_shards.c b/src/backend/distributed/master/master_repair_shards.c index c14cfc523..1f84a0889 100644 --- a/src/backend/distributed/master/master_repair_shards.c +++ b/src/backend/distributed/master/master_repair_shards.c @@ -188,6 +188,16 @@ RepairShardPlacement(int64 shardId, char *sourceNodeName, int32 sourceNodePort, "not supported.", relationName))); } + /* + * We take a lock on the referenced table if there is a foreign constraint + * during the copy procedure. If we do not block DMLs on the referenced + * table, we cannot avoid the inconsistency between the two copies of the + * data. Currently, we do not support replication factor > 1 on the tables + * with foreign constraints, so this command will fail for this case anyway. + * However, it is taken as a precaution in case we support it one day. + */ + LockReferencedReferenceShardDistributionMetadata(shardId, ExclusiveLock); + /* * We plan to move the placement to the healthy state, so we need to grab a shard * metadata lock (in exclusive mode). @@ -394,7 +404,6 @@ CopyShardForeignConstraintCommandList(ShardInterval *shardInterval) return copyShardForeignConstraintCommandList; } - /* * ConstuctQualifiedShardName creates the fully qualified name string of the * given shard in ._ format. diff --git a/src/backend/distributed/utils/resource_lock.c b/src/backend/distributed/utils/resource_lock.c index 555a89261..06fd61a99 100644 --- a/src/backend/distributed/utils/resource_lock.c +++ b/src/backend/distributed/utils/resource_lock.c @@ -25,6 +25,7 @@ #include "distributed/distributed_planner.h" #include "distributed/multi_router_executor.h" #include "distributed/relay_utility.h" +#include "distributed/reference_table_utils.h" #include "distributed/resource_lock.h" #include "distributed/shardinterval_utils.h" #include "distributed/worker_protocol.h" @@ -33,6 +34,7 @@ /* local function forward declarations */ static LOCKMODE IntToLockMode(int mode); +static List * GetSortedReferenceShardIntervals(List *relationList); /* exports for SQL callable functions */ @@ -164,6 +166,60 @@ LockShardDistributionMetadata(int64 shardId, LOCKMODE lockMode) } +/* + * LockReferencedReferenceShardDistributionMetadata acquires the given lock + * on the reference tables which has a foreign key from the given relation. + */ +void +LockReferencedReferenceShardDistributionMetadata(uint64 shardId, LOCKMODE lock) +{ + ListCell *shardIntervalCell = NULL; + Oid relationId = RelationIdForShard(shardId); + + DistTableCacheEntry *cacheEntry = DistributedTableCacheEntry(relationId); + List *referencedRelationList = cacheEntry->referencedRelationsViaForeignKey; + + List *shardIntervalList = GetSortedReferenceShardIntervals(referencedRelationList); + foreach(shardIntervalCell, shardIntervalList) + { + ShardInterval *shardInterval = (ShardInterval *) lfirst(shardIntervalCell); + + LockShardDistributionMetadata(shardInterval->shardId, lock); + } +} + + +/* + * GetSortedReferenceShards iterates through the given relation list. + * Lists the shards of reference tables and returns the list after sorting. + */ +static List * +GetSortedReferenceShardIntervals(List *relationList) +{ + List *shardIntervalList = NIL; + ListCell *relationCell = NULL; + + foreach(relationCell, relationList) + { + Oid relationId = lfirst_oid(relationCell); + List *currentShardIntervalList = NIL; + + if (PartitionMethod(relationId) != DISTRIBUTE_BY_NONE) + { + continue; + } + + currentShardIntervalList = LoadShardIntervalList(relationId); + shardIntervalList = lappend(shardIntervalList, linitial( + currentShardIntervalList)); + } + + shardIntervalList = SortList(shardIntervalList, CompareShardIntervalsById); + + return shardIntervalList; +} + + /* * TryLockShardDistributionMetadata tries to grab a lock for distribution * metadata related to the specified shard, returning false if the lock diff --git a/src/include/distributed/resource_lock.h b/src/include/distributed/resource_lock.h index 014b68e61..a83519088 100644 --- a/src/include/distributed/resource_lock.h +++ b/src/include/distributed/resource_lock.h @@ -67,6 +67,10 @@ typedef enum AdvisoryLocktagClass extern void LockShardDistributionMetadata(int64 shardId, LOCKMODE lockMode); extern bool TryLockShardDistributionMetadata(int64 shardId, LOCKMODE lockMode); +/* Lock shard/relation metadata of the referenced reference table if exists */ +extern void LockReferencedReferenceShardDistributionMetadata(uint64 shardId, LOCKMODE + lock); + /* Lock shard data, for DML commands or remote fetches */ extern void LockShardResource(uint64 shardId, LOCKMODE lockmode); extern void UnlockShardResource(uint64 shardId, LOCKMODE lockmode); From f7b901e3fdf15478209d0ca71d11ed4a8b81fbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mehmet=20furkan=20=C5=9Fahin?= Date: Mon, 2 Jul 2018 15:25:09 +0300 Subject: [PATCH 10/10] CopyShardForeignConstraintCommandList API change for grouped constraints --- .../distributed/master/master_repair_shards.c | 63 ++++++++++++++++--- src/include/distributed/master_protocol.h | 5 ++ 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/src/backend/distributed/master/master_repair_shards.c b/src/backend/distributed/master/master_repair_shards.c index 1f84a0889..4d56350d8 100644 --- a/src/backend/distributed/master/master_repair_shards.c +++ b/src/backend/distributed/master/master_repair_shards.c @@ -348,8 +348,31 @@ CopyShardCommandList(ShardInterval *shardInterval, List * CopyShardForeignConstraintCommandList(ShardInterval *shardInterval) { - List *copyShardForeignConstraintCommandList = NIL; + List *colocatedShardForeignConstraintCommandList = NIL; + List *referenceTableForeignConstraintList = NIL; + CopyShardForeignConstraintCommandListGrouped(shardInterval, + & + colocatedShardForeignConstraintCommandList, + &referenceTableForeignConstraintList); + + return list_concat(colocatedShardForeignConstraintCommandList, + referenceTableForeignConstraintList); +} + + +/* + * CopyShardForeignConstraintCommandListGrouped generates command lists + * to create foreign constraints existing in source shard after copying it to other + * node in separate groups for foreign constraints in between hash distributed tables + * and from a hash distributed to reference tables. + */ +void +CopyShardForeignConstraintCommandListGrouped(ShardInterval *shardInterval, + List ** + colocatedShardForeignConstraintCommandList, + List **referenceTableForeignConstraintList) +{ Oid schemaId = get_rel_namespace(shardInterval->relationId); char *schemaName = get_namespace_name(schemaId); char *escapedSchemaName = quote_literal_cstr(schemaName); @@ -364,6 +387,9 @@ CopyShardForeignConstraintCommandList(ShardInterval *shardInterval) shardIndex = ShardIndex(shardInterval); } + *colocatedShardForeignConstraintCommandList = NIL; + *referenceTableForeignConstraintList = NIL; + foreach(commandCell, commandList) { char *command = (char *) lfirst(commandCell); @@ -374,6 +400,7 @@ CopyShardForeignConstraintCommandList(ShardInterval *shardInterval) char *referencedSchemaName = NULL; char *escapedReferencedSchemaName = NULL; uint64 referencedShardId = INVALID_SHARD_ID; + bool colocatedForeignKey = false; StringInfo applyForeignConstraintCommand = makeStringInfo(); @@ -389,21 +416,43 @@ CopyShardForeignConstraintCommandList(ShardInterval *shardInterval) referencedSchemaId = get_rel_namespace(referencedRelationId); referencedSchemaName = get_namespace_name(referencedSchemaId); escapedReferencedSchemaName = quote_literal_cstr(referencedSchemaName); - referencedShardId = ColocatedShardIdInRelation(referencedRelationId, shardIndex); + + if (PartitionMethod(referencedRelationId) == DISTRIBUTE_BY_NONE) + { + List *shardList = LoadShardList(referencedRelationId); + uint64 *shardIdPointer = (uint64 *) linitial(shardList); + + referencedShardId = (*shardIdPointer); + } + else + { + referencedShardId = ColocatedShardIdInRelation(referencedRelationId, + shardIndex); + + colocatedForeignKey = true; + } appendStringInfo(applyForeignConstraintCommand, WORKER_APPLY_INTER_SHARD_DDL_COMMAND, shardInterval->shardId, escapedSchemaName, referencedShardId, escapedReferencedSchemaName, escapedCommand); - copyShardForeignConstraintCommandList = lappend( - copyShardForeignConstraintCommandList, - applyForeignConstraintCommand->data); + if (colocatedForeignKey) + { + *colocatedShardForeignConstraintCommandList = lappend( + *colocatedShardForeignConstraintCommandList, + applyForeignConstraintCommand->data); + } + else + { + *referenceTableForeignConstraintList = lappend( + *referenceTableForeignConstraintList, + applyForeignConstraintCommand->data); + } } - - return copyShardForeignConstraintCommandList; } + /* * ConstuctQualifiedShardName creates the fully qualified name string of the * given shard in ._ format. diff --git a/src/include/distributed/master_protocol.h b/src/include/distributed/master_protocol.h index 018073545..a9d2b4cb9 100644 --- a/src/include/distributed/master_protocol.h +++ b/src/include/distributed/master_protocol.h @@ -164,6 +164,11 @@ extern Datum master_copy_shard_placement(PG_FUNCTION_ARGS); extern List * CopyShardCommandList(ShardInterval *shardInterval, char *sourceNodeName, int32 sourceNodePort); extern List * CopyShardForeignConstraintCommandList(ShardInterval *shardInterval); +extern void CopyShardForeignConstraintCommandListGrouped(ShardInterval *shardInterval, + List ** + colocatedShardForeignConstraintCommandList, + List ** + referenceTableForeignConstraintList); extern ShardPlacement * SearchShardPlacementInList(List *shardPlacementList, char *nodeName, uint32 nodePort, bool missingOk);