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 e2777240471c6..1d0ab5ad454eb 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 @@ -784,9 +784,7 @@ else if (rel instanceof Intersect) RelDataType rowType = rel.getRowType(); - 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)); } /** {@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 b29f91d6a7fe8..a653e2630e67d 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 @@ -17,16 +17,26 @@ package org.apache.ignite.internal.processors.query.calcite.exec; +import java.lang.reflect.Type; import java.util.Collection; import java.util.Iterator; import java.util.function.Supplier; +import org.apache.calcite.linq4j.tree.Primitive; +import org.apache.calcite.linq4j.tree.Types; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.type.SqlTypeName; 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; +import org.jetbrains.annotations.Nullable; /** */ public class TableFunctionScan implements Iterable { + /** */ + private final ExecutionContext ctx; + /** */ private final RelDataType rowType; @@ -38,13 +48,15 @@ public class TableFunctionScan implements Iterable { /** */ public TableFunctionScan( + ExecutionContext ctx, RelDataType rowType, - Supplier> dataSupplier, - RowFactory rowFactory + Supplier> dataSupplier ) { + this.ctx = ctx; this.rowType = rowType; this.dataSupplier = dataSupplier; - this.rowFactory = rowFactory; + + rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType); } /** {@inheritDoc} */ @@ -58,7 +70,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()) { @@ -66,6 +78,27 @@ private Row convertToRow(Object rowContainer) { + "] doesn't match defined columns number [" + rowType.getFieldCount() + "]."); } + for (int i = 0; i < rowArr.length; i++) + rowArr[i] = convertToInternal(rowArr[i], rowType.getFieldList().get(i).getType()); + return rowFactory.create(rowArr); } + + /** */ + private @Nullable Object convertToInternal(@Nullable Object val, RelDataType type) { + // Preserve objects for both Ignite's custom OTHER type and Calcite's SQL OTHER type. + if (val == null || type instanceof OtherType || type.getSqlTypeName() == SqlTypeName.OTHER) + return val; + + Type storageType = ctx.getTypeFactory().getResultClass(type); + + if (!TypeUtils.isConvertableType(storageType)) + return TypeUtils.toInternal(ctx, val); + + // SQL table functions can already return values in the internal representation. + if (Types.isAssignableFrom(Primitive.box(ctx.getTypeFactory().getJavaClass(type)), val.getClass())) + return val; + + return TypeUtils.toInternal(ctx, val, storageType); + } } 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..0ed5ea1590a32 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; @@ -38,6 +39,8 @@ 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; +import org.jetbrains.annotations.Nullable; /** */ public class ConverterUtils { @@ -80,11 +83,6 @@ else if (targetType == Long.class) /** Converts from internal representation to JDBC representation used by * arguments of user-defined functions. For example, converts date values from * {@code int} to {@link java.sql.Date}. */ - private static Expression fromInternal(Expression operand, Type targetType) { - return fromInternal(operand, operand.getType(), targetType); - } - - /** */ private static Expression fromInternal(Expression operand, Type fromType, Type targetType) { if (operand == ConstantUntypedNull.INSTANCE) @@ -111,6 +109,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". @@ -123,28 +124,73 @@ else if (targetType == java.sql.Timestamp.class) { /** */ static List fromInternal(Class[] targetTypes, List expressions) { - final List list = new ArrayList<>(); + return fromInternal(null, targetTypes, expressions); + } + + /** */ + static List fromInternal(@Nullable Expression root, + Class[] targetTypes, + List expressions + ) { + final List list = new ArrayList<>(expressions.size()); + if (targetTypes.length == expressions.size()) { for (int i = 0; i < expressions.size(); i++) - list.add(fromInternal(expressions.get(i), targetTypes[i])); + list.add(fromInternal(root, expressions.get(i), targetTypes[i])); } else { int j = 0; - for (int i = 0; i < expressions.size(); i++) { - Class type; + + for (Expression expression : expressions) { + Class targetType; + if (!targetTypes[j].isArray()) { - type = targetTypes[j]; + targetType = targetTypes[j]; j++; } else - type = targetTypes[j].getComponentType(); + targetType = targetTypes[j].getComponentType(); - list.add(fromInternal(expressions.get(i), type)); + list.add(fromInternal(root, expression, targetType)); } } + return list; } + /** */ + private static Expression fromInternal(@Nullable Expression root, Expression operand, Type targetType) { + // Preserve Calcite conversions when no execution context is available, including temporal conversions. + if (root == null) + return fromInternal(operand, operand.getType(), targetType); + + // Let the Java method call box compatible primitives instead of generating a reference cast. + if (Types.isAssignableFrom(targetType, operand.getType()) + || Types.isAssignableFrom(targetType, Primitive.box(operand.getType()))) + return operand; + + if (!TypeUtils.isConvertableType(targetType)) + return targetType == BigDecimal.class ? fromInternal(operand, operand.getType(), targetType) : + convert(operand, operand.getType(), targetType); + + if (Primitive.is(operand.getType())) + operand = Expressions.box(operand); + + Expression converted = Expressions.call( + TypeUtils.class, + "fromInternal", + root, + 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); @@ -230,6 +276,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/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..f673e2acc4fd2 --- /dev/null +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java @@ -0,0 +1,75 @@ +/* + * 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; + + /** */ + 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/IgniteReflectiveFunctionBase.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteReflectiveFunctionBase.java index 1a5dcf8b04578..d75f66af40e0e 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteReflectiveFunctionBase.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteReflectiveFunctionBase.java @@ -17,6 +17,8 @@ package org.apache.ignite.internal.processors.query.calcite.exec.exp; import java.lang.reflect.Method; +import java.util.List; +import org.apache.calcite.schema.FunctionParameter; import org.apache.calcite.schema.impl.ReflectiveFunctionBase; /** A base for outer java-method functions. */ @@ -24,11 +26,21 @@ abstract class IgniteReflectiveFunctionBase extends ReflectiveFunctionBase imple /** */ protected final CallImplementor implementor; + /** */ + private final List funcParams; + /** */ protected IgniteReflectiveFunctionBase(Method method, CallImplementor implementor) { super(method); this.implementor = implementor; + + funcParams = IgniteFunctionParameter.toSql(super.getParameters()); + } + + /** {@inheritDoc} */ + @Override public List getParameters() { + return funcParams; } /** {@inheritDoc} */ 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..39cfb5afa6cde 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 @@ -18,6 +18,7 @@ import java.lang.reflect.Method; 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.ScalarFunction; @@ -54,7 +55,9 @@ 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())); } /** 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..1cd7b2cce3aa0 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 @@ -21,6 +21,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.calcite.adapter.enumerable.NullPolicy; @@ -80,7 +81,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 -> columnType(tf, cl)).collect(Collectors.toList()); return typeFactory.createStructType(converted, colNames); } @@ -100,6 +101,15 @@ public static IgniteTableFunction create(Method method, Class[] colTypes, Str return Iterable.class; } + /** Resolves collection types without treating user-defined classes as records. */ + private static RelDataType columnType(JavaTypeFactory tf, Class cls) { + RelDataType type = cls.isArray() || List.class.isAssignableFrom(cls) || Map.class.isAssignableFrom(cls) + ? tf.createType(cls) + : tf.createJavaType(cls); + + return tf.toSql(type); + } + /** 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)) 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..cb68d23b4d67a 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.getRoot(), 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,18 @@ 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, + Expressions.constant(method.getReturnType())), + 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 6056f2bbc7c30..a7194e33fde82 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 @@ -355,12 +355,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) @@ -429,7 +429,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) diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java index 6ab1ea04565a9..effdb44eb1b3f 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/exec/rel/ExecutionTest.java @@ -17,18 +17,34 @@ package org.apache.ignite.internal.processors.query.calcite.exec.rel; +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.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; import com.google.common.collect.ImmutableSet; import org.apache.calcite.rel.core.CorrelationId; import org.apache.calcite.rel.core.JoinRelType; import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFieldImpl; +import org.apache.calcite.rel.type.RelRecordType; +import org.apache.calcite.sql.type.BasicSqlType; +import org.apache.calcite.sql.type.SqlTypeName; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler; +import org.apache.ignite.internal.processors.query.calcite.exec.TableFunctionScan; import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory; +import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeSystem; +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; import org.apache.ignite.internal.util.typedef.internal.U; @@ -63,6 +79,51 @@ public class ExecutionTest extends AbstractExecutionTest { super.setup(); } + /** */ + @Test + public void testTableFunctionOtherValues() { + checkTableFunctionOtherValues(new BasicSqlType(IgniteTypeSystem.INSTANCE, SqlTypeName.OTHER)); + checkTableFunctionOtherValues(new OtherType(true)); + } + + /** */ + private void checkTableFunctionOtherValues(RelDataType colType) { + ExecutionContext ctx = executionContext(); + + // Bypass type canonization to test both representations of OTHER regardless of the shared type cache state. + RelDataType rowType = new RelRecordType(Collections.singletonList(new RelDataTypeFieldImpl("V", 0, colType))); + + Object[] vals = { + new byte[] {1, 2, 3}, + new java.util.Date(0), + Date.valueOf("2020-01-01"), + Time.valueOf("02:03:04"), + Timestamp.valueOf("2020-01-01 02:03:04"), + LocalDate.of(2020, 1, 1), + LocalTime.of(2, 3, 4), + LocalDateTime.of(2020, 1, 1, 2, 3, 4), + Duration.ofHours(2), + Period.ofMonths(3), + null + }; + + for (Object val : vals) { + Object[] row = {val}; + + for (Object container : new Object[] {row, Collections.singletonList(val)}) { + TableFunctionScan scan = new TableFunctionScan<>(ctx, rowType, + () -> Collections.singletonList(container)); + + Object[] res = scan.iterator().next(); + + assertNotSame(row, res); + assertEquals(1, res.length); + assertSame(val, res[0]); + assertSame(val, row[0]); + } + } + } + /** * */ diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java index 004580b915c45..5fa18d06b6d27 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java @@ -140,6 +140,16 @@ private IgniteConfiguration getConfiguration(String igniteInstanceName, SqlConfo OperatorsExtensionIntegrationTest.class.getMethod("toNumber", String.class), NullPolicy.STRICT ); + + RexImpTable.INSTANCE.defineReflective( + OperatorTable.REFLECTIVE_BINARY_LENGTH, + OperatorsExtensionIntegrationTest.class.getMethod("binaryLength", byte[].class) + ); + + RexImpTable.INSTANCE.defineReflective( + OperatorTable.REFLECTIVE_BINARY_VALUE, + OperatorsExtensionIntegrationTest.class.getMethod("binaryValue") + ); } catch (NoSuchMethodException e) { throw new RuntimeException(e); @@ -171,6 +181,25 @@ public void test() throws Exception { .returns(Timestamp.valueOf("2021-01-01 00:00:00")).check(); } + /** */ + @Test + public void testByteArrayFunctions() { + assertQuery("SELECT REFLECTIVE_BINARY_LENGTH(x'010203')").returns(3).check(); + assertQuery("SELECT REFLECTIVE_BINARY_LENGTH(?)").withParams((Object) new byte[] {1, 2, 3}).returns(3).check(); + assertQuery("SELECT REFLECTIVE_BINARY_LENGTH(REFLECTIVE_BINARY_VALUE())").returns(3).check(); + + assertQuery("SELECT OCTET_LENGTH(REFLECTIVE_BINARY_VALUE())").returns(3).check(); + + assertQuery("SELECT REFLECTIVE_BINARY_VALUE()") + .withResultChecker(rows -> { + assertEquals(1, rows.size()); + assertEquals(1, rows.get(0).size()); + assertEquals(byte[].class, rows.get(0).get(0).getClass()); + assertEqualsArraysAware(new byte[] {1, 2, 3}, rows.get(0).get(0)); + }) + .check(); + } + /** */ @Test public void testOperatorsCallsInViews() { @@ -346,6 +375,16 @@ public static BigDecimal toNumber(String s) { return new BigDecimal(s); } + /** Implementor for {@code REFLECTIVE_BINARY_LENGTH} function. */ + public static int binaryLength(byte[] bytes) { + return bytes.length; + } + + /** Implementor for {@code REFLECTIVE_BINARY_VALUE} function. */ + public static byte[] binaryValue() { + return new byte[] {1, 2, 3}; + } + /** Extended operator table. */ public static class OperatorTable extends ReflectiveSqlOperatorTable { /** */ @@ -393,6 +432,26 @@ public static class OperatorTable extends ReflectiveSqlOperatorTable { /** */ public static final SqlAggFunction TEST_COUNT_PAIRS = new SqlTestCountPairsAggFunction(); + + /** */ + public static final SqlFunction REFLECTIVE_BINARY_LENGTH = new SqlFunction( + "REFLECTIVE_BINARY_LENGTH", + SqlKind.OTHER_FUNCTION, + ReturnTypes.INTEGER_NULLABLE, + null, + OperandTypes.BINARY, + SqlFunctionCategory.USER_DEFINED_FUNCTION + ); + + /** */ + public static final SqlFunction REFLECTIVE_BINARY_VALUE = new SqlFunction( + "REFLECTIVE_BINARY_VALUE", + SqlKind.OTHER_FUNCTION, + opBinding -> opBinding.getTypeFactory().createSqlType(SqlTypeName.VARBINARY), + null, + OperandTypes.NILADIC, + SqlFunctionCategory.USER_DEFINED_FUNCTION + ); } /** Extended convertlet table. */ 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 e5f8e569c998e..7f7f99081fe82 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 @@ -17,17 +17,30 @@ package org.apache.ignite.internal.processors.query.calcite.integration; +import java.io.Serializable; 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.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; 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; 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; @@ -492,6 +505,375 @@ 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(); + } + + /** */ + @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}; + + assertQuery("SELECT checkPrimitiveTypes(?, ?, ?, ?, ?, ?, ?)") + .withParams(values) + .returns(true) + .check(); + + assertQuery("SELECT primitiveBoolean(), primitiveByte(), primitiveShort(), primitiveInt(), " + + "primitiveLong(), primitiveFloat(), primitiveDouble()") + .returns(true, (byte)1, (short)2, 3, 4L, 5.0f, 6.0d) + .check(); + + assertQuery("SELECT * FROM primitiveTable(?, ?, ?, ?, ?, ?, ?)") + .withParams(values) + .returns(true, (byte)1, (short)2, 3, 4L, 5.0f, 6.0d) + .check(); + } + + /** */ + @Test + public void testBoxedFunctionArguments() { + client.getOrCreateCache(new CacheConfiguration<>("boxed-argument-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(BoxedArgumentsFunctionsLibrary.class)); + + String[] literals = {"-1", "CAST(-1 AS BIGINT)", "CAST(-1 AS REAL)", "CAST(-1 AS DOUBLE)"}; + Object[] values = {-1, -1L, -1.0f, -1.0d}; + + for (int i = 0; i < literals.length; i++) { + String args = String.join(", ", Collections.nCopies(3, literals[i])); + Object val = values[i]; + + assertQuery("SELECT checkBoxedArguments(" + args + ")").returns(true).check(); + assertQuery("SELECT checkBoxedArguments(?, ?, ?)").withParams(val, val, val).returns(true).check(); + + assertQuery("SELECT * FROM boxedArgumentsTable(" + args + ")").returns(val, val, val).check(); + assertQuery("SELECT * FROM boxedArgumentsTable(?, ?, ?)") + .withParams(val, val, val) + .returns(val, val, val) + .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 testArrayListTableFunctionResult() { + client.getOrCreateCache(new CacheConfiguration<>("array-list-table-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(CollectionFunctionsLibrary.class)); + + assertQuery("SELECT val FROM arrayListTable()") + .returns(Arrays.asList(1, 2)) + .check(); + + assertQuery("SELECT CARDINALITY(val), val[1], val[2] FROM arrayListTable()") + .returns(2, 1, 2) + .check(); + } + + /** */ + @Test + public void testHashMapTableFunctionResult() { + client.getOrCreateCache(new CacheConfiguration<>("hash-map-table-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(CollectionFunctionsLibrary.class)); + + assertQuery("SELECT val FROM hashMapTable()") + .returns(F.asMap("first", 10, "second", 20)) + .check(); + + assertQuery("SELECT CARDINALITY(val), val['first'], val['second'], val['missing'] FROM hashMapTable()") + .returns(2, 10, 20, null) + .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 testSerializableTableFunctionResult() { + client.getOrCreateCache(new CacheConfiguration<>("serializable-table-functions") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(SerializableFunctionsLibrary.class)); + + assertQuery("SELECT * FROM serializableTableValues()") + .withResultChecker(rows -> { + assertEquals(1, rows.size()); + assertEquals(2, rows.get(0).size()); + assertEquals(Date.class, rows.get(0).get(0).getClass()); + assertEquals(Date.valueOf("2020-01-01"), rows.get(0).get(0)); + assertEquals(byte[].class, rows.get(0).get(1).getClass()); + assertEqualsArraysAware(new byte[] {1, 2, 3}, rows.get(0).get(1)); + }) + .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(); + } + + /** */ + @Test + public void testTemporalScalarFunctionResultSubtypes() { + client.getOrCreateCache(new CacheConfiguration<>("temporal-scalar-result-subtypes") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(TemporalFunctionsLibrary.class)); + + assertQuery("SELECT udfDateAsUtilDate()") + .returns(Timestamp.valueOf("2020-01-01 00:00:00")) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM udfDateAsUtilDate()), EXTRACT(HOUR FROM udfDateAsUtilDate())") + .returns(2020L, 0L) + .check(); + + assertQuery("SELECT udfTimeAsUtilDate()") + .returns(Timestamp.valueOf("1970-01-01 02:03:04")) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM udfTimeAsUtilDate()), EXTRACT(HOUR FROM udfTimeAsUtilDate())") + .returns(1970L, 2L) + .check(); + + assertQuery("SELECT udfTimestampAsUtilDate()") + .returns(Timestamp.valueOf("2021-01-15 03:04:05")) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM udfTimestampAsUtilDate()), " + + "EXTRACT(HOUR FROM udfTimestampAsUtilDate())") + .returns(2021L, 3L) + .check(); + + assertQuery("SELECT udfNullUtilDate()") + .returns(NULL_RESULT) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM udfNullUtilDate()), EXTRACT(HOUR FROM udfNullUtilDate())") + .returns(null, null) + .check(); + } + + /** */ + @Test + public void testTemporalTableFunctionResultSubtypes() { + client.getOrCreateCache(new CacheConfiguration<>("temporal-table-result-subtypes") + .setSqlSchema("PUBLIC") + .setSqlFunctionClasses(TemporalFunctionsLibrary.class)); + + assertQuery("SELECT d FROM utilDateSubtypeTable()") + .returns(Timestamp.valueOf("2020-01-01 00:00:00")) + .returns(Timestamp.valueOf("1970-01-01 02:03:04")) + .returns(Timestamp.valueOf("2021-01-15 03:04:05")) + .returns(NULL_RESULT) + .check(); + + assertQuery("SELECT EXTRACT(YEAR FROM d), EXTRACT(HOUR FROM d) FROM utilDateSubtypeTable()") + .returns(2020L, 0L) + .returns(1970L, 2L) + .returns(2021L, 3L) + .returns(null, null) + .check(); + } + + /** */ + private static java.util.Date[] temporalSubtypeValues() { + return new java.util.Date[] { + Date.valueOf("2020-01-01"), + Time.valueOf("02:03:04"), + Timestamp.valueOf("2021-01-15 03:04:05"), + null + }; + } + + /** */ + 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, + 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) { @@ -914,4 +1296,402 @@ private static LogListener createUnableRegisterFunctionLogListener(String fun) { return LogListener.matches("Unable to register function '" + fun + "'. Other function " + "with the same name and parameters is already registered").build(); } + + /** */ + 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}); + } + } + + /** */ + 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 + ) { + return booleanVal && byteVal == 1 && shortVal == 2 && intVal == 3 && longVal == 4 + && floatVal == 5.0f && doubleVal == 6.0d; + } + + /** */ + @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; + } + + /** */ + @QuerySqlTableFunction( + columnTypes = { + boolean.class, + byte.class, + short.class, + int.class, + long.class, + float.class, + double.class + }, + columnNames = { + "BOOLEAN_VALUE", + "BYTE_VALUE", + "SHORT_VALUE", + "INT_VALUE", + "LONG_VALUE", + "FLOAT_VALUE", + "DOUBLE_VALUE" + } + ) + public static Iterable primitiveTable( + boolean booleanVal, + byte byteVal, + short shortVal, + int intVal, + long longVal, + float floatVal, + double doubleVal + ) { + return Collections.singletonList(new Object[] { + booleanVal, byteVal, shortVal, intVal, longVal, floatVal, doubleVal + }); + } + } + + /** */ + public static class BoxedArgumentsFunctionsLibrary { + /** */ + @QuerySqlFunction + public static boolean checkBoxedArguments(Object obj, Number num, Serializable serializable) { + return obj.equals(num) && obj.equals(serializable); + } + + /** */ + @QuerySqlTableFunction(columnTypes = {Object.class, Object.class, Object.class}, columnNames = {"O", "N", "S"}) + public static Iterable boxedArgumentsTable(Object obj, Number num, Serializable serializable) { + return Collections.singletonList(new Object[] {obj, num, serializable}); + } + } + + /** */ + public static class SerializableFunctionsLibrary { + /** */ + @QuerySqlTableFunction(columnTypes = {Serializable.class, Serializable.class}, columnNames = {"D", "B"}) + public static Iterable serializableTableValues() { + return Collections.singletonList(new Object[] {Date.valueOf("2020-01-01"), new byte[] {1, 2, 3}}); + } + } + + /** */ + public static class CollectionFunctionsLibrary { + /** */ + @QuerySqlTableFunction(columnTypes = {ArrayList.class}, columnNames = {"VAL"}) + public static Iterable arrayListTable() { + return Collections.singletonList(new Object[] {new ArrayList<>(Arrays.asList(1, 2))}); + } + + /** */ + @QuerySqlTableFunction(columnTypes = {HashMap.class}, columnNames = {"VAL"}) + public static Iterable hashMapTable() { + return Collections.singletonList(new Object[] {new HashMap<>(F.asMap("first", 10, "second", 20))}); + } + } + + /** */ + 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 + }, + columnNames = { + "BYTES", + "BOOLEAN_VALUE", + "BYTE_VALUE", + "SHORT_VALUE", + "INT_VALUE", + "LONG_VALUE", + "FLOAT_VALUE", + "DOUBLE_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 udfDateAsUtilDate() { + return Date.valueOf("2020-01-01"); + } + + /** */ + @QuerySqlFunction + public static java.util.Date udfTimeAsUtilDate() { + return Time.valueOf("02:03:04"); + } + + /** */ + @QuerySqlFunction + public static java.util.Date udfTimestampAsUtilDate() { + return Timestamp.valueOf("2021-01-15 03:04:05"); + } + + /** */ + @QuerySqlFunction + public static java.util.Date udfNullUtilDate() { + return null; + } + + /** */ + @QuerySqlTableFunction(columnTypes = {java.util.Date.class}, columnNames = {"D"}) + public static Iterable utilDateSubtypeTable() { + return Arrays.stream(temporalSubtypeValues()).map(val -> new Object[] {val}).collect(Collectors.toList()); + } + + /** */ + @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 + }); + } + } }