Skip to content

Commit d6875ff

Browse files
theirixmartin-gjayzhan211
authored
feat: Use NativeType in get_example_type, information schema (#21737)
## Which issue does this PR close? - Closes #14761 ## 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? - Introduce `get_representative_types` to provide `NativeType` via `LogicalType::default_cast_for` - Add missing conversions for `Union` to that helper - Deprecate `NUMERICS` finally (a brush-up for #18092) - Switch information schema to use native type. ## 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 --------- Co-authored-by: Martin Grigorov <martin-g@users.noreply.github.com> Co-authored-by: Jay Zhan <jayzhan211@gmail.com>
1 parent a23b89f commit d6875ff

4 files changed

Lines changed: 572 additions & 113 deletions

File tree

‎datafusion/catalog/src/information_schema.rs‎

Lines changed: 153 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,19 @@ use async_trait::async_trait;
3232
use datafusion_common::DataFusionError;
3333
use datafusion_common::config::{ConfigEntry, ConfigOptions};
3434
use datafusion_common::error::Result;
35-
use datafusion_common::types::NativeType;
35+
use datafusion_common::types::{LogicalType, NativeType};
3636
use datafusion_execution::TaskContext;
3737
use datafusion_execution::runtime_env::RuntimeEnv;
3838
use datafusion_expr::function::WindowUDFFieldArgs;
39+
use datafusion_expr::type_coercion::functions::fields_with_udf;
3940
use datafusion_expr::{
4041
AggregateUDF, ReturnFieldArgs, ScalarUDF, Signature, TypeSignature, WindowUDF,
4142
};
4243
use datafusion_expr::{TableType, Volatility};
4344
use datafusion_physical_plan::SendableRecordBatchStream;
4445
use datafusion_physical_plan::stream::RecordBatchStreamAdapter;
4546
use datafusion_physical_plan::streaming::PartitionStream;
47+
use itertools::Itertools;
4648
use std::collections::{BTreeSet, HashMap, HashSet};
4749
use std::fmt::Debug;
4850
use std::sync::Arc;
@@ -454,118 +456,99 @@ impl InformationSchemaConfig {
454456
}
455457
}
456458

457-
/// get the arguments and return types of a UDF
458-
/// returns a tuple of (arg_types, return_type)
459+
/// Origins used to enumerate the physical types a native type can take
460+
const RESOLVE_CAST_SOURCES: [DataType; 2] = [DataType::Null, DataType::LargeUtf8];
461+
462+
/// Build argument fields for `information_schema` to provide possible return types
463+
fn resolve_informational_fields(idx: usize, t: &NativeType) -> Vec<FieldRef> {
464+
// Since native types map to several physical types, resolve it against
465+
// ambiguous types to get canonical `DataType`s for the native type.
466+
// Skip origins the type has no cast from (e.g. `Struct` from `LargeUtf8`)
467+
RESOLVE_CAST_SOURCES
468+
.iter()
469+
.filter_map(|source| t.default_cast_for(source).ok())
470+
.unique()
471+
.map(|dt| Arc::new(Field::new(format!("arg_{idx}"), dt, true)))
472+
.collect()
473+
}
474+
475+
/// Function information schema is a set of tuples - argument types and an optional return type
476+
type FunctionInformationSchema = BTreeSet<(Vec<String>, Option<String>)>;
477+
478+
/// Get the arguments and return types of a function from its signature
479+
fn get_args_and_return_types(
480+
signature: &Signature,
481+
return_field: impl Fn(&[FieldRef]) -> Result<FieldRef>,
482+
) -> Result<FunctionInformationSchema> {
483+
let arg_types = signature.type_signature.get_representative_types();
484+
if arg_types.is_empty() {
485+
// Edge case if function doesn't have arguments
486+
return Ok(BTreeSet::from([(vec![], None)]));
487+
}
488+
arg_types
489+
.into_iter()
490+
.map(|arg_types| {
491+
// Get possible types for each input arg
492+
let arg_fields = arg_types
493+
.iter()
494+
.enumerate()
495+
.map(|(i, t)| resolve_informational_fields(i, t))
496+
.collect::<Vec<_>>();
497+
// Build combinations of arg types with the return type
498+
let return_types = arg_fields
499+
.into_iter()
500+
.multi_cartesian_product()
501+
.filter_map(|arg_fields| return_field(&arg_fields).ok())
502+
.map(|f| Some(remove_native_type_prefix(&f.data_type().into())))
503+
.collect::<BTreeSet<_>>();
504+
let return_types = if return_types.is_empty() {
505+
// Indicate `None` if the return type cannot be represented from a signature,
506+
BTreeSet::from([None])
507+
} else {
508+
return_types
509+
};
510+
let arg_types = arg_types
511+
.iter()
512+
.map(remove_native_type_prefix)
513+
.collect::<Vec<_>>();
514+
let tuples = return_types
515+
.into_iter()
516+
.map(move |return_type| (arg_types.clone(), return_type));
517+
Ok(tuples)
518+
})
519+
.flatten_ok()
520+
.collect()
521+
}
522+
459523
fn get_udf_args_and_return_types(
460524
udf: &Arc<ScalarUDF>,
461-
) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
462-
let signature = udf.signature();
463-
let arg_types = signature.type_signature.get_example_types();
464-
if arg_types.is_empty() {
465-
Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
466-
} else {
467-
Ok(arg_types
468-
.into_iter()
469-
.map(|arg_types| {
470-
let arg_fields: Vec<FieldRef> = arg_types
471-
.iter()
472-
.enumerate()
473-
.map(|(i, t)| {
474-
Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
475-
})
476-
.collect();
477-
let scalar_arguments = vec![None; arg_fields.len()];
478-
let return_type = udf
479-
.return_field_from_args(ReturnFieldArgs {
480-
arg_fields: &arg_fields,
481-
scalar_arguments: &scalar_arguments,
482-
})
483-
.map(|f| {
484-
remove_native_type_prefix(&NativeType::from(
485-
f.data_type().clone(),
486-
))
487-
})
488-
.ok();
489-
let arg_types = arg_types
490-
.into_iter()
491-
.map(|t| remove_native_type_prefix(&NativeType::from(t)))
492-
.collect::<Vec<_>>();
493-
(arg_types, return_type)
494-
})
495-
.collect::<BTreeSet<_>>())
496-
}
525+
) -> Result<FunctionInformationSchema> {
526+
get_args_and_return_types(udf.signature(), |arg_fields| {
527+
let arg_fields = &fields_with_udf(arg_fields, udf.as_ref())?;
528+
let scalar_arguments = &vec![None; arg_fields.len()];
529+
udf.return_field_from_args(ReturnFieldArgs {
530+
arg_fields,
531+
scalar_arguments,
532+
})
533+
})
497534
}
498535

499536
fn get_udaf_args_and_return_types(
500537
udaf: &Arc<AggregateUDF>,
501-
) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
502-
let signature = udaf.signature();
503-
let arg_types = signature.type_signature.get_example_types();
504-
if arg_types.is_empty() {
505-
Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
506-
} else {
507-
Ok(arg_types
508-
.into_iter()
509-
.map(|arg_types| {
510-
let arg_fields: Vec<FieldRef> = arg_types
511-
.iter()
512-
.enumerate()
513-
.map(|(i, t)| {
514-
Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
515-
})
516-
.collect();
517-
let return_type = udaf
518-
.return_field(&arg_fields)
519-
.map(|f| {
520-
remove_native_type_prefix(&NativeType::from(
521-
f.data_type().clone(),
522-
))
523-
})
524-
.ok();
525-
let arg_types = arg_types
526-
.into_iter()
527-
.map(|t| remove_native_type_prefix(&NativeType::from(t)))
528-
.collect::<Vec<_>>();
529-
(arg_types, return_type)
530-
})
531-
.collect::<BTreeSet<_>>())
532-
}
538+
) -> Result<FunctionInformationSchema> {
539+
get_args_and_return_types(udaf.signature(), |arg_fields| {
540+
let arg_fields = &fields_with_udf(arg_fields, udaf.as_ref())?;
541+
udaf.return_field(arg_fields)
542+
})
533543
}
534544

535545
fn get_udwf_args_and_return_types(
536546
udwf: &Arc<WindowUDF>,
537-
) -> Result<BTreeSet<(Vec<String>, Option<String>)>> {
538-
let signature = udwf.signature();
539-
let arg_types = signature.type_signature.get_example_types();
540-
if arg_types.is_empty() {
541-
Ok(vec![(vec![], None)].into_iter().collect::<BTreeSet<_>>())
542-
} else {
543-
Ok(arg_types
544-
.into_iter()
545-
.map(|arg_types| {
546-
let arg_fields: Vec<FieldRef> = arg_types
547-
.iter()
548-
.enumerate()
549-
.map(|(i, t)| {
550-
Arc::new(Field::new(format!("arg_{i}"), t.clone(), true))
551-
})
552-
.collect();
553-
let return_type = udwf
554-
.field(WindowUDFFieldArgs::new(&arg_fields, udwf.name()))
555-
.map(|f| {
556-
remove_native_type_prefix(&NativeType::from(
557-
f.data_type().clone(),
558-
))
559-
})
560-
.ok();
561-
let arg_types = arg_types
562-
.into_iter()
563-
.map(|t| remove_native_type_prefix(&NativeType::from(t)))
564-
.collect::<Vec<_>>();
565-
(arg_types, return_type)
566-
})
567-
.collect::<BTreeSet<_>>())
568-
}
547+
) -> Result<FunctionInformationSchema> {
548+
get_args_and_return_types(udwf.signature(), |arg_fields| {
549+
let arg_fields = &fields_with_udf(arg_fields, udwf.as_ref())?;
550+
udwf.field(WindowUDFFieldArgs::new(arg_fields, udwf.name()))
551+
})
569552
}
570553

571554
#[inline]
@@ -1510,6 +1493,9 @@ mod tests {
15101493
use super::*;
15111494
use crate::CatalogProvider;
15121495
use arrow::array::Array;
1496+
use arrow::datatypes::Fields;
1497+
use datafusion_common::ScalarValue;
1498+
use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
15131499

15141500
#[test]
15151501
fn schemata_builder_emits_canonical_schema_and_rows() {
@@ -1580,6 +1566,70 @@ mod tests {
15801566
assert_eq!("BASE TABLE", builder.table_types.finish().value(0));
15811567
}
15821568

1569+
// UDF
1570+
#[derive(Debug, PartialEq, Eq, Hash)]
1571+
struct TestScalarUDF {
1572+
signature: Signature,
1573+
}
1574+
impl ScalarUDFImpl for TestScalarUDF {
1575+
fn name(&self) -> &str {
1576+
"TestScalarUDF"
1577+
}
1578+
1579+
fn signature(&self) -> &Signature {
1580+
&self.signature
1581+
}
1582+
1583+
fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
1584+
Ok(arg_types.last().unwrap().clone())
1585+
}
1586+
1587+
fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result<ColumnarValue> {
1588+
Ok(ColumnarValue::Scalar(ScalarValue::from("a")))
1589+
}
1590+
}
1591+
1592+
#[test]
1593+
fn test_get_udf_args_and_return_types() -> Result<()> {
1594+
// heterogeneous arguments to test mixed arguments retrieval
1595+
let signature = Signature::exact(
1596+
[
1597+
vec![DataType::Int32; 6],
1598+
vec![DataType::Float32; 6],
1599+
vec![DataType::Utf8; 1],
1600+
]
1601+
.concat(),
1602+
Volatility::Stable,
1603+
);
1604+
let udf = Arc::new(ScalarUDF::from(TestScalarUDF { signature }));
1605+
let result = get_udf_args_and_return_types(&udf)?;
1606+
assert_eq!(result.len(), 1);
1607+
let (args, ret) = result.iter().next().unwrap();
1608+
assert_eq!(
1609+
*args,
1610+
[
1611+
vec![String::from("Int32"); 6],
1612+
vec![String::from("Float32"); 6],
1613+
vec![String::from("String"); 1]
1614+
]
1615+
.concat()
1616+
);
1617+
assert_eq!(*ret, Some(String::from("String")));
1618+
1619+
Ok(())
1620+
}
1621+
1622+
#[test]
1623+
fn test_get_udf_args_and_return_types_nested() -> Result<()> {
1624+
let struct_type =
1625+
DataType::Struct(Fields::from(vec![Field::new("a", DataType::Int32, true)]));
1626+
let signature = Signature::exact(vec![struct_type], Volatility::Stable);
1627+
let udf = Arc::new(ScalarUDF::from(TestScalarUDF { signature }));
1628+
let result = get_udf_args_and_return_types(&udf)?;
1629+
assert_eq!(result.len(), 1);
1630+
Ok(())
1631+
}
1632+
15831633
#[derive(Debug)]
15841634
struct Fixture;
15851635

0 commit comments

Comments
 (0)