@@ -39,9 +39,7 @@ pub enum ASAPOp {
3939 } ,
4040 /// Read an exact accumulator's state as its finalized value: the
4141 /// maintenance-to-read boundary before query-time operators.
42- FinalizeExactAccumulator {
43- child : Rc < OperatorNode > ,
44- } ,
42+ FinalizeExactAccumulator { child : Rc < OperatorNode > } ,
4543 /// Maintain the full declared population, including membership changes.
4644 MaintainPopulation {
4745 child : Rc < OperatorNode > ,
@@ -52,10 +50,9 @@ pub enum ASAPOp {
5250 child : Rc < OperatorNode > ,
5351 evaluation : PopulationStatistic ,
5452 } ,
55- // ── Reserved: migrated but unimplemented (§1.3 of the proposal) ──
56- SummaryMerge {
57- children : Vec < Rc < OperatorNode > > ,
58- } ,
53+ /// Merge compatible partial states for the same grouping and family.
54+ SummaryMerge { children : Vec < Rc < OperatorNode > > } ,
55+ // ── Reserved: migrated but unimplemented ──
5956 SummarySubtract {
6057 left : Rc < OperatorNode > ,
6158 right : Rc < OperatorNode > ,
@@ -186,11 +183,7 @@ impl ASAPOp {
186183 use ASAPOp :: * ;
187184 matches ! (
188185 self ,
189- SummaryMerge { .. }
190- | SummarySubtract { .. }
191- | SummaryDelete { .. }
192- | SummaryJoin { .. }
193- | Extension { .. }
186+ SummarySubtract { .. } | SummaryDelete { .. } | SummaryJoin { .. } | Extension { .. }
194187 )
195188 }
196189
@@ -202,6 +195,14 @@ impl ASAPOp {
202195 pub fn produced_state ( & self ) -> Option < & FieldDataType > {
203196 match self {
204197 ASAPOp :: SummaryAgg { family, .. } | ASAPOp :: SummaryJoin { family, .. } => Some ( family) ,
198+ ASAPOp :: SummaryMerge { children } => children. first ( ) . and_then ( |child| {
199+ child
200+ . schema
201+ . fields
202+ . iter ( )
203+ . find ( |field| !field. is_plain ( ) )
204+ . map ( |field| & field. dtype )
205+ } ) ,
205206 _ => None ,
206207 }
207208 }
@@ -405,8 +406,11 @@ impl ASAPOp {
405406 . output_schema ( ) ?
406407 }
407408 }
408- SummaryMerge { .. }
409- | SummarySubtract { .. }
409+ SummaryMerge { children } => {
410+ self . validate_inputs ( ) ?;
411+ children[ 0 ] . schema . clone ( )
412+ }
413+ SummarySubtract { .. }
410414 | SummaryDelete { .. }
411415 | SummaryJoin { .. }
412416 | Extension { .. } => return Err ( Self :: unimplemented ( ) ) ,
@@ -444,6 +448,37 @@ impl ASAPOp {
444448 }
445449 } ;
446450 match self {
451+ SummaryMerge { children } => {
452+ let Some ( first) = children. first ( ) else {
453+ return Err ( SchemaDerivationError :: InvalidScalarSignature (
454+ "summary merge requires at least one state input" . into ( ) ,
455+ ) ) ;
456+ } ;
457+ // Matching state parameters and grouping positions are necessary;
458+ // matching names alone cannot prove two states compatible.
459+ if first
460+ . schema
461+ . fields
462+ . iter ( )
463+ . filter ( |field| !field. is_plain ( ) )
464+ . count ( )
465+ != 1
466+ {
467+ return Err ( SchemaDerivationError :: InvalidScalarSignature (
468+ "summary merge requires exactly one state column" . into ( ) ,
469+ ) ) ;
470+ }
471+ for child in children {
472+ needs_state ( child, "SummaryMerge" ) ?;
473+ if child. schema != first. schema {
474+ return Err ( SchemaDerivationError :: InvalidScalarSignature (
475+ "summary merge inputs must have identical state and grouping schemas"
476+ . into ( ) ,
477+ ) ) ;
478+ }
479+ }
480+ Ok ( ( ) )
481+ }
447482 SummaryEstimate {
448483 summary_input,
449484 query,
0 commit comments