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

alexpl 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 8998f078f55 IGNITE-16806 Fix SQL query property type validation 
failure in case of implicit convertible types - Fixes #9988.
8998f078f55 is described below

commit 8998f078f55fede320d62a5cd6df38f3f6285d1b
Author: Mikhail Petrov <pmgheap....@gmail.com>
AuthorDate: Thu May 12 15:21:01 2022 +0300

    IGNITE-16806 Fix SQL query property type validation failure in case of 
implicit convertible types - Fixes #9988.
    
    Signed-off-by: Aleksey Plekhanov <plehanov.a...@gmail.com>
---
 .../apache/ignite/internal/binary/BinaryUtils.java |   7 +
 .../processors/query/GridQueryIndexing.java        |  13 ++
 .../processors/query/QueryTypeDescriptorImpl.java  |  86 ++++-----
 .../processors/query/DummyQueryIndexing.java       |   5 +
 .../internal/processors/query/h2/H2Utils.java      |  41 ++++-
 .../processors/query/h2/IgniteH2Indexing.java      |  17 ++
 .../IgniteQueryConvertibleTypesValidationTest.java | 205 +++++++++++++++++++++
 .../h2/H2ColumnTypeConversionCheckSelfTest.java    |  57 ++++++
 .../IgniteBinaryCacheQueryTestSuite3.java          |   5 +
 9 files changed, 389 insertions(+), 47 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index f3203a627e1..60a00441737 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -2726,6 +2726,13 @@ public class BinaryUtils {
         return Object[].class == cls || BinaryArray.class == cls || 
BinaryEnumArray.class == cls;
     }
 
+    /** @return Type name of the specified object. If {@link BinaryObject} was 
specified its type will be returned. */
+    public static String typeName(Object obj) {
+        return obj instanceof BinaryObject
+            ? ((BinaryObject)obj).type().typeName()
+            : obj == null ? null : obj.getClass().getSimpleName();
+    }
+
     /**
      * Enum type.
      */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
index 3629f254eb4..64f09934139 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryIndexing.java
@@ -462,4 +462,17 @@ public interface GridQueryIndexing {
     default Map<String, Integer> secondaryIndexesInlineSize() {
         return Collections.emptyMap();
     }
+
+    /**
+     * Checks if object of the specified class can be stored in the specified 
table column by the query engine.
+     *
+     * @param schemaName Schema name.
+     * @param tblName Table name.
+     * @param colName Name of the column.
+     * @param cls Class to perform check on.
+     * @return Whether object of the specified class can be successfully 
stored and accessed from the SQL column by the
+     *         query engine.
+     * @throws IgniteSQLException if table or column with specified name was 
not found.
+     */
+    boolean isConvertibleToColumnType(String schemaName, String tblName, 
String colName, Class<?> cls);
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
index abe0fa475e2..bca1593bac2 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryTypeDescriptorImpl.java
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.jetbrains.annotations.Nullable;
 
+import static org.apache.ignite.internal.binary.BinaryUtils.typeName;
 import static 
org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.KEY_SCALE_OUT_OF_RANGE;
 import static 
org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.NULL_KEY;
 import static 
org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.NULL_VALUE;
@@ -636,27 +637,9 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
                     isKey ? NULL_KEY : NULL_VALUE);
             }
 
-            if (validateTypes && propVal != null) {
-                if (!(propVal instanceof BinaryObject) || propVal instanceof 
BinaryArray) {
-                    if 
(!U.box(prop.type()).isAssignableFrom(U.box(propVal.getClass()))) {
-                        // Some reference type arrays end up being converted 
to Object[]
-                        if (!(prop.type().isArray() && 
BinaryUtils.isObjectArray(propVal.getClass()) &&
-                            
Arrays.stream(BinaryUtils.rawArrayFromBinary(propVal)).
-                            noneMatch(x -> x != null && 
!U.box(prop.type().getComponentType()).isAssignableFrom(U.box(x.getClass()))))) 
{
-                            throw new IgniteSQLException("Type for a column '" 
+ prop.name() +
-                                "' is not compatible with table definition. 
Expected '" +
-                                prop.type().getSimpleName() + "', actual type 
'" +
-                                propVal.getClass().getSimpleName() + "'");
-                        }
-                    }
-                }
-                else if 
(coCtx.kernalContext().cacheObjects().typeId(prop.type().getName()) !=
-                        ((BinaryObject)propVal).type().typeId()) {
-                    throw new IgniteSQLException("Type for a column '" + 
prop.name() +
-                        "' is not compatible with table definition. Expected 
'" +
-                        prop.type().getSimpleName() + "', actual type '" +
-                        ((BinaryObject)propVal).type().typeName() + "'");
-                }
+            if (validateTypes && propVal != null && 
!isCompatibleWithPropertyType(propVal, prop.name(), prop.type())) {
+                throw new IgniteSQLException("Type for a column '" + 
prop.name() + "' is not compatible with table definition." +
+                    " Expected '" + prop.type().getSimpleName() + "', actual 
type '" + typeName(propVal) + "'");
             }
 
             if (propVal == null || prop.precision() == -1)
@@ -721,36 +704,47 @@ public class QueryTypeDescriptorImpl implements 
GridQueryTypeDescriptor {
                 if (propVal == null)
                     continue;
 
-                if (!(propVal instanceof BinaryObject) || propVal instanceof 
BinaryArray) {
-                    if 
(!U.box(propType).isAssignableFrom(U.box(propVal.getClass()))) {
-                        // Some reference type arrays end up being converted 
to Object[]
-                        if (!(propType.isArray() && 
BinaryUtils.isObjectArray(propVal.getClass()) &&
-                            
Arrays.stream(BinaryUtils.rawArrayFromBinary(propVal)).
-                                noneMatch(x -> x != null && 
!U.box(propType.getComponentType()).isAssignableFrom(U.box(x.getClass()))))) {
-                            throw new IgniteSQLException("Type for a column '" 
+ idxField +
-                                "' is not compatible with index definition. 
Expected '" +
-                                propType.getSimpleName() + "', actual type '" +
-                                propVal.getClass().getSimpleName() + "'");
-                        }
-                    }
-                }
-                else if 
(coCtx.kernalContext().cacheObjects().typeId(propType.getName()) !=
-                    ((BinaryObject)propVal).type().typeId()) {
-                    // Check for classes/enums implementing indexed interfaces.
-                    final Class<?> cls = 
U.classForName(((BinaryObject)propVal).type().typeName(), null, true);
-
-                    if ((cls == null && propType == Object.class) || (cls != 
null && propType.isAssignableFrom(cls)))
-                        continue;
-
-                    throw new IgniteSQLException("Type for a column '" + 
idxField +
-                        "' is not compatible with index definition. Expected 
'" +
-                        propType.getSimpleName() + "', actual type '" +
-                        ((BinaryObject)propVal).type().typeName() + "'");
+                if (!isCompatibleWithPropertyType(propVal, idxField, 
propType)) {
+                    throw new IgniteSQLException("Type for a column '" + 
idxField + "' is not compatible with index definition." +
+                        " Expected '" + prop.type().getSimpleName() + "', 
actual type '" + typeName(propVal) + "'");
                 }
             }
         }
     }
 
+    /**
+     * Checks if the specified object is compatible with the type of the 
column through which this object will be accessed.
+     *
+     * @param val Object to check.
+     * @param colName Name of the column to which current property corresponds.
+     * @param expColType Type of the column based on Query Property info.
+     */
+    private boolean isCompatibleWithPropertyType(Object val, String colName, 
Class<?> expColType) {
+        if (!(val instanceof BinaryObject) || val instanceof BinaryArray) {
+            if (U.box(expColType).isAssignableFrom(U.box(val.getClass())))
+                return true;
+
+            GridQueryIndexing indexing = 
coCtx.kernalContext().query().getIndexing();
+
+            assert indexing != null;
+
+            if (indexing.isConvertibleToColumnType(schemaName, tableName(), 
colName, val.getClass()))
+                return true;
+
+            return expColType.isArray()
+                && BinaryUtils.isObjectArray(val.getClass())
+                && Arrays.stream(BinaryUtils.rawArrayFromBinary(val))
+                    .allMatch(x -> x == null || 
U.box(expColType.getComponentType()).isAssignableFrom(U.box(x.getClass())));
+        }
+        else if 
(coCtx.kernalContext().cacheObjects().typeId(expColType.getName()) != 
((BinaryObject)val).type().typeId()) {
+            final Class<?> cls = 
U.classForName(((BinaryObject)val).type().typeName(), null, true);
+
+            return (cls == null && expColType == Object.class) || (cls != null 
&& expColType.isAssignableFrom(cls));
+        }
+
+        return true;
+    }
+
     /** {@inheritDoc} */
     @SuppressWarnings("ForLoopReplaceableByForEach")
     @Override public void setDefaults(Object key, Object val) throws 
IgniteCheckedException {
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/query/DummyQueryIndexing.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/query/DummyQueryIndexing.java
index ec746ba3f15..08e7ff25111 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/query/DummyQueryIndexing.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/query/DummyQueryIndexing.java
@@ -318,4 +318,9 @@ public class DummyQueryIndexing implements 
GridQueryIndexing {
         String colNamePtrn) {
         return null;
     }
+
+    /** {@inheritDoc} */
+    @Override public boolean isConvertibleToColumnType(String schemaName, 
String tblName, String colName, Class<?> cls) {
+        return false;
+    }
 }
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
index 52b0d1f5575..b55077bc854 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/H2Utils.java
@@ -31,17 +31,21 @@ import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
 import java.text.MessageFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
-
 import javax.cache.CacheException;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
@@ -153,6 +157,21 @@ public class H2Utils {
     /** Quotation character. */
     private static final char ESC_CH = '\"';
 
+    /** Types that can be implicitly converted to H2 column type. */
+    private static final Map<Integer, Set<Class<?>>> CONVERTABLE_TYPES = new 
HashMap<Integer, Set<Class<?>>>() {
+        {
+            put(Value.TIMESTAMP, new HashSet<Class<?>>() {
+                {
+                    add(LocalDateTime.class);
+                    add(java.util.Date.class);
+                    add(java.sql.Date.class);
+                }
+            });
+            put(Value.TIME, Collections.singleton(LocalTime.class));
+            put(Value.DATE, Collections.singleton(LocalDate.class));
+        }
+    };
+
     /**
      * @param c1 First column.
      * @param c2 Second column.
@@ -569,13 +588,33 @@ public class H2Utils {
         return false;
     }
 
+    /**
+     * @param cls The class whose convertibility is to be tested.
+     * @param colType Column target type.
+     * @return Whether specified class can be implicitly converted to the 
specified type.
+     * @see #wrap(CacheObjectValueContext, Object, int)
+     */
+    public static boolean isConvertableToColumnType(Class<?> cls, int colType) 
{
+        assert cls != null;
+
+        if (DataType.getTypeClassName(colType).equals(cls.getName()))
+            return true;
+
+        Set<Class<?>> types = CONVERTABLE_TYPES.get(colType);
+
+        return types != null && types.contains(cls);
+    }
+
     /**
      * Wraps object to respective {@link Value}.
      *
+     * Note, implicit type conversions must be also included into {@link 
#CONVERTABLE_TYPES}.
+     *
      * @param obj Object.
      * @param type Value type.
      * @return Value.
      * @throws IgniteCheckedException If failed.
+     * @see #isConvertableToColumnType(Class, int)
      */
     @SuppressWarnings("ConstantConditions")
     public static Value wrap(CacheObjectValueContext coCtx, Object obj, int 
type) throws IgniteCheckedException {
diff --git 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 868895055f4..f3679f277d5 100644
--- 
a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ 
b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -185,6 +185,7 @@ import org.h2.engine.Session;
 import org.h2.engine.SysProperties;
 import org.h2.index.Index;
 import org.h2.index.IndexType;
+import org.h2.message.DbException;
 import org.h2.table.Column;
 import org.h2.table.IndexColumn;
 import org.h2.table.TableType;
@@ -2068,6 +2069,22 @@ public class IgniteH2Indexing implements 
GridQueryIndexing {
         return infos;
     }
 
+    /** {@inheritDoc} */
+    @Override public boolean isConvertibleToColumnType(String schemaName, 
String tblName, String colName, Class<?> cls) {
+        GridH2Table table = schemaMgr.dataTable(schemaName, tblName);
+
+        if (table == null)
+            throw new IgniteSQLException("Table was not found [schemaName=" + 
schemaName + ", tableName=" + tblName + ']');
+
+        try {
+            return H2Utils.isConvertableToColumnType(cls, 
table.getColumn(colName).getType());
+        }
+        catch (DbException e) {
+            throw new IgniteSQLException("Colum with specified name was not 
found for the table [schemaName=" + schemaName +
+                ", tableName=" + tblName + ", colName=" + colName + ']', e);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override public boolean isStreamableInsertStatement(String schemaName, 
SqlFieldsQuery qry) throws SQLException {
         QueryParserResult parsed = parser.parse(schemaName, qry, true);
diff --git 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteQueryConvertibleTypesValidationTest.java
 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteQueryConvertibleTypesValidationTest.java
new file mode 100644
index 00000000000..dc045273406
--- /dev/null
+++ 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/IgniteQueryConvertibleTypesValidationTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static java.util.Collections.singletonList;
+import static org.h2.util.LocalDateTimeUtils.localDateToDateValue;
+import static org.h2.util.LocalDateTimeUtils.localTimeToTimeValue;
+
+/** */
+@RunWith(Parameterized.class)
+public class IgniteQueryConvertibleTypesValidationTest extends 
GridCommonAbstractTest {
+    /** */
+    @Parameterized.Parameter()
+    public boolean isValidationEnabled;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public String sqlType;
+
+    /** */
+    @Parameterized.Parameter(2)
+    public Class<?> objType;
+
+    /** */
+    @Parameterized.Parameter(3)
+    public Function<Object, Object> sqlTypeConverter;
+
+    /** */
+    @Parameterized.Parameter(4)
+    public boolean isDdl;
+
+    /** */
+    @Parameterized.Parameters(name = "isValidationEnabled={0}, sqlType={1}, 
testObjCls={2}, isDdl={4}")
+    public static Collection<Object[]> parameters() {
+        Collection<Object[]> params = new ArrayList<>();
+
+        for (boolean isV : Arrays.asList(true, false)) {
+            for (boolean isDdl : Arrays.asList(true, false)) {
+                params.add(new Object[] {isV, "TIMESTAMP", 
LocalDateTime.class, f(d -> Timestamp.valueOf((LocalDateTime)d)), isDdl});
+                params.add(new Object[] {isV, "TIMESTAMP", Date.class, f(d -> 
new Timestamp(((Date)d).getTime())), isDdl});
+                params.add(new Object[] {isV, "TIMESTAMP", 
java.sql.Date.class, f(d -> new Timestamp(((Date)d).getTime())), isDdl});
+                params.add(new Object[] {isV, "DATE", LocalDate.class, f(d -> 
localDateToDateValue(d).getDate()), isDdl});
+                params.add(new Object[] {isV, "TIME", LocalTime.class, f(d -> 
localTimeToTimeValue(d).getTime()), isDdl});
+            }
+        }
+
+        return params;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        cfg.getSqlConfiguration().setValidationEnabled(isValidationEnabled);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        stopAllGrids();
+    }
+
+    /** */
+    @Test
+    public void testIgniteQueryConvertibleTypesValidation() throws Exception {
+        startGrid(0);
+
+        createTable();
+
+        execute("CREATE INDEX DATA_IDX ON DATA(data DESC);");
+
+        Object testObj = generateTestObject(objType);
+
+        execute("INSERT INTO DATA(_key, id, data) values(?, ?, ?)", 0, 0, 
testObj);
+
+        grid(0).cache(DEFAULT_CACHE_NAME).put(1, new Data(1, testObj));
+
+        grid(0).cache(DEFAULT_CACHE_NAME).put(2, grid(0).binary().toBinary(new 
Data(2, testObj)));
+
+        List<List<?>> selectData = execute("SELECT data FROM DATA");
+
+        Object sqlObj = sqlTypeConverter.apply(testObj);
+
+        assertTrue(selectData.get(0).stream().allMatch(d -> 
Objects.equals(sqlObj, d)));
+    }
+
+    /** */
+    private void createTable() {
+        if (isDdl) {
+            execute("CREATE TABLE DATA (id INT PRIMARY KEY, data " + sqlType + 
") WITH" +
+                " \"KEY_TYPE=java.lang.Integer" +
+                ", 
VALUE_TYPE=org.apache.ignite.internal.processors.query.IgniteQueryConvertibleTypesValidationTest$Data"
 +
+                ", CACHE_NAME=default\"");
+        }
+        else {
+            QueryEntity projEntity = new QueryEntity();
+            projEntity.setKeyType(Integer.class.getName());
+            projEntity.setValueType(Data.class.getName());
+            projEntity.addQueryField("id", Integer.class.getName(), null);
+            projEntity.addQueryField("data", toClassName(sqlType), null);
+
+            projEntity.setTableName("DATA");
+
+            grid(0).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME)
+                .setQueryEntities(singletonList(projEntity))
+                .setSqlSchema("PUBLIC"));
+        }
+    }
+
+    /** */
+    private List<List<?>> execute(String qry, Object... args) {
+        return grid(0).context().query().querySqlFields(new 
SqlFieldsQuery(qry).setArgs(args), false).getAll();
+    }
+
+    /** */
+    private static Object generateTestObject(Class<?> cls) {
+        if (cls == LocalDateTime.class)
+            return LocalDateTime.now();
+        else if (cls == LocalTime.class)
+            return LocalTime.now();
+        else if (cls == LocalDate.class)
+            return LocalDate.now();
+        else if (cls == Date.class)
+            return Date.from(Instant.now());
+        else if (cls == java.sql.Date.class)
+            return java.sql.Date.valueOf(LocalDate.now());
+        else if (cls == java.sql.Time.class)
+            return java.sql.Time.valueOf(LocalTime.now());
+        else
+            throw new IllegalStateException();
+    }
+
+    /** */
+    private static String toClassName(String sqlType) {
+        switch (sqlType) {
+            case "TIMESTAMP" : return java.sql.Timestamp.class.getName();
+            case "TIME" : return java.sql.Time.class.getName();
+            case "DATE" : return java.sql.Date.class.getName();
+            default: throw new IllegalStateException();
+        }
+    }
+
+    /** */
+    private static <T, R> Function<Object, Object> f(Function<T, R> f) {
+        return (Function<Object, Object>)f;
+    }
+
+    /** */
+    public static class Data implements Serializable {
+        /** Serial version UID. */
+        private static final long serialVersionUID = 1L;
+
+        /** */
+        public int id;
+
+        /** */
+        public Object data;
+
+        /** */
+        public Data(int id, Object data) {
+            this.id = id;
+            this.data = data;
+        }
+    }
+}
diff --git 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/H2ColumnTypeConversionCheckSelfTest.java
 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/H2ColumnTypeConversionCheckSelfTest.java
new file mode 100644
index 00000000000..ed73d1edc7b
--- /dev/null
+++ 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/h2/H2ColumnTypeConversionCheckSelfTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.h2;
+
+import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/** */
+public class H2ColumnTypeConversionCheckSelfTest extends 
GridCommonAbstractTest {
+    /** */
+    @Test
+    public void testConversions() throws Exception {
+        startGrid(0);
+
+        grid(0).context().query().querySqlFields(
+            new SqlFieldsQuery("CREATE TABLE TBL (id INT PRIMARY KEY, 
timestamp TIMESTAMP, date DATE, time TIME)"), false);
+
+        assertTrue(isConvertible("TIMESTAMP", Timestamp.class));
+        assertTrue(isConvertible("TIMESTAMP", java.sql.Date.class));
+        assertTrue(isConvertible("TIMESTAMP", java.util.Date.class));
+        assertTrue(isConvertible("TIMESTAMP", LocalDateTime.class));
+        assertFalse(isConvertible("TIMESTAMP", Integer.class));
+
+        assertTrue(isConvertible("DATE", java.sql.Date.class));
+        assertTrue(isConvertible("DATE", LocalDate.class));
+        assertFalse(isConvertible("DATE", Integer.class));
+
+        assertTrue(isConvertible("TIME", java.sql.Time.class));
+        assertTrue(isConvertible("TIME", LocalTime.class));
+        assertFalse(isConvertible("TIME", Integer.class));
+    }
+
+    /** */
+    private boolean isConvertible(String colName, Class<?> cls) {
+        return 
grid(0).context().query().getIndexing().isConvertibleToColumnType("PUBLIC", 
"TBL", colName, cls);
+    }
+}
diff --git 
a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite3.java
 
b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite3.java
index 225a9a54521..8d53763a233 100644
--- 
a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite3.java
+++ 
b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteBinaryCacheQueryTestSuite3.java
@@ -100,6 +100,7 @@ import 
org.apache.ignite.internal.processors.cache.metric.SqlViewExporterSpiTest
 import 
org.apache.ignite.internal.processors.cache.query.IgniteCacheQueryCacheDestroySelfTest;
 import 
org.apache.ignite.internal.processors.cache.query.ScanQueryConcurrentSqlUpdatesTest;
 import 
org.apache.ignite.internal.processors.cache.query.ScanQueryConcurrentUpdatesTest;
+import 
org.apache.ignite.internal.processors.query.IgniteQueryConvertibleTypesValidationTest;
 import 
org.apache.ignite.internal.processors.query.IgniteQueryDedicatedPoolTest;
 import org.apache.ignite.internal.processors.query.IgniteSqlCustomSchemaTest;
 import 
org.apache.ignite.internal.processors.query.IgniteSqlCustomSchemaWithPdsEnabled;
@@ -135,6 +136,7 @@ import 
org.apache.ignite.internal.processors.query.SqlQueryHistorySelfTest;
 import org.apache.ignite.internal.processors.query.SqlSystemViewsSelfTest;
 import org.apache.ignite.internal.processors.query.h2.GridIndexRebuildSelfTest;
 import org.apache.ignite.internal.processors.query.h2.GridIndexRebuildTest;
+import 
org.apache.ignite.internal.processors.query.h2.H2ColumnTypeConversionCheckSelfTest;
 import 
org.apache.ignite.internal.processors.query.h2.QueryParserMetricsHolderSelfTest;
 import 
org.apache.ignite.internal.processors.query.h2.RowCountTableStatisticsSurvivesNodeRestartTest;
 import 
org.apache.ignite.internal.processors.query.h2.RowCountTableStatisticsUsageTest;
@@ -370,6 +372,9 @@ import org.junit.runners.Suite;
 
     InlineIndexKeyTypeRegistryTest.class,
 
+    IgniteQueryConvertibleTypesValidationTest.class,
+    H2ColumnTypeConversionCheckSelfTest.class,
+
     IgniteStatisticsTestSuite.class,
 
     // CDC tests.

Reply via email to