Catalog of behavioral inconsistencies between the PromQL instant-query and range-query paths in asap-query-engine/src/engines/simple_engine/{mod,promql}.rs, found while scoping #584/#583/#581 (see #587). Filing so each item gets its own fix/decision instead of getting re-discovered piecemeal.
1. Self-keyed expansion, single-population (#584)
collect_results_same_aggregation (mod.rs:1275) calls get_keys() on every value accumulator and falls back to the raw key if None. execute_range_query_pipeline's no-keys_query branch (mod.rs:1551-1556) never calls get_keys() at all, and also drops every None-keyed group via group_key.as_ref().map(...). Top-k over a range with no separate keys aggregation returns empty. Fix in progress: #587.
2. Keys snapshot is single-shot in range, not per-step (#583)
Range fetches keys_query once, anchored at end (mod.rs:1491-1495), and reuses that snapshot for every output step. handle_binary_expr_range_promql's arms hit the same single-snapshot behavior via build_arm_range_context → finish_range_context (promql.rs:610, 551) — a second instance of the same bug class, not just the plain-query path.
3. keys_query window is instant-shaped even when widened for range
create_keys_query_params (mod.rs:352) computes a window from one end_timestamp; finish_range_context (promql.rs:551) widens values_query for the range but clones keys_query unchanged. For SetAggregator ("latest window only"), the range loop iterates merged_keys rather than all_data (mod.rs:1538-1557), so a label that only had samples earlier in the range but fell out of the final window's key snapshot is never iterated at all — its whole history silently disappears, not just goes stale.
4. Sliding-window fetch strategy diverges between instant and range
Instant uses the exact-match store call for Sliding aggregations (mod.rs:469, query_precomputed_output_exact). Range unconditionally forces is_exact_query = false (promql.rs:585) and instead does one wide range fetch plus manual bucket_map/window reconstruction (mod.rs:1560-1642). Two independent implementations of "which buckets fall in this window" for the same aggregation type.
5. WindowMerger::slide() is defined but unused on the range path
The trait supports incremental add/remove (window_merger.rs:24), but execute_range_query_pipeline calls initialize() + get_merged() fresh every step (mod.rs:1597-1598) instead — full recompute per step, and a second merge implementation (vs. merge_accumulators's fold in the instant path) that can silently diverge per accumulator type as new types are added.
6. Every inconsistency above is duplicated inside binary-expr arms
evaluate_binary_arm/execute_query_pipeline (instant) vs. build_arm_range_context/execute_range_query_pipeline (range) are separate forks of the same logic (promql.rs:431, 610). A fix to the plain-query path does not automatically fix the binary-expr path — #582's dual-population fix needed a separate review comment calling out build_arm_range_context specifically.
7. Error semantics differ by branch, not consistently by instant/range
Range's dual-population branch hard-fails the whole query if a group_key has no matching value (mod.rs:1541-1550, mirroring collect_results_separate_keys's error). The single-population branch has no equivalent check and just silently omits missing groups. Needs a decision on whether this asymmetry is intentional, then applying it uniformly.
Root cause (tracked separately as #581)
Items 4-6 stem from PromQL instant and range queries using two independently implemented fetch/merge designs for the same underlying job rather than one design expressed twice. Unifying them (#581) would resolve most of this list structurally instead of one item at a time.
Related: #584, #583, #581, #587.
Catalog of behavioral inconsistencies between the PromQL instant-query and range-query paths in
asap-query-engine/src/engines/simple_engine/{mod,promql}.rs, found while scoping #584/#583/#581 (see #587). Filing so each item gets its own fix/decision instead of getting re-discovered piecemeal.1. Self-keyed expansion, single-population (#584)
collect_results_same_aggregation(mod.rs:1275) callsget_keys()on every value accumulator and falls back to the raw key ifNone.execute_range_query_pipeline's no-keys_querybranch (mod.rs:1551-1556) never callsget_keys()at all, and also drops everyNone-keyed group viagroup_key.as_ref().map(...). Top-k over a range with no separate keys aggregation returns empty. Fix in progress: #587.2. Keys snapshot is single-shot in range, not per-step (#583)
Range fetches
keys_queryonce, anchored atend(mod.rs:1491-1495), and reuses that snapshot for every output step.handle_binary_expr_range_promql's arms hit the same single-snapshot behavior viabuild_arm_range_context→finish_range_context(promql.rs:610, 551) — a second instance of the same bug class, not just the plain-query path.3.
keys_querywindow is instant-shaped even when widened for rangecreate_keys_query_params(mod.rs:352) computes a window from oneend_timestamp;finish_range_context(promql.rs:551) widensvalues_queryfor the range but cloneskeys_queryunchanged. ForSetAggregator("latest window only"), the range loop iteratesmerged_keysrather thanall_data(mod.rs:1538-1557), so a label that only had samples earlier in the range but fell out of the final window's key snapshot is never iterated at all — its whole history silently disappears, not just goes stale.4. Sliding-window fetch strategy diverges between instant and range
Instant uses the exact-match store call for Sliding aggregations (mod.rs:469,
query_precomputed_output_exact). Range unconditionally forcesis_exact_query = false(promql.rs:585) and instead does one wide range fetch plus manualbucket_map/window reconstruction (mod.rs:1560-1642). Two independent implementations of "which buckets fall in this window" for the same aggregation type.5.
WindowMerger::slide()is defined but unused on the range pathThe trait supports incremental add/remove (window_merger.rs:24), but
execute_range_query_pipelinecallsinitialize()+get_merged()fresh every step (mod.rs:1597-1598) instead — full recompute per step, and a second merge implementation (vs.merge_accumulators's fold in the instant path) that can silently diverge per accumulator type as new types are added.6. Every inconsistency above is duplicated inside binary-expr arms
evaluate_binary_arm/execute_query_pipeline(instant) vs.build_arm_range_context/execute_range_query_pipeline(range) are separate forks of the same logic (promql.rs:431, 610). A fix to the plain-query path does not automatically fix the binary-expr path — #582's dual-population fix needed a separate review comment calling outbuild_arm_range_contextspecifically.7. Error semantics differ by branch, not consistently by instant/range
Range's dual-population branch hard-fails the whole query if a
group_keyhas no matching value (mod.rs:1541-1550, mirroringcollect_results_separate_keys's error). The single-population branch has no equivalent check and just silently omits missing groups. Needs a decision on whether this asymmetry is intentional, then applying it uniformly.Root cause (tracked separately as #581)
Items 4-6 stem from PromQL instant and range queries using two independently implemented fetch/merge designs for the same underlying job rather than one design expressed twice. Unifying them (#581) would resolve most of this list structurally instead of one item at a time.
Related: #584, #583, #581, #587.