fix: Set Substrait output_type on aggregate functions - #25090
fix: Set Substrait output_type on aggregate functions#25090namanjain24-sudo wants to merge 1 commit into
Conversation
40df8f8 to
3cc32f3
Compare
The Substrait producer exported every aggregate call with output_type: None, even though the logical plan already knows the result type. Derive the output field from the logical expression and write it to AggregateFunction.output_type, mirroring the handling already used for scalar functions. Closes apache#25049.
3cc32f3 to
3616533
Compare
|
@alamb could a committer approve the workflows on this PR and on #25091? Both were opened on 2026-09-08 and neither has had a single check run since. The one green mark on each is the I asked on #25191 a few hours ago rather than pinging the same reviewers repeatedly, so I'm folding these two into one request here instead of opening a third thread. Both are rebased onto current They are small and independent:
Happy to rework either if you'd prefer a different approach. And if approving workflows on fork PRs is itself part of what #25148 is trying to reduce, I'm fine with these waiting until there's a decision there — just wanted to flag that they aren't stalled on anything I can fix from my side. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #25090 +/- ##
==========================================
- Coverage 81.93% 81.93% -0.01%
==========================================
Files 1133 1133
Lines 423529 423557 +28
Branches 423529 423557 +28
==========================================
+ Hits 347028 347049 +21
- Misses 55910 55912 +2
- Partials 20591 20596 +5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Fair question, and my PR description was the reason for it — it argued from the spec and only said a The engine is substrait-java ( // ProtoAggregateFunctionConverter.java:82
.outputType(protoTypeConverter.from(measure.getOutputType()))// ProtoTypeConverter.java:125
case KIND_NOT_SET:
throw new UnsupportedOperationException("Type is not set: " + type);There is no Reproduction, with no DataFusion consumer in the loop. I built One wrinkle that matters if you try this, and that I should have led with: a DataFusion plan as because we still write
So Why nothing in our suite catches it. Our consumer never reads this field — the only Spec wording, identical in the pinned
Precedent. This is the same defect and the same fix as #15831, fixed by #20597, which set One correction to my own description while I am here: it lists I will answer the same question on #25100 and #25190 on their own threads. One clarification about |
Which issue does this PR close?
Rationale for this change
The Substrait producer exported every aggregate call with
output_type: None, eventhough the logical plan already knows the result type. Per the Substrait spec,
AggregateFunction.output_typecarries the return type derived from the referencedfunction declaration. A consumer that validates required fields can reject such plans,
and a consumer that relies on the declaration for schema inference has no type to use.
This mirrors #15831 / #20597, which fixed the same missing
output_typeforBinaryExprand other scalar functions.What changes are included in this PR?
from_aggregate_functionnow derives the output field from the logical expression(
Expr::AggregateFunction(..).to_field(schema)) and writes it toAggregateFunction.output_typeviato_substrait_type_from_field, which is the samepath already used for scalar functions.
Because the type is now converted rather than dropped, an aggregate whose return type
cannot be represented in Substrait produces an error instead of silently emitting a
call with no declared type. That is the behaviour the issue asks for ("write a
conforming type, or report that it cannot represent that function contract").
The change is limited to aggregate functions. The sites that still omit
output_typearewindow functions (
window_function.rs:110), the LIKE and NOT LIKE paths(
scalar_function.rs:296and:312) and thenot()wrapper inutils.rs:106; they are leftfor follow-up work, as in #20597. (
from_in_listis not one of them: it emitsSingularOrList, which has nooutput_typefield.)What is the testing strategy for this PR?
A new unit test
aggregate_function_output_typeindatafusion/substrait/src/logical_plan/producer/expr/aggregate_function.rscovers thefour aggregates from the issue report and asserts both the type and its nullability:
output_typecount(i)Int64(non-nullable)sum(i)Int64(nullable)avg(i)Float64(nullable)min(i)Int64(nullable)The test fails on
main(left: None) and passes with this change.I also ran the reproducer from the issue (the
--aggregate-output-typesprobe fromsubstrait-conformance-cases), which now reports:down from
{"cases":4,"missing_output_types":4}. The existingdatafusion-substraitsuite (including the roundtrip tests) passes unchanged.
Are there any user-facing changes?
Substrait plans produced by DataFusion now declare
output_typeon aggregate calls.There are no public API changes.