This is an automated email from the ASF dual-hosted git repository.
guohongyu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 04a7832751 [CALCITE-7187] Java UDF byte arrays cannot be mapped to
VARBINARY
04a7832751 is described below
commit 04a7832751f7d8475f5ffc9a73c80c147b6ded30
Author: hongyu guo <[email protected]>
AuthorDate: Sat Mar 28 23:57:22 2026 +0800
[CALCITE-7187] Java UDF byte arrays cannot be mapped to VARBINARY
---
.../calcite/adapter/enumerable/EnumUtils.java | 12 ++++++++
.../org/apache/calcite/runtime/SqlFunctions.java | 16 ++++++++++
.../org/apache/calcite/util/BuiltInMethod.java | 2 ++
.../test/java/org/apache/calcite/test/UdfTest.java | 35 ++++++++++++++++++++++
site/_docs/reference.md | 4 +++
.../main/java/org/apache/calcite/util/Smalls.java | 18 +++++++++++
6 files changed, 87 insertions(+)
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 8787e682cc..018ba8bb75 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -17,6 +17,7 @@
package org.apache.calcite.adapter.enumerable;
import org.apache.calcite.adapter.java.JavaTypeFactory;
+import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.avatica.util.DateTimeUtils;
import org.apache.calcite.linq4j.AbstractEnumerable;
import org.apache.calcite.linq4j.Enumerable;
@@ -306,6 +307,8 @@ private static Expression toInternal(Expression operand,
} else if (targetType == Long.class) {
return
Expressions.call(BuiltInMethod.TIMESTAMP_TO_LONG_OPTIONAL.method, operand);
}
+ } else if (fromType == byte[].class && targetType == ByteString.class) {
+ return Expressions.call(BuiltInMethod.BYTE_ARRAY_TO_BYTE_STRING.method,
operand);
}
return operand;
}
@@ -346,6 +349,8 @@ private static Expression fromInternal(Expression operand,
if (isA(fromType, Primitive.LONG)) {
return Expressions.call(BuiltInMethod.INTERNAL_TO_TIMESTAMP.method,
operand);
}
+ } else if (targetType == byte[].class && fromType == ByteString.class) {
+ return Expressions.call(BuiltInMethod.BYTE_STRING_TO_BYTE_ARRAY.method,
operand);
}
if (Primitive.is(operand.type)
&& Primitive.isBox(targetType)) {
@@ -437,6 +442,13 @@ public static Expression convert(Expression operand, Type
fromType,
return operand;
}
+ if (fromType == byte[].class && toType == ByteString.class) {
+ return Expressions.call(BuiltInMethod.BYTE_ARRAY_TO_BYTE_STRING.method,
operand);
+ }
+ if (fromType == ByteString.class && toType == byte[].class) {
+ return Expressions.call(BuiltInMethod.BYTE_STRING_TO_BYTE_ARRAY.method,
operand);
+ }
+
// TODO use Expressions#convertChecked to throw exception in case of
overflow (CALCITE-6366)
// E.g. from "Short" to "int".
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index e2adce8ea1..0d1db4f855 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -6010,6 +6010,22 @@ public static int time(long timestampMillis, String
timeZone) {
}
}
+ public static @PolyNull ByteString byteArrayToByteString(byte @PolyNull []
bytes) {
+ if (bytes == null) {
+ return null;
+ } else {
+ return new ByteString(bytes);
+ }
+ }
+
+ public static byte @PolyNull [] byteStringToByteArray(@PolyNull ByteString
s) {
+ if (s == null) {
+ return null;
+ } else {
+ return s.getBytes();
+ }
+ }
+
/** Helper for CAST(... AS VARBINARY(maxLength)). */
public static @PolyNull ByteString truncate(@PolyNull ByteString s, int
maxLength) {
if (s == null) {
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 7ff2995282..61aebe15f5 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -720,6 +720,8 @@ public enum BuiltInMethod {
STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class,
"toTimestampWithLocalTimeZone",
String.class),
STRING_TO_BINARY(SqlFunctions.class, "stringToBinary", String.class,
Charset.class),
+ BYTE_ARRAY_TO_BYTE_STRING(SqlFunctions.class, "byteArrayToByteString",
byte[].class),
+ BYTE_STRING_TO_BYTE_ARRAY(SqlFunctions.class, "byteStringToByteArray",
ByteString.class),
TIMESTAMP_STRING_TO_TIMESTAMP_WITH_LOCAL_TIME_ZONE(SqlFunctions.class,
"toTimestampWithLocalTimeZone", String.class, TimeZone.class),
TIME_WITH_LOCAL_TIME_ZONE_TO_TIME(SqlFunctions.class,
"timeWithLocalTimeZoneToTime",
diff --git a/core/src/test/java/org/apache/calcite/test/UdfTest.java
b/core/src/test/java/org/apache/calcite/test/UdfTest.java
index 60ed8e5be1..63492af2bc 100644
--- a/core/src/test/java/org/apache/calcite/test/UdfTest.java
+++ b/core/src/test/java/org/apache/calcite/test/UdfTest.java
@@ -190,6 +190,18 @@ private CalciteAssert.AssertThat withUdf() {
+ "'\n"
+ " },\n"
+ " {\n"
+ + " name: 'BYTEARRAY',\n"
+ + " className: '"
+ + Smalls.ByteArrayFunction.class.getName()
+ + "'\n"
+ + " },\n"
+ + " {\n"
+ + " name: 'BYTEARRAY_LENGTH',\n"
+ + " className: '"
+ + Smalls.ByteArrayLengthFunction.class.getName()
+ + "'\n"
+ + " },\n"
+ + " {\n"
+ " name: 'CHARACTERARRAY',\n"
+ " className: '"
+ Smalls.CharacterArrayFunction.class.getName()
@@ -1107,6 +1119,29 @@ private static CalciteAssert.AssertThat
withBadUdf(Class<?> clazz) {
withUdf().query(sql2).returns("C=true\n");
}
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7187">[CALCITE-7187]
+ * Java UDF byte arrays cannot be mapped to VARBINARY</a>. */
+ @Test void testByteArrayDirectComparison() {
+ final String testString = "test";
+ final String testHex = "74657374";
+
+ final String sql = "values \"adhoc\".bytearray('" + testString + "')";
+ withUdf().query(sql).typeIs("[EXPR$0 VARBINARY]");
+
+ final String sql2 = "select \"adhoc\".bytearray(cast('" + testString
+ + "' as varchar)) = x'" + testHex + "' as C\n";
+ withUdf().query(sql2).returns("C=true\n");
+ }
+
+ /** Test case for
+ * <a
href="https://issues.apache.org/jira/browse/CALCITE-7187">[CALCITE-7187]
+ * Java UDF byte arrays cannot be mapped to VARBINARY</a>. */
+ @Test void testByteArrayParameter() {
+ withUdf().query("values \"adhoc\".bytearray_length(x'74657374')")
+ .returns("EXPR$0=4\n");
+ }
+
/**
* Test for <a
href="https://issues.apache.org/jira/browse/CALCITE-7186">[CALCITE-7186]</a>
* Add mapping from Character[] to VARCHAR in Java UDF.
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index ee3d2d739f..96eb1a25b5 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -3426,6 +3426,10 @@ ## User-defined functions
[Parameter]({{ site.apiRoot
}}/org/apache/calcite/linq4j/function/Parameter.html)
annotation.
+For Java UDFs, `byte[]` and `ByteString` are supported Java representations of
+SQL `VARBINARY` values for parameters and return types. Boxed byte arrays
+(`Byte[]`) are not supported.
+
### Calling functions with named and optional parameters
Usually when you call a function, you need to specify all of its parameters,
diff --git a/testkit/src/main/java/org/apache/calcite/util/Smalls.java
b/testkit/src/main/java/org/apache/calcite/util/Smalls.java
index 3b34d1a88d..a6c52e466f 100644
--- a/testkit/src/main/java/org/apache/calcite/util/Smalls.java
+++ b/testkit/src/main/java/org/apache/calcite/util/Smalls.java
@@ -69,6 +69,7 @@
import java.io.IOException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
@@ -1493,6 +1494,23 @@ public static ByteString eval(String s) {
}
}
+ /** User-defined function with return type byte[]. */
+ public static class ByteArrayFunction {
+ public static byte[] eval(String s) {
+ if (s == null) {
+ return null;
+ }
+ return s.getBytes(StandardCharsets.UTF_8);
+ }
+ }
+
+ /** User-defined function with parameter type byte[]. */
+ public static class ByteArrayLengthFunction {
+ public static int eval(byte[] bytes) {
+ return bytes.length;
+ }
+ }
+
/** User-defined function with return type Character[]. */
public static class CharacterArrayFunction {
public static Character[] eval(String s) {