Skip to content
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/fix_direct_compress_exclusion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixes: #10281 Disable Direct Compress when the destination table has an exclusion constraint so the constraint is still enforced
6 changes: 6 additions & 0 deletions src/copy.c
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,12 @@ choose_copy_method(Hypertable *ht, CopyChunkState *ccstate, ResultRelInfo *resul
(errmsg("disabling direct compress because the destination table has unique "
"constraints")));
}
else if (ts_indexing_relation_has_exclusion_constraint(ccstate->rel))
{
ereport(WARNING,
(errmsg("disabling direct compress because the destination table has exclusion "
"constraints")));
}
else if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->numtriggers > 1)
{
ereport(WARNING,
Expand Down
34 changes: 34 additions & 0 deletions src/indexing.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,40 @@ ts_indexing_relation_has_primary_or_unique_index(Relation htrel)
return result;
}

bool TSDLLEXPORT
ts_indexing_relation_has_exclusion_constraint(Relation htrel)
{
List *indexoidlist = RelationGetIndexList(htrel);
ListCell *lc;
bool result = false;

foreach (lc, indexoidlist)
{
Oid indexoid = lfirst_oid(lc);
HeapTuple index_tuple;
Form_pg_index index;

index_tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
if (!HeapTupleIsValid(index_tuple)) /* should not happen */
{
elog(ERROR,
"cache lookup failed for index %u in \"%s\" ",
indexoid,
RelationGetRelationName(htrel));
}
index = (Form_pg_index) GETSTRUCT(index_tuple);
result = index->indisexclusion;
ReleaseSysCache(index_tuple);
if (result)
{
break;
}
}

list_free(indexoidlist);
return result;
}

/*
* Collect the heap attribute numbers covered by any valid unique index
* (PRIMARY KEY included) on the relation. Returns a Bitmapset of raw attribute
Expand Down
1 change: 1 addition & 0 deletions src/indexing.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extern TSDLLEXPORT Oid ts_indexing_find_clustered_index(Oid table_relid);
extern void ts_indexing_mark_as_valid(Oid index_id);
extern bool ts_indexing_mark_as_invalid(Oid index_id);
extern bool TSDLLEXPORT ts_indexing_relation_has_primary_or_unique_index(Relation htrel);
extern bool TSDLLEXPORT ts_indexing_relation_has_exclusion_constraint(Relation htrel);
extern TSDLLEXPORT Bitmapset *ts_indexing_relation_get_unique_columns(Relation rel);
extern TSDLLEXPORT bool ts_indexing_compare(Oid index1, Oid index2);
extern TSDLLEXPORT Datum ts_index_matches(PG_FUNCTION_ARGS);
8 changes: 8 additions & 0 deletions src/nodes/modify_hypertable.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ should_use_direct_compress(ModifyHypertableState *state)
return false;
}

if (ts_indexing_relation_has_exclusion_constraint(state->ctr->root_rel))
{
ereport(WARNING,
(errmsg("disabling direct compress because the destination table has exclusion "
"constraints")));
return false;
}

Plan *subplan = mtstate->ps.plan->lefttree;
if (subplan->plan_rows < 10)
{
Expand Down
29 changes: 29 additions & 0 deletions tsl/test/expected/direct_compress_insert.out
Original file line number Diff line number Diff line change
Expand Up @@ -932,3 +932,32 @@ SELECT count(*), min(value), max(value) FROM metrics_view;
RESET timescaledb.enable_direct_compress_insert;
DROP VIEW metrics_view_pos;
DROP TABLE metrics_view;
-- Direct compress must be disabled when the table has an exclusion constraint
CREATE TABLE dc_excl(time timestamptz NOT NULL, device text NOT NULL, value float,
EXCLUDE USING btree (time WITH =, device WITH =))
WITH (tsdb.hypertable, tsdb.partition_column='time');
ALTER TABLE dc_excl SET (timescaledb.compress, timescaledb.compress_segmentby='device');
NOTICE: updated compression settings will only apply to future compressions
SET timescaledb.enable_direct_compress_insert = true;
-- insert warns, disables direct compress and falls back to a normal insert
INSERT INTO dc_excl SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(1,100) i;
WARNING: disabling direct compress because the destination table has exclusion constraints
-- chunk stays uncompressed since direct compress was disabled
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('dc_excl') chunk;
chunk_status_text
-------------------
{}

-- exclusion constraint is still enforced
\set ON_ERROR_STOP 0
INSERT INTO dc_excl VALUES ('2025-01-01'::timestamptz + INTERVAL '1 minute', 'd1', 99);
WARNING: disabling direct compress because the destination table has exclusion constraints
ERROR: conflicting key value violates exclusion constraint "62_dc_excl_time_device_excl"
\set ON_ERROR_STOP 1
SELECT count(*) FROM dc_excl;
count
-------
100

RESET timescaledb.enable_direct_compress_insert;
DROP TABLE dc_excl;
18 changes: 18 additions & 0 deletions tsl/test/sql/direct_compress_insert.sql
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,21 @@ RESET timescaledb.enable_direct_compress_insert;
DROP VIEW metrics_view_pos;
DROP TABLE metrics_view;

-- Direct compress must be disabled when the table has an exclusion constraint
CREATE TABLE dc_excl(time timestamptz NOT NULL, device text NOT NULL, value float,
EXCLUDE USING btree (time WITH =, device WITH =))
WITH (tsdb.hypertable, tsdb.partition_column='time');
ALTER TABLE dc_excl SET (timescaledb.compress, timescaledb.compress_segmentby='device');
SET timescaledb.enable_direct_compress_insert = true;
-- insert warns, disables direct compress and falls back to a normal insert
INSERT INTO dc_excl SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(1,100) i;
-- chunk stays uncompressed since direct compress was disabled
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('dc_excl') chunk;
-- exclusion constraint is still enforced
\set ON_ERROR_STOP 0
INSERT INTO dc_excl VALUES ('2025-01-01'::timestamptz + INTERVAL '1 minute', 'd1', 99);
\set ON_ERROR_STOP 1
SELECT count(*) FROM dc_excl;
RESET timescaledb.enable_direct_compress_insert;
DROP TABLE dc_excl;

Loading