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) {

Reply via email to