diff --git a/src/backend/distributed/commands/create_distributed_table.c b/src/backend/distributed/commands/create_distributed_table.c index 1f28ead9b..93d1689a2 100644 --- a/src/backend/distributed/commands/create_distributed_table.c +++ b/src/backend/distributed/commands/create_distributed_table.c @@ -408,12 +408,20 @@ ErrorIfNotSupportedConstraint(Relation relation, char distributionMethod, List *indexOidList = NULL; ListCell *indexOidCell = NULL; + /* + * We first perform check for foreign constraints. It is important to do this check + * before next check, because other types of constraints are allowed on reference + * tables and we return early for those constraints thanks to next check. Therefore, + * for reference tables, we first check for foreing constraints and if they are OK, + * we do not error out for other types of constraints. + */ + ErrorIfNotSupportedForeignConstraint(relation, distributionMethod, distributionColumn, + colocationId); + /* * Citus supports any kind of uniqueness constraints for reference tables * given that they only consist of a single shard and we can simply rely on * Postgres. - * TODO: Here we should be erroring out if there exists any foreign keys - * from/to a reference table. */ if (distributionMethod == DISTRIBUTE_BY_NONE) { @@ -499,10 +507,6 @@ ErrorIfNotSupportedConstraint(Relation relation, char distributionMethod, index_close(indexDesc, NoLock); } - - /* we also perform check for foreign constraints */ - ErrorIfNotSupportedForeignConstraint(relation, distributionMethod, distributionColumn, - colocationId); } @@ -542,6 +546,7 @@ ErrorIfNotSupportedForeignConstraint(Relation relation, char distributionMethod, bool isNull = false; int attrIdx = 0; bool foreignConstraintOnPartitionColumn = false; + bool selfReferencingTable = false; pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, @@ -560,6 +565,27 @@ ErrorIfNotSupportedForeignConstraint(Relation relation, char distributionMethod, continue; } + referencedTableId = constraintForm->confrelid; + selfReferencingTable = relation->rd_id == 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 is not + * 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"), + errdetail("Foreign key constraints are not allowed 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. @@ -589,13 +615,11 @@ ErrorIfNotSupportedForeignConstraint(Relation relation, char distributionMethod, " supported in ON UPDATE operation."))); } - referencedTableId = constraintForm->confrelid; - /* * Some checks are not meaningful if foreign key references the table itself. * Therefore we will skip those checks. */ - if (referencedTableId != relation->rd_id) + if (!selfReferencingTable) { if (!IsDistributedTable(referencedTableId)) { diff --git a/src/backend/distributed/executor/multi_utility.c b/src/backend/distributed/executor/multi_utility.c index 9bab7b39b..b7250746d 100644 --- a/src/backend/distributed/executor/multi_utility.c +++ b/src/backend/distributed/executor/multi_utility.c @@ -1494,6 +1494,23 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) errhint("You can issue each subcommand separately"))); } + referencingTableId = RangeVarGetRelid(alterTableStatement->relation, + lockmode, + alterTableStatement->missing_ok); + referencedTableId = RangeVarGetRelid(constraint->pktable, lockmode, + alterTableStatement->missing_ok); + + /* we do not support foreign keys for reference tables */ + if (PartitionMethod(referencingTableId) == DISTRIBUTE_BY_NONE || + PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create foreign key constraint"), + errdetail( + "Foreign key constraints are not allowed 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. @@ -1538,12 +1555,6 @@ ErrorIfUnsupportedAlterTableStmt(AlterTableStmt *alterTableStatement) } /* to enforce foreign constraints, tables must be co-located */ - referencingTableId = RangeVarGetRelid(alterTableStatement->relation, - lockmode, - alterTableStatement->missing_ok); - referencedTableId = RangeVarGetRelid(constraint->pktable, lockmode, - alterTableStatement->missing_ok); - if (!TablesColocated(referencingTableId, referencedTableId)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), diff --git a/src/test/regress/expected/multi_foreign_key.out b/src/test/regress/expected/multi_foreign_key.out index 970d47c37..20058b869 100644 --- a/src/test/regress/expected/multi_foreign_key.out +++ b/src/test/regress/expected/multi_foreign_key.out @@ -773,3 +773,122 @@ SELECT * FROM self_referencing_table2; -- we no longer need those tables DROP TABLE self_referencing_table2; +-- test reference tables +-- test foreign key creation on CREATE TABLE from reference table +CREATE TABLE referenced_by_reference_table(id int PRIMARY KEY, other_column int); +SELECT create_distributed_table('referenced_by_reference_table', 'id'); + create_distributed_table +-------------------------- + +(1 row) + +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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- test foreign key creation on CREATE TABLE 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_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 +DETAIL: Foreign key constraints are not allowed 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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- 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 +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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- test foreign key creation on CREATE TABLE on self referencing reference table +CREATE TABLE self_referencing_reference_table( + id int, + other_column int, + other_column_ref int, + PRIMARY KEY(id, other_column), + 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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- 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); +SELECT create_reference_table('reference_table'); + create_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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- 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 +-------------------------- + +(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 +DETAIL: Foreign key constraints are not allowed 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); +SELECT create_reference_table('reference_table_second'); + create_reference_table +------------------------ + +(1 row) + +ALTER TABLE reference_table_second ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES reference_table(id); +ERROR: cannot create foreign key constraint +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- test foreign key creation on ALTER TABLE from reference table to local table +DROP TABLE reference_table; +CREATE TABLE reference_table(id int PRIMARY KEY, referencing_column int); +SELECT create_reference_table('reference_table'); + create_reference_table +------------------------ + +(1 row) + +ALTER TABLE reference_table ADD CONSTRAINT fk FOREIGN KEY(referencing_column) REFERENCES referenced_local_table(id); +ERROR: cannot create foreign key constraint +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- test foreign key creation on ALTER TABLE on self referencing reference table +DROP TABLE self_referencing_reference_table; +CREATE TABLE self_referencing_reference_table( + id int, + other_column int, + other_column_ref int, + PRIMARY KEY(id, other_column) +); +SELECT create_reference_table('self_referencing_reference_table'); + create_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 +DETAIL: Foreign key constraints are not allowed from or to reference tables. +-- 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/sql/multi_foreign_key.sql b/src/test/regress/sql/multi_foreign_key.sql index 9690b0e26..f7258521d 100644 --- a/src/test/regress/sql/multi_foreign_key.sql +++ b/src/test/regress/sql/multi_foreign_key.sql @@ -449,3 +449,78 @@ SELECT * FROM self_referencing_table2; -- we no longer need those tables DROP TABLE self_referencing_table2; + + +-- test reference tables +-- test foreign key creation on CREATE TABLE from reference table +CREATE TABLE referenced_by_reference_table(id int PRIMARY KEY, other_column int); +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 +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'); + +-- 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; +CREATE TABLE reference_table(id int, referencing_column int REFERENCES referenced_local_table(id)); +SELECT create_reference_table('reference_table'); + +-- test foreign key creation on CREATE TABLE on self referencing reference table +CREATE TABLE self_referencing_reference_table( + id int, + other_column int, + other_column_ref int, + PRIMARY KEY(id, other_column), + FOREIGN KEY(id, other_column_ref) REFERENCES self_referencing_reference_table(id, other_column) +); +SELECT create_reference_table('self_referencing_reference_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); +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); + +-- 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); +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; +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); + +-- test foreign key creation on ALTER TABLE on self referencing reference table +DROP TABLE self_referencing_reference_table; +CREATE TABLE self_referencing_reference_table( + id int, + other_column int, + other_column_ref int, + PRIMARY KEY(id, other_column) +); +SELECT create_reference_table('self_referencing_reference_table'); +ALTER TABLE self_referencing_reference_table ADD CONSTRAINT fk FOREIGN KEY(id, other_column_ref) REFERENCES self_referencing_reference_table(id, other_column); + +-- 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;