diff --git a/datafusion/catalog/src/information_schema.rs b/datafusion/catalog/src/information_schema.rs index d9ad7791af67c..85222bc2f6ec5 100644 --- a/datafusion/catalog/src/information_schema.rs +++ b/datafusion/catalog/src/information_schema.rs @@ -32,10 +32,11 @@ use async_trait::async_trait; use datafusion_common::DataFusionError; use datafusion_common::config::{ConfigEntry, ConfigOptions}; use datafusion_common::error::Result; -use datafusion_common::types::NativeType; +use datafusion_common::types::{LogicalType, NativeType}; use datafusion_execution::TaskContext; use datafusion_execution::runtime_env::RuntimeEnv; use datafusion_expr::function::WindowUDFFieldArgs; +use datafusion_expr::type_coercion::functions::fields_with_udf; use datafusion_expr::{ AggregateUDF, ReturnFieldArgs, ScalarUDF, Signature, TypeSignature, WindowUDF, }; @@ -43,6 +44,7 @@ use datafusion_expr::{TableType, Volatility}; use datafusion_physical_plan::SendableRecordBatchStream; use datafusion_physical_plan::stream::RecordBatchStreamAdapter; use datafusion_physical_plan::streaming::PartitionStream; +use itertools::Itertools; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Debug; use std::sync::Arc; @@ -454,118 +456,99 @@ impl InformationSchemaConfig { } } -/// get the arguments and return types of a UDF -/// returns a tuple of (arg_types, return_type) +/// Origins used to enumerate the physical types a native type can take +const RESOLVE_CAST_SOURCES: [DataType; 2] = [DataType::Null, DataType::LargeUtf8]; + +/// Build argument fields for `information_schema` to provide possible return types +fn resolve_informational_fields(idx: usize, t: &NativeType) -> Vec { + // Since native types map to several physical types, resolve it against + // 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() + .filter_map(|source| t.default_cast_for(source).ok()) + .unique() + .map(|dt| Arc::new(Field::new(format!("arg_{idx}"), dt, true))) + .collect() +} + +/// Function information schema is a set of tuples - argument types and an optional return type +type FunctionInformationSchema = BTreeSet<(Vec, Option)>; + +/// Get the arguments and return types of a function from its signature +fn get_args_and_return_types( + signature: &Signature, + return_field: impl Fn(&[FieldRef]) -> Result, +) -> Result { + let arg_types = signature.type_signature.get_representative_types(); + if arg_types.is_empty() { + // Edge case if function doesn't have arguments + return Ok(BTreeSet::from([(vec![], None)])); + } + arg_types + .into_iter() + .map(|arg_types| { + // Get possible types for each input arg + let arg_fields = arg_types + .iter() + .enumerate() + .map(|(i, t)| resolve_informational_fields(i, t)) + .collect::>(); + // Build combinations of arg types with the return type + let return_types = arg_fields + .into_iter() + .multi_cartesian_product() + .filter_map(|arg_fields| return_field(&arg_fields).ok()) + .map(|f| Some(remove_native_type_prefix(&f.data_type().into()))) + .collect::>(); + let return_types = if return_types.is_empty() { + // Indicate `None` if the return type cannot be represented from a signature, + BTreeSet::from([None]) + } else { + return_types + }; + let arg_types = arg_types + .iter() + .map(remove_native_type_prefix) + .collect::>(); + let tuples = return_types + .into_iter() + .map(move |return_type| (arg_types.clone(), return_type)); + Ok(tuples) + }) + .flatten_ok() + .collect() +} + fn get_udf_args_and_return_types( udf: &Arc, -) -> Result, Option)>> { - let signature = udf.signature(); - let arg_types = signature.type_signature.get_example_types(); - if arg_types.is_empty() { - Ok(vec![(vec![], None)].into_iter().collect::>()) - } else { - Ok(arg_types - .into_iter() - .map(|arg_types| { - let arg_fields: Vec = arg_types - .iter() - .enumerate() - .map(|(i, t)| { - Arc::new(Field::new(format!("arg_{i}"), t.clone(), true)) - }) - .collect(); - let scalar_arguments = vec![None; arg_fields.len()]; - let return_type = udf - .return_field_from_args(ReturnFieldArgs { - arg_fields: &arg_fields, - scalar_arguments: &scalar_arguments, - }) - .map(|f| { - remove_native_type_prefix(&NativeType::from( - f.data_type().clone(), - )) - }) - .ok(); - let arg_types = arg_types - .into_iter() - .map(|t| remove_native_type_prefix(&NativeType::from(t))) - .collect::>(); - (arg_types, return_type) - }) - .collect::>()) - } +) -> Result { + 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()]; + udf.return_field_from_args(ReturnFieldArgs { + arg_fields, + scalar_arguments, + }) + }) } fn get_udaf_args_and_return_types( udaf: &Arc, -) -> Result, Option)>> { - let signature = udaf.signature(); - let arg_types = signature.type_signature.get_example_types(); - if arg_types.is_empty() { - Ok(vec![(vec![], None)].into_iter().collect::>()) - } else { - Ok(arg_types - .into_iter() - .map(|arg_types| { - let arg_fields: Vec = arg_types - .iter() - .enumerate() - .map(|(i, t)| { - Arc::new(Field::new(format!("arg_{i}"), t.clone(), true)) - }) - .collect(); - let return_type = udaf - .return_field(&arg_fields) - .map(|f| { - remove_native_type_prefix(&NativeType::from( - f.data_type().clone(), - )) - }) - .ok(); - let arg_types = arg_types - .into_iter() - .map(|t| remove_native_type_prefix(&NativeType::from(t))) - .collect::>(); - (arg_types, return_type) - }) - .collect::>()) - } +) -> Result { + get_args_and_return_types(udaf.signature(), |arg_fields| { + let arg_fields = &fields_with_udf(arg_fields, udaf.as_ref())?; + udaf.return_field(arg_fields) + }) } fn get_udwf_args_and_return_types( udwf: &Arc, -) -> Result, Option)>> { - let signature = udwf.signature(); - let arg_types = signature.type_signature.get_example_types(); - if arg_types.is_empty() { - Ok(vec![(vec![], None)].into_iter().collect::>()) - } else { - Ok(arg_types - .into_iter() - .map(|arg_types| { - let arg_fields: Vec = arg_types - .iter() - .enumerate() - .map(|(i, t)| { - Arc::new(Field::new(format!("arg_{i}"), t.clone(), true)) - }) - .collect(); - let return_type = udwf - .field(WindowUDFFieldArgs::new(&arg_fields, udwf.name())) - .map(|f| { - remove_native_type_prefix(&NativeType::from( - f.data_type().clone(), - )) - }) - .ok(); - let arg_types = arg_types - .into_iter() - .map(|t| remove_native_type_prefix(&NativeType::from(t))) - .collect::>(); - (arg_types, return_type) - }) - .collect::>()) - } +) -> Result { + 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())) + }) } #[inline] @@ -1510,6 +1493,9 @@ mod tests { use super::*; use crate::CatalogProvider; use arrow::array::Array; + use arrow::datatypes::Fields; + use datafusion_common::ScalarValue; + use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl}; #[test] fn schemata_builder_emits_canonical_schema_and_rows() { @@ -1580,6 +1566,70 @@ mod tests { assert_eq!("BASE TABLE", builder.table_types.finish().value(0)); } + // UDF + #[derive(Debug, PartialEq, Eq, Hash)] + struct TestScalarUDF { + signature: Signature, + } + impl ScalarUDFImpl for TestScalarUDF { + fn name(&self) -> &str { + "TestScalarUDF" + } + + fn signature(&self) -> &Signature { + &self.signature + } + + fn return_type(&self, arg_types: &[DataType]) -> Result { + Ok(arg_types.last().unwrap().clone()) + } + + fn invoke_with_args(&self, _args: ScalarFunctionArgs) -> Result { + Ok(ColumnarValue::Scalar(ScalarValue::from("a"))) + } + } + + #[test] + fn test_get_udf_args_and_return_types() -> Result<()> { + // heterogeneous arguments to test mixed arguments retrieval + let signature = Signature::exact( + [ + vec![DataType::Int32; 6], + vec![DataType::Float32; 6], + vec![DataType::Utf8; 1], + ] + .concat(), + Volatility::Stable, + ); + let udf = Arc::new(ScalarUDF::from(TestScalarUDF { signature })); + let result = get_udf_args_and_return_types(&udf)?; + assert_eq!(result.len(), 1); + let (args, ret) = result.iter().next().unwrap(); + assert_eq!( + *args, + [ + vec![String::from("Int32"); 6], + vec![String::from("Float32"); 6], + vec![String::from("String"); 1] + ] + .concat() + ); + assert_eq!(*ret, Some(String::from("String"))); + + Ok(()) + } + + #[test] + fn test_get_udf_args_and_return_types_nested() -> Result<()> { + 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(()) + } + #[derive(Debug)] struct Fixture; diff --git a/datafusion/common/src/types/native.rs b/datafusion/common/src/types/native.rs index 580d572af4c0f..19278d04e3f09 100644 --- a/datafusion/common/src/types/native.rs +++ b/datafusion/common/src/types/native.rs @@ -23,7 +23,7 @@ use crate::error::{_internal_err, Result}; use arrow::compute::can_cast_types; use arrow::datatypes::{ DECIMAL32_MAX_PRECISION, DECIMAL64_MAX_PRECISION, DECIMAL128_MAX_PRECISION, DataType, - Field, FieldRef, Fields, IntervalUnit, TimeUnit, UnionFields, + Field, FieldRef, Fields, IntervalUnit, TimeUnit, UnionFields, UnionMode, }; use std::{fmt::Display, sync::Arc}; @@ -432,6 +432,23 @@ impl LogicalType for NativeType { *mode, ) } + // There is no mode to preserve with null origin, so pick dense encoding + (Self::Union(to_fields), Null) => Union( + to_fields + .iter() + .map(|(type_id, field)| { + Ok(( + *type_id, + Arc::new(Field::new( + field.name.clone(), + field.logical_type.default_cast_for(&Null)?, + field.nullable, + )), + )) + }) + .collect::>()?, + UnionMode::Dense, + ), _ => { return _internal_err!( "Unavailable default cast for native type {} from physical type {}", @@ -645,4 +662,113 @@ mod tests { )))); assert_snapshot!(map, @"Map(non-null String)"); } + + #[test] + fn test_default_cast_for_null_origin() { + // Native types with several physical types: pick a canonical one + assert_eq!( + NativeType::String + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::Utf8View + ); + assert_eq!( + NativeType::Binary + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::BinaryView + ); + assert_eq!( + NativeType::Date.default_cast_for(&DataType::Null).unwrap(), + DataType::Date32 + ); + + // Decimals pick the narrowest physical type holding the precision + assert_eq!( + NativeType::Decimal(9, 2) + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::Decimal32(9, 2) + ); + assert_eq!( + NativeType::Decimal(18, 2) + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::Decimal64(18, 2) + ); + assert_eq!( + NativeType::Decimal(38, 10) + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::Decimal128(38, 10) + ); + assert_eq!( + NativeType::Decimal(50, 2) + .default_cast_for(&DataType::Null) + .unwrap(), + DataType::Decimal256(50, 2) + ); + + // nested types + let list = NativeType::List(Arc::new(LogicalField::from(&Field::new( + "item", + DataType::Int32, + true, + )))); + assert_eq!( + list.default_cast_for(&DataType::Null).unwrap(), + DataType::List(Arc::new(Field::new("item", DataType::Int32, true))) + ); + + let struct_type = NativeType::Struct(LogicalFields::from(&Fields::from(vec![ + Field::new("name", DataType::Utf8, false), + Field::new("age", DataType::Int32, true), + ]))); + assert_eq!( + struct_type.default_cast_for(&DataType::Null).unwrap(), + DataType::Struct(Fields::from(vec![ + // The logical `String` field resolves to its canonical type + Field::new("name", DataType::Utf8View, false), + Field::new("age", DataType::Int32, true), + ])) + ); + } + + #[test] + fn test_default_cast_for_union() { + let origin_fields = UnionFields::try_new( + vec![3, 7], + vec![ + Field::new("a", DataType::Int32, true), + Field::new("b", DataType::Utf8, false), + ], + ) + .unwrap(); + let union_type = NativeType::Union(LogicalUnionFields::from(&origin_fields)); + + // From a `Null` origin there are only type ids, but no mode or string + let expected_fields = UnionFields::try_new( + vec![3, 7], + vec![ + Field::new("a", DataType::Int32, true), + Field::new("b", DataType::Utf8View, false), + ], + ) + .unwrap(); + assert_eq!( + union_type.default_cast_for(&DataType::Null).unwrap(), + DataType::Union(expected_fields, UnionMode::Dense) + ); + + // From a union origin both the mode and the encodings are preserved + assert_eq!( + union_type + .default_cast_for(&DataType::Union( + origin_fields.clone(), + UnionMode::Sparse + )) + .unwrap(), + DataType::Union(origin_fields, UnionMode::Sparse) + ); + } } diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index d88cc791e99ca..cc3c178f56b62 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -416,10 +416,9 @@ impl TypeSignatureClass { /// /// This is used for `information_schema` and can be used to generate /// documentation or error messages. + // Deprecated, remove with `get_example_types` fn get_example_types(&self) -> Vec { match self { - // TODO: might be too much info to return every single type here - // maybe https://github.com/apache/datafusion/issues/14761 will help here? TypeSignatureClass::Any => vec![], TypeSignatureClass::Native(l) => get_data_types(l.native()), TypeSignatureClass::Timestamp => { @@ -456,6 +455,50 @@ impl TypeSignatureClass { } } + /// Get example acceptable types for this `TypeSignatureClass` + /// + /// This is used for `information_schema` and can be used to generate + /// documentation or error messages. + fn get_representative_types(&self) -> Vec { + match self { + TypeSignatureClass::Any => vec![], + TypeSignatureClass::Native(l) => vec![l.native().clone()], + TypeSignatureClass::Timestamp => { + vec![ + NativeType::Timestamp(TimeUnit::Nanosecond, None), + NativeType::Timestamp( + TimeUnit::Nanosecond, + Some(TIMEZONE_WILDCARD.into()), + ), + ] + } + TypeSignatureClass::Time => { + vec![NativeType::Time(TimeUnit::Nanosecond)] + } + TypeSignatureClass::Interval => { + vec![NativeType::Interval(IntervalUnit::DayTime)] + } + TypeSignatureClass::Duration => { + vec![NativeType::Duration(TimeUnit::Nanosecond)] + } + TypeSignatureClass::Integer => { + vec![NativeType::Int64] + } + TypeSignatureClass::Binary => { + vec![NativeType::Binary] + } + TypeSignatureClass::Decimal => vec![Decimal128Type::DEFAULT_TYPE.into()], + TypeSignatureClass::Float => vec![NativeType::Float64], + TypeSignatureClass::Numeric => { + vec![ + NativeType::Float64, + NativeType::Int64, + Decimal128Type::DEFAULT_TYPE.into(), + ] + } + } + } + /// Does the specified `NativeType` match this type signature class? pub fn matches_native_type(&self, logical_type: &NativeType) -> bool { if logical_type == &NativeType::Null { @@ -598,6 +641,7 @@ impl Display for ArrayFunctionArgument { } } +// Deprecated, remove with `get_example_types` static NUMERICS: &[DataType] = &[ DataType::Int8, DataType::Int16, @@ -889,6 +933,7 @@ impl TypeSignature { /// /// This is used for `information_schema` and can be used to generate /// documentation or error messages. + #[deprecated(since = "55.0.0", note = "See get_representative_types instead")] pub fn get_example_types(&self) -> Vec> { match self { TypeSignature::Exact(types) => vec![types.clone()], @@ -943,8 +988,78 @@ impl TypeSignature { | TypeSignature::UserDefined => vec![], } } + + /// Return example acceptable types for this `TypeSignature' + /// + /// Returns a `Vec` for each argument to the function + /// + /// This is used for `information_schema` and can be used to generate + /// documentation or error messages. The returned types are the types a caller may pass. + /// Callers computing return types must apply coercion themselves. + pub fn get_representative_types(&self) -> Vec> { + match self { + TypeSignature::Exact(types) => vec![ + types + .iter() + .map(|data_type| NativeType::from(data_type.clone())) + .collect(), + ], + TypeSignature::OneOf(types) => types + .iter() + .flat_map(|type_sig| type_sig.get_representative_types()) + .collect(), + TypeSignature::Uniform(arg_count, types) => types + .iter() + .cloned() + .map(|data_type| vec![data_type.into(); *arg_count]) + .collect(), + TypeSignature::Coercible(coercions) => coercions + .iter() + .map(|c| { + let mut all_types: IndexSet = c + .desired_type() + .get_representative_types() + .into_iter() + .collect(); + + if let Some(implicit_coercion) = c.implicit_coercion() { + let allowed_casts: Vec = implicit_coercion + .allowed_source_types + .iter() + .flat_map(|t| t.get_representative_types()) + .collect(); + all_types.extend(allowed_casts); + } + + all_types.into_iter().collect::>() + }) + .multi_cartesian_product() + .collect(), + TypeSignature::Variadic(types) => types + .iter() + .cloned() + .map(|data_type| vec![data_type.into()]) + .collect(), + TypeSignature::Numeric(arg_count) => TypeSignatureClass::Numeric + .get_representative_types() + .into_iter() + .map(|t| vec![t; *arg_count]) + .collect(), + TypeSignature::String(arg_count) => { + vec![vec![NativeType::String; *arg_count]] + } + // TODO: Implement for other types + TypeSignature::Any(_) + | TypeSignature::Comparable(_) + | TypeSignature::Nullary + | TypeSignature::VariadicAny + | TypeSignature::ArraySignature(_) + | TypeSignature::UserDefined => vec![], + } + } } +// Deprecated, remove with get_data_types fn get_data_types(native_type: &NativeType) -> Vec { match native_type { NativeType::Null => vec![DataType::Null], @@ -1651,8 +1766,10 @@ mod tests { ); } + // Remove with get_example_types #[test] - fn test_get_possible_types() { + #[expect(deprecated)] + fn test_get_example_types() { let type_signature = TypeSignature::Exact(vec![DataType::Int32, DataType::Int64]); let possible_types = type_signature.get_example_types(); assert_eq!(possible_types, vec![vec![DataType::Int32, DataType::Int64]]); @@ -1749,6 +1866,88 @@ mod tests { ); } + #[test] + fn test_get_representative_types() { + let type_signature = TypeSignature::Exact(vec![DataType::Int32, DataType::Int64]); + let types = type_signature.get_representative_types(); + assert_eq!(types, vec![vec![NativeType::Int32, NativeType::Int64]]); + + let type_signature = TypeSignature::OneOf(vec![ + TypeSignature::Exact(vec![DataType::Int32, DataType::Int64]), + TypeSignature::Exact(vec![DataType::Float32, DataType::Float64]), + ]); + let types = type_signature.get_representative_types(); + assert_eq!( + types, + vec![ + vec![NativeType::Int32, NativeType::Int64], + vec![NativeType::Float32, NativeType::Float64] + ] + ); + + let type_signature = TypeSignature::OneOf(vec![ + TypeSignature::Exact(vec![DataType::Int32, DataType::Int64]), + TypeSignature::Exact(vec![DataType::Float32, DataType::Float64]), + TypeSignature::Exact(vec![DataType::Utf8]), + ]); + let types = type_signature.get_representative_types(); + assert_eq!( + types, + vec![ + vec![NativeType::Int32, NativeType::Int64], + vec![NativeType::Float32, NativeType::Float64], + vec![NativeType::String] + ] + ); + + let type_signature = + TypeSignature::Uniform(2, vec![DataType::Float32, DataType::Int64]); + let types = type_signature.get_representative_types(); + assert_eq!( + types, + vec![ + vec![NativeType::Float32, NativeType::Float32], + vec![NativeType::Int64, NativeType::Int64] + ] + ); + + let type_signature = TypeSignature::Coercible(vec![ + Coercion::new_exact(TypeSignatureClass::Native(logical_string())), + Coercion::new_exact(TypeSignatureClass::Native(logical_int64())), + ]); + let types = type_signature.get_representative_types(); + assert_eq!(types, vec![vec![NativeType::String, NativeType::Int64]]); + + let type_signature = + TypeSignature::Variadic(vec![DataType::Int32, DataType::Int64]); + let types = type_signature.get_representative_types(); + assert_eq!( + types, + vec![vec![NativeType::Int32], vec![NativeType::Int64]] + ); + + let type_signature = TypeSignature::Numeric(2); + let types = type_signature.get_representative_types(); + assert_eq!( + types, + vec![ + vec![NativeType::Float64, NativeType::Float64], + vec![NativeType::Int64, NativeType::Int64], + vec![ + Decimal128Type::DEFAULT_TYPE.into(), + Decimal128Type::DEFAULT_TYPE.into() + ], + ] + ); + + let type_signature = TypeSignature::String(2); + let possible_types = type_signature.get_representative_types(); + assert_eq!( + possible_types, + vec![vec![NativeType::String, NativeType::String],] + ); + } + #[test] fn test_signature_with_parameter_names() { let sig = Signature::exact( diff --git a/datafusion/sqllogictest/test_files/information_schema.slt b/datafusion/sqllogictest/test_files/information_schema.slt index 5305a7613326a..27bc27915a523 100644 --- a/datafusion/sqllogictest/test_files/information_schema.slt +++ b/datafusion/sqllogictest/test_files/information_schema.slt @@ -844,8 +844,6 @@ string_agg String AGGREGATE query TTTTTTTBTTTT rowsort select * from information_schema.routines where routine_name = 'date_trunc' OR routine_name = 'string_agg' OR routine_name = 'rank' ORDER BY routine_name ---- -datafusion public date_trunc datafusion public date_trunc FUNCTION true Date SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -datafusion public date_trunc datafusion public date_trunc FUNCTION true String SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Time(ns) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(ns) SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) datafusion public date_trunc datafusion public date_trunc FUNCTION true Timestamp(ns, "+TZ") SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) @@ -861,12 +859,12 @@ false query TTTITTTTBI select * from information_schema.parameters where specific_name = 'date_trunc' OR specific_name = 'string_agg' OR specific_name = 'rank' ORDER BY specific_name, rid, data_type; ---- -datafusion public date_trunc 1 OUT NULL Date NULL false 0 datafusion public date_trunc 2 IN expression Date NULL false 0 datafusion public date_trunc 1 IN precision String NULL false 0 +datafusion public date_trunc 1 OUT NULL Timestamp(ns) NULL false 0 datafusion public date_trunc 1 IN precision String NULL false 1 datafusion public date_trunc 2 IN expression String NULL false 1 -datafusion public date_trunc 1 OUT NULL String NULL false 1 +datafusion public date_trunc 1 OUT NULL Timestamp(ns) NULL false 1 datafusion public date_trunc 1 IN precision String NULL false 2 datafusion public date_trunc 2 IN expression Time(ns) NULL false 2 datafusion public date_trunc 1 OUT NULL Time(ns) NULL false 2 @@ -894,12 +892,98 @@ repeat String 1 OUT 0 query TT??TTT rowsort show functions like 'date_trunc'; ---- -date_trunc Date [precision, expression] [String, Date] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) -date_trunc String [precision, expression] [String, String] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Time(ns) [precision, expression] [String, Time(ns)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(ns) [precision, expression] [String, Date] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +date_trunc Timestamp(ns) [precision, expression] [String, String] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Timestamp(ns) [precision, expression] [String, Timestamp(ns)] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) date_trunc Timestamp(ns, "+TZ") [precision, expression] [String, Timestamp(ns, "+TZ")] SCALAR Truncates a timestamp or time value to a specified precision. date_trunc(precision, expression) +# Schema for function with TypeSignature::Numeric +query TTITI rowsort +select specific_name, data_type, ordinal_position, parameter_mode, rid from information_schema.parameters where specific_name = 'abs'; +---- +abs Decimal(38, 10) 1 IN 0 +abs Decimal(38, 10) 1 OUT 0 +abs Float64 1 IN 1 +abs Float64 1 OUT 1 +abs Int64 1 IN 2 +abs Int64 1 OUT 2 + +query TTT rowsort +select routine_name, data_type, function_type from information_schema.routines where routine_name = 'abs'; +---- +abs Decimal(38, 10) SCALAR +abs Float64 SCALAR +abs Int64 SCALAR + +query TT rowsort +select routine_name, data_type from information_schema.routines where routine_name = 'length'; +---- +length Int32 +length Int64 + +# Schema for function with TypeSignature::Coercible plus uniform with exact float types +query TTITI rowsort +select specific_name, data_type, ordinal_position, parameter_mode, rid from information_schema.parameters where specific_name = 'floor'; +---- +floor Decimal(29, 0) 1 OUT 0 +floor Decimal(38, 10) 1 IN 0 +floor Float32 1 IN 1 +floor Float32 1 OUT 1 +floor Float64 1 IN 2 +floor Float64 1 OUT 2 + +query TTT rowsort +select routine_name, data_type, function_type from information_schema.routines where routine_name = 'floor'; +---- +floor Decimal(29, 0) SCALAR +floor Float32 SCALAR +floor Float64 SCALAR + +# Schemas for date/duration - exact type for Date32/Date64 (logical type is provided) +query TTITI rowsort +select specific_name, data_type, ordinal_position, parameter_mode, rid from information_schema.parameters where specific_name = 'to_char' and (data_type like '%Date%' or data_type like '%Duration%'); +---- +to_char Date 1 IN 0 +to_char Duration(ms) 1 IN 1 +to_char Duration(ns) 1 IN 2 +to_char Duration(s) 1 IN 3 +to_char Duration(µs) 1 IN 4 + +query TTT rowsort +select routine_name, data_type, function_type from information_schema.routines where routine_name = 'to_char'; +---- +to_char String SCALAR + +# Return type cannot be deduced from the signature alone +query TTT +select routine_name, data_type, function_type from information_schema.routines where routine_name = 'date_part'; +---- +date_part NULL SCALAR + +# No args +query TTT +select routine_name, data_type, function_type from information_schema.routines where routine_name = 'pi'; +---- +pi NULL SCALAR + +query I +select count(*) from information_schema.parameters where specific_name = 'date_part' and parameter_mode = 'OUT'; +---- +0 + +# Verify coerced return type of `generate_series`: it should be Date32 +query TI?T rowsort +select specific_name, rid, + array_agg(data_type order by ordinal_position) filter (where parameter_mode = 'IN') as args, + max(data_type) filter (where parameter_mode = 'OUT') as return +from information_schema.parameters where specific_name = 'generate_series' +group by specific_name, rid +having args[1] = 'String' and args[2] like 'Date' and args[3] like 'Interval%'; +---- +generate_series 7 [String, Date, Interval(DayTime)] List(Date) +generate_series 8 [String, Date, Interval(MonthDayNano)] List(Date) + # Table functions (UDTFs) appear in information_schema.routines with # function_type = TABLE and data_type = TABLE. # Note: built-in `generate_series` and `range` are registered as BOTH a