Skip to content

feat: Use NativeType in get_example_type, information schema - #21737

Merged
jayzhan211 merged 22 commits into
apache:mainfrom
theirix:type-signature-native-type
Oct 9, 2026
Merged

jayzhan211 merged 22 commits into
apache:mainfrom
theirix:type-signature-native-type

Conversation

@theirix

@theirix theirix commented Apr 19, 2026 •

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Moving from physical types: get_example_types and the information schema use Arrow DataType, but it is usually sufficient to use Datafusion's NativeType instead.

Let's introduce a new API get_representative_types, based on NativeType, and deprecate the public get_example_types API

It is a logical continuation of #15965

What changes are included in this PR?

Are these changes tested?

  • Tests are passing
  • Added more tests to check schema for different types of functions

Are there any user-facing changes?

  • Deprecation of the TypeSignature::get_example_types API

@github-actions github-actions Bot added logical-expr Logical plan and expressions catalog Related to the catalog crate labels Apr 19, 2026
@theirix
theirix marked this pull request as ready for review April 19, 2026 20:28
Comment thread datafusion/expr-common/src/signature.rs Outdated

#[deprecated(since = "46.0.0", note = "See get_example_types instead")]
pub fn get_possible_types(&self) -> Vec<Vec<DataType>> {
pub fn get_possible_types(&self) -> Vec<Vec<NativeType>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hm. A public API break for a deprecated method ... Maybe it is time to just remove it ?!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's true. I will drop get_possible_types. However, get_example_types is also public - it is used in the information schema internally, and can be used by DF users.

Since it breaks the expr-common API, how about deprecating the get_example_types signature and adding a NativeType-based get_representative_types?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@martin-g to avoid breaking changes in the get_example_types (my miss), I've introduced the new method, while deprecating get_example_types.

The Signature API is heavily DataType-based, so the get_example_types. Should we let them coexist, or even move the NativeType-based logic to the information schema? cc @jayzhan211

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems the NativeType is not powerful enough to fully replace DataType here.
For example https://github.com/apache/datafusion/pull/21737/changes#diff-52d29120f24c2a01793f6d729fdb7898abaa8d2320db75aba5cbb06cd714930eR505-R516 shows that there are no counterparts for Union's UnionMode and Map's keys_sorted and defaults should be used. And this may lead to confusions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Undoubtedly, it's not a replacement now. The question for this improvement is whether we'd like to provide changes only to the information-schema, which benefits from native types (then we move code there), or also provide a public API for the Signature to support NativeType alongside DataType (as now).

At the same time, UDFs are migrating to TypeSignature, abstracting from physical data types.

Comment thread datafusion/expr-common/src/signature.rs Outdated
Comment thread datafusion/catalog/src/information_schema.rs Outdated
Comment thread datafusion/catalog/src/information_schema.rs Outdated
Comment thread datafusion/catalog/src/information_schema.rs Outdated
Comment thread datafusion/expr-common/src/signature.rs Outdated
theirix and others added 5 commits April 20, 2026 20:08
Co-authored-by: Martin Grigorov <martin-g@users.noreply.github.com>
Instead, add the new `get_representative_types` API with NativeType.
Deprecated old `get_example_types` and related helpers, left as-is
to avoid breaking API change.
Comment thread datafusion/expr-common/src/signature.rs Outdated
///
/// This is used for `information_schema` and can be used to generate
/// documentation or error messages.
/// Remove with `get_example_types`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should be a comment (//) instead of rustdoc (///)

Comment thread datafusion/expr-common/src/signature.rs Outdated

#[deprecated(since = "46.0.0", note = "See get_example_types instead")]
pub fn get_possible_types(&self) -> Vec<Vec<DataType>> {
pub fn get_possible_types(&self) -> Vec<Vec<NativeType>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It seems the NativeType is not powerful enough to fully replace DataType here.
For example https://github.com/apache/datafusion/pull/21737/changes#diff-52d29120f24c2a01793f6d729fdb7898abaa8d2320db75aba5cbb06cd714930eR505-R516 shows that there are no counterparts for Union's UnionMode and Map's keys_sorted and defaults should be used. And this may lead to confusions.

@Jefffrey Jefffrey 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.

sorry it took so long for me to get around to looking at this 😅

NativeType::Boolean => Ok(DataType::Boolean),
NativeType::Int8 => Ok(DataType::Int8),
NativeType::Int16 => Ok(DataType::Int16),
NativeType::Int32 => Ok(DataType::Int32),

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 feels like we're just moving this logic from where it was in get_example_types to here; I think to properly work towards closing the original issue we might need a larger rework around how datatypes work with information schema, otherwise we're still stuck with this datatype <---> nativetype interwork no matter where we move it 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@Jefffrey , thank you for this! I took another look and reworked it significantly.

I agree, UDF's field and return type resolvers are still on physical Arrow types, and the high-level rework should be done as part of the bigger #12622 epic. I think this could be a small step toward that migration.

Instead of moving a huge mapping around, I decided to reuse the existing LogicalType::default_cast_for logical-physical mapping. It is much leaner now, and all the logic is consolidated in one place, not spread across crates. We can reason on logical types for the catalog. When we remove the deprecated get_example_types, there won't be any traces of the mapping in the high-level catalog and expr crates. What do you think?

@martin-g, regarding your concern - the details of mapping, keys_sorted, and nested structures are now the responsibility of a LogicalType

@github-actions

Copy link
Copy Markdown

Thank you for your contribution. Unfortunately, this pull request is stale because it has been open 60 days with no activity. Please remove the stale label or comment or this will be closed in 7 days.

@github-actions github-actions Bot added the Stale PR has not had any activity for some time label Aug 14, 2026
@github-actions github-actions Bot added sqllogictest SQL Logic Tests (.slt) common Related to common crate labels Aug 18, 2026
@github-actions

github-actions Bot commented Aug 18, 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-catalog v55.1.0 (current)
       Built [  45.892s] (current)
     Parsing datafusion-catalog v55.1.0 (current)
      Parsed [   0.021s] (current)
    Building datafusion-catalog v55.1.0 (baseline)
       Built [  38.074s] (baseline)
     Parsing datafusion-catalog v55.1.0 (baseline)
      Parsed [   0.022s] (baseline)
    Checking datafusion-catalog v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.113s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  85.528s] datafusion-catalog
    Building datafusion-common v55.1.0 (current)
       Built [  33.053s] (current)
     Parsing datafusion-common v55.1.0 (current)
      Parsed [   0.062s] (current)
    Building datafusion-common v55.1.0 (baseline)
       Built [  32.972s] (baseline)
     Parsing datafusion-common v55.1.0 (baseline)
      Parsed [   0.064s] (baseline)
    Checking datafusion-common v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.791s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [  68.161s] datafusion-common
    Building datafusion-expr-common v55.1.0 (current)
       Built [  19.114s] (current)
     Parsing datafusion-expr-common v55.1.0 (current)
      Parsed [   0.018s] (current)
    Building datafusion-expr-common v55.1.0 (baseline)
       Built [  18.805s] (baseline)
     Parsing datafusion-expr-common v55.1.0 (baseline)
      Parsed [   0.019s] (baseline)
    Checking datafusion-expr-common v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.234s] 223 checks: 222 pass, 1 fail, 0 warn, 31 skip

--- failure type_method_marked_deprecated: type method #[deprecated] added ---

Description:
A type method is now #[deprecated]. Downstream crates will get a compiler warning when using this method.
        ref: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.50.0/src/lints/type_method_marked_deprecated.ron

Failed in:
  method datafusion_expr_common::signature::TypeSignature::get_example_types in /home/runner/work/datafusion/datafusion/datafusion/expr-common/src/signature.rs:937

     Summary semver requires new minor version: 0 major and 1 minor checks failed
    Finished [  38.956s] datafusion-expr-common
    Building datafusion-sqllogictest v55.1.0 (current)
       Built [  93.624s] (current)
     Parsing datafusion-sqllogictest v55.1.0 (current)
      Parsed [   0.015s] (current)
    Building datafusion-sqllogictest v55.1.0 (baseline)
       Built [  93.642s] (baseline)
     Parsing datafusion-sqllogictest v55.1.0 (baseline)
      Parsed [   0.016s] (baseline)
    Checking datafusion-sqllogictest v55.1.0 -> v55.1.0 (no change; assume patch)
     Checked [   0.101s] 223 checks: 223 pass, 31 skip
     Summary no semver update required
    Finished [ 189.923s] datafusion-sqllogictest

@github-actions github-actions Bot added the auto detected api change Auto detected API change label Aug 18, 2026
@codecov-commenter

codecov-commenter commented Aug 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.89441% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.76%. Comparing base (a23b89f) to head (2486a6e).

Files with missing lines Patch % Lines
datafusion/catalog/src/information_schema.rs 92.59% 3 Missing and 5 partials ⚠️
datafusion/common/src/types/native.rs 97.64% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main   #21737    +/-   ##
========================================
  Coverage   82.75%   82.76%            
========================================
  Files        1147     1147            
  Lines      449881   450108   +227     
  Branches   449881   450108   +227     
========================================
+ Hits       372306   372511   +205     
- Misses      54905    54911     +6     
- Partials    22670    22686    +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@theirix

theirix commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

bot unstale ?

@github-actions github-actions Bot removed the Stale PR has not had any activity for some time label Aug 19, 2026
@Jefffrey

Copy link
Copy Markdown
Contributor

maybe @jayzhan211 can help take a look at this

@jayzhan211 jayzhan211 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 @theirix , here is a suggestion

.collect::<Result<Vec<FieldRef>>>()?;
// Even with collecting results into a set, drop duplicates early
arg_fields.sort();
arg_fields.dedup();

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.

Field's Ord compares name first, and every field is named arg_{i}, so dedup() never removes anything. sort() is a no-op up to 10 args, but from 11 args it reorders them lexicographically (arg_0, arg_1, arg_10, arg_2, …). The return type is then computed from permuted argument types, while the arg_types shown keep the original order.

Repro: a UDF with Signature::exact(vec![DataType::Int32; 10] + [DataType::Utf8]) whose return_type returns the last arg. get_udf_args_and_return_types reports args [Int32 ×10, String] with return type Some("Int32") instead of Some("String").

The BTreeSet collect already dedups rows, so drop both lines:

-                let mut arg_fields = arg_types
+                let arg_fields = arg_types
                     .iter()
                     .enumerate()
                     .map(|(i, t)| resolve_informational_field(i, t))
                     .collect::<Result<Vec<FieldRef>>>()?;
-                // Even with collecting results into a set, drop duplicates early
-                arg_fields.sort();
-                arg_fields.dedup();

Please add a test with more than 10 heterogeneous args.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thank you, it's a good spot. Removed the dedup to have an expected result (backed by a unit test)

@jayzhan211 jayzhan211 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 @theirix , here are suggestions

fn resolve_informational_field(idx: usize, t: &NativeType) -> Result<FieldRef> {
// Since a native type maps to several physical types, resolve it against `Null` data type
// to get the canonical `DataType` for the native type
let data_type = t.default_cast_for(&DataType::Null)?;

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.

Resolving each NativeType against Null collapses String to Utf8View, so string functions that return Int64 for LargeUtf8 lose that row. Diffing information_schema.routines/parameters against the base shows the Int64 OUT row disappearing for bit_length, char_length, character_length, find_in_set, instr, length, levenshtein, octet_length, position, strpos. If that's intended, please pin it in information_schema.slt and mention it under user-facing changes:

query TT rowsort
select routine_name, data_type from information_schema.routines where routine_name = 'length';
----
length Int32

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, it's a tie between logical type and physical type. Changed to resolve ambiguity by checking more types, not just null. It allows emitting int32 and int64 as expected.

Also, refactored three udf/udaf/udwf functions into one to reduce code duplication.

Comment thread parquet-testing

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.

Do we need this change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Accidentally added

@jayzhan211 jayzhan211 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.

@theirix , still have 2 suggestions

fn resolve_informational_fields(idx: usize, t: &NativeType) -> Result<Vec<FieldRef>> {
// Since native types map to several physical types, resolve it against
// ambiguous types to get canonical `DataType`s for the native type
let data_types = RESOLVE_CAST_SOURCES

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.

default_cast_for(&LargeUtf8) has no arm for Struct/Map/Union (falls through to _internal_err!), and the ? here propagates it out of make_routines/make_parameters. So registering one UDF with such an argument (also nested, e.g. List<Struct>) makes information_schema.routines, information_schema.parameters and SHOW FUNCTIONS fail for every function. Before this PR the path was infallible. On head this test fails with Internal("Unavailable default cast for native type Struct(\"a\": Int32) from physical type LargeUtf8"):

#[test]
fn test_get_udf_args_and_return_types_nested() -> Result<()> {
    use arrow::datatypes::Fields;
    let struct_type =
        DataType::Struct(Fields::from(vec![Field::new("a", DataType::Int32, true)]));
    let signature = Signature::exact(vec![struct_type], Volatility::Stable);
    let udf = Arc::new(ScalarUDF::from(TestScalarUDF { signature }));
    let result = get_udf_args_and_return_types(&udf)?;
    assert_eq!(result.len(), 1);
    Ok(())
}

Fix: skip origins the type has no cast from:

-fn resolve_informational_fields(idx: usize, t: &NativeType) -> Result<Vec<FieldRef>> {
+fn resolve_informational_fields(idx: usize, t: &NativeType) -> Vec<FieldRef> {
     // Since native types map to several physical types, resolve it against
-    // ambiguous types to get canonical `DataType`s for the native type
-    let data_types = RESOLVE_CAST_SOURCES
+    // ambiguous types to get canonical `DataType`s for the native type.
+    // Skip origins the type has no cast from (e.g. `Struct` from `LargeUtf8`)
+    RESOLVE_CAST_SOURCES
         .iter()
-        .map(|source| t.default_cast_for(source))
-        .collect::<Result<Vec<DataType>, _>>()?;
-    Ok(data_types
-        .into_iter()
+        .filter_map(|source| t.default_cast_for(source).ok())
         .unique()
         .map(|dt| Arc::new(Field::new(format!("arg_{idx}"), dt, true)))
-        .collect())
+        .collect()
 }
                 .map(|(i, t)| resolve_informational_fields(i, t))
-                .collect::<Result<Vec<_>>>()?;
+                .collect::<Vec<_>>();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's reasonable, since default_cast_for cannot handle it on its own - added

.map(|(i, t)| resolve_informational_fields(i, t))
.collect::<Result<Vec<_>>>()?;
// Build combinations of arg types with the return type
let return_types = arg_fields

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.

generate_series/range now report only (String, Date, Interval) → List(String), but the query returns List(Date32). Base also listed List(Date), but only because its Date64 example happened to hit (_, Some(Date64)) in Range::return_type. That match is lost now that Date resolves only to Date32. The underlying cause predates this PR: the return type is computed on arguments that haven't been coerced, which the get_representative_types docs say callers must do. Fine to handle in a follow-up.

select arrow_typeof(generate_series('2020-01-01', DATE '2020-01-03', INTERVAL '1 day'));
-- List(Date32)
select data_type from information_schema.parameters
where specific_name = 'generate_series' and parameter_mode = 'OUT';
-- has List(String) for (String, Date, Interval), no List(Date)

Fix: coerce the arguments the same way the planner does before asking for the return type. I tried this locally: it fixes this row and also corrects sum(Int32) (NULL → Int64), trunc(Int64) (NULL → Float64), median(Int64) (Int64 → Float64) and date_trunc(String, Date) (Date → Timestamp(ns)), all checked against arrow_typeof. It does change the existing date_trunc expectations in information_schema.slt.

 use datafusion_expr::function::WindowUDFFieldArgs;
+use datafusion_expr::type_coercion::functions::fields_with_udf;
     get_args_and_return_types(udf.signature(), |arg_fields| {
+        let arg_fields = &fields_with_udf(arg_fields, udf.as_ref())?;
         let scalar_arguments = &vec![None; arg_fields.len()];
     get_args_and_return_types(udaf.signature(), |arg_fields| {
-        udaf.return_field(arg_fields)
+        udaf.return_field(&fields_with_udf(arg_fields, udaf.as_ref())?)
     })
     get_args_and_return_types(udwf.signature(), |arg_fields| {
+        let arg_fields = &fields_with_udf(arg_fields, udwf.as_ref())?;
         udwf.field(WindowUDFFieldArgs::new(arg_fields, udwf.name()))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added, tests are updated to check date_trunc and a new generate_series case

@jayzhan211 jayzhan211 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 @theirix !

@jayzhan211
jayzhan211 enabled auto-merge October 9, 2026 00:35
@jayzhan211

Copy link
Copy Markdown
Contributor

Merge main to re-trigger CI

@jayzhan211
jayzhan211 added this pull request to the merge queue Oct 9, 2026
Merged via the queue into apache:main with commit d6875ff Oct 9, 2026
42 checks passed
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 catalog Related to the catalog crate common Related to common crate logical-expr Logical plan and expressions sqllogictest SQL Logic Tests (.slt) v56.0.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Return NativeType instead of DataType for get_example_types

5 participants