You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ko...@apache.org on 2022/06/21 07:51:27 UTC

[ignite-3] branch main updated: IGNITE-17094 ColumnMetadata.nullable() returns value for non-nullable columns (#887)

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

korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new ae6a93c51 IGNITE-17094 ColumnMetadata.nullable() returns value for non-nullable columns (#887)
ae6a93c51 is described below

commit ae6a93c51ee42caf9df77b24226fedc1ae36cac1
Author: korlov42 <ko...@gridgain.com>
AuthorDate: Tue Jun 21 10:51:22 2022 +0300

    IGNITE-17094 ColumnMetadata.nullable() returns value for non-nullable columns (#887)
---
 .../internal/sql/api/ItSqlAsynchronousApiTest.java |   7 +-
 .../internal/sql/engine/ItDataTypesTest.java       | 141 ++++++-------
 .../internal/sql/engine/ItFunctionsTest.java       |   8 +-
 .../ignite/internal/sql/engine/ItMetadataTest.java |   6 +-
 .../internal/sql/engine/util/QueryChecker.java     |   4 +-
 .../ignite/internal/schema/NativeTypeSpec.java     |  48 +++++
 .../sql/engine/exec/ExecutionServiceImpl.java      |   2 +-
 .../sql/engine/exec/exp/IgniteSqlFunctions.java    |   7 +
 .../internal/sql/engine/exec/exp/RexImpTable.java  |   2 +-
 .../sql/engine/prepare/PrepareServiceImpl.java     |  10 +-
 .../sql/engine/schema/ColumnDescriptor.java        |  22 +-
 .../sql/engine/schema/ColumnDescriptorImpl.java    |  61 ++++--
 ...mnDescriptor.java => DefaultValueStrategy.java} |  36 ++--
 .../sql/engine/schema/IgniteTableImpl.java         |  12 +-
 .../sql/engine/schema/SqlSchemaManagerImpl.java    |   2 +
 .../sql/engine/schema/TableDescriptorImpl.java     |  36 ++--
 .../sql/engine/type/IgniteTypeFactory.java         |   7 +-
 .../internal/sql/engine/util/IgniteMethod.java     |   6 +-
 .../ignite/internal/sql/engine/util/TypeUtils.java | 222 +++++++++++----------
 .../sql/engine/planner/AbstractPlannerTest.java    |  17 +-
 20 files changed, 373 insertions(+), 283 deletions(-)

diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java
index c64f0335e..2f45a0a77 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlAsynchronousApiTest.java
@@ -216,7 +216,7 @@ public class ItSqlAsynchronousApiTest extends AbstractBasicIntegrationTest {
 
         AsyncResultSet rs = ses.executeAsync(null, "SELECT COL1, COL0 FROM TEST").get();
 
-        // Validata columns metadata.
+        // Validate columns metadata.
         ResultSetMetadata meta = rs.metadata();
 
         assertNotNull(meta);
@@ -224,10 +224,9 @@ public class ItSqlAsynchronousApiTest extends AbstractBasicIntegrationTest {
         assertEquals(0, meta.indexOf("COL1"));
         assertEquals(1, meta.indexOf("COL0"));
 
-        //TODO: IGNITE-17094: ColumnMetadata.nullable() must return false for non-null column.
-        checkMetadata(new ColumnMetadataImpl("COL1", SqlColumnType.STRING, 0, 0, true, new ColumnOriginImpl("PUBLIC", "TEST", "COL1")),
+        checkMetadata(new ColumnMetadataImpl("COL1", SqlColumnType.STRING, 0, 0, false, new ColumnOriginImpl("PUBLIC", "TEST", "COL1")),
                 meta.columns().get(0));
-        checkMetadata(new ColumnMetadataImpl("COL0", SqlColumnType.INT64, 0, 0, true, new ColumnOriginImpl("PUBLIC", "TEST", "COL0")),
+        checkMetadata(new ColumnMetadataImpl("COL0", SqlColumnType.INT64, 0, 0, false, new ColumnOriginImpl("PUBLIC", "TEST", "COL0")),
                 meta.columns().get(1));
 
         // Validate result columns types.
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index 359998734..f9f129333 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -23,59 +23,74 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
-import org.apache.ignite.lang.IgniteException;
-import org.junit.jupiter.api.Disabled;
+import org.apache.calcite.runtime.CalciteContextException;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 /**
  * Test SQL data types.
  */
-@Disabled("https://issues.apache.org/jira/browse/IGNITE-16679")
 public class ItDataTypesTest extends AbstractBasicIntegrationTest {
+    /**
+     * Drops all created tables.
+     */
+    @AfterEach
+    public void dropTables() {
+        var igniteTables = CLUSTER_NODES.get(0).tables();
+
+        var tables = igniteTables.tables();
+
+        var futs = new CompletableFuture<?>[tables.size()];
+
+        int idx = 0;
+        for (var table : tables) {
+            futs[idx++] = igniteTables.dropTableAsync(table.name());
+        }
+
+        CompletableFuture.allOf(futs).join();
+    }
+
     /** Tests correctness with unicode. */
     @Test
     public void testUnicodeStrings() {
-        try {
-            sql("CREATE TABLE string_table(key int primary key, val varchar)");
+        sql("CREATE TABLE string_table(key int primary key, val varchar)");
 
-            String[] values = new String[]{"Кирилл", "Müller", "我是谁", "ASCII"};
+        String[] values = new String[]{"Кирилл", "Müller", "我是谁", "ASCII"};
 
-            int key = 0;
+        int key = 0;
 
-            // Insert as inlined values.
-            for (String val : values) {
-                sql("INSERT INTO string_table (key, val) VALUES (?, ?)", key++, val);
-            }
+        // Insert as inlined values.
+        for (String val : values) {
+            sql("INSERT INTO string_table (key, val) VALUES (?, ?)", key++, val);
+        }
 
-            var rows = sql("SELECT val FROM string_table");
+        var rows = sql("SELECT val FROM string_table");
 
-            assertEquals(Set.of(values), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
+        assertEquals(Set.of(values), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
 
-            sql("DELETE FROM string_table");
+        sql("DELETE FROM string_table");
 
-            // Insert as parameters.
-            for (String val : values) {
-                sql("INSERT INTO string_table (key, val) VALUES (?, ?)", key++, val);
-            }
+        // Insert as parameters.
+        for (String val : values) {
+            sql("INSERT INTO string_table (key, val) VALUES (?, ?)", key++, val);
+        }
 
-            rows = sql("SELECT val FROM string_table");
+        rows = sql("SELECT val FROM string_table");
 
-            assertEquals(Set.of(values), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
+        assertEquals(Set.of(values), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
 
-            rows = sql("SELECT substring(val, 1, 2) FROM string_table");
+        rows = sql("SELECT substring(val, 1, 2) FROM string_table");
 
-            assertEquals(Set.of("Ки", "Mü", "我是", "AS"),
-                    rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
+        assertEquals(Set.of("Ки", "Mü", "我是", "AS"),
+                rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
 
-            for (String val : values) {
-                rows = sql("SELECT char_length(val) FROM string_table WHERE val = ?", val);
+        for (String val : values) {
+            rows = sql("SELECT char_length(val) FROM string_table WHERE val = ?", val);
 
-                assertEquals(1, rows.size());
-                assertEquals(val.length(), rows.get(0).get(0));
-            }
-        } finally {
-            sql("DROP TABLE IF EXISTS string_table");
+            assertEquals(1, rows.size());
+            assertEquals(val.length(), rows.get(0).get(0));
         }
     }
 
@@ -97,7 +112,7 @@ public class ItDataTypesTest extends AbstractBasicIntegrationTest {
         assertEquals(Set.of(101), rows.stream().map(r -> r.get(0)).collect(Collectors.toSet()));
 
         //todo: correct exception https://issues.apache.org/jira/browse/IGNITE-16095
-        assertThrows(IgniteException.class, () -> sql("INSERT INTO tbl(c1, c2) VALUES (2, NULL)"));
+        assertThrows(CalciteContextException.class, () -> sql("INSERT INTO tbl(c1, c2) VALUES (2, NULL)"));
     }
 
     /**
@@ -105,29 +120,25 @@ public class ItDataTypesTest extends AbstractBasicIntegrationTest {
      */
     @Test
     public void testNumericRanges() {
-        try {
-            sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
+        sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
 
-            sql("INSERT INTO tbl VALUES (1, " + Byte.MAX_VALUE + ", " + Short.MAX_VALUE + ", "
-                    + Integer.MAX_VALUE + ", " + Long.MAX_VALUE + ')');
+        sql("INSERT INTO tbl VALUES (1, " + Byte.MAX_VALUE + ", " + Short.MAX_VALUE + ", "
+                + Integer.MAX_VALUE + ", " + Long.MAX_VALUE + ')');
 
-            assertQuery("SELECT tiny FROM tbl").returns(Byte.MAX_VALUE).check();
-            assertQuery("SELECT small FROM tbl").returns(Short.MAX_VALUE).check();
-            assertQuery("SELECT i FROM tbl").returns(Integer.MAX_VALUE).check();
-            assertQuery("SELECT big FROM tbl").returns(Long.MAX_VALUE).check();
+        assertQuery("SELECT tiny FROM tbl").returns(Byte.MAX_VALUE).check();
+        assertQuery("SELECT small FROM tbl").returns(Short.MAX_VALUE).check();
+        assertQuery("SELECT i FROM tbl").returns(Integer.MAX_VALUE).check();
+        assertQuery("SELECT big FROM tbl").returns(Long.MAX_VALUE).check();
 
-            sql("DELETE from tbl");
+        sql("DELETE from tbl");
 
-            sql("INSERT INTO tbl VALUES (1, " + Byte.MIN_VALUE + ", " + Short.MIN_VALUE + ", "
-                    + Integer.MIN_VALUE + ", " + Long.MIN_VALUE + ')');
+        sql("INSERT INTO tbl VALUES (1, " + Byte.MIN_VALUE + ", " + Short.MIN_VALUE + ", "
+                + Integer.MIN_VALUE + ", " + Long.MIN_VALUE + ')');
 
-            assertQuery("SELECT tiny FROM tbl").returns(Byte.MIN_VALUE).check();
-            assertQuery("SELECT small FROM tbl").returns(Short.MIN_VALUE).check();
-            assertQuery("SELECT i FROM tbl").returns(Integer.MIN_VALUE).check();
-            assertQuery("SELECT big FROM tbl").returns(Long.MIN_VALUE).check();
-        } finally {
-            sql("DROP TABLE IF EXISTS tbl");
-        }
+        assertQuery("SELECT tiny FROM tbl").returns(Byte.MIN_VALUE).check();
+        assertQuery("SELECT small FROM tbl").returns(Short.MIN_VALUE).check();
+        assertQuery("SELECT i FROM tbl").returns(Integer.MIN_VALUE).check();
+        assertQuery("SELECT big FROM tbl").returns(Long.MIN_VALUE).check();
     }
 
     /**
@@ -135,31 +146,27 @@ public class ItDataTypesTest extends AbstractBasicIntegrationTest {
      */
     @Test
     public void testNumericConvertingOnEquals() {
-        try {
-            sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
+        sql("CREATE TABLE tbl(id int PRIMARY KEY, tiny TINYINT, small SMALLINT, i INTEGER, big BIGINT)");
 
-            sql("INSERT INTO tbl VALUES (-1, 1, 2, 3, 4), (0, 5, 5, 5, 5)");
+        sql("INSERT INTO tbl VALUES (-1, 1, 2, 3, 4), (0, 5, 5, 5, 5)");
 
-            assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.small)").returns((byte) 5).check();
-            assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.tiny)").returns((short) 5).check();
+        assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.small)").returns((byte) 5).check();
+        assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.tiny)").returns((short) 5).check();
 
-            assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.i)").returns((byte) 5).check();
-            assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.tiny)").returns(5).check();
+        assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.i)").returns((byte) 5).check();
+        assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.tiny)").returns(5).check();
 
-            assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.big)").returns((byte) 5).check();
-            assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.tiny)").returns(5L).check();
+        assertQuery("SELECT t1.tiny FROM tbl t1 JOIN tbl t2 ON (t1.tiny=t2.big)").returns((byte) 5).check();
+        assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.tiny)").returns(5L).check();
 
-            assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.i)").returns((short) 5).check();
-            assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.small)").returns(5).check();
+        assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.i)").returns((short) 5).check();
+        assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.small)").returns(5).check();
 
-            assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.big)").returns((short) 5).check();
-            assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.small)").returns(5L).check();
+        assertQuery("SELECT t1.small FROM tbl t1 JOIN tbl t2 ON (t1.small=t2.big)").returns((short) 5).check();
+        assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.small)").returns(5L).check();
 
-            assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.big)").returns(5).check();
-            assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.i)").returns(5L).check();
-        } finally {
-            sql("DROP TABLE if exists tbl");
-        }
+        assertQuery("SELECT t1.i FROM tbl t1 JOIN tbl t2 ON (t1.i=t2.big)").returns(5).check();
+        assertQuery("SELECT t1.big FROM tbl t1 JOIN tbl t2 ON (t1.big=t2.i)").returns(5L).check();
     }
 
     /**
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
index 7ed8c9e49..95efbd8c5 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItFunctionsTest.java
@@ -310,13 +310,7 @@ public class ItFunctionsTest extends AbstractBasicIntegrationTest {
         /**
          * A clock reporting a local time.
          */
-        Clock<LocalTime> TIME_CLOCK = () -> {
-            var tmp = LocalTime.now();
-
-            // need to erase millis because that is what we do when converting from internal types
-            // see: org.apache.ignite.internal.sql.engine.util.TypeUtils.fromInternal
-            return LocalTime.of(tmp.getHour(), tmp.getMinute(), tmp.getSecond());
-        };
+        Clock<LocalTime> TIME_CLOCK = LocalTime::now;
 
         /**
          * A clock reporting a local date.
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
index 9f3dba325..769c9853e 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItMetadataTest.java
@@ -20,6 +20,8 @@ package org.apache.ignite.internal.sql.engine;
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Stream.generate;
 
+import java.time.Duration;
+import java.time.Period;
 import org.apache.ignite.internal.schema.configuration.SchemaConfigurationConverter;
 import org.apache.ignite.schema.SchemaBuilders;
 import org.apache.ignite.schema.definition.ColumnType;
@@ -81,9 +83,7 @@ public class ItMetadataTest extends AbstractBasicIntegrationTest {
         assertQuery("select id, id::tinyint as tid, id::smallint as sid, id::varchar as vid, id::interval hour, "
                 + "id::interval year from person")
                 .columnNames("ID", "TID", "SID", "VID", "ID :: INTERVAL INTERVAL_HOUR", "ID :: INTERVAL INTERVAL_YEAR")
-                // TODO: IGNITE-16635 replace byte arrays for correct types.
-                //.columnTypes(Integer.class, Byte.class, Short.class, String.class, Duration.class, Period.class)
-                .columnTypes(Integer.class, Byte.class, Short.class, String.class, byte[].class, byte[].class)
+                .columnTypes(Integer.class, Byte.class, Short.class, String.class, Duration.class, Period.class)
                 .check();
     }
 
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
index 5027cf735..43d99888a 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/util/QueryChecker.java
@@ -368,12 +368,12 @@ public abstract class QueryChecker {
         }
 
         if (expectedColumnTypes != null) {
-            List<Type> colNames = cur.metadata().columns().stream()
+            List<Type> colTypes = cur.metadata().columns().stream()
                     .map(ColumnMetadata::type)
                     .map(Commons::columnTypeToClass)
                     .collect(Collectors.toList());
 
-            assertThat("Column types don't match", colNames, equalTo(expectedColumnTypes));
+            assertThat("Column types don't match", colTypes, equalTo(expectedColumnTypes));
         }
 
         var res = CursorUtils.getAllFromCursor(cur);
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
index 66180043b..d65eea7cc 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/NativeTypeSpec.java
@@ -315,6 +315,54 @@ public enum NativeTypeSpec {
         return null;
     }
 
+    /**
+     * Maps native type spec to a particular java class.
+     *
+     * @param spec Type spec to map.
+     * @param nullable Whether class should accept null values.
+     * @return Java class.
+     */
+    public static Class<?> toClass(NativeTypeSpec spec, boolean nullable) {
+        assert spec != null;
+
+        switch (spec) {
+            case INT8:
+                return nullable ? Byte.class : byte.class;
+            case INT16:
+                return nullable ? Short.class : short.class;
+            case INT32:
+                return nullable ? Integer.class : int.class;
+            case INT64:
+                return nullable ? Long.class : long.class;
+            case FLOAT:
+                return nullable ? Float.class : float.class;
+            case DOUBLE:
+                return nullable ? Double.class : double.class;
+            case BITMASK:
+                return BitSet.class;
+            case BYTES:
+                return byte[].class;
+            case STRING:
+                return String.class;
+            case DATE:
+                return LocalDate.class;
+            case TIME:
+                return LocalTime.class;
+            case TIMESTAMP:
+                return Instant.class;
+            case DATETIME:
+                return LocalDateTime.class;
+            case UUID:
+                return java.util.UUID.class;
+            case NUMBER:
+                return BigInteger.class;
+            case DECIMAL:
+                return BigDecimal.class;
+            default:
+                throw new IllegalStateException("Unknown typeSpec " + spec);
+        }
+    }
+
     /**
      * Maps object to native type.
      *
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
index 150028a0d..dc2fec326 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/ExecutionServiceImpl.java
@@ -578,7 +578,7 @@ public class ExecutionServiceImpl<RowT> implements ExecutionService, TopologyEve
 
             start
                     .thenCompose(none -> {
-                        if (!root.completeExceptionally(new ExecutionCancelledException())) {
+                        if (!root.completeExceptionally(new ExecutionCancelledException()) && !root.isCompletedExceptionally()) {
                             if (cancel) {
                                 return root.thenAccept(root -> root.onError(new ExecutionCancelledException()));
                             }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
index 2d75feadb..b86f95707 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/IgniteSqlFunctions.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine.exec.exp;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.math.RoundingMode;
+import java.time.LocalTime;
 import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.config.CalciteConnectionConfig;
@@ -35,8 +36,10 @@ import org.apache.calcite.schema.Statistic;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeSystem;
 import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.checkerframework.checker.nullness.qual.Nullable;
 
 /**
@@ -158,6 +161,10 @@ public class IgniteSqlFunctions {
         return s == null ? null : new ByteString(s.getBytes(Commons.typeFactory().getDefaultCharset()));
     }
 
+    public static int currentTime(DataContext ctx) {
+        return (int) TypeUtils.toInternal((ExecutionContext<?>) ctx, LocalTime.now(), LocalTime.class);
+    }
+
     /**
      * Dummy table to implement the SYSTEM_RANGE function.
      */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
index 3c5ee0702..1623ead2a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/exp/RexImpTable.java
@@ -1712,7 +1712,7 @@ public class RexImpTable {
             } else if (op == CURRENT_TIMESTAMP) {
                 return Expressions.call(BuiltInMethod.CURRENT_TIMESTAMP.method, root);
             } else if (op == CURRENT_TIME) {
-                return Expressions.call(BuiltInMethod.CURRENT_TIME.method, root);
+                return Expressions.call(IgniteMethod.CURRENT_TIME.method(), root);
             } else if (op == CURRENT_DATE) {
                 return Expressions.call(BuiltInMethod.CURRENT_DATE.method, root);
             } else if (op == LOCALTIMESTAMP) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
index f55f0fbc8..20d4942f3 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/PrepareServiceImpl.java
@@ -226,7 +226,7 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
 
             QueryTemplate template = new QueryTemplate(fragments);
 
-            return new MultiStepQueryPlan(template, resultSetMetadata(ctx, validated.dataType(), validated.origins()));
+            return new MultiStepQueryPlan(template, resultSetMetadata(validated.dataType(), validated.origins()));
         }, planningPool));
 
         return planFut.thenApply(QueryPlan::copy);
@@ -255,12 +255,12 @@ public class PrepareServiceImpl implements PrepareService, SchemaUpdateListener
         return planFut.thenApply(QueryPlan::copy);
     }
 
-    private ResultSetMetadata resultSetMetadata(PlanningContext ctx, RelDataType sqlType,
-            @Nullable List<List<String>> origins) {
+    private ResultSetMetadata resultSetMetadata(
+            RelDataType rowType,
+            @Nullable List<List<String>> origins
+    ) {
         return new LazyResultSetMetadata(
                 () -> {
-                    RelDataType rowType = TypeUtils.getResultType(ctx.typeFactory(), ctx.catalogReader(), sqlType, origins);
-
                     List<ColumnMetadata> fieldsMeta = new ArrayList<>(rowType.getFieldCount());
 
                     for (int i = 0; i < rowType.getFieldCount(); ++i) {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java
index 57293c8d5..e2236a9f2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java
@@ -17,28 +17,34 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
-import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.internal.schema.NativeType;
-import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
+import org.jetbrains.annotations.Nullable;
 
 /**
- * ColumnDescriptor interface.
- * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+ * An object describing a particular column in a table.
  */
 public interface ColumnDescriptor {
+    /** Returns {@code true} if this column accepts a null value. */
+    boolean nullable();
+
+    /** Returns {@code true} if this column is part of the primary key. */
     boolean key();
 
-    boolean hasDefaultValue();
+    /** Returns the strategy to follow when generating value for column not specified in the INSERT statement. */
+    DefaultValueStrategy defaultStrategy();
 
+    /** Returns the name of the column. */
     String name();
 
+    /** Returns 0-based index of the column according to a schema defined by a user. */
     int logicalIndex();
 
+    /** Returns 0-based index of the column according to an actual row layout defined by a storage. */
     int physicalIndex();
 
-    RelDataType logicalType(IgniteTypeFactory f);
-
+    /** Returns the type of this column in a storage. */
     NativeType physicalType();
 
-    Object defaultValue();
+    /** Returns the value to use for column not specified in the INSERT statement. */
+    @Nullable Object defaultValue();
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptorImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptorImpl.java
index baabb4ae3..346d3d4cc 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptorImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptorImpl.java
@@ -17,23 +17,25 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
+import java.util.Objects;
 import java.util.function.Supplier;
-import org.apache.calcite.rel.type.RelDataType;
 import org.apache.ignite.internal.schema.NativeType;
-import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
-import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.jetbrains.annotations.Nullable;
 
 /**
- * ColumnDescriptorImpl.
- * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+ * Simple implementation of {@link ColumnDescriptor}.
  */
 public class ColumnDescriptorImpl implements ColumnDescriptor {
+    private static final Supplier<Object> NULL_SUPPLIER = () -> null;
+
+    private final boolean nullable;
+
     private final boolean key;
 
     private final String name;
 
-    private final @Nullable Supplier<Object> dfltVal;
+    private final Supplier<Object> dfltVal;
+
+    private final DefaultValueStrategy defaultStrategy;
 
     private final int logicalIndex;
 
@@ -43,22 +45,45 @@ public class ColumnDescriptorImpl implements ColumnDescriptor {
 
     /**
      * Constructor.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+     *
+     * @param name The name of the column.
+     * @param key If {@code true}, this column will be considered as a part of PK.
+     * @param nullable If {@code true}, this column will be considered as a nullable.
+     * @param logicalIndex A 0-based index in a schema defined by a user.
+     * @param physicalIndex A 0-based index in a schema defined by a storage.
+     * @param type Type of the value in the underlying storage.
+     * @param defaultStrategy A strategy to follow when generating value for column not specified in the INSERT statement.
+     * @param dfltVal A value generator to use when generating value for column not specified in the INSERT statement.
+     *               If {@link #defaultStrategy} is {@link DefaultValueStrategy#DEFAULT_NULL DEFAULT_NULL} then the passed supplier will
+     *               be ignored, thus may be {@code null}. In other cases value supplier MUST be specified.
      */
     public ColumnDescriptorImpl(
             String name,
             boolean key,
+            boolean nullable,
             int logicalIndex,
             int physicalIndex,
-            NativeType storageType,
-            @Nullable Supplier<Object> dfltVal
+            NativeType type,
+            DefaultValueStrategy defaultStrategy,
+            Supplier<Object> dfltVal
     ) {
         this.key = key;
+        this.nullable = nullable;
         this.name = name;
-        this.dfltVal = dfltVal;
+        this.defaultStrategy = defaultStrategy;
         this.logicalIndex = logicalIndex;
         this.physicalIndex = physicalIndex;
-        this.storageType = storageType;
+        this.storageType = type;
+
+        this.dfltVal = defaultStrategy != DefaultValueStrategy.DEFAULT_NULL
+                ? Objects.requireNonNull(dfltVal, "dfltVal")
+                : NULL_SUPPLIER;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean nullable() {
+        return nullable;
     }
 
     /** {@inheritDoc} */
@@ -69,14 +94,14 @@ public class ColumnDescriptorImpl implements ColumnDescriptor {
 
     /** {@inheritDoc} */
     @Override
-    public boolean hasDefaultValue() {
-        return dfltVal != null;
+    public DefaultValueStrategy defaultStrategy() {
+        return defaultStrategy;
     }
 
     /** {@inheritDoc} */
     @Override
     public Object defaultValue() {
-        return dfltVal != null ? dfltVal.get() : null;
+        return dfltVal.get();
     }
 
     /** {@inheritDoc} */
@@ -97,12 +122,6 @@ public class ColumnDescriptorImpl implements ColumnDescriptor {
         return physicalIndex;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public RelDataType logicalType(IgniteTypeFactory f) {
-        return f.createJavaType(Commons.nativeTypeToClass(storageType));
-    }
-
     /** {@inheritDoc} */
     @Override
     public NativeType physicalType() {
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/DefaultValueStrategy.java
similarity index 61%
copy from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java
copy to modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/DefaultValueStrategy.java
index 57293c8d5..bbd8c054c 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/ColumnDescriptor.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/DefaultValueStrategy.java
@@ -17,28 +17,20 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
-import org.apache.calcite.rel.type.RelDataType;
-import org.apache.ignite.internal.schema.NativeType;
-import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
-
 /**
- * ColumnDescriptor interface.
- * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
+ * The strategy of default value computation for a particular column.
  */
-public interface ColumnDescriptor {
-    boolean key();
-
-    boolean hasDefaultValue();
-
-    String name();
-
-    int logicalIndex();
-
-    int physicalIndex();
-
-    RelDataType logicalType(IgniteTypeFactory f);
-
-    NativeType physicalType();
-
-    Object defaultValue();
+public enum DefaultValueStrategy {
+    /** Default value is not specified, thus {@code null} will be used instead. */
+    DEFAULT_NULL,
+
+    /**
+     * Default value is specified as a constant, thus may be inlined into the final plan.
+     *
+     * <p>Note: the value may still be {@code null} if someone specify this explicitly.
+     */
+    DEFAULT_CONSTANT,
+
+    /** Default value is specified as an expression and will be evaluated at a runtime. */
+    DEFAULT_COMPUTED
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
index 1cbe84ec1..37d09d5c2 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/IgniteTableImpl.java
@@ -43,6 +43,7 @@ import org.apache.calcite.schema.Statistic;
 import org.apache.calcite.schema.impl.AbstractTable;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.schema.BinaryRow;
+import org.apache.ignite.internal.schema.NativeTypeSpec;
 import org.apache.ignite.internal.schema.SchemaDescriptor;
 import org.apache.ignite.internal.schema.SchemaRegistry;
 import org.apache.ignite.internal.schema.row.Row;
@@ -58,6 +59,7 @@ import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.RewindabilityTrait;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.apache.ignite.internal.storage.PartitionStorage;
 import org.apache.ignite.internal.table.InternalTable;
 import org.jetbrains.annotations.Nullable;
@@ -318,6 +320,8 @@ public class IgniteTableImpl extends AbstractTable implements InternalIgniteTabl
                 val = hnd.get(colDesc.logicalIndex(), row);
             }
 
+            val = TypeUtils.fromInternal(ectx, val, NativeTypeSpec.toClass(colDesc.physicalType().spec(), colDesc.nullable()));
+
             RowAssembler.writeValue(rowAssembler, colDesc.physicalType(), val);
         }
 
@@ -379,7 +383,8 @@ public class IgniteTableImpl extends AbstractTable implements InternalIgniteTabl
         for (ColumnDescriptor colDesc : columnsOrderedByPhysSchema) {
             int colIdx = columnToIndex.getOrDefault(colDesc.name(), colDesc.logicalIndex() + offset);
 
-            Object val = hnd.get(colIdx, row);
+            Object val = TypeUtils.fromInternal(ectx, hnd.get(colIdx, row),
+                    NativeTypeSpec.toClass(colDesc.physicalType().spec(), colDesc.nullable()));
 
             RowAssembler.writeValue(rowAssembler, colDesc.physicalType(), val);
         }
@@ -434,7 +439,10 @@ public class IgniteTableImpl extends AbstractTable implements InternalIgniteTabl
                 break;
             }
 
-            RowAssembler.writeValue(rowAssembler, colDesc.physicalType(), hnd.get(colDesc.logicalIndex(), row));
+            Object val = TypeUtils.fromInternal(ectx, hnd.get(colDesc.logicalIndex(), row),
+                    NativeTypeSpec.toClass(colDesc.physicalType().spec(), colDesc.nullable()));
+
+            RowAssembler.writeValue(rowAssembler, colDesc.physicalType(), val);
         }
 
         return new ModifyRow(new Row(schemaDescriptor, rowAssembler.build()), Operation.DELETE_ROW);
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java
index be2ed7b89..b61aac62b 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/SqlSchemaManagerImpl.java
@@ -317,9 +317,11 @@ public class SqlSchemaManagerImpl implements SqlSchemaManager {
                 .map(col -> new ColumnDescriptorImpl(
                         col.name(),
                         descriptor.isKeyColumn(col.schemaIndex()),
+                        col.nullable(),
                         col.columnOrder(),
                         col.schemaIndex(),
                         col.type(),
+                        DefaultValueStrategy.DEFAULT_CONSTANT,
                         col::defaultValue
                 ))
                 .collect(Collectors.toList());
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
index ada088a9a..d0d0befb4 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/schema/TableDescriptorImpl.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.sql.engine.schema;
 
+import static org.apache.ignite.internal.sql.engine.util.TypeUtils.native2relationalType;
+
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -35,7 +37,6 @@ import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.internal.sql.engine.util.Commons;
-import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 
 /**
  * TableDescriptorImpl.
@@ -93,27 +94,20 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
         keyFields = keyFieldsBuilder.build();
     }
 
+    @SuppressWarnings("AssertWithSideEffects")
     private ColumnDescriptor injectDefault(ColumnDescriptor desc) {
         assert Commons.implicitPkEnabled() && Commons.IMPLICIT_PK_COL_NAME.equals(desc.name()) : desc;
 
         return new ColumnDescriptorImpl(
                 desc.name(),
                 desc.key(),
+                desc.nullable(),
                 desc.logicalIndex(),
                 desc.physicalIndex(),
                 desc.physicalType(),
-                null
-        ) {
-            @Override
-            public boolean hasDefaultValue() {
-                return false;
-            }
-
-            @Override
-            public Object defaultValue() {
-                return UUID.randomUUID().toString();
-            }
-        };
+                DefaultValueStrategy.DEFAULT_COMPUTED,
+                () -> UUID.randomUUID().toString()
+        );
     }
 
     /** {@inheritDoc} */
@@ -143,7 +137,7 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
     /** {@inheritDoc} */
     @Override
     public ColumnStrategy generationStrategy(RelOptTable tbl, int colIdx) {
-        if (descriptors[colIdx].hasDefaultValue()) {
+        if (descriptors[colIdx].defaultStrategy() != DefaultValueStrategy.DEFAULT_NULL) {
             return ColumnStrategy.DEFAULT;
         }
 
@@ -155,14 +149,14 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
     public RexNode newColumnDefaultValue(RelOptTable tbl, int colIdx, InitializerContext ctx) {
         final ColumnDescriptor desc = descriptors[colIdx];
 
-        if (!desc.hasDefaultValue()) {
+        if (desc.defaultStrategy() != DefaultValueStrategy.DEFAULT_CONSTANT) {
             return super.newColumnDefaultValue(tbl, colIdx, ctx);
         }
 
         final RexBuilder rexBuilder = ctx.getRexBuilder();
         final IgniteTypeFactory typeFactory = (IgniteTypeFactory) rexBuilder.getTypeFactory();
 
-        return rexBuilder.makeLiteral(desc.defaultValue(), desc.logicalType(typeFactory), false);
+        return rexBuilder.makeLiteral(desc.defaultValue(), deriveLogicalType(typeFactory, desc), false);
     }
 
     /** {@inheritDoc} */
@@ -172,15 +166,15 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
 
         if (usedColumns == null) {
             for (int i = 0; i < descriptors.length; i++) {
-                b.add(descriptors[i].name(), descriptors[i].logicalType(factory));
+                b.add(descriptors[i].name(), deriveLogicalType(factory, descriptors[i]));
             }
         } else {
             for (int i = usedColumns.nextSetBit(0); i != -1; i = usedColumns.nextSetBit(i + 1)) {
-                b.add(descriptors[i].name(), descriptors[i].logicalType(factory));
+                b.add(descriptors[i].name(), deriveLogicalType(factory, descriptors[i]));
             }
         }
 
-        return TypeUtils.sqlType(factory, b.build());
+        return b.build();
     }
 
     /** {@inheritDoc} */
@@ -200,4 +194,8 @@ public class TableDescriptorImpl extends NullInitializerExpressionFactory implem
     public int columnsCount() {
         return descriptors.length;
     }
+
+    private RelDataType deriveLogicalType(RelDataTypeFactory factory, ColumnDescriptor desc) {
+        return native2relationalType(factory, desc.physicalType(), desc.nullable());
+    }
 }
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
index d9c0da349..893515d53 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
@@ -26,6 +26,7 @@ import java.math.BigInteger;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -206,12 +207,12 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl {
                 case DATE:
                     return LocalDate.class;
                 case TIME:
+                case TIME_WITH_LOCAL_TIME_ZONE:
                     return LocalTime.class;
                 case TIMESTAMP:
-                case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
                     return LocalDateTime.class;
-                case TIME_WITH_LOCAL_TIME_ZONE:
-                    return LocalTime.class;
+                case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                    return Instant.class;
                 case INTEGER:
                     return type.isNullable() ? Integer.class : int.class;
                 case INTERVAL_YEAR:
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
index 74a518255..a343ae705 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteMethod.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.sql.engine.util;
 
 import java.lang.reflect.Method;
+import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
 import org.apache.calcite.linq4j.tree.Types;
 import org.apache.calcite.sql.SqlIntervalQualifier;
@@ -73,7 +74,10 @@ public enum IgniteMethod {
     BYTESTRING_TO_STRING(IgniteSqlFunctions.class, "toString", ByteString.class),
 
     /** See {@link IgniteSqlFunctions#toByteString(String)}. */
-    STRING_TO_BYTESTRING(IgniteSqlFunctions.class, "toByteString", String.class);
+    STRING_TO_BYTESTRING(IgniteSqlFunctions.class, "toByteString", String.class),
+
+    /** See {@link IgniteSqlFunctions#currentTime(DataContext)}. */
+    CURRENT_TIME(IgniteSqlFunctions.class, "currentTime", DataContext.class);
 
     private final Method method;
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
index 510c7847e..16b1804b4 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TypeUtils.java
@@ -17,12 +17,9 @@
 
 package org.apache.ignite.internal.sql.engine.util;
 
-import static org.apache.calcite.util.Util.last;
 import static org.apache.ignite.internal.sql.engine.util.Commons.transform;
-import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 
 import java.lang.reflect.Type;
-import java.sql.Timestamp;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -30,6 +27,7 @@ import java.time.LocalTime;
 import java.time.Period;
 import java.time.ZoneOffset;
 import java.util.Arrays;
+import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -37,41 +35,48 @@ import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
-import org.apache.calcite.DataContext;
 import org.apache.calcite.avatica.util.ByteString;
-import org.apache.calcite.avatica.util.DateTimeUtils;
-import org.apache.calcite.plan.RelOptSchema;
-import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
 import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.runtime.SqlFunctions;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.util.Pair;
+import org.apache.ignite.internal.schema.DecimalNativeType;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.NumberNativeType;
+import org.apache.ignite.internal.schema.VarlenNativeType;
 import org.apache.ignite.internal.sql.engine.exec.ExecutionContext;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler;
-import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
-import org.apache.ignite.internal.sql.engine.schema.TableDescriptor;
 import org.apache.ignite.internal.sql.engine.type.IgniteTypeFactory;
 import org.apache.ignite.sql.SqlColumnType;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 /**
  * TypeUtils.
  * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
  */
 public class TypeUtils {
-    private static final Set<Type> CONVERTABLE_TYPES = Set.of(
-            LocalDate.class,
-            LocalDateTime.class,
-            LocalTime.class,
-            Timestamp.class,
-            Duration.class,
-            Period.class
+    private static final Set<SqlTypeName> CONVERTABLE_TYPES = EnumSet.of(
+            SqlTypeName.DATE,
+            SqlTypeName.TIME,
+            SqlTypeName.TIME_WITH_LOCAL_TIME_ZONE,
+            SqlTypeName.TIMESTAMP,
+            SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE,
+            SqlTypeName.INTERVAL_SECOND,
+            SqlTypeName.INTERVAL_MINUTE,
+            SqlTypeName.INTERVAL_MINUTE_SECOND,
+            SqlTypeName.INTERVAL_HOUR,
+            SqlTypeName.INTERVAL_HOUR_MINUTE,
+            SqlTypeName.INTERVAL_HOUR_SECOND,
+            SqlTypeName.INTERVAL_DAY,
+            SqlTypeName.INTERVAL_DAY_HOUR,
+            SqlTypeName.INTERVAL_DAY_MINUTE,
+            SqlTypeName.INTERVAL_DAY_SECOND,
+            SqlTypeName.INTERVAL_MONTH,
+            SqlTypeName.INTERVAL_YEAR,
+            SqlTypeName.INTERVAL_YEAR_MONTH
     );
 
     /**
@@ -174,71 +179,6 @@ public class TypeUtils {
         return typeFactory.createStructType(fields, names);
     }
 
-    /**
-     * SqlType.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public static RelDataType sqlType(IgniteTypeFactory typeFactory, RelDataType rowType) {
-        if (!rowType.isStruct()) {
-            return typeFactory.toSql(rowType);
-        }
-
-        return typeFactory.createStructType(
-                transform(rowType.getFieldList(),
-                        f -> Pair.of(f.getName(), sqlType(typeFactory, f.getType()))));
-    }
-
-    /**
-     * GetResultType.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     *
-     * @param schema  Schema.
-     * @param sqlType Logical row type.
-     * @param origins Columns origins.
-     * @return Result type.
-     */
-    public static RelDataType getResultType(IgniteTypeFactory typeFactory, RelOptSchema schema, RelDataType sqlType,
-            @Nullable List<List<String>> origins) {
-        assert origins == null || origins.size() == sqlType.getFieldCount();
-
-        RelDataTypeFactory.Builder b = new RelDataTypeFactory.Builder(typeFactory);
-        List<RelDataTypeField> fields = sqlType.getFieldList();
-
-        for (int i = 0; i < sqlType.getFieldCount(); i++) {
-            List<String> origin = origins == null ? null : origins.get(i);
-            b.add(fields.get(i).getName(), typeFactory.createType(
-                    getResultClass(typeFactory, schema, fields.get(i).getType(), origin)));
-        }
-
-        return b.build();
-    }
-
-    /**
-     * GetResultClass.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     *
-     * @param schema Schema.
-     * @param type   Logical column type.
-     * @param origin Column origin.
-     * @return Result type.
-     */
-    private static Type getResultClass(IgniteTypeFactory typeFactory, RelOptSchema schema, RelDataType type,
-            @Nullable List<String> origin) {
-        if (nullOrEmpty(origin)) {
-            return typeFactory.getResultClass(type);
-        }
-
-        RelOptTable table = schema.getTableForMember(origin.subList(0, origin.size() - 1));
-
-        assert table != null;
-
-        ColumnDescriptor fldDesc = table.unwrap(TableDescriptor.class).columnDescriptor(last(origin));
-
-        assert fldDesc != null;
-
-        return Commons.nativeTypeToClass(fldDesc.physicalType());
-    }
-
     /**
      * Function.
      * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
@@ -246,8 +186,6 @@ public class TypeUtils {
     public static <RowT> Function<RowT, RowT> resultTypeConverter(ExecutionContext<RowT> ectx, RelDataType resultType) {
         assert resultType.isStruct();
 
-        resultType = TypeUtils.getResultType(Commons.typeFactory(), ectx.unwrap(BaseQueryContext.class).catalogReader(), resultType, null);
-
         if (hasConvertableFields(resultType)) {
             RowHandler<RowT> handler = ectx.rowHandler();
             List<RelDataType> types = RelOptUtil.getFieldTypeList(resultType);
@@ -268,30 +206,21 @@ public class TypeUtils {
     }
 
     private static Function<Object, Object> fieldConverter(ExecutionContext<?> ectx, RelDataType fieldType) {
-        Type storageType = ectx.getTypeFactory().getJavaClass(fieldType);
+        Type storageType = ectx.getTypeFactory().getResultClass(fieldType);
 
-        if (isConvertableType(storageType)) {
+        if (isConvertableType(fieldType)) {
             return v -> fromInternal(ectx, v, storageType);
         }
 
         return Function.identity();
     }
 
-    /**
-     * IsConvertableType.
-     * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
-     */
-    public static boolean isConvertableType(Type type) {
-        return CONVERTABLE_TYPES.contains(type);
-    }
-
     /**
      * IsConvertableType.
      * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859
      */
     public static boolean isConvertableType(RelDataType type) {
-        return type instanceof RelDataTypeFactoryImpl.JavaType
-                && isConvertableType(((RelDataTypeFactoryImpl.JavaType) type).getJavaClass());
+        return CONVERTABLE_TYPES.contains(type.getSqlTypeName());
     }
 
     private static boolean hasConvertableFields(RelDataType resultType) {
@@ -314,21 +243,19 @@ public class TypeUtils {
     public static Object toInternal(ExecutionContext<?> ectx, Object val, Type storageType) {
         if (val == null) {
             return null;
-        } else if (storageType == java.sql.Date.class) {
-            return (int) (SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx))
-                    / DateTimeUtils.MILLIS_PER_DAY);
-        } else if (storageType == java.sql.Time.class) {
-            return (int) (SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx))
-                    % DateTimeUtils.MILLIS_PER_DAY);
-        } else if (storageType == Timestamp.class) {
-            return SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx));
-        } else if (storageType == java.util.Date.class) {
-            return SqlFunctions.toLong((java.util.Date) val, DataContext.Variable.TIME_ZONE.get(ectx));
+        } else if (storageType == LocalDate.class) {
+            return (int) ((LocalDate) val).toEpochDay();
+        } else if (storageType == LocalTime.class) {
+            return (int) (((LocalTime) val).toNanoOfDay() / 1000 / 1000 /* convert to millis */);
+        } else if (storageType == LocalDateTime.class) {
+            return ((LocalDateTime) val).toEpochSecond(ZoneOffset.UTC);
         } else if (storageType == Duration.class) {
             return TimeUnit.SECONDS.toMillis(((Duration) val).getSeconds())
                     + TimeUnit.NANOSECONDS.toMillis(((Duration) val).getNano());
         } else if (storageType == Period.class) {
             return (int) ((Period) val).toTotalMonths();
+        } else if (storageType == byte[].class) {
+            return new ByteString((byte[]) val);
         } else {
             return val;
         }
@@ -344,7 +271,7 @@ public class TypeUtils {
         } else if (storageType == LocalDate.class && val instanceof Integer) {
             return LocalDate.ofEpochDay((Integer) val);
         } else if (storageType == LocalTime.class && val instanceof Integer) {
-            return LocalTime.ofSecondOfDay((Integer) val / 1000);
+            return LocalTime.ofNanoOfDay(Long.valueOf((Integer) val) * 1000 * 1000 /* convert from millis */);
         } else if (storageType == LocalDateTime.class && (val instanceof Long)) {
             return LocalDateTime.ofEpochSecond((Long) val / 1000, (int) ((Long) val % 1000) * 1000 * 1000, ZoneOffset.UTC);
         } else if (storageType == Duration.class && val instanceof Long) {
@@ -374,6 +301,7 @@ public class TypeUtils {
             case INTEGER:
                 return SqlColumnType.INT32;
             case TIMESTAMP:
+                return SqlColumnType.DATETIME;
             case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
                 return SqlColumnType.TIMESTAMP;
             case BIGINT:
@@ -416,4 +344,80 @@ public class TypeUtils {
                 return null;
         }
     }
+
+    /**
+     * Converts a {@link NativeType native type} to {@link RelDataType relational type} with respect to the nullability flag.
+     *
+     * @param factory Type factory.
+     * @param nativeType A native type to convert.
+     * @param nullable A flag that specify whether the resulting type should be nullable or not.
+     * @return Relational type.
+     */
+    public static RelDataType native2relationalType(RelDataTypeFactory factory, NativeType nativeType, boolean nullable) {
+        return factory.createTypeWithNullability(native2relationalType(factory, nativeType), nullable);
+    }
+
+    /**
+     * Converts a {@link NativeType native type} to {@link RelDataType relational type}.
+     *
+     * @param factory Type factory.
+     * @param nativeType A native type to convert.
+     * @return Relational type.
+     */
+    public static RelDataType native2relationalType(RelDataTypeFactory factory, NativeType nativeType) {
+        switch (nativeType.spec()) {
+            case INT8:
+                return factory.createSqlType(SqlTypeName.TINYINT);
+            case INT16:
+                return factory.createSqlType(SqlTypeName.SMALLINT);
+            case INT32:
+                return factory.createSqlType(SqlTypeName.INTEGER);
+            case INT64:
+                return factory.createSqlType(SqlTypeName.BIGINT);
+            case FLOAT:
+                return factory.createSqlType(SqlTypeName.REAL);
+            case DOUBLE:
+                return factory.createSqlType(SqlTypeName.DOUBLE);
+            case DECIMAL:
+                assert nativeType instanceof DecimalNativeType;
+
+                var decimal = (DecimalNativeType) nativeType;
+
+                return factory.createSqlType(SqlTypeName.DECIMAL, decimal.precision(), decimal.scale());
+            case UUID:
+                throw new AssertionError("UUID is not supported yet");
+            case STRING: {
+                assert nativeType instanceof VarlenNativeType;
+
+                var varlen = (VarlenNativeType) nativeType;
+
+                return factory.createSqlType(SqlTypeName.VARCHAR, varlen.length());
+            }
+            case BYTES: {
+                assert nativeType instanceof VarlenNativeType;
+
+                var varlen = (VarlenNativeType) nativeType;
+
+                return factory.createSqlType(SqlTypeName.BINARY, varlen.length());
+            }
+            case BITMASK:
+                throw new AssertionError("BITMASK is not supported yet");
+            case NUMBER:
+                assert nativeType instanceof NumberNativeType;
+
+                var number = (NumberNativeType) nativeType;
+
+                return factory.createSqlType(SqlTypeName.DECIMAL, number.precision(), 0);
+            case DATE:
+                return factory.createSqlType(SqlTypeName.DATE);
+            case TIME:
+                return factory.createSqlType(SqlTypeName.TIME);
+            case TIMESTAMP:
+                return factory.createSqlType(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+            case DATETIME:
+                return factory.createSqlType(SqlTypeName.TIMESTAMP);
+            default:
+                throw new IllegalStateException("Unexpected native type " + nativeType);
+        }
+    }
 }
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
index 245406ae6..4397fe3f3 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/AbstractPlannerTest.java
@@ -92,6 +92,7 @@ import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalIndexScan;
 import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.schema.ColumnDescriptor;
+import org.apache.ignite.internal.sql.engine.schema.DefaultValueStrategy;
 import org.apache.ignite.internal.sql.engine.schema.IgniteIndex;
 import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
@@ -1024,6 +1025,12 @@ public abstract class AbstractPlannerTest extends IgniteAbstractTest {
             this.name = name;
         }
 
+        /** {@inheritDoc} */
+        @Override
+        public boolean nullable() {
+            return true;
+        }
+
         /** {@inheritDoc} */
         @Override
         public boolean key() {
@@ -1032,8 +1039,8 @@ public abstract class AbstractPlannerTest extends IgniteAbstractTest {
 
         /** {@inheritDoc} */
         @Override
-        public boolean hasDefaultValue() {
-            return false;
+        public DefaultValueStrategy defaultStrategy() {
+            return DefaultValueStrategy.DEFAULT_NULL;
         }
 
         /** {@inheritDoc} */
@@ -1054,12 +1061,6 @@ public abstract class AbstractPlannerTest extends IgniteAbstractTest {
             return idx;
         }
 
-        /** {@inheritDoc} */
-        @Override
-        public RelDataType logicalType(IgniteTypeFactory f) {
-            throw new AssertionError();
-        }
-
         /** {@inheritDoc} */
         @Override
         public NativeType physicalType() {