This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion.git


The following commit(s) were added to refs/heads/main by this push:
     new 6f86bfad2f feat: enable "substring" as a UDF in addition to "substr" 
(#11277)
6f86bfad2f is described below

commit 6f86bfad2fa12478c29eaa14355d0801b4ebf489
Author: Arttu <[email protected]>
AuthorDate: Sat Jul 6 13:27:18 2024 +0200

    feat: enable "substring" as a UDF in addition to "substr" (#11277)
    
    * feat: enable "substring" as a UDF in addition to "substr"
    
    Substrait uses the name "substring", and it already exists in DF SQL
    
    The setup here is a bit weird; I'd have added substring as an alias for 
substr, but then we have here this "substring" version being created as udf 
already and exported through the export_functions, with slightly different args 
than substr
    (even though in reality the underlying function for both is the same substr 
impl).
    
    I think this PR should work, but if you have suggestions on how to make the 
situation here cleaner, I'd be happy to!
    
    * okay redo everything: add an alias instead, and add renaming in the 
substrait producer
    
    * add alias into scalar_functions.md
---
 datafusion/functions/src/unicode/substr.rs         |  6 +++
 datafusion/substrait/src/logical_plan/consumer.rs  | 60 +++++++++++-----------
 datafusion/substrait/src/logical_plan/producer.rs  | 32 +++++++-----
 .../tests/cases/roundtrip_logical_plan.rs          |  2 +-
 docs/source/user-guide/sql/scalar_functions.md     |  8 +++
 5 files changed, 65 insertions(+), 43 deletions(-)

diff --git a/datafusion/functions/src/unicode/substr.rs 
b/datafusion/functions/src/unicode/substr.rs
index c297182057..9d15920bb6 100644
--- a/datafusion/functions/src/unicode/substr.rs
+++ b/datafusion/functions/src/unicode/substr.rs
@@ -32,6 +32,7 @@ use crate::utils::{make_scalar_function, utf8_to_str_type};
 #[derive(Debug)]
 pub struct SubstrFunc {
     signature: Signature,
+    aliases: Vec<String>,
 }
 
 impl Default for SubstrFunc {
@@ -53,6 +54,7 @@ impl SubstrFunc {
                 ],
                 Volatility::Immutable,
             ),
+            aliases: vec![String::from("substring")],
         }
     }
 }
@@ -81,6 +83,10 @@ impl ScalarUDFImpl for SubstrFunc {
             other => exec_err!("Unsupported data type {other:?} for function 
substr"),
         }
     }
+
+    fn aliases(&self) -> &[String] {
+        &self.aliases
+    }
 }
 
 /// Extracts the substring of string starting at the start'th character, and 
extending for count characters if that is specified. (Same as substring(string 
from start for count).)
diff --git a/datafusion/substrait/src/logical_plan/consumer.rs 
b/datafusion/substrait/src/logical_plan/consumer.rs
index cc10ea0619..c65943643e 100644
--- a/datafusion/substrait/src/logical_plan/consumer.rs
+++ b/datafusion/substrait/src/logical_plan/consumer.rs
@@ -88,36 +88,36 @@ use substrait::proto::{
 };
 use substrait::proto::{FunctionArgument, SortField};
 
-pub fn name_to_op(name: &str) -> Result<Operator> {
+pub fn name_to_op(name: &str) -> Option<Operator> {
     match name {
-        "equal" => Ok(Operator::Eq),
-        "not_equal" => Ok(Operator::NotEq),
-        "lt" => Ok(Operator::Lt),
-        "lte" => Ok(Operator::LtEq),
-        "gt" => Ok(Operator::Gt),
-        "gte" => Ok(Operator::GtEq),
-        "add" => Ok(Operator::Plus),
-        "subtract" => Ok(Operator::Minus),
-        "multiply" => Ok(Operator::Multiply),
-        "divide" => Ok(Operator::Divide),
-        "mod" => Ok(Operator::Modulo),
-        "and" => Ok(Operator::And),
-        "or" => Ok(Operator::Or),
-        "is_distinct_from" => Ok(Operator::IsDistinctFrom),
-        "is_not_distinct_from" => Ok(Operator::IsNotDistinctFrom),
-        "regex_match" => Ok(Operator::RegexMatch),
-        "regex_imatch" => Ok(Operator::RegexIMatch),
-        "regex_not_match" => Ok(Operator::RegexNotMatch),
-        "regex_not_imatch" => Ok(Operator::RegexNotIMatch),
-        "bitwise_and" => Ok(Operator::BitwiseAnd),
-        "bitwise_or" => Ok(Operator::BitwiseOr),
-        "str_concat" => Ok(Operator::StringConcat),
-        "at_arrow" => Ok(Operator::AtArrow),
-        "arrow_at" => Ok(Operator::ArrowAt),
-        "bitwise_xor" => Ok(Operator::BitwiseXor),
-        "bitwise_shift_right" => Ok(Operator::BitwiseShiftRight),
-        "bitwise_shift_left" => Ok(Operator::BitwiseShiftLeft),
-        _ => not_impl_err!("Unsupported function name: {name:?}"),
+        "equal" => Some(Operator::Eq),
+        "not_equal" => Some(Operator::NotEq),
+        "lt" => Some(Operator::Lt),
+        "lte" => Some(Operator::LtEq),
+        "gt" => Some(Operator::Gt),
+        "gte" => Some(Operator::GtEq),
+        "add" => Some(Operator::Plus),
+        "subtract" => Some(Operator::Minus),
+        "multiply" => Some(Operator::Multiply),
+        "divide" => Some(Operator::Divide),
+        "mod" => Some(Operator::Modulo),
+        "and" => Some(Operator::And),
+        "or" => Some(Operator::Or),
+        "is_distinct_from" => Some(Operator::IsDistinctFrom),
+        "is_not_distinct_from" => Some(Operator::IsNotDistinctFrom),
+        "regex_match" => Some(Operator::RegexMatch),
+        "regex_imatch" => Some(Operator::RegexIMatch),
+        "regex_not_match" => Some(Operator::RegexNotMatch),
+        "regex_not_imatch" => Some(Operator::RegexNotIMatch),
+        "bitwise_and" => Some(Operator::BitwiseAnd),
+        "bitwise_or" => Some(Operator::BitwiseOr),
+        "str_concat" => Some(Operator::StringConcat),
+        "at_arrow" => Some(Operator::AtArrow),
+        "arrow_at" => Some(Operator::ArrowAt),
+        "bitwise_xor" => Some(Operator::BitwiseXor),
+        "bitwise_shift_right" => Some(Operator::BitwiseShiftRight),
+        "bitwise_shift_left" => Some(Operator::BitwiseShiftLeft),
+        _ => None,
     }
 }
 
@@ -1124,7 +1124,7 @@ pub async fn from_substrait_rex(
                 Ok(Arc::new(Expr::ScalarFunction(
                     expr::ScalarFunction::new_udf(func.to_owned(), args),
                 )))
-            } else if let Ok(op) = name_to_op(fn_name) {
+            } else if let Some(op) = name_to_op(fn_name) {
                 if f.arguments.len() < 2 {
                     return not_impl_err!(
                         "Expect at least two arguments for binary operator 
{op:?}, the provided number of operators is {:?}",
diff --git a/datafusion/substrait/src/logical_plan/producer.rs 
b/datafusion/substrait/src/logical_plan/producer.rs
index c3bef1689d..899fec21f8 100644
--- a/datafusion/substrait/src/logical_plan/producer.rs
+++ b/datafusion/substrait/src/logical_plan/producer.rs
@@ -818,7 +818,7 @@ pub fn to_substrait_agg_measure(
                     for arg in args {
                         arguments.push(FunctionArgument { arg_type: 
Some(ArgType::Value(to_substrait_rex(ctx, arg, schema, 0, extension_info)?)) });
                     }
-                    let function_anchor = _register_function(fun.to_string(), 
extension_info);
+                    let function_anchor = register_function(fun.to_string(), 
extension_info);
                     Ok(Measure {
                         measure: Some(AggregateFunction {
                             function_reference: function_anchor,
@@ -849,7 +849,7 @@ pub fn to_substrait_agg_measure(
                     for arg in args {
                         arguments.push(FunctionArgument { arg_type: 
Some(ArgType::Value(to_substrait_rex(ctx, arg, schema, 0, extension_info)?)) });
                     }
-                    let function_anchor = 
_register_function(fun.name().to_string(), extension_info);
+                    let function_anchor = 
register_function(fun.name().to_string(), extension_info);
                     Ok(Measure {
                         measure: Some(AggregateFunction {
                             function_reference: function_anchor,
@@ -917,7 +917,7 @@ fn to_substrait_sort_field(
     }
 }
 
-fn _register_function(
+fn register_function(
     function_name: String,
     extension_info: &mut (
         Vec<extensions::SimpleExtensionDeclaration>,
@@ -926,6 +926,14 @@ fn _register_function(
 ) -> u32 {
     let (function_extensions, function_set) = extension_info;
     let function_name = function_name.to_lowercase();
+
+    // Some functions are named differently in Substrait default extensions 
than in DF
+    // Rename those to match the Substrait extensions for interoperability
+    let function_name = match function_name.as_str() {
+        "substr" => "substring".to_string(),
+        _ => function_name,
+    };
+
     // To prevent ambiguous references between ScalarFunctions and 
AggregateFunctions,
     // a plan-relative identifier starting from 0 is used as the 
function_anchor.
     // The consumer is responsible for correctly registering 
<function_anchor,function_name>
@@ -969,7 +977,7 @@ pub fn make_binary_op_scalar_func(
     ),
 ) -> Expression {
     let function_anchor =
-        _register_function(operator_to_name(op).to_string(), extension_info);
+        register_function(operator_to_name(op).to_string(), extension_info);
     Expression {
         rex_type: Some(RexType::ScalarFunction(ScalarFunction {
             function_reference: function_anchor,
@@ -1044,7 +1052,7 @@ pub fn to_substrait_rex(
 
             if *negated {
                 let function_anchor =
-                    _register_function("not".to_string(), extension_info);
+                    register_function("not".to_string(), extension_info);
 
                 Ok(Expression {
                     rex_type: Some(RexType::ScalarFunction(ScalarFunction {
@@ -1076,7 +1084,7 @@ pub fn to_substrait_rex(
             }
 
             let function_anchor =
-                _register_function(fun.name().to_string(), extension_info);
+                register_function(fun.name().to_string(), extension_info);
             Ok(Expression {
                 rex_type: Some(RexType::ScalarFunction(ScalarFunction {
                     function_reference: function_anchor,
@@ -1252,7 +1260,7 @@ pub fn to_substrait_rex(
             null_treatment: _,
         }) => {
             // function reference
-            let function_anchor = _register_function(fun.to_string(), 
extension_info);
+            let function_anchor = register_function(fun.to_string(), 
extension_info);
             // arguments
             let mut arguments: Vec<FunctionArgument> = vec![];
             for arg in args {
@@ -1330,7 +1338,7 @@ pub fn to_substrait_rex(
             };
             if *negated {
                 let function_anchor =
-                    _register_function("not".to_string(), extension_info);
+                    register_function("not".to_string(), extension_info);
 
                 Ok(Expression {
                     rex_type: Some(RexType::ScalarFunction(ScalarFunction {
@@ -1727,9 +1735,9 @@ fn make_substrait_like_expr(
     ),
 ) -> Result<Expression> {
     let function_anchor = if ignore_case {
-        _register_function("ilike".to_string(), extension_info)
+        register_function("ilike".to_string(), extension_info)
     } else {
-        _register_function("like".to_string(), extension_info)
+        register_function("like".to_string(), extension_info)
     };
     let expr = to_substrait_rex(ctx, expr, schema, col_ref_offset, 
extension_info)?;
     let pattern = to_substrait_rex(ctx, pattern, schema, col_ref_offset, 
extension_info)?;
@@ -1759,7 +1767,7 @@ fn make_substrait_like_expr(
     };
 
     if negated {
-        let function_anchor = _register_function("not".to_string(), 
extension_info);
+        let function_anchor = register_function("not".to_string(), 
extension_info);
 
         Ok(Expression {
             rex_type: Some(RexType::ScalarFunction(ScalarFunction {
@@ -2128,7 +2136,7 @@ fn to_substrait_unary_scalar_fn(
         HashMap<String, u32>,
     ),
 ) -> Result<Expression> {
-    let function_anchor = _register_function(fn_name.to_string(), 
extension_info);
+    let function_anchor = register_function(fn_name.to_string(), 
extension_info);
     let substrait_expr =
         to_substrait_rex(ctx, arg, schema, col_ref_offset, extension_info)?;
 
diff --git a/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs 
b/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
index 7ed376f62b..dbc2e404bf 100644
--- a/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
+++ b/datafusion/substrait/tests/cases/roundtrip_logical_plan.rs
@@ -327,7 +327,7 @@ async fn simple_scalar_function_pow() -> Result<()> {
 
 #[tokio::test]
 async fn simple_scalar_function_substr() -> Result<()> {
-    roundtrip("SELECT * FROM data WHERE a = SUBSTR('datafusion', 0, 3)").await
+    roundtrip("SELECT SUBSTR(f, 1, 3) FROM data").await
 }
 
 #[tokio::test]
diff --git a/docs/source/user-guide/sql/scalar_functions.md 
b/docs/source/user-guide/sql/scalar_functions.md
index ec34dbf9ba..d636726b45 100644
--- a/docs/source/user-guide/sql/scalar_functions.md
+++ b/docs/source/user-guide/sql/scalar_functions.md
@@ -1132,6 +1132,14 @@ substr(str, start_pos[, length])
 - **length**: Number of characters to extract.
   If not specified, returns the rest of the string after the start position.
 
+#### Aliases
+
+- substring
+
+### `substring`
+
+_Alias of [substr](#substr)._
+
 ### `translate`
 
 Translates characters in a string to specified translation characters.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to