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}); + } + } }
