This is an automated email from the ASF dual-hosted git repository.

tkalkirill pushed a commit to branch ignite-29031
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit e353bca2b9cbddca2e487c8a20a63ad03e99f43c
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Wed Sep 2 13:48:26 2026 +0300

    IGNITE-29031 Wip
---
 .../query/calcite/exec/TableFunctionScan.java      | 20 +++++-
 .../query/calcite/exec/exp/ConverterUtils.java     | 13 ++++
 .../calcite/exec/exp/IgniteTableFunction.java      | 52 +++++++++++++++
 .../processors/query/calcite/util/TypeUtils.java   |  4 +-
 .../UserDefinedFunctionsIntegrationTest.java       | 78 ++++++++++++++++++++++
 5 files changed, 164 insertions(+), 3 deletions(-)

diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
index b29f91d6a7f..2d3742c0734 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/TableFunctionScan.java
@@ -20,7 +20,9 @@ package 
org.apache.ignite.internal.processors.query.calcite.exec;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.function.Supplier;
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.RowHandler.RowFactory;
 import org.apache.ignite.internal.util.typedef.F;
@@ -66,6 +68,22 @@ public class TableFunctionScan<Row> implements Iterable<Row> 
{
                 + "] doesn't match defined columns number [" + 
rowType.getFieldCount() + "].");
         }
 
-        return rowFactory.create(rowArr);
+        return rowFactory.create(convertBinaryColumns(rowArr));
+    }
+
+    /** Converts binary column values to the internal representation. */
+    private Object[] convertBinaryColumns(Object[] row) {
+        Object[] convertedRow = row;
+
+        for (int i = 0; i < row.length; i++) {
+            if (row[i] instanceof byte[] && 
SqlTypeUtil.isBinary(rowType.getFieldList().get(i).getType())) {
+                if (convertedRow == row)
+                    convertedRow = row.clone();
+
+                convertedRow[i] = new ByteString((byte[])row[i]);
+            }
+        }
+
+        return convertedRow;
     }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
index 3d61ad80048..a7e705058d3 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
 import org.apache.calcite.adapter.enumerable.RexImpTable;
+import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.tree.ConstantExpression;
 import org.apache.calcite.linq4j.tree.ConstantUntypedNull;
 import org.apache.calcite.linq4j.tree.Expression;
@@ -74,6 +75,9 @@ public class ConverterUtils {
             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;
     }
 
@@ -111,6 +115,9 @@ public class ConverterUtils {
             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)) {
             // E.g. operand is "int", target is "Long", generate "(long) 
operand".
@@ -230,6 +237,12 @@ public class ConverterUtils {
         if (toType == BigDecimal.class)
             throw new AssertionError("For conversion to decimal, 
ConverterUtils#convertToDecimal method should be used instead.");
 
+        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);
+
         // E.g. from "Short" to "int".
         // Generate "x.intValue()".
         final Primitive toPrimitive = Primitive.of(toType);
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteTableFunction.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteTableFunction.java
index e9ece1ee9a7..384bb93a5e0 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteTableFunction.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteTableFunction.java
@@ -18,7 +18,9 @@ package 
org.apache.ignite.internal.processors.query.calcite.exec.exp;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -27,6 +29,7 @@ import org.apache.calcite.adapter.enumerable.NullPolicy;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.schema.FunctionParameter;
 import org.apache.calcite.schema.TableFunction;
 import org.apache.ignite.cache.query.annotations.QuerySqlTableFunction;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
@@ -44,6 +47,9 @@ public class IgniteTableFunction extends 
IgniteReflectiveFunctionBase implements
     /** Column names of the returned table representation. */
     private final List<String> colNames;
 
+    /** Function parameters. */
+    private final List<FunctionParameter> funcParams;
+
     /**
      * Creates user-defined table function holder.
      *
@@ -59,6 +65,8 @@ public class IgniteTableFunction extends 
IgniteReflectiveFunctionBase implements
 
         this.colTypes = colTypes;
         this.colNames = Arrays.asList(colNames);
+
+        funcParams = sqlParameters(method, super.getParameters());
     }
 
     /**
@@ -100,6 +108,11 @@ public class IgniteTableFunction extends 
IgniteReflectiveFunctionBase implements
         return Iterable.class;
     }
 
+    /** {@inheritDoc} */
+    @Override public List<FunctionParameter> getParameters() {
+        return funcParams;
+    }
+
     /** Validates the parameters and throws an exception if it finds an 
incorrect parameter. */
     private static void validate(Method mtd, Class<?>[] colTypes, String[] 
colNames) {
         if (F.isEmpty(colTypes))
@@ -132,4 +145,43 @@ public class IgniteTableFunction extends 
IgniteReflectiveFunctionBase implements
 
         throw new IgniteSQLException("Unable to create table function for 
method '" + mtdSign + "'. " + errPostfix);
     }
+
+    /** Returns function parameters represented as SQL types where required. */
+    private static List<FunctionParameter> sqlParameters(Method method, 
List<FunctionParameter> functionParameters) {
+        var res = new ArrayList<>(functionParameters);
+
+        for (int i = 0; i < method.getParameterTypes().length; i++) {
+            if (method.getParameterTypes()[i] == byte[].class)
+                res.set(i, sqlBinaryParameter(res.get(i)));
+        }
+
+        return Collections.unmodifiableList(res);
+    }
+
+    /** Prevents Calcite from evaluating binary literals while deriving a 
table function row type. */
+    private static FunctionParameter sqlBinaryParameter(FunctionParameter 
delegate) {
+        return new FunctionParameter() {
+            /** {@inheritDoc} */
+            @Override public int getOrdinal() {
+                return delegate.getOrdinal();
+            }
+
+            /** {@inheritDoc} */
+            @Override public String getName() {
+                return delegate.getName();
+            }
+
+            /** {@inheritDoc} */
+            @Override public RelDataType getType(RelDataTypeFactory 
typeFactory) {
+                JavaTypeFactory tf = (JavaTypeFactory)typeFactory;
+
+                return tf.toSql(tf.createType(byte[].class));
+            }
+
+            /** {@inheritDoc} */
+            @Override public boolean isOptional() {
+                return delegate.isOptional();
+            }
+        };
+    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
index 6056f2bbc7c..31e8cc94677 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
@@ -309,7 +309,7 @@ public class TypeUtils {
 
     /** */
     private static Function<Object, Object> fieldConverter(ExecutionContext<?> 
ectx, RelDataType fieldType) {
-        Type storageType = ectx.getTypeFactory().getJavaClass(fieldType);
+        Type storageType = SqlTypeUtil.isBinary(fieldType) ? byte[].class : 
ectx.getTypeFactory().getJavaClass(fieldType);
 
         if (isConvertableType(storageType))
             return v -> fromInternal(ectx, v, storageType);
@@ -331,7 +331,7 @@ public class TypeUtils {
     /** */
     private static boolean hasConvertableFields(RelDataType resultType) {
         return RelOptUtil.getFieldTypeList(resultType).stream()
-            .anyMatch(TypeUtils::isConvertableType);
+            .anyMatch(type -> SqlTypeUtil.isBinary(type) || 
isConvertableType(type));
     }
 
     /** */
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
index 569420c7401..5ff92ee451b 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/UserDefinedFunctionsIntegrationTest.java
@@ -21,7 +21,9 @@ import java.math.BigDecimal;
 import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.validate.SqlValidatorException;
@@ -464,6 +466,43 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
         assertQuery("SELECT udf.decimalToInt(5.3)").returns(5).check();
     }
 
+    /** */
+    @Test
+    public void testBinaryFunctions() {
+        client.getOrCreateCache(new CacheConfiguration<>("binary-functions")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(BinaryFunctionsLibrary.class));
+
+        byte[] bytes = {1, 2, 3};
+        Consumer<List<List<?>>> binaryResultChecker = rows -> {
+            assertEquals(1, rows.size());
+            assertEquals(1, rows.get(0).size());
+            assertEqualsArraysAware(bytes, rows.get(0).get(0));
+        };
+
+        // Scalar function arguments.
+        assertQuery("SELECT binaryLength(x'010203')").returns(3).check();
+        assertQuery("SELECT 
binaryLength(?)").withParams(bytes).returns(3).check();
+
+        // Scalar function results.
+        assertQuery("SELECT 
binaryValue()").withResultChecker(binaryResultChecker).check();
+        assertQuery("SELECT 
binaryEcho(x'010203')").withResultChecker(binaryResultChecker).check();
+        assertQuery("SELECT 
binaryEcho(?)").withParams(bytes).withResultChecker(binaryResultChecker).check();
+        assertQuery("SELECT OCTET_LENGTH(binaryValue())").returns(3).check();
+
+        // Table function results.
+        assertQuery("SELECT * FROM 
binaryTableValue()").withResultChecker(binaryResultChecker).check();
+        assertQuery("SELECT * FROM 
binaryTable(?)").withParams(bytes).withResultChecker(binaryResultChecker).check();
+        assertQuery("SELECT OCTET_LENGTH(bytes) FROM 
binaryTableValue()").returns(3).check();
+        assertQuery("SELECT binaryLength(bytes) FROM 
binaryTableValue()").returns(3).check();
+
+        // Table function arguments.
+        assertQuery("SELECT * FROM 
binaryTableLength(x'010203')").returns(3).check();
+        assertQuery("SELECT * FROM 
binaryTableLength(?)").withParams(bytes).returns(3).check();
+        assertQuery("SELECT * FROM 
TABLE(binaryTableLength(binaryValue()))").returns(3).check();
+        assertQuery("SELECT * FROM 
binaryTable(x'010203')").withResultChecker(binaryResultChecker).check();
+    }
+
     /** */
     @SuppressWarnings("ThrowableNotThrown")
     private void assertThrows(String sql) {
@@ -829,4 +868,43 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
             return "CustomClass.toString";
         }
     }
+
+    /** */
+    public static class BinaryFunctionsLibrary {
+        /** */
+        @QuerySqlFunction
+        public static int binaryLength(byte[] bytes) {
+            return bytes.length;
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static byte[] binaryValue() {
+            return new byte[] {1, 2, 3};
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static byte[] binaryEcho(byte[] bytes) {
+            return bytes;
+        }
+
+        /** */
+        @QuerySqlTableFunction(columnTypes = {int.class}, columnNames = 
{"LENGTH"})
+        public static Iterable<Object[]> binaryTableLength(byte[] bytes) {
+            return Collections.singletonList(new Object[] {bytes.length});
+        }
+
+        /** */
+        @QuerySqlTableFunction(columnTypes = {byte[].class}, columnNames = 
{"BYTES"})
+        public static Iterable<Object[]> binaryTableValue() {
+            return Collections.singletonList(new Object[] {new byte[] {1, 2, 
3}});
+        }
+
+        /** */
+        @QuerySqlTableFunction(columnTypes = {byte[].class}, columnNames = 
{"BYTES"})
+        public static Iterable<Object[]> binaryTable(byte[] bytes) {
+            return Collections.singletonList(new Object[] {bytes});
+        }
+    }
 }

Reply via email to