Skip to content

Commit cbc80de

Browse files
svenklemmtimescale-automation
authored andcommitted
Skip columnar index scan when grouping by an expression
The rewrite that swaps ColumnarScan for ColumnarIndexScan only emits bare metadata columns. When the group-by column reaches the aggregate through a cast or other wrapper, the rewrite drops the wrapper, leaving the aggregate's hash and equality functions bound to the wrapper's type and the actual data of a different type. This produced a crash for queries like GROUP BY (segmentby_col)::text on a compressed chunk. Bail out of the rewrite when any group-by column is not a bare column in the child output. (cherry picked from commit 73c18ed)
1 parent c9ffccd commit cbc80de

7 files changed

Lines changed: 92 additions & 0 deletions

File tree

.unreleased/pr_9828

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixes: #9828 Skip columnar index scan when grouping by an expression

tsl/src/nodes/columnar_index_scan/columnar_index_scan.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,19 @@ insert_columnar_index_scan(Plan *plan, void *context)
768768
return plan;
769769
}
770770

771+
/*
772+
* Every group-by column must reach the Agg as a bare Var.
773+
* Grouping by expression is currently not supported.
774+
*/
775+
for (int k = 0; k < agg->numCols; k++)
776+
{
777+
TargetEntry *tle = list_nth_node(TargetEntry, childplan->targetlist, agg->grpColIdx[k] - 1);
778+
if (!IsA(tle->expr, Var))
779+
{
780+
return plan;
781+
}
782+
}
783+
771784
Plan *result = columnar_index_scan_plan_create(agg, cscan, rtable);
772785
if (result == NULL)
773786
{

tsl/test/expected/columnar_index_scan-15.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,3 +2371,19 @@ SELECT device, max(time), min(time) FROM metrics GROUP BY device ORDER BY 1,2,3;
23712371
+ on
23722372

23732373
device | count
2374+
NOTICE: using column "ts" as partitioning column
2375+
compress_chunk
2376+
----------------------------------------
2377+
_timescaledb_internal._hyper_3_5_chunk
2378+
2379+
--- QUERY PLAN ---
2380+
HashAggregate
2381+
Group Key: (_hyper_3_5_chunk.device_id)::text
2382+
-> Custom Scan (ColumnarScan) on _hyper_3_5_chunk
2383+
-> Seq Scan on compress_hyper_4_6_chunk
2384+
2385+
count
2386+
-------
2387+
1
2388+
1
2389+

tsl/test/expected/columnar_index_scan-16.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2373,3 +2373,19 @@ SELECT device, max(time), min(time) FROM metrics GROUP BY device ORDER BY 1,2,3;
23732373
+ on
23742374

23752375
device | count
2376+
NOTICE: using column "ts" as partitioning column
2377+
compress_chunk
2378+
----------------------------------------
2379+
_timescaledb_internal._hyper_3_5_chunk
2380+
2381+
--- QUERY PLAN ---
2382+
HashAggregate
2383+
Group Key: (_hyper_3_5_chunk.device_id)::text
2384+
-> Custom Scan (ColumnarScan) on _hyper_3_5_chunk
2385+
-> Seq Scan on compress_hyper_4_6_chunk
2386+
2387+
count
2388+
-------
2389+
1
2390+
1
2391+

tsl/test/expected/columnar_index_scan-17.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,3 +2524,19 @@ SELECT device, max(time), min(time) FROM metrics GROUP BY device ORDER BY 1,2,3;
25242524
+ on
25252525

25262526
device | count
2527+
NOTICE: using column "ts" as partitioning column
2528+
compress_chunk
2529+
----------------------------------------
2530+
_timescaledb_internal._hyper_3_5_chunk
2531+
2532+
--- QUERY PLAN ---
2533+
HashAggregate
2534+
Group Key: (_hyper_3_5_chunk.device_id)::text
2535+
-> Custom Scan (ColumnarScan) on _hyper_3_5_chunk
2536+
-> Seq Scan on compress_hyper_4_6_chunk
2537+
2538+
count
2539+
-------
2540+
1
2541+
1
2542+

tsl/test/expected/columnar_index_scan-18.out

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2524,3 +2524,19 @@ SELECT device, max(time), min(time) FROM metrics GROUP BY device ORDER BY 1,2,3;
25242524
+ on
25252525

25262526
device | count
2527+
NOTICE: using column "ts" as partitioning column
2528+
compress_chunk
2529+
----------------------------------------
2530+
_timescaledb_internal._hyper_3_5_chunk
2531+
2532+
--- QUERY PLAN ---
2533+
HashAggregate
2534+
Group Key: (_hyper_3_5_chunk.device_id)::text
2535+
-> Custom Scan (ColumnarScan) on _hyper_3_5_chunk
2536+
-> Seq Scan on compress_hyper_4_6_chunk
2537+
2538+
count
2539+
-------
2540+
1
2541+
1
2542+

tsl/test/sql/columnar_index_scan.sql.in

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,17 @@ SET timescaledb.enable_columnarindexscan = on;
132132
-- compare optimized vs non-optimized results
133133
:DIFF_CMD
134134

135+
136+
-- GROUP BY a cast of a segmentby column whose underlying type differs from the cast's output type
137+
CREATE TABLE cast_grp(ts timestamptz NOT NULL, device_id int) WITH (
138+
tsdb.hypertable, tsdb.orderby='ts', tsdb.segmentby='device_id');
139+
INSERT INTO cast_grp VALUES ('2025-01-01', 1), ('2025-01-01', 2);
140+
SELECT compress_chunk(c) FROM show_chunks('cast_grp') c;
141+
142+
SELECT format('%I.%I', chunk_schema, chunk_name) AS "CAST_CHUNK"
143+
FROM timescaledb_information.chunks WHERE hypertable_name = 'cast_grp' \gset
144+
145+
SET timescaledb.enable_columnarindexscan = on;
146+
EXPLAIN (costs off) SELECT count(*) FROM :CAST_CHUNK GROUP BY (device_id)::text;
147+
SELECT count(*) FROM :CAST_CHUNK GROUP BY (device_id)::text ORDER BY 1;
148+

0 commit comments

Comments
 (0)