This is an automated email from the ASF dual-hosted git repository. morningman pushed a commit to branch branch-2.0 in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-2.0 by this push: new cf9a2097786 [fix](udf)java udf does not support overloaded evaluate method (#22681) (#26768) cf9a2097786 is described below commit cf9a20977862ec6ec303c5e8a73ebb8cc016c5ec Author: Mingyu Chen <morning...@163.com> AuthorDate: Fri Nov 10 19:27:15 2023 +0800 [fix](udf)java udf does not support overloaded evaluate method (#22681) (#26768) Co-authored-by: HB <hubia...@corp.netease.com> --- .../apache/doris/analysis/CreateFunctionStmt.java | 81 +++++++++++--------- .../javaudf_p0/test_javaudf_multi_evaluate.out | 23 ++++++ .../org/apache/doris/udf/MultiEvaluateTest.java | 35 +++++++++ .../javaudf_p0/test_javaudf_multi_evaluate.groovy | 87 ++++++++++++++++++++++ 4 files changed, 192 insertions(+), 34 deletions(-) diff --git a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java index ac1ba1b4e65..54159abb9a9 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java +++ b/fe/fe-core/src/main/java/org/apache/doris/analysis/CreateFunctionStmt.java @@ -63,10 +63,12 @@ import java.net.URL; import java.net.URLClassLoader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; // create a user define function public class CreateFunctionStmt extends DdlStmt { @@ -530,45 +532,56 @@ public class CreateFunctionStmt extends DdlStmt { URL[] urls = {new URL("jar:" + userFile + "!/")}; try (URLClassLoader cl = URLClassLoader.newInstance(urls)) { Class udfClass = cl.loadClass(clazz); - - Method eval = null; - for (Method m : udfClass.getMethods()) { - if (!m.getDeclaringClass().equals(udfClass)) { - continue; - } - String name = m.getName(); - if (EVAL_METHOD_KEY.equals(name) && eval == null) { - eval = m; - } else if (EVAL_METHOD_KEY.equals(name)) { - throw new AnalysisException(String.format( - "UDF class '%s' has multiple methods with name '%s' ", udfClass.getCanonicalName(), - EVAL_METHOD_KEY)); - } - } - if (eval == null) { + List<Method> evalList = Arrays.stream(udfClass.getMethods()) + .filter(m -> m.getDeclaringClass().equals(udfClass) && EVAL_METHOD_KEY.equals(m.getName())) + .collect(Collectors.toList()); + if (evalList.size() == 0) { throw new AnalysisException(String.format( - "No method '%s' in class '%s'!", EVAL_METHOD_KEY, udfClass.getCanonicalName())); + "No method '%s' in class '%s'!", EVAL_METHOD_KEY, udfClass.getCanonicalName())); } - if (Modifier.isStatic(eval.getModifiers())) { + List<Method> evalNonStaticAndPublicList = evalList.stream() + .filter(m -> !Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers())) + .collect(Collectors.toList()); + if (evalNonStaticAndPublicList.size() == 0) { throw new AnalysisException( - String.format("Method '%s' in class '%s' should be non-static", eval.getName(), - udfClass.getCanonicalName())); + String.format("Method '%s' in class '%s' should be non-static and public", EVAL_METHOD_KEY, + udfClass.getCanonicalName())); } - if (!Modifier.isPublic(eval.getModifiers())) { + List<Method> evalArgLengthMatchList = evalNonStaticAndPublicList.stream().filter( + m -> m.getParameters().length == argsDef.getArgTypes().length).collect(Collectors.toList()); + if (evalArgLengthMatchList.size() == 0) { throw new AnalysisException( - String.format("Method '%s' in class '%s' should be public", eval.getName(), - udfClass.getCanonicalName())); - } - if (eval.getParameters().length != argsDef.getArgTypes().length) { - throw new AnalysisException( - String.format("The number of parameters for method '%s' in class '%s' should be %d", - eval.getName(), udfClass.getCanonicalName(), argsDef.getArgTypes().length)); - } - - checkUdfType(udfClass, eval, returnType.getType(), eval.getReturnType(), "return"); - for (int i = 0; i < eval.getParameters().length; i++) { - Parameter p = eval.getParameters()[i]; - checkUdfType(udfClass, eval, argsDef.getArgTypes()[i], p.getType(), p.getName()); + String.format("The number of parameters for method '%s' in class '%s' should be %d", + EVAL_METHOD_KEY, udfClass.getCanonicalName(), argsDef.getArgTypes().length)); + } else if (evalArgLengthMatchList.size() == 1) { + Method method = evalArgLengthMatchList.get(0); + checkUdfType(udfClass, method, returnType.getType(), method.getReturnType(), "return"); + for (int i = 0; i < method.getParameters().length; i++) { + Parameter p = method.getParameters()[i]; + checkUdfType(udfClass, method, argsDef.getArgTypes()[i], p.getType(), p.getName()); + } + } else { + // If multiple methods have the same parameters, + // the error message returned cannot be as specific as a single method + boolean hasError = false; + for (Method method : evalArgLengthMatchList) { + try { + checkUdfType(udfClass, method, returnType.getType(), method.getReturnType(), "return"); + for (int i = 0; i < method.getParameters().length; i++) { + Parameter p = method.getParameters()[i]; + checkUdfType(udfClass, method, argsDef.getArgTypes()[i], p.getType(), p.getName()); + } + hasError = false; + break; + } catch (AnalysisException e) { + hasError = true; + } + } + if (hasError) { + throw new AnalysisException(String.format( + "Multi methods '%s' in class '%s' and no one passed parameter matching verification", + EVAL_METHOD_KEY, udfClass.getCanonicalName())); + } } } catch (ClassNotFoundException e) { throw new AnalysisException("Class [" + clazz + "] not found in file :" + userFile); diff --git a/regression-test/data/javaudf_p0/test_javaudf_multi_evaluate.out b/regression-test/data/javaudf_p0/test_javaudf_multi_evaluate.out new file mode 100644 index 00000000000..204cfba8a1a --- /dev/null +++ b/regression-test/data/javaudf_p0/test_javaudf_multi_evaluate.out @@ -0,0 +1,23 @@ +-- This file is automatically generated. You should know what you did if you want to edit this +-- !select_default -- +1 1.11 2 3 +2 \N 3 4 +3 3.33 2 \N + +-- !select -- +2.11 + +-- !select -- +\N + +-- !select -- +5 + +-- !select -- +\N + +-- !select -- +7 + +-- !select -- +\N diff --git a/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MultiEvaluateTest.java b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MultiEvaluateTest.java new file mode 100644 index 00000000000..8aa12b34393 --- /dev/null +++ b/regression-test/java-udf-src/src/main/java/org/apache/doris/udf/MultiEvaluateTest.java @@ -0,0 +1,35 @@ +// 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. + +package org.apache.doris.udf; +import org.apache.hadoop.hive.ql.exec.UDF; + +public class MultiEvaluateTest extends UDF { + + public Float evaluate(Float flo) { + return flo == null ? null : flo + 1; + } + + public Integer evaluate(Integer value) { + return value == null ? null : value + 1; + } + + public Integer evaluate(Integer value1, Integer value2) { + return value1 == null || value2 == null ? null : value1 + value2; + } + +} diff --git a/regression-test/suites/javaudf_p0/test_javaudf_multi_evaluate.groovy b/regression-test/suites/javaudf_p0/test_javaudf_multi_evaluate.groovy new file mode 100644 index 00000000000..d8f390bbaac --- /dev/null +++ b/regression-test/suites/javaudf_p0/test_javaudf_multi_evaluate.groovy @@ -0,0 +1,87 @@ +// 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_multi_evaluate") { + def tableName = "test_javaudf_multi_evaluate" + def jarPath = """${context.file.parent}/jars/java-udf-case-jar-with-dependencies.jar""" + + log.info("Jar path: ${jarPath}".toString()) + try { + sql """ DROP TABLE IF EXISTS ${tableName} """ + sql """ + CREATE TABLE IF NOT EXISTS ${tableName} ( + `user_id` INT NOT NULL COMMENT "", + `float_1` FLOAT COMMENT "", + `int_1` INT COMMENT "", + `int_2` INT COMMENT "" + ) + DISTRIBUTED BY HASH(user_id) PROPERTIES("replication_num" = "1"); + """ + + + sql """ INSERT INTO ${tableName} (`user_id`,`float_1`,`int_1`,`int_2`) VALUES + (1,1.11,2,3), + (2,null,3,4), + (3,3.33,2,null); + """ + qt_select_default """ SELECT * FROM ${tableName} t ORDER BY user_id; """ + + File path = new File(jarPath) + if (!path.exists()) { + throw new IllegalStateException("""${jarPath} doesn't exist! """) + } + + sql """ CREATE FUNCTION java_udf_multi_evaluate_test(FLOAT) RETURNS FLOAT PROPERTIES ( + "file"="file://${jarPath}", + "symbol"="org.apache.doris.udf.MultiEvaluateTest", + "type"="JAVA_UDF" + ); """ + + qt_select """ SELECT java_udf_multi_evaluate_test(float_1) FROM ${tableName} where user_id = 1; """ + qt_select """ SELECT java_udf_multi_evaluate_test(float_1) FROM ${tableName} where user_id = 2; """ + + sql """ CREATE FUNCTION java_udf_multi_evaluate_test(int) RETURNS int PROPERTIES ( + "file"="file://${jarPath}", + "symbol"="org.apache.doris.udf.MultiEvaluateTest", + "type"="JAVA_UDF" + ); """ + + qt_select """ SELECT java_udf_multi_evaluate_test(int_2) FROM ${tableName} where user_id = 2; """ + qt_select """ SELECT java_udf_multi_evaluate_test(int_2) FROM ${tableName} where user_id = 3; """ + + sql """ CREATE FUNCTION java_udf_multi_evaluate_test(int,int) RETURNS int PROPERTIES ( + "file"="file://${jarPath}", + "symbol"="org.apache.doris.udf.MultiEvaluateTest", + "type"="JAVA_UDF" + ); """ + + qt_select """ SELECT java_udf_multi_evaluate_test(int_1, int_2) FROM ${tableName} where user_id = 2; """ + qt_select """ SELECT java_udf_multi_evaluate_test(int_1, int_2) FROM ${tableName} where user_id = 3; """ + + } finally { + try_sql("DROP FUNCTION IF EXISTS java_udf_multi_evaluate_test(FLOAT);") + try_sql("DROP FUNCTION IF EXISTS java_udf_multi_evaluate_test(int);") + try_sql("DROP FUNCTION IF EXISTS java_udf_multi_evaluate_test(int,int);") + try_sql("DROP TABLE IF EXISTS ${tableName}") + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org For additional commands, e-mail: commits-h...@doris.apache.org