1010//! replaced. Tests that depend on them are `#[ignore]`d with the feature.
1111#![ allow( dead_code) ]
1212
13+ use asap_types:: ir:: NonASAPOp ;
1314use std:: collections:: { BTreeMap , BTreeSet , HashSet } ;
1415
1516use asap_physical_optimizer:: implementation:: physical_candidates:: PhysicalCandidate ;
1617use asap_plan_selection:: { plan_stages, PlanningModels , Selection } ;
17- use asap_types:: ir:: export:: {
18- compile_logical_asap_workload, LogicalASAPDAG , LogicalASAPNodeId , LogicalASAPOperatorPayload ,
19- LogicalASAPQueryRoot , NonASAPOpKind , PhysicalASAPDAG ,
20- } ;
18+ use asap_types:: ir:: flat:: { flatten, FlatDag } ;
19+ use asap_types:: ir:: physical_export:: { PhysicalASAPDAG , PhysicalASAPNodeId } ;
2120use asap_types:: ir:: properties:: ExecutionTiming ;
2221use asap_types:: ir:: schema:: { FieldDataType , SketchAlgorithm , SketchParams , SketchStatistic } ;
2322use asap_types:: ir:: QueryRoot ;
23+ use asap_types:: ir:: { ASAPOp , Operator } ;
2424use asap_types:: types:: AccuracyTarget ;
2525use asap_types:: workload:: {
2626 AccuracyRequirement , BatchEntry , DataArrival , DataDistribution , DataWorkload , DurationMs ,
@@ -29,6 +29,8 @@ use asap_types::workload::{
2929 RepeatedDemand , RepeatingEntry , RepetitionInterval , RootDemand , TimeSelection , TimestampMs ,
3030} ;
3131
32+ pub type Payload = Operator < PhysicalASAPNodeId > ;
33+
3234/// Every enumerated candidate is built and displayed; the largest example
3335/// (Example 3, Pattern A) has 486 today.
3436pub const MAX_CANDIDATES : usize = 4096 ;
@@ -234,17 +236,15 @@ pub fn lower_promql(workload: &PlanningWorkload) -> Vec<QueryRoot> {
234236/// as `aggregate:<kind>`, for each node of one query's Stage 0 DAG, sorted
235237/// (node ids are post-order; compare as a set of operations).
236238pub fn stage0_operations ( root : & QueryRoot ) -> Vec < String > {
237- let dag = asap_types:: ir:: export:: compile_logical_asap_query ( root) . expect ( "compiles" ) ;
238- let json = serde_json:: to_value ( & dag) . expect ( "serializes" ) ;
239- let mut ops: Vec < String > = json[ "nodes" ]
240- . as_array ( )
241- . unwrap ( )
239+ let dag = flatten ( std:: slice:: from_ref ( root) ) . 0 ;
240+ let mut ops: Vec < String > = dag
241+ . nodes
242242 . iter ( )
243243 . map ( |n| {
244- let op = & n [ "payload" ] [ " operator" ] ;
245- match op [ "measures" ] [ 0 ] [ "kind" ] . as_str ( ) {
244+ let json = serde_json :: to_value ( & n . operator ) . expect ( "serializes" ) ;
245+ match json [ "NonASAP" ] [ "Aggregate" ] [ "measures" ] [ 0 ] [ "kind" ] . as_str ( ) {
246246 Some ( measure) => format ! ( "aggregate:{measure}" ) ,
247- None => op [ "kind" ] . as_str ( ) . unwrap_or ( "?" ) . to_owned ( ) ,
247+ None => relational ( & n . operator ) . unwrap_or_else ( || "?" . into ( ) ) ,
248248 }
249249 } )
250250 . collect ( ) ;
@@ -261,8 +261,8 @@ pub struct Logical {
261261 pub id : String ,
262262 /// From Pass 2's identical-expression variant.
263263 pub shared_input : bool ,
264- pub dag : LogicalASAPDAG ,
265- pub query_roots : Vec < LogicalASAPNodeId > ,
264+ pub dag : FlatDag ,
265+ pub query_roots : Vec < PhysicalASAPNodeId > ,
266266}
267267
268268/// One Stage 2 candidate, derived from the Stage 1 candidate `from_logical`.
@@ -271,7 +271,7 @@ pub struct Physical {
271271 pub id : String ,
272272 pub from_logical : String ,
273273 pub dag : PhysicalASAPDAG ,
274- pub query_roots : Vec < LogicalASAPNodeId > ,
274+ pub query_roots : Vec < PhysicalASAPNodeId > ,
275275 pub stage2 : PhysicalCandidate ,
276276}
277277
@@ -308,14 +308,17 @@ impl Run {
308308 }
309309}
310310
311- fn export ( roots : & [ QueryRoot ] ) -> ( LogicalASAPDAG , Vec < LogicalASAPNodeId > ) {
312- let dag = compile_logical_asap_workload ( roots) . expect ( "logical export" ) ;
311+ fn export ( roots : & [ QueryRoot ] ) -> ( FlatDag , Vec < PhysicalASAPNodeId > ) {
312+ for root in roots {
313+ root. validate_structure ( ) . expect ( "valid DAG" ) ;
314+ }
315+ let dag = flatten ( roots) . 0 ;
313316 let query_roots = dag
314317 . roots
315318 . iter ( )
316319 . map ( |root| match root {
317- LogicalASAPQueryRoot :: Operator ( id) => * id,
318- LogicalASAPQueryRoot :: Scalar ( _) => panic ! ( "Examples 2–4 have operator roots" ) ,
320+ QueryRoot :: Operator ( id) => * id,
321+ QueryRoot :: Scalar ( _) => panic ! ( "Examples 2–4 have operator roots" ) ,
319322 } )
320323 . collect ( ) ;
321324 ( dag, query_roots)
@@ -386,45 +389,61 @@ pub fn run_stages(workload: &PlanningWorkload, roots: Vec<QueryRoot>) -> Run {
386389
387390// ── DAG helpers ──────────────────────────────────────────────────────────
388391
389- /// The logical and physical exports share node ids, payloads and edge
390- /// endpoints; the helpers below read only those.
392+ /// The logical (flat) and physical DAGs share node ids and payloads; the
393+ /// helpers below read only those and each node's producers and consumers .
391394pub trait ExportedDag {
392- fn producers ( & self , consumer : LogicalASAPNodeId ) -> Vec < LogicalASAPNodeId > ;
393- fn consumers ( & self , producer : LogicalASAPNodeId ) -> Vec < LogicalASAPNodeId > ;
394- fn node_ids ( & self ) -> Vec < LogicalASAPNodeId > ;
395- fn payload ( & self , id : LogicalASAPNodeId ) -> & LogicalASAPOperatorPayload ;
396- }
397-
398- macro_rules! exported_dag {
399- ( $t: ty) => {
400- impl ExportedDag for $t {
401- fn producers( & self , consumer: LogicalASAPNodeId ) -> Vec <LogicalASAPNodeId > {
402- let edges = self . edges. iter( ) . filter( |e| e. consumer == consumer) ;
403- edges. map( |e| e. producer) . collect( )
404- }
405- fn consumers( & self , producer: LogicalASAPNodeId ) -> Vec <LogicalASAPNodeId > {
406- let edges = self . edges. iter( ) . filter( |e| e. producer == producer) ;
407- edges. map( |e| e. consumer) . collect( )
408- }
409- fn node_ids( & self ) -> Vec <LogicalASAPNodeId > {
410- self . nodes. iter( ) . map( |n| n. id) . collect( )
411- }
412- fn payload( & self , id: LogicalASAPNodeId ) -> & LogicalASAPOperatorPayload {
413- & self
414- . nodes
415- . iter( )
416- . find( |n| n. id == id)
417- . expect( "node" )
418- . payload
419- }
420- }
421- } ;
395+ fn producers ( & self , consumer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > ;
396+ fn consumers ( & self , producer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > ;
397+ fn node_ids ( & self ) -> Vec < PhysicalASAPNodeId > ;
398+ fn payload ( & self , id : PhysicalASAPNodeId ) -> & Payload ;
399+ }
400+
401+ impl ExportedDag for FlatDag {
402+ fn producers ( & self , consumer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > {
403+ self . nodes [ consumer]
404+ . operator
405+ . children ( )
406+ . into_iter ( )
407+ . copied ( )
408+ . collect ( )
409+ }
410+ fn consumers ( & self , producer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > {
411+ ( 0 ..self . nodes . len ( ) )
412+ . filter ( |& id| self . producers ( id) . contains ( & producer) )
413+ . collect ( )
414+ }
415+ fn node_ids ( & self ) -> Vec < PhysicalASAPNodeId > {
416+ ( 0 ..self . nodes . len ( ) ) . collect ( )
417+ }
418+ fn payload ( & self , id : PhysicalASAPNodeId ) -> & Payload {
419+ & self . nodes [ id] . operator
420+ }
421+ }
422+
423+ impl ExportedDag for PhysicalASAPDAG {
424+ fn producers ( & self , consumer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > {
425+ let edges = self . edges . iter ( ) . filter ( |e| e. consumer == consumer) ;
426+ edges. map ( |e| e. producer ) . collect ( )
427+ }
428+ fn consumers ( & self , producer : PhysicalASAPNodeId ) -> Vec < PhysicalASAPNodeId > {
429+ let edges = self . edges . iter ( ) . filter ( |e| e. producer == producer) ;
430+ edges. map ( |e| e. consumer ) . collect ( )
431+ }
432+ fn node_ids ( & self ) -> Vec < PhysicalASAPNodeId > {
433+ self . nodes . iter ( ) . map ( |n| n. id ) . collect ( )
434+ }
435+ fn payload ( & self , id : PhysicalASAPNodeId ) -> & Payload {
436+ & self
437+ . nodes
438+ . iter ( )
439+ . find ( |n| n. id == id)
440+ . expect ( "node" )
441+ . payload
442+ }
422443}
423- exported_dag ! ( LogicalASAPDAG ) ;
424- exported_dag ! ( PhysicalASAPDAG ) ;
425444
426445/// Every node `root` depends on, including itself.
427- pub fn closure ( dag : & impl ExportedDag , root : LogicalASAPNodeId ) -> HashSet < LogicalASAPNodeId > {
446+ pub fn closure ( dag : & impl ExportedDag , root : PhysicalASAPNodeId ) -> HashSet < PhysicalASAPNodeId > {
428447 let mut seen = HashSet :: from ( [ root] ) ;
429448 let mut stack = vec ! [ root] ;
430449 while let Some ( node) = stack. pop ( ) {
@@ -440,8 +459,8 @@ pub fn closure(dag: &impl ExportedDag, root: LogicalASAPNodeId) -> HashSet<Logic
440459/// The queries (indexes into `query_roots`) whose result depends on `node`.
441460pub fn readers (
442461 dag : & impl ExportedDag ,
443- query_roots : & [ LogicalASAPNodeId ] ,
444- node : LogicalASAPNodeId ,
462+ query_roots : & [ PhysicalASAPNodeId ] ,
463+ node : PhysicalASAPNodeId ,
445464) -> BTreeSet < usize > {
446465 query_roots
447466 . iter ( )
@@ -451,40 +470,46 @@ pub fn readers(
451470 . collect ( )
452471}
453472
454- /// The relational operator's wire ` kind` (`"scan"`, `"time_range"`, …).
455- pub fn relational ( payload : & LogicalASAPOperatorPayload ) -> Option < String > {
473+ /// The non-ASAP operator's kind in snake case (`"scan"`, `"time_range"`, …).
474+ pub fn relational ( payload : & Payload ) -> Option < String > {
456475 match payload {
457- LogicalASAPOperatorPayload :: Relational { .. } => {
458- let json = serde_json:: to_value ( payload) . expect ( "payload serializes" ) ;
459- json[ "operator" ] [ "kind" ] . as_str ( ) . map ( str:: to_owned)
476+ Operator :: NonASAP ( _) => {
477+ let mut out = String :: new ( ) ;
478+ for ( i, c) in payload. kind_name ( ) . chars ( ) . enumerate ( ) {
479+ if c. is_ascii_uppercase ( ) && i > 0 {
480+ out. push ( '_' ) ;
481+ }
482+ out. push ( c. to_ascii_lowercase ( ) ) ;
483+ }
484+ Some ( out)
460485 }
461- _ => None ,
486+ Operator :: ASAP ( _ ) => None ,
462487 }
463488}
464489
465- pub fn is_summary ( payload : & LogicalASAPOperatorPayload ) -> bool {
490+ pub fn is_summary ( payload : & Payload ) -> bool {
466491 matches ! (
467492 payload,
468- LogicalASAPOperatorPayload :: SummaryAgg {
493+ Operator :: ASAP ( ASAPOp :: SummaryAgg {
469494 family: FieldDataType :: Sketch ( ..) ,
470495 ..
471- } | LogicalASAPOperatorPayload :: SummaryEstimate { .. }
472- | LogicalASAPOperatorPayload :: SummaryMerge
496+ } ) | Operator :: ASAP ( ASAPOp :: SummaryEstimate { .. } )
497+ | Operator :: ASAP ( ASAPOp :: SummaryMerge { .. } )
473498 )
474499}
475500
476501/// The sketch build nodes (`SummaryAgg` over a sketch family) of `dag`,
477502/// with their algorithm and parameters.
478503pub fn sketch_builds (
479504 dag : & impl ExportedDag ,
480- ) -> Vec < ( LogicalASAPNodeId , SketchAlgorithm , SketchParams ) > {
505+ ) -> Vec < ( PhysicalASAPNodeId , SketchAlgorithm , SketchParams ) > {
481506 dag. node_ids ( )
482507 . into_iter ( )
483508 . filter_map ( |id| match dag. payload ( id) {
484- LogicalASAPOperatorPayload :: SummaryAgg {
509+ Operator :: ASAP ( ASAPOp :: SummaryAgg {
485510 family : FieldDataType :: Sketch ( kind, _) ,
486511 ..
487- } => Some ( ( id, kind. algorithm ( ) . clone ( ) , kind. params ( ) . clone ( ) ) ) ,
512+ } ) => Some ( ( id, kind. algorithm ( ) . clone ( ) , kind. params ( ) . clone ( ) ) ) ,
488513 _ => None ,
489514 } )
490515 . collect ( )
@@ -493,8 +518,8 @@ pub fn sketch_builds(
493518/// The estimation nodes that read `build`, directly or through merges.
494519pub fn estimates_of (
495520 dag : & impl ExportedDag ,
496- build : LogicalASAPNodeId ,
497- ) -> Vec < ( LogicalASAPNodeId , SketchStatistic ) > {
521+ build : PhysicalASAPNodeId ,
522+ ) -> Vec < ( PhysicalASAPNodeId , SketchStatistic ) > {
498523 let mut out = Vec :: new ( ) ;
499524 let mut stack = vec ! [ build] ;
500525 let mut seen = HashSet :: new ( ) ;
@@ -504,10 +529,10 @@ pub fn estimates_of(
504529 continue ;
505530 }
506531 match dag. payload ( consumer) {
507- LogicalASAPOperatorPayload :: SummaryEstimate { query } => {
532+ Operator :: ASAP ( ASAPOp :: SummaryEstimate { query, .. } ) => {
508533 out. push ( ( consumer, query. clone ( ) ) )
509534 }
510- LogicalASAPOperatorPayload :: SummaryMerge => stack. push ( consumer) ,
535+ Operator :: ASAP ( ASAPOp :: SummaryMerge { .. } ) => stack. push ( consumer) ,
511536 _ => { }
512537 }
513538 }
@@ -519,7 +544,7 @@ pub fn estimates_of(
519544/// closure, or `"exact"` when it builds none.
520545pub fn query_option (
521546 dag : & impl ExportedDag ,
522- query_roots : & [ LogicalASAPNodeId ] ,
547+ query_roots : & [ PhysicalASAPNodeId ] ,
523548 query : usize ,
524549) -> String {
525550 let reach = closure ( dag, query_roots[ query] ) ;
@@ -539,8 +564,8 @@ pub fn query_option(
539564/// Nodes reachable from more than one query root.
540565pub fn cross_query_nodes (
541566 dag : & impl ExportedDag ,
542- query_roots : & [ LogicalASAPNodeId ] ,
543- ) -> BTreeSet < LogicalASAPNodeId > {
567+ query_roots : & [ PhysicalASAPNodeId ] ,
568+ ) -> BTreeSet < PhysicalASAPNodeId > {
544569 dag. node_ids ( )
545570 . into_iter ( )
546571 . filter ( |& id| readers ( dag, query_roots, id) . len ( ) > 1 )
@@ -551,7 +576,16 @@ pub fn assert_valid_and_uniquely_named(run: &Run) {
551576 let ids: BTreeSet < _ > = run. logical . iter ( ) . map ( |c| c. id . as_str ( ) ) . collect ( ) ;
552577 assert_eq ! ( ids. len( ) , run. logical. len( ) , "unique logical ids" ) ;
553578 for c in & run. logical {
554- c. dag . validate ( ) . unwrap_or_else ( |e| panic ! ( "{}: {e}" , c. id) ) ;
579+ for ( id, node) in c. dag . nodes . iter ( ) . enumerate ( ) {
580+ assert ! (
581+ node. operator
582+ . children( )
583+ . into_iter( )
584+ . all( |child| * child < id) ,
585+ "{}: children come before their parents" ,
586+ c. id
587+ ) ;
588+ }
555589 }
556590}
557591
@@ -659,21 +693,19 @@ pub enum WindowForm {
659693/// `TimeShift(i·w)` over the scan); any other build is rebuilt from its
660694/// query window. Sliding windows and Exponential Histograms are not planned
661695/// yet.
662- pub fn window_form ( dag : & impl ExportedDag , build : LogicalASAPNodeId ) -> WindowForm {
696+ pub fn window_form ( dag : & impl ExportedDag , build : PhysicalASAPNodeId ) -> WindowForm {
663697 let merged = dag
664698 . consumers ( build)
665699 . into_iter ( )
666- . any ( |c| matches ! ( dag. payload( c) , LogicalASAPOperatorPayload :: SummaryMerge ) ) ;
700+ . any ( |c| matches ! ( dag. payload( c) , Operator :: ASAP ( ASAPOp :: SummaryMerge { .. } ) ) ) ;
667701 if !merged {
668702 return WindowForm :: None ;
669703 }
670704 let ranges: Vec < u64 > = dag
671705 . producers ( build)
672706 . into_iter ( )
673707 . filter_map ( |input| match dag. payload ( input) {
674- LogicalASAPOperatorPayload :: Relational {
675- operator : NonASAPOpKind :: TimeRange { range, .. } ,
676- } => Some ( range. as_millis ( ) as u64 ) ,
708+ Operator :: NonASAP ( NonASAPOp :: TimeRange { range, .. } ) => Some ( range. as_millis ( ) as u64 ) ,
677709 _ => None ,
678710 } )
679711 . collect ( ) ;
@@ -699,7 +731,7 @@ pub enum Materialization {
699731
700732/// Pending (needs Stage 2 materialization): the export records only the
701733/// execution timing, so a query-time node is never kept.
702- pub fn materialization ( p : & Physical , node : LogicalASAPNodeId ) -> Materialization {
734+ pub fn materialization ( p : & Physical , node : PhysicalASAPNodeId ) -> Materialization {
703735 let n = p. dag . nodes . iter ( ) . find ( |n| n. id == node) . expect ( "node" ) ;
704736 match n. output_state . timing {
705737 ExecutionTiming :: IngestionTime => Materialization :: IngestionTime ,
@@ -709,11 +741,11 @@ pub fn materialization(p: &Physical, node: LogicalASAPNodeId) -> Materialization
709741
710742/// Pending (needs Stage 2 materialization): how long a materialized node's
711743/// output is kept, in event time. `None` until Stage 2 records retention.
712- pub fn retention_ms ( _p : & Physical , _node : LogicalASAPNodeId ) -> Option < u64 > {
744+ pub fn retention_ms ( _p : & Physical , _node : PhysicalASAPNodeId ) -> Option < u64 > {
713745 None
714746}
715747
716- pub fn runs_at_ingestion ( p : & Physical , node : LogicalASAPNodeId ) -> bool {
748+ pub fn runs_at_ingestion ( p : & Physical , node : PhysicalASAPNodeId ) -> bool {
717749 materialization ( p, node) == Materialization :: IngestionTime
718750}
719751
0 commit comments