From c6ef8a56df6b00cb619f01a0210ebec43e59878a Mon Sep 17 00:00:00 2001 From: Jay Zhan Date: Mon, 11 Nov 2024 20:39:45 +0800 Subject: [PATCH 1/6] simplify signature for nullif Signed-off-by: Jay Zhan --- datafusion/expr-common/src/signature.rs | 21 ++++++++++ .../expr/src/type_coercion/functions.rs | 37 ++++++++++++++++- datafusion/functions/src/core/nullif.rs | 40 +++++-------------- datafusion/sqllogictest/test_files/nullif.slt | 26 +++++++++++- 4 files changed, 90 insertions(+), 34 deletions(-) diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index 3846fae5de5d..0a3473216de7 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -135,6 +135,15 @@ pub enum TypeSignature { /// Null is considerd as `Utf8` by default /// Dictionary with string value type is also handled. String(usize), + /// Fixed number of arguments of boolean types. + Boolean(usize), +} + +impl TypeSignature { + #[inline] + pub fn is_one_of(&self) -> bool { + matches!(self, TypeSignature::OneOf(_)) + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] @@ -200,6 +209,9 @@ impl TypeSignature { .collect::>() .join(", ")] } + TypeSignature::Boolean(num) => { + vec![format!("Boolean({num})")] + } TypeSignature::String(num) => { vec![format!("String({num})")] } @@ -285,6 +297,7 @@ impl TypeSignature { .cloned() .map(|string_type| vec![string_type; *arg_count]) .collect(), + TypeSignature::Boolean(arg_count) => vec![vec![DataType::Boolean; *arg_count]], // TODO: Implement for other types TypeSignature::Any(_) | TypeSignature::VariadicAny @@ -374,6 +387,14 @@ impl Signature { } } + /// A specified number of boolean arguments + pub fn boolean(arg_count: usize, volatility: Volatility) -> Self { + Self { + type_signature: TypeSignature::Boolean(arg_count), + volatility, + } + } + /// An arbitrary number of arguments of any type. pub fn variadic_any(volatility: Volatility) -> Self { Self { diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index 5a4d89a0b2ec..3ca329a82bff 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -180,6 +180,7 @@ fn is_well_supported_signature(type_signature: &TypeSignature) -> bool { | TypeSignature::Numeric(_) | TypeSignature::String(_) | TypeSignature::Coercible(_) + | TypeSignature::Boolean(_) | TypeSignature::Any(_) ) } @@ -193,13 +194,18 @@ fn try_coerce_types( // Well-supported signature that returns exact valid types. if !valid_types.is_empty() && is_well_supported_signature(type_signature) { - // exact valid types - assert_eq!(valid_types.len(), 1); + // There may be many valid types if valid signature is OneOf + // Otherwise, there should be only one valid type + if !type_signature.is_one_of() { + assert_eq!(valid_types.len(), 1); + } + let valid_types = valid_types.swap_remove(0); if let Some(t) = maybe_data_types_without_coercion(&valid_types, current_types) { return Ok(t); } } else { + // TODO: Deprecate this branch after all signatures are well-supported (aka coercion are happend already) // Try and coerce the argument types to match the signature, returning the // coerced types from the first matching signature. for valid_types in valid_types { @@ -476,6 +482,33 @@ fn get_valid_types( vec![vec![base_type_or_default_type(&coerced_type); *number]] } + TypeSignature::Boolean(number) => { + function_length_check(current_types.len(), *number)?; + + // Find common boolean type amongs given types + let mut valid_type = current_types.first().unwrap().to_owned(); + for t in current_types.iter().skip(1) { + let logical_data_type: NativeType = t.into(); + if logical_data_type == NativeType::Null { + continue; + } + + if logical_data_type != NativeType::Boolean { + return plan_err!( + "The signature expected NativeType::Boolean but received {logical_data_type}" + ); + } + + valid_type = t.to_owned(); + } + + let logical_data_type: NativeType = valid_type.clone().into(); + if logical_data_type == NativeType::Null { + valid_type = DataType::Boolean; + } + + vec![vec![valid_type; *number]] + } TypeSignature::Numeric(number) => { function_length_check(current_types.len(), *number)?; diff --git a/datafusion/functions/src/core/nullif.rs b/datafusion/functions/src/core/nullif.rs index f96ee1ea7a12..8ce9fb19b1e9 100644 --- a/datafusion/functions/src/core/nullif.rs +++ b/datafusion/functions/src/core/nullif.rs @@ -17,7 +17,7 @@ use arrow::datatypes::DataType; use datafusion_common::{exec_err, Result}; -use datafusion_expr::{ColumnarValue, Documentation}; +use datafusion_expr::{ColumnarValue, Documentation, TypeSignature}; use arrow::compute::kernels::cmp::eq; use arrow::compute::kernels::nullif::nullif; @@ -32,25 +32,6 @@ pub struct NullIfFunc { signature: Signature, } -/// Currently supported types by the nullif function. -/// The order of these types correspond to the order on which coercion applies -/// This should thus be from least informative to most informative -static SUPPORTED_NULLIF_TYPES: &[DataType] = &[ - DataType::Boolean, - DataType::UInt8, - DataType::UInt16, - DataType::UInt32, - DataType::UInt64, - DataType::Int8, - DataType::Int16, - DataType::Int32, - DataType::Int64, - DataType::Float32, - DataType::Float64, - DataType::Utf8, - DataType::LargeUtf8, -]; - impl Default for NullIfFunc { fn default() -> Self { Self::new() @@ -60,9 +41,13 @@ impl Default for NullIfFunc { impl NullIfFunc { pub fn new() -> Self { Self { - signature: Signature::uniform( - 2, - SUPPORTED_NULLIF_TYPES.to_vec(), + signature: Signature::one_of( + // Hack: String is at the beginning so the return type is String if both args are Nulls + vec![ + TypeSignature::String(2), + TypeSignature::Numeric(2), + TypeSignature::Boolean(2), + ], Volatility::Immutable, ), } @@ -82,14 +67,7 @@ impl ScalarUDFImpl for NullIfFunc { } fn return_type(&self, arg_types: &[DataType]) -> Result { - // NULLIF has two args and they might get coerced, get a preview of this - let coerced_types = datafusion_expr::type_coercion::functions::data_types( - arg_types, - &self.signature, - ); - coerced_types - .map(|typs| typs[0].clone()) - .map_err(|e| e.context("Failed to coerce arguments for NULLIF")) + Ok(arg_types[0].to_owned()) } fn invoke(&self, args: &[ColumnarValue]) -> Result { diff --git a/datafusion/sqllogictest/test_files/nullif.slt b/datafusion/sqllogictest/test_files/nullif.slt index f8240f70e363..62fa394bd15d 100644 --- a/datafusion/sqllogictest/test_files/nullif.slt +++ b/datafusion/sqllogictest/test_files/nullif.slt @@ -97,7 +97,31 @@ SELECT NULLIF(1, 3); ---- 1 -query I +query T SELECT NULLIF(NULL, NULL); ---- NULL + +query R +select nullif(1, 1.2); +---- +1 + +query R +select nullif(1.0, 2); +---- +1 + +query error DataFusion error: Error during planning: Internal error: Failed to match any signature, errors: Error during planning: The signature expected NativeType::String but received NativeType::Int64 +select nullif(2, 'a'); + + +query T +select nullif('2', '3'); +---- +2 + +# TODO: support numeric string +# This query success in Postgres and DuckDB +query error DataFusion error: Error during planning: Internal error: Failed to match any signature, errors: Error during planning: The signature expected NativeType::String but received NativeType::Int64 +select nullif(2, '1'); \ No newline at end of file From ffd02c435a3e414ecb656eb294f96c9d1ffe6ac2 Mon Sep 17 00:00:00 2001 From: Jay Zhan Date: Mon, 11 Nov 2024 20:50:01 +0800 Subject: [PATCH 2/6] add possible types Signed-off-by: Jay Zhan --- datafusion/expr-common/src/signature.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index 0a3473216de7..bdf687a8b5e3 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -18,7 +18,7 @@ //! Signature module contains foundational types that are used to represent signatures, types, //! and return types of functions in DataFusion. -use crate::type_coercion::aggregates::{NUMERICS, STRINGS}; +use crate::type_coercion::aggregates::NUMERICS; use arrow::datatypes::DataType; use datafusion_common::types::{LogicalTypeRef, NativeType}; use itertools::Itertools; @@ -292,12 +292,13 @@ impl TypeSignature { .cloned() .map(|numeric_type| vec![numeric_type; *arg_count]) .collect(), - TypeSignature::String(arg_count) => STRINGS - .iter() - .cloned() - .map(|string_type| vec![string_type; *arg_count]) - .collect(), - TypeSignature::Boolean(arg_count) => vec![vec![DataType::Boolean; *arg_count]], + TypeSignature::String(arg_count) => get_data_types(&NativeType::String) + .into_iter() + .map(|dt| vec![dt; *arg_count]) + .collect::>(), + TypeSignature::Boolean(arg_count) => { + vec![vec![DataType::Boolean; *arg_count]] + } // TODO: Implement for other types TypeSignature::Any(_) | TypeSignature::VariadicAny From 95f09c44976fb334f82fde1a884eba0aac72586a Mon Sep 17 00:00:00 2001 From: jayzhan211 Date: Wed, 13 Nov 2024 10:31:57 +0800 Subject: [PATCH 3/6] typo Signed-off-by: jayzhan211 --- datafusion/expr/src/type_coercion/functions.rs | 4 ++-- datafusion/functions/src/core/nullif.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index 949d27a2213d..758e462145cd 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -206,7 +206,7 @@ fn try_coerce_types( return Ok(t); } } else { - // TODO: Deprecate this branch after all signatures are well-supported (aka coercion are happend already) + // TODO: Deprecate this branch after all signatures are well-supported (aka coercion has happened already) // Try and coerce the argument types to match the signature, returning the // coerced types from the first matching signature. for valid_types in valid_types { @@ -486,7 +486,7 @@ fn get_valid_types( TypeSignature::Boolean(number) => { function_length_check(current_types.len(), *number)?; - // Find common boolean type amongs given types + // Find common boolean type amongst the given types let mut valid_type = current_types.first().unwrap().to_owned(); for t in current_types.iter().skip(1) { let logical_data_type: NativeType = t.into(); diff --git a/datafusion/functions/src/core/nullif.rs b/datafusion/functions/src/core/nullif.rs index 8ce9fb19b1e9..9841c7572068 100644 --- a/datafusion/functions/src/core/nullif.rs +++ b/datafusion/functions/src/core/nullif.rs @@ -42,7 +42,7 @@ impl NullIfFunc { pub fn new() -> Self { Self { signature: Signature::one_of( - // Hack: String is at the beginning so the return type is String if both args are Nulls + // String is at the beginning so the return type is String if both args are Nulls vec![ TypeSignature::String(2), TypeSignature::Numeric(2), From 60496e54cace046a8f6af42c529d3007fe51a2da Mon Sep 17 00:00:00 2001 From: jayzhan211 Date: Fri, 15 Nov 2024 16:46:07 +0800 Subject: [PATCH 4/6] numeric string Signed-off-by: jayzhan211 --- datafusion/expr-common/src/signature.rs | 30 ++++++------ .../expr-common/src/type_coercion/binary.rs | 34 +++++++++++++ .../expr/src/type_coercion/functions.rs | 48 ++++++++----------- datafusion/functions/src/core/nullif.rs | 25 ++++++---- datafusion/sqllogictest/test_files/nullif.slt | 29 +++++++++-- 5 files changed, 106 insertions(+), 60 deletions(-) diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index 3bf779059559..a23180dd79ae 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -113,6 +113,8 @@ pub enum TypeSignature { /// arguments like `vec![DataType::Int32]` or `vec![DataType::Float32]` /// since i32 and f32 can be casted to f64 Coercible(Vec), + /// The number of arguments that are comparable + Comparable(usize), /// Fixed number of arguments of arbitrary types, number should be larger than 0 Any(usize), /// Matches exactly one of a list of [`TypeSignature`]s. Coercion is attempted to match @@ -134,8 +136,6 @@ pub enum TypeSignature { /// Null is considerd as `Utf8` by default /// Dictionary with string value type is also handled. String(usize), - /// Fixed number of arguments of boolean types. - Boolean(usize), /// Zero argument NullAry, } @@ -213,15 +213,15 @@ impl TypeSignature { .collect::>() .join(", ")] } - TypeSignature::Boolean(num) => { - vec![format!("Boolean({num})")] - } TypeSignature::String(num) => { vec![format!("String({num})")] } TypeSignature::Numeric(num) => { vec![format!("Numeric({num})")] } + TypeSignature::Comparable(num) => { + vec![format!("Comparable({num})")] + } TypeSignature::Coercible(types) => { vec![Self::join_types(types, ", ")] } @@ -300,11 +300,9 @@ impl TypeSignature { .into_iter() .map(|dt| vec![dt; *arg_count]) .collect::>(), - TypeSignature::Boolean(arg_count) => { - vec![vec![DataType::Boolean; *arg_count]] - } // TODO: Implement for other types TypeSignature::Any(_) + | TypeSignature::Comparable(_) | TypeSignature::NullAry | TypeSignature::VariadicAny | TypeSignature::ArraySignature(_) @@ -393,14 +391,6 @@ impl Signature { } } - /// A specified number of boolean arguments - pub fn boolean(arg_count: usize, volatility: Volatility) -> Self { - Self { - type_signature: TypeSignature::Boolean(arg_count), - volatility, - } - } - /// An arbitrary number of arguments of any type. pub fn variadic_any(volatility: Volatility) -> Self { Self { @@ -434,6 +424,14 @@ impl Signature { } } + /// Used for function that expects comparable data types, it will try to coerced all the types into single final one. + pub fn comparable(arg_count: usize, volatility: Volatility) -> Self { + Self { + type_signature: TypeSignature::Comparable(arg_count), + volatility, + } + } + pub fn nullary(volatility: Volatility) -> Self { Signature { type_signature: TypeSignature::NullAry, diff --git a/datafusion/expr-common/src/type_coercion/binary.rs b/datafusion/expr-common/src/type_coercion/binary.rs index 31fe6a59baee..39ccf202574f 100644 --- a/datafusion/expr-common/src/type_coercion/binary.rs +++ b/datafusion/expr-common/src/type_coercion/binary.rs @@ -28,6 +28,7 @@ use arrow::datatypes::{ DataType, Field, FieldRef, Fields, TimeUnit, DECIMAL128_MAX_PRECISION, DECIMAL128_MAX_SCALE, DECIMAL256_MAX_PRECISION, DECIMAL256_MAX_SCALE, }; +use datafusion_common::types::NativeType; use datafusion_common::{ exec_datafusion_err, exec_err, internal_err, plan_datafusion_err, plan_err, Result, }; @@ -643,6 +644,21 @@ pub fn comparison_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option Option { + if lhs_type == rhs_type { + // same type => equality is possible + return Some(lhs_type.clone()); + } + binary_numeric_coercion(lhs_type, rhs_type) + .or_else(|| string_coercion(lhs_type, rhs_type)) + .or_else(|| null_coercion(lhs_type, rhs_type)) + .or_else(|| string_numeric_coercion_as_numeric(lhs_type, rhs_type)) +} + /// Coerce `lhs_type` and `rhs_type` to a common type for the purposes of a comparison operation /// where one is numeric and one is `Utf8`/`LargeUtf8`. fn string_numeric_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option { @@ -656,6 +672,24 @@ fn string_numeric_coercion(lhs_type: &DataType, rhs_type: &DataType) -> Option Option { + let lhs_logical_type = NativeType::from(lhs_type); + let rhs_logical_type = NativeType::from(rhs_type); + if lhs_logical_type.is_numeric() && rhs_logical_type == NativeType::String { + return Some(lhs_type.to_owned()); + } + if rhs_logical_type.is_numeric() && lhs_logical_type == NativeType::String { + return Some(rhs_type.to_owned()); + } + + None +} + /// Coerce `lhs_type` and `rhs_type` to a common type for the purposes of a comparison operation /// where one is temporal and one is `Utf8View`/`Utf8`/`LargeUtf8`. /// diff --git a/datafusion/expr/src/type_coercion/functions.rs b/datafusion/expr/src/type_coercion/functions.rs index 758e462145cd..5f52c7ccc20e 100644 --- a/datafusion/expr/src/type_coercion/functions.rs +++ b/datafusion/expr/src/type_coercion/functions.rs @@ -29,7 +29,7 @@ use datafusion_common::{ }; use datafusion_expr_common::{ signature::{ArrayFunctionSignature, FIXED_SIZE_LIST_WILDCARD, TIMEZONE_WILDCARD}, - type_coercion::binary::string_coercion, + type_coercion::binary::{comparison_coercion_numeric, string_coercion}, }; use std::sync::Arc; @@ -180,9 +180,9 @@ fn is_well_supported_signature(type_signature: &TypeSignature) -> bool { | TypeSignature::Numeric(_) | TypeSignature::String(_) | TypeSignature::Coercible(_) - | TypeSignature::Boolean(_) | TypeSignature::Any(_) | TypeSignature::NullAry + | TypeSignature::Comparable(_) ) } @@ -483,33 +483,6 @@ fn get_valid_types( vec![vec![base_type_or_default_type(&coerced_type); *number]] } - TypeSignature::Boolean(number) => { - function_length_check(current_types.len(), *number)?; - - // Find common boolean type amongst the given types - let mut valid_type = current_types.first().unwrap().to_owned(); - for t in current_types.iter().skip(1) { - let logical_data_type: NativeType = t.into(); - if logical_data_type == NativeType::Null { - continue; - } - - if logical_data_type != NativeType::Boolean { - return plan_err!( - "The signature expected NativeType::Boolean but received {logical_data_type}" - ); - } - - valid_type = t.to_owned(); - } - - let logical_data_type: NativeType = valid_type.clone().into(); - if logical_data_type == NativeType::Null { - valid_type = DataType::Boolean; - } - - vec![vec![valid_type; *number]] - } TypeSignature::Numeric(number) => { function_length_check(current_types.len(), *number)?; @@ -548,6 +521,23 @@ fn get_valid_types( vec![vec![valid_type; *number]] } + TypeSignature::Comparable(num) => { + function_length_check(current_types.len(), *num)?; + let mut target_type = current_types[0].to_owned(); + for data_type in current_types.iter().skip(1) { + if let Some(dt) = comparison_coercion_numeric(&target_type, data_type) { + target_type = dt; + } else { + return plan_err!("{target_type} and {data_type} is not comparable"); + } + } + // Convert null to String type. + if target_type.is_null() { + vec![vec![DataType::Utf8View; *num]] + } else { + vec![vec![target_type; *num]] + } + } TypeSignature::Coercible(target_types) => { function_length_check(current_types.len(), target_types.len())?; diff --git a/datafusion/functions/src/core/nullif.rs b/datafusion/functions/src/core/nullif.rs index 9841c7572068..05af8d3f589e 100644 --- a/datafusion/functions/src/core/nullif.rs +++ b/datafusion/functions/src/core/nullif.rs @@ -17,7 +17,7 @@ use arrow::datatypes::DataType; use datafusion_common::{exec_err, Result}; -use datafusion_expr::{ColumnarValue, Documentation, TypeSignature}; +use datafusion_expr::{ColumnarValue, Documentation}; use arrow::compute::kernels::cmp::eq; use arrow::compute::kernels::nullif::nullif; @@ -41,15 +41,20 @@ impl Default for NullIfFunc { impl NullIfFunc { pub fn new() -> Self { Self { - signature: Signature::one_of( - // String is at the beginning so the return type is String if both args are Nulls - vec![ - TypeSignature::String(2), - TypeSignature::Numeric(2), - TypeSignature::Boolean(2), - ], - Volatility::Immutable, - ), + // Documentation mentioned in Postgres, + // The result has the same type as the first argument — but there is a subtlety. + // What is actually returned is the first argument of the implied = operator, + // and in some cases that will have been promoted to match the second argument's type. + // For example, NULLIF(1, 2.2) yields numeric, because there is no integer = numeric operator, only numeric = numeric + // + // We don't strictly follow Postgres or DuckDB for **simplicity**. + // In this function, we will coerce arguments to the same data type for comparison need. Unlike DuckDB + // we don't return the **original** first argument type but return the final coerced type. + // + // In Postgres, nullif('2', 2) returns Null but nullif('2::varchar', 2) returns error. + // While in DuckDB both query returns Null. We follow DuckDB in this case since I think they are equivalent thing and should + // have the same result as well. + signature: Signature::comparable(2, Volatility::Immutable), } } } diff --git a/datafusion/sqllogictest/test_files/nullif.slt b/datafusion/sqllogictest/test_files/nullif.slt index 08c7982b2df1..18642f6971ca 100644 --- a/datafusion/sqllogictest/test_files/nullif.slt +++ b/datafusion/sqllogictest/test_files/nullif.slt @@ -112,19 +112,38 @@ select nullif(1.0, 2); ---- 1 -query error DataFusion error: Error during planning: Internal error: Failed to match any signature, errors: Error during planning: The signature expected NativeType::String but received NativeType::Int64 +query error DataFusion error: Arrow error: Cast error: Cannot cast string 'a' to value of Int64 type select nullif(2, 'a'); - query T select nullif('2', '3'); ---- 2 -# TODO: support numeric string -# This query success in Postgres and DuckDB -query error DataFusion error: Error during planning: Internal error: Failed to match any signature, errors: Error during planning: The signature expected NativeType::String but received NativeType::Int64 +query I select nullif(2, '1'); +---- +2 + +query I +select nullif('2', 2); +---- +NULL + +query I +select nullif('1', 2); +---- +1 + +statement ok +create table t(a varchar, b int) as values ('1', 2), ('2', 2), ('3', 2); + +query I +select nullif(a, b) from t; +---- +1 +NULL +3 query T SELECT NULLIF(arrow_cast('a', 'Utf8View'), 'a'); From 8d0b73fc1defd136b5d45d0e1ca89b96f14eb8e3 Mon Sep 17 00:00:00 2001 From: Jay Zhan Date: Fri, 22 Nov 2024 09:03:38 +0800 Subject: [PATCH 5/6] add doc for signature Signed-off-by: Jay Zhan --- datafusion/expr-common/src/signature.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index a23180dd79ae..1caca7870c05 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -113,7 +113,14 @@ pub enum TypeSignature { /// arguments like `vec![DataType::Int32]` or `vec![DataType::Float32]` /// since i32 and f32 can be casted to f64 Coercible(Vec), - /// The number of arguments that are comparable + /// The arguments will be coerced to a single type based on the comparison rules. + /// For example, i32 and i64 has coerced type Int64. + /// + /// Note: + /// - If compares with numeric and string, numeric is preferred for numeric string cases. For example, nullif('2', 1) has coerced types Int64. + /// - If the result is Null, it will be coerced to String (Utf8View). + /// + /// See `comparison_coercion_numeric` for more details. Comparable(usize), /// Fixed number of arguments of arbitrary types, number should be larger than 0 Any(usize), From e4316a2b0948f320184b82356b0fa74ee908243b Mon Sep 17 00:00:00 2001 From: Jay Zhan Date: Fri, 22 Nov 2024 09:03:53 +0800 Subject: [PATCH 6/6] add doc for signature Signed-off-by: Jay Zhan --- datafusion/expr-common/src/signature.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datafusion/expr-common/src/signature.rs b/datafusion/expr-common/src/signature.rs index 1caca7870c05..5e0fe2d7b85f 100644 --- a/datafusion/expr-common/src/signature.rs +++ b/datafusion/expr-common/src/signature.rs @@ -119,7 +119,7 @@ pub enum TypeSignature { /// Note: /// - If compares with numeric and string, numeric is preferred for numeric string cases. For example, nullif('2', 1) has coerced types Int64. /// - If the result is Null, it will be coerced to String (Utf8View). - /// + /// /// See `comparison_coercion_numeric` for more details. Comparable(usize), /// Fixed number of arguments of arbitrary types, number should be larger than 0