@@ -67,6 +67,9 @@ pub struct QueryCompilationInput {
6767 /// Planner-selected post-ASAP DAG. The physical compiler must not
6868 /// re-select a summary family from pre-ASAP input.
6969 pub selected_plan_root : Rc < SummaryNode > ,
70+ /// Encoded Planner PhysicalCandidate retained before deployment pricing.
71+ /// Installation decodes this candidate; it must not compile another graph.
72+ pub physical_candidate : Option < Vec < u8 > > ,
7073 /// Legacy catalog source retained for request compatibility. Physical
7174 /// materialization sources are derived from each post-ASAP SummaryAgg;
7275 /// it also remains part of the cost manifest workload identity.
@@ -88,6 +91,46 @@ pub struct QueryCompilationInput {
8891 pub materialization_runtime_policy : RuntimeRulePolicy ,
8992}
9093
94+ impl QueryCompilationInput {
95+ /// Physical planning precedes deployment feasibility and cost selection.
96+ pub ( crate ) fn retain_physical_candidate ( & mut self ) -> Result < ( ) , CompileError > {
97+ use asap_physical_operators:: physical_planner:: { promql_rows, PhysicalCandidate } ;
98+ let candidate =
99+ promql_rows:: compile_fixed_window_rate_aggregation ( & self . selected_plan_root )
100+ . or_else ( |_| {
101+ promql_rows:: compile_current_series_readout ( & self . selected_plan_root )
102+ . or_else ( |_| {
103+ promql_rows:: compile_rate_ranking ( & self . selected_plan_root )
104+ . map ( |( _, dag) | dag)
105+ } )
106+ . map ( |query| PhysicalCandidate {
107+ precompute : None ,
108+ query,
109+ materialized_outputs : BTreeMap :: new ( ) ,
110+ } )
111+ } )
112+ . ok ( ) ;
113+ self . physical_candidate = candidate
114+ . map ( |candidate| candidate. encode ( ) )
115+ . transpose ( )
116+ . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) ) ?;
117+ Ok ( ( ) )
118+ }
119+
120+ fn retained_physical (
121+ & self ,
122+ ) -> Result < Option < asap_physical_operators:: physical_planner:: PhysicalCandidate > , CompileError >
123+ {
124+ self . physical_candidate
125+ . as_ref ( )
126+ . map ( |bytes| {
127+ asap_physical_operators:: physical_planner:: PhysicalCandidate :: decode ( bytes)
128+ . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) )
129+ } )
130+ . transpose ( )
131+ }
132+ }
133+
91134#[ derive( Debug , Clone , Serialize , Deserialize , PartialEq ) ]
92135#[ serde( deny_unknown_fields) ]
93136pub struct WindowRealizationCostQuote {
@@ -917,6 +960,7 @@ impl BackendLocalPlanningInput {
917960 query_id,
918961 query_string : query_string. clone ( ) ,
919962 selected_plan_root : post_asap,
963+ physical_candidate : None ,
920964 legacy_query_source : Source :: TimeSeries {
921965 metric : source_hint,
922966 } ,
@@ -1084,6 +1128,12 @@ impl BackendLocalPlanningInput {
10841128 }
10851129 planner_candidate_forests. push ( forest) ;
10861130 }
1131+ for query in queries
1132+ . iter_mut ( )
1133+ . chain ( planner_candidate_forests. iter_mut ( ) . flatten ( ) )
1134+ {
1135+ query. retain_physical_candidate ( ) ?;
1136+ }
10871137 // Composable lowering residualizes unsafe leaves individually; retain Planner siblings.
10881138 Ok ( (
10891139 PhysicalCompilationRequest {
@@ -1571,7 +1621,10 @@ impl DeploymentPlanCompiler {
15711621 for state in & selected {
15721622 if matches ! ( & state. node. expr, SummaryExpr :: SummaryAgg {
15731623 reduction: planner_types:: pre_asap:: Reduction :: Reduce ( keys) , ..
1574- } if keys. is_empty( ) ) || asap_physical_operators:: physical_planner:: promql_rows:: compile_fixed_window_rate_aggregation ( & query. selected_plan_root ) . is_ok ( )
1624+ } if keys. is_empty( ) )
1625+ || query
1626+ . retained_physical ( ) ?
1627+ . is_some_and ( |candidate| candidate. precompute . is_some ( ) )
15751628 {
15761629 if let Some ( sources) = immutable_materialization_sources ( & state. node ) {
15771630 canonical_nodes. insert ( Rc :: as_ptr ( & state. node ) as usize ) ;
@@ -1582,7 +1635,9 @@ impl DeploymentPlanCompiler {
15821635 }
15831636 }
15841637 let cohort_nodes = windows:: cohort_nodes ( & selected) ;
1585- let native_cohort = asap_physical_operators:: physical_planner:: promql_rows:: compile_fixed_window_rate_aggregation ( & query. selected_plan_root ) . is_ok ( ) ;
1638+ let native_cohort = query
1639+ . retained_physical ( ) ?
1640+ . is_some_and ( |candidate| candidate. precompute . is_some ( ) ) ;
15861641 for ( ordinal, selected) in selected. into_iter ( ) . enumerate ( ) {
15871642 let mut branch_query = query. clone ( ) ;
15881643 branch_query. query_lookback_ms = selected
@@ -2162,23 +2217,39 @@ impl DeploymentPlanCompiler {
21622217 } else {
21632218 false
21642219 } ;
2165- let native_rate =
2166- asap_physical_operators:: physical_planner:: promql_rows:: compile_rate_ranking (
2167- & query. selected_plan_root ,
2168- )
2169- . ok ( ) ;
2170- let native_rate = native_rate. or_else ( || {
2171- let physical = asap_physical_operators:: physical_planner:: promql_rows:: compile_fixed_window_rate_aggregation ( & query. selected_plan_root ) . ok ( ) ?;
2172- fn heap ( node : & Rc < SummaryNode > ) -> Option < Rc < SummaryNode > > {
2173- match & node. expr {
2174- SummaryExpr :: SummaryAgg { family : SummaryFamilyType :: Sketch ( ..) | SummaryFamilyType :: ExactAggregate ( planner_types:: post_asap:: ExactKind :: Sum , _) , .. } => Some ( node. clone ( ) ) ,
2175- SummaryExpr :: ValueOperation { child, .. } => heap ( child) ,
2176- SummaryExpr :: SummaryEstimate { summary_input, .. } => heap ( summary_input) ,
2177- _ => None ,
2178- }
2179- }
2180- Some ( ( heap ( & query. selected_plan_root ) ?, physical. query ) )
2181- } ) ;
2220+ let native_rate = if population_operators[ query_index] . is_none ( ) {
2221+ query
2222+ . retained_physical ( ) ?
2223+ . map ( |physical| {
2224+ let program = physical. query ;
2225+ let sources = program
2226+ . input_contracts ( )
2227+ . map ( |( id, _) | id)
2228+ . collect :: < Vec < _ > > ( ) ;
2229+ let [ source] = sources. as_slice ( ) else {
2230+ return Err ( CompileError :: Snapshot (
2231+ "native candidate requires one bound input" . into ( ) ,
2232+ ) ) ;
2233+ } ;
2234+ let source = executable_dags[ query_index]
2235+ . as_ref ( )
2236+ . and_then ( |compiled| {
2237+ u32:: try_from ( * source) . ok ( ) . and_then ( |id| {
2238+ compiled. node_ids . summary_node ( PostAsapNodeId ( id) )
2239+ } )
2240+ } )
2241+ . cloned ( )
2242+ . ok_or_else ( || {
2243+ CompileError :: Snapshot (
2244+ "selected physical input has no semantic binding" . into ( ) ,
2245+ )
2246+ } ) ?;
2247+ Ok ( ( source, program) )
2248+ } )
2249+ . transpose ( ) ?
2250+ } else {
2251+ None
2252+ } ;
21822253 let mut entry = if let Some ( ( source, program) ) = native_rate {
21832254 let native_state_binding = if let SummaryExpr :: SummaryAgg {
21842255 family :
@@ -2371,7 +2442,10 @@ impl DeploymentPlanCompiler {
23712442 }
23722443 super :: maintained_population:: install_native_topk (
23732444 & mut entry,
2374- & query. selected_plan_root ,
2445+ query
2446+ . retained_physical ( ) ?
2447+ . as_ref ( )
2448+ . map ( |candidate| & candidate. query ) ,
23752449 ) ?;
23762450 let catalog_key = QueryPlan :: catalog_key ( entry. language , & canonical) ;
23772451 if query_entries. insert ( catalog_key, entry) . is_some ( ) {
@@ -2414,10 +2488,23 @@ impl DeploymentPlanCompiler {
24142488 query_id : query_id. clone ( ) ,
24152489 reason,
24162490 } ) ?;
2417- if let Ok ( physical) = asap_physical_operators:: physical_planner:: promql_rows:: compile_fixed_window_rate_aggregation ( & request. queries [ query_index] . selected_plan_root ) {
2418- let program = physical. precompute . ok_or_else ( || CompileError :: Snapshot ( "fixed-window candidate has no maintenance program" . into ( ) ) ) ?;
2419- let sink = PostAsapNodeId ( u32:: try_from ( program. roots ( ) [ 0 ] ) . map_err ( |_| CompileError :: Snapshot ( "maintenance sink overflow" . into ( ) ) ) ?) ;
2420- installed. native_programs . insert ( sink, serde_json:: from_slice ( & program. encode ( ) . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) ) ?) . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) ) ?) ;
2491+ if let Some ( program) = request. queries [ query_index]
2492+ . retained_physical ( ) ?
2493+ . and_then ( |physical| physical. precompute )
2494+ {
2495+ let sink = PostAsapNodeId (
2496+ u32:: try_from ( program. roots ( ) [ 0 ] )
2497+ . map_err ( |_| CompileError :: Snapshot ( "precompute sink overflow" . into ( ) ) ) ?,
2498+ ) ;
2499+ installed. native_programs . insert (
2500+ sink,
2501+ serde_json:: from_slice (
2502+ & program
2503+ . encode ( )
2504+ . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) ) ?,
2505+ )
2506+ . map_err ( |e| CompileError :: Snapshot ( e. to_string ( ) ) ) ?,
2507+ ) ;
24212508 }
24222509 query_plan
24232510 . selected_dags
@@ -5546,6 +5633,7 @@ pub(crate) mod tests {
55465633 query_id: query_id. into( ) ,
55475634 query_string: promql. into( ) ,
55485635 selected_plan_root: post_asap,
5636+ physical_candidate: None ,
55495637 legacy_query_source: Source :: TimeSeries { metric: "m" . into( ) } ,
55505638 query_lookback_ms: 60_000 ,
55515639 group_by_labels: vec![ ] ,
0 commit comments