diff --git a/.unreleased/pr_6797 b/.unreleased/pr_6797 new file mode 100644 index 000000000..849d80b16 --- /dev/null +++ b/.unreleased/pr_6797 @@ -0,0 +1 @@ +Fixes: #6797 Fix foreign key constraint handling on compressed hypertables diff --git a/src/chunk.c b/src/chunk.c index 4219843a5..7d4413062 100644 --- a/src/chunk.c +++ b/src/chunk.c @@ -3222,58 +3222,6 @@ ts_chunk_recreate_all_constraints_for_dimension(Hypertable *ht, int32 dimension_ chunk_scan_ctx_destroy(&chunkctx); } -/* - * Drops all FK constraints on a given chunk. - * Currently it is used only for chunks, which have been compressed and - * contain no data. - */ -void -ts_chunk_drop_fks(const Chunk *const chunk) -{ - Relation rel; - List *fks; - ListCell *lc; - - ASSERT_IS_VALID_CHUNK(chunk); - - rel = table_open(chunk->table_id, AccessShareLock); - fks = copyObject(RelationGetFKeyList(rel)); - table_close(rel, AccessShareLock); - - foreach (lc, fks) - { - const ForeignKeyCacheInfo *const fk = lfirst_node(ForeignKeyCacheInfo, lc); - ts_chunk_constraint_delete_by_constraint_name(chunk->fd.id, - get_constraint_name(fk->conoid), - true, - true); - } -} - -/* - * Recreates all FK constraints on a chunk by using the constraints on the parent hypertable - * as a template. Currently it is used only during chunk decompression, since FK constraints - * are dropped during compression. - */ -void -ts_chunk_create_fks(const Hypertable *ht, const Chunk *const chunk) -{ - Relation rel; - List *fks; - ListCell *lc; - - ASSERT_IS_VALID_CHUNK(chunk); - - rel = table_open(chunk->hypertable_relid, AccessShareLock); - fks = copyObject(RelationGetFKeyList(rel)); - table_close(rel, AccessShareLock); - foreach (lc, fks) - { - ForeignKeyCacheInfo *fk = lfirst_node(ForeignKeyCacheInfo, lc); - ts_chunk_constraint_create_on_chunk(ht, chunk, fk->conoid); - } -} - /* * Chunk catalog updates are done in three steps. * This is achieved by following this sequence: diff --git a/src/chunk.h b/src/chunk.h index da5ac9539..da77b6b8c 100644 --- a/src/chunk.h +++ b/src/chunk.h @@ -187,8 +187,6 @@ extern bool ts_chunk_exists_relid(Oid relid); extern TSDLLEXPORT int ts_chunk_num_of_chunks_created_after(const Chunk *chunk); extern TSDLLEXPORT bool ts_chunk_exists_with_compression(int32 hypertable_id); extern void ts_chunk_recreate_all_constraints_for_dimension(Hypertable *ht, int32 dimension_id); -extern TSDLLEXPORT void ts_chunk_drop_fks(const Chunk *const chunk); -extern TSDLLEXPORT void ts_chunk_create_fks(const Hypertable *ht, const Chunk *const chunk); extern int ts_chunk_delete_by_hypertable_id(int32 hypertable_id); extern int ts_chunk_delete_by_name(const char *schema, const char *table, DropBehavior behavior); extern bool ts_chunk_set_name(Chunk *chunk, const char *newname); diff --git a/tsl/src/compression/api.c b/tsl/src/compression/api.c index 1255301d1..c567be60e 100644 --- a/tsl/src/compression/api.c +++ b/tsl/src/compression/api.c @@ -457,11 +457,6 @@ compress_chunk_impl(Oid hypertable_relid, Oid chunk_relid) before_size = ts_relation_size_impl(cxt.srcht_chunk->table_id); cstat = compress_chunk(cxt.srcht_chunk->table_id, compress_ht_chunk->table_id, insert_options); - /* Drop all FK constraints on the uncompressed chunk. This is needed to allow - * cascading deleted data in FK-referenced tables, while blocking deleting data - * directly on the hypertable or chunks. - */ - ts_chunk_drop_fks(cxt.srcht_chunk); after_size = ts_relation_size_impl(compress_ht_chunk->table_id); if (new_compressed_chunk) @@ -596,9 +591,6 @@ decompress_chunk_impl(Chunk *uncompressed_chunk, bool if_compressed) decompress_chunk(compressed_chunk->table_id, uncompressed_chunk->table_id); - /* Recreate FK constraints, since they were dropped during compression. */ - ts_chunk_create_fks(uncompressed_hypertable, uncompressed_chunk); - /* Delete the compressed chunk */ ts_compression_chunk_size_delete(uncompressed_chunk->fd.id); ts_chunk_clear_compressed_chunk(uncompressed_chunk); @@ -670,12 +662,6 @@ tsl_create_compressed_chunk(PG_FUNCTION_ARGS) ts_chunk_constraints_create(cxt.compress_ht, compress_ht_chunk); ts_trigger_create_all_on_chunk(compress_ht_chunk); - /* Drop all FK constraints on the uncompressed chunk. This is needed to allow - * cascading deleted data in FK-referenced tables, while blocking deleting data - * directly on the hypertable or chunks. - */ - ts_chunk_drop_fks(cxt.srcht_chunk); - /* Insert empty stats to compression_chunk_size */ compression_chunk_size_catalog_insert(cxt.srcht_chunk->fd.id, &uncompressed_size, diff --git a/tsl/test/expected/compression.out b/tsl/test/expected/compression.out index bf3a83a4d..2f5ab8483 100644 --- a/tsl/test/expected/compression.out +++ b/tsl/test/expected/compression.out @@ -1054,9 +1054,10 @@ SELECT constraint_schema, constraint_name, table_schema, table_name, constraint_ FROM information_schema.table_constraints WHERE table_name = :'CHUNK_NAME' AND constraint_type = 'FOREIGN KEY' ORDER BY constraint_name; - constraint_schema | constraint_name | table_schema | table_name | constraint_type --------------------+-----------------+--------------+------------+----------------- -(0 rows) + constraint_schema | constraint_name | table_schema | table_name | constraint_type +-----------------------+---------------------------+-----------------------+--------------------+----------------- + _timescaledb_internal | 42_6_hyper_device_id_fkey | _timescaledb_internal | _hyper_18_42_chunk | FOREIGN KEY +(1 row) -- Delete data from compressed chunk directly fails \set ON_ERROR_STOP 0 @@ -1094,7 +1095,7 @@ WHERE table_name = :'CHUNK_NAME' AND constraint_type = 'FOREIGN KEY' ORDER BY constraint_name; constraint_schema | constraint_name | table_schema | table_name | constraint_type -----------------------+---------------------------+-----------------------+--------------------+----------------- - _timescaledb_internal | 42_8_hyper_device_id_fkey | _timescaledb_internal | _hyper_18_42_chunk | FOREIGN KEY + _timescaledb_internal | 42_6_hyper_device_id_fkey | _timescaledb_internal | _hyper_18_42_chunk | FOREIGN KEY (1 row) -- create hypertable with 2 chunks diff --git a/tsl/test/expected/compression_fks.out b/tsl/test/expected/compression_fks.out new file mode 100644 index 000000000..9123a5725 --- /dev/null +++ b/tsl/test/expected/compression_fks.out @@ -0,0 +1,46 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. +-- test foreign key constraints with compression +CREATE TABLE keys(time timestamptz unique); +CREATE TABLE ht_with_fk(time timestamptz); +SELECT create_hypertable('ht_with_fk','time'); +NOTICE: adding not-null constraint to column "time" + create_hypertable +------------------------- + (1,public,ht_with_fk,t) +(1 row) + +ALTER TABLE ht_with_fk ADD CONSTRAINT keys FOREIGN KEY (time) REFERENCES keys(time) ON DELETE CASCADE; +ALTER TABLE ht_with_fk SET (timescaledb.compress,timescaledb.compress_segmentby='time'); +NOTICE: default order by for hypertable "ht_with_fk" is set to "" +-- no keys added yet so any insert into ht_with_fk should fail +\set ON_ERROR_STOP 0 +INSERT INTO ht_with_fk SELECT '2000-01-01'; +ERROR: insert or update on table "_hyper_1_1_chunk" violates foreign key constraint "1_1_keys" +\set ON_ERROR_STOP 1 +-- create a key in the referenced table +INSERT INTO keys SELECT '2000-01-01'; +-- now the insert should succeed +INSERT INTO ht_with_fk SELECT '2000-01-01'; +SELECT compress_chunk(ch) FROM show_chunks('ht_with_fk') ch; + compress_chunk +---------------------------------------- + _timescaledb_internal._hyper_1_2_chunk +(1 row) + +-- insert should still succeed after compression +INSERT INTO ht_with_fk SELECT '2000-01-01'; +-- inserting key not present in keys should fail +\set ON_ERROR_STOP 0 +INSERT INTO ht_with_fk SELECT '2000-01-01 0:00:01'; +ERROR: insert or update on table "_hyper_1_2_chunk" violates foreign key constraint "2_2_keys" +\set ON_ERROR_STOP 1 +SELECT conrelid::regclass,conname,confrelid::regclass FROM pg_constraint WHERE contype = 'f' AND confrelid = 'keys'::regclass ORDER BY conrelid::regclass::text COLLATE "C",conname; + conrelid | conname | confrelid +------------------------------------------------+----------+----------- + _timescaledb_internal._hyper_1_2_chunk | 2_2_keys | keys + _timescaledb_internal.compress_hyper_2_3_chunk | keys | keys + ht_with_fk | keys | keys +(3 rows) + diff --git a/tsl/test/sql/CMakeLists.txt b/tsl/test/sql/CMakeLists.txt index 4cbb61570..3469f236b 100644 --- a/tsl/test/sql/CMakeLists.txt +++ b/tsl/test/sql/CMakeLists.txt @@ -22,6 +22,7 @@ set(TEST_FILES compression_create_compressed_table.sql compression_conflicts.sql compression_defaults.sql + compression_fks.sql compression_insert.sql compression_policy.sql compression_qualpushdown.sql diff --git a/tsl/test/sql/compression_fks.sql b/tsl/test/sql/compression_fks.sql new file mode 100644 index 000000000..334a0523e --- /dev/null +++ b/tsl/test/sql/compression_fks.sql @@ -0,0 +1,35 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +-- test foreign key constraints with compression +CREATE TABLE keys(time timestamptz unique); +CREATE TABLE ht_with_fk(time timestamptz); +SELECT create_hypertable('ht_with_fk','time'); + +ALTER TABLE ht_with_fk ADD CONSTRAINT keys FOREIGN KEY (time) REFERENCES keys(time) ON DELETE CASCADE; +ALTER TABLE ht_with_fk SET (timescaledb.compress,timescaledb.compress_segmentby='time'); + +-- no keys added yet so any insert into ht_with_fk should fail +\set ON_ERROR_STOP 0 +INSERT INTO ht_with_fk SELECT '2000-01-01'; +\set ON_ERROR_STOP 1 + +-- create a key in the referenced table +INSERT INTO keys SELECT '2000-01-01'; + +-- now the insert should succeed +INSERT INTO ht_with_fk SELECT '2000-01-01'; + +SELECT compress_chunk(ch) FROM show_chunks('ht_with_fk') ch; + +-- insert should still succeed after compression +INSERT INTO ht_with_fk SELECT '2000-01-01'; + +-- inserting key not present in keys should fail +\set ON_ERROR_STOP 0 +INSERT INTO ht_with_fk SELECT '2000-01-01 0:00:01'; +\set ON_ERROR_STOP 1 + +SELECT conrelid::regclass,conname,confrelid::regclass FROM pg_constraint WHERE contype = 'f' AND confrelid = 'keys'::regclass ORDER BY conrelid::regclass::text COLLATE "C",conname; +