Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix FK constraints for compressed chunks #6797

Merged
merged 1 commit into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .unreleased/pr_6797
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes: #6797 Fix foreign key constraint handling on compressed hypertables
52 changes: 0 additions & 52 deletions src/chunk.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 0 additions & 2 deletions src/chunk.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 0 additions & 14 deletions tsl/src/compression/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 5 additions & 4 deletions tsl/test/expected/compression.out
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions tsl/test/expected/compression_fks.out
Original file line number Diff line number Diff line change
@@ -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)

1 change: 1 addition & 0 deletions tsl/test/sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions tsl/test/sql/compression_fks.sql
Original file line number Diff line number Diff line change
@@ -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;

Loading