Skip to content

Commit

Permalink
Fix NULLS NOT DISTINCT for DML decompression
Browse files Browse the repository at this point in the history
If unique constraints have NULLS NOT DISTINCT set on them,
we need to check the constraints by building scan keys.
This fix reads all the indexes settings and does the scan
if any of them have this setting enabled.
  • Loading branch information
antekresic committed Sep 20, 2024
1 parent e339f1b commit c9c3191
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 0 deletions.
7 changes: 7 additions & 0 deletions tsl/src/compression/compression_dml.c
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,7 @@ get_batch_keys_for_unique_constraints(const ChunkInsertState *cis, Relation rela
{
tuple_filtering_constraints *constraints = palloc0(sizeof(tuple_filtering_constraints));
constraints->on_conflict = ONCONFLICT_UPDATE;
constraints->nullsnotdistinct = false;
ListCell *lc;

/* Fast path if definitely no indexes */
Expand Down Expand Up @@ -833,6 +834,12 @@ get_batch_keys_for_unique_constraints(const ChunkInsertState *cis, Relation rela
constraints->covered = false;
}

#if PG15_GE
/* If any of the unique indexes have NULLS NOT DISTINCT set, we proceed
* with checking the constraints with decompression */
constraints->nullsnotdistinct |= indexDesc->rd_index->indnullsnotdistinct;
#endif

/* When multiple unique indexes are present, in theory there could be no shared
* columns even though that is very unlikely as they will probably at least share
* the partitioning columns. But since we are looking at chunk indexes here that
Expand Down
33 changes: 33 additions & 0 deletions tsl/test/shared/expected/compression_nulls_not_distinct.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
-- 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 NULL NOT DISTINCT unique constraint
-- this test is run only on PG15 since support for NULL NOT DISTINCT
-- only exists since then. once we drop support for PG14, this test
-- can be moved to non-shared compression_conflicts test
set timescaledb.debug_compression_path_info to on;
CREATE TABLE nulls_not_distinct(time timestamptz not null, device text, value float);
CREATE UNIQUE INDEX ON nulls_not_distinct (time, device) NULLS NOT DISTINCT;
SELECT table_name FROM create_hypertable('nulls_not_distinct', 'time');
table_name
nulls_not_distinct
(1 row)

ALTER TABLE nulls_not_distinct SET (timescaledb.compress, timescaledb.compress_segmentby = 'device');
NOTICE: default order by for hypertable "nulls_not_distinct" is set to ""time" DESC"
INSERT INTO nulls_not_distinct SELECT '2024-01-01'::timestamptz + format('%s',i)::interval, 'd1', i FROM generate_series(1,6000) g(i);
INSERT INTO nulls_not_distinct VALUES ('2024-01-01 0:00:00.5', NULL, 1);
SELECT count(compress_chunk(c)) FROM show_chunks('nulls_not_distinct') c;
INFO: compress_chunk_tuplesort_start
count
1
(1 row)

-- shouldn't succeed because nulls are not distinct
\set ON_ERROR_STOP 0
INSERT INTO nulls_not_distinct VALUES ('2024-01-01 0:00:00.5', NULL, 1);
INFO: Using index scan with scan keys: index 1, heap 2, memory 1.
ERROR: duplicate key value violates unique constraint "_hyper_X_X_chunk_nulls_not_distinct_time_device_idx"
\set ON_ERROR_STOP 1
RESET timescaledb.debug_compression_path_info;
DROP TABLE nulls_not_distinct;
1 change: 1 addition & 0 deletions tsl/test/shared/sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ endif()
# other dependent tests to fail. Hence running this test in solo
if((${PG_VERSION_MAJOR} GREATER_EQUAL "15"))
list(APPEND SOLO_TESTS merge_dml.sql)
list(APPEND TEST_FILES_SHARED compression_nulls_not_distinct.sql)
endif()

if(CMAKE_BUILD_TYPE MATCHES Debug)
Expand Down
28 changes: 28 additions & 0 deletions tsl/test/shared/sql/compression_nulls_not_distinct.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- 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 NULL NOT DISTINCT unique constraint

-- this test is run only on PG15 since support for NULL NOT DISTINCT
-- only exists since then. once we drop support for PG14, this test
-- can be moved to non-shared compression_conflicts test

set timescaledb.debug_compression_path_info to on;
CREATE TABLE nulls_not_distinct(time timestamptz not null, device text, value float);
CREATE UNIQUE INDEX ON nulls_not_distinct (time, device) NULLS NOT DISTINCT;
SELECT table_name FROM create_hypertable('nulls_not_distinct', 'time');
ALTER TABLE nulls_not_distinct SET (timescaledb.compress, timescaledb.compress_segmentby = 'device');

INSERT INTO nulls_not_distinct SELECT '2024-01-01'::timestamptz + format('%s',i)::interval, 'd1', i FROM generate_series(1,6000) g(i);
INSERT INTO nulls_not_distinct VALUES ('2024-01-01 0:00:00.5', NULL, 1);

SELECT count(compress_chunk(c)) FROM show_chunks('nulls_not_distinct') c;

-- shouldn't succeed because nulls are not distinct
\set ON_ERROR_STOP 0
INSERT INTO nulls_not_distinct VALUES ('2024-01-01 0:00:00.5', NULL, 1);
\set ON_ERROR_STOP 1

RESET timescaledb.debug_compression_path_info;
DROP TABLE nulls_not_distinct;

0 comments on commit c9c3191

Please sign in to comment.