Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
29e1e53
docs: define how PlanSpace, PlanOutput, and PostAsapDag relate
zzylol Sep 29, 2026
1c73b1a
docs: layer planner output as candidate, selection, lifecycle, physic…
zzylol Sep 30, 2026
109a2eb
docs: place PlanOutput lifecycle variant on the library path
zzylol Sep 30, 2026
222ee0d
docs: propose the Planner and deployment layering contract
zzylol Sep 30, 2026
e575054
docs: name Planner graph stages and clarify compilation layers
zzylol Sep 30, 2026
cb50cac
docs: label lifecycle output as PostASAPDAG with timing
zzylol Sep 30, 2026
64fc6e9
docs: expose timed lifecycle candidate DAGs before selection
zzylol Sep 30, 2026
cd64eb6
docs: preserve all candidate DAGs through every Planner layer
zzylol Sep 30, 2026
6574877
docs: distinguish candidate DAG collections from individual DAGs
zzylol Sep 30, 2026
b3e679e
docs: unify candidate generation layer outputs
zzylol Sep 30, 2026
27fc8eb
docs: name frontend candidate collection CandidatePreASAPDAGs
zzylol Sep 30, 2026
481e172
docs: record DAG API alignment implementation plan
zzylol Sep 30, 2026
21e234c
refactor!: align DAG node and graph API names
zzylol Sep 30, 2026
736096d
refactor: share logical timing assignments and physical candidates
xxxxxxxxxxxxx Sep 30, 2026
807fb5b
docs: document aligned DAG APIs and candidate migration
xxxxxxxxxxxxx Sep 30, 2026
ef34bbf
refactor: name frontend lowering lower_pre_asap_dag_candidates
xxxxxxxxxxxxx Sep 30, 2026
e4f7d6f
docs: clarify remaining lifecycle candidate API alignment
xxxxxxxxxxxxx Sep 30, 2026
7e006df
refactor!: unify timed logical and physical candidate collections
xxxxxxxxxxxxx Sep 30, 2026
7ca0ca2
docs: describe unified candidate collection boundaries
xxxxxxxxxxxxx Sep 30, 2026
ca4bc89
refactor!: present bound physical graphs as an execution handle
zzylol Sep 30, 2026
2b64791
refactor!: decouple physical candidate compilation from lifecycle pla…
zzylol Sep 30, 2026
c68ed1a
test: check timed candidates against independent assignment compilation
zzylol Sep 30, 2026
0f15b3b
refactor!: spell remaining Post-ASAP type names PostASAP
zzylol Sep 30, 2026
25d54e9
docs: match the DAG API docs to the implemented collection boundaries
zzylol Sep 30, 2026
a9d8856
fix: validate lifecycle pricing requests and type every expansion limit
zzylol Sep 30, 2026
eabbe9b
docs: describe selection as Planner's, over deployment-supplied prices
zzylol Sep 30, 2026
7df341b
docs: say the backend places work on machines, not choose timing
zzylol Sep 30, 2026
7fde378
docs: sync the layering proposal with #509
zzylol Sep 30, 2026
c8ff36b
refactor!: merge the lifecycle plan into LifecyclePostASAPDAG
zzylol Sep 30, 2026
e7453ee
docs: make selection Planner's, over the deployment's cost model
zzylol Sep 30, 2026
c3f10eb
docs: copy the layering proposal from #509 byte for byte
zzylol Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ASAPPlanner

ASAPPlanner turns SQL, PromQL, and MetricsQL query workloads into legal candidate plans that may use Approximate Streaming Analytics Primitives (ASAPs), such as sketches and exact summaries. It normalizes language-specific queries into a shared representation, then enumerates and ranks semantically equivalent alternatives. Downstream systems choose, deploy, and execute a physical plan.
ASAPPlanner turns SQL, PromQL, and MetricsQL query workloads into legal candidate plans that may use Approximate Streaming Analytics Primitives (ASAPs), such as sketches and exact summaries. It normalizes language-specific queries into a shared representation, then enumerates and ranks semantically equivalent alternatives. Planner selects one physical plan using the deployment's cost model; the deployment executes it.

## Start here

Expand Down
6 changes: 3 additions & 3 deletions crates/asap-aware-mapping/src/accuracy/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub trait AccuracyEvidenceProvider {
/// An observed cardinality is not an enforced population bound.
fn estimator_contract(
&self,
_expression: &asap_types::pre_asap::QueryExpr,
_expression: &asap_types::pre_asap::PreASAPNode,
) -> Option<EstimatorContract> {
None
}
Expand All @@ -101,7 +101,7 @@ pub trait AccuracyEvidenceProvider {
/// selected candidates. Observed cardinality is not sufficient evidence.
fn topk_max_distinct_items(
&self,
_expression: &asap_types::pre_asap::QueryExpr,
_expression: &asap_types::pre_asap::PreASAPNode,
) -> Option<u64> {
None
}
Expand All @@ -110,7 +110,7 @@ pub trait AccuracyEvidenceProvider {
/// filters, grouping and window. `None` means unknown, including emptiness.
fn quantile_input_domain(
&self,
_operand: &asap_types::pre_asap::query_expr::QueryExpr,
_operand: &asap_types::pre_asap::query_expr::PreASAPNode,
) -> Option<QuantileInputDomain> {
None
}
Expand Down
44 changes: 24 additions & 20 deletions crates/asap-aware-mapping/src/accuracy/reconciliation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
//!
//! ## What counts as a "near-duplicate", and why
//!
//! Two [`QueryExpr::Aggregate`] nodes are accuracy-near-duplicates here iff,
//! Two [`PreASAPNode::Aggregate`] nodes are accuracy-near-duplicates here iff,
//! **in this order**:
//!
//! 1. Both are the same bindable shape [`crate::replacement::SketchAlgorithmStrategy`]
Expand Down Expand Up @@ -153,7 +153,7 @@ use std::cmp::Ordering;
use std::rc::Rc;

use asap_types::pre_asap::agg_intent::AggIntent;
use asap_types::pre_asap::query_expr::{QueryExpr, Reduction};
use asap_types::pre_asap::query_expr::{PreASAPNode, Reduction};
use asap_types::types::AccuracyTarget;

use crate::replacement::{
Expand All @@ -169,7 +169,7 @@ type BindableAccuracyAggregate<'a> = (
&'a AggIntent,
&'a AccuracyTarget,
&'a [String],
&'a Rc<QueryExpr>,
&'a Rc<PreASAPNode>,
);

/// The `(reduction, intent, accuracy, output_names, child)` shape this
Expand All @@ -181,8 +181,8 @@ type BindableAccuracyAggregate<'a> = (
/// `Quantile` / `Cardinality` / `TopK`). `None` for anything else, including
/// a multi-measure or `HAVING` aggregate, a non-`Aggregate` node, or an
/// accuracy-free intent (`Sum`, `Avg`, …).
fn bindable_accuracy_aggregate(node: &QueryExpr) -> Option<BindableAccuracyAggregate<'_>> {
let QueryExpr::Aggregate {
fn bindable_accuracy_aggregate(node: &PreASAPNode) -> Option<BindableAccuracyAggregate<'_>> {
let PreASAPNode::Aggregate {
reduction,
measures,
output_names,
Expand Down Expand Up @@ -274,15 +274,15 @@ fn strictly_tighter(a: &AccuracyTarget, b: &AccuracyTarget) -> bool {
/// this strategy from the same post-CSE `Aggregate` sibling set it already
/// builds for `RollupStrategy`.
pub struct AccuracyReconciliationStrategy {
siblings: Vec<Rc<QueryExpr>>,
siblings: Vec<Rc<PreASAPNode>>,
}

impl AccuracyReconciliationStrategy {
/// A strategy that owns clones of every node in `siblings` and considers
/// each as a candidate tighter-accuracy source (or looser-accuracy
/// target) — typically the full set of `Aggregate` nodes a workload-wide
/// discovery pass already found.
pub fn new(siblings: &[Rc<QueryExpr>]) -> Self {
pub fn new(siblings: &[Rc<PreASAPNode>]) -> Self {
Self {
siblings: siblings.to_vec(),
}
Expand All @@ -307,14 +307,14 @@ impl AccuracyReconciliationStrategy {
/// reports no unique key — see `cse.rs`'s "Legality" section) would get
/// proposed for reconciliation even though nothing guarantees a second
/// read of it lines up row-for-row with the first.
fn tighter_sources<'a>(&'a self, target: &TargetSubDAG<'_>) -> Vec<&'a Rc<QueryExpr>> {
fn tighter_sources<'a>(&'a self, target: &TargetSubDAG<'_>) -> Vec<&'a Rc<PreASAPNode>> {
let Some((target_reduction, target_intent, target_accuracy, target_names, target_child)) =
bindable_accuracy_aggregate(target.root)
else {
return Vec::new();
};

let mut sources: Vec<&Rc<QueryExpr>> = self
let mut sources: Vec<&Rc<PreASAPNode>> = self
.siblings
.iter()
.filter(|candidate| {
Expand Down Expand Up @@ -397,8 +397,8 @@ mod tests {
/// willing to hoist it — see `Schema::has_unique_key`/`cse.rs`'s own
/// "Legality" section: a producer with no provable unique key is always
/// inserted fresh, never hoisted, regardless of structural equality.
fn metric_scan() -> Rc<QueryExpr> {
Rc::new(QueryExpr::Scan {
fn metric_scan() -> Rc<PreASAPNode> {
Rc::new(PreASAPNode::Scan {
source: Source::TimeSeries { metric: "m".into() },
predicates: vec![],
schema: Schema::with_time_index(
Expand All @@ -413,8 +413,8 @@ mod tests {
})
}

fn agg(by: Vec<ColumnId>, intent: AggIntent, child: &Rc<QueryExpr>) -> Rc<QueryExpr> {
Rc::new(QueryExpr::Aggregate {
fn agg(by: Vec<ColumnId>, intent: AggIntent, child: &Rc<PreASAPNode>) -> Rc<PreASAPNode> {
Rc::new(PreASAPNode::Aggregate {
reduction: Reduction::by(by),
measures: vec![intent],
output_names: vec![],
Expand All @@ -423,7 +423,7 @@ mod tests {
})
}

fn quantile(q: f64, accuracy: AccuracyTarget, child: &Rc<QueryExpr>) -> Rc<QueryExpr> {
fn quantile(q: f64, accuracy: AccuracyTarget, child: &Rc<PreASAPNode>) -> Rc<PreASAPNode> {
agg(
vec![2],
AggIntent::Quantile {
Expand All @@ -439,7 +439,11 @@ mod tests {
/// reports no unique key for an empty `by` (see `query_expr.rs`'s own
/// `unique_keys = if by.is_empty() || has_count_values { vec![] } else
/// { .. }`).
fn global_quantile(q: f64, accuracy: AccuracyTarget, child: &Rc<QueryExpr>) -> Rc<QueryExpr> {
fn global_quantile(
q: f64,
accuracy: AccuracyTarget,
child: &Rc<PreASAPNode>,
) -> Rc<PreASAPNode> {
agg(
vec![],
AggIntent::Quantile {
Expand All @@ -458,9 +462,9 @@ mod tests {
q: f64,
accuracy: AccuracyTarget,
excluded: Vec<ColumnId>,
child: &Rc<QueryExpr>,
) -> Rc<QueryExpr> {
Rc::new(QueryExpr::Aggregate {
child: &Rc<PreASAPNode>,
) -> Rc<PreASAPNode> {
Rc::new(PreASAPNode::Aggregate {
reduction: Reduction::Reduce(GroupKeys::without(excluded)),
measures: vec![AggIntent::Quantile {
col: None,
Expand Down Expand Up @@ -678,10 +682,10 @@ mod tests {

// The identical scan child, though, is still shared exactly as
// before — this module changes nothing about that.
let QueryExpr::Aggregate { child: child_a, .. } = roots[0].1.as_ref() else {
let PreASAPNode::Aggregate { child: child_a, .. } = roots[0].1.as_ref() else {
panic!("expected an Aggregate root");
};
let QueryExpr::Aggregate { child: child_b, .. } = roots[1].1.as_ref() else {
let PreASAPNode::Aggregate { child: child_b, .. } = roots[1].1.as_ref() else {
panic!("expected an Aggregate root");
};
assert!(Rc::ptr_eq(child_a, child_b));
Expand Down
4 changes: 2 additions & 2 deletions crates/asap-aware-mapping/src/analytical_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3010,7 +3010,7 @@ mod tests {
fn comparison_rejects_different_snapshot_predicate_time_or_horizon() {
use std::rc::Rc;

use asap_types::pre_asap::query_expr::{Predicate, QueryExpr};
use asap_types::pre_asap::query_expr::{PreASAPNode, Predicate};
use asap_types::workload::{DurationMs, TimestampMs};

let raw = comparison_scope();
Expand All @@ -3026,7 +3026,7 @@ mod tests {
candidate = raw.clone();
candidate.sources[0]
.predicates
.push(Predicate(Rc::new(QueryExpr::promql_scalar(1.0))));
.push(Predicate(Rc::new(PreASAPNode::promql_scalar(1.0))));
assert_eq!(
validate_comparison_scopes(&raw, &candidate),
Err(AnalyticalCostError::ComparisonScopeMismatch("sources"))
Expand Down
Loading
Loading