This is an automated email from the ASF dual-hosted git repository.
morrySnow pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new f052d9da44e [fix](UDF) Fix create function rollback after Nereids
failure (#65100)
f052d9da44e is described below
commit f052d9da44e3403b85ed882fbd5f4c95104d4185
Author: morrySnow <[email protected]>
AuthorDate: Tue Jul 14 10:56:47 2026 +0800
[fix](UDF) Fix create function rollback after Nereids failure (#65100)
### What problem does this PR solve?
Problem Summary: CREATE FUNCTION added the new function to the catalog
and wrote the edit log before registering the function in Nereids. If
Nereids registration failed, rollback removed the whole overload list
for that function name, so a failed overload creation could delete
existing overloads. This change registers the function in Nereids before
journaling it and rolls back only the just-created overload when
registration fails.
---
.../java/org/apache/doris/catalog/Database.java | 83 +++++-
.../main/java/org/apache/doris/catalog/Env.java | 7 +
.../org/apache/doris/catalog/FunctionRegistry.java | 8 +-
.../org/apache/doris/catalog/FunctionUtil.java | 18 +-
.../apache/doris/catalog/GlobalFunctionMgr.java | 4 +-
.../org/apache/doris/journal/JournalEntity.java | 6 +
.../trees/expressions/functions/udf/AliasUdf.java | 15 +-
.../expressions/functions/udf/UdfBuilder.java | 4 +
.../plans/commands/CreateFunctionCommand.java | 12 +-
.../apache/doris/persist/CreateFunctionInfo.java | 57 ++++
.../java/org/apache/doris/persist/EditLog.java | 9 +
.../org/apache/doris/persist/OperationType.java | 1 +
.../apache/doris/catalog/CreateFunctionTest.java | 317 +++++++++++++++++++++
.../doris/nereids/trees/expressions/UdfTest.java | 2 +-
.../test_create_function_rollback.groovy | 52 ++++
15 files changed, 572 insertions(+), 23 deletions(-)
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Database.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Database.java
index e54c21bb153..f6f21be4977 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Database.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Database.java
@@ -769,18 +769,87 @@ public class Database extends MetaObject implements
Writable, DatabaseIf<Table>,
}
public synchronized void addFunction(Function function, boolean
ifNotExists) throws UserException {
- function.checkWritable();
- if (FunctionUtil.addFunctionImpl(function, ifNotExists, false,
name2Function)) {
- Env.getCurrentEnv().getEditLog().logAddFunction(function);
- try {
+ addFunctions(ImmutableList.of(function), ifNotExists, false);
+ }
+
+ public synchronized void addTableFunction(Function function, boolean
ifNotExists) throws UserException {
+ // Doris table functions are registered as two functions: the normal
function and its outer variant.
+ Function outerFunction = function.clone();
+ FunctionName name = outerFunction.getFunctionName();
+ name.setFn(name.getFunction() + "_outer");
+ List<Function> functionsToAdd = getTableFunctionsToAdd(function,
outerFunction, ifNotExists);
+ if (functionsToAdd.isEmpty()) {
+ return;
+ }
+ addFunctions(functionsToAdd, false, functionsToAdd.size() > 1);
+ }
+
+ private List<Function> getTableFunctionsToAdd(Function function, Function
outerFunction, boolean ifNotExists)
+ throws UserException {
+ Function existingFunction = getExistingFunction(function);
+ Function existingOuterFunction = getExistingFunction(outerFunction);
+ if (existingFunction == null && existingOuterFunction == null) {
+ return ImmutableList.of(function, outerFunction);
+ }
+ if (!ifNotExists) {
+ throw new
UserException(getExistingFunctionMessage(existingFunction,
existingOuterFunction));
+ }
+ return ImmutableList.of();
+ }
+
+ private String getExistingFunctionMessage(Function existingFunction,
Function existingOuterFunction) {
+ List<String> existingFunctionNames = Lists.newArrayList();
+ if (existingFunction != null) {
+ existingFunctionNames.add(existingFunction.functionName());
+ }
+ if (existingOuterFunction != null) {
+ existingFunctionNames.add(existingOuterFunction.functionName());
+ }
+ return "function already exists: " + String.join(", ",
existingFunctionNames);
+ }
+
+ private Function getExistingFunction(Function function) {
+ try {
+ return getFunction(getFunctionSearchDesc(function));
+ } catch (AnalysisException e) {
+ return null;
+ }
+ }
+
+ private void addFunctions(List<Function> functions, boolean ifNotExists,
boolean logAsBatch) throws UserException {
+ List<Function> addedFunctions = Lists.newArrayList();
+ try {
+ for (Function function : functions) {
+ function.checkWritable();
+ if (FunctionUtil.addFunctionImpl(function, ifNotExists, false,
name2Function)) {
+ addedFunctions.add(function);
+ }
+ }
+ for (Function function : addedFunctions) {
FunctionUtil.translateToNereidsThrows(this.getFullName(),
function);
- } catch (Exception e) {
- name2Function.remove(function.getFunctionName().getFunction());
- throw e;
+ }
+ } catch (Exception e) {
+ for (Function function : addedFunctions) {
+ FunctionUtil.dropFromNereids(this.getFullName(),
getFunctionSearchDesc(function));
+ }
+ for (int i = addedFunctions.size() - 1; i >= 0; i--) {
+ FunctionUtil.removeFunctionImpl(addedFunctions.get(i), true,
name2Function);
+ }
+ throw e;
+ }
+ if (logAsBatch) {
+ Env.getCurrentEnv().getEditLog().logAddFunctions(addedFunctions);
+ } else {
+ for (Function function : addedFunctions) {
+ Env.getCurrentEnv().getEditLog().logAddFunction(function);
}
}
}
+ private FunctionSearchDesc getFunctionSearchDesc(Function function) {
+ return new FunctionSearchDesc(function.getFunctionName(),
function.getArgs(), function.hasVarArgs());
+ }
+
public synchronized void replayAddFunction(Function function) {
try {
FunctionUtil.addFunctionImpl(function, false, true, name2Function);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
index 8ef13b43522..4c5937e4e55 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/Env.java
@@ -214,6 +214,7 @@ import org.apache.doris.persist.BackendTabletsInfo;
import org.apache.doris.persist.BinlogGcInfo;
import org.apache.doris.persist.CleanQueryStatsInfo;
import org.apache.doris.persist.CreateDbInfo;
+import org.apache.doris.persist.CreateFunctionInfo;
import org.apache.doris.persist.DropDbInfo;
import org.apache.doris.persist.DropPartitionInfo;
import org.apache.doris.persist.EditLog;
@@ -6771,6 +6772,12 @@ public class Env {
db.replayAddFunction(function);
}
+ public void replayCreateFunctions(CreateFunctionInfo info) throws
MetaNotFoundException {
+ for (Function function : info.getFunctions()) {
+ replayCreateFunction(function);
+ }
+ }
+
public void replayCreateGlobalFunction(Function function) {
globalFunctionMgr.replayAddFunction(function);
}
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 1a8402520ef..f736fc30f3a 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
@@ -306,14 +306,18 @@ public class FunctionRegistry {
}
}
- public void dropUdf(String dbName, String name, List<DataType> argTypes) {
+ public void dropUdf(String dbName, String name, List<DataType> argTypes,
boolean hasVarArgs) {
if (dbName == null) {
dbName = GLOBAL_FUNCTION;
}
synchronized (name2UdfBuilders) {
Map<String, List<FunctionBuilder>> builders =
name2UdfBuilders.getOrDefault(dbName, ImmutableMap.of());
builders.getOrDefault(name, Lists.newArrayList())
- .removeIf(builder -> ((UdfBuilder)
builder).getArgTypes().equals(argTypes));
+ .removeIf(builder -> {
+ UdfBuilder udfBuilder = (UdfBuilder) builder;
+ return udfBuilder.getArgTypes().equals(argTypes)
+ && udfBuilder.hasVarArguments() == hasVarArgs;
+ });
// the name will be used when show functions, so remove the name
when it's dropped
if (builders.getOrDefault(name, Lists.newArrayList()).isEmpty()) {
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionUtil.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionUtil.java
index c9ef0e81f14..76d454e9ed1 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionUtil.java
@@ -21,6 +21,7 @@ import org.apache.doris.analysis.SetType;
import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.Config;
import org.apache.doris.common.UserException;
+import org.apache.doris.common.util.DebugPointUtil;
import org.apache.doris.nereids.trees.expressions.functions.udf.AliasUdf;
import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdaf;
import org.apache.doris.nereids.trees.expressions.functions.udf.JavaUdf;
@@ -136,6 +137,18 @@ public class FunctionUtil {
return true;
}
+ public static boolean removeFunctionImpl(Function function,
+ ConcurrentMap<String, ImmutableList<Function>> name2Function)
throws UserException {
+ return removeFunctionImpl(function, false, name2Function);
+ }
+
+ public static boolean removeFunctionImpl(Function function, boolean
ifExists,
+ ConcurrentMap<String, ImmutableList<Function>> name2Function)
throws UserException {
+ FunctionSearchDesc functionSearchDesc = new
FunctionSearchDesc(function.getFunctionName(), function.getArgs(),
+ function.hasVarArgs());
+ return dropFunctionImpl(functionSearchDesc, ifExists, name2Function);
+ }
+
public static Function getFunction(FunctionSearchDesc function,
ConcurrentMap<String, ImmutableList<Function>> name2Function)
throws AnalysisException {
String functionName = function.getName().getFunction();
@@ -179,6 +192,9 @@ public class FunctionUtil {
}
public static boolean translateToNereidsThrows(String dbName, Function
function) {
+ if
(DebugPointUtil.isEnable("FunctionUtil.translateToNereidsThrows.exception")) {
+ throw new RuntimeException("debug point
FunctionUtil.translateToNereidsThrows.exception");
+ }
try {
translateToNereidsImpl(dbName, function);
} catch (Exception e) {
@@ -220,7 +236,7 @@ public class FunctionUtil {
String fnName = function.getName().getFunction();
List<DataType> argTypes =
Arrays.stream(function.getArgTypes()).map(DataType::fromCatalogType)
.collect(Collectors.toList());
- Env.getCurrentEnv().getFunctionRegistry().dropUdf(dbName, fnName,
argTypes);
+ Env.getCurrentEnv().getFunctionRegistry().dropUdf(dbName, fnName,
argTypes, function.isVariadic());
} catch (Exception e) {
LOG.warn("Nereids drop function {}:{} failed, caused by: {}",
dbName == null ? "_global_" : dbName,
function.getName(), e);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/GlobalFunctionMgr.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/GlobalFunctionMgr.java
index c2f4fb3c1c0..956b618de78 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/GlobalFunctionMgr.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/GlobalFunctionMgr.java
@@ -74,14 +74,14 @@ public class GlobalFunctionMgr extends MetaObject
implements GsonPostProcessable
function.setGlobal(true);
function.checkWritable();
if (FunctionUtil.addFunctionImpl(function, ifNotExists, false,
name2Function)) {
- Env.getCurrentEnv().getEditLog().logAddGlobalFunction(function);
try {
FunctionUtil.translateToNereidsThrows(null, function);
} catch (Exception e) {
LOG.warn("Nereids add function failed", e);
- name2Function.remove(function.getFunctionName().getFunction());
+ FunctionUtil.removeFunctionImpl(function, true, name2Function);
throw e;
}
+ Env.getCurrentEnv().getEditLog().logAddGlobalFunction(function);
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
index bc0be815609..2c92775bc3f 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/journal/JournalEntity.java
@@ -87,6 +87,7 @@ import org.apache.doris.persist.ColocatePersistInfo;
import org.apache.doris.persist.ConsistencyCheckInfo;
import org.apache.doris.persist.CreateDbInfo;
import org.apache.doris.persist.CreateDictionaryPersistInfo;
+import org.apache.doris.persist.CreateFunctionInfo;
import org.apache.doris.persist.CreateTableInfo;
import org.apache.doris.persist.DatabaseInfo;
import org.apache.doris.persist.DictionaryDecreaseVersionInfo;
@@ -499,6 +500,11 @@ public class JournalEntity implements Writable {
isRead = true;
break;
}
+ case OperationType.OP_ADD_FUNCTIONS: {
+ data = CreateFunctionInfo.read(in);
+ isRead = true;
+ break;
+ }
case OperationType.OP_DROP_FUNCTION: {
data = FunctionSearchDesc.read(in);
isRead = true;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdf.java
index 60833ea70cc..47cc4bbab74 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/AliasUdf.java
@@ -46,6 +46,7 @@ public class AliasUdf extends ScalarFunction implements
ExplicitlyCastableSignat
private final Expression unboundFunction;
private final List<String> parameters;
private final List<DataType> argTypes;
+ private final boolean hasVarArguments;
private final Map<String, String> sessionVariables;
/**
@@ -53,8 +54,17 @@ public class AliasUdf extends ScalarFunction implements
ExplicitlyCastableSignat
*/
public AliasUdf(String name, List<DataType> argTypes, Expression
unboundFunction,
List<String> parameters, Map<String, String> sessionVariables,
Expression... arguments) {
+ this(name, argTypes, false, unboundFunction, parameters,
sessionVariables, arguments);
+ }
+
+ /**
+ * constructor with session variables.
+ */
+ public AliasUdf(String name, List<DataType> argTypes, boolean
hasVarArguments, Expression unboundFunction,
+ List<String> parameters, Map<String, String> sessionVariables,
Expression... arguments) {
super(name, arguments);
this.argTypes = argTypes;
+ this.hasVarArguments = hasVarArguments;
this.unboundFunction = unboundFunction;
this.parameters = parameters;
this.sessionVariables = sessionVariables;
@@ -62,7 +72,7 @@ public class AliasUdf extends ScalarFunction implements
ExplicitlyCastableSignat
@Override
public List<FunctionSignature> getSignatures() {
- return ImmutableList.of(FunctionSignature.of(NullType.INSTANCE,
argTypes));
+ return ImmutableList.of(FunctionSignature.of(NullType.INSTANCE,
hasVarArguments, argTypes));
}
public List<String> getParameters() {
@@ -101,6 +111,7 @@ public class AliasUdf extends ScalarFunction implements
ExplicitlyCastableSignat
AliasUdf aliasUdf = new AliasUdf(
function.functionName(),
Arrays.stream(function.getArgs()).map(DataType::fromCatalogType).collect(Collectors.toList()),
+ function.hasVarArgs(),
parsedFunction,
function.getParameters(),
sessionVariables);
@@ -116,7 +127,7 @@ public class AliasUdf extends ScalarFunction implements
ExplicitlyCastableSignat
@Override
public Expression withChildren(List<Expression> children) {
- return new AliasUdf(getName(), argTypes, unboundFunction, parameters,
sessionVariables,
+ return new AliasUdf(getName(), argTypes, hasVarArguments,
unboundFunction, parameters, sessionVariables,
children.toArray(new Expression[0]));
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfBuilder.java
index 2c57cfad1be..6337908bc7b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfBuilder.java
@@ -30,4 +30,8 @@ public abstract class UdfBuilder extends FunctionBuilder {
public abstract List<DataType> getArgTypes();
public abstract List<FunctionSignature> getSignatures();
+
+ public boolean hasVarArguments() {
+ return getSignatures().get(0).hasVarArgs;
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java
index 0cbfb30d59f..66a4c1f428c 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/CreateFunctionCommand.java
@@ -225,6 +225,7 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
public void run(ConnectContext ctx, StmtExecutor executor) throws
Exception {
analyze(ctx);
if (SetType.GLOBAL.equals(setType)) {
+ // TODO: Register global table functions as normal/_outer pairs
when global UDTFs are supported.
Env.getCurrentEnv().getGlobalFunctionMgr().addFunction(function,
ifNotExists);
} else {
String dbName = functionName.getDb();
@@ -233,15 +234,10 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
functionName.setDb(dbName);
}
Database db =
Env.getCurrentInternalCatalog().getDbOrDdlException(dbName);
- db.addFunction(function, ifNotExists);
if (function.isUDTFunction()) {
- // all of the table function in doris will have two function
- // one is the noraml, and another is outer, the different of
them is deal with
- // empty: whether need to insert NULL result value
- Function outerFunction = function.clone();
- FunctionName name = outerFunction.getFunctionName();
- name.setFn(name.getFunction() + "_outer");
- db.addFunction(outerFunction, ifNotExists);
+ db.addTableFunction(function, ifNotExists);
+ } else {
+ db.addFunction(function, ifNotExists);
}
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/CreateFunctionInfo.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/CreateFunctionInfo.java
new file mode 100644
index 00000000000..d3034868403
--- /dev/null
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/CreateFunctionInfo.java
@@ -0,0 +1,57 @@
+// 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.persist;
+
+import org.apache.doris.catalog.Function;
+import org.apache.doris.common.io.Writable;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.List;
+
+public class CreateFunctionInfo implements Writable {
+ private final List<Function> functions;
+
+ public CreateFunctionInfo(List<Function> functions) {
+ this.functions = ImmutableList.copyOf(functions);
+ }
+
+ public List<Function> getFunctions() {
+ return functions;
+ }
+
+ @Override
+ public void write(DataOutput out) throws IOException {
+ out.writeInt(functions.size());
+ for (Function function : functions) {
+ function.write(out);
+ }
+ }
+
+ public static CreateFunctionInfo read(DataInput in) throws IOException {
+ ImmutableList.Builder<Function> builder = ImmutableList.builder();
+ int functionSize = in.readInt();
+ for (int i = 0; i < functionSize; i++) {
+ builder.add(Function.read(in));
+ }
+ return new CreateFunctionInfo(builder.build());
+ }
+}
diff --git a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
index 378b9de46b9..baf2615bdbd 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/EditLog.java
@@ -831,6 +831,11 @@ public class EditLog {
Env.getCurrentEnv().replayCreateFunction(function);
break;
}
+ case OperationType.OP_ADD_FUNCTIONS: {
+ final CreateFunctionInfo info = (CreateFunctionInfo)
journal.getData();
+ Env.getCurrentEnv().replayCreateFunctions(info);
+ break;
+ }
case OperationType.OP_DROP_FUNCTION: {
FunctionSearchDesc function = (FunctionSearchDesc)
journal.getData();
Env.getCurrentEnv().replayDropFunction(function);
@@ -2135,6 +2140,10 @@ public class EditLog {
logEdit(OperationType.OP_ADD_FUNCTION, function);
}
+ public void logAddFunctions(List<Function> functions) {
+ logEdit(OperationType.OP_ADD_FUNCTIONS, new
CreateFunctionInfo(functions));
+ }
+
public void logAddGlobalFunction(Function function) {
logEdit(OperationType.OP_ADD_GLOBAL_FUNCTION, function);
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
index d4e57a780ed..8161a7a20e9 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/persist/OperationType.java
@@ -215,6 +215,7 @@ public class OperationType {
public static final short OP_DROP_FUNCTION = 131;
public static final short OP_ADD_GLOBAL_FUNCTION = 132;
public static final short OP_DROP_GLOBAL_FUNCTION = 133;
+ public static final short OP_ADD_FUNCTIONS = 134;
// modify database/table/tablet/replica meta
public static final short OP_SET_REPLICA_VERSION = 141;
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java
index 72d29a16f2b..4a83cc6c33d 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/CreateFunctionTest.java
@@ -20,14 +20,22 @@ package org.apache.doris.catalog;
import org.apache.doris.analysis.Expr;
import org.apache.doris.analysis.FunctionCallExpr;
import org.apache.doris.analysis.StringLiteral;
+import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.FeConstants;
import org.apache.doris.common.jmockit.Deencapsulation;
+import org.apache.doris.common.util.URI;
+import org.apache.doris.journal.JournalEntity;
import org.apache.doris.nereids.StatementContext;
import org.apache.doris.nereids.parser.NereidsParser;
+import org.apache.doris.nereids.trees.expressions.functions.FunctionBuilder;
+import org.apache.doris.nereids.trees.expressions.functions.udf.UdfBuilder;
import org.apache.doris.nereids.trees.plans.commands.CreateDatabaseCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateFunctionCommand;
import org.apache.doris.nereids.trees.plans.commands.CreateTableCommand;
import org.apache.doris.nereids.trees.plans.logical.LogicalPlan;
+import org.apache.doris.persist.CreateFunctionInfo;
+import org.apache.doris.persist.EditLog;
+import org.apache.doris.persist.OperationType;
import org.apache.doris.planner.PlanFragment;
import org.apache.doris.planner.Planner;
import org.apache.doris.planner.UnionNode;
@@ -37,13 +45,21 @@ import org.apache.doris.qe.StmtExecutor;
import org.apache.doris.utframe.DorisAssert;
import org.apache.doris.utframe.UtFrameUtils;
+import com.google.common.collect.ImmutableList;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
import java.io.File;
import java.util.List;
+import java.util.Map;
import java.util.UUID;
/*
@@ -170,6 +186,271 @@ public class CreateFunctionTest {
"ARRAY unsupported sub-type: bitmap");
}
+ @Test
+ public void testCreateFunctionRollbackOnlyFailedOverload() throws
Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database rollback_function_db;");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("rollback_function_db");
+ Assert.assertNotNull(db);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddFunction(Mockito.any(Function.class));
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ Function existingFunction = createJavaUdf("rollback_function_db",
"rollback_fn", Type.INT);
+ db.addFunction(existingFunction, false);
+ Assert.assertSame(existingFunction,
db.getFunction(searchDesc(existingFunction)));
+
+ Mockito.clearInvocations(spyEditLog);
+ Function failedFunction = createJavaUdf("rollback_function_db",
"rollback_fn", Type.BIGINT);
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ "rollback_function_db", failedFunction)).thenThrow(new
RuntimeException("translate failed"));
+
+ RuntimeException exception =
Assert.assertThrows(RuntimeException.class,
+ () -> db.addFunction(failedFunction, false));
+ Assert.assertEquals("translate failed", exception.getMessage());
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunction(Mockito.any(Function.class));
+ Assert.assertSame(existingFunction,
db.getFunction(searchDesc(existingFunction)));
+ Assert.assertThrows(AnalysisException.class, () ->
db.getFunction(searchDesc(failedFunction)));
+ mockedFunctionUtil.verify(() -> FunctionUtil.removeFunctionImpl(
+ Mockito.eq(failedFunction), Mockito.eq(true),
Mockito.any()));
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ }
+ }
+
+ @Test
+ public void testCreateTableFunctionRollbackWhenOuterFunctionFails() throws
Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database rollback_table_function_db;");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("rollback_table_function_db");
+ Assert.assertNotNull(db);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddFunction(Mockito.any(Function.class));
+
Mockito.doNothing().when(spyEditLog).logAddFunctions(Mockito.anyList());
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ Function tableFunction =
createJavaUdtf("rollback_table_function_db", "rollback_table_fn", Type.INT);
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ Mockito.eq("rollback_table_function_db"),
Mockito.any(Function.class)))
+ .thenAnswer(invocation -> {
+ Function function = invocation.getArgument(1);
+ if
("rollback_table_fn_outer".equals(function.functionName())) {
+ throw new RuntimeException("outer translate
failed");
+ }
+ return true;
+ });
+
+ RuntimeException exception =
Assert.assertThrows(RuntimeException.class,
+ () -> db.addTableFunction(tableFunction, false));
+ Assert.assertEquals("outer translate failed",
exception.getMessage());
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunction(Mockito.any(Function.class));
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunctions(Mockito.anyList());
+ Assert.assertThrows(AnalysisException.class, () ->
db.getFunction(searchDesc(tableFunction)));
+ Assert.assertThrows(AnalysisException.class,
+ () ->
db.getFunction(searchDesc("rollback_table_function_db",
"rollback_table_fn_outer",
+ Type.INT)));
+ mockedFunctionUtil.verify(() -> FunctionUtil.removeFunctionImpl(
+ Mockito.argThat(function ->
"rollback_table_fn".equals(function.functionName())),
+ Mockito.eq(true), Mockito.any()));
+ mockedFunctionUtil.verify(() -> FunctionUtil.removeFunctionImpl(
+ Mockito.argThat(function ->
"rollback_table_fn_outer".equals(function.functionName())),
+ Mockito.eq(true), Mockito.any()));
+ mockedFunctionUtil.verify(() ->
FunctionUtil.dropFromNereids(Mockito.eq("rollback_table_function_db"),
+ Mockito.argThat(function ->
"rollback_table_fn".equals(function.getName().getFunction()))));
+ mockedFunctionUtil.verify(() ->
FunctionUtil.dropFromNereids(Mockito.eq("rollback_table_function_db"),
+ Mockito.argThat(function ->
"rollback_table_fn_outer".equals(function.getName().getFunction()))));
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ }
+ }
+
+ @Test
+ public void testCreateTableFunctionRollbackKeepsVariadicNereidsOverload()
throws Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database
rollback_table_function_vararg_db;");
+ ctx.setDatabase("rollback_table_function_vararg_db");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("rollback_table_function_vararg_db");
+ Assert.assertNotNull(db);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddFunction(Mockito.any(Function.class));
+
Mockito.doNothing().when(spyEditLog).logAddFunctions(Mockito.anyList());
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ Mockito.eq("rollback_table_function_vararg_db"),
+ Mockito.argThat(function ->
"rollback_vararg_table_fn_outer".equals(function.functionName()))))
+ .thenThrow(new RuntimeException("outer translate failed"));
+
+ Function variadicFunction = createJavaUdtf(
+ "rollback_table_function_vararg_db",
"rollback_vararg_table_fn", Type.INT);
+ variadicFunction.setHasVarArgs(true);
+ db.addFunction(variadicFunction, false);
+ Assert.assertSame(variadicFunction,
db.getFunction(searchDesc(variadicFunction)));
+
assertSingleVariadicUdfBuilder("rollback_table_function_vararg_db",
"rollback_vararg_table_fn");
+
+ Mockito.clearInvocations(spyEditLog);
+ Function tableFunction = createJavaUdtf(
+ "rollback_table_function_vararg_db",
"rollback_vararg_table_fn", Type.INT);
+ RuntimeException exception =
Assert.assertThrows(RuntimeException.class,
+ () -> db.addTableFunction(tableFunction, false));
+ Assert.assertEquals("outer translate failed",
exception.getMessage());
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunction(Mockito.any(Function.class));
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunctions(Mockito.anyList());
+ Assert.assertThrows(AnalysisException.class, () ->
db.getFunction(searchDesc(tableFunction)));
+ Assert.assertSame(variadicFunction,
db.getFunction(searchDesc(variadicFunction)));
+
assertSingleVariadicUdfBuilder("rollback_table_function_vararg_db",
"rollback_vararg_table_fn");
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ }
+ }
+
+ @Test
+ public void testCreateTableFunctionIfNotExistsSkipsExistingPair() throws
Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database
existing_table_function_pair_db;");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("existing_table_function_pair_db");
+ Assert.assertNotNull(db);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddFunction(Mockito.any(Function.class));
+
Mockito.doNothing().when(spyEditLog).logAddFunctions(Mockito.anyList());
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ Mockito.eq("existing_table_function_pair_db"),
Mockito.any(Function.class)))
+ .thenReturn(true);
+ Function existingFunction = createJavaUdtf(
+ "existing_table_function_pair_db",
"existing_table_pair_fn", Type.INT);
+ Function existingOuterFunction = createJavaUdtf(
+ "existing_table_function_pair_db",
"existing_table_pair_fn_outer", Type.INT);
+ db.addFunction(existingFunction, false);
+ db.addFunction(existingOuterFunction, false);
+ Assert.assertSame(existingFunction,
db.getFunction(searchDesc(existingFunction)));
+ Assert.assertSame(existingOuterFunction,
db.getFunction(searchDesc(existingOuterFunction)));
+
+ Mockito.clearInvocations(spyEditLog);
+ Function tableFunction = createJavaUdtf(
+ "existing_table_function_pair_db",
"existing_table_pair_fn", Type.INT);
+ db.addTableFunction(tableFunction, true);
+
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunction(Mockito.any(Function.class));
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunctions(Mockito.anyList());
+ Assert.assertSame(existingFunction,
db.getFunction(searchDesc(existingFunction)));
+ Assert.assertSame(existingOuterFunction,
db.getFunction(searchDesc(existingOuterFunction)));
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ }
+ }
+
+ @Test
+ public void testCreateTableFunctionLogsPairAtomically() throws Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database atomic_table_function_db;");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("atomic_table_function_db");
+ Assert.assertNotNull(db);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddFunction(Mockito.any(Function.class));
+
Mockito.doNothing().when(spyEditLog).logAddFunctions(Mockito.anyList());
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ Mockito.eq("atomic_table_function_db"),
Mockito.any(Function.class)))
+ .thenReturn(true);
+ Function tableFunction =
createJavaUdtf("atomic_table_function_db", "atomic_table_fn", Type.INT);
+
+ db.addTableFunction(tableFunction, false);
+
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddFunction(Mockito.any(Function.class));
+
Mockito.verify(spyEditLog).logAddFunctions(Mockito.argThat(functions ->
+ functions.size() == 2
+ &&
"atomic_table_fn".equals(functions.get(0).functionName())
+ &&
"atomic_table_fn_outer".equals(functions.get(1).functionName())));
+ Assert.assertSame(tableFunction,
db.getFunction(searchDesc(tableFunction)));
+
Assert.assertNotNull(db.getFunction(searchDesc("atomic_table_function_db",
"atomic_table_fn_outer",
+ Type.INT)));
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ }
+ }
+
+ @Test
+ public void testCreateTableFunctionJournalReplayRestoresPair() throws
Exception {
+ ConnectContext ctx = UtFrameUtils.createDefaultCtx();
+ createDatabase(ctx, "create database replay_table_function_db;");
+ Database db =
Env.getCurrentInternalCatalog().getDbNullable("replay_table_function_db");
+ Assert.assertNotNull(db);
+
+ Function tableFunction = createJavaUdtf("replay_table_function_db",
"replay_table_fn", Type.INT);
+ Function outerFunction = createOuterTableFunction(tableFunction);
+ JournalEntity journalEntity = new JournalEntity();
+ journalEntity.setOpCode(OperationType.OP_ADD_FUNCTIONS);
+ journalEntity.setData(new
CreateFunctionInfo(ImmutableList.of(tableFunction, outerFunction)));
+
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ journalEntity.write(new DataOutputStream(outputStream));
+ JournalEntity replayJournalEntity = new JournalEntity();
+ replayJournalEntity.readFields(new DataInputStream(new
ByteArrayInputStream(outputStream.toByteArray())));
+
+ Assert.assertEquals(OperationType.OP_ADD_FUNCTIONS,
replayJournalEntity.getOpCode());
+ Assert.assertTrue(replayJournalEntity.getData() instanceof
CreateFunctionInfo);
+ Assert.assertEquals(2, ((CreateFunctionInfo)
replayJournalEntity.getData()).getFunctions().size());
+ EditLog.loadJournal(Env.getCurrentEnv(), 0L, replayJournalEntity);
+ Assert.assertNotNull(db.getFunction(searchDesc(tableFunction)));
+ Assert.assertNotNull(db.getFunction(searchDesc(outerFunction)));
+ }
+
+ @Test
+ public void testCreateGlobalFunctionRollbackOnlyFailedOverload() throws
Exception {
+ GlobalFunctionMgr globalFunctionMgr =
Env.getCurrentEnv().getGlobalFunctionMgr();
+ FunctionSearchDesc existingFunctionDesc = searchDesc(null,
"rollback_global_fn", Type.INT);
+ FunctionSearchDesc failedFunctionDesc = searchDesc(null,
"rollback_global_fn", Type.BIGINT);
+ globalFunctionMgr.dropFunction(existingFunctionDesc, true);
+ globalFunctionMgr.dropFunction(failedFunctionDesc, true);
+
+ EditLog editLog = Env.getCurrentEnv().getEditLog();
+ EditLog spyEditLog = Mockito.spy(editLog);
+
Mockito.doNothing().when(spyEditLog).logAddGlobalFunction(Mockito.any(Function.class));
+ Env.getCurrentEnv().setEditLog(spyEditLog);
+ try (MockedStatic<FunctionUtil> mockedFunctionUtil =
Mockito.mockStatic(FunctionUtil.class,
+ Mockito.CALLS_REAL_METHODS)) {
+ Function existingFunction = createJavaUdf(null,
"rollback_global_fn", Type.INT);
+ globalFunctionMgr.addFunction(existingFunction, false);
+ Assert.assertSame(existingFunction,
globalFunctionMgr.getFunction(existingFunctionDesc));
+
+ Mockito.clearInvocations(spyEditLog);
+ Function failedFunction = createJavaUdf(null,
"rollback_global_fn", Type.BIGINT);
+ mockedFunctionUtil.when(() ->
FunctionUtil.translateToNereidsThrows(
+ null, failedFunction)).thenThrow(new
RuntimeException("global translate failed"));
+
+ RuntimeException exception =
Assert.assertThrows(RuntimeException.class,
+ () -> globalFunctionMgr.addFunction(failedFunction,
false));
+ Assert.assertEquals("global translate failed",
exception.getMessage());
+ Mockito.verify(spyEditLog,
Mockito.never()).logAddGlobalFunction(Mockito.any(Function.class));
+ Assert.assertSame(existingFunction,
globalFunctionMgr.getFunction(existingFunctionDesc));
+ Assert.assertThrows(AnalysisException.class, () ->
globalFunctionMgr.getFunction(failedFunctionDesc));
+ mockedFunctionUtil.verify(() -> FunctionUtil.removeFunctionImpl(
+ Mockito.eq(failedFunction), Mockito.eq(true),
Mockito.any()));
+ } finally {
+ Env.getCurrentEnv().setEditLog(editLog);
+ globalFunctionMgr.dropFunction(existingFunctionDesc, true);
+ globalFunctionMgr.dropFunction(failedFunctionDesc, true);
+ }
+ }
+
@Test
public void testCreateGlobalFunction() throws Exception {
ConnectContext ctx = UtFrameUtils.createDefaultCtx();
@@ -273,4 +554,40 @@ public class CreateFunctionTest {
}
throw new AssertionError("function not found: " + functionName);
}
+
+ private Function createJavaUdf(String dbName, String functionName, Type...
argTypes) throws AnalysisException {
+ return ScalarFunction.createUdf(Function.BinaryType.JAVA_UDF, new
FunctionName(dbName, functionName), argTypes,
+ Type.INT, false, URI.create("file:///tmp/" + functionName +
".jar"), "evaluate", null, null);
+ }
+
+ private Function createJavaUdtf(String dbName, String functionName,
Type... argTypes) throws AnalysisException {
+ Function function = createJavaUdf(dbName, functionName, argTypes);
+ function.setUDTFunction(true);
+ return function;
+ }
+
+ private void assertSingleVariadicUdfBuilder(String dbName, String
functionName) {
+ Map<String, List<FunctionBuilder>> builders =
Env.getCurrentEnv().getFunctionRegistry()
+ .getName2UdfBuilders().get(dbName);
+ Assert.assertNotNull(builders);
+ List<FunctionBuilder> functionBuilders = builders.get(functionName);
+ Assert.assertNotNull(functionBuilders);
+ Assert.assertEquals(1, functionBuilders.size());
+ Assert.assertTrue(((UdfBuilder)
functionBuilders.get(0)).hasVarArguments());
+ }
+
+ private Function createOuterTableFunction(Function function) {
+ Function outerFunction = function.clone();
+ FunctionName name = outerFunction.getFunctionName();
+ name.setFn(name.getFunction() + "_outer");
+ return outerFunction;
+ }
+
+ private FunctionSearchDesc searchDesc(Function function) {
+ return new FunctionSearchDesc(function.getFunctionName(),
function.getArgs(), function.hasVarArgs());
+ }
+
+ private FunctionSearchDesc searchDesc(String dbName, String functionName,
Type... argTypes) {
+ return new FunctionSearchDesc(new FunctionName(dbName, functionName),
argTypes, false);
+ }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/UdfTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/UdfTest.java
index 73a44299f73..d262beded07 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/UdfTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/UdfTest.java
@@ -223,7 +223,7 @@ public class UdfTest extends TestWithFeService implements
PlanPatternMatchSuppor
public void testReadFromStream() throws Exception {
createFunction("create global alias function f8(int) with parameter(n)
as hours_add(now(3), n)");
Env.getCurrentEnv().getFunctionRegistry().dropUdf(null, "f8",
- ImmutableList.of(IntegerType.INSTANCE));
+ ImmutableList.of(IntegerType.INSTANCE), false);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Env.getCurrentEnv().getGlobalFunctionMgr().write(new
DataOutputStream(outputStream));
diff --git
a/regression-test/suites/fault_injection_p0/test_create_function_rollback.groovy
b/regression-test/suites/fault_injection_p0/test_create_function_rollback.groovy
new file mode 100644
index 00000000000..3dc6f65becf
--- /dev/null
+++
b/regression-test/suites/fault_injection_p0/test_create_function_rollback.groovy
@@ -0,0 +1,52 @@
+// 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.apache.doris.regression.suite.ClusterOptions
+
+suite("test_create_function_rollback", "docker") {
+ def options = new ClusterOptions()
+ options.feConfigs += [
+ "enable_debug_points=true"
+ ]
+ options.cloudMode = false
+
+ docker(options) {
+ sql """DROP FUNCTION IF EXISTS create_fn_rollback_fn(INT)"""
+ sql """DROP FUNCTION IF EXISTS create_fn_rollback_fn(BIGINT)"""
+ sql """CREATE ALIAS FUNCTION create_fn_rollback_fn(INT) WITH
PARAMETER(x) AS add(x, 1)"""
+
+ try {
+ GetDebugPoint().enableDebugPointForAllFEs(
+ "FunctionUtil.translateToNereidsThrows.exception",
[execute: 1])
+ test {
+ sql """CREATE ALIAS FUNCTION create_fn_rollback_fn(BIGINT)
WITH PARAMETER(x) AS add(x, 1)"""
+ exception "debug point
FunctionUtil.translateToNereidsThrows.exception"
+ }
+ } finally {
+
GetDebugPoint().disableDebugPointForAllFEs("FunctionUtil.translateToNereidsThrows.exception")
+ }
+
+ test {
+ sql """CREATE ALIAS FUNCTION create_fn_rollback_fn(INT) WITH
PARAMETER(x) AS add(x, 1)"""
+ exception "function already exists"
+ }
+ test {
+ sql """DROP FUNCTION create_fn_rollback_fn(BIGINT)"""
+ exception "function does not exist"
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]