You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Unify spatial and temporal aggregation handling across PromQL, SQL, and Elastic #488
ASAPQuery was built PromQL-first, which distinguishes "temporal" aggregation (sum_over_time(metric[5m]), explicit range) from "spatial" aggregation (sum by (cpu) (metric), implicit "last scrape interval"). That distinction is encoded today as enums (QueryPatternType, QueryType::Spatial) that don't map cleanly onto SQL or Elastic, where a time range is always explicit — and the implicit defaults each query language applies have diverged, producing at least one concrete bug.
Status: Design decided, not yet implemented Touches: promql_utilities, asap-planner-rs, asap-query-engine
Background
ASAPQuery was built PromQL-first. PromQL distinguishes:
Temporal aggregation: sum_over_time(metric[5m]) — explicit range vector
Spatial aggregation: sum by (cpu) (metric) — implicit "last scrape interval"
This distinction was encoded as QueryPatternType { OnlyTemporal, OnlySpatial, OneTemporalOneSpatial } and caused branching in 8+ places across 3 crates. It also never mapped cleanly to SQL/Elastic, where a time range is always a time range.
The insight: OnlySpatial is just OnlyTemporal with range = scrape_interval_ms. All three variants are special cases of:
SELECT agg(col) WHERE time_range AND spatial_filter GROUP BY group_by_cols
Decisions
1. Kill the enums
QueryPatternType (in promql_utilities) — deleted entirely
QueryType::Spatial (in SQL planner) — deleted entirely
2. Time ranges are always explicit, filled in at plan time
PromQL sum(metric) (no range vector) → planner substitutes scrape_interval_ms
SQL spatial query (no time range) → planner substitutes data_ingestion_interval_ms
Elastic → fixed to use actual time range (was always using t_repeat_ms — bug)
Engine receives only concrete (start_timestamp, end_timestamp), no defaults
3. do_merge stays explicit in QueryExecutionContext
Field stays on the struct for clarity
Set by planner using derived check: range_ms > window_size_ms
First match wins; patterns are structurally disjoint so order doesn't affect correctness
Also fixes a latent bug: HashMap iteration is non-deterministic
5. Branch predicate at every former enum site
Replace match query_pattern_type { ... } with structural inspection of PromQLMatchResult:
match_result.get_range_duration().is_some() → has temporal function
match_result.get_aggregation_op().is_some() → has spatial aggregation
grouping labels (by/without in PromQL, GROUP BY in SQL) extracted directly
6. Shared promql_utilities functions inspect match result internally
Functions like get_statistics_to_compute and get_spatial_aggregation_output_labels currently take QueryPatternType as a param. After: they inspect PromQLMatchResult directly and derive what they need. No enum param.
What doesn't change
QueryExecutionContext struct shape stays the same (spatial_filter, grouping_labels, aggregated_labels were already unified fields)
Sketch selection logic (multi-population keyed vs single-population) is driven by whether grouping_labels is non-empty — no change needed there, it already uses the field not the enum
Staging: two PRs
PR 1 — Make all time ranges explicit (behavior change, fixes bugs)
PromQL planner: fill in scrape_interval_ms when no range vector present
SQL planner: fill in data_ingestion_interval_ms for spatial-style queries
Elastic planner: fix bug — use actual query time range instead of always t_repeat_ms
do_merge derived from range_ms > window_size_ms instead of pattern type
Enum still exists and compiles; all branches become trivially equivalent
Independently verifiable: time window correctness can be tested without touching the enum
PR 2 — Delete the enums (pure cleanup, no behavior change)
Delete QueryPatternType from promql_utilities
Delete QueryType::Spatial from SQL planner
Flatten controller_patterns to Vec<PromQLPattern>
Update get_statistics_to_compute, get_spatial_aggregation_output_labels to inspect match result internally
Replace all match query_pattern_type sites with structural predicates
Easy to review: no behavior should change, compiler enforces completeness
test_esdsl_time_range_excludes_data_outside_range — stores data at t = now-5s, queries gte: now-1s, lte: now, t_repeat_ms=30s. With the bug the 5s-old data is included (30s
window). After PR 1 the result is empty — data is outside the 1s range. This is the only
genuinely red test; it targets the Elastic bug directly.
PR 2 — Pinning tests (pass now, must still pass after enum is deleted)
These lock in the correct behavior so PR 2 can be reviewed as a pure mechanical cleanup.
Note: the PR 2 pinning tests will need their QueryPatternType param replaced when the enum
is deleted, but the numeric assertions stay identical — that's the regression guard.
Key files touched
File
What changes
promql_utilities/src/query_logics/enums.rs
Delete QueryPatternType (PR 2)
asap-planner-rs/src/planner/window.rs:51
Spatial window size → derived from explicit range (PR 1)
asap-planner-rs/src/planner/cleanup.rs:26
Spatial lookback → derived from explicit range (PR 1)
asap-planner-rs/src/planner/promql.rs
Fill in scrape_interval_ms when no range vector (PR 1); kill enum branches (PR 2)
asap-planner-rs/src/planner/sql.rs:168
Fill in data_ingestion_interval_ms; kill QueryType::Spatial (PR 1+2)
Activity
milindsrivastava1997 commentedon Jun 30, 2026
Spatial/Temporal Aggregation Unification
Status: Design decided, not yet implemented
Touches:
promql_utilities,asap-planner-rs,asap-query-engineBackground
ASAPQuery was built PromQL-first. PromQL distinguishes:
sum_over_time(metric[5m])— explicit range vectorsum by (cpu) (metric)— implicit "last scrape interval"This distinction was encoded as
QueryPatternType { OnlyTemporal, OnlySpatial, OneTemporalOneSpatial }and caused branching in 8+ places across 3 crates. It also never mapped cleanly to SQL/Elastic, where a time range is always a time range.The insight:
OnlySpatialis justOnlyTemporalwithrange = scrape_interval_ms. All three variants are special cases of:Decisions
1. Kill the enums
QueryPatternType(inpromql_utilities) — deleted entirelyQueryType::Spatial(in SQL planner) — deleted entirely2. Time ranges are always explicit, filled in at plan time
sum(metric)(no range vector) → planner substitutesscrape_interval_msdata_ingestion_interval_mst_repeat_ms— bug)(start_timestamp, end_timestamp), no defaults3.
do_mergestays explicit inQueryExecutionContextrange_ms > window_size_msQueryPatternTypeanymore4. Pattern registry flattened
controller_patterns: HashMap<QueryPatternType, Vec<PromQLPattern>>→Vec<PromQLPattern>5. Branch predicate at every former enum site
Replace
match query_pattern_type { ... }with structural inspection ofPromQLMatchResult:match_result.get_range_duration().is_some()→ has temporal functionmatch_result.get_aggregation_op().is_some()→ has spatial aggregationby/withoutin PromQL,GROUP BYin SQL) extracted directly6. Shared
promql_utilitiesfunctions inspect match result internallyFunctions like
get_statistics_to_computeandget_spatial_aggregation_output_labelscurrently takeQueryPatternTypeas a param. After: they inspectPromQLMatchResultdirectly and derive what they need. No enum param.What doesn't change
QueryExecutionContextstruct shape stays the same (spatial_filter,grouping_labels,aggregated_labelswere already unified fields)grouping_labelsis non-empty — no change needed there, it already uses the field not the enumStaging: two PRs
PR 1 — Make all time ranges explicit (behavior change, fixes bugs)
scrape_interval_mswhen no range vector presentdata_ingestion_interval_msfor spatial-style queriest_repeat_msdo_mergederived fromrange_ms > window_size_msinstead of pattern typePR 2 — Delete the enums (pure cleanup, no behavior change)
QueryPatternTypefrompromql_utilitiesQueryType::Spatialfrom SQL plannercontroller_patternstoVec<PromQLPattern>get_statistics_to_compute,get_spatial_aggregation_output_labelsto inspect match result internallymatch query_pattern_typesites with structural predicatesTDD tests
PR 1 — Red tests (fail now, pass after fix)
asap-query-engine/src/tests/elastic_dsl_query_tests.rstest_esdsl_time_range_excludes_data_outside_range— stores data att = now-5s, queriesgte: now-1s, lte: now,t_repeat_ms=30s. With the bug the 5s-old data is included (30swindow). After PR 1 the result is empty — data is outside the 1s range. This is the only
genuinely red test; it targets the Elastic bug directly.
PR 2 — Pinning tests (pass now, must still pass after enum is deleted)
These lock in the correct behavior so PR 2 can be reviewed as a pure mechanical cleanup.
asap-query-engine/src/tests/query_equivalence_tests.rs— extend existing patterntest_spatial_start_timestamp_equals_end_minus_scrape_interval—sum(cpu_usage),scrape_interval=15s,query_time=1000s→ assertcontext.store_plan.values_query.start_timestamp == 999_985_000.test_temporal_start_timestamp_equals_end_minus_range—sum_over_time(cpu_usage[5m]),query_time=1000s→ assertstart_timestamp == 999_700_000.test_do_merge_false_for_spatial_context—sum(metric)wherewindow_size == scrape_interval→ assert
context.do_merge == false.test_do_merge_true_for_temporal_context—sum_over_time(metric[5m])whererange_ms > window_size_ms→ assertcontext.do_merge == true.asap-planner-rs/src/planner/window.rs— theOnlySpatialbranch has no test todayspatial_window_size_equals_data_ingestion_interval—set_window_parameters(OnlySpatial, t_repeat=300_000, ingestion=15_000, ...)→ assertconfig.window_size_ms == 15_000.temporal_window_size_equals_effective_repeat—set_window_parameters(OnlyTemporal, t_repeat=300_000, step=30_000, ...)→ assertconfig.window_size_ms == 30_000.Note: the PR 2 pinning tests will need their
QueryPatternTypeparam replaced when the enumis deleted, but the numeric assertions stay identical — that's the regression guard.
Key files touched
promql_utilities/src/query_logics/enums.rsQueryPatternType(PR 2)asap-planner-rs/src/planner/window.rs:51asap-planner-rs/src/planner/cleanup.rs:26asap-planner-rs/src/planner/promql.rsscrape_interval_mswhen no range vector (PR 1); kill enum branches (PR 2)asap-planner-rs/src/planner/sql.rs:168data_ingestion_interval_ms; killQueryType::Spatial(PR 1+2)asap-planner-rs/src/planner/elastic_dsl.rsasap-query-engine/src/engines/simple_engine/mod.rs:140controller_patterns(PR 2)asap-query-engine/src/engines/simple_engine/promql.rsQueryPatternTypebranches (PR 2)asap-query-engine/src/engines/simple_engine/sql.rsQueryTypebranch (PR 2)milindsrivastava1997 commentedon Aug 28, 2026
Closing. Only remaining work is tracked in #485