From e353bca2b9cbddca2e487c8a20a63ad03e99f43c Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Wed, 2 Sep 2026 13:48:26 +0300 Subject: [PATCH 1/2] IGNITE-29031 Wip --- .../query/calcite/exec/TableFunctionScan.java | 20 ++++- .../calcite/exec/exp/ConverterUtils.java | 13 ++++ .../calcite/exec/exp/IgniteTableFunction.java | 52 +++++++++++++ .../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 b29f91d6a7fe8..2d3742c073434 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 @@ 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 @@ private Row convertToRow(Object rowContainer) { + "] 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 3d61ad80048ac..a7e705058d35d 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.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 @@ else if (fromType == java.sql.Timestamp.class) { 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 @@ else if (targetType == java.sql.Timestamp.class) { 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 static Expression convert(Expression operand, Type fromType, Type toType) 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 e9ece1ee9a740..384bb93a5e0c8 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 @@ 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.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 colNames; + /** Function parameters. */ + private final List funcParams; + /** * Creates user-defined table function holder. * @@ -59,6 +65,8 @@ private IgniteTableFunction(Method method, Class[] colTypes, String[] colName this.colTypes = colTypes; this.colNames = Arrays.asList(colNames); + + funcParams = sqlParameters(method, super.getParameters()); } /** @@ -100,6 +108,11 @@ public static IgniteTableFunction create(Method method, Class[] colTypes, Str return Iterable.class; } + /** {@inheritDoc} */ + @Override public List 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 @@ private static void raiseValidationError(Method method, String errPostfix) { throw new IgniteSQLException("Unable to create table function for method '" + mtdSign + "'. " + errPostfix); } + + /** Returns function parameters represented as SQL types where required. */ + private static List sqlParameters(Method method, List 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 6056f2bbc7c30..31e8cc9467716 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 static Function resultTypeConverter(ExecutionContext /** */ private static Function 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 static boolean isConvertableType(RelDataType type) { /** */ 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 569420c7401b5..5ff92ee451bad 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.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 void testBigDecimalFunctionArgument() { 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>> 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 @@ private static class CustomClass { 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 binaryTableLength(byte[] bytes) { + return Collections.singletonList(new Object[] {bytes.length}); + } + + /** */ + @QuerySqlTableFunction(columnTypes = {byte[].class}, columnNames = {"BYTES"}) + public static Iterable binaryTableValue() { + return Collections.singletonList(new Object[] {new byte[] {1, 2, 3}}); + } + + /** */ + @QuerySqlTableFunction(columnTypes = {byte[].class}, columnNames = {"BYTES"}) + public static Iterable binaryTable(byte[] bytes) { + return Collections.singletonList(new Object[] {bytes}); + } + } } From f73b7e74f959694fe2f5d3a8e4ddf7fe1b3482ab Mon Sep 17 00:00:00 2001 From: Kirill Tkalenko Date: Thu, 3 Sep 2026 17:43:21 +0300 Subject: [PATCH 2/2] IGNITE-29031 After review 1.0 --- .../calcite/exec/LogicalRelImplementor.java | 2 +- .../query/calcite/exec/TableFunctionScan.java | 30 +- .../calcite/exec/exp/ConverterUtils.java | 59 +++ .../exec/exp/IgniteFunctionParameter.java | 79 +++ .../exec/exp/IgniteScalarFunction.java | 17 +- .../calcite/exec/exp/IgniteTableFunction.java | 47 +- .../exp/ReflectiveCallNotNullImplementor.java | 17 +- .../query/calcite/util/TypeUtils.java | 16 +- .../UserDefinedFunctionsIntegrationTest.java | 494 ++++++++++++++++++ 9 files changed, 690 insertions(+), 71 deletions(-) create mode 100644 modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java index ed04f327eb3ef..47d591d9eb103 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/LogicalRelImplementor.java @@ -768,7 +768,7 @@ else if (rel instanceof Intersect) RowFactory rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType); - return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(rowType, dataSupplier, rowFactory)); + return new ScanNode<>(ctx, rowType, new TableFunctionScan<>(ctx, rowType, dataSupplier, rowFactory)); } /** {@inheritDoc} */ 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 2d3742c073434..cb2f244ee8c22 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,15 +20,18 @@ 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.processors.query.calcite.type.OtherType; +import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; import org.apache.ignite.internal.util.typedef.F; /** */ public class TableFunctionScan implements Iterable { + /** */ + private final ExecutionContext ctx; + /** */ private final RelDataType rowType; @@ -40,10 +43,12 @@ public class TableFunctionScan implements Iterable { /** */ public TableFunctionScan( + ExecutionContext ctx, RelDataType rowType, Supplier> dataSupplier, RowFactory rowFactory ) { + this.ctx = ctx; this.rowType = rowType; this.dataSupplier = dataSupplier; this.rowFactory = rowFactory; @@ -60,7 +65,7 @@ private Row convertToRow(Object rowContainer) { throw new IgniteSQLException("Unable to process table function data: row type is neither Collection or Object[]."); Object[] rowArr = rowContainer.getClass() == Object[].class - ? (Object[])rowContainer + ? ((Object[])rowContainer).clone() : ((Collection)rowContainer).toArray(); if (rowArr.length != rowType.getFieldCount()) { @@ -68,22 +73,11 @@ private Row convertToRow(Object rowContainer) { + "] doesn't match defined columns number [" + rowType.getFieldCount() + "]."); } - 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]); - } + for (int i = 0; i < rowArr.length; i++) { + if (!(rowType.getFieldList().get(i).getType() instanceof OtherType)) + rowArr[i] = TypeUtils.toInternal(ctx, rowArr[i]); } - return convertedRow; + return rowFactory.create(rowArr); } } 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 a7e705058d35d..f28ea56b8e055 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 @@ -39,6 +39,7 @@ import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Util; import org.apache.ignite.internal.processors.query.calcite.util.Commons; +import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; /** */ public class ConverterUtils { @@ -152,6 +153,64 @@ static List fromInternal(Class[] targetTypes, return list; } + /** */ + static List fromInternal(RexToLixTranslator translator, + Class[] targetTypes, + List expressions + ) { + final List list = new ArrayList<>(); + + if (targetTypes.length == expressions.size()) { + for (int i = 0; i < expressions.size(); i++) + list.add(fromInternal(translator, expressions.get(i), targetTypes[i])); + } + else { + int j = 0; + + for (Expression expression : expressions) { + Class targetType; + + if (!targetTypes[j].isArray()) { + targetType = targetTypes[j]; + j++; + } + else + targetType = targetTypes[j].getComponentType(); + + list.add(fromInternal(translator, expression, targetType)); + } + } + + return list; + } + + /** */ + private static Expression fromInternal(RexToLixTranslator translator, Expression operand, Type targetType) { + if (Types.isAssignableFrom(targetType, operand.getType())) + return operand; + + if (!TypeUtils.isConvertableType(targetType)) + return targetType == BigDecimal.class ? fromInternal(operand, targetType) : + convert(operand, operand.getType(), targetType); + + if (Primitive.is(operand.getType())) + operand = Expressions.box(operand); + + Expression converted = Expressions.call( + TypeUtils.class, + "fromInternal", + translator.getRoot(), + operand, + Expressions.constant(targetType) + ); + + Primitive primitive = Primitive.of(targetType); + + return primitive == null + ? Expressions.convert_(converted, targetType) + : Expressions.unbox(Expressions.convert_(converted, primitive.boxClass), primitive); + } + /** */ private static Type toInternal(RelDataType type) { return toInternal(type, false); diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java new file mode 100644 index 0000000000000..7f714dcb1e8b4 --- /dev/null +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ignite.internal.processors.query.calcite.exec.exp; + +import java.util.List; +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.sql.type.SqlTypeName; +import org.apache.ignite.internal.processors.query.calcite.type.OtherType; + +/** + * Reflective Java function parameter represented with a SQL type. + * + *

The SQL representation is required to validate user-defined function arguments and to convert literal arguments + * while deriving a table function row type. + */ +final class IgniteFunctionParameter implements FunctionParameter { + /** Original function parameter. */ + private final FunctionParameter delegate; + + /** + * Constructor. + * + * @param delegate Original function parameter. + */ + private IgniteFunctionParameter(FunctionParameter delegate) { + this.delegate = delegate; + } + + /** Returns function parameters represented with SQL types. */ + static List toSql(List parameters) { + return parameters.stream().map(IgniteFunctionParameter::toSql).toList(); + } + + /** Returns a function parameter represented with a SQL type. */ + static FunctionParameter toSql(FunctionParameter parameter) { + return new IgniteFunctionParameter(parameter); + } + + /** {@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; + RelDataType type = tf.toSql(delegate.getType(typeFactory)); + + // Prevent the validator from replacing OTHER with a structured type derived from a dynamic parameter value. + return type.getSqlTypeName() == SqlTypeName.OTHER ? new OtherType(type.isNullable()) : type; + } + + /** {@inheritDoc} */ + @Override public boolean isOptional() { + return delegate.isOptional(); + } +} diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteScalarFunction.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteScalarFunction.java index 09f377e3560bc..0c2d2415bc793 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteScalarFunction.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteScalarFunction.java @@ -17,9 +17,12 @@ package org.apache.ignite.internal.processors.query.calcite.exec.exp; import java.lang.reflect.Method; +import java.util.List; 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.ScalarFunction; /** @@ -29,6 +32,9 @@ public class IgniteScalarFunction extends IgniteReflectiveFunctionBase implement /** */ private final boolean deterministic; + /** */ + private final List funcParams; + /** * Private constructor. */ @@ -36,6 +42,8 @@ private IgniteScalarFunction(Method method, CallImplementor implementor, boolean super(method, implementor); this.deterministic = deterministic; + + funcParams = IgniteFunctionParameter.toSql(super.getParameters()); } /** @@ -54,7 +62,14 @@ public static ScalarFunction create(Method method, boolean deterministic) { /** {@inheritDoc} */ @Override public RelDataType getReturnType(RelDataTypeFactory typeFactory) { - return typeFactory.createJavaType(method.getReturnType()); + JavaTypeFactory tf = (JavaTypeFactory)typeFactory; + + return tf.toSql(tf.createJavaType(method.getReturnType())); + } + + /** {@inheritDoc} */ + @Override public List getParameters() { + return funcParams; } /** 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 384bb93a5e0c8..9f0ff919d06e2 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,9 +18,7 @@ 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; @@ -47,7 +45,7 @@ public class IgniteTableFunction extends IgniteReflectiveFunctionBase implements /** Column names of the returned table representation. */ private final List colNames; - /** Function parameters. */ + /** */ private final List funcParams; /** @@ -66,7 +64,7 @@ private IgniteTableFunction(Method method, Class[] colTypes, String[] colName this.colTypes = colTypes; this.colNames = Arrays.asList(colNames); - funcParams = sqlParameters(method, super.getParameters()); + funcParams = IgniteFunctionParameter.toSql(super.getParameters()); } /** @@ -88,7 +86,7 @@ public static IgniteTableFunction create(Method method, Class[] colTypes, Str @Override public RelDataType getRowType(RelDataTypeFactory typeFactory, List arguments) { JavaTypeFactory tf = (JavaTypeFactory)typeFactory; - List converted = Stream.of(colTypes).map(cl -> tf.toSql(tf.createType(cl))).collect(Collectors.toList()); + List converted = Stream.of(colTypes).map(cl -> tf.toSql(tf.createJavaType(cl))).collect(Collectors.toList()); return typeFactory.createStructType(converted, colNames); } @@ -145,43 +143,4 @@ private static void raiseValidationError(Method method, String errPostfix) { throw new IgniteSQLException("Unable to create table function for method '" + mtdSign + "'. " + errPostfix); } - - /** Returns function parameters represented as SQL types where required. */ - private static List sqlParameters(Method method, List 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/exec/exp/ReflectiveCallNotNullImplementor.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java index 0f8958c4e5a8b..591b347c6ff58 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java @@ -18,11 +18,13 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Type; import java.util.List; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; import org.apache.calcite.rex.RexCall; +import org.apache.ignite.internal.processors.query.calcite.util.TypeUtils; import static org.apache.ignite.internal.processors.query.calcite.util.IgniteMethod.UDF_INSTANCE; @@ -49,10 +51,10 @@ public ReflectiveCallNotNullImplementor(Method method) { @Override public Expression implement(RexToLixTranslator translator, RexCall call, List translatedOperands) { translatedOperands = - ConverterUtils.fromInternal(method.getParameterTypes(), translatedOperands); + ConverterUtils.fromInternal(translator, method.getParameterTypes(), translatedOperands); translatedOperands = ConverterUtils.convertAssignableTypes(method.getParameterTypes(), translatedOperands); - final Expression callExpr; + Expression callExpr; if ((method.getModifiers() & Modifier.STATIC) != 0) callExpr = Expressions.call(method, translatedOperands); @@ -66,6 +68,17 @@ public ReflectiveCallNotNullImplementor(Method method) { callExpr = Expressions.call(target, method, translatedOperands); } + + if (TypeUtils.isConvertableType(method.getReturnType())) { + Type targetType = translator.typeFactory.getJavaClass(call.getType()); + Expression result = method.getReturnType().isPrimitive() ? Expressions.box(callExpr) : callExpr; + + callExpr = Expressions.convert_( + Expressions.call(TypeUtils.class, "toInternal", translator.getRoot(), result), + targetType + ); + } + if (!containsCheckedException(method)) return callExpr; 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 31e8cc9467716..6071b5b422731 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 @@ -94,6 +94,8 @@ public class TypeUtils { LocalTime.class, Duration.class, Period.class, + char.class, + Character.class, byte[].class ); @@ -309,7 +311,7 @@ public static Function resultTypeConverter(ExecutionContext /** */ private static Function fieldConverter(ExecutionContext ectx, RelDataType fieldType) { - Type storageType = SqlTypeUtil.isBinary(fieldType) ? byte[].class : ectx.getTypeFactory().getJavaClass(fieldType); + Type storageType = ectx.getTypeFactory().getJavaClass(fieldType); if (isConvertableType(storageType)) return v -> fromInternal(ectx, v, storageType); @@ -331,7 +333,7 @@ public static boolean isConvertableType(RelDataType type) { /** */ private static boolean hasConvertableFields(RelDataType resultType) { return RelOptUtil.getFieldTypeList(resultType).stream() - .anyMatch(type -> SqlTypeUtil.isBinary(type) || isConvertableType(type)); + .anyMatch(TypeUtils::isConvertableType); } /** */ @@ -355,12 +357,12 @@ public static boolean hasScale(RelDataType type) { } /** */ - public static Object toInternal(DataContext ctx, Object val) { + public static @Nullable Object toInternal(DataContext ctx, @Nullable Object val) { return val == null ? null : toInternal(ctx, val, val.getClass()); } /** */ - public static Object toInternal(DataContext ctx, Object val, Type storageType) { + public static @Nullable Object toInternal(DataContext ctx, @Nullable Object val, Type storageType) { if (val == null) return null; else if (storageType == java.sql.Date.class) @@ -383,6 +385,8 @@ else if (storageType == Duration.class) { } else if (storageType == Period.class) return (int)((Period)val).toTotalMonths(); + else if ((storageType == char.class || storageType == Character.class) && val instanceof Character) + return val.toString(); else if (storageType == byte[].class) return new ByteString((byte[])val); else if (val instanceof Number && storageType != val.getClass()) { @@ -429,7 +433,7 @@ private static long toLong(java.util.Date val, TimeZone tz) { } /** */ - public static Object fromInternal(DataContext ctx, Object val, Type storageType) { + public static @Nullable Object fromInternal(DataContext ctx, @Nullable Object val, Type storageType) { if (val == null) return null; else if (storageType == java.sql.Date.class && val instanceof Integer) @@ -450,6 +454,8 @@ else if (storageType == Duration.class && val instanceof Long) return Duration.ofMillis((Long)val); else if (storageType == Period.class && val instanceof Integer) return Period.of((Integer)val / 12, (Integer)val % 12, 0); + else if ((storageType == char.class || storageType == Character.class) && val instanceof String) + return ((String)val).charAt(0); else if (storageType == byte[].class && val instanceof ByteString) return ((ByteString)val).getBytes(); else 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 5ff92ee451bad..7095dbbe91899 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 @@ -18,7 +18,14 @@ package org.apache.ignite.internal.processors.query.calcite.integration; import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Time; import java.sql.Timestamp; +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -30,6 +37,7 @@ import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.Ignition; +import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.cache.QueryEntity; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.apache.ignite.cache.query.annotations.QuerySqlFunction; @@ -503,6 +511,190 @@ public void testBinaryFunctions() { assertQuery("SELECT * FROM binaryTable(x'010203')").withResultChecker(binaryResultChecker).check(); } + /** */ + @Test + public void testBinaryObjectFunctions() { + client.getOrCreateCache(new CacheConfiguration<>("binary-object-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(BinaryObjectFunctionsLibrary.class)); + + BinaryObject obj = client.binary().builder("TestBinaryObject") + .setField("value", 42, Integer.class) + .build(); + + Consumer>> resultChecker = rows -> { + assertEquals(1, rows.size()); + assertEquals(1, rows.get(0).size()); + assertTrue(rows.get(0).get(0) instanceof BinaryObject); + assertEquals(Integer.valueOf(42), ((BinaryObject)rows.get(0).get(0)).field("value")); + }; + + assertQuery("SELECT binaryObjectEcho(?)") + .withParams(obj) + .withResultChecker(resultChecker) + .check(); + + assertQuery("SELECT * FROM binaryObjectTable(?)") + .withParams(obj) + .withResultChecker(resultChecker) + .check(); + } + + /** */ + @Test + public void testPrimitiveFunctions() { + client.getOrCreateCache(new CacheConfiguration<>("primitive-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(PrimitiveFunctionsLibrary.class)); + + Object[] values = {true, (byte)1, (short)2, 3, 4L, 5.0f, 6.0d, 'a'}; + + assertQuery("SELECT checkPrimitiveTypes(?, ?, ?, ?, ?, ?, ?, ?)") + .withParams(values) + .returns(true) + .check(); + + assertQuery("SELECT primitiveBoolean(), primitiveByte(), primitiveShort(), primitiveInt(), " + + "primitiveLong(), primitiveFloat(), primitiveDouble(), primitiveChar()") + .returns(true, (byte)1, (short)2, 3, 4L, 5.0f, 6.0d, "a") + .check(); + + assertQuery("SELECT * FROM primitiveTable(?, ?, ?, ?, ?, ?, ?, ?)") + .withParams(values) + .returns(true, (byte)1, (short)2, 3, 4L, 5.0f, 6.0d, "a") + .check(); + } + + /** */ + @Test + public void testCustomTypeFunctions() { + client.getOrCreateCache(new CacheConfiguration<>("custom-type-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(CustomTypeFunctionsLibrary.class)); + + Employer obj = new Employer("Igor", 42.0d); + Consumer>> resultChecker = rows -> { + assertEquals(1, rows.size()); + assertEquals(1, rows.get(0).size()); + assertTrue(rows.get(0).get(0) instanceof Employer); + assertEquals(obj, rows.get(0).get(0)); + }; + + assertQuery("SELECT customTypeEcho(?)") + .withParams(obj) + .withResultChecker(resultChecker) + .check(); + + assertQuery("SELECT * FROM customTypeTable(?)") + .withParams(obj) + .withResultChecker(resultChecker) + .check(); + } + + /** */ + @Test + public void testObjectTableFunctionResult() { + client.getOrCreateCache(new CacheConfiguration<>("object-table-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(CustomTypeFunctionsLibrary.class)); + + BinaryObject binaryObj = client.binary().builder("TestObjectTableBinaryObject") + .setField("value", 42, Integer.class) + .build(); + Object[] exp = objectValues(binaryObj); + + assertQuery("SELECT * FROM objectTableValues(?)") + .withParams(binaryObj) + .withResultChecker(rows -> { + assertEquals(1, rows.size()); + assertEquals(exp.length, rows.get(0).size()); + + for (int i = 0; i < exp.length; i++) { + Object actual = rows.get(0).get(i); + + assertEquals("Unexpected value type at index " + i, exp[i].getClass(), actual.getClass()); + assertEqualsArraysAware("Unexpected value at index " + i, exp[i], actual); + } + }) + .check(); + } + + /** */ + @Test + public void testTemporalFunctions() { + client.getOrCreateCache(new CacheConfiguration<>("temporal-table-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(TemporalFunctionsLibrary.class)); + + assertQuery("SELECT checkTemporalTypes(?, ?, ?, ?, ?, ?, ?, ?, ?)") + .withParams(temporalValues()) + .returns(true) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM udfUtilDateValue()), EXTRACT(DAY FROM udfDateValue()), " + + "EXTRACT(HOUR FROM udfTimeValue()), EXTRACT(YEAR FROM udfTimestampValue()), " + + "EXTRACT(DAY FROM udfLocalDateValue()), EXTRACT(HOUR FROM udfLocalTimeValue()), " + + "EXTRACT(YEAR FROM udfLocalDateTimeValue()), EXTRACT(DAY FROM udfDurationValue()), " + + "EXTRACT(HOUR FROM udfDurationValue()), EXTRACT(MINUTE FROM udfDurationValue()), " + + "EXTRACT(YEAR FROM udfPeriodValue()), EXTRACT(MONTH FROM udfPeriodValue())") + .returns(2020L, 15L, 2L, 2021L, 16L, 3L, 2023L, 1L, 2L, 3L, 1L, 2L) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM util_date), EXTRACT(DAY FROM sql_date), " + + "EXTRACT(HOUR FROM sql_time), EXTRACT(YEAR FROM sql_timestamp), " + + "EXTRACT(DAY FROM local_date), EXTRACT(HOUR FROM local_time), " + + "EXTRACT(YEAR FROM local_timestamp), EXTRACT(DAY FROM duration_value), " + + "EXTRACT(HOUR FROM duration_value), EXTRACT(MINUTE FROM duration_value), " + + "EXTRACT(YEAR FROM period_value), EXTRACT(MONTH FROM period_value) " + + "FROM temporalTable(?, ?, ?, ?, ?, ?, ?, ?, ?)") + .withParams(temporalValues()) + .returns(2020L, 15L, 2L, 2021L, 16L, 3L, 2023L, 1L, 2L, 3L, 1L, 2L) + .check(); + } + + /** */ + private static Object[] temporalValues() { + return new Object[] { + new java.util.Date(Timestamp.valueOf("2020-01-14 01:02:03").getTime()), + Date.valueOf("2021-01-15"), + Time.valueOf("02:03:04"), + Timestamp.valueOf("2021-01-15 02:03:04"), + LocalDate.of(2022, 2, 16), + LocalTime.of(3, 4, 5), + LocalDateTime.of(2023, 3, 17, 4, 5, 6), + Duration.ofDays(1).plusHours(2).plusMinutes(3), + Period.of(1, 2, 0) + }; + } + + /** */ + private static Object[] objectValues(BinaryObject binaryObj) { + Object[] temporalValues = temporalValues(); + + return new Object[] { + new byte[] {1, 2, 3}, + true, + (byte)1, + (short)2, + 3, + 4L, + 5.0f, + 6.0d, + 'a', + temporalValues[0], + temporalValues[1], + temporalValues[2], + temporalValues[3], + temporalValues[4], + temporalValues[5], + temporalValues[6], + temporalValues[7], + temporalValues[8], + new Employer("Igor", 42.0d), + binaryObj + }; + } + /** */ @SuppressWarnings("ThrowableNotThrown") private void assertThrows(String sql) { @@ -907,4 +1099,306 @@ public static Iterable binaryTable(byte[] bytes) { return Collections.singletonList(new Object[] {bytes}); } } + + /** */ + public static class BinaryObjectFunctionsLibrary { + /** */ + @QuerySqlFunction + public static BinaryObject binaryObjectEcho(BinaryObject obj) { + return obj; + } + + /** */ + @QuerySqlTableFunction(columnTypes = {BinaryObject.class}, columnNames = {"OBJ"}) + public static Iterable binaryObjectTable(BinaryObject obj) { + return Collections.singletonList(new Object[] {obj}); + } + } + + /** */ + public static class PrimitiveFunctionsLibrary { + /** */ + @QuerySqlFunction + public static boolean checkPrimitiveTypes( + boolean booleanVal, + byte byteVal, + short shortVal, + int intVal, + long longVal, + float floatVal, + double doubleVal, + char charVal + ) { + return booleanVal && byteVal == 1 && shortVal == 2 && intVal == 3 && longVal == 4 + && floatVal == 5.0f && doubleVal == 6.0d && charVal == 'a'; + } + + /** */ + @QuerySqlFunction + public static boolean primitiveBoolean() { + return true; + } + + /** */ + @QuerySqlFunction + public static byte primitiveByte() { + return 1; + } + + /** */ + @QuerySqlFunction + public static short primitiveShort() { + return 2; + } + + /** */ + @QuerySqlFunction + public static int primitiveInt() { + return 3; + } + + /** */ + @QuerySqlFunction + public static long primitiveLong() { + return 4; + } + + /** */ + @QuerySqlFunction + public static float primitiveFloat() { + return 5.0f; + } + + /** */ + @QuerySqlFunction + public static double primitiveDouble() { + return 6.0d; + } + + /** */ + @QuerySqlFunction + public static char primitiveChar() { + return 'a'; + } + + /** */ + @QuerySqlTableFunction( + columnTypes = { + boolean.class, + byte.class, + short.class, + int.class, + long.class, + float.class, + double.class, + char.class + }, + columnNames = { + "BOOLEAN_VALUE", + "BYTE_VALUE", + "SHORT_VALUE", + "INT_VALUE", + "LONG_VALUE", + "FLOAT_VALUE", + "DOUBLE_VALUE", + "CHAR_VALUE" + } + ) + public static Iterable primitiveTable( + boolean booleanVal, + byte byteVal, + short shortVal, + int intVal, + long longVal, + float floatVal, + double doubleVal, + char charVal + ) { + return Collections.singletonList(new Object[] { + booleanVal, byteVal, shortVal, intVal, longVal, floatVal, doubleVal, charVal + }); + } + } + + /** */ + public static class CustomTypeFunctionsLibrary { + /** */ + @QuerySqlFunction + public static Employer customTypeEcho(Employer obj) { + return obj; + } + + /** */ + @QuerySqlTableFunction(columnTypes = {Employer.class}, columnNames = {"OBJ"}) + public static Iterable customTypeTable(Employer obj) { + return Collections.singletonList(new Object[] {obj}); + } + + /** */ + @QuerySqlTableFunction( + columnTypes = { + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class, + Object.class + }, + columnNames = { + "BYTES", + "BOOLEAN_VALUE", + "BYTE_VALUE", + "SHORT_VALUE", + "INT_VALUE", + "LONG_VALUE", + "FLOAT_VALUE", + "DOUBLE_VALUE", + "CHAR_VALUE", + "UTIL_DATE", + "SQL_DATE", + "SQL_TIME", + "SQL_TIMESTAMP", + "LOCAL_DATE", + "LOCAL_TIME", + "LOCAL_TIMESTAMP", + "DURATION_VALUE", + "PERIOD_VALUE", + "CUSTOM_VALUE", + "BINARY_OBJECT_VALUE" + } + ) + public static Iterable objectTableValues(BinaryObject binaryObj) { + return Collections.singletonList(objectValues(binaryObj)); + } + } + + /** */ + public static class TemporalFunctionsLibrary { + /** */ + @QuerySqlFunction + public static java.util.Date udfUtilDateValue() { + return new java.util.Date(Timestamp.valueOf("2020-01-14 01:02:03").getTime()); + } + + /** */ + @QuerySqlFunction + public static Date udfDateValue() { + return Date.valueOf("2021-01-15"); + } + + /** */ + @QuerySqlFunction + public static Time udfTimeValue() { + return Time.valueOf("02:03:04"); + } + + /** */ + @QuerySqlFunction + public static Timestamp udfTimestampValue() { + return Timestamp.valueOf("2021-01-15 02:03:04"); + } + + /** */ + @QuerySqlFunction + public static LocalDate udfLocalDateValue() { + return LocalDate.of(2022, 2, 16); + } + + /** */ + @QuerySqlFunction + public static LocalTime udfLocalTimeValue() { + return LocalTime.of(3, 4, 5); + } + + /** */ + @QuerySqlFunction + public static LocalDateTime udfLocalDateTimeValue() { + return LocalDateTime.of(2023, 3, 17, 4, 5, 6); + } + + /** */ + @QuerySqlFunction + public static Duration udfDurationValue() { + return Duration.ofDays(1).plusHours(2).plusMinutes(3); + } + + /** */ + @QuerySqlFunction + public static Period udfPeriodValue() { + return Period.of(1, 2, 0); + } + + /** */ + @QuerySqlFunction + public static boolean checkTemporalTypes( + java.util.Date utilDate, + Date date, + Time time, + Timestamp timestamp, + LocalDate localDate, + LocalTime localTime, + LocalDateTime localDateTime, + Duration duration, + Period period + ) { + return Arrays.equals(temporalValues(), new Object[] { + utilDate, date, time, timestamp, localDate, localTime, localDateTime, duration, period + }); + } + + /** */ + @QuerySqlTableFunction( + columnTypes = { + java.util.Date.class, + Date.class, + Time.class, + Timestamp.class, + LocalDate.class, + LocalTime.class, + LocalDateTime.class, + Duration.class, + Period.class + }, + columnNames = { + "UTIL_DATE", + "SQL_DATE", + "SQL_TIME", + "SQL_TIMESTAMP", + "LOCAL_DATE", + "LOCAL_TIME", + "LOCAL_TIMESTAMP", + "DURATION_VALUE", + "PERIOD_VALUE" + } + ) + public static Iterable temporalTable( + java.util.Date utilDate, + Date date, + Time time, + Timestamp timestamp, + LocalDate localDate, + LocalTime localTime, + LocalDateTime localDateTime, + Duration duration, + Period period + ) { + return Collections.singletonList(new Object[] { + utilDate, date, time, timestamp, localDate, localTime, localDateTime, duration, period + }); + } + } }