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

[ignite-3] branch main updated: IGNITE-17052 Java thin: Implement query metadata (#891)

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

ptupitsyn 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 4119f80b6 IGNITE-17052 Java thin: Implement query metadata (#891)
4119f80b6 is described below

commit 4119f80b6ddcb56af522ba61f038b82c64382da7
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Wed Jun 22 14:10:01 2022 +0300

    IGNITE-17052 Java thin: Implement query metadata (#891)
    
    * Pass result set metadata to the client.
    * Optimize row serialization using known column types.
---
 .../internal/sql/SqlColumnTypeConverter.java       | 104 +++++++++++++
 .../client/proto/ClientMessageUnpacker.java        |  39 +++++
 .../client/proto/ClientSqlColumnTypeConverter.java | 165 +++++++++++++++++++++
 .../proto/ClientMessagePackerUnpackerTest.java     |  32 ++++
 .../proto/ClientSqlColumnTypeConverterTest.java    |  38 +++++
 .../client/handler/JdbcQueryEventHandlerImpl.java  |   4 +-
 .../handler/requests/sql/ClientSqlCommon.java      | 109 +++++++++++++-
 .../requests/sql/ClientSqlExecuteRequest.java      |  88 ++++++++---
 .../internal/client/sql/ClientAsyncResultSet.java  |  77 +++++++++-
 .../internal/client/sql/ClientColumnMetadata.java  |  52 ++++---
 .../internal/client/sql/ClientColumnOrigin.java    |  85 +++++++++++
 .../client/sql/ClientResultSetMetadata.java        |   3 +-
 .../ignite/internal/client/sql/ClientSession.java  |  10 +-
 .../ignite/internal/client/sql/ClientSqlRow.java   |  20 ++-
 .../org/apache/ignite/client/ClientSqlTest.java    | 124 ++++++++++++++--
 .../ignite/client/fakes/FakeAsyncResultSet.java    | 103 +++++++++++--
 .../ignite/client/fakes/FakeColumnMetadata.java    |  45 ++++--
 .../runner/app/client/ItThinClientSqlTest.java     |  37 ++++-
 .../sql/api/ItSqlClientAsynchronousApiTest.java    |  12 --
 .../internal/sql/engine/util/QueryChecker.java     |   3 +-
 .../internal/sql/api/ColumnMetadataImpl.java       |   4 +-
 .../ignite/internal/sql/engine/util/Commons.java   |  71 ---------
 22 files changed, 1027 insertions(+), 198 deletions(-)

diff --git a/modules/api/src/main/java/org/apache/ignite/internal/sql/SqlColumnTypeConverter.java b/modules/api/src/main/java/org/apache/ignite/internal/sql/SqlColumnTypeConverter.java
new file mode 100644
index 000000000..dadf62d4d
--- /dev/null
+++ b/modules/api/src/main/java/org/apache/ignite/internal/sql/SqlColumnTypeConverter.java
@@ -0,0 +1,104 @@
+/*
+ * 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.sql;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.sql.SqlColumnType;
+
+/**
+ * Column type converter.
+ */
+public class SqlColumnTypeConverter {
+    /**
+     * Column type to Java class.
+     */
+    public static Class<?> columnTypeToClass(SqlColumnType type) {
+        assert type != null;
+
+        switch (type) {
+            case BOOLEAN:
+                return Boolean.class;
+
+            case INT8:
+                return Byte.class;
+
+            case INT16:
+                return Short.class;
+
+            case INT32:
+                return Integer.class;
+
+            case INT64:
+                return Long.class;
+
+            case FLOAT:
+                return Float.class;
+
+            case DOUBLE:
+                return Double.class;
+
+            case NUMBER:
+                return BigInteger.class;
+
+            case DECIMAL:
+                return BigDecimal.class;
+
+            case UUID:
+                return UUID.class;
+
+            case STRING:
+                return String.class;
+
+            case BYTE_ARRAY:
+                return byte[].class;
+
+            case BITMASK:
+                return BitSet.class;
+
+            case DATE:
+                return LocalDate.class;
+
+            case TIME:
+                return LocalTime.class;
+
+            case DATETIME:
+                return LocalDateTime.class;
+
+            case TIMESTAMP:
+                return Instant.class;
+
+            case PERIOD:
+                return Period.class;
+
+            case DURATION:
+                return Duration.class;
+
+            default:
+                throw new IllegalArgumentException("Unsupported type " + type);
+        }
+    }
+}
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
index 973dca047..3201a9a75 100644
--- a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageUnpacker.java
@@ -145,6 +145,45 @@ public class ClientMessageUnpacker implements AutoCloseable {
         }
     }
 
+    /**
+     * Reads an int.
+     *
+     * @param defaultValue Default value to return when type is not from int family.
+     * @return the int value.
+     * @throws MessageTypeException when value is not MessagePack Integer type.
+     */
+    public int tryUnpackInt(int defaultValue) {
+        assert refCnt > 0 : "Unpacker is closed";
+
+        byte code = buf.readByte();
+
+        if (Code.isFixInt(code)) {
+            return code;
+        }
+
+        switch (code) {
+            case Code.UINT8:
+                return buf.readUnsignedByte();
+
+            case Code.INT8:
+                return buf.readByte();
+
+            case Code.UINT16:
+                return buf.readUnsignedShort();
+
+            case Code.INT16:
+                return buf.readShort();
+
+            case Code.UINT32:
+            case Code.INT32:
+                return buf.readInt();
+
+            default:
+                buf.readerIndex(buf.readerIndex() - 1);
+                return defaultValue;
+        }
+    }
+
     /**
      * Reads a string.
      *
diff --git a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverter.java b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverter.java
new file mode 100644
index 000000000..cd2030d70
--- /dev/null
+++ b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.client.proto;
+
+import org.apache.ignite.sql.SqlColumnType;
+
+/**
+ * SQL column type utils.
+ */
+public class ClientSqlColumnTypeConverter {
+    /**
+     * Converts column type to wire code.
+     *
+     * @param columnType Column type.
+     * @return Wire code.
+     */
+    public static int columnTypeToOrdinal(SqlColumnType columnType) {
+        switch (columnType) {
+            case BOOLEAN:
+                return 0;
+
+            case INT8:
+                return 1;
+
+            case INT16:
+                return 2;
+
+            case INT32:
+                return 3;
+
+            case INT64:
+                return 4;
+
+            case FLOAT:
+                return 5;
+
+            case DOUBLE:
+                return 6;
+
+            case DECIMAL:
+                return 7;
+
+            case DATE:
+                return 8;
+
+            case TIME:
+                return 9;
+
+            case DATETIME:
+                return 10;
+
+            case TIMESTAMP:
+                return 11;
+
+            case UUID:
+                return 12;
+
+            case BITMASK:
+                return 13;
+
+            case STRING:
+                return 14;
+
+            case BYTE_ARRAY:
+                return 15;
+
+            case PERIOD:
+                return 16;
+
+            case DURATION:
+                return 17;
+
+            case NUMBER:
+                return 18;
+
+            default:
+                throw new IllegalArgumentException("Invalid column type: " + columnType);
+        }
+    }
+
+    /**
+     * Converts wire type code to column type.
+     *
+     * @param ordinal Type code.
+     * @return Column type.
+     */
+    public static SqlColumnType ordinalToColumnType(int ordinal) {
+        switch (ordinal) {
+            case 0:
+                return SqlColumnType.BOOLEAN;
+
+            case 1:
+                return SqlColumnType.INT8;
+
+            case 2:
+                return SqlColumnType.INT16;
+
+            case 3:
+                return SqlColumnType.INT32;
+
+            case 4:
+                return SqlColumnType.INT64;
+
+            case 5:
+                return SqlColumnType.FLOAT;
+
+            case 6:
+                return SqlColumnType.DOUBLE;
+
+            case 7:
+                return SqlColumnType.DECIMAL;
+
+            case 8:
+                return SqlColumnType.DATE;
+
+            case 9:
+                return SqlColumnType.TIME;
+
+            case 10:
+                return SqlColumnType.DATETIME;
+
+            case 11:
+                return SqlColumnType.TIMESTAMP;
+
+            case 12:
+                return SqlColumnType.UUID;
+
+            case 13:
+                return SqlColumnType.BITMASK;
+
+            case 14:
+                return SqlColumnType.STRING;
+
+            case 15:
+                return SqlColumnType.BYTE_ARRAY;
+
+            case 16:
+                return SqlColumnType.PERIOD;
+
+            case 17:
+                return SqlColumnType.DURATION;
+
+            case 18:
+                return SqlColumnType.NUMBER;
+
+            default:
+                throw new IllegalArgumentException("Invalid column type code: " + ordinal);
+        }
+    }
+}
diff --git a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
index db97c9c35..6346a8de1 100644
--- a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
+++ b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientMessagePackerUnpackerTest.java
@@ -346,4 +346,36 @@ public class ClientMessagePackerUnpackerTest {
             }
         }
     }
+
+    @Test
+    public void testTryUnpackInt() {
+        try (var packer = new ClientMessagePacker(PooledByteBufAllocator.DEFAULT.directBuffer())) {
+            packer.packInt(1);
+            packer.packInt(Byte.MAX_VALUE);
+            packer.packInt(Short.MAX_VALUE);
+            packer.packInt(Integer.MAX_VALUE);
+            packer.packNoValue();
+            packer.packString("s");
+
+            var buf = packer.getBuffer();
+
+            byte[] data = new byte[buf.readableBytes()];
+            buf.readBytes(data);
+
+            try (var unpacker = new ClientMessageUnpacker(Unpooled.wrappedBuffer(data))) {
+                unpacker.skipValues(4);
+
+                assertEquals(1, unpacker.tryUnpackInt(-1));
+                assertEquals(Byte.MAX_VALUE, unpacker.tryUnpackInt(-1));
+                assertEquals(Short.MAX_VALUE, unpacker.tryUnpackInt(-1));
+                assertEquals(Integer.MAX_VALUE, unpacker.tryUnpackInt(-1));
+
+                assertEquals(-1, unpacker.tryUnpackInt(-1));
+                assertTrue(unpacker.tryUnpackNoValue());
+
+                assertEquals(-2, unpacker.tryUnpackInt(-2));
+                assertEquals("s", unpacker.unpackString());
+            }
+        }
+    }
 }
diff --git a/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverterTest.java b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverterTest.java
new file mode 100644
index 000000000..3655ce46a
--- /dev/null
+++ b/modules/client-common/src/test/java/org/apache/ignite/internal/client/proto/ClientSqlColumnTypeConverterTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.client.proto;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.ignite.sql.SqlColumnType;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests column type converter.
+ */
+public class ClientSqlColumnTypeConverterTest {
+    @Test
+    public void testConvertAllTypes() {
+        for (SqlColumnType columnType : SqlColumnType.values()) {
+            int ordinal = ClientSqlColumnTypeConverter.columnTypeToOrdinal(columnType);
+            SqlColumnType resColumnType = ClientSqlColumnTypeConverter.ordinalToColumnType(ordinal);
+
+            assertEquals(columnType, resColumnType);
+        }
+    }
+}
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
index f055b7d21..ea5d18201 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/JdbcQueryEventHandlerImpl.java
@@ -57,6 +57,7 @@ import org.apache.ignite.internal.jdbc.proto.event.QueryFetchRequest;
 import org.apache.ignite.internal.jdbc.proto.event.QueryFetchResult;
 import org.apache.ignite.internal.jdbc.proto.event.QuerySingleResult;
 import org.apache.ignite.internal.jdbc.proto.event.Response;
+import org.apache.ignite.internal.sql.SqlColumnTypeConverter;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.QueryContext;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
@@ -64,7 +65,6 @@ import org.apache.ignite.internal.sql.engine.QueryValidator;
 import org.apache.ignite.internal.sql.engine.exec.QueryValidationException;
 import org.apache.ignite.internal.sql.engine.prepare.QueryPlan;
 import org.apache.ignite.internal.sql.engine.prepare.QueryPlan.Type;
-import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ColumnMetadata.ColumnOrigin;
@@ -336,7 +336,7 @@ public class JdbcQueryEventHandlerImpl implements JdbcQueryEventHandler {
                 schemaName,
                 tblName,
                 colName,
-                Commons.columnTypeToClass(fldMeta.type()),
+                SqlColumnTypeConverter.columnTypeToClass(fldMeta.type()),
                 fldMeta.precision(),
                 fldMeta.scale(),
                 fldMeta.nullable()
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlCommon.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlCommon.java
index 697c7f200..f5797ad66 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlCommon.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlCommon.java
@@ -17,9 +17,14 @@
 
 package org.apache.ignite.client.handler.requests.sql;
 
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.Period;
 import java.util.List;
 import org.apache.ignite.internal.client.proto.ClientMessagePacker;
 import org.apache.ignite.sql.ColumnMetadata;
+import org.apache.ignite.sql.ResultSetMetadata;
+import org.apache.ignite.sql.SqlColumnType;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.async.AsyncResultSet;
 
@@ -28,14 +33,16 @@ import org.apache.ignite.sql.async.AsyncResultSet;
  */
 class ClientSqlCommon {
     static void packCurrentPage(ClientMessagePacker out, AsyncResultSet asyncResultSet) {
-        List<ColumnMetadata> cols = asyncResultSet.metadata().columns();
+        ResultSetMetadata meta = asyncResultSet.metadata();
+        assert meta != null : "Metadata can't be null when row set is present.";
+
+        List<ColumnMetadata> cols = meta.columns();
 
         out.packArrayHeader(asyncResultSet.currentPageSize());
 
         for (SqlRow row : asyncResultSet.currentPage()) {
             for (int i = 0; i < cols.size(); i++) {
-                // TODO: IGNITE-17052 pack only the value according to the known type.
-                out.packObjectWithType(row.value(i));
+                packValue(out, cols.get(i).type(), row, i);
             }
         }
 
@@ -43,4 +50,100 @@ class ClientSqlCommon {
             asyncResultSet.closeAsync();
         }
     }
+
+    private static void packValue(ClientMessagePacker out, SqlColumnType colType, SqlRow row, int idx) {
+        if (row.value(idx) == null) {
+            out.packNil();
+            return;
+        }
+
+        switch (colType) {
+            case BOOLEAN:
+                out.packBoolean(row.value(idx));
+                break;
+
+            case INT8:
+                out.packByte(row.byteValue(idx));
+                break;
+
+            case INT16:
+                out.packShort(row.shortValue(idx));
+                break;
+
+            case INT32:
+                out.packInt(row.intValue(idx));
+                break;
+
+            case INT64:
+                out.packLong(row.longValue(idx));
+                break;
+
+            case FLOAT:
+                out.packFloat(row.floatValue(idx));
+                break;
+
+            case DOUBLE:
+                out.packDouble(row.doubleValue(idx));
+                break;
+
+            case DECIMAL:
+                out.packDecimal(row.value(idx));
+                break;
+
+            case DATE:
+                out.packDate(row.dateValue(idx));
+                break;
+
+            case TIME:
+                out.packTime(row.timeValue(idx));
+                break;
+
+            case DATETIME:
+                out.packDateTime(row.datetimeValue(idx));
+                break;
+
+            case TIMESTAMP:
+                out.packTimestamp(row.timestampValue(idx));
+                break;
+
+            case UUID:
+                out.packUuid(row.uuidValue(idx));
+                break;
+
+            case BITMASK:
+                out.packBitSet(row.bitmaskValue(idx));
+                break;
+
+            case STRING:
+                out.packString(row.stringValue(idx));
+                break;
+
+            case BYTE_ARRAY:
+                byte[] bytes = row.value(idx);
+                out.packBinaryHeader(bytes.length);
+                out.writePayload(bytes);
+                break;
+
+            case PERIOD:
+                Period period = row.value(idx);
+                out.packInt(period.getYears());
+                out.packInt(period.getMonths());
+                out.packInt(period.getDays());
+                break;
+
+            case DURATION:
+                Duration duration = row.value(idx);
+                out.packLong(duration.getSeconds());
+                out.packInt(duration.getNano());
+                break;
+
+            case NUMBER:
+                BigInteger number = row.value(idx);
+                out.packBigInteger(number);
+                break;
+
+            default:
+                throw new UnsupportedOperationException("Unsupported column type: " + colType);
+        }
+    }
 }
diff --git a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
index f9997e47a..b213f87d0 100644
--- a/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
+++ b/modules/client-handler/src/main/java/org/apache/ignite/client/handler/requests/sql/ClientSqlExecuteRequest.java
@@ -20,7 +20,9 @@ package org.apache.ignite.client.handler.requests.sql;
 import static org.apache.ignite.client.handler.requests.sql.ClientSqlCommon.packCurrentPage;
 import static org.apache.ignite.client.handler.requests.table.ClientTableCommon.readTx;
 
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.TimeUnit;
@@ -28,11 +30,14 @@ import org.apache.ignite.client.handler.ClientResource;
 import org.apache.ignite.client.handler.ClientResourceRegistry;
 import org.apache.ignite.internal.client.proto.ClientMessagePacker;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.client.proto.ClientSqlColumnTypeConverter;
 import org.apache.ignite.internal.util.ArrayUtils;
 import org.apache.ignite.lang.IgniteInternalCheckedException;
 import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.sql.ColumnMetadata;
+import org.apache.ignite.sql.ColumnMetadata.ColumnOrigin;
 import org.apache.ignite.sql.IgniteSql;
+import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.Session;
 import org.apache.ignite.sql.Session.SessionBuilder;
 import org.apache.ignite.sql.Statement;
@@ -93,24 +98,7 @@ public class ClientSqlExecuteRequest {
         out.packBoolean(asyncResultSet.wasApplied());
         out.packLong(asyncResultSet.affectedRows());
 
-        // Pack metadata.
-        if (asyncResultSet.metadata() == null || asyncResultSet.metadata().columns() == null) {
-            out.packArrayHeader(0);
-        } else {
-            List<ColumnMetadata> cols = asyncResultSet.metadata().columns();
-            out.packArrayHeader(cols.size());
-
-            for (int i = 0; i < cols.size(); i++) {
-                ColumnMetadata col = cols.get(i);
-                out.packString(col.name());
-                out.packBoolean(col.nullable());
-
-                // TODO: IGNITE-17052 Implement query metadata.
-                // Ideally we only need the type code here.
-                out.packString(col.valueClass().getName());
-                out.packObjectWithType(null /*col.type()*/);
-            }
-        }
+        packMeta(out, asyncResultSet.metadata());
 
         // Pack first page.
         if (asyncResultSet.hasRowSet()) {
@@ -155,6 +143,10 @@ public class ClientSqlExecuteRequest {
     }
 
     private static Object[] readArguments(ClientMessageUnpacker in) {
+        if (in.tryUnpackNil()) {
+            return null;
+        }
+
         int size = in.unpackArrayHeader();
 
         if (size == 0) {
@@ -169,4 +161,64 @@ public class ClientSqlExecuteRequest {
 
         return res;
     }
+
+    private static void packMeta(ClientMessagePacker out, ResultSetMetadata meta) {
+        // TODO IGNITE-17179 metadata caching - avoid sending same meta over and over.
+        if (meta == null || meta.columns() == null) {
+            out.packArrayHeader(0);
+            return;
+        }
+
+        List<ColumnMetadata> cols = meta.columns();
+        out.packArrayHeader(cols.size());
+
+        // In many cases there are multiple columns from the same table.
+        // Schema is the same for all columns in most cases.
+        // When table or schema name was packed before, pack index instead of string.
+        Map<String, Integer> schemas = new HashMap<>();
+        Map<String, Integer> tables = new HashMap<>();
+
+        for (int i = 0; i < cols.size(); i++) {
+            ColumnMetadata col = cols.get(i);
+
+            out.packString(col.name());
+            out.packBoolean(col.nullable());
+            out.packInt(ClientSqlColumnTypeConverter.columnTypeToOrdinal(col.type()));
+            out.packInt(col.scale());
+            out.packInt(col.precision());
+
+            ColumnOrigin origin = col.origin();
+
+            if (origin == null) {
+                out.packBoolean(false);
+                continue;
+            }
+
+            out.packBoolean(true);
+
+            if (col.name().equals(origin.columnName())) {
+                out.packNil();
+            } else {
+                out.packString(origin.columnName());
+            }
+
+            Integer schemaIdx = schemas.get(origin.schemaName());
+
+            if (schemaIdx == null) {
+                schemas.put(origin.schemaName(), i);
+                out.packString(origin.schemaName());
+            } else {
+                out.packInt(schemaIdx);
+            }
+
+            Integer tableIdx = tables.get(origin.tableName());
+
+            if (tableIdx == null) {
+                tables.put(origin.tableName(), i);
+                out.packString(origin.tableName());
+            } else {
+                out.packInt(tableIdx);
+            }
+        }
+    }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientAsyncResultSet.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientAsyncResultSet.java
index b643a8cd7..af037d75c 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientAsyncResultSet.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientAsyncResultSet.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.client.sql;
 
+import java.time.Duration;
+import java.time.Period;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -28,6 +30,7 @@ import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
 import org.apache.ignite.internal.client.proto.ClientOp;
 import org.apache.ignite.sql.NoRowSetExpectedException;
 import org.apache.ignite.sql.ResultSetMetadata;
+import org.apache.ignite.sql.SqlColumnType;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.async.AsyncResultSet;
 import org.jetbrains.annotations.Nullable;
@@ -188,13 +191,81 @@ class ClientAsyncResultSet implements AsyncResultSet {
             var row = new ArrayList<>(rowSize);
 
             for (int j = 0; j < rowSize; j++) {
-                // TODO: IGNITE-17052 Unpack according to metadata type.
-                row.add(in.unpackObjectWithType());
+                var col = metadata.columns().get(j);
+                row.add(readValue(in, col.type()));
             }
 
-            res.add(new ClientSqlRow(row));
+            res.add(new ClientSqlRow(row, metadata));
         }
 
         rows = Collections.unmodifiableList(res);
     }
+
+    private static Object readValue(ClientMessageUnpacker in, SqlColumnType colType) {
+        if (in.tryUnpackNil()) {
+            return null;
+        }
+
+        switch (colType) {
+            case BOOLEAN:
+                return in.unpackBoolean();
+
+            case INT8:
+                return in.unpackByte();
+
+            case INT16:
+                return in.unpackShort();
+
+            case INT32:
+                return in.unpackInt();
+
+            case INT64:
+                return in.unpackLong();
+
+            case FLOAT:
+                return in.unpackFloat();
+
+            case DOUBLE:
+                return in.unpackDouble();
+
+            case DECIMAL:
+                return in.unpackDecimal();
+
+            case DATE:
+                return in.unpackDate();
+
+            case TIME:
+                return in.unpackTime();
+
+            case DATETIME:
+                return in.unpackDateTime();
+
+            case TIMESTAMP:
+                return in.unpackTimestamp();
+
+            case UUID:
+                return in.unpackUuid();
+
+            case BITMASK:
+                return in.unpackBitSet();
+
+            case STRING:
+                return in.unpackString();
+
+            case BYTE_ARRAY:
+                return in.readPayload(in.unpackBinaryHeader());
+
+            case PERIOD:
+                return Period.of(in.unpackInt(), in.unpackInt(), in.unpackInt());
+
+            case DURATION:
+                return Duration.ofSeconds(in.unpackLong(), in.unpackInt());
+
+            case NUMBER:
+                return in.unpackBigInteger();
+
+            default:
+                throw new UnsupportedOperationException("Unsupported column type: " + colType);
+        }
+    }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
index dd9dfaf7a..654d5a26c 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnMetadata.java
@@ -17,8 +17,10 @@
 
 package org.apache.ignite.internal.client.sql;
 
-import org.apache.ignite.client.IgniteClientException;
+import java.util.List;
 import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.client.proto.ClientSqlColumnTypeConverter;
+import org.apache.ignite.internal.sql.SqlColumnTypeConverter;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.SqlColumnType;
 
@@ -29,29 +31,38 @@ public class ClientColumnMetadata implements ColumnMetadata {
     /** Name. */
     private final String name;
 
-    /** Value class. */
-    private final Class<?> valueClass;
-
     /** Type. */
-    private final Object type;
+    private final SqlColumnType type;
 
     /** Nullable flag. */
     private final boolean nullable;
 
+    /** Column precision. */
+    private final int precision;
+
+    /** Column scale. */
+    private final int scale;
+
+    /** Origin of the result's column. */
+    private final ColumnOrigin origin;
+
     /**
      * Constructor.
      *
      * @param unpacker Unpacker.
+     * @param prevColumns Previous columns.
      */
-    public ClientColumnMetadata(ClientMessageUnpacker unpacker) {
-        try {
-            name = unpacker.unpackString();
-            nullable = unpacker.unpackBoolean();
-            valueClass = Class.forName(unpacker.unpackString());
-            // TODO: IGNITE-17052 Unpack according to metadata type.
-            type = unpacker.unpackObjectWithType();
-        } catch (ClassNotFoundException e) {
-            throw new IgniteClientException(e.getMessage(), e);
+    public ClientColumnMetadata(ClientMessageUnpacker unpacker, List<ColumnMetadata> prevColumns) {
+        name = unpacker.unpackString();
+        nullable = unpacker.unpackBoolean();
+        type = ClientSqlColumnTypeConverter.ordinalToColumnType(unpacker.unpackInt());
+        scale = unpacker.unpackInt();
+        precision = unpacker.unpackInt();
+
+        if (unpacker.unpackBoolean()) {
+            origin = new ClientColumnOrigin(unpacker, name, prevColumns);
+        } else {
+            origin = null;
         }
     }
 
@@ -64,27 +75,25 @@ public class ClientColumnMetadata implements ColumnMetadata {
     /** {@inheritDoc} */
     @Override
     public Class<?> valueClass() {
-        return valueClass;
+        return SqlColumnTypeConverter.columnTypeToClass(type);
     }
 
     /** {@inheritDoc} */
     @Override
     public SqlColumnType type() {
-        return (SqlColumnType) type;
+        return type;
     }
 
     /** {@inheritDoc} */
     @Override
     public int precision() {
-        // TODO: IGNITE-17052
-        return -1;
+        return precision;
     }
 
     /** {@inheritDoc} */
     @Override
     public int scale() {
-        // TODO: IGNITE-17052
-        return -1;
+        return scale;
     }
 
     /** {@inheritDoc} */
@@ -96,7 +105,6 @@ public class ClientColumnMetadata implements ColumnMetadata {
     /** {@inheritDoc} */
     @Override
     public ColumnOrigin origin() {
-        // TODO: IGNITE-17052
-        return null;
+        return origin;
     }
 }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnOrigin.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnOrigin.java
new file mode 100644
index 000000000..e9050595b
--- /dev/null
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientColumnOrigin.java
@@ -0,0 +1,85 @@
+/*
+ * 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.client.sql;
+
+import java.util.List;
+import org.apache.ignite.internal.client.proto.ClientMessageUnpacker;
+import org.apache.ignite.internal.tostring.S;
+import org.apache.ignite.sql.ColumnMetadata;
+
+/**
+ * Column origin.
+ */
+public class ClientColumnOrigin implements ColumnMetadata.ColumnOrigin {
+    /** Schema name. */
+    private final String schemaName;
+
+    /** Table name. */
+    private final String tableName;
+
+    /** Column name. */
+    private final String columnName;
+
+    /**
+     * Constructor.
+     */
+    public ClientColumnOrigin(
+            ClientMessageUnpacker unpacker,
+            String cursorColumnName,
+            List<ColumnMetadata> prevColumns) {
+        this.columnName = unpacker.tryUnpackNil() ? cursorColumnName : unpacker.unpackString();
+
+        int schemaNameIdx = unpacker.tryUnpackInt(-1);
+
+        //noinspection ConstantConditions
+        this.schemaName = schemaNameIdx == -1
+                ? unpacker.unpackString()
+                : prevColumns.get(schemaNameIdx).origin().schemaName();
+
+        int tableNameIdx = unpacker.tryUnpackInt(-1);
+
+        //noinspection ConstantConditions
+        this.tableName = tableNameIdx == -1
+                ? unpacker.unpackString()
+                : prevColumns.get(tableNameIdx).origin().tableName();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String schemaName() {
+        return schemaName;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String tableName() {
+        return tableName;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String columnName() {
+        return columnName;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return S.toString(ClientColumnOrigin.class,  this);
+    }
+}
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientResultSetMetadata.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientResultSetMetadata.java
index a6d01bfc3..7b81945e3 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientResultSetMetadata.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientResultSetMetadata.java
@@ -43,12 +43,13 @@ class ClientResultSetMetadata implements ResultSetMetadata {
      */
     public ClientResultSetMetadata(ClientMessageUnpacker unpacker) {
         var size = unpacker.unpackArrayHeader();
+        assert size > 0 : "ResultSetMetadata should not be empty.";
 
         var columns = new ArrayList<ColumnMetadata>(size);
         columnIndices =  new HashMap<>(size);
 
         for (int i = 0; i < size; i++) {
-            ClientColumnMetadata column = new ClientColumnMetadata(unpacker);
+            ClientColumnMetadata column = new ClientColumnMetadata(unpacker, columns);
             columns.add(column);
             columnIndices.put(column.name(), i);
         }
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
index c0de66705..d6de231b9 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSession.java
@@ -114,15 +114,7 @@ public class ClientSession implements Session {
             w.out().packObject(clientStatement.query());
             w.out().packBoolean(clientStatement.prepared());
 
-            if (arguments == null) {
-                w.out().packArrayHeader(0);
-            } else {
-                w.out().packArrayHeader(arguments.length);
-
-                for (int i = 0; i < arguments.length; i++) {
-                    w.out().packObjectWithType(arguments[i]);
-                }
-            }
+            w.out().packObjectArray(arguments);
         }, r -> new ClientAsyncResultSet(r.clientChannel(), r.in()));
     }
 
diff --git a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSqlRow.java b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSqlRow.java
index 99f5865b2..e489cceda 100644
--- a/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSqlRow.java
+++ b/modules/client/src/main/java/org/apache/ignite/internal/client/sql/ClientSqlRow.java
@@ -34,18 +34,27 @@ import org.jetbrains.annotations.NotNull;
 /**
  * Client SQL row.
  */
+@SuppressWarnings("unchecked")
 public class ClientSqlRow implements SqlRow {
     /** Row. */
     private final List<Object> row;
 
+    /** Meta. */
+    private final ResultSetMetadata metadata;
+
     /**
      * Constructor.
      *
      * @param row Row.
+     * @param meta Meta.
      */
-    ClientSqlRow(List<Object> row) {
+    public ClientSqlRow(List<Object> row, ResultSetMetadata meta) {
+        assert row != null;
+        assert meta != null;
+
         //noinspection AssignmentOrReturnOfFieldWithMutableType
         this.row = row;
+        this.metadata = meta;
     }
 
     /** {@inheritDoc} */
@@ -57,15 +66,13 @@ public class ClientSqlRow implements SqlRow {
     /** {@inheritDoc} */
     @Override
     public String columnName(int columnIndex) {
-        // TODO: IGNITE-17052
-        throw new UnsupportedOperationException("Not implemented yet.");
+        return metadata.columns().get(columnIndex).name();
     }
 
     /** {@inheritDoc} */
     @Override
     public int columnIndex(@NotNull String columnName) {
-        // TODO: IGNITE-17052
-        throw new UnsupportedOperationException("Not implemented yet.");
+        return metadata.indexOf(columnName);
     }
 
     private int columnIndexChecked(@NotNull String columnName) {
@@ -282,7 +289,6 @@ public class ClientSqlRow implements SqlRow {
     /** {@inheritDoc} */
     @Override
     public ResultSetMetadata metadata() {
-        // TODO: IGNITE-17052
-        throw new UnsupportedOperationException("Not implemented yet.");
+        return metadata;
     }
 }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/ClientSqlTest.java b/modules/client/src/test/java/org/apache/ignite/client/ClientSqlTest.java
index 30b58fe77..660ba32aa 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/ClientSqlTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/ClientSqlTest.java
@@ -19,14 +19,29 @@ package org.apache.ignite.client;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
+import java.util.BitSet;
 import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
+import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ResultSet;
+import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.Session;
+import org.apache.ignite.sql.SqlColumnType;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.Statement;
 import org.apache.ignite.sql.async.AsyncResultSet;
@@ -67,8 +82,8 @@ public class ClientSqlTest extends AbstractClientTableTest {
                 .defaultSchema("SCHEMA1")
                 .defaultTimeout(123, TimeUnit.SECONDS)
                 .defaultPageSize(234)
-                .property("prop1", 1)
-                .property("prop2", 2)
+                .property("prop1", "1")
+                .property("prop2", "2")
                 .build();
 
         AsyncResultSet resultSet = session.executeAsync(null, "SELECT PROPS").join();
@@ -77,10 +92,10 @@ public class ClientSqlTest extends AbstractClientTableTest {
                 .collect(Collectors.toMap(x -> x.stringValue(0), x -> x.value(1)));
 
         assertEquals("SCHEMA1", props.get("schema"));
-        assertEquals(123000L, props.get("timeout"));
-        assertEquals(234, props.get("pageSize"));
-        assertEquals(1, props.get("prop1"));
-        assertEquals(2, props.get("prop2"));
+        assertEquals("123000", props.get("timeout"));
+        assertEquals("234", props.get("pageSize"));
+        assertEquals("1", props.get("prop1"));
+        assertEquals("2", props.get("prop2"));
     }
 
     @Test
@@ -89,8 +104,8 @@ public class ClientSqlTest extends AbstractClientTableTest {
                 .defaultSchema("SCHEMA1")
                 .defaultTimeout(123, TimeUnit.SECONDS)
                 .defaultPageSize(234)
-                .property("prop1", 1)
-                .property("prop2", 2)
+                .property("prop1", "1")
+                .property("prop2", "2")
                 .build();
 
         Statement statement = client.sql().statementBuilder()
@@ -98,8 +113,8 @@ public class ClientSqlTest extends AbstractClientTableTest {
                 .defaultSchema("SCHEMA2")
                 .queryTimeout(124, TimeUnit.SECONDS)
                 .pageSize(235)
-                .property("prop2", 22)
-                .property("prop3", 3)
+                .property("prop2", "22")
+                .property("prop3", "3")
                 .build();
 
         AsyncResultSet resultSet = session.executeAsync(null, statement).join();
@@ -108,10 +123,89 @@ public class ClientSqlTest extends AbstractClientTableTest {
                 .collect(Collectors.toMap(x -> x.stringValue(0), x -> x.value(1)));
 
         assertEquals("SCHEMA2", props.get("schema"));
-        assertEquals(124000L, props.get("timeout"));
-        assertEquals(235, props.get("pageSize"));
-        assertEquals(1, props.get("prop1"));
-        assertEquals(22, props.get("prop2"));
-        assertEquals(3, props.get("prop3"));
+        assertEquals("124000", props.get("timeout"));
+        assertEquals("235", props.get("pageSize"));
+        assertEquals("1", props.get("prop1"));
+        assertEquals("22", props.get("prop2"));
+        assertEquals("3", props.get("prop3"));
+    }
+
+    @Test
+    public void testMetadata() {
+        Session session = client.sql().createSession();
+        ResultSet resultSet = session.execute(null, "SELECT META");
+        ResultSetMetadata meta = resultSet.metadata();
+        SqlRow row = resultSet.next();
+
+        assertNotNull(meta);
+        assertSame(meta, row.metadata());
+
+        for (int i = 0; i < meta.columns().size(); i++) {
+            ColumnMetadata col = meta.columns().get(i);
+            assertEquals(i, meta.indexOf(col.name()));
+            assertEquals(row.<Object>value(i), row.value(col.name()));
+        }
+
+        assertTrue((boolean) row.value(0));
+        assertEquals(SqlColumnType.BOOLEAN, meta.columns().get(0).type());
+
+        assertEquals(Byte.MIN_VALUE, row.byteValue(1));
+        assertEquals(SqlColumnType.INT8, meta.columns().get(1).type());
+
+        assertEquals(Short.MIN_VALUE, row.shortValue(2));
+        assertEquals(SqlColumnType.INT16, meta.columns().get(2).type());
+
+        assertEquals(Integer.MIN_VALUE, row.intValue(3));
+        assertEquals(SqlColumnType.INT32, meta.columns().get(3).type());
+
+        assertEquals(Long.MIN_VALUE, row.longValue(4));
+        assertEquals(SqlColumnType.INT64, meta.columns().get(4).type());
+
+        assertEquals(1.3f, row.floatValue(5));
+        assertEquals(SqlColumnType.FLOAT, meta.columns().get(5).type());
+
+        assertEquals(1.4d, row.doubleValue(6));
+        assertEquals(SqlColumnType.DOUBLE, meta.columns().get(6).type());
+
+        assertEquals(BigDecimal.valueOf(145), row.value(7));
+        ColumnMetadata decimalCol = meta.columns().get(7);
+        assertEquals(SqlColumnType.DECIMAL, decimalCol.type());
+        assertEquals(1, decimalCol.precision());
+        assertEquals(2, decimalCol.scale());
+        assertTrue(decimalCol.nullable());
+        assertNotNull(decimalCol.origin());
+        assertEquals("SCHEMA1", decimalCol.origin().schemaName());
+        assertEquals("TBL2", decimalCol.origin().tableName());
+        assertEquals("BIG_DECIMAL", decimalCol.origin().columnName());
+
+        assertEquals(LocalDate.of(2001, 2, 3), row.dateValue(8));
+        assertEquals(SqlColumnType.DATE, meta.columns().get(8).type());
+
+        assertEquals(LocalTime.of(4, 5), row.timeValue(9));
+        assertEquals(SqlColumnType.TIME, meta.columns().get(9).type());
+
+        assertEquals(LocalDateTime.of(2001, 3, 4, 5, 6), row.datetimeValue(10));
+        assertEquals(SqlColumnType.DATETIME, meta.columns().get(10).type());
+
+        assertEquals(Instant.ofEpochSecond(987), row.timestampValue(11));
+        assertEquals(SqlColumnType.TIMESTAMP, meta.columns().get(11).type());
+
+        assertEquals(new UUID(0, 0), row.uuidValue(12));
+        assertEquals(SqlColumnType.UUID, meta.columns().get(12).type());
+
+        assertEquals(BitSet.valueOf(new byte[0]), row.bitmaskValue(13));
+        assertEquals(SqlColumnType.BITMASK, meta.columns().get(13).type());
+
+        assertEquals(0, ((byte[]) row.value(14))[0]);
+        assertEquals(SqlColumnType.BYTE_ARRAY, meta.columns().get(14).type());
+
+        assertEquals(Period.of(10, 9, 8), row.value(15));
+        assertEquals(SqlColumnType.PERIOD, meta.columns().get(15).type());
+
+        assertEquals(Duration.ofDays(11), row.value(16));
+        assertEquals(SqlColumnType.DURATION, meta.columns().get(16).type());
+
+        assertEquals(BigInteger.valueOf(42), row.value(17));
+        assertEquals(SqlColumnType.NUMBER, meta.columns().get(17).type());
     }
 }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeAsyncResultSet.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeAsyncResultSet.java
index 4e94b4833..0087b071c 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeAsyncResultSet.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeAsyncResultSet.java
@@ -17,22 +17,31 @@
 
 package org.apache.ignite.client.fakes;
 
-import static org.mockito.Mockito.mock;
-
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Period;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.List;
+import java.util.UUID;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.client.sql.ClientSqlRow;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.Session;
+import org.apache.ignite.sql.SqlColumnType;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.Statement;
 import org.apache.ignite.sql.async.AsyncResultSet;
 import org.apache.ignite.tx.Transaction;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
-import org.mockito.Mockito;
 
 /**
  * Fake result set.
@@ -72,8 +81,8 @@ public class FakeAsyncResultSet implements AsyncResultSet {
             rows = new ArrayList<>();
 
             rows.add(getRow("schema", session.defaultSchema()));
-            rows.add(getRow("timeout", session.defaultTimeout(TimeUnit.MILLISECONDS)));
-            rows.add(getRow("pageSize", session.defaultPageSize()));
+            rows.add(getRow("timeout", String.valueOf(session.defaultTimeout(TimeUnit.MILLISECONDS))));
+            rows.add(getRow("pageSize", String.valueOf(session.defaultPageSize())));
 
             var props = ((FakeSession) session).properties();
 
@@ -83,14 +92,55 @@ public class FakeAsyncResultSet implements AsyncResultSet {
 
             columns = new ArrayList<>();
 
-            columns.add(new FakeColumnMetadata("name"));
-            columns.add(new FakeColumnMetadata("val"));
-        } else {
-            rows = new ArrayList<>();
-            rows.add(getRow(1));
-
+            columns.add(new FakeColumnMetadata("name", SqlColumnType.STRING));
+            columns.add(new FakeColumnMetadata("val", SqlColumnType.STRING));
+        } else if ("SELECT META".equals(statement.query())) {
             columns = new ArrayList<>();
-            columns.add(new FakeColumnMetadata("col1"));
+
+            columns.add(new FakeColumnMetadata("bool", SqlColumnType.BOOLEAN));
+            columns.add(new FakeColumnMetadata("int8", SqlColumnType.INT8));
+            columns.add(new FakeColumnMetadata("int16", SqlColumnType.INT16));
+            columns.add(new FakeColumnMetadata("int32", SqlColumnType.INT32));
+            columns.add(new FakeColumnMetadata("int64", SqlColumnType.INT64));
+            columns.add(new FakeColumnMetadata("float", SqlColumnType.FLOAT));
+            columns.add(new FakeColumnMetadata("double", SqlColumnType.DOUBLE));
+            columns.add(new FakeColumnMetadata("decimal", SqlColumnType.DECIMAL, 1, 2,
+                    true, new ColumnOrigin("SCHEMA1", "TBL2", "BIG_DECIMAL")));
+            columns.add(new FakeColumnMetadata("date", SqlColumnType.DATE));
+            columns.add(new FakeColumnMetadata("time", SqlColumnType.TIME));
+            columns.add(new FakeColumnMetadata("datetime", SqlColumnType.DATETIME));
+            columns.add(new FakeColumnMetadata("timestamp", SqlColumnType.TIMESTAMP));
+            columns.add(new FakeColumnMetadata("uuid", SqlColumnType.UUID));
+            columns.add(new FakeColumnMetadata("bitmask", SqlColumnType.BITMASK));
+            columns.add(new FakeColumnMetadata("byte_array", SqlColumnType.BYTE_ARRAY));
+            columns.add(new FakeColumnMetadata("period", SqlColumnType.PERIOD));
+            columns.add(new FakeColumnMetadata("duration", SqlColumnType.DURATION));
+            columns.add(new FakeColumnMetadata("number", SqlColumnType.NUMBER));
+
+            var row = getRow(
+                    true,
+                    Byte.MIN_VALUE,
+                    Short.MIN_VALUE,
+                    Integer.MIN_VALUE,
+                    Long.MIN_VALUE,
+                    1.3f,
+                    1.4d,
+                    BigDecimal.valueOf(145),
+                    LocalDate.of(2001, 2, 3),
+                    LocalTime.of(4, 5),
+                    LocalDateTime.of(2001, 3, 4, 5, 6),
+                    Instant.ofEpochSecond(987),
+                    new UUID(0, 0),
+                    BitSet.valueOf(new byte[0]),
+                    new byte[1],
+                    Period.of(10, 9, 8),
+                    Duration.ofDays(11),
+                    BigInteger.valueOf(42));
+
+            rows = List.of(row);
+        } else {
+            rows = List.of(getRow(1));
+            columns = List.of(new FakeColumnMetadata("col1", SqlColumnType.INT32));
         }
     }
 
@@ -160,12 +210,33 @@ public class FakeAsyncResultSet implements AsyncResultSet {
 
     @NotNull
     private SqlRow getRow(Object... vals) {
-        var row = mock(SqlRow.class);
+        return new ClientSqlRow(List.of(vals), metadata());
+    }
 
-        for (int i = 0; i < vals.length; i++) {
-            Mockito.when(row.value(i)).thenReturn(vals[i]);
+    private static class ColumnOrigin implements ColumnMetadata.ColumnOrigin {
+        private final String schemaName;
+        private final String tableName;
+        private final String columnName;
+
+        public ColumnOrigin(String schemaName, String tableName, String columnName) {
+            this.schemaName = schemaName;
+            this.tableName = tableName;
+            this.columnName = columnName;
+        }
+
+        @Override
+        public String schemaName() {
+            return schemaName;
         }
 
-        return row;
+        @Override
+        public String tableName() {
+            return tableName;
+        }
+
+        @Override
+        public String columnName() {
+            return columnName;
+        }
     }
 }
diff --git a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeColumnMetadata.java b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeColumnMetadata.java
index 53042e10a..5e82b5764 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeColumnMetadata.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/fakes/FakeColumnMetadata.java
@@ -26,13 +26,36 @@ import org.apache.ignite.sql.SqlColumnType;
 class FakeColumnMetadata implements ColumnMetadata {
     private final String name;
 
-    /**
-     * Constructor.
-     *
-     * @param name Column name.
-     */
-    FakeColumnMetadata(String name) {
+    private final SqlColumnType type;
+
+    private final int precision;
+
+    private final int scale;
+
+    private final boolean nullable;
+
+    private final ColumnOrigin origin;
+
+    FakeColumnMetadata(String name, SqlColumnType type) {
+        this(name, type, -1, -1, false, null);
+    }
+
+    FakeColumnMetadata(
+            String name,
+            SqlColumnType type,
+            int precision,
+            int scale,
+            boolean nullable,
+            ColumnOrigin origin) {
+        assert name != null;
+        assert type != null;
+
         this.name = name;
+        this.type = type;
+        this.precision = precision;
+        this.scale = scale;
+        this.nullable = nullable;
+        this.origin = origin;
     }
 
     /** {@inheritDoc} */
@@ -50,30 +73,30 @@ class FakeColumnMetadata implements ColumnMetadata {
     /** {@inheritDoc} */
     @Override
     public SqlColumnType type() {
-        return null;
+        return type;
     }
 
     /** {@inheritDoc} */
     @Override
     public int precision() {
-        return -1;
+        return precision;
     }
 
     /** {@inheritDoc} */
     @Override
     public int scale() {
-        return -1;
+        return scale;
     }
 
     /** {@inheritDoc} */
     @Override
     public boolean nullable() {
-        return false;
+        return nullable;
     }
 
     /** {@inheritDoc} */
     @Override
     public ColumnOrigin origin() {
-        return null;
+        return origin;
     }
 }
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
index 99b15200d..6a4092291 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/runner/app/client/ItThinClientSqlTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.runner.app.client;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -31,7 +32,9 @@ import org.apache.ignite.client.IgniteClientException;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.NoRowSetExpectedException;
 import org.apache.ignite.sql.ResultSet;
+import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.Session;
+import org.apache.ignite.sql.SqlColumnType;
 import org.apache.ignite.sql.SqlRow;
 import org.apache.ignite.sql.Statement;
 import org.apache.ignite.sql.async.AsyncResultSet;
@@ -61,7 +64,10 @@ public class ItThinClientSqlTest extends ItAbstractThinClientTest {
         assertEquals(1, row.intValue(0));
         assertEquals("hello", row.stringValue(1));
 
-        List<ColumnMetadata> columns = resultSet.metadata().columns();
+        ResultSetMetadata metadata = resultSet.metadata();
+        assertNotNull(metadata);
+
+        List<ColumnMetadata> columns = metadata.columns();
         assertEquals(2, columns.size());
         assertEquals("NUM", columns.get(0).name());
         assertEquals("STR", columns.get(1).name());
@@ -156,13 +162,15 @@ public class ItThinClientSqlTest extends ItAbstractThinClientTest {
         assertTrue(deleteRes.wasApplied());
     }
 
+    @SuppressWarnings("ConstantConditions")
     @Test
     void testExecuteDdlDml() {
         Session session = client().sql().createSession();
 
         // Create table.
-        ResultSet createRes = session
-                .execute(null, "CREATE TABLE testExecuteDdlDml(ID INT PRIMARY KEY, VAL VARCHAR)");
+        ResultSet createRes = session.execute(
+                null,
+                "CREATE TABLE testExecuteDdlDml(ID INT NOT NULL PRIMARY KEY, VAL VARCHAR)");
 
         assertFalse(createRes.hasRowSet());
         assertNull(createRes.metadata());
@@ -171,8 +179,9 @@ public class ItThinClientSqlTest extends ItAbstractThinClientTest {
 
         // Insert data.
         for (int i = 0; i < 10; i++) {
-            ResultSet insertRes = session
-                    .execute(null, "INSERT INTO testExecuteDdlDml VALUES (?, ?)", i, "hello " + i);
+            ResultSet insertRes = session.execute(
+                    null,
+                    "INSERT INTO testExecuteDdlDml VALUES (?, ?)", i, "hello " + i);
 
             assertFalse(insertRes.hasRowSet());
             assertNull(insertRes.metadata());
@@ -190,9 +199,27 @@ public class ItThinClientSqlTest extends ItAbstractThinClientTest {
 
         List<ColumnMetadata> columns = selectRes.metadata().columns();
         assertEquals(3, columns.size());
+
         assertEquals("MYVALUE", columns.get(0).name());
+        assertEquals("VAL", columns.get(0).origin().columnName());
+        assertEquals("PUBLIC", columns.get(0).origin().schemaName());
+        assertEquals("TESTEXECUTEDDLDML", columns.get(0).origin().tableName());
+        assertTrue(columns.get(0).nullable());
+        assertEquals(String.class, columns.get(0).valueClass());
+        assertEquals(SqlColumnType.STRING, columns.get(0).type());
+
+        // TODO IGNITE-17203
+        // assertEquals(-1, columns.get(0).scale());
+        // assertEquals(-1, columns.get(0).precision());
+
         assertEquals("ID", columns.get(1).name());
+        assertEquals("ID", columns.get(1).origin().columnName());
+        assertEquals("PUBLIC", columns.get(1).origin().schemaName());
+        assertEquals("TESTEXECUTEDDLDML", columns.get(1).origin().tableName());
+        assertFalse(columns.get(1).nullable());
+
         assertEquals("ID + 1", columns.get(2).name());
+        assertNull(columns.get(2).origin());
 
         var rows = new ArrayList<SqlRow>();
         selectRes.forEachRemaining(rows::add);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlClientAsynchronousApiTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlClientAsynchronousApiTest.java
index bf8c5c95b..9da9d1dcb 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlClientAsynchronousApiTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/api/ItSqlClientAsynchronousApiTest.java
@@ -47,18 +47,6 @@ public class ItSqlClientAsynchronousApiTest extends ItSqlAsynchronousApiTest {
         return client.sql();
     }
 
-    @Override
-    @Disabled("IGNITE-17052")
-    public void metadata() throws ExecutionException, InterruptedException {
-        super.metadata();
-    }
-
-    @Override
-    @Disabled("IGNITE-17052")
-    public void sqlRow() throws ExecutionException, InterruptedException {
-        super.sqlRow();
-    }
-
     @Override
     @Disabled("IGNITE-17134")
     public void closeSession() throws ExecutionException, InterruptedException {
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 43d99888a..1af6711c8 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
@@ -35,6 +35,7 @@ import java.util.Iterator;
 import java.util.List;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import org.apache.ignite.internal.sql.SqlColumnTypeConverter;
 import org.apache.ignite.internal.sql.engine.QueryProcessor;
 import org.apache.ignite.internal.util.CollectionUtils;
 import org.apache.ignite.sql.ColumnMetadata;
@@ -370,7 +371,7 @@ public abstract class QueryChecker {
         if (expectedColumnTypes != null) {
             List<Type> colTypes = cur.metadata().columns().stream()
                     .map(ColumnMetadata::type)
-                    .map(Commons::columnTypeToClass)
+                    .map(SqlColumnTypeConverter::columnTypeToClass)
                     .collect(Collectors.toList());
 
             assertThat("Column types don't match", colTypes, equalTo(expectedColumnTypes));
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/ColumnMetadataImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/ColumnMetadataImpl.java
index e9fdf264c..cb42cd65a 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/ColumnMetadataImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/ColumnMetadataImpl.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.sql.api;
 
 import java.util.List;
-import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.SqlColumnTypeConverter;
 import org.apache.ignite.internal.tostring.S;
 import org.apache.ignite.sql.ColumnMetadata;
 import org.apache.ignite.sql.SqlColumnType;
@@ -110,7 +110,7 @@ public class ColumnMetadataImpl implements ColumnMetadata {
     /** {@inheritDoc} */
     @Override
     public Class<?> valueClass() {
-        return Commons.columnTypeToClass(type);
+        return SqlColumnTypeConverter.columnTypeToClass(type);
     }
 
     /** {@inheritDoc} */
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
index 93766d528..c968add06 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
@@ -27,12 +27,10 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.time.Duration;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
-import java.time.Period;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
@@ -114,7 +112,6 @@ import org.apache.ignite.lang.IgniteInternalException;
 import org.apache.ignite.lang.IgniteLogger;
 import org.apache.ignite.lang.IgniteSystemProperties;
 import org.apache.ignite.sql.ResultSetMetadata;
-import org.apache.ignite.sql.SqlColumnType;
 import org.codehaus.commons.compiler.CompilerFactoryFactory;
 import org.codehaus.commons.compiler.IClassBodyEvaluator;
 import org.codehaus.commons.compiler.ICompilerFactory;
@@ -702,74 +699,6 @@ public final class Commons {
         }
     }
 
-    /**
-     * Column type to Java class.
-     */
-    public static Class<?> columnTypeToClass(SqlColumnType type) {
-        assert type != null;
-
-        switch (type) {
-            case BOOLEAN:
-                return Boolean.class;
-            case INT8:
-                return Byte.class;
-
-            case INT16:
-                return Short.class;
-
-            case INT32:
-                return Integer.class;
-
-            case INT64:
-                return Long.class;
-
-            case FLOAT:
-                return Float.class;
-
-            case DOUBLE:
-                return Double.class;
-
-            case NUMBER:
-                return BigInteger.class;
-
-            case DECIMAL:
-                return BigDecimal.class;
-
-            case UUID:
-                return UUID.class;
-
-            case STRING:
-                return String.class;
-
-            case BYTE_ARRAY:
-                return byte[].class;
-
-            case BITMASK:
-                return BitSet.class;
-
-            case DATE:
-                return LocalDate.class;
-
-            case TIME:
-                return LocalTime.class;
-
-            case DATETIME:
-                return LocalDateTime.class;
-
-            case TIMESTAMP:
-                return Instant.class;
-
-            case PERIOD:
-                return Period.class;
-
-            case DURATION:
-                return Duration.class;
-
-            default:
-                throw new IllegalArgumentException("Unsupported type " + type);
-        }
-    }
-
     /**
      * NativeTypePrecision.
      * TODO Documentation https://issues.apache.org/jira/browse/IGNITE-15859