Skip to content

Commit 7c19397

Browse files
committed
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.
1 parent 11b0fd1 commit 7c19397

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)