You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Design] Why the SummaryExpr is designed like this? #463
The confusion is understandable: SummaryExpr mixes several classification axes, rather than representing a strict hierarchy of operator semantics. The three distinctions in the current implementation are:
1. BinaryOp vs. ValueOperation
ValueOperation wraps one post-ASAP child, while BinaryOp connects two independently planned children and carries PromQL vector-matching semantics.
Arithmetic between columns of a single input row can be represented as a projection. Arithmetic between independently computed vectors also needs to match elements across the two inputs. The separate variant preserves both input plans and those matching semantics.
So the distinction is primarily about input structure and composition. The name ValueOperation is broader than its current single-child representation, which makes the overlap confusing.
2. KeepPreAsap vs. ValueOperation
These represent different planning boundaries:
KeepPreAsap embeds an entire original QueryExpr subtree. Its internal operators are not exposed as post-ASAP nodes.
ValueOperation exposes an operation over a post-ASAP child, allowing that child to contain independently selected summaries and readouts.
For example:
KeepPreAsap(Project(Aggregate(Scan)))
retains the original computation as a whole, whereas:
allows the aggregate to use a summary while preserving the projection above its readout.
The projection/filter semantics can therefore be the same in both representations. What differs is how much of the computation has been exposed to post-ASAP planning. This also explains why summary_children(KeepPreAsap(...)) returns no children: it has no SummaryNode children, even though it contains a QueryExpr subtree.
3. ExactAggregate in SummaryAgg vs. an original aggregate in KeepPreAsap
Both can produce exact results, but they expose different execution contracts:
Representation
Meaning
KeepPreAsap(Aggregate(...))
Retains the original aggregate computation.
SummaryAgg with an ExactAggregate family
Selects a concrete exact accumulator and explicitly represents its summary state.
ValueOperation(Exact(Aggregate(...)))
Executes ordinary aggregation over an independently planned post-ASAP child.
Here, “summary” does not necessarily mean “approximate.” An exact accumulator is also summary state, with explicit maintenance and applicable merge behavior.
The current implementation uses FinalizeExactAccumulator to mark the transition from maintained exact state to a read-time value, even when the accumulator state already represents the scalar result. Approximate summaries instead use SummaryEstimate for their readout.
The organizing distinctions are therefore retained logical subtrees, computation over ordinary values, and computation over summary state, with input structure and execution timing providing additional dimensions. The semantic overlap is real, but these variants expose different planning and execution boundaries.
Is it true that BinaryOp is only used for PromQL, and has nothing to do with the SQL world?
Against your response 2:
Is the sub-tree covered by KeepPreASAP not re-shaped by the optimizer at all? I see some generated plans half-ValueOperation and hald-KeePreASAP,
I still do not understand why you want to do such separation for, say, a projection operator. Operators like Projection only semantically mean that I am going to do some per-row transformation from my input data. Why you think pre-ASAP Projection and post-ASAP Projection need to be distinguished? (since to me they have the same semantic)
Against your response 3:
The way I understand your explanation is: Our "Summary" actually means "State". And in our current design, we are considering the query execution in the following perspectives:
What kind of data are we operating on: Summary defined by us v.s. others
What kind of operations are we performing: Like project, filter, agg, binaryop, etc.
Whether the execution is legacy execution or ASAP execution
Based on the above dimensions, we have
KeepPreAsap(Aggregate(...)) fall into others + agg + legacy
SummaryAgg fall into summary + agg + asap
ValueOperation(Exact(Aggregate(...))) falls into others + agg + asap
so on and so forth
Is this understanding correct?
In general
As you yourself said: "SummaryExpr mixes several classification axes", can you articulate what are the classification axes?
I tried to mentally reverse-engineer some axes in the section above, but I feel it would be better to have you articulate the design intention directly.
Also, according to what I have reversed out, I feel some axes may be unnecessary, like "legacy execution vs ASAP execution". However, now I am not sure either if this an actual axis, or if my feeling of un-necessity is correct.
Therefore, I feel it will be extremely helpful for you to articulate out the design considerations of the post-ASAP dag language. :)
Activity
zzylol commentedon Sep 24, 2026
The confusion is understandable:
SummaryExprmixes several classification axes, rather than representing a strict hierarchy of operator semantics. The three distinctions in the current implementation are:1.
BinaryOpvs.ValueOperationValueOperationwraps one post-ASAP child, whileBinaryOpconnects two independently planned children and carries PromQL vector-matching semantics.Arithmetic between columns of a single input row can be represented as a projection. Arithmetic between independently computed vectors also needs to match elements across the two inputs. The separate variant preserves both input plans and those matching semantics.
So the distinction is primarily about input structure and composition. The name
ValueOperationis broader than its current single-child representation, which makes the overlap confusing.2.
KeepPreAsapvs.ValueOperationThese represent different planning boundaries:
KeepPreAsapembeds an entire originalQueryExprsubtree. Its internal operators are not exposed as post-ASAP nodes.ValueOperationexposes an operation over a post-ASAP child, allowing that child to contain independently selected summaries and readouts.For example:
retains the original computation as a whole, whereas:
allows the aggregate to use a summary while preserving the projection above its readout.
The projection/filter semantics can therefore be the same in both representations. What differs is how much of the computation has been exposed to post-ASAP planning. This also explains why
summary_children(KeepPreAsap(...))returns no children: it has noSummaryNodechildren, even though it contains aQueryExprsubtree.3.
ExactAggregateinSummaryAggvs. an original aggregate inKeepPreAsapBoth can produce exact results, but they expose different execution contracts:
KeepPreAsap(Aggregate(...))SummaryAggwith anExactAggregatefamilyValueOperation(Exact(Aggregate(...)))Here, “summary” does not necessarily mean “approximate.” An exact accumulator is also summary state, with explicit maintenance and applicable merge behavior.
The current implementation uses
FinalizeExactAccumulatorto mark the transition from maintained exact state to a read-time value, even when the accumulator state already represents the scalar result. Approximate summaries instead useSummaryEstimatefor their readout.The organizing distinctions are therefore retained logical subtrees, computation over ordinary values, and computation over summary state, with input structure and execution timing providing additional dimensions. The semantic overlap is real, but these variants expose different planning and execution boundaries.
References: expression definitions, execution-state rules.
Selvomega commentedon Sep 24, 2026
Further questions:
Against your response 1:
Is it true that BinaryOp is only used for PromQL, and has nothing to do with the SQL world?
Against your response 2:
KeepPreASAPnot re-shaped by the optimizer at all? I see some generated plans half-ValueOperationand hald-KeePreASAP,Projectiononly semantically mean that I am going to do some per-row transformation from my input data. Why you think pre-ASAPProjectionand post-ASAPProjectionneed to be distinguished? (since to me they have the same semantic)Against your response 3:
The way I understand your explanation is: Our "Summary" actually means "State". And in our current design, we are considering the query execution in the following perspectives:
Summarydefined by us v.s. othersBased on the above dimensions, we have
KeepPreAsap(Aggregate(...))fall intoothers + agg + legacySummaryAggfall intosummary + agg + asapValueOperation(Exact(Aggregate(...)))falls intoothers + agg + asapIs this understanding correct?
In general
As you yourself said: "
SummaryExprmixes several classification axes", can you articulate what are the classification axes?I tried to mentally reverse-engineer some axes in the section above, but I feel it would be better to have you articulate the design intention directly.
Also, according to what I have reversed out, I feel some axes may be unnecessary, like "legacy execution vs ASAP execution". However, now I am not sure either if this an actual axis, or if my feeling of un-necessity is correct.
Therefore, I feel it will be extremely helpful for you to articulate out the design considerations of the post-ASAP dag language. :)