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

dataroaring pushed a commit to branch branch-3.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-3.0 by this push:
     new 9c34a7f6b75 branch-3.0: [feat](udf) support "prefer_udf_over_builtin" 
session variable #51195 (#51275)
9c34a7f6b75 is described below

commit 9c34a7f6b75f91811219ba46c971f7f098bd9a65
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Wed Jun 11 10:54:44 2025 +0800

    branch-3.0: [feat](udf) support "prefer_udf_over_builtin" session variable 
#51195 (#51275)
    
    Cherry-picked from #51195
    
    Co-authored-by: Mingyu Chen (Rayner) <morning...@163.com>
---
 .../org/apache/doris/catalog/FunctionRegistry.java |  66 +++++++++++------
 .../nereids/trees/expressions/functions/Udf.java   |   9 +++
 .../java/org/apache/doris/qe/SessionVariable.java  |   9 +++
 .../data/javaudf_p0/test_javaudf_override.out      | Bin 0 -> 299 bytes
 .../suites/javaudf_p0/test_javaudf_override.groovy |  82 +++++++++++++++++++++
 5 files changed, 142 insertions(+), 24 deletions(-)

diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java
index dc11b5d02be..0ff447811c0 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionRegistry.java
@@ -124,33 +124,28 @@ public class FunctionRegistry {
         int arity = arguments.size();
         String qualifiedName = StringUtils.isEmpty(dbName) ? name : dbName + 
"." + name;
 
-        if (StringUtils.isEmpty(dbName)) {
-            // search internal function only if dbName is empty
-            functionBuilders = name2BuiltinBuilders.get(name.toLowerCase());
-            if (CollectionUtils.isEmpty(functionBuilders) && 
AggCombinerFunctionBuilder.isAggStateCombinator(name)) {
-                String nestedName = 
AggCombinerFunctionBuilder.getNestedName(name);
-                String combinatorSuffix = 
AggCombinerFunctionBuilder.getCombinatorSuffix(name);
-                functionBuilders = 
name2BuiltinBuilders.get(nestedName.toLowerCase());
-                if (functionBuilders != null) {
-                    List<FunctionBuilder> candidateBuilders = 
Lists.newArrayListWithCapacity(functionBuilders.size());
-                    for (FunctionBuilder functionBuilder : functionBuilders) {
-                        AggCombinerFunctionBuilder combinerBuilder
-                                = new 
AggCombinerFunctionBuilder(combinatorSuffix, functionBuilder);
-                        if (combinerBuilder.canApply(arguments)) {
-                            candidateBuilders.add(combinerBuilder);
-                        }
-                    }
-                    functionBuilders = candidateBuilders;
-                }
-            }
-        }
+        boolean preferUdfOverBuiltin = ConnectContext.get() == null ? false
+                : 
ConnectContext.get().getSessionVariable().preferUdfOverBuiltin;
 
-        // search udf
-        if (CollectionUtils.isEmpty(functionBuilders)) {
+        if (preferUdfOverBuiltin) {
+            // find udf first, then find builtin function
             functionBuilders = findUdfBuilder(dbName, name);
-            if (functionBuilders == null || functionBuilders.isEmpty()) {
-                throw new AnalysisException("Can not found function '" + 
qualifiedName + "'");
+            if (CollectionUtils.isEmpty(functionBuilders) && 
StringUtils.isEmpty(dbName)) {
+                // if dbName is not empty, we should search builtin functions 
first
+                functionBuilders = findBuiltinFunctionBuilder(name, arguments);
+            }
+        } else {
+            // find builtin function first, then find udf
+            if (StringUtils.isEmpty(dbName)) {
+                functionBuilders = findBuiltinFunctionBuilder(name, arguments);
             }
+            if (CollectionUtils.isEmpty(functionBuilders)) {
+                functionBuilders = findUdfBuilder(dbName, name);
+            }
+        }
+
+        if (functionBuilders == null || functionBuilders.isEmpty()) {
+            throw new AnalysisException("Can not found function '" + 
qualifiedName + "'");
         }
 
         // check the arity and type
@@ -205,6 +200,29 @@ public class FunctionRegistry {
         return candidateBuilders.get(0);
     }
 
+    private List<FunctionBuilder> findBuiltinFunctionBuilder(String name, 
List<?> arguments) {
+        List<FunctionBuilder> functionBuilders;
+        // search internal function only if dbName is empty
+        functionBuilders = name2BuiltinBuilders.get(name.toLowerCase());
+        if (CollectionUtils.isEmpty(functionBuilders) && 
AggCombinerFunctionBuilder.isAggStateCombinator(name)) {
+            String nestedName = AggCombinerFunctionBuilder.getNestedName(name);
+            String combinatorSuffix = 
AggCombinerFunctionBuilder.getCombinatorSuffix(name);
+            functionBuilders = 
name2BuiltinBuilders.get(nestedName.toLowerCase());
+            if (functionBuilders != null) {
+                List<FunctionBuilder> candidateBuilders = 
Lists.newArrayListWithCapacity(functionBuilders.size());
+                for (FunctionBuilder functionBuilder : functionBuilders) {
+                    AggCombinerFunctionBuilder combinerBuilder
+                            = new AggCombinerFunctionBuilder(combinatorSuffix, 
functionBuilder);
+                    if (combinerBuilder.canApply(arguments)) {
+                        candidateBuilders.add(combinerBuilder);
+                    }
+                }
+                functionBuilders = candidateBuilders;
+            }
+        }
+        return functionBuilders;
+    }
+
     /**
      * public for test.
      */
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Udf.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Udf.java
index e1bdbf5add6..7b4229058c1 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Udf.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/Udf.java
@@ -46,4 +46,13 @@ public interface Udf extends ComputeNullable {
     NullableMode getNullableMode();
 
     List<Expression> children();
+
+    @Override
+    default boolean foldable() {
+        // Udf should not be folded in FE.
+        // When session variable "prefer_udf_fold" is set to true,
+        // we may find udf with same signature as builtin function.
+        // If return true, the FE will calculate the result of the udf with 
builtin function's logic, which is wrong.
+        return false;
+    }
 }
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index cfbdbdfd436..15affdff26b 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -716,6 +716,8 @@ public class SessionVariable implements Serializable, 
Writable {
 
     public static final String SQL_CONVERTOR_CONFIG = "sql_convertor_config";
 
+    public static final String PREFER_UDF_OVER_BUILTIN = 
"prefer_udf_over_builtin";
+
     /**
      * If set false, user couldn't submit analyze SQL and FE won't allocate 
any related resources.
      */
@@ -2450,6 +2452,13 @@ public class SessionVariable implements Serializable, 
Writable {
             })
     public String sqlConvertorConfig = "{}";
 
+    @VariableMgr.VarAttr(name = PREFER_UDF_OVER_BUILTIN, needForward = true,
+            description = {
+                    "是否优先查找 UDF 而不是内置函数",
+                    "Whether to prefer UDF over builtin functions"
+            })
+    public boolean preferUdfOverBuiltin = false;
+
     public void setEnableEsParallelScroll(boolean enableESParallelScroll) {
         this.enableESParallelScroll = enableESParallelScroll;
     }
diff --git a/regression-test/data/javaudf_p0/test_javaudf_override.out 
b/regression-test/data/javaudf_p0/test_javaudf_override.out
new file mode 100644
index 00000000000..d000890723d
Binary files /dev/null and 
b/regression-test/data/javaudf_p0/test_javaudf_override.out differ
diff --git a/regression-test/suites/javaudf_p0/test_javaudf_override.groovy 
b/regression-test/suites/javaudf_p0/test_javaudf_override.groovy
new file mode 100644
index 00000000000..e95bcf989cc
--- /dev/null
+++ b/regression-test/suites/javaudf_p0/test_javaudf_override.groovy
@@ -0,0 +1,82 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import org.codehaus.groovy.runtime.IOGroovyMethods
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Paths
+
+suite("test_javaudf_override") {
+    def tableName = "test_javaudf_override"
+    def jarPath = 
"""${context.file.parent}/jars/java-udf-case-jar-with-dependencies.jar"""
+    scp_udf_file_to_all_be(jarPath)
+
+    log.info("Jar path: ${jarPath}".toString())
+    sql "drop database if exists test_javaudf_override_db"
+    sql "create database test_javaudf_override_db"
+    try {
+        sql """ use test_javaudf_override_db"""
+        sql """ DROP TABLE IF EXISTS ${tableName} """
+        sql """
+        CREATE TABLE IF NOT EXISTS ${tableName} (
+            int_col int
+            )
+            DISTRIBUTED BY HASH(int_col) PROPERTIES("replication_num" = "1");
+        """
+        sql """insert into ${tableName} values(-100),(100),(0)"""
+
+        File path = new File(jarPath)
+        if (!path.exists()) {
+            throw new IllegalStateException("""${jarPath} doesn't exist! """)
+        }
+
+        // 1. create global udf "abs", same name as builtin function "abs"
+
+        sql """DROP GLOBAL FUNCTION IF EXISTS abs(int);"""
+        sql """CREATE GLOBAL FUNCTION abs(int) RETURNS int PROPERTIES (
+            "file"="file://${jarPath}",
+            "symbol"="org.apache.doris.udf.Echo\$EchoInt",
+            "type"="JAVA_UDF"
+        );"""
+
+        // default, will use builtin function first
+        sql "set prefer_udf_over_builtin=false"
+        qt_sql01 """select abs(-1), abs(1), abs(int_col) from ${tableName}"""
+        // set prefer_udf_over_builtin, will use udf first
+        sql "set prefer_udf_over_builtin=true"
+        qt_sql02 """select abs(-1), abs(1), abs(int_col) from ${tableName}"""
+
+        // 2. create database udf "abs", same name as builtin function "abs"
+        def curdb = "test_javaudf_override_db"
+        sql """DROP GLOBAL FUNCTION IF EXISTS abs(int);"""
+        sql "use ${curdb}"
+        sql """CREATE FUNCTION abs(int) RETURNS int PROPERTIES (
+            "file"="file://${jarPath}",
+            "symbol"="org.apache.doris.udf.Echo\$EchoInt",
+            "type"="JAVA_UDF"
+        );"""
+
+        
+        sql "set prefer_udf_over_builtin=false"
+        qt_sql03 """select abs(-1), abs(1), ${curdb}.abs(-1), ${curdb}.abs(1), 
abs(int_col), ${curdb}.abs(int_col) from ${tableName}"""
+        sql "set prefer_udf_over_builtin=true"
+        qt_sql04 """select abs(-1), abs(1), ${curdb}.abs(-1), ${curdb}.abs(1), 
abs(int_col), ${curdb}.abs(int_col) from ${tableName}"""
+
+    } finally {
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to