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