11//! Query string → planned summary state → `coverage()`, for the examples of
2- //! the ASAP primitive schema design doc (#573 §4.2.2). Each query is lowered,
3- //! searched and selected with the default cost model, and the quantile's KLL
4- //! `SummaryAgg` in the selected DAG is checked.
2+ //! the ASAP primitive schema design doc (#573 §4.2.2). Each query is lowered
3+ //! and its Stage 1 logical candidates are enumerated; the quantile's KLL
4+ //! `SummaryAgg` is checked. Candidates, not the selected plan: the plan the
5+ //! cost models select for these small queries may build no summary.
56
7+ use std:: collections:: BTreeMap ;
68use std:: ops:: Bound ;
79use std:: rc:: Rc ;
810
911use asap_frontend_sql:: { lower_sql, SqlCatalog } ;
1012use asap_integration_tests:: fixtures:: lower_promql;
11- use asap_logical_optimizer:: search_workload ;
12- use asap_plan_selection :: candidate_selection :: global_selection ;
13- use asap_plan_selection :: DefaultCostModel ;
13+ use asap_logical_optimizer:: pass1 :: logical_candidates :: {
14+ compose_logical_candidate , enumerate_choices , enumerate_local_logical_candidates ,
15+ } ;
1416use asap_types:: ir:: properties:: summary_coverage:: { ColumnIdentity , Constraint , SelectionBox } ;
1517use asap_types:: ir:: scalar:: ScalarValue ;
1618use asap_types:: ir:: schema:: { DataType , Field , FieldDataType , Schema , SketchAlgorithm } ;
17- use asap_types:: ir:: { ASAPOp , NonASAPOp , OperatorNode , Predicate , ScalarExpr } ;
19+ use asap_types:: ir:: { ASAPOp , NonASAPOp , OperatorNode , Predicate , QueryRoot , ScalarExpr } ;
1820use asap_types:: types:: AccuracyTarget ;
1921
2022fn accuracy ( ) -> AccuracyTarget {
@@ -24,25 +26,25 @@ fn accuracy() -> AccuracyTarget {
2426 }
2527}
2628
27- fn plan ( pre : Rc < OperatorNode > ) -> Rc < OperatorNode > {
28- let space = search_workload ( vec ! [ ( "query" , pre) ] ) ;
29- global_selection ( & space, & DefaultCostModel )
30- . assemble_selected_dag ( & space. roots [ 0 ] . 1 )
31- . expect ( "materialization failed" )
32- . expect ( "root must be discovered" )
33- }
34-
35- /// The single KLL `SummaryAgg` of the selected DAG.
36- fn kll_state ( root : & Rc < OperatorNode > ) -> Rc < OperatorNode > {
37- let states: Vec < _ > = OperatorNode :: reachable ( root)
38- . into_iter ( )
39- . filter ( |node| {
29+ /// The KLL `SummaryAgg` of the first logical candidate that builds one.
30+ fn kll_state ( root : Rc < OperatorNode > ) -> Rc < OperatorNode > {
31+ let inventory =
32+ enumerate_local_logical_candidates ( vec ! [ ( 0 , QueryRoot :: Operator ( root) ) ] , & BTreeMap :: new ( ) )
33+ . unwrap ( ) ;
34+ for choice in enumerate_choices ( & inventory, usize:: MAX ) {
35+ let roots = compose_logical_candidate ( & inventory, & choice) . unwrap ( ) ;
36+ let QueryRoot :: Operator ( root) = & roots[ 0 ] . 1 else {
37+ panic ! ( "operator root" )
38+ } ;
39+ let state = OperatorNode :: reachable ( root) . into_iter ( ) . find ( |node| {
4040 matches ! ( node. asap( ) , Some ( ASAPOp :: SummaryAgg { family: FieldDataType :: Sketch ( kind, _) , .. } )
4141 if kind. algorithm( ) == & SketchAlgorithm :: Kll )
42- } )
43- . collect ( ) ;
44- assert_eq ! ( states. len( ) , 1 , "expected one KLL state in {root:#?}" ) ;
45- states[ 0 ] . clone ( )
42+ } ) ;
43+ if let Some ( state) = state {
44+ return state;
45+ }
46+ }
47+ panic ! ( "no candidate builds a KLL state" ) ;
4648}
4749
4850async fn sql_state ( sql : & str ) -> Rc < OperatorNode > {
@@ -57,13 +59,11 @@ async fn sql_state(sql: &str) -> Rc<OperatorNode> {
5759 let pre = lower_sql ( sql, & catalog, accuracy ( ) )
5860 . await
5961 . unwrap_or_else ( |e| panic ! ( "lower failed for {sql:?}: {e}" ) ) ;
60- kll_state ( & plan ( pre) )
62+ kll_state ( pre)
6163}
6264
6365fn promql_state ( query : & str ) -> Rc < OperatorNode > {
64- kll_state ( & plan (
65- lower_promql ( query, accuracy ( ) ) . expect ( "lowering failed" ) ,
66- ) )
66+ kll_state ( lower_promql ( query, accuracy ( ) ) . expect ( "lowering failed" ) )
6767}
6868
6969fn column ( table : Option < & str > , name : & str ) -> ColumnIdentity {
@@ -157,6 +157,41 @@ async fn sql_renamed_column_moves_and_expression_stays() {
157157 ) ;
158158}
159159
160+ /// The worked example of #573 §4.2.2: the `FILTER` range and the renamed
161+ /// value set move to the selection, the expression condition stays.
162+ #[ tokio:: test]
163+ async fn sql_worked_example ( ) {
164+ let state = sql_state (
165+ "SELECT job, approx_percentile_cont(latency, 0.99) FILTER (WHERE latency < 100) \
166+ FROM (SELECT job, region AS r, latency FROM t \
167+ WHERE region = 'us' AND latency * 2 > 10) \
168+ GROUP BY job",
169+ )
170+ . await ;
171+ assert_eq ! (
172+ state. coverage( ) . unwrap( ) . selection,
173+ vec![ SelectionBox {
174+ columns: [
175+ (
176+ column( None , "latency" ) ,
177+ Constraint :: Interval {
178+ lower: Bound :: Unbounded ,
179+ upper: Bound :: Excluded ( ScalarValue :: Float64 ( 100.0 ) ) ,
180+ } ,
181+ ) ,
182+ ( column( None , "r" ) , Constraint :: In ( vec![ utf8( "us" ) ] ) ) ,
183+ ]
184+ . into( ) ,
185+ relative_time: None ,
186+ } ]
187+ ) ;
188+ let definition = & state. coverage ( ) . unwrap ( ) . definition ;
189+ assert ! ( matches!(
190+ definition. asap( ) ,
191+ Some ( ASAPOp :: SummaryAgg { filter: None , .. } )
192+ ) ) ;
193+ }
194+
160195#[ test]
161196fn promql_offset_window_is_relative_time ( ) {
162197 let state = promql_state ( "quantile_over_time(0.99, m[1m] offset 2m)" ) ;
0 commit comments