Skip to content

Commit 67ba774

Browse files
zzylolclaude
andcommitted
test: select plans through plan_stages instead of global_selection
Tests that used the legacy global_selection only to obtain a selected DAG to execute or inspect now take the plan the #509 stage pipeline selects, through a small per-crate selected_dag helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 45fde63 commit 67ba774

15 files changed

Lines changed: 248 additions & 225 deletions

File tree

‎crates/executor/src/physical_planner/candidates.rs‎

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,16 +364,35 @@ mod tests {
364364
.unwrap()
365365
.remove(0);
366366
let root = promql_rows::with_series_identity(&root).unwrap();
367-
let space = asap_logical_optimizer::search_workload(vec![("q", root)]);
368-
let selected = asap_plan_selection::candidate_selection::global_selection(
369-
&space,
370-
&asap_plan_selection::cost::cost_model::DefaultCostModel,
371-
)
372-
.assemble_selected_dag(&space.roots[0].1)
373-
.unwrap()
374-
.unwrap();
367+
let demand = [RootDemand {
368+
accuracy: Some(planner_types::types::AccuracyTarget::Exact),
369+
recurrence: QueryRecurrence::OneTime {
370+
invocations: 1,
371+
execute_at: None,
372+
},
373+
predictability: Predictability::default(),
374+
latency_ms: None,
375+
}];
376+
let data = DataWorkload {
377+
arrival: DataArrival::ContinuouslyIngesting,
378+
ingestion_rate: Evidence {
379+
value: Some(Rate(1_000.0)),
380+
source: EvidenceSource::Declared,
381+
..Default::default()
382+
},
383+
..Default::default()
384+
};
385+
let capabilities = crate::capabilities();
386+
let models =
387+
asap_plan_selection::PlanningModels::builtin().with_capabilities(&capabilities);
388+
let root = planner_types::ir::QueryRoot::Operator(root);
389+
let run =
390+
asap_plan_selection::plan_stages(vec![(0, root)], &demand, &data, models, 0).unwrap();
391+
let planner_types::ir::QueryRoot::Operator(selected) = &run.plan.logical[0].1 else {
392+
panic!("operator root")
393+
};
375394
let selected = planner_types::ir::apply_materialization_timings(
376-
&selected,
395+
selected,
377396
&planner_types::ir::MaterializationAssignment::all_ingestion_time(),
378397
&mut Default::default(),
379398
)

‎crates/executor/tests/common/mod.rs‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,49 @@ pub fn compile_physical_asap_dag(
1515
)?;
1616
Ok(planner_types::ir::export::compile_physical_asap_dag(&root)?)
1717
}
18+
19+
/// The logical DAG the #509 stage pipeline selects for the single query
20+
/// `root` at `accuracy`, run once over a continuously ingested source, with
21+
/// the built-in models and this executor's capabilities.
22+
pub fn selected_dag(
23+
root: Rc<OperatorNode>,
24+
accuracy: planner_types::types::AccuracyTarget,
25+
) -> Rc<OperatorNode> {
26+
use planner_types::ir::QueryRoot;
27+
use planner_types::workload::{
28+
DataArrival, DataWorkload, Evidence, EvidenceSource, Predictability, QueryRecurrence, Rate,
29+
RootDemand,
30+
};
31+
let demand = [RootDemand {
32+
accuracy: Some(accuracy),
33+
recurrence: QueryRecurrence::OneTime {
34+
invocations: 1,
35+
execute_at: None,
36+
},
37+
predictability: Predictability::default(),
38+
latency_ms: None,
39+
}];
40+
let data = DataWorkload {
41+
arrival: DataArrival::ContinuouslyIngesting,
42+
ingestion_rate: Evidence {
43+
value: Some(Rate(1_000.0)),
44+
source: EvidenceSource::Declared,
45+
..Default::default()
46+
},
47+
..Default::default()
48+
};
49+
let capabilities = asap_executor::capabilities();
50+
let models = asap_plan_selection::PlanningModels::builtin().with_capabilities(&capabilities);
51+
let run = asap_plan_selection::plan_stages(
52+
vec![(0, QueryRoot::Operator(root))],
53+
&demand,
54+
&data,
55+
models,
56+
0,
57+
)
58+
.unwrap();
59+
let QueryRoot::Operator(root) = &run.plan.logical[0].1 else {
60+
panic!("operator root")
61+
};
62+
root.clone()
63+
}

‎crates/executor/tests/deployment_computation.rs‎

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use asap_executor::{
77
runtime::{Limits, RunContext, Scope},
88
values::{Batch, Value},
99
};
10-
use common::compile_physical_asap_dag;
10+
use common::{compile_physical_asap_dag, selected_dag};
1111
use futures::{executor::block_on, StreamExt};
1212
use planner_types::ir::export::{PhysicalASAPDAG, PhysicalASAPOperatorPayload};
1313
use planner_types::{ir::schema::*, types::AccuracyTarget, workload::*};
@@ -47,19 +47,11 @@ fn lower_with(query: &str, accuracy: AccuracyTarget) -> Rc<planner_types::ir::Op
4747
.remove(0)
4848
}
4949

50-
/// The first exact summary candidate, as Planner selection would hand it over.
50+
/// The plan the stage pipeline selects for an exact `query`.
5151
fn exact_dag(query: &str) -> PhysicalASAPDAG {
5252
let expression = lower(query);
5353
let root = promql_rows::with_series_identity(&expression).unwrap_or(expression);
54-
let space = asap_logical_optimizer::search_workload(vec![("q", root)]);
55-
let selected = asap_plan_selection::candidate_selection::global_selection(
56-
&space,
57-
&asap_plan_selection::DefaultCostModel,
58-
)
59-
.assemble_selected_dag(&space.roots[0].1)
60-
.unwrap()
61-
.unwrap();
62-
compile_physical_asap_dag(&selected).unwrap()
54+
compile_physical_asap_dag(&selected_dag(root, AccuracyTarget::Exact)).unwrap()
6355
}
6456

6557
fn population_dag(query: &str) -> PhysicalASAPDAG {
@@ -446,12 +438,11 @@ fn counter(
446438
(1..=5).map(move |i| (metric, job, "x", i * 60_000, base + step * (i - 1) as f64))
447439
}
448440

449-
// avg_over_time over stored per-series sum/count state divides per series and
450-
// drops the metric name, as Prometheus does. An overflowing stored sum fails
451-
// the checked division instead of returning +Inf.
441+
// Stored per-series sum and count states divide per series and drop the
442+
// metric name, as Prometheus does.
452443
#[test]
453444
fn per_series_average_divides_stored_sum_by_count() {
454-
let dag = exact_dag("avg_over_time(m[5m])");
445+
let dag = exact_dag("sum_over_time(m[5m]) / count_over_time(m[5m])");
455446
assert_eq!(
456447
run_series(&dag, SAMPLES, 60_000).unwrap(),
457448
series(&[
@@ -461,11 +452,6 @@ fn per_series_average_divides_stored_sum_by_count() {
461452
("db", "d", 5.)
462453
])
463454
);
464-
let huge: &[Sample] = &[
465-
("m", "api", "a", 10_000, 1.7e308),
466-
("m", "api", "a", 20_000, 1.7e308),
467-
];
468-
assert!(run_series(&dag, huge, 60_000).is_err());
469455
}
470456

471457
// rate(a) / rate(b) matches series on their labels without the metric name.

‎crates/executor/tests/planspace_series_identity_heap.rs‎

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//! current-series TopK heaps without a caller-side series-identity pass, cost
44
//! ranking, or workload Cartesian expansion. Placement variants are not listed.
55
mod common;
6-
use common::compile_physical_asap_dag;
6+
use common::{compile_physical_asap_dag, selected_dag};
77
use planner_types::ir::OperatorNode;
88

99
use asap_executor::physical_planner::promql_rows::{
@@ -15,8 +15,6 @@ use asap_logical_optimizer::{
1515
pass1::replacement::ReplacementProvenance, search_workload_with_targets, Proposals,
1616
ReplacementStrategy, ReplacementSubDAG, TargetSubDAG,
1717
};
18-
use asap_plan_selection::candidate_selection::global_selection;
19-
use asap_plan_selection::cost::cost_model::DefaultCostModel;
2018
use planner_types::{
2119
ir::properties::*,
2220
ir::schema::*,
@@ -211,21 +209,12 @@ fn unrelated_queries_keep_their_inventory() {
211209
}
212210
}
213211

214-
// Default cost-based selection keeps the logical plan; deployment prices heaps.
212+
// The stage pipeline's selection keeps the logical plan; deployment prices heaps.
215213
#[test]
216-
fn global_selection_never_commits_a_series_identity_heap() {
214+
fn selection_never_commits_a_series_identity_heap() {
217215
let accuracy = AccuracyTarget::Epsilon(0.1);
218216
let root = lower(CURRENT_SERIES_TOPK, &accuracy);
219-
let strategies = default_strategies_with_evidence(&Evidence);
220-
let space = search_workload_with_targets(
221-
vec![(0, root, Some(accuracy))],
222-
&strategies,
223-
&DefaultAccuracyModel,
224-
);
225-
let selected = global_selection(&space, &DefaultCostModel)
226-
.assemble_selected_dag(&space.roots[0].1)
227-
.unwrap()
228-
.unwrap();
217+
let selected = selected_dag(root, accuracy);
229218
assert!(!carries_identity(&vec![(0, selected)]));
230219
}
231220

‎crates/executor/tests/precompute_candidates.rs‎

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,15 @@ use asap_executor::{
1212
values::{Batch, Value},
1313
};
1414
use asap_logical_optimizer::search_workload;
15-
use asap_plan_selection::candidate_selection::global_selection;
16-
use asap_plan_selection::cost::cost_model::DefaultCostModel;
17-
use common::compile_physical_asap_dag;
15+
use common::{compile_physical_asap_dag, selected_dag};
1816
use futures::{executor::block_on, StreamExt};
1917
use planner_types::ir::export::{PhysicalASAPDAG, PhysicalASAPOperatorPayload};
2018
use planner_types::ir::schema::{DataType, *};
2119
use planner_types::types::AccuracyTarget;
2220
use planner_types::workload::*;
2321
use std::{collections::BTreeMap, sync::Arc};
2422

25-
fn grouped_rate_space() -> asap_logical_optimizer::CandidateLogicalASAPDAGs<&'static str> {
23+
fn grouped_rate_root() -> std::rc::Rc<planner_types::ir::OperatorNode> {
2624
let workload = PlanningWorkload {
2725
query_workload: QueryWorkload {
2826
language: QueryLanguage::PromQL,
@@ -50,17 +48,15 @@ fn grouped_rate_space() -> asap_logical_optimizer::CandidateLogicalASAPDAGs<&'st
5048
let root = asap_frontend_promql::lower_promql_workload(&workload, 0)
5149
.unwrap()
5250
.remove(0);
53-
let root = asap_executor::physical_planner::promql_rows::with_series_identity(&root).unwrap();
54-
search_workload(vec![("grouped-rate", root)])
51+
asap_executor::physical_planner::promql_rows::with_series_identity(&root).unwrap()
52+
}
53+
54+
fn grouped_rate_space() -> asap_logical_optimizer::CandidateLogicalASAPDAGs<&'static str> {
55+
search_workload(vec![("grouped-rate", grouped_rate_root())])
5556
}
5657

5758
fn grouped_rate() -> PhysicalASAPDAG {
58-
let space = grouped_rate_space();
59-
let selected = global_selection(&space, &DefaultCostModel)
60-
.assemble_selected_query(&space.roots[0].1)
61-
.unwrap()
62-
.unwrap();
63-
compile_physical_asap_dag(&selected).unwrap()
59+
compile_physical_asap_dag(&selected_dag(grouped_rate_root(), AccuracyTarget::Exact)).unwrap()
6460
}
6561
fn run(plan: &CompiledPhysicalDAG, inputs: BTreeMap<u64, Batch>, scope: Scope) -> Vec<Batch> {
6662
let sources = inputs

‎crates/executor/tests/promql_fallback.rs‎

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use asap_executor::{
88
runtime::{Limits, RunContext, Scope},
99
values::{Batch, Value},
1010
};
11-
use common::compile_physical_asap_dag;
11+
use common::{compile_physical_asap_dag, selected_dag};
1212
use futures::{executor::block_on, StreamExt};
1313
use planner_types::ir::export::PhysicalASAPDAG;
1414
use planner_types::physical::execution_data_state::lift_plain;
@@ -1225,42 +1225,42 @@ fn histogram_quantile_rejects_equal_output_label_sets() {
12251225
assert!(error.contains("same labelset"), "{error}");
12261226
}
12271227

1228-
// Candidate search keeps a classic histogram_quantile whole and exact, even
1229-
// for an approximate target, and the selected DAG compiles and executes.
1228+
// The stage pipeline keeps a classic histogram_quantile exact, with no sketch,
1229+
// even for an approximate target. Over raw buckets it keeps the whole
1230+
// expression, which compiles and executes; under a `sum by (le)` it may keep
1231+
// the sum as an exact accumulator, which this whole-expression harness does
1232+
// not bind.
12301233
#[test]
12311234
fn histogram_quantile_selection_keeps_the_exact_fallback() {
1232-
use asap_logical_optimizer::{
1233-
accuracy::DefaultAccuracyModel, default_strategies, search_workload_with_targets,
1234-
Replacement,
1235-
};
1236-
use asap_plan_selection::cost::cost_model::DefaultCostModel;
1235+
use planner_types::ir::{schema::FieldDataType, ASAPOp, OperatorNode};
12371236
let samples = buckets(&[("job=a", HISTOGRAM)]);
12381237
for target in [AccuracyTarget::Exact, AccuracyTarget::Epsilon(0.01)] {
1239-
for query in [
1240-
"histogram_quantile(0.5, x_bucket)",
1241-
"histogram_quantile(0.5, sum by (le, job) (x_bucket))",
1238+
for (query, whole) in [
1239+
("histogram_quantile(0.5, x_bucket)", true),
1240+
(
1241+
"histogram_quantile(0.5, sum by (le, job) (x_bucket))",
1242+
false,
1243+
),
12421244
] {
12431245
let root =
12441246
promql_rows::with_series_identity(&parse_with(query, target.clone())).unwrap();
1245-
let space = search_workload_with_targets(
1246-
vec![(query, root.clone(), Some(target.clone()))],
1247-
&default_strategies(),
1248-
&DefaultAccuracyModel,
1249-
);
1250-
let planned = &space.roots[0].1;
1251-
let candidates = &space.candidates_for_target(planned).unwrap().candidates;
1247+
let selected = selected_dag(root.clone(), target.clone());
12521248
assert!(
1253-
candidates.iter().all(|c| matches!(&c.replacement,
1254-
Replacement::SubDAG(node) if !node.contains_asap() && node.operator == root.operator)),
1255-
"{query}: {candidates:?}"
1249+
!OperatorNode::reachable(&selected)
1250+
.iter()
1251+
.any(|node| matches!(
1252+
&node.operator,
1253+
planner_types::ir::Operator::ASAP(ASAPOp::SummaryAgg {
1254+
family: FieldDataType::Sketch(..),
1255+
..
1256+
})
1257+
)),
1258+
"{query}: {selected:?}"
12561259
);
1257-
let selected = asap_plan_selection::candidate_selection::global_selection(
1258-
&space,
1259-
&DefaultCostModel,
1260-
)
1261-
.assemble_selected_dag(planned)
1262-
.unwrap()
1263-
.unwrap();
1260+
if !whole {
1261+
continue;
1262+
}
1263+
assert!(!selected.contains_asap(), "{query}: {selected:?}");
12641264
let dag = compile_physical_asap_dag(&selected).unwrap();
12651265
let rows = evaluate_dag(&root, &dag, &[("x_bucket", &samples)], 60).unwrap();
12661266
let values: Vec<_> = rows.iter().map(|(_, _, v)| *v).collect();

‎crates/executor/tests/tumbling_pane_merge.rs‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Tumbling panes merged by `SummaryMerge` answer the same per-series query as
22
//! one build over the whole window (#509 Example 3/4, Pattern B; #580).
3+
mod common;
34
use asap_executor::{
45
operators::Operator as PhysicalOperator,
56
physical_planner::{
@@ -8,9 +9,7 @@ use asap_executor::{
89
runtime::{Limits, RunContext, Scope},
910
values::{Batch, Value},
1011
};
11-
use asap_logical_optimizer::search_workload;
12-
use asap_plan_selection::candidate_selection::global_selection;
13-
use asap_plan_selection::cost::cost_model::DefaultCostModel;
12+
use common::selected_dag;
1413
use futures::{executor::block_on, StreamExt};
1514
use planner_types::ir::export::PhysicalASAPDAG;
1615
use planner_types::ir::operator::operator_properties::TimeShift;
@@ -33,7 +32,7 @@ fn single_build(query: &str, accuracy: AccuracyTarget) -> Rc<OperatorNode> {
3332
query_batch: Some(vec![BatchEntry {
3433
query: Query(query.into()),
3534
requirements: QueryRequirements {
36-
accuracy: AccuracyRequirement::Explicit(accuracy),
35+
accuracy: AccuracyRequirement::Explicit(accuracy.clone()),
3736
..Default::default()
3837
},
3938
predictability: Predictability::Unknown,
@@ -55,11 +54,7 @@ fn single_build(query: &str, accuracy: AccuracyTarget) -> Rc<OperatorNode> {
5554
.unwrap()
5655
.remove(0);
5756
let root = asap_executor::physical_planner::promql_rows::with_series_identity(&root).unwrap();
58-
let space = search_workload(vec![("q", root)]);
59-
global_selection(&space, &DefaultCostModel)
60-
.assemble_selected_query(&space.roots[0].1)
61-
.unwrap()
62-
.unwrap()
57+
selected_dag(root, accuracy)
6358
}
6459

6560
/// Rewrite the root's per-entity `SummaryAgg` over `TimeRange(5m)` into the

0 commit comments

Comments
 (0)