Skip to content

feat: attach Diagnostic to "invalid function argument types" error - #23063

Open
choplin wants to merge 5 commits into
apache:mainfrom
choplin:feat/issue-14431-diagnostic
Open

feat: attach Diagnostic to "invalid function argument types" error#23063
choplin wants to merge 5 commits into
apache:mainfrom
choplin:feat/issue-14431-diagnostic

Conversation

@choplin

@choplin choplin commented Jun 21, 2026

Copy link
Copy Markdown

Which issue does this PR close?

Rationale for this change

When a scalar or aggregate function is called with argument types that don't match any supported signature (e.g. SELECT sum('a')), the error message is a raw text dump with no source location. This PR attaches a Diagnostic so downstream consumers can highlight the offending call site in the original SQL query.

What changes are included in this PR?

  • Add spans: Spans field to ScalarFunction and AggregateFunction so function-call source locations survive planning and optimization.
  • Populate spans from sqlparser in the SQL planner when collect_spans is enabled, following the same pattern used for Column.
  • In verify_function_arguments, attach a Diagnostic with:
    • message: the function name (invalid argument type(s) for 'sum')
    • span: the function call site in the original SQL
    • note: the actual argument types (called with argument type(s): Utf8)
    • help: the candidate signatures (candidate function(s): sum(Decimal), sum(UInt64), ...)
  • Update all pattern matches, tree-node transforms, and serialization code for the new spans field.

Are there any user-facing changes?

No breaking API changes. ScalarFunction and AggregateFunction gain a spans: Spans field, but Spans is ignored in PartialEq / Hash and defaults to empty, so existing code using ScalarFunction::new_udf / AggregateFunction::new_udf is unaffected.

When an error occurs, a diagnositc is attached in the error message.

Before (no Diagnostic attached):

Error during planning: No function matches the given name and argument types 'sum(Utf8)'. You might need to add explicit type casts.
	Candidate functions:
	sum(Decimal)
	sum(UInt64)
	sum(Int64)
	sum(Float64)
	sum(Duration)

After (same error message, with Diagnostic now attached):

Error during planning: No function matches the given name and argument types 'sum(Utf8)'. You might need to add explicit type casts.
	Candidate functions:
	sum(Decimal)
	sum(UInt64)
	sum(Int64)
	sum(Float64)
	sum(Duration)

Diagnostic:
  message: invalid argument type(s) for 'sum'
  span:    1:8 - 1:11
  note:    called with argument type(s): Utf8
  help:    candidate function(s): sum(Decimal), sum(UInt64), sum(Int64), sum(Float64), sum(Duration)

@github-actions github-actions Bot added sql SQL Planner logical-expr Logical plan and expressions physical-expr Changes to the physical-expr crates optimizer Optimizer rules core Core DataFusion crate proto Related to proto crate functions Changes to functions implementation labels Jun 21, 2026
@kosiew

kosiew commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

@choplin
can you resolve the merge conflict?

choplin added 2 commits June 29, 2026 23:24
Add span-aware Diagnostic to the error produced by
`verify_function_arguments` when a scalar or aggregate function is
called with argument types that do not match any supported signature.

- Add `spans: Spans` field to `ScalarFunction` and `AggregateFunction`
  so function-call source locations survive planning and optimization.
- Populate spans from sqlparser in the SQL planner when
  `collect_spans` is enabled.
- Pass the function-call span to `Diagnostic::new_error` so
  downstream consumers can highlight the offending call site.
- Update all pattern matches, tree-node transforms, and serialization
  code for the new field.

Closes apache#14431
Collapse a double blank line flagged by rustfmt
(blank_lines_upper_bound) in the diagnostic test file.
@choplin
choplin force-pushed the feat/issue-14431-diagnostic branch from 0eeb855 to 8fc9954 Compare June 29, 2026 14:35
@choplin

choplin commented Jun 29, 2026

Copy link
Copy Markdown
Author

@kosiew I rebased onto the latest main HEAD and resolved the conflict.

@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown

Thank you for opening this pull request!

Reviewer note: cargo-semver-checks reported the current version number is not SemVer-compatible with the changes in this pull request (compared against the base branch).

Details
     Cloning apache/main
    Building datafusion v54.0.0 (current)
       Built [ 100.156s] (current)
     Parsing datafusion v54.0.0 (current)
      Parsed [   0.034s] (current)
    Building datafusion v54.0.0 (baseline)
       Built [ 100.027s] (baseline)
     Parsing datafusion v54.0.0 (baseline)
      Parsed [   0.035s] (baseline)
    Checking datafusion v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.650s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 202.732s] datafusion
    Building datafusion-expr v54.0.0 (current)
       Built [  25.866s] (current)
     Parsing datafusion-expr v54.0.0 (current)
      Parsed [   0.075s] (current)
    Building datafusion-expr v54.0.0 (baseline)
       Built [  25.845s] (baseline)
     Parsing datafusion-expr v54.0.0 (baseline)
      Parsed [   0.079s] (baseline)
    Checking datafusion-expr v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   1.346s] 223 checks: 222 pass, 1 fail, 0 warn, 30 skip

--- failure constructible_struct_adds_field: externally-constructible struct adds field ---

Description:
A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field.
        ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/constructible_struct_adds_field.ron

Failed in:
  field ScalarFunction.spans in /home/runner/work/datafusion/datafusion/datafusion/expr/src/expr.rs:934
  field AggregateFunction.spans in /home/runner/work/datafusion/datafusion/datafusion/expr/src/expr.rs:1109
  field WindowFunction.spans in /home/runner/work/datafusion/datafusion/datafusion/expr/src/expr.rs:1249

     Summary semver requires new major version: 1 major and 0 minor checks failed
    Finished [  54.104s] datafusion-expr
    Building datafusion-functions v54.0.0 (current)
       Built [  29.281s] (current)
     Parsing datafusion-functions v54.0.0 (current)
      Parsed [   0.083s] (current)
    Building datafusion-functions v54.0.0 (baseline)
       Built [  29.267s] (baseline)
     Parsing datafusion-functions v54.0.0 (baseline)
      Parsed [   0.083s] (baseline)
    Checking datafusion-functions v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.410s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  60.237s] datafusion-functions
    Building datafusion-functions-aggregate v54.0.0 (current)
       Built [  29.543s] (current)
     Parsing datafusion-functions-aggregate v54.0.0 (current)
      Parsed [   0.044s] (current)
    Building datafusion-functions-aggregate v54.0.0 (baseline)
       Built [  29.746s] (baseline)
     Parsing datafusion-functions-aggregate v54.0.0 (baseline)
      Parsed [   0.046s] (baseline)
    Checking datafusion-functions-aggregate v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.184s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  60.933s] datafusion-functions-aggregate
    Building datafusion-functions-nested v54.0.0 (current)
       Built [  34.649s] (current)
     Parsing datafusion-functions-nested v54.0.0 (current)
      Parsed [   0.037s] (current)
    Building datafusion-functions-nested v54.0.0 (baseline)
       Built [  34.482s] (baseline)
     Parsing datafusion-functions-nested v54.0.0 (baseline)
      Parsed [   0.040s] (baseline)
    Checking datafusion-functions-nested v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.217s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  70.661s] datafusion-functions-nested
    Building datafusion-functions-window v54.0.0 (current)
       Built [  24.833s] (current)
     Parsing datafusion-functions-window v54.0.0 (current)
      Parsed [   0.012s] (current)
    Building datafusion-functions-window v54.0.0 (baseline)
       Built [  24.688s] (baseline)
     Parsing datafusion-functions-window v54.0.0 (baseline)
      Parsed [   0.013s] (baseline)
    Checking datafusion-functions-window v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.089s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  50.314s] datafusion-functions-window
    Building datafusion-optimizer v54.0.0 (current)
       Built [  25.804s] (current)
     Parsing datafusion-optimizer v54.0.0 (current)
      Parsed [   0.030s] (current)
    Building datafusion-optimizer v54.0.0 (baseline)
       Built [  25.716s] (baseline)
     Parsing datafusion-optimizer v54.0.0 (baseline)
      Parsed [   0.031s] (baseline)
    Checking datafusion-optimizer v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.196s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  52.549s] datafusion-optimizer
    Building datafusion-physical-expr v54.0.0 (current)
       Built [  28.052s] (current)
     Parsing datafusion-physical-expr v54.0.0 (current)
      Parsed [   0.047s] (current)
    Building datafusion-physical-expr v54.0.0 (baseline)
       Built [  27.811s] (baseline)
     Parsing datafusion-physical-expr v54.0.0 (baseline)
      Parsed [   0.046s] (baseline)
    Checking datafusion-physical-expr v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.333s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  57.097s] datafusion-physical-expr
    Building datafusion-proto v54.0.0 (current)
       Built [  58.551s] (current)
     Parsing datafusion-proto v54.0.0 (current)
      Parsed [   0.017s] (current)
    Building datafusion-proto v54.0.0 (baseline)
       Built [  58.233s] (baseline)
     Parsing datafusion-proto v54.0.0 (baseline)
      Parsed [   0.018s] (baseline)
    Checking datafusion-proto v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.259s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 118.407s] datafusion-proto
    Building datafusion-sql v54.0.0 (current)
       Built [  40.880s] (current)
     Parsing datafusion-sql v54.0.0 (current)
      Parsed [   0.030s] (current)
    Building datafusion-sql v54.0.0 (baseline)
       Built [  40.741s] (baseline)
     Parsing datafusion-sql v54.0.0 (baseline)
      Parsed [   0.033s] (baseline)
    Checking datafusion-sql v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.243s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [  83.112s] datafusion-sql
    Building datafusion-substrait v54.0.0 (current)
       Built [ 359.223s] (current)
     Parsing datafusion-substrait v54.0.0 (current)
      Parsed [   0.019s] (current)
    Building datafusion-substrait v54.0.0 (baseline)
       Built [ 361.471s] (baseline)
     Parsing datafusion-substrait v54.0.0 (baseline)
      Parsed [   0.018s] (baseline)
    Checking datafusion-substrait v54.0.0 -> v54.0.0 (no change; assume patch)
     Checked [   0.225s] 223 checks: 223 pass, 30 skip
     Summary no semver update required
    Finished [ 722.954s] datafusion-substrait

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Jun 30, 2026
The `spans` field added to `expr::AggregateFunction` was not included in
the substrait producer's exhaustive pattern match, breaking the build
for the (non-default) substrait feature with E0027.
@github-actions github-actions Bot added the substrait Changes to the substrait crate label Jul 1, 2026

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@choplin
Thanks for the update. I found one issue that still needs to be addressed before this is ready, plus one small regression test suggestion.

Comment thread datafusion/expr/src/expr_schema.rs Outdated
WindowFunctionDefinition::AggregateUDF(udaf) => {
let new_fields =
verify_function_arguments(udaf.as_ref(), &fields)?;
verify_function_arguments(udaf.as_ref(), &fields, None)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Window aggregate calls still seem to produce diagnostics without a source span.

The regular aggregate path passes spans.first() into verify_function_arguments, but the Expr::WindowFunction path now passes None for both aggregate and window UDFs. For example, SELECT sum(first_name) OVER () FROM person reaches this branch through WindowFunctionDefinition::AggregateUDF, so the new invalid argument type(s) for 'sum' diagnostic cannot highlight the SQL call site.

Could you carry the function-call span on WindowFunction too, set it in SqlToRel when building the window expression, and pass it through here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Fixed in c8a4431.

WindowFunction now carries a spans field (mirroring ScalarFunction / AggregateFunction). SqlToRel populates it from the function-call token when collect_spans is enabled, and both the AggregateUDF and WindowUDF window branches now pass spans.first() into verify_function_arguments instead of None, so sum(first_name) OVER () and window-UDF calls point at the SQL call site.

Added test_invalid_window_function_argument_types based on your example (sum(first_name) OVER ()), asserting the diagnostic points at the sum call site.

Ok(())
}

#[test]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to add a scalar-function regression test as well.

The PR covers scalar and aggregate calls, but the new test only exercises the aggregate path. A small invalid scalar call with collect_spans enabled would protect the other changed branch too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 371b705 — added test_invalid_scalar_function_argument_types.

choplin added 2 commits July 8, 2026 14:35
Window aggregate / window UDF calls (e.g. `sum(first_name) OVER ()`)
previously passed `None` as the span into `verify_function_arguments`, so
the resulting diagnostic could not point at the SQL call site.

Add a `spans` field to `WindowFunction` (mirroring `ScalarFunction` /
`AggregateFunction`), populate it in `SqlToRel`, and thread it through
`ExprSchemable` so window function argument diagnostics carry the span.
Add a regression test for the window function path.

Addresses PR review feedback.
The "invalid function argument types" work covered scalar and aggregate
calls, but the diagnostic test suite only exercised the aggregate path.
Add a test for an invalid scalar call (`power(first_name, first_name)`)
with `collect_spans` enabled to cover the scalar branch.

Addresses PR review feedback.
@choplin

choplin commented Jul 9, 2026

Copy link
Copy Markdown
Author

@kosiew Thanks for the review! I've addressed the two points from your review. Please take a look!

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates. The window aggregate and window UDF diagnostics now propagate WindowFunction.spans from SqlToRel and pass spans.first() into verify_function_arguments, so that concern is addressed. The scalar-function regression test has also been added.

There is still one high-severity semver issue that needs to be resolved before merge. The new spans fields change the public layouts of externally constructible expression structs.

/// List of expressions to feed to the functions as arguments
pub args: Vec<Expr>,
/// Original source code location, if known
pub spans: Spans,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid adding spans as a required public field on these expression structs?

ScalarFunction, AggregateFunction, and WindowFunction are publicly constructible with struct literals. Adding a new required field means existing downstream code using those literals will no longer compile. cargo-semver-checks reports constructible_struct_adds_field for ScalarFunction.spans, AggregateFunction.spans, and WindowFunction.spans.

Please keep the diagnostic span data outside these public struct layouts, or use another semver-compatible design. The new required public fields should be removed before this is merged.

@choplin choplin Jul 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the slow reply here!

You're right about the semver break — adding spans to these public structs trips
constructible_struct_adds_field.

My thinking so far: I noticed Column already carries the same pub spans: Spans
field, added the same way to that (publicly constructible) struct in the earlier
diagnostics work (#13664) — so one direction would be to stay consistent with that
precedent. Another would be to keep the span off these struct layouts entirely and
validate arguments eagerly in SqlToRel, where the call-site span is already in scope,
so the diagnostic is attached at planning time.

I'm not sure which fits best here, though, and there may well be a cleaner
semver-compatible approach I'm not seeing. Do you have a direction you'd prefer — or a
better idea than either of these?

kosiew

This comment was marked as duplicate.

kosiew

This comment was marked as duplicate.

@kosiew kosiew left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@choplin

Thanks for addressing the window aggregate diagnostics and adding the scalar-function regression coverage. I also confirmed that the focused scalar, aggregate, and window diagnostic tests pass.

/// List of expressions to feed to the functions as arguments
pub args: Vec<Expr>,
/// Original source code location, if known
pub spans: Spans,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Between the two options you mentioned, I'd lean toward keeping the parser-only span information out of these public struct layouts. Validating in SqlToRel, where the call-site span is already available, seems like a good fit and avoids introducing a semver break while still preserving the improved diagnostics. If there's another semver-compatible way to associate the span with these expressions, that would also work, but I don't think adding the new public fields is something we should ship in a patch release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto detected api change Auto detected API change core Core DataFusion crate functions Changes to functions implementation logical-expr Logical plan and expressions optimizer Optimizer rules physical-expr Changes to the physical-expr crates proto Related to proto crate sql SQL Planner substrait Changes to the substrait crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attach Diagnostic to "invalid function argument types" error

2 participants