diff --git a/Cargo.lock b/Cargo.lock index d59452de..0af1a5af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -329,6 +329,13 @@ dependencies = [ "asap-control-lower", ] +[[package]] +name = "asap-plan" +version = "0.1.0" +dependencies = [ + "asap-control-core", +] + [[package]] name = "async-compression" version = "0.4.19" diff --git a/Cargo.toml b/Cargo.toml index 751492b7..19096bc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/core", + "crates/plan", "crates/lower", "crates/e2e", ] diff --git a/crates/core/src/intent_algebra/mod.rs b/crates/core/src/intent_algebra/mod.rs index a33efc6b..db25063c 100644 --- a/crates/core/src/intent_algebra/mod.rs +++ b/crates/core/src/intent_algebra/mod.rs @@ -8,12 +8,14 @@ //! - [`query_expr`] — the canonical, language- and deployment-independent L3 //! intent algebra ([`QueryExpr`] + [`AggIntent`]), with positional //! [`ColumnId`] schema flow. -//! - [`cse`] — workload-level common-sub-expression elimination over L3. +//! +//! Workload-level common-sub-expression elimination over L3 lives in the +//! optimizer layer (`asap-plan`), not here — the IR crate stays free of +//! cost-aware passes. pub mod agg_intent; pub mod binder; pub mod column_resolution; -pub mod cse; pub mod expr_ir; pub mod lower; pub mod names; @@ -29,7 +31,6 @@ pub use column_resolution::{ infer_schema_for_root, infer_source_schema, output_schema_for_aggregate, resolve_column_ref, resolve_column_refs, resolve_expr, ResolveError, }; -pub use cse::{dedupe_subtrees, CseWorkloadPlan}; pub use expr_ir::{ArithOp, ColumnRef, CompareOp, Expr, L2Expr, L3Expr, L3Scalar}; pub use lower::{convert, convert_root, ConvertError}; pub use names::{BindingName, QueryId}; diff --git a/crates/core/src/intent_algebra/schema.rs b/crates/core/src/intent_algebra/schema.rs index afb83b0a..fa0b68a8 100644 --- a/crates/core/src/intent_algebra/schema.rs +++ b/crates/core/src/intent_algebra/schema.rs @@ -205,8 +205,8 @@ impl Schema { // the producer's logic." // // `cse_reuse_is_legal` is the gatekeeper. The workload-level CSE pass -// (`intent_algebra::cse::dedupe_subtrees`) consults it before emitting -// a `LetBinding` to share a producer between ≥2 `Ref` consumers. +// (`asap_plan::cse::dedupe_subtrees`) consults it before emitting a +// `LetBinding` to share a producer between ≥2 `Ref` consumers. use thiserror::Error; diff --git a/crates/plan/Cargo.toml b/crates/plan/Cargo.toml new file mode 100644 index 00000000..7e875671 --- /dev/null +++ b/crates/plan/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "asap-plan" +version = "0.1.0" +edition = "2021" + +# The cost-aware optimizer layer (L4 decisions) over the L3 intent algebra. +# Depends only on the IR crate — never on a front end. +[dependencies] +asap-control-core = { path = "../core" } diff --git a/crates/plan/src/boundary.rs b/crates/plan/src/boundary.rs new file mode 100644 index 00000000..0b3ac931 --- /dev/null +++ b/crates/plan/src/boundary.rs @@ -0,0 +1,9 @@ +//! Sketch-vs-exact boundary (stub). +//! +//! The per-node accuracy decision — whether an approximate intent +//! (`Quantile`/`Cardinality`/`Count`/`TopK`) is realised by an exact operator +//! or a sketch, and with which parameters. This is an L4 concern: L3 carries +//! only the intent + accuracy target, never the realization. +//! +//! TODO(#34, cross-cutting): confirm the boundary decision fires per node over +//! nested trees once this layer is fleshed out. diff --git a/crates/plan/src/canonicalize.rs b/crates/plan/src/canonicalize.rs new file mode 100644 index 00000000..152e782b --- /dev/null +++ b/crates/plan/src/canonicalize.rs @@ -0,0 +1,11 @@ +//! Post-lowering canonicalization (stub). +//! +//! A single normalization pass both front ends run their L3 output through, so +//! semantically-equivalent SQL and PromQL produce **identical** L3 (e.g. the +//! heavy-hitter TopK recognition that is currently duplicated across the two +//! lowerers). Owning it here — above both front ends, over the shared IR — is +//! what lets a future language path inherit the normalization for free. +//! +//! TODO(#34): implement `canonicalize(expr: QueryExpr) -> QueryExpr` and route +//! both `lower_promql` / `lower_sql` outputs through it; add the cross-language +//! equivalence tests that pin the canonical form. diff --git a/crates/plan/src/cost_model.rs b/crates/plan/src/cost_model.rs new file mode 100644 index 00000000..87a59937 --- /dev/null +++ b/crates/plan/src/cost_model.rs @@ -0,0 +1,7 @@ +//! Cost model (stub). +//! +//! The cost traits the optimizer consults — and, in particular, the model that +//! credits a hoisted [`cse`](crate::cse) producer once instead of per consumer. +//! +//! TODO(#6): wire workload-level CSE into a cost model. +//! TODO(#33): detect which optimizations are applicable to a query workload. diff --git a/crates/core/src/intent_algebra/cse.rs b/crates/plan/src/cse.rs similarity index 92% rename from crates/core/src/intent_algebra/cse.rs rename to crates/plan/src/cse.rs index ce50f08a..b0dd581c 100644 --- a/crates/core/src/intent_algebra/cse.rs +++ b/crates/plan/src/cse.rs @@ -2,7 +2,7 @@ //! //! Multi-root planning hoists shared sub-DAGs into `LetBinding`s so the cost //! model can credit the producer once. Legality is gated by -//! [`cse_reuse_is_legal`](super::schema::cse_reuse_is_legal): a candidate +//! [`cse_reuse_is_legal`](asap_control_core::intent_algebra::schema::cse_reuse_is_legal): a candidate //! sub-DAG only becomes a binding when its output schema has at least one //! `unique_keys` set — the load-bearing field for this pass. //! @@ -10,9 +10,9 @@ //! case. The fully-general algorithm (alpha-equivalence, schema-merge, //! nested CSE) is a downstream optimisation, not part of the IR contract. -use crate::intent_algebra::names::{BindingName, QueryId}; -use crate::intent_algebra::query_expr::QueryExpr; -use crate::intent_algebra::schema::cse_reuse_is_legal; +use asap_control_core::intent_algebra::names::{BindingName, QueryId}; +use asap_control_core::intent_algebra::query_expr::QueryExpr; +use asap_control_core::intent_algebra::schema::cse_reuse_is_legal; /// Multi-root container produced by the CSE pass. #[derive(Debug, Clone, PartialEq)] @@ -115,10 +115,10 @@ pub fn dedupe_subtrees(roots: Vec<(QueryId, QueryExpr)>) -> CseWorkloadPlan { #[cfg(test)] mod tests { use super::*; - use crate::intent_algebra::agg_intent::AggIntent; - use crate::intent_algebra::query_expr::{Source, WindowKind}; - use crate::intent_algebra::schema::{Column, DataType, Schema}; - use crate::types::AccuracyTarget; + use asap_control_core::intent_algebra::agg_intent::AggIntent; + use asap_control_core::intent_algebra::query_expr::{Source, WindowKind}; + use asap_control_core::intent_algebra::schema::{Column, DataType, Schema}; + use asap_control_core::types::AccuracyTarget; use std::time::Duration; fn col(name: &str, dtype: DataType) -> Column { diff --git a/crates/plan/src/lib.rs b/crates/plan/src/lib.rs new file mode 100644 index 00000000..28656668 --- /dev/null +++ b/crates/plan/src/lib.rs @@ -0,0 +1,30 @@ +//! `asap-plan` — the cost-aware optimizer layer over the L3 intent algebra. +//! +//! This crate sits between the language-agnostic IR ([`asap_control_core`]) and +//! any runtime: it consumes L3 [`QueryExpr`](asap_control_core::intent_algebra::QueryExpr) +//! trees and makes the cost-aware decisions that L3 deliberately leaves open — +//! which shared sub-expressions to hoist, which sketch (if any) realises each +//! approximate intent, and the canonical form both front ends should agree on. +//! +//! It depends only on the IR crate, never on a front end — the layering +//! invariant (arrows point up) holds here too. +//! +//! ## Status +//! +//! Landed today as a **placeholder** with one real occupant, [`cse`] +//! (workload-level common-sub-expression elimination). The remaining modules +//! are intentional stubs marking where the optimizer work lands: +//! +//! - [`cost_model`] — cost traits + the model CSE credits a shared producer +//! against (issue #6). +//! - [`boundary`] — the per-node sketch-vs-exact (accuracy) decision (issue #34 +//! cross-cutting item; an L4 concern, not carried in L3). +//! - [`canonicalize`] — a single post-lowering normalization pass both front +//! ends run through, so semantically-equal SQL and PromQL produce identical +//! L3 (issue #34). + +pub mod cse; + +pub mod boundary; +pub mod cost_model; +pub mod canonicalize;