This is an automated email from the ASF dual-hosted git repository.
zclllyybb 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 c2432387467 [Enhancement](udf) support volatility for udaf && udtf
(#63611)
c2432387467 is described below
commit c24323874675762d1b42b8487b3dcd9b5ed81061
Author: linrrarity <[email protected]>
AuthorDate: Mon Jun 1 14:51:34 2026 +0800
[Enhancement](udf) support volatility for udaf && udtf (#63611)
Related PR: https://github.com/apache/doris/pull/62698
Problem Summary:
Add volatility metadata support for UDAF and UDTF definitions, so
user-defined aggregate and table functions can preserve and expose their
volatility semantics consistently with UDFs.
---
.../doris/catalog/FunctionToSqlConverter.java | 10 ++--
.../nereids/trees/expressions/functions/Udf.java | 19 ++++++-
.../trees/expressions/functions/udf/JavaUdaf.java | 58 ++++++++++++++++++++-
.../expressions/functions/udf/JavaUdafBuilder.java | 2 +-
.../trees/expressions/functions/udf/JavaUdf.java | 17 +++----
.../trees/expressions/functions/udf/JavaUdtf.java | 58 ++++++++++++++++++++-
.../expressions/functions/udf/JavaUdtfBuilder.java | 2 +-
.../expressions/functions/udf/PythonUdaf.java | 59 +++++++++++++++++++++-
.../functions/udf/PythonUdafBuilder.java | 2 +-
.../trees/expressions/functions/udf/PythonUdf.java | 17 +++----
.../expressions/functions/udf/PythonUdtf.java | 58 ++++++++++++++++++++-
.../functions/udf/PythonUdtfBuilder.java | 2 +-
.../plans/commands/CreateFunctionCommand.java | 21 ++++----
.../trees/plans/commands/ShowFunctionsCommand.java | 9 ++--
.../apache/doris/catalog/CreateFunctionTest.java | 11 ++++
.../doris/catalog/FunctionToSqlConverterTest.java | 12 +++--
.../functions/udf/UdfVolatilityTest.java | 52 +++++++++++++++++++
.../plans/commands/ShowFunctionsCommandTest.java | 33 +++++++++---
18 files changed, 379 insertions(+), 63 deletions(-)
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionToSqlConverter.java
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionToSqlConverter.java
index 49db8ed7e24..590a6582594 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionToSqlConverter.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/catalog/FunctionToSqlConverter.java
@@ -78,18 +78,14 @@ public class FunctionToSqlConverter {
.append("\"" + (fn.getLocation() == null ? "" :
fn.getLocation().toString()) + "\"");
boolean isReturnNull = fn.getNullableMode() ==
NullableMode.ALWAYS_NULLABLE;
sb.append(",\n \"ALWAYS_NULLABLE\"=").append("\"" + isReturnNull
+ "\"");
- if (!fn.isUDTFunction()) {
- sb.append(",\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\"");
- }
+ sb.append(",\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\"");
} else if (fn.getBinaryType() == Function.BinaryType.PYTHON_UDF) {
appendFileIfPresent(sb, fn, true);
boolean isReturnNull = fn.getNullableMode() ==
NullableMode.ALWAYS_NULLABLE;
sb.append(",\n \"ALWAYS_NULLABLE\"=").append("\"" + isReturnNull
+ "\"");
sb.append(",\n \"RUNTIME_VERSION\"=").append("\"" +
Strings.nullToEmpty(fn.getRuntimeVersion()) + "\"");
appendExpirationTimeIfNeeded(sb, fn);
- if (!fn.isUDTFunction()) {
- sb.append(",\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\"");
- }
+ sb.append(",\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\"");
} else {
sb.append(",\n \"OBJECT_FILE\"=")
.append("\"" + (fn.getLocation() == null ? "" :
fn.getLocation().toString()) + "\"");
@@ -167,6 +163,7 @@ public class FunctionToSqlConverter {
.append("\"" + (fn.getLocation() == null ? "" :
fn.getLocation().toString()) + "\",");
boolean isReturnNull = fn.getNullableMode() ==
NullableMode.ALWAYS_NULLABLE;
sb.append("\n \"ALWAYS_NULLABLE\"=").append("\"" + isReturnNull +
"\",");
+ sb.append("\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\",");
} else if (fn.getBinaryType() == Function.BinaryType.PYTHON_UDF) {
appendFileIfPresent(sb, fn, false);
if (fn.getLocation() != null) {
@@ -179,6 +176,7 @@ public class FunctionToSqlConverter {
if (fn.getExpirationTime() != DEFAULT_EXPIRATION_TIME) {
sb.append("\n \"EXPIRATION_TIME\"=").append("\"" +
fn.getExpirationTime() + "\",");
}
+ sb.append("\n \"VOLATILITY\"=").append("\"" +
fn.getVolatility().toSql() + "\",");
} else {
sb.append("\n \"OBJECT_FILE\"=")
.append("\"" + (fn.getLocation() == null ? "" :
fn.getLocation().toString()) + "\",");
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 7b4229058c1..dc66c259d99 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
@@ -19,15 +19,18 @@ package
org.apache.doris.nereids.trees.expressions.functions;
import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
+import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
+import org.apache.doris.nereids.trees.expressions.VolatileExpression;
+import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import java.util.List;
/**
* interface for udf
*/
-public interface Udf extends ComputeNullable {
+public interface Udf extends ComputeNullable, VolatileExpression {
@Override
default boolean nullable() {
NullableMode mode = getNullableMode();
@@ -45,8 +48,22 @@ public interface Udf extends ComputeNullable {
NullableMode getNullableMode();
+ FunctionVolatility getVolatility();
+
List<Expression> children();
+ @Override
+ default boolean isDeterministic() {
+ return getVolatility() == FunctionVolatility.IMMUTABLE;
+ }
+
+ Udf withFreshVolatileIdentity();
+
+ static VolatileIdentity createVolatileIdentity(FunctionVolatility
volatility) {
+ return volatility == FunctionVolatility.VOLATILE
+ ? VolatileIdentity.newVolatileIdentity() :
VolatileIdentity.NON_VOLATILE;
+ }
+
@Override
default boolean foldable() {
// Udf should not be folded in FE.
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdaf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdaf.java
index c3eebfc283f..27fc9c98f10 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdaf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdaf.java
@@ -22,11 +22,13 @@ import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.FunctionName;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
import
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
@@ -50,6 +52,8 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
private final FunctionSignature signature;
private final DataType intermediateType;
private final NullableMode nullableMode;
+ private final FunctionVolatility volatility;
+ private final VolatileIdentity volatileIdentity;
private final String objectFile;
private final String symbol;
private final String initFn;
@@ -69,7 +73,7 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
public JavaUdaf(String name, long functionId, String dbName,
Function.BinaryType binaryType,
FunctionSignature signature,
DataType intermediateType, NullableMode nullableMode,
- String objectFile, String symbol,
+ FunctionVolatility volatility, VolatileIdentity volatileIdentity,
String objectFile, String symbol,
String initFn, String updateFn, String mergeFn,
String serializeFn, String finalizeFn, String getValueFn, String
removeFn,
boolean isDistinct, String checkSum, boolean isStaticLoad, long
expirationTime, Expression... args) {
@@ -80,6 +84,8 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
this.signature = signature;
this.intermediateType = intermediateType == null ?
signature.returnType : intermediateType;
this.nullableMode = nullableMode;
+ this.volatility = volatility;
+ this.volatileIdentity = volatileIdentity;
this.objectFile = objectFile;
this.symbol = symbol;
this.initFn = initFn;
@@ -121,8 +127,48 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
public JavaUdaf withDistinctAndChildren(boolean isDistinct,
List<Expression> children) {
Preconditions.checkArgument(children.size() == this.children.size());
return new JavaUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
+ volatility, volatileIdentity, objectFile, symbol, initFn,
updateFn, mergeFn, serializeFn, finalizeFn,
+ getValueFn, removeFn, isDistinct, checkSum, isStaticLoad,
expirationTime,
+ children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public VolatileIdentity getVolatileIdentity() {
+ return volatileIdentity;
+ }
+
+ @Override
+ public JavaUdaf withIgnoreUniqueId(boolean ignoreUniqueId) {
+ Preconditions.checkState(isVolatile(), "Only volatile Java UDAF can
ignore unique id");
+ return new JavaUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
+ volatility,
volatileIdentity.withIgnoreUniqueId(ignoreUniqueId),
objectFile, symbol, initFn, updateFn, mergeFn, serializeFn,
finalizeFn, getValueFn, removeFn,
- isDistinct, checkSum, isStaticLoad, expirationTime,
children.toArray(new Expression[0]));
+ distinct, checkSum, isStaticLoad, expirationTime,
children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public JavaUdaf withFreshVolatileIdentity() {
+ if (volatility != FunctionVolatility.VOLATILE) {
+ return this;
+ }
+ return new JavaUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
+ volatility, VolatileIdentity.newVolatileIdentity(),
+ objectFile, symbol, initFn, updateFn, mergeFn, serializeFn,
finalizeFn, getValueFn, removeFn,
+ distinct, checkSum, isStaticLoad, expirationTime,
children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof JavaUdaf)) {
+ return false;
+ }
+ JavaUdaf other = (JavaUdaf) o;
+ return volatileIdentity.equalsByIdentity(other.volatileIdentity,
super.equals(o));
+ }
+
+ @Override
+ public int computeHashCode() {
+ return volatileIdentity.hashCodeByIdentity(super.computeHashCode());
}
/**
@@ -152,6 +198,8 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
JavaUdaf udaf = new JavaUdaf(fnName, aggregate.getId(), dbName,
aggregate.getBinaryType(), sig,
intermediateType,
aggregate.getNullableMode(),
+ aggregate.getVolatility(),
+ Udf.createVolatileIdentity(aggregate.getVolatility()),
aggregate.getLocation() == null ? null :
aggregate.getLocation().getLocation(),
aggregate.getSymbolName(),
aggregate.getInitFnSymbol(),
@@ -201,9 +249,15 @@ public class JavaUdaf extends AggregateFunction implements
ExplicitlyCastableSig
expr.setId(functionId);
expr.setStaticLoad(isStaticLoad);
expr.setExpirationTime(expirationTime);
+ expr.setVolatility(volatility);
return expr;
} catch (Exception e) {
throw new AnalysisException(e.getMessage(), e.getCause());
}
}
+
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java
index c88f24f4eec..4822ab6ef1a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdafBuilder.java
@@ -79,7 +79,7 @@ public class JavaUdafBuilder extends UdfBuilder {
@Override
public Pair<JavaUdaf, JavaUdaf> build(String name, List<?> arguments) {
- return Pair.ofSame((JavaUdaf) udaf.withChildren(
+ return Pair.ofSame((JavaUdaf)
udaf.withFreshVolatileIdentity().withChildren(
arguments.stream()
.map(Expression.class::cast)
.collect(Collectors.toList()))
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdf.java
index 3e53200fa9c..76e98ed39e5 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdf.java
@@ -28,7 +28,6 @@ import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
-import org.apache.doris.nereids.trees.expressions.VolatileExpression;
import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
@@ -46,7 +45,7 @@ import java.util.stream.Collectors;
/**
* Java UDF for Nereids
*/
-public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignature, Udf, VolatileExpression {
+public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignature, Udf {
private final String dbName;
private final long functionId;
private final Function.BinaryType binaryType;
@@ -134,6 +133,7 @@ public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignatu
}
/** Return a copy with a new per-call identity when this UDF is VOLATILE.
*/
+ @Override
public JavaUdf withFreshVolatileIdentity() {
if (volatility != FunctionVolatility.VOLATILE) {
return this;
@@ -144,11 +144,6 @@ public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignatu
children.toArray(new Expression[0]));
}
- @Override
- public boolean isDeterministic() {
- return volatility == FunctionVolatility.IMMUTABLE;
- }
-
@Override
public boolean equals(Object o) {
if (!(o instanceof JavaUdf)) {
@@ -183,7 +178,7 @@ public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignatu
.toArray(SlotReference[]::new);
JavaUdf udf = new JavaUdf(fnName, scalar.getId(), dbName,
scalar.getBinaryType(), sig,
- scalar.getNullableMode(), scalar.getVolatility(),
createVolatileIdentity(scalar.getVolatility()),
+ scalar.getNullableMode(), scalar.getVolatility(),
Udf.createVolatileIdentity(scalar.getVolatility()),
scalar.getLocation() == null ? null :
scalar.getLocation().getLocation(),
scalar.getSymbolName(),
scalar.getPrepareFnSymbol(),
@@ -226,8 +221,8 @@ public class JavaUdf extends ScalarFunction implements
ExplicitlyCastableSignatu
}
}
- private static VolatileIdentity createVolatileIdentity(FunctionVolatility
volatility) {
- return volatility == FunctionVolatility.VOLATILE
- ? VolatileIdentity.newVolatileIdentity() :
VolatileIdentity.NON_VOLATILE;
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtf.java
index 2e04dec1d68..401ab968392 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtf.java
@@ -22,11 +22,13 @@ import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.FunctionName;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
import
org.apache.doris.nereids.trees.expressions.functions.generator.TableGeneratingFunction;
@@ -49,6 +51,8 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
private final Function.BinaryType binaryType;
private final FunctionSignature signature;
private final NullableMode nullableMode;
+ private final FunctionVolatility volatility;
+ private final VolatileIdentity volatileIdentity;
private final String objectFile;
private final String symbol;
private final String prepareFn;
@@ -62,7 +66,9 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
*/
public JavaUdtf(String name, long functionId, String dbName,
Function.BinaryType binaryType,
FunctionSignature signature,
- NullableMode nullableMode, String objectFile, String symbol,
String prepareFn, String closeFn,
+ NullableMode nullableMode, FunctionVolatility volatility,
VolatileIdentity volatileIdentity,
+ String objectFile, String symbol,
+ String prepareFn, String closeFn,
String checkSum, boolean isStaticLoad, long expirationTime,
Expression... args) {
super(name, args);
this.dbName = dbName;
@@ -70,6 +76,8 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
this.binaryType = binaryType;
this.signature = signature;
this.nullableMode = nullableMode;
+ this.volatility = volatility;
+ this.volatileIdentity = volatileIdentity;
this.objectFile = objectFile;
this.symbol = symbol;
this.prepareFn = prepareFn;
@@ -86,10 +94,50 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
public JavaUdtf withChildren(List<Expression> children) {
Preconditions.checkArgument(children.size() == this.children.size());
return new JavaUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility, volatileIdentity, objectFile, symbol, prepareFn,
closeFn, checkSum, isStaticLoad,
+ expirationTime,
+ children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public VolatileIdentity getVolatileIdentity() {
+ return volatileIdentity;
+ }
+
+ @Override
+ public JavaUdtf withIgnoreUniqueId(boolean ignoreUniqueId) {
+ Preconditions.checkState(isVolatile(), "Only volatile Java UDTF can
ignore unique id");
+ return new JavaUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility,
volatileIdentity.withIgnoreUniqueId(ignoreUniqueId),
objectFile, symbol, prepareFn, closeFn, checkSum,
isStaticLoad, expirationTime,
children.toArray(new Expression[0]));
}
+ @Override
+ public JavaUdtf withFreshVolatileIdentity() {
+ if (volatility != FunctionVolatility.VOLATILE) {
+ return this;
+ }
+ return new JavaUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility, VolatileIdentity.newVolatileIdentity(),
+ objectFile, symbol, prepareFn, closeFn, checkSum,
isStaticLoad, expirationTime,
+ children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof JavaUdtf)) {
+ return false;
+ }
+ JavaUdtf other = (JavaUdtf) o;
+ return volatileIdentity.equalsByIdentity(other.volatileIdentity,
super.equals(o));
+ }
+
+ @Override
+ public int computeHashCode() {
+ return volatileIdentity.hashCodeByIdentity(super.computeHashCode());
+ }
+
@Override
public List<FunctionSignature> getSignatures() {
return ImmutableList.of(signature);
@@ -125,6 +173,7 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
expr.setStaticLoad(isStaticLoad);
expr.setExpirationTime(expirationTime);
expr.setUDTFunction(true);
+ expr.setVolatility(volatility);
return expr;
} catch (Exception e) {
throw new AnalysisException(e.getMessage(), e.getCause());
@@ -152,6 +201,8 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
JavaUdtf udf = new JavaUdtf(fnName, scalar.getId(), dbName,
scalar.getBinaryType(), sig,
scalar.getNullableMode(),
+ scalar.getVolatility(),
+ Udf.createVolatileIdentity(scalar.getVolatility()),
scalar.getLocation() == null ? null :
scalar.getLocation().getLocation(),
scalar.getSymbolName(),
scalar.getPrepareFnSymbol(),
@@ -170,6 +221,11 @@ public class JavaUdtf extends TableGeneratingFunction
implements ExplicitlyCasta
return nullableMode;
}
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
+ }
+
@Override
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
return visitor.visitJavaUdtf(this, context);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtfBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtfBuilder.java
index 5d85c0faf86..f00fa22cf5b 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtfBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/JavaUdtfBuilder.java
@@ -88,7 +88,7 @@ public class JavaUdtfBuilder extends UdfBuilder {
for (int i = 0; i < exprs.size(); ++i) {
processedExprs.add(TypeCoercionUtils.castIfNotSameType(exprs.get(i),
argTypes.get(i)));
}
- return Pair.ofSame(udf.withChildren(processedExprs));
+ return
Pair.ofSame(udf.withFreshVolatileIdentity().withChildren(processedExprs));
}
@Override
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdaf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdaf.java
index 456e0f1a6ea..39a27b72834 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdaf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdaf.java
@@ -22,11 +22,13 @@ import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.FunctionName;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
import
org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction;
@@ -50,6 +52,8 @@ public class PythonUdaf extends AggregateFunction implements
ExplicitlyCastableS
private final FunctionSignature signature;
private final DataType intermediateType;
private final NullableMode nullableMode;
+ private final FunctionVolatility volatility;
+ private final VolatileIdentity volatileIdentity;
private final String objectFile;
private final String symbol;
private final String initFn;
@@ -71,6 +75,7 @@ public class PythonUdaf extends AggregateFunction implements
ExplicitlyCastableS
public PythonUdaf(String name, long functionId, String dbName,
Function.BinaryType binaryType,
FunctionSignature signature,
DataType intermediateType, NullableMode nullableMode,
+ FunctionVolatility volatility, VolatileIdentity
volatileIdentity,
String objectFile, String symbol,
String initFn, String updateFn, String mergeFn,
String serializeFn, String finalizeFn, String
getValueFn, String removeFn,
@@ -83,6 +88,8 @@ public class PythonUdaf extends AggregateFunction implements
ExplicitlyCastableS
this.signature = signature;
this.intermediateType = intermediateType == null ?
signature.returnType : intermediateType;
this.nullableMode = nullableMode;
+ this.volatility = volatility;
+ this.volatileIdentity = volatileIdentity;
this.objectFile = objectFile;
this.symbol = symbol;
this.initFn = initFn;
@@ -126,11 +133,53 @@ public class PythonUdaf extends AggregateFunction
implements ExplicitlyCastableS
public PythonUdaf withDistinctAndChildren(boolean isDistinct,
List<Expression> children) {
Preconditions.checkArgument(children.size() == this.children.size());
return new PythonUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
- objectFile, symbol, initFn, updateFn, mergeFn, serializeFn,
finalizeFn, getValueFn, removeFn,
+ volatility, volatileIdentity, objectFile, symbol, initFn,
updateFn, mergeFn, serializeFn, finalizeFn,
+ getValueFn, removeFn,
isDistinct, checkSum, isStaticLoad, expirationTime,
runtimeVersion, functionCode,
children.toArray(new Expression[0]));
}
+ @Override
+ public VolatileIdentity getVolatileIdentity() {
+ return volatileIdentity;
+ }
+
+ @Override
+ public PythonUdaf withIgnoreUniqueId(boolean ignoreUniqueId) {
+ Preconditions.checkState(isVolatile(), "Only volatile Python UDAF can
ignore unique id");
+ return new PythonUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
+ volatility,
volatileIdentity.withIgnoreUniqueId(ignoreUniqueId),
+ objectFile, symbol, initFn, updateFn, mergeFn, serializeFn,
finalizeFn, getValueFn, removeFn,
+ distinct, checkSum, isStaticLoad, expirationTime,
runtimeVersion, functionCode,
+ children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public PythonUdaf withFreshVolatileIdentity() {
+ if (volatility != FunctionVolatility.VOLATILE) {
+ return this;
+ }
+ return new PythonUdaf(getName(), functionId, dbName, binaryType,
signature, intermediateType, nullableMode,
+ volatility, VolatileIdentity.newVolatileIdentity(),
+ objectFile, symbol, initFn, updateFn, mergeFn, serializeFn,
finalizeFn, getValueFn, removeFn,
+ distinct, checkSum, isStaticLoad, expirationTime,
runtimeVersion, functionCode,
+ children.toArray(new Expression[0]));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PythonUdaf)) {
+ return false;
+ }
+ PythonUdaf other = (PythonUdaf) o;
+ return volatileIdentity.equalsByIdentity(other.volatileIdentity,
super.equals(o));
+ }
+
+ @Override
+ public int computeHashCode() {
+ return volatileIdentity.hashCodeByIdentity(super.computeHashCode());
+ }
+
/**
* translate catalog python udaf to nereids python udaf
*/
@@ -158,6 +207,8 @@ public class PythonUdaf extends AggregateFunction
implements ExplicitlyCastableS
PythonUdaf udaf = new PythonUdaf(fnName, aggregate.getId(), dbName,
aggregate.getBinaryType(), sig,
intermediateType,
aggregate.getNullableMode(),
+ aggregate.getVolatility(),
+ Udf.createVolatileIdentity(aggregate.getVolatility()),
aggregate.getLocation() == null ? null :
aggregate.getLocation().getLocation(),
aggregate.getSymbolName(),
aggregate.getInitFnSymbol(),
@@ -211,9 +262,15 @@ public class PythonUdaf extends AggregateFunction
implements ExplicitlyCastableS
expr.setExpirationTime(expirationTime);
expr.setRuntimeVersion(runtimeVersion);
expr.setFunctionCode(functionCode);
+ expr.setVolatility(volatility);
return expr;
} catch (Exception e) {
throw new AnalysisException(e.getMessage(), e.getCause());
}
}
+
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
+ }
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdafBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdafBuilder.java
index 73a50b6bc40..d18800fd429 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdafBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdafBuilder.java
@@ -79,7 +79,7 @@ public class PythonUdafBuilder extends UdfBuilder {
@Override
public Pair<PythonUdaf, PythonUdaf> build(String name, List<?> arguments) {
- return Pair.ofSame((PythonUdaf) udaf.withChildren(
+ return Pair.ofSame((PythonUdaf)
udaf.withFreshVolatileIdentity().withChildren(
arguments.stream()
.map(Expression.class::cast)
.collect(Collectors.toList()))
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdf.java
index 65c8f2f14d7..8bf05a8a6af 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdf.java
@@ -28,7 +28,6 @@ import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
-import org.apache.doris.nereids.trees.expressions.VolatileExpression;
import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
@@ -46,7 +45,7 @@ import java.util.stream.Collectors;
/**
* Python UDF for Nereids
*/
-public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSignature, Udf, VolatileExpression {
+public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSignature, Udf {
private final String dbName;
private final long functionId;
private final Function.BinaryType binaryType;
@@ -139,6 +138,7 @@ public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSigna
}
/** Return a copy with a new per-call identity when this UDF is VOLATILE.
*/
+ @Override
public PythonUdf withFreshVolatileIdentity() {
if (volatility != FunctionVolatility.VOLATILE) {
return this;
@@ -149,11 +149,6 @@ public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSigna
runtimeVersion, functionCode, children.toArray(new Expression[0]));
}
- @Override
- public boolean isDeterministic() {
- return volatility == FunctionVolatility.IMMUTABLE;
- }
-
@Override
public boolean equals(Object o) {
if (!(o instanceof PythonUdf)) {
@@ -188,7 +183,7 @@ public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSigna
.toArray(SlotReference[]::new);
PythonUdf udf = new PythonUdf(fnName, scalar.getId(), dbName,
scalar.getBinaryType(), sig,
- scalar.getNullableMode(), scalar.getVolatility(),
createVolatileIdentity(scalar.getVolatility()),
+ scalar.getNullableMode(), scalar.getVolatility(),
Udf.createVolatileIdentity(scalar.getVolatility()),
scalar.getLocation() == null ? null :
scalar.getLocation().getLocation(),
scalar.getSymbolName(),
scalar.getPrepareFnSymbol(),
@@ -235,8 +230,8 @@ public class PythonUdf extends ScalarFunction implements
ExplicitlyCastableSigna
}
}
- private static VolatileIdentity createVolatileIdentity(FunctionVolatility
volatility) {
- return volatility == FunctionVolatility.VOLATILE
- ? VolatileIdentity.newVolatileIdentity() :
VolatileIdentity.NON_VOLATILE;
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
}
}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtf.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtf.java
index 74e662aee72..61aa694ff8e 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtf.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtf.java
@@ -22,11 +22,13 @@ import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.Function.NullableMode;
import org.apache.doris.catalog.FunctionName;
import org.apache.doris.catalog.FunctionSignature;
+import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.catalog.Type;
import org.apache.doris.common.util.URI;
import org.apache.doris.nereids.exceptions.AnalysisException;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.SlotReference;
+import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
import
org.apache.doris.nereids.trees.expressions.functions.ExplicitlyCastableSignature;
import org.apache.doris.nereids.trees.expressions.functions.Udf;
import
org.apache.doris.nereids.trees.expressions.functions.generator.TableGeneratingFunction;
@@ -49,6 +51,8 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
private final Function.BinaryType binaryType;
private final FunctionSignature signature;
private final NullableMode nullableMode;
+ private final FunctionVolatility volatility;
+ private final VolatileIdentity volatileIdentity;
private final String objectFile;
private final String symbol;
private final String prepareFn;
@@ -64,7 +68,9 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
*/
public PythonUdtf(String name, long functionId, String dbName,
Function.BinaryType binaryType,
FunctionSignature signature,
- NullableMode nullableMode, String objectFile, String symbol,
String prepareFn, String closeFn,
+ NullableMode nullableMode, FunctionVolatility volatility,
VolatileIdentity volatileIdentity,
+ String objectFile, String symbol,
+ String prepareFn, String closeFn,
String checkSum, boolean isStaticLoad, long expirationTime,
String runtimeVersion, String functionCode, Expression... args) {
super(name, args);
@@ -73,6 +79,8 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
this.binaryType = binaryType;
this.signature = signature;
this.nullableMode = nullableMode;
+ this.volatility = volatility;
+ this.volatileIdentity = volatileIdentity;
this.objectFile = objectFile;
this.symbol = symbol;
this.prepareFn = prepareFn;
@@ -91,10 +99,50 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
public PythonUdtf withChildren(List<Expression> children) {
Preconditions.checkArgument(children.size() == this.children.size());
return new PythonUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility, volatileIdentity, objectFile, symbol, prepareFn,
closeFn, checkSum, isStaticLoad,
+ expirationTime,
+ runtimeVersion, functionCode, children.toArray(new
Expression[0]));
+ }
+
+ @Override
+ public VolatileIdentity getVolatileIdentity() {
+ return volatileIdentity;
+ }
+
+ @Override
+ public PythonUdtf withIgnoreUniqueId(boolean ignoreUniqueId) {
+ Preconditions.checkState(isVolatile(), "Only volatile Python UDTF can
ignore unique id");
+ return new PythonUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility,
volatileIdentity.withIgnoreUniqueId(ignoreUniqueId),
objectFile, symbol, prepareFn, closeFn, checkSum,
isStaticLoad, expirationTime,
runtimeVersion, functionCode, children.toArray(new
Expression[0]));
}
+ @Override
+ public PythonUdtf withFreshVolatileIdentity() {
+ if (volatility != FunctionVolatility.VOLATILE) {
+ return this;
+ }
+ return new PythonUdtf(getName(), functionId, dbName, binaryType,
signature, nullableMode,
+ volatility, VolatileIdentity.newVolatileIdentity(),
+ objectFile, symbol, prepareFn, closeFn, checkSum,
isStaticLoad, expirationTime,
+ runtimeVersion, functionCode, children.toArray(new
Expression[0]));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof PythonUdtf)) {
+ return false;
+ }
+ PythonUdtf other = (PythonUdtf) o;
+ return volatileIdentity.equalsByIdentity(other.volatileIdentity,
super.equals(o));
+ }
+
+ @Override
+ public int computeHashCode() {
+ return volatileIdentity.hashCodeByIdentity(super.computeHashCode());
+ }
+
@Override
public List<FunctionSignature> getSignatures() {
return ImmutableList.of(signature);
@@ -132,6 +180,7 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
expr.setUDTFunction(true);
expr.setRuntimeVersion(runtimeVersion);
expr.setFunctionCode(functionCode);
+ expr.setVolatility(volatility);
return expr;
} catch (Exception e) {
throw new AnalysisException(e.getMessage(), e.getCause());
@@ -159,6 +208,8 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
PythonUdtf udtf = new PythonUdtf(fnName, scalar.getId(), dbName,
scalar.getBinaryType(), sig,
scalar.getNullableMode(),
+ scalar.getVolatility(),
+ Udf.createVolatileIdentity(scalar.getVolatility()),
scalar.getLocation() == null ? null :
scalar.getLocation().getLocation(),
scalar.getSymbolName(),
scalar.getPrepareFnSymbol(),
@@ -179,6 +230,11 @@ public class PythonUdtf extends TableGeneratingFunction
implements ExplicitlyCas
return nullableMode;
}
+ @Override
+ public FunctionVolatility getVolatility() {
+ return volatility;
+ }
+
@Override
public <R, C> R accept(ExpressionVisitor<R, C> visitor, C context) {
return visitor.visitPythonUdtf(this, context);
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtfBuilder.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtfBuilder.java
index 3c032ba18ab..dd9638f3c20 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtfBuilder.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/udf/PythonUdtfBuilder.java
@@ -88,7 +88,7 @@ public class PythonUdtfBuilder extends UdfBuilder {
for (int i = 0; i < exprs.size(); ++i) {
processedExprs.add(TypeCoercionUtils.castIfNotSameType(exprs.get(i),
argTypes.get(i)));
}
- return Pair.ofSame(udtf.withChildren(processedExprs));
+ return
Pair.ofSame(udtf.withFreshVolatileIdentity().withChildren(processedExprs));
}
@Override
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 027056ba321..0cbfb30d59f 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
@@ -185,6 +185,7 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
// if not, will core dump when input is not null column, but need return
null
// like https://github.com/apache/doris/pull/14002/files
private NullableMode returnNullMode = NullableMode.ALWAYS_NULLABLE;
+ // Keep IMMUTABLE as the UDAF/UDTF default for compatibility with previous
behavior.
private FunctionVolatility volatility = FunctionVolatility.IMMUTABLE;
private String runtimeVersion;
private String functionCode;
@@ -324,10 +325,6 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
throw new AnalysisException("do not support 'NATIVE' udf type
after doris version 1.2.0,"
+ "please use JAVA_UDF or RPC instead");
}
- if (properties.containsKey(VOLATILITY) && (isAggregate ||
isTableFunction)) {
- throw new AnalysisException("volatility property only supports
scalar JAVA_UDF and PYTHON_UDF");
- }
-
userFile = properties.getOrDefault(FILE_KEY,
properties.get(OBJECT_FILE_KEY));
originalUserFile = userFile; // Keep original jar name for BE
// Inline Python code is authoritative. Keep FILE in metadata for
replay, but do not load or validate it here.
@@ -347,9 +344,7 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
if (binaryType == Function.BinaryType.JAVA_UDF) {
FunctionUtil.checkEnableJavaUdf();
checkUdfSupportedTypes();
- if (!isAggregate && !isTableFunction) {
- volatility = analyzeVolatility();
- }
+ volatility = analyzeVolatility(defaultVolatility());
// always_nullable the default value is true, equal null means true
Boolean isReturnNull = parseBooleanFromProperties(IS_RETURN_NULL);
@@ -365,9 +360,7 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
} else if (binaryType == Function.BinaryType.PYTHON_UDF) {
FunctionUtil.checkEnablePythonUdf();
checkUdfSupportedTypes();
- if (!isAggregate && !isTableFunction) {
- volatility = analyzeVolatility();
- }
+ volatility = analyzeVolatility(defaultVolatility());
// always_nullable the default value is true, equal null means true
Boolean isReturnNull = parseBooleanFromProperties(IS_RETURN_NULL);
@@ -389,9 +382,13 @@ public class CreateFunctionCommand extends Command
implements ForwardWithSync {
}
}
- private FunctionVolatility analyzeVolatility() throws AnalysisException {
+ private FunctionVolatility defaultVolatility() {
+ return isAggregate || isTableFunction ? FunctionVolatility.IMMUTABLE :
FunctionVolatility.VOLATILE;
+ }
+
+ private FunctionVolatility analyzeVolatility(FunctionVolatility
defaultVolatility) throws AnalysisException {
if (!properties.containsKey(VOLATILITY)) {
- return FunctionVolatility.VOLATILE;
+ return defaultVolatility;
}
try {
return FunctionVolatility.fromString(properties.get(VOLATILITY));
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java
index 081d482308a..58a849ce308 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommand.java
@@ -295,9 +295,8 @@ public class ShowFunctionsCommand extends ShowCommand {
}
if (function instanceof ScalarFunction) {
ScalarFunction scalarFunction = (ScalarFunction) function;
- if (!scalarFunction.isUDTFunction()
- && (function.getBinaryType() ==
Function.BinaryType.JAVA_UDF
- || function.getBinaryType() ==
Function.BinaryType.PYTHON_UDF)) {
+ if (function.getBinaryType() == Function.BinaryType.JAVA_UDF
+ || function.getBinaryType() ==
Function.BinaryType.PYTHON_UDF) {
properties.put("VOLATILITY", function.getVolatility().toSql());
}
properties.put("SYMBOL",
Strings.nullToEmpty(scalarFunction.getSymbolName()));
@@ -311,6 +310,10 @@ public class ShowFunctionsCommand extends ShowCommand {
if (function instanceof AggregateFunction) {
AggregateFunction aggregateFunction = (AggregateFunction) function;
+ if (function.getBinaryType() == Function.BinaryType.JAVA_UDF
+ || function.getBinaryType() ==
Function.BinaryType.PYTHON_UDF) {
+ properties.put("VOLATILITY", function.getVolatility().toSql());
+ }
properties.put("INIT_FN",
Strings.nullToEmpty(aggregateFunction.getInitFnSymbol()));
properties.put("UPDATE_FN",
Strings.nullToEmpty(aggregateFunction.getUpdateFnSymbol()));
properties.put("MERGE_FN",
Strings.nullToEmpty(aggregateFunction.getMergeFnSymbol()));
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 e6741b9e54c..72d29a16f2b 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
@@ -128,6 +128,17 @@ public class CreateFunctionTest {
+ "properties('type'='PYTHON_UDF', 'symbol'='evaluate',
'runtime_version'='3.10.2');";
createFunction(defaultVolatileSql, ctx);
Assert.assertEquals(FunctionVolatility.VOLATILE, findFunction(db,
"py_default").getVolatility());
+
+ String defaultImmutableUdafSql = "create aggregate function
db1.py_agg_default(int) returns int "
+ + "properties('type'='PYTHON_UDF', 'symbol'='Agg',
'runtime_version'='3.10.2');";
+ createFunction(defaultImmutableUdafSql, ctx);
+ Assert.assertEquals(FunctionVolatility.IMMUTABLE, findFunction(db,
"py_agg_default").getVolatility());
+
+ String stableUdtfSql = "create tables function
db1.py_table_stable(int) returns array<int> "
+ + "properties('type'='PYTHON_UDF', 'symbol'='evaluate',
'runtime_version'='3.10.2', "
+ + "'volatility'='stable');";
+ createFunction(stableUdtfSql, ctx);
+ Assert.assertEquals(FunctionVolatility.STABLE, findFunction(db,
"py_table_stable").getVolatility());
}
@Test
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/catalog/FunctionToSqlConverterTest.java
b/fe/fe-core/src/test/java/org/apache/doris/catalog/FunctionToSqlConverterTest.java
index d60a4502d2d..9013c7671db 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/catalog/FunctionToSqlConverterTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/catalog/FunctionToSqlConverterTest.java
@@ -154,7 +154,7 @@ public class FunctionToSqlConverterTest {
ScalarFunction fn = ScalarFunction.createUdf(BinaryType.JAVA_UDF,
name, argTypes,
Type.INT, false, null, "com.example.TableFn", null, null);
fn.setUDTFunction(true);
- fn.setVolatility(FunctionVolatility.IMMUTABLE);
+ fn.setVolatility(FunctionVolatility.STABLE);
String sql = FunctionToSqlConverter.toSql(fn, true);
@@ -162,7 +162,7 @@ public class FunctionToSqlConverterTest {
Assertions.assertTrue(sql.contains("java_table_fn(int)"));
Assertions.assertTrue(sql.contains("RETURNS array<int>"));
Assertions.assertTrue(sql.contains("\"TYPE\"=\"JAVA_UDF\""));
- Assertions.assertFalse(sql.contains("VOLATILITY"));
+ Assertions.assertTrue(sql.contains("\"VOLATILITY\"=\"stable\""));
}
@Test
@@ -174,7 +174,7 @@ public class FunctionToSqlConverterTest {
fn.setUDTFunction(true);
fn.setRuntimeVersion("3.10.2");
fn.setFunctionCode("def evaluate(x):\n yield x");
- fn.setVolatility(FunctionVolatility.IMMUTABLE);
+ fn.setVolatility(FunctionVolatility.VOLATILE);
String sql = FunctionToSqlConverter.toSql(fn, false);
@@ -182,10 +182,10 @@ public class FunctionToSqlConverterTest {
Assertions.assertTrue(sql.contains("py_table_fn(int)"));
Assertions.assertTrue(sql.contains("RETURNS array<int>"));
Assertions.assertTrue(sql.contains("\"RUNTIME_VERSION\"=\"3.10.2\""));
+ Assertions.assertTrue(sql.contains("\"VOLATILITY\"=\"volatile\""));
Assertions.assertTrue(sql.contains("\"TYPE\"=\"PYTHON_UDF\""));
Assertions.assertTrue(sql.contains("AS $$\ndef evaluate(x):\n yield
x\n$$;"));
Assertions.assertFalse(sql.contains("\"FILE\"="));
- Assertions.assertFalse(sql.contains("VOLATILITY"));
}
// ======================== ScalarFunction — IF NOT EXISTS
========================
@@ -263,6 +263,7 @@ public class FunctionToSqlConverterTest {
.symbolName("com.example.MySum")
.location(URI.create("file:///tmp/java-udaf.jar"))
.build();
+ fn.setVolatility(FunctionVolatility.STABLE);
String sql = FunctionToSqlConverter.toSql(fn, false);
@@ -272,6 +273,7 @@ public class FunctionToSqlConverterTest {
Assertions.assertTrue(sql.contains("\"SYMBOL\"=\"com.example.MySum\""));
Assertions.assertTrue(sql.contains("\"FILE\"=\"file:///tmp/java-udaf.jar\""));
Assertions.assertTrue(sql.contains("\"TYPE\"=\"JAVA_UDF\""));
+ Assertions.assertTrue(sql.contains("\"VOLATILITY\"=\"stable\""));
Assertions.assertTrue(sql.contains("\"ALWAYS_NULLABLE\"="));
Assertions.assertFalse(sql.contains("INIT_FN"));
Assertions.assertFalse(sql.contains("UPDATE_FN"));
@@ -313,11 +315,13 @@ public class FunctionToSqlConverterTest {
fn.setRuntimeVersion("3.10.2");
fn.setExpirationTime(45);
fn.setFunctionCode("class SumState:\n pass");
+ fn.setVolatility(FunctionVolatility.IMMUTABLE);
String sql = FunctionToSqlConverter.toSql(fn, false);
Assertions.assertTrue(sql.contains("\"RUNTIME_VERSION\"=\"3.10.2\""));
Assertions.assertTrue(sql.contains("\"EXPIRATION_TIME\"=\"45\""));
+ Assertions.assertTrue(sql.contains("\"VOLATILITY\"=\"immutable\""));
Assertions.assertTrue(sql.contains("\"TYPE\"=\"PYTHON_UDF\""));
Assertions.assertTrue(sql.contains("AS $$\nclass SumState:\n
pass\n$$;"));
Assertions.assertFalse(sql.contains("\"FILE\"="));
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfVolatilityTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfVolatilityTest.java
index 290650435e9..3c54a1acd18 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfVolatilityTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/udf/UdfVolatilityTest.java
@@ -23,6 +23,7 @@ import org.apache.doris.catalog.FunctionSignature;
import org.apache.doris.catalog.FunctionVolatility;
import org.apache.doris.nereids.trees.expressions.Expression;
import org.apache.doris.nereids.trees.expressions.VolatileIdentity;
+import org.apache.doris.nereids.trees.expressions.functions.Udf;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
import org.apache.doris.nereids.types.IntegerType;
import org.apache.doris.nereids.util.ExpressionUtils;
@@ -37,6 +38,7 @@ class UdfVolatilityTest {
PythonUdf udf = pythonUdf(FunctionVolatility.IMMUTABLE,
VolatileIdentity.NON_VOLATILE);
Assertions.assertTrue(udf.isDeterministic());
+ Assertions.assertEquals(FunctionVolatility.IMMUTABLE,
udf.getVolatility());
Assertions.assertFalse(udf.containsVolatileExpression());
Assertions.assertEquals(PythonUdf.class, new
PythonUdfBuilder(udf).functionClass());
}
@@ -47,6 +49,7 @@ class UdfVolatilityTest {
PythonUdf second = pythonUdf(FunctionVolatility.VOLATILE,
VolatileIdentity.newVolatileIdentity());
Assertions.assertFalse(first.isDeterministic());
+ Assertions.assertEquals(FunctionVolatility.VOLATILE,
first.getVolatility());
Assertions.assertTrue(first.containsVolatileExpression());
Assertions.assertNotEquals(first, second);
@@ -69,9 +72,45 @@ class UdfVolatilityTest {
JavaUdf udf = javaUdf(FunctionVolatility.STABLE,
VolatileIdentity.NON_VOLATILE);
Assertions.assertFalse(udf.isDeterministic());
+ Assertions.assertEquals(FunctionVolatility.STABLE,
udf.getVolatility());
Assertions.assertFalse(udf.containsVolatileExpression());
}
+ @Test
+ void testPythonUdafVolatility() {
+ PythonUdaf immutable = pythonUdaf(FunctionVolatility.IMMUTABLE);
+ PythonUdaf stable = pythonUdaf(FunctionVolatility.STABLE);
+
+ Assertions.assertTrue(immutable.isDeterministic());
+ Assertions.assertFalse(stable.isDeterministic());
+ Assertions.assertEquals(FunctionVolatility.STABLE,
stable.getCatalogFunction().getVolatility());
+ Assertions.assertFalse(stable.containsVolatileExpression());
+ }
+
+ @Test
+ void testPythonUdtfVolatility() {
+ PythonUdtf immutable = pythonUdtf(FunctionVolatility.IMMUTABLE);
+ PythonUdtf volatileUdtf = pythonUdtf(FunctionVolatility.VOLATILE);
+
+ Assertions.assertTrue(immutable.isDeterministic());
+ Assertions.assertFalse(volatileUdtf.isDeterministic());
+ Assertions.assertEquals(FunctionVolatility.VOLATILE,
volatileUdtf.getCatalogFunction().getVolatility());
+ Assertions.assertTrue(volatileUdtf.containsVolatileExpression());
+ }
+
+ @Test
+ void testVolatilePythonUdafUsesUniqueIdentity() {
+ PythonUdaf first = pythonUdaf(FunctionVolatility.VOLATILE);
+ PythonUdaf second = pythonUdaf(FunctionVolatility.VOLATILE);
+
+ Assertions.assertTrue(first.containsVolatileExpression());
+ Assertions.assertNotEquals(first, second);
+
+ Expression ignoredFirst =
ExpressionUtils.setIgnoreUniqueIdForVolatileExpression(first, true);
+ Expression ignoredSecond =
ExpressionUtils.setIgnoreUniqueIdForVolatileExpression(second, true);
+ Assertions.assertEquals(ignoredFirst, ignoredSecond);
+ }
+
private PythonUdf pythonUdf(FunctionVolatility volatility,
VolatileIdentity volatileIdentity) {
return new PythonUdf("py_fn", 1, "db1",
Function.BinaryType.PYTHON_UDF, signature(),
NullableMode.ALWAYS_NULLABLE, volatility, volatileIdentity,
@@ -85,6 +124,19 @@ class UdfVolatilityTest {
null, "evaluate", null, null, "", false, 360, new
IntegerLiteral(1));
}
+ private PythonUdaf pythonUdaf(FunctionVolatility volatility) {
+ return new PythonUdaf("py_agg", 1, "db1",
Function.BinaryType.PYTHON_UDF, signature(),
+ IntegerType.INSTANCE, NullableMode.ALWAYS_NULLABLE,
volatility, Udf.createVolatileIdentity(volatility),
+ null, "Agg", null, null, null, null, null, null, null, false,
"", false, 360,
+ "3.10.2", "", new IntegerLiteral(1));
+ }
+
+ private PythonUdtf pythonUdtf(FunctionVolatility volatility) {
+ return new PythonUdtf("py_table", 1, "db1",
Function.BinaryType.PYTHON_UDF, signature(),
+ NullableMode.ALWAYS_NULLABLE, volatility,
Udf.createVolatileIdentity(volatility),
+ null, "evaluate", null, null, "", false, 360, "3.10.2", "",
new IntegerLiteral(1));
+ }
+
private FunctionSignature signature() {
return
FunctionSignature.ret(IntegerType.INSTANCE).args(IntegerType.INSTANCE);
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java
index 716b9fa24da..3c1f3ba6b88 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/ShowFunctionsCommandTest.java
@@ -22,6 +22,7 @@ import org.apache.doris.analysis.UserDesc;
import org.apache.doris.analysis.UserIdentity;
import org.apache.doris.catalog.AccessPrivilege;
import org.apache.doris.catalog.AccessPrivilegeWithCols;
+import org.apache.doris.catalog.AggregateFunction;
import org.apache.doris.catalog.Env;
import org.apache.doris.catalog.Function;
import org.apache.doris.catalog.FunctionName;
@@ -130,35 +131,55 @@ public class ShowFunctionsCommandTest extends
TestWithFeService {
}
@Test
- void testBuildProperties_javaUdtfDoesNotEmitVolatility() {
+ void testBuildProperties_javaUdtfEmitsVolatility() {
ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, null);
ScalarFunction fn =
ScalarFunction.createUdf(Function.BinaryType.JAVA_UDF,
new FunctionName("test", "java_table_fn"), new Type[]
{Type.INT},
Type.INT, false, null, "com.example.TableFn", null, null);
fn.setUDTFunction(true);
- fn.setVolatility(FunctionVolatility.IMMUTABLE);
+ fn.setVolatility(FunctionVolatility.STABLE);
String properties = sf.buildPropertiesForTest(fn);
Assertions.assertTrue(properties.contains("SYMBOL=com.example.TableFn"));
- Assertions.assertFalse(properties.contains("VOLATILITY"));
+ Assertions.assertTrue(properties.contains("VOLATILITY=stable"));
}
@Test
- void testBuildProperties_pythonUdtfDoesNotEmitVolatility() {
+ void testBuildProperties_pythonUdtfEmitsVolatility() {
ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, null);
ScalarFunction fn =
ScalarFunction.createUdf(Function.BinaryType.PYTHON_UDF,
new FunctionName("test", "py_table_fn"), new Type[] {Type.INT},
Type.INT, false, null, "evaluate", null, null);
fn.setUDTFunction(true);
fn.setRuntimeVersion("3.10.2");
- fn.setVolatility(FunctionVolatility.IMMUTABLE);
+ fn.setVolatility(FunctionVolatility.VOLATILE);
String properties = sf.buildPropertiesForTest(fn);
Assertions.assertTrue(properties.contains("RUNTIME_VERSION=3.10.2"));
Assertions.assertTrue(properties.contains("SYMBOL=evaluate"));
- Assertions.assertFalse(properties.contains("VOLATILITY"));
+ Assertions.assertTrue(properties.contains("VOLATILITY=volatile"));
+ }
+
+ @Test
+ void testBuildProperties_udafEmitsVolatility() {
+ ShowFunctionsCommand sf = new ShowFunctionsCommand("test", true, null);
+ AggregateFunction fn =
AggregateFunction.AggregateFunctionBuilder.createUdfBuilder()
+ .binaryType(Function.BinaryType.PYTHON_UDF)
+ .name(new FunctionName("test", "py_agg_fn"))
+ .argsType(new Type[] {Type.INT})
+ .retType(Type.INT)
+ .intermediateType(Type.INT)
+ .hasVarArgs(false)
+ .symbolName("Agg")
+ .build();
+ fn.setVolatility(FunctionVolatility.STABLE);
+
+ String properties = sf.buildPropertiesForTest(fn);
+
+ Assertions.assertTrue(properties.contains("SYMBOL=Agg"));
+ Assertions.assertTrue(properties.contains("VOLATILITY=stable"));
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]