Skip to content

Commit 2f40213

Browse files
committed
Improve HAVING support in ColumnarIndexScan
When a query uses HAVING with an aggregate (e.g., HAVING min(value) > 20) on a single fully-compressed chunk, PostgreSQL produces an AGGSPLIT_SIMPLE plan where the HAVING filter lives on the Agg node's plan.qual. Previously we unconditionally bailed when there was a plan.qual, preventing the ColumnarIndexScan optimization for these queries. This commit adds support for HAVING quals in these circumstances.
1 parent 5773544 commit 2f40213

9 files changed

Lines changed: 63 additions & 58 deletions

File tree

src/expression_utils.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,8 @@ ts_plan_tree_walker(Plan *plan, ts_plan_tree_walkerfunc func, void *context)
254254
* targetlists of aggregation nodes, replacing them with the uncompressed chunk
255255
* variables.
256256
*/
257-
List *
258-
ts_resolve_outer_special_vars(List *agg_tlist, Plan *childplan)
257+
Node *
258+
ts_resolve_outer_special_vars(Node *node, Plan *childplan)
259259
{
260-
return castNode(List, resolve_outer_special_vars_mutator((Node *) agg_tlist, childplan));
260+
return resolve_outer_special_vars_mutator(node, childplan);
261261
}

src/expression_utils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ bool TSDLLEXPORT ts_extract_expr_args(Expr *expr, Var **var, Expr **arg_value, O
1414
Oid *opcode);
1515

1616
TSDLLEXPORT List *ts_build_trivial_custom_output_targetlist(List *scan_targetlist);
17-
TSDLLEXPORT List *ts_resolve_outer_special_vars(List *agg_tlist, Plan *childplan);
17+
TSDLLEXPORT Node *ts_resolve_outer_special_vars(Node *node, Plan *childplan);
1818

1919
typedef Plan *(*ts_plan_tree_walkerfunc)(Plan *, void *);
2020
extern TSDLLEXPORT Plan *ts_plan_tree_walker(Plan *plan, ts_plan_tree_walkerfunc func,

tsl/src/nodes/columnar_index_scan/columnar_index_scan.c

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,7 @@ rewrite_agg_tlist_mutator(Node *node, void *context)
485485
* supported — the walker/mutator handles Var and Aggref nodes at any depth.
486486
*/
487487
static Plan *
488-
columnar_index_scan_plan_create(Agg *agg, CustomScan *cscan, List *resolved_targetlist,
489-
List *rtable)
488+
columnar_index_scan_plan_create(Agg *agg, CustomScan *cscan, List *rtable)
490489
{
491490
Plan *compressed_scan_subtree = linitial(cscan->custom_plans);
492491

@@ -518,9 +517,19 @@ columnar_index_scan_plan_create(Agg *agg, CustomScan *cscan, List *resolved_targ
518517
.next_resno = 1,
519518
};
520519

521-
if (validate_entries_walker((Node *) resolved_targetlist, &validate_ctx))
520+
Plan *childplan = agg->plan.lefttree;
521+
Node *resolved_targetlist =
522+
ts_resolve_outer_special_vars((Node *) agg->plan.targetlist, childplan);
523+
if (validate_entries_walker(resolved_targetlist, &validate_ctx))
522524
return NULL;
523525

526+
if (agg->plan.qual)
527+
{
528+
Node *resolved_qual = ts_resolve_outer_special_vars((Node *) agg->plan.qual, childplan);
529+
if (validate_entries_walker(resolved_qual, &validate_ctx))
530+
return NULL;
531+
}
532+
524533
List *custom_scan_tlist = validate_ctx.custom_scan_tlist;
525534
List *output_map = validate_ctx.output_map;
526535

@@ -538,7 +547,11 @@ columnar_index_scan_plan_create(Agg *agg, CustomScan *cscan, List *resolved_targ
538547
};
539548

540549
agg->plan.targetlist =
541-
(List *) rewrite_agg_tlist_mutator((Node *) agg->plan.targetlist, &rewrite_ctx);
550+
castNode(List, rewrite_agg_tlist_mutator((Node *) agg->plan.targetlist, &rewrite_ctx));
551+
552+
if (agg->plan.qual)
553+
agg->plan.qual =
554+
castNode(List, rewrite_agg_tlist_mutator((Node *) agg->plan.qual, &rewrite_ctx));
542555

543556
/* Build ColumnarIndexScan CustomScan */
544557
CustomScan *columnar_index_scan = (CustomScan *) makeNode(CustomScan);
@@ -596,10 +609,6 @@ insert_columnar_index_scan(Plan *plan, void *context)
596609
if (agg->aggsplit != AGGSPLIT_INITIAL_SERIAL && agg->aggsplit != AGGSPLIT_SIMPLE)
597610
return plan;
598611

599-
/* bail out on HAVING */
600-
if (agg->plan.qual != NIL)
601-
return plan;
602-
603612
Plan *childplan = agg->plan.lefttree;
604613

605614
/*
@@ -622,12 +631,7 @@ insert_columnar_index_scan(Plan *plan, void *context)
622631
if (!columnar_scan_has_no_vector_quals(cscan))
623632
return plan;
624633

625-
/*
626-
* Resolve OUTER_VAR references in the Agg targetlist.
627-
*/
628-
List *resolved_targetlist = ts_resolve_outer_special_vars(agg->plan.targetlist, childplan);
629-
630-
Plan *result = columnar_index_scan_plan_create(agg, cscan, resolved_targetlist, rtable);
634+
Plan *result = columnar_index_scan_plan_create(agg, cscan, rtable);
631635
if (result == NULL)
632636
return plan;
633637

tsl/src/nodes/vector_agg/plan.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,8 @@ insert_vector_agg(Plan *plan, void *context)
629629
* the subsequent checks are performed on the aggregated targetlist with
630630
* all variables resolved to uncompressed chunk variables.
631631
*/
632-
List *resolved_targetlist = ts_resolve_outer_special_vars(agg->plan.targetlist, childplan);
632+
List *resolved_targetlist =
633+
castNode(List, ts_resolve_outer_special_vars((Node *) agg->plan.targetlist, childplan));
633634

634635
const VectorAggGroupingType grouping_type =
635636
get_vectorized_grouping_type(&vqi, agg, resolved_targetlist);

tsl/test/expected/columnar_index_scan-15.out

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,26 +216,26 @@ SHOW timescaledb.enable_columnarindexscan;
216216
:PREFIX SELECT device, min(value) FROM metrics GROUP BY device HAVING min(value) > 20;
217217
--- QUERY PLAN ---
218218
GroupAggregate
219-
Group Key: _hyper_1_1_chunk.device
220-
Filter: (min(_hyper_1_1_chunk.value) > '20'::double precision)
221-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
219+
Group Key: device
220+
Filter: (min(value) > '20'::double precision)
221+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
222222
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
223223

224224
:PREFIX SELECT device, max(value) FROM metrics GROUP BY device HAVING min(value) < 25;
225225
--- QUERY PLAN ---
226226
GroupAggregate
227-
Group Key: _hyper_1_1_chunk.device
228-
Filter: (min(_hyper_1_1_chunk.value) < '25'::double precision)
229-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
227+
Group Key: device
228+
Filter: (min(value) < '25'::double precision)
229+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
230230
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
231231

232232
-- HAVING
233233
:PREFIX SELECT device, min(time) FROM metrics GROUP BY device HAVING min(time) > '2025-01-01 00:30:00 PST';
234234
--- QUERY PLAN ---
235235
GroupAggregate
236-
Group Key: _hyper_1_1_chunk.device
237-
Filter: (min(_hyper_1_1_chunk."time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
236+
Group Key: device
237+
Filter: (min("time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
239239
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
240240

241241
-- tableoid doesnt prevent optimization

tsl/test/expected/columnar_index_scan-16.out

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,26 +216,26 @@ SHOW timescaledb.enable_columnarindexscan;
216216
:PREFIX SELECT device, min(value) FROM metrics GROUP BY device HAVING min(value) > 20;
217217
--- QUERY PLAN ---
218218
GroupAggregate
219-
Group Key: _hyper_1_1_chunk.device
220-
Filter: (min(_hyper_1_1_chunk.value) > '20'::double precision)
221-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
219+
Group Key: device
220+
Filter: (min(value) > '20'::double precision)
221+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
222222
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
223223

224224
:PREFIX SELECT device, max(value) FROM metrics GROUP BY device HAVING min(value) < 25;
225225
--- QUERY PLAN ---
226226
GroupAggregate
227-
Group Key: _hyper_1_1_chunk.device
228-
Filter: (min(_hyper_1_1_chunk.value) < '25'::double precision)
229-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
227+
Group Key: device
228+
Filter: (min(value) < '25'::double precision)
229+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
230230
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
231231

232232
-- HAVING
233233
:PREFIX SELECT device, min(time) FROM metrics GROUP BY device HAVING min(time) > '2025-01-01 00:30:00 PST';
234234
--- QUERY PLAN ---
235235
GroupAggregate
236-
Group Key: _hyper_1_1_chunk.device
237-
Filter: (min(_hyper_1_1_chunk."time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
236+
Group Key: device
237+
Filter: (min("time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
239239
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
240240

241241
-- tableoid doesnt prevent optimization

tsl/test/expected/columnar_index_scan-17.out

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,26 +216,26 @@ SHOW timescaledb.enable_columnarindexscan;
216216
:PREFIX SELECT device, min(value) FROM metrics GROUP BY device HAVING min(value) > 20;
217217
--- QUERY PLAN ---
218218
GroupAggregate
219-
Group Key: _hyper_1_1_chunk.device
220-
Filter: (min(_hyper_1_1_chunk.value) > '20'::double precision)
221-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
219+
Group Key: device
220+
Filter: (min(value) > '20'::double precision)
221+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
222222
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
223223

224224
:PREFIX SELECT device, max(value) FROM metrics GROUP BY device HAVING min(value) < 25;
225225
--- QUERY PLAN ---
226226
GroupAggregate
227-
Group Key: _hyper_1_1_chunk.device
228-
Filter: (min(_hyper_1_1_chunk.value) < '25'::double precision)
229-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
227+
Group Key: device
228+
Filter: (min(value) < '25'::double precision)
229+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
230230
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
231231

232232
-- HAVING
233233
:PREFIX SELECT device, min(time) FROM metrics GROUP BY device HAVING min(time) > '2025-01-01 00:30:00 PST';
234234
--- QUERY PLAN ---
235235
GroupAggregate
236-
Group Key: _hyper_1_1_chunk.device
237-
Filter: (min(_hyper_1_1_chunk."time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
236+
Group Key: device
237+
Filter: (min("time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
239239
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
240240

241241
-- tableoid doesnt prevent optimization

tsl/test/expected/columnar_index_scan-18.out

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -216,26 +216,26 @@ SHOW timescaledb.enable_columnarindexscan;
216216
:PREFIX SELECT device, min(value) FROM metrics GROUP BY device HAVING min(value) > 20;
217217
--- QUERY PLAN ---
218218
GroupAggregate
219-
Group Key: _hyper_1_1_chunk.device
220-
Filter: (min(_hyper_1_1_chunk.value) > '20'::double precision)
221-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
219+
Group Key: device
220+
Filter: (min(value) > '20'::double precision)
221+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
222222
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
223223

224224
:PREFIX SELECT device, max(value) FROM metrics GROUP BY device HAVING min(value) < 25;
225225
--- QUERY PLAN ---
226226
GroupAggregate
227-
Group Key: _hyper_1_1_chunk.device
228-
Filter: (min(_hyper_1_1_chunk.value) < '25'::double precision)
229-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
227+
Group Key: device
228+
Filter: (min(value) < '25'::double precision)
229+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
230230
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
231231

232232
-- HAVING
233233
:PREFIX SELECT device, min(time) FROM metrics GROUP BY device HAVING min(time) > '2025-01-01 00:30:00 PST';
234234
--- QUERY PLAN ---
235235
GroupAggregate
236-
Group Key: _hyper_1_1_chunk.device
237-
Filter: (min(_hyper_1_1_chunk."time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
236+
Group Key: device
237+
Filter: (min("time") > 'Wed Jan 01 00:30:00 2025 PST'::timestamp with time zone)
238+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
239239
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
240240

241241
-- tableoid doesnt prevent optimization

tsl/test/expected/compress_unordered_sort.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,9 +254,9 @@ select device, sensor, avg(value+1), max(time) + make_interval(days => length(se
254254
:PREFIX select device, sensor, count(*) from metrics group by device, sensor having count(*) > length(sensor) order by 1,2;
255255
--- QUERY PLAN ---
256256
GroupAggregate
257-
Group Key: _hyper_1_1_chunk.device, _hyper_1_1_chunk.sensor
258-
Filter: (count(*) > length(_hyper_1_1_chunk.sensor))
259-
-> Custom Scan (ColumnarScan) on _hyper_1_1_chunk
257+
Group Key: device, sensor
258+
Filter: (COALESCE(sum(compress_hyper_2_2_chunk._ts_meta_count), '0'::bigint) > length(sensor))
259+
-> Custom Scan (ColumnarIndexScan) on _hyper_1_1_chunk metrics
260260
-> Index Scan using compress_hyper_2_2_chunk_device_sensor__ts_meta_min_1__ts_m_idx on compress_hyper_2_2_chunk
261261

262262
select device, sensor, count(*) from metrics group by device, sensor having count(*) > length(sensor) order by 1,2;

0 commit comments

Comments
 (0)