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

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


The following commit(s) were added to refs/heads/master by this push:
     new 0a3288a114a IGNITE-29045 SQL Calcite: Support Java temporal types in 
UDF and UDTF parameters and results (#13569)
0a3288a114a is described below

commit 0a3288a114a8682ec832bed7bd05ab51af4ad9eb
Author: Kirill Tkalenko <[email protected]>
AuthorDate: Fri Sep 25 17:47:49 2026 +0300

    IGNITE-29045 SQL Calcite: Support Java temporal types in UDF and UDTF 
parameters and results (#13569)
---
 .../query/calcite/exec/LogicalRelImplementor.java  |   4 +-
 .../query/calcite/exec/TableFunctionScan.java      |  59 +-
 .../query/calcite/exec/exp/ConverterUtils.java     |  55 +-
 .../calcite/exec/exp/IgniteFunctionParameter.java  |  54 ++
 .../exec/exp/IgniteReflectiveFunctionBase.java     |  14 +
 .../calcite/exec/exp/IgniteScalarFunction.java     |   5 +-
 .../exec/exp/ReflectiveCallNotNullImplementor.java |   8 +-
 .../query/calcite/prepare/IgniteTypeCoercion.java  |  31 +
 .../processors/query/calcite/type/OtherType.java   |  14 +-
 .../processors/query/calcite/util/TypeUtils.java   |  67 +-
 .../query/calcite/integration/DataTypesTest.java   |  20 +
 .../UserDefinedFunctionsIntegrationTest.java       | 677 +++++++++++++++++++++
 .../query/calcite/type/OtherTypeTest.java          |  68 +++
 .../query/calcite/util/TypeUtilsTest.java          | 141 +++++
 .../apache/ignite/testsuites/UtilTestSuite.java    |   4 +
 15 files changed, 1188 insertions(+), 33 deletions(-)

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 7b2172c33ac..ac39e7c6a4b 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 @@ public class LogicalRelImplementor<Row> implements 
IgniteRelVisitor<Node<Row>> {
 
         RelDataType rowType = rel.getRowType();
 
-        RowFactory<Row> 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 b29f91d6a7f..74d7e2cd298 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<Row> implements Iterable<Row> {
+    /** */
+    private final ExecutionContext<Row> ctx;
+
     /** */
     private final RelDataType rowType;
 
@@ -36,15 +46,22 @@ public class TableFunctionScan<Row> implements 
Iterable<Row> {
     /** */
     private final RowFactory<Row> rowFactory;
 
+    /** */
+    private final boolean hasConvertableFields;
+
     /** */
     public TableFunctionScan(
+        ExecutionContext<Row> ctx,
         RelDataType rowType,
-        Supplier<Iterable<?>> dataSupplier,
-        RowFactory<Row> rowFactory
+        Supplier<Iterable<?>> dataSupplier
     ) {
+        this.ctx = ctx;
         this.rowType = rowType;
         this.dataSupplier = dataSupplier;
-        this.rowFactory = rowFactory;
+
+        rowFactory = ctx.rowHandler().factory(ctx.getTypeFactory(), rowType);
+
+        hasConvertableFields = hasConvertableFields(ctx, rowType);
     }
 
     /** {@inheritDoc} */
@@ -66,6 +83,42 @@ public class TableFunctionScan<Row> implements Iterable<Row> 
{
                 + "] doesn't match defined columns number [" + 
rowType.getFieldCount() + "].");
         }
 
+        if (hasConvertableFields) {
+            if (rowContainer.getClass() == Object[].class)
+                rowArr = rowArr.clone();
+
+            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 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);
+    }
+
+    /** */
+    private static boolean hasConvertableFields(ExecutionContext<?> ctx, 
RelDataType rowType) {
+        return rowType.getFieldList().stream().anyMatch(field -> {
+            RelDataType type = field.getType();
+
+            return !(type instanceof OtherType) && type.getSqlTypeName() != 
SqlTypeName.OTHER
+                && 
TypeUtils.isConvertableType(ctx.getTypeFactory().getResultClass(type));
+        });
+    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ConverterUtils.java
index 3d61ad80048..8dc7224dcea 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
@@ -38,6 +38,8 @@ import org.apache.calcite.sql.type.SqlTypeUtil;
 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 {
@@ -53,6 +55,25 @@ public class ConverterUtils {
         return toInternal(operand, operand.getType(), targetType);
     }
 
+    /** Converts a user-defined function result to the internal representation 
using the execution context. */
+    static Expression toInternal(Expression root, Expression operand, Type 
targetType) {
+        Type fromType = operand.getType();
+
+        if (!TypeUtils.isConvertableType(fromType))
+            return operand;
+
+        // Preserve Calcite's calendar conversion for JDBC dates and 
timestamps.
+        Expression converted = toInternal(operand, targetType);
+
+        if (converted != operand)
+            return converted;
+
+        return Expressions.convert_(
+            Expressions.call(TypeUtils.class, "toInternal", root, operand, 
Expressions.constant(fromType)),
+            targetType
+        );
+    }
+
     /** */
     private static Expression toInternal(Expression operand,
         Type fromType, Type targetType) {
@@ -123,10 +144,18 @@ public class ConverterUtils {
     /** */
     static List<Expression> fromInternal(Class<?>[] targetTypes,
         List<Expression> expressions) {
-        final List<Expression> list = new ArrayList<>();
+        return fromInternal(null, targetTypes, expressions);
+    }
+
+    /** Converts user-defined function arguments using the execution context 
when available. */
+    static List<Expression> fromInternal(@Nullable Expression root,
+        Class<?>[] targetTypes,
+        List<Expression> expressions
+    ) {
+        final List<Expression> 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;
@@ -139,12 +168,32 @@ public class ConverterUtils {
                 else
                     type = targetTypes[j].getComponentType();
 
-                list.add(fromInternal(expressions.get(i), type));
+                list.add(fromInternal(root, expressions.get(i), type));
             }
         }
         return list;
     }
 
+    /** */
+    private static Expression fromInternal(@Nullable Expression root, 
Expression operand, Type targetType) {
+        if (Types.isAssignableFrom(targetType, operand.getType()))
+            return operand;
+
+        // Preserve Calcite's calendar conversion for JDBC dates and 
timestamps.
+        Expression converted = fromInternal(operand, targetType);
+
+        if (root == null || converted != operand || 
!TypeUtils.isConvertableType(targetType))
+            return converted;
+
+        if (Primitive.is(operand.getType()))
+            operand = Expressions.box(operand);
+
+        return Expressions.convert_(
+            Expressions.call(TypeUtils.class, "fromInternal", root, operand, 
Expressions.constant(targetType)),
+            targetType
+        );
+    }
+
     /** */
     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 00000000000..f311b192ffb
--- /dev/null
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/IgniteFunctionParameter.java
@@ -0,0 +1,54 @@
+/*
+ * 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 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;
+
+/** Function parameter that exposes its SQL type to validation. */
+class IgniteFunctionParameter implements FunctionParameter {
+    /** */
+    private final FunctionParameter delegate;
+
+    /** */
+    IgniteFunctionParameter(FunctionParameter delegate) {
+        this.delegate = delegate;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getOrdinal() {
+        return delegate.getOrdinal();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getName() {
+        return delegate.getName();
+    }
+
+    /** {@inheritDoc} */
+    @Override public RelDataType getType(RelDataTypeFactory typeFactory) {
+        // Normalize UDF metadata without losing Java types used to convert 
query results.
+        return 
((JavaTypeFactory)typeFactory).toSql(delegate.getType(typeFactory));
+    }
+
+    /** {@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 1a5dcf8b045..6971197cb85 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,18 +17,32 @@
 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;
 
+import static java.util.stream.Collectors.toUnmodifiableList;
+
 /** A base for outer java-method functions. */
 abstract class IgniteReflectiveFunctionBase extends ReflectiveFunctionBase 
implements ImplementableFunction {
     /** */
     protected final CallImplementor implementor;
 
+    /** */
+    private final List<FunctionParameter> params;
+
     /** */
     protected IgniteReflectiveFunctionBase(Method method, CallImplementor 
implementor) {
         super(method);
 
         this.implementor = implementor;
+
+        params = 
super.getParameters().stream().map(IgniteFunctionParameter::new).collect(toUnmodifiableList());
+    }
+
+    /** {@inheritDoc} */
+    @Override public List<FunctionParameter> getParameters() {
+        return params;
     }
 
     /** {@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 09f377e3560..39cfb5afa6c 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 @@ package 
org.apache.ignite.internal.processors.query.calcite.exec.exp;
 
 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 class IgniteScalarFunction extends 
IgniteReflectiveFunctionBase implement
 
     /** {@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/ReflectiveCallNotNullImplementor.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/exec/exp/ReflectiveCallNotNullImplementor.java
index 0f8958c4e5a..fee01452d5e 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
@@ -49,10 +49,10 @@ public class ReflectiveCallNotNullImplementor implements 
NotNullImplementor {
     @Override public Expression implement(RexToLixTranslator translator,
         RexCall call, List<Expression> 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 +66,10 @@ public class ReflectiveCallNotNullImplementor implements 
NotNullImplementor {
 
             callExpr = Expressions.call(target, method, translatedOperands);
         }
+
+        callExpr = ConverterUtils.toInternal(translator.getRoot(), callExpr,
+            translator.typeFactory.getJavaClass(call.getType()));
+
         if (!containsCheckedException(method))
             return callExpr;
 
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteTypeCoercion.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteTypeCoercion.java
index 96eb4233f1b..6e5924f7607 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteTypeCoercion.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/IgniteTypeCoercion.java
@@ -19,6 +19,7 @@ package 
org.apache.ignite.internal.processors.query.calcite.prepare;
 
 import java.nio.charset.Charset;
 import java.util.Arrays;
+import java.util.List;
 import org.apache.calcite.adapter.java.JavaTypeFactory;
 import org.apache.calcite.rel.type.DynamicRecordType;
 import org.apache.calcite.rel.type.RelDataType;
@@ -29,6 +30,7 @@ import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCallBinding;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlDataTypeSpec;
+import org.apache.calcite.sql.SqlFunction;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
@@ -37,6 +39,7 @@ import org.apache.calcite.sql.SqlTypeNameSpec;
 import org.apache.calcite.sql.SqlUserDefinedTypeNameSpec;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.type.SqlOperandMetadata;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
@@ -59,6 +62,34 @@ public class IgniteTypeCoercion extends TypeCoercionImpl {
         super(typeFactory, validator);
     }
 
+    /** {@inheritDoc} */
+    @Override public boolean userDefinedFunctionCoercion(SqlValidatorScope 
scope, SqlCall call, SqlFunction function) {
+        SqlOperandMetadata metadata = 
(SqlOperandMetadata)function.getOperandTypeChecker();
+        List<RelDataType> paramTypes = metadata.paramTypes(factory);
+
+        for (int i = 0; i < call.operandCount(); i++) {
+            SqlNode operand = call.operand(i);
+            int paramIdx = i;
+
+            if (operand.getKind() == SqlKind.ARGUMENT_ASSIGNMENT) {
+                SqlCall assignment = (SqlCall)operand;
+
+                paramIdx = 
metadata.paramNames().indexOf(((SqlIdentifier)assignment.operand(1)).getSimple());
+                operand = assignment.operand(0);
+
+                if (paramIdx < 0)
+                    return false;
+            }
+
+            // Numeric-to-timestamp casts are supported explicitly, but are 
not temporal UDF arguments.
+            if (SqlTypeUtil.isDatetime(paramTypes.get(paramIdx))
+                && SqlTypeUtil.isNumeric(validator.deriveType(scope, operand)))
+                return false;
+        }
+
+        return super.userDefinedFunctionCoercion(scope, call, function);
+    }
+
     /** {@inheritDoc} **/
     @Override public boolean binaryComparisonCoercion(SqlCallBinding binding) {
         // Although it is not reflected in the docs, this method is also 
invoked for MAX, MIN (and other similar operators)
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/OtherType.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/OtherType.java
index 1a87a117ccd..7ac9aa55053 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/OtherType.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/type/OtherType.java
@@ -18,7 +18,6 @@
 package org.apache.ignite.internal.processors.query.calcite.type;
 
 import java.lang.reflect.Type;
-import org.jetbrains.annotations.Nullable;
 
 /** OTHER SQL type for any value. */
 public class OtherType extends IgniteCustomType {
@@ -29,21 +28,12 @@ public class OtherType extends IgniteCustomType {
 
     /** {@inheritDoc} */
     @Override protected void generateTypeString(StringBuilder sb, boolean 
withDetail) {
-        sb.append("OTHER");
+        // The digest must differ from Calcite's OTHER to keep the types 
distinct in its shared type cache.
+        sb.append(withDetail ? "IGNITE_OTHER" : "OTHER");
     }
 
     /** @return Storage type */
     @Override public Type storageType() {
         return Object.class;
     }
-
-    /** {@inheritDoc} */
-    @Override public boolean equals(@Nullable Object obj) {
-        // Digest is the same for built-in Calcite's OTHER type, make sure we 
get instance of correct class during
-        // canonization.
-        if (obj == null || obj.getClass() != getClass())
-            return false;
-
-        return super.equals(obj);
-    }
 }
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtils.java
index 6056f2bbc7c..4c75d7113e8 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
@@ -30,8 +30,11 @@ import java.time.LocalTime;
 import java.time.Period;
 import java.time.ZoneOffset;
 import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import java.util.TimeZone;
 import java.util.UUID;
@@ -65,7 +68,6 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.TimeString;
 import org.apache.calcite.util.TimestampString;
 import org.apache.ignite.IgniteException;
-import 
org.apache.ignite.internal.cache.query.index.sorted.inline.types.DateValueUtils;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext;
 import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler;
@@ -83,6 +85,14 @@ import static 
org.apache.ignite.internal.processors.query.calcite.util.Commons.t
 
 /** */
 public class TypeUtils {
+    /**
+     * Cutover (1582-10-15) for the calendar used by {@link java.sql.Date} and 
{@link Timestamp}.
+     * These classes interpret earlier dates using the Julian calendar; {@link 
LocalDate}, {@link LocalDateTime}
+     * and Calcite use the proleptic Gregorian calendar. Conversions must 
preserve the calendar date.
+     */
+    private static final long GREGORIAN_CUTOVER =
+        LocalDate.of(1582, 10, 15).toEpochDay() * DateTimeUtils.MILLIS_PER_DAY;
+
     /** */
     private static final Set<Type> CONVERTABLE_TYPES = ImmutableSet.of(
         java.util.Date.class,
@@ -409,23 +419,42 @@ public class TypeUtils {
      * @return Millis value.
      */
     private static long toLong(DataContext ctx, Object val) {
+        // Java time values have no time zone and use the proleptic Gregorian 
calendar, as does Calcite.
         if (val instanceof LocalDateTime)
-            return 
toLong(DateValueUtils.convertToTimestamp((LocalDateTime)val), 
DataContext.Variable.TIME_ZONE.get(ctx));
+            return 
((LocalDateTime)val).toInstant(ZoneOffset.UTC).toEpochMilli();
 
         if (val instanceof LocalDate)
-            return toLong(DateValueUtils.convertToSqlDate((LocalDate)val), 
DataContext.Variable.TIME_ZONE.get(ctx));
+            return ((LocalDate)val).toEpochDay() * 
DateTimeUtils.MILLIS_PER_DAY;
 
         if (val instanceof LocalTime)
-            return toLong(DateValueUtils.convertToSqlTime((LocalTime)val), 
DataContext.Variable.TIME_ZONE.get(ctx));
+            return 
TimeUnit.NANOSECONDS.toMillis(((LocalTime)val).toNanoOfDay());
 
-        return toLong((java.util.Date)val, 
DataContext.Variable.TIME_ZONE.get(ctx));
+        return toLong((java.util.Date)val, timeZone(ctx));
     }
 
     /** */
     private static long toLong(java.util.Date val, TimeZone tz) {
         long time = val.getTime();
+        long locTs = time + tz.getOffset(time);
+
+        // Optimization: skip calendar conversion when both calendars agree.
+        if (locTs >= GREGORIAN_CUTOVER)
+            return locTs;
+
+        // java.sql.Date and Timestamp use the Julian calendar before the 
cutover, while Calcite uses the
+        // proleptic Gregorian calendar. Using only the time-zone-adjusted 
millis would turn a java.sql.Date
+        // representing 1500-01-02 into SQL DATE '1500-01-11'. Preserve its 
calendar fields instead.
+        Calendar cal = new GregorianCalendar(DateTimeUtils.UTC_ZONE, 
Locale.ROOT);
 
-        return time + tz.getOffset(time);
+        cal.setTimeInMillis(locTs);
+
+        int year = cal.get(Calendar.YEAR);
+
+        if (cal.get(Calendar.ERA) == GregorianCalendar.BC)
+            year = 1 - year;
+
+        return LocalDate.of(year, cal.get(Calendar.MONTH) + 1, 
cal.get(Calendar.DAY_OF_MONTH)).toEpochDay()
+            * DateTimeUtils.MILLIS_PER_DAY + Math.floorMod(locTs, 
DateTimeUtils.MILLIS_PER_DAY);
     }
 
     /** */
@@ -435,7 +464,7 @@ public class TypeUtils {
         else if (storageType == java.sql.Date.class && val instanceof Integer)
             return new java.sql.Date(fromLocalTs(ctx, (Integer)val * 
DateTimeUtils.MILLIS_PER_DAY));
         else if (storageType == LocalDate.class && val instanceof Integer)
-            return new java.sql.Date(fromLocalTs(ctx, (Integer)val * 
DateTimeUtils.MILLIS_PER_DAY)).toLocalDate();
+            return LocalDate.ofEpochDay((Integer)val);
         else if (storageType == java.sql.Time.class && val instanceof Integer)
             return new java.sql.Time(fromLocalTs(ctx, (Integer)val));
         else if (storageType == LocalTime.class && val instanceof Integer)
@@ -443,7 +472,7 @@ public class TypeUtils {
         else if (storageType == Timestamp.class && val instanceof Long)
             return new Timestamp(fromLocalTs(ctx, (Long)val));
         else if (storageType == LocalDateTime.class && val instanceof Long)
-            return new Timestamp(fromLocalTs(ctx, 
(Long)val)).toLocalDateTime();
+            return LocalDateTime.ofInstant(Instant.ofEpochMilli((Long)val), 
ZoneOffset.UTC);
         else if (storageType == java.util.Date.class && val instanceof Long)
             return new java.util.Date(fromLocalTs(ctx, (Long)val));
         else if (storageType == Duration.class && val instanceof Long)
@@ -514,7 +543,20 @@ public class TypeUtils {
 
     /** */
     private static long fromLocalTs(DataContext ctx, long ts) {
-        TimeZone tz = DataContext.Variable.TIME_ZONE.get(ctx);
+        if (ts < GREGORIAN_CUTOVER) {
+            LocalDate date = LocalDate.ofEpochDay(Math.floorDiv(ts, 
DateTimeUtils.MILLIS_PER_DAY));
+            Calendar cal = new GregorianCalendar(DateTimeUtils.UTC_ZONE, 
Locale.ROOT);
+
+            cal.clear();
+            cal.set(Calendar.ERA, date.getYear() > 0 ? GregorianCalendar.AD : 
GregorianCalendar.BC);
+            cal.set(date.getYear() > 0 ? date.getYear() : 1 - date.getYear(), 
date.getMonthValue() - 1,
+                date.getDayOfMonth());
+
+            // Reconstruct the date in the legacy Java calendar before 
applying the query's time zone.
+            ts = cal.getTimeInMillis() + Math.floorMod(ts, 
DateTimeUtils.MILLIS_PER_DAY);
+        }
+
+        TimeZone tz = timeZone(ctx);
 
         // Taking into account DST, offset can be changed after converting 
from UTC to time-zone.
         return ts - tz.getOffset(ts - tz.getOffset(ts));
@@ -528,4 +570,11 @@ public class TypeUtils {
 
         return rexBuilder.makeLiteral(dfltVal, type, true);
     }
+
+    /** */
+    private static TimeZone timeZone(DataContext ctx) {
+        TimeZone tz = DataContext.Variable.TIME_ZONE.get(ctx);
+
+        return tz != null ? tz : TimeZone.getDefault();
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
index e3f9d5e7cd9..5922949257b 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/DataTypesTest.java
@@ -199,6 +199,26 @@ public class DataTypesTest extends 
AbstractBasicIntegrationTransactionalTest {
             .check();
     }
 
+    /** Tests that implicit casts use the registered SQL name of OTHER. */
+    @Test
+    public void testOtherTypeImplicitCast() {
+        sql("CREATE TABLE t(id INT, oth OTHER) WITH " + atomicity());
+
+        sql("INSERT INTO t VALUES (1, 'str1')");
+        sql("INSERT INTO t VALUES (?, ?)", 2, "str2");
+
+        sql("INSERT INTO t SELECT 3, 42");
+        sql("INSERT INTO t SELECT ?, ?", 4, 69);
+
+        assertQuery("SELECT oth FROM t ORDER BY id")
+            .ordered()
+            .returns("str1")
+            .returns("str2")
+            .returns(42)
+            .returns(69)
+            .check();
+    }
+
     /** Tests UUID without index. */
     @Test
     public void testUuidWithoutIndex() {
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 e5f8e569c99..ed3654f13dc 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,11 +17,21 @@
 
 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.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
+import java.util.TimeZone;
 import java.util.stream.Collectors;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.sql.validate.SqlValidatorException;
@@ -37,11 +47,13 @@ import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.processors.query.calcite.QueryChecker;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.ListeningTestLogger;
 import org.apache.ignite.testframework.LogListener;
 import org.apache.ignite.testframework.junits.WithSystemProperty;
+import org.hamcrest.CoreMatchers;
 import org.junit.Test;
 
 import static 
org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor.IGNITE_CALCITE_USE_QUERY_BLOCKING_TASK_EXECUTOR;
@@ -492,6 +504,383 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
         assertQuery("SELECT udf.decimalToInt(5.3)").returns(5).check();
     }
 
+    /** */
+    @Test
+    public void testObjectTableFunctionResult() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("object-table-functions")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(CustomTypeFunctionsLibrary.class));
+
+        Object[] exp = temporalValues();
+
+        assertQuery("SELECT * FROM objectTableValues()")
+            .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());
+                    assertEquals("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(1, rows.get(0).size());
+                assertEquals(Date.class, rows.get(0).get(0).getClass());
+                assertEquals(Date.valueOf("2020-01-01"), rows.get(0).get(0));
+            })
+            .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 testHistoricalTemporalFunctions() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("historical-temporal-functions")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(TemporalFunctionsLibrary.class, 
DeterministicTemporalFunctionsLibrary.class));
+
+        assertQuery("SELECT detDateToStr(DATE '1500-01-02'), "
+            + "detTimestampToStr(TIMESTAMP '1500-01-02 03:04:05')")
+            .returns("1500-01-02", "1500-01-02 03:04:05.0")
+            .check();
+
+        assertQuery("SELECT CAST(udfDateFromString('1500-01-02') AS VARCHAR), "
+            + "EXTRACT(DAY FROM udfDateFromString('1500-01-02'))")
+            .returns("1500-01-02", 2L)
+            .check();
+
+        assertQuery("SELECT CAST(udfTimestampFromString('1500-01-02 03:04:05') 
AS VARCHAR), "
+            + "EXTRACT(DAY FROM udfTimestampFromString('1500-01-02 
03:04:05'))")
+            .returns("1500-01-02 03:04:05", 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();
+    }
+
+    /** */
+    @Test
+    public void testJavaTimeFunctionParametersWithSqlTypeValues() {
+        client.getOrCreateCache(new CacheConfiguration<>("java-time-params")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(JavaTimeParametersFunctionsLibrary.class));
+
+        assertQuery("SELECT localDateToStr(?)")
+            .withParams(Date.valueOf("2022-02-16"))
+            .returns("2022-02-16")
+            .check();
+
+        assertQuery("SELECT localTimeToStr(?)")
+            .withParams(Time.valueOf("03:04:05"))
+            .returns("03:04:05")
+            .check();
+
+        assertQuery("SELECT localDateTimeToStr(?)")
+            .withParams(Timestamp.valueOf("2023-03-17 04:05:06"))
+            .returns("2023-03-17T04:05:06")
+            .check();
+
+        // Control: the opposite direction already works for java.sql 
parameters.
+        assertQuery("SELECT sqlDateToStr(?)")
+            .withParams(LocalDate.of(2022, 2, 16))
+            .returns("2022-02-16")
+            .check();
+
+        // Incompatible values must be rejected by the validator.
+        assertThrows("SELECT sqlDateToStr(?)", SqlValidatorException.class,
+            "No match found for function signature SQLDATETOSTR(<NUMERIC>)", 
5);
+        assertThrows("SELECT localDateToStr(?)", SqlValidatorException.class,
+            "No match found for function signature LOCALDATETOSTR(<NUMERIC>)", 
5);
+        assertThrows("SELECT localTimeToStr(?)", SqlValidatorException.class,
+            "No match found for function signature LOCALTIMETOSTR(<NUMERIC>)", 
5);
+        assertThrows("SELECT localDateTimeToStr(?)", 
SqlValidatorException.class,
+            "No match found for function signature 
LOCALDATETIMETOSTR(<NUMERIC>)", 5);
+    }
+
+    /** */
+    @Test
+    public void testJavaTimeFunctionParametersAfterTableQuery() {
+        sql("CREATE TABLE type_warmup(i INT)");
+
+        // Resolving the table's hidden columns caches Ignite OTHER before 
Calcite infers UDF parameter types.
+        sql("SELECT * FROM type_warmup");
+
+        testJavaTimeFunctionParametersWithSqlTypeValues();
+    }
+
+    /** */
+    @Test
+    public void testHistoricalJavaTimeFunctions() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("historical-java-time-functions")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(JavaTimeParametersFunctionsLibrary.class));
+
+        checkJavaTimeFunctions("1500-01-02", "03:04:05");
+        checkJavaTimeFunctions("1582-10-04", "12:34:56");
+        checkJavaTimeFunctions("1582-10-15", "12:34:56");
+        checkJavaTimeFunctions("1969-12-31", "23:59:59");
+    }
+
+    /** */
+    @Test
+    public void testJavaTimeFunctionResultsAsJdbcValues() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("java-time-jdbc-results")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(JavaTimeParametersFunctionsLibrary.class));
+
+        for (String ts : new String[] {
+            "0001-01-01 00:00:00", "1500-01-02 03:04:05.123", "1582-10-04 
23:59:59.999",
+            "1582-10-15 00:00:00", "1969-12-31 23:59:59.999", "1970-01-01 
00:00:00", "2021-03-14 12:30:00.123"
+        }) {
+            LocalDateTime locTs = LocalDateTime.parse(ts.replace(' ', 'T'));
+            String date = locTs.toLocalDate().toString();
+            Date sqlDate = Date.valueOf(date);
+            Timestamp sqlTs = Timestamp.valueOf(ts);
+
+            // Check the JDBC values returned to the client, without 
converting them to strings inside SQL.
+            assertQuery("SELECT localDateFromStr('" + date + "'), 
localDateTimeFromStr('" + locTs + "')")
+                .returns(sqlDate, sqlTs)
+                .check();
+
+            assertQuery("SELECT d, ts FROM javaTimeValuesTable('" + date + "', 
'"
+                + locTs.toLocalTime() + "', '" + locTs + "')")
+                .returns(sqlDate, sqlTs)
+                .check();
+
+            // The same JDBC values must retain their calendar fields when 
passed back to SQL.
+            assertQuery("SELECT localDateToStr(?), localDateTimeToStr(?)")
+                .withParams(sqlDate, sqlTs)
+                .returns(date, locTs.toString())
+                .check();
+        }
+    }
+
+    /** */
+    @Test
+    public void testJavaTimeFunctionsDuringDstTransition() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("dst-java-time-functions")
+            .setSqlSchema("PUBLIC")
+            .setSqlFunctionClasses(JavaTimeParametersFunctionsLibrary.class));
+
+        // Initialize Calcite's cached default time zone before changing the 
JVM default.
+        checkJavaTimeFunctions("2021-01-01", "12:00:00");
+
+        TimeZone oldTz = TimeZone.getDefault();
+
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("America/New_York"));
+
+            checkJavaTimeFunctions("2021-03-14", "02:30:00");
+            checkJavaTimeFunctions("2021-11-07", "01:30:00");
+
+            TimeZone.setDefault(TimeZone.getTimeZone("Pacific/Apia"));
+
+            // This local date was skipped when the time zone moved across the 
date line.
+            checkJavaTimeFunctions("2011-12-30", "12:34:56");
+        }
+        finally {
+            TimeZone.setDefault(oldTz);
+        }
+    }
+
+    /** Checks Java time parameters and results independently, so opposite 
conversion errors cannot cancel out. */
+    private void checkJavaTimeFunctions(String date, String time) {
+        LocalDate locDate = LocalDate.parse(date);
+        LocalTime locTime = LocalTime.parse(time);
+        LocalDateTime locTs = LocalDateTime.of(locDate, locTime);
+        String ts = date + ' ' + time;
+        String literals = "DATE '" + date + "', TIME '" + time + "', TIMESTAMP 
'" + ts + '\'';
+
+        assertQuery("SELECT localDateToStr(DATE '" + date + "'), 
localTimeToStr(TIME '" + time + "'), "
+            + "localDateTimeToStr(TIMESTAMP '" + ts + "')")
+            .returns(date, locTime.toString(), locTs.toString())
+            .check();
+
+        assertQuery("SELECT localDateToStr(?), localTimeToStr(?), 
localDateTimeToStr(?)")
+            .withParams(locDate, locTime, locTs)
+            .returns(date, locTime.toString(), locTs.toString())
+            .check();
+
+        assertQuery("SELECT CAST(localDateFromStr('" + date + "') AS VARCHAR), 
"
+            + "CAST(localTimeFromStr('" + time + "') AS VARCHAR), "
+            + "CAST(localDateTimeFromStr('" + locTs + "') AS VARCHAR)")
+            .returns(date, time, ts)
+            .check();
+
+        assertQuery("SELECT * FROM javaTimeStringsTable(" + literals + ')')
+            .returns(date, locTime.toString(), locTs.toString())
+            .check();
+
+        assertQuery("SELECT * FROM javaTimeStringsTable(?, ?, ?)")
+            .withParams(locDate, locTime, locTs)
+            .returns(date, locTime.toString(), locTs.toString())
+            .check();
+
+        assertQuery("SELECT CAST(d AS VARCHAR), CAST(t AS VARCHAR), CAST(ts AS 
VARCHAR) "
+            + "FROM javaTimeValuesTable('" + date + "', '" + time + "', '" + 
locTs + "')")
+            .returns(date, time, ts)
+            .check();
+    }
+
+    /** */
+    @Test
+    public void testDeterministicTemporalFunctionReduced() {
+        client.getOrCreateCache(new 
CacheConfiguration<>("deterministic-temporal")
+            .setSqlSchema("PUBLIC")
+            
.setSqlFunctionClasses(DeterministicTemporalFunctionsLibrary.class));
+
+        sql("CREATE TABLE reduce_tbl (id INT PRIMARY KEY, val INT)");
+        sql("INSERT INTO reduce_tbl VALUES (1, 1), (2, 2)");
+
+        // Control: a non-temporal argument is reduced.
+        assertReduced("SELECT id FROM reduce_tbl WHERE detIntToStr(1) = '1'", 
"DETINTTOSTR");
+
+        assertReduced("SELECT id FROM reduce_tbl WHERE detDateToStr(DATE 
'2020-01-01') = '2020-01-01'", "DETDATETOSTR");
+        assertReduced("SELECT id FROM reduce_tbl WHERE detTimeToStr(TIME 
'02:03:04') = '02:03:04'", "DETTIMETOSTR");
+        assertReduced("SELECT id FROM reduce_tbl WHERE 
detTimestampToStr(TIMESTAMP '2021-01-15 02:03:04') = "
+            + "'2021-01-15 02:03:04.0'", "DETTIMESTAMPTOSTR");
+    }
+
+    /** Checks that the function call is not present in the plan (reduced to a 
constant) and the query result is correct. */
+    private void assertReduced(String sql, String fnName) {
+        assertQuery(sql)
+            .matches(CoreMatchers.not(QueryChecker.containsSubPlan(fnName)))
+            .returns(1)
+            .returns(2)
+            .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)
+        };
+    }
+
     /** */
     @SuppressWarnings("ThrowableNotThrown")
     private void assertThrows(String sql) {
@@ -914,4 +1303,292 @@ public class UserDefinedFunctionsIntegrationTest extends 
AbstractBasicIntegratio
         return LogListener.matches("Unable to register function '" + fun + "'. 
Other function " +
             "with the same name and parameters is already registered").build();
     }
+
+    /** */
+    public static class SerializableFunctionsLibrary {
+        /** */
+        @QuerySqlTableFunction(columnTypes = {Serializable.class}, columnNames 
= {"D"})
+        public static Iterable<Object[]> serializableTableValues() {
+            return Collections.singletonList(new Object[] 
{Date.valueOf("2020-01-01")});
+        }
+    }
+
+    /** */
+    public static class CustomTypeFunctionsLibrary {
+        /** */
+        @QuerySqlTableFunction(
+            columnTypes = {
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class,
+                Object.class
+            },
+            columnNames = {
+                "UTIL_DATE",
+                "SQL_DATE",
+                "SQL_TIME",
+                "SQL_TIMESTAMP",
+                "LOCAL_DATE",
+                "LOCAL_TIME",
+                "LOCAL_TIMESTAMP",
+                "DURATION_VALUE",
+                "PERIOD_VALUE"
+            }
+        )
+        public static Iterable<Object[]> objectTableValues() {
+            return Collections.singletonList(temporalValues());
+        }
+    }
+
+    /** */
+    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<Object[]> 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 Date udfDateFromString(String val) {
+            return Date.valueOf(val);
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static Timestamp udfTimestampFromString(String val) {
+            return Timestamp.valueOf(val);
+        }
+
+        /** */
+        @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<Object[]> 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
+            });
+        }
+    }
+
+    /** */
+    public static class JavaTimeParametersFunctionsLibrary {
+        /** */
+        @QuerySqlFunction
+        public static LocalDate localDateFromStr(String val) {
+            return LocalDate.parse(val);
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static LocalTime localTimeFromStr(String val) {
+            return LocalTime.parse(val);
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static LocalDateTime localDateTimeFromStr(String val) {
+            return LocalDateTime.parse(val);
+        }
+
+        /** */
+        @QuerySqlTableFunction(columnTypes = {String.class, String.class, 
String.class}, columnNames = {"D", "T", "TS"})
+        public static Iterable<Object[]> javaTimeStringsTable(LocalDate date, 
LocalTime time, LocalDateTime ts) {
+            return Collections.singletonList(new Object[] {date.toString(), 
time.toString(), ts.toString()});
+        }
+
+        /** */
+        @QuerySqlTableFunction(
+            columnTypes = {LocalDate.class, LocalTime.class, 
LocalDateTime.class},
+            columnNames = {"D", "T", "TS"}
+        )
+        public static Iterable<Object[]> javaTimeValuesTable(String date, 
String time, String ts) {
+            return Collections.singletonList(new Object[] {
+                LocalDate.parse(date), LocalTime.parse(time), 
LocalDateTime.parse(ts)
+            });
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static String localDateToStr(LocalDate val) {
+            return val.toString();
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static String localTimeToStr(LocalTime val) {
+            return val.toString();
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static String localDateTimeToStr(LocalDateTime val) {
+            return val.toString();
+        }
+
+        /** */
+        @QuerySqlFunction
+        public static String sqlDateToStr(Date val) {
+            return val.toString();
+        }
+    }
+
+    /** */
+    public static class DeterministicTemporalFunctionsLibrary {
+        /** */
+        @QuerySqlFunction(deterministic = true)
+        public static String detIntToStr(int val) {
+            return String.valueOf(val);
+        }
+
+        /** */
+        @QuerySqlFunction(deterministic = true)
+        public static String detDateToStr(Date val) {
+            return val.toString();
+        }
+
+        /** */
+        @QuerySqlFunction(deterministic = true)
+        public static String detTimeToStr(Time val) {
+            return val.toString();
+        }
+
+        /** */
+        @QuerySqlFunction(deterministic = true)
+        public static String detTimestampToStr(Timestamp val) {
+            return val.toString();
+        }
+    }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java
new file mode 100644
index 00000000000..0ce072253e1
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/type/OtherTypeTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.type;
+
+import org.apache.calcite.jdbc.JavaTypeFactoryImpl;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.sql.type.BasicSqlType;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertSame;
+
+/** */
+public class OtherTypeTest {
+    /** */
+    @Test
+    public void testEquality() {
+        for (boolean nullable : new boolean[] {false, true}) {
+            RelDataType igniteType = new OtherType(nullable);
+            RelDataType calciteType = new 
BasicSqlType(IgniteTypeSystem.INSTANCE, SqlTypeName.OTHER)
+                .createWithNullability(nullable);
+
+            assertNotEquals(igniteType, calciteType);
+            assertNotEquals(calciteType, igniteType);
+            assertEquals(igniteType, new OtherType(nullable));
+            assertNotEquals(igniteType, new OtherType(!nullable));
+            assertEquals("OTHER", igniteType.toString());
+        }
+    }
+
+    /** */
+    @Test
+    public void testTypeInterning() {
+        IgniteTypeFactory igniteFactory = new IgniteTypeFactory();
+        JavaTypeFactoryImpl calciteFactory = new JavaTypeFactoryImpl();
+
+        for (boolean nullable : new boolean[] {false, true}) {
+            RelDataType igniteType = 
igniteFactory.createCustomType(Object.class, nullable);
+            RelDataType calciteType = calciteFactory.createTypeWithNullability(
+                calciteFactory.createSqlType(SqlTypeName.OTHER), nullable);
+
+            assertEquals(OtherType.class, igniteType.getClass());
+            assertEquals(BasicSqlType.class, calciteType.getClass());
+            assertEquals(nullable, igniteType.isNullable());
+            assertEquals(nullable, calciteType.isNullable());
+            assertSame(igniteType, 
igniteFactory.createCustomType(Object.class, nullable));
+            assertSame(calciteType, calciteFactory.createTypeWithNullability(
+                calciteFactory.createSqlType(SqlTypeName.OTHER), nullable));
+        }
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtilsTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtilsTest.java
new file mode 100644
index 00000000000..635df8da510
--- /dev/null
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/util/TypeUtilsTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Type;
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import org.apache.calcite.DataContext;
+import org.apache.calcite.DataContexts;
+import org.apache.calcite.util.DateString;
+import org.apache.calcite.util.TimeString;
+import org.apache.calcite.util.TimestampString;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/** */
+public class TypeUtilsTest {
+    /** */
+    @Test
+    public void testLocalDateConversion() {
+        for (String date : new String[] {
+            "0001-01-01", "1500-01-02", "1582-10-10", "1969-12-31", 
"1970-01-01", "2011-12-30", "9999-12-31"
+        })
+            checkConversion(LocalDate.parse(date), new 
DateString(date).getDaysSinceEpoch(), Date.class);
+    }
+
+    /** */
+    @Test
+    public void testLocalTimeConversion() {
+        for (String time : new String[] {"00:00:00", "02:30:00", 
"12:34:56.123", "23:59:59.999"})
+            checkConversion(LocalTime.parse(time), new 
TimeString(time).getMillisOfDay(), Time.class);
+    }
+
+    /** */
+    @Test
+    public void testLocalDateTimeConversion() {
+        for (String ts : new String[] {
+            "0001-01-01 00:00:00", "1500-01-02 03:04:05", "1582-10-10 
12:34:56", "1969-12-31 23:59:59.999",
+            "1970-01-01 00:00:00", "2011-12-30 12:34:56", "2021-03-14 
02:30:00.123", "2021-11-07 01:30:00.123"
+        }) {
+            checkConversion(LocalDateTime.parse(ts.replace(' ', 'T')), new 
TimestampString(ts).getMillisSinceEpoch(),
+                Timestamp.class);
+        }
+    }
+
+    /** */
+    @Test
+    public void testSqlDateConversion() {
+        for (String date : new String[] {
+            "0001-01-01", "1500-01-02", "1582-10-04", "1582-10-15", 
"1969-12-31", "1970-01-01", "9999-12-31"
+        })
+            checkConversion(DataContexts.EMPTY, Date.valueOf(date), new 
DateString(date).getDaysSinceEpoch(), Date.class);
+    }
+
+    /** */
+    @Test
+    public void testSqlTimestampConversion() {
+        for (String ts : new String[] {
+            "0001-01-01 00:00:00", "1500-01-02 03:04:05.123", "1582-10-04 
23:59:59.999",
+            "1582-10-15 00:00:00", "1969-12-31 23:59:59.999", "1970-01-01 
00:00:00", "2021-03-14 12:30:00.123"
+        }) {
+            Timestamp val = Timestamp.valueOf(ts);
+            long internal = new TimestampString(ts).getMillisSinceEpoch();
+
+            checkConversion(DataContexts.EMPTY, val, internal, 
Timestamp.class);
+            checkConversion(DataContexts.EMPTY, new 
java.util.Date(val.getTime()), internal, java.util.Date.class);
+        }
+    }
+
+    /** */
+    @Test
+    public void testHistoricalJdbcConversionWithTimeZone() {
+        for (String zone : new String[] {"UTC", "Europe/Moscow", 
"America/New_York", "Pacific/Apia"}) {
+            TimeZone tz = TimeZone.getTimeZone(zone);
+            DataContext ctx = 
DataContexts.of(Collections.singletonMap(DataContext.Variable.TIME_ZONE.camelName,
 tz));
+            Calendar cal = new GregorianCalendar(tz, Locale.ROOT);
+
+            cal.clear();
+            cal.set(1500, Calendar.JANUARY, 2);
+
+            checkConversion(ctx, new Date(cal.getTimeInMillis()), new 
DateString("1500-01-02").getDaysSinceEpoch(),
+                Date.class);
+
+            cal.set(1500, Calendar.JANUARY, 2, 3, 4, 5);
+            cal.set(Calendar.MILLISECOND, 123);
+
+            long internal = new TimestampString("1500-01-02 
03:04:05.123").getMillisSinceEpoch();
+
+            checkConversion(ctx, new Timestamp(cal.getTimeInMillis()), 
internal, Timestamp.class);
+            checkConversion(ctx, new java.util.Date(cal.getTimeInMillis()), 
internal, java.util.Date.class);
+        }
+    }
+
+    /** */
+    private void checkConversion(Object val, Object internal, Type 
sqlJavaType) {
+        // Constant reduction has no time zone in its data context.
+        checkConversion(DataContexts.EMPTY, val, internal, sqlJavaType);
+
+        for (String zone : new String[] {"UTC", "Europe/Moscow", 
"America/New_York", "Pacific/Apia"}) {
+            DataContext ctx = DataContexts.of(Collections.singletonMap(
+                DataContext.Variable.TIME_ZONE.camelName, 
TimeZone.getTimeZone(zone)));
+
+            checkConversion(ctx, val, internal, sqlJavaType);
+        }
+    }
+
+    /** */
+    private void checkConversion(DataContext ctx, Object val, Object internal, 
Type sqlJavaType) {
+        assertEquals(internal, TypeUtils.toInternal(ctx, val));
+        // Table functions and dynamic parameters may use the corresponding 
JDBC class as the storage type.
+        assertEquals(internal, TypeUtils.toInternal(ctx, val, sqlJavaType));
+        assertEquals(val, TypeUtils.fromInternal(ctx, internal, 
val.getClass()));
+        assertSame(val, TypeUtils.toInternal(ctx, val, Object.class));
+    }
+}
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/UtilTestSuite.java 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/UtilTestSuite.java
index 527f0240060..2a731dde3d3 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/testsuites/UtilTestSuite.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/testsuites/UtilTestSuite.java
@@ -24,6 +24,8 @@ import 
org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteSqlFun
 import 
org.apache.ignite.internal.processors.query.calcite.exec.task.QueryBlockingTaskExecutorTest;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.task.QueryTasksQueueTest;
 import 
org.apache.ignite.internal.processors.query.calcite.exec.tracker.MemoryTrackerTest;
+import org.apache.ignite.internal.processors.query.calcite.type.OtherTypeTest;
+import org.apache.ignite.internal.processors.query.calcite.util.TypeUtilsTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -39,6 +41,8 @@ import org.junit.runners.Suite;
     KeyFilteringCursorTest.class,
     QueryBlockingTaskExecutorTest.class,
     QueryTasksQueueTest.class,
+    OtherTypeTest.class,
+    TypeUtilsTest.class,
 })
 public class UtilTestSuite {
 }

Reply via email to