You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2019/02/01 13:29:44 UTC

[ignite] branch master updated: IGNITE-9989: JDBC: Fixed metadata pattern handling for JDBCv2 driver. Moved metadata management code common to v2 and thin drivers to a single place. This closes #5716.

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

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


The following commit(s) were added to refs/heads/master by this push:
     new bf49d56  IGNITE-9989: JDBC: Fixed metadata pattern handling for JDBCv2 driver. Moved metadata management code common to v2 and thin drivers to a single place. This closes #5716.
bf49d56 is described below

commit bf49d56cc0635a5962523415c6a9d97a31c4e62c
Author: Pavel Kuznetsov <pa...@gmail.com>
AuthorDate: Fri Feb 1 16:29:20 2019 +0300

    IGNITE-9989: JDBC: Fixed metadata pattern handling for JDBCv2 driver. Moved metadata management code common to v2 and thin drivers to a single place. This closes #5716.
---
 .../internal/jdbc2/JdbcMetadataSelfTest.java       |  88 +++++--
 .../ignite/jdbc/thin/JdbcThinMetadataSelfTest.java |  26 +-
 .../jdbc/thin/JdbcThinDatabaseMetadata.java        | 123 +--------
 .../ignite/internal/jdbc2/JdbcConnection.java      |   4 +-
 .../internal/jdbc2/JdbcDatabaseMetadata.java       | 289 ++++++---------------
 .../apache/ignite/internal/jdbc2/JdbcUtils.java    | 125 ++++++++-
 .../processors/odbc/jdbc/JdbcMetadataInfo.java     | 269 +++++++++++++++++++
 .../processors/odbc/jdbc/JdbcRequestHandler.java   | 172 ++----------
 .../internal/processors/query/QueryUtils.java      |  26 +-
 .../processors/query/h2/dml/UpdatePlanBuilder.java |   3 +
 .../Cache/Query/CacheDmlQueriesTest.cs             |   2 +-
 11 files changed, 604 insertions(+), 523 deletions(-)

diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
index f845b9e..ad21e6a 100755
--- a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
@@ -21,6 +21,7 @@ import java.io.Serializable;
 import java.math.BigDecimal;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
+import java.sql.Date;
 import java.sql.DriverManager;
 import java.sql.ParameterMetaData;
 import java.sql.PreparedStatement;
@@ -29,7 +30,6 @@ import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Types;
-import java.sql.Date;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -51,10 +51,10 @@ import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.jetbrains.annotations.NotNull;
 import org.junit.Test;
 
+import static java.sql.Types.DATE;
+import static java.sql.Types.DECIMAL;
 import static java.sql.Types.INTEGER;
 import static java.sql.Types.VARCHAR;
-import static java.sql.Types.DECIMAL;
-import static java.sql.Types.DATE;
 import static org.apache.ignite.IgniteJdbcDriver.CFG_URL_PREFIX;
 import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
 import static org.apache.ignite.cache.CacheMode.PARTITIONED;
@@ -129,6 +129,21 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
         personCache.put(new AffinityKey<>("p1", "o1"), new Person("John White", 25, 1));
         personCache.put(new AffinityKey<>("p2", "o1"), new Person("Joe Black", 35, 1));
         personCache.put(new AffinityKey<>("p3", "o2"), new Person("Mike Green", 40, 2));
+
+        jcache(grid(0),
+            defaultCacheConfiguration().setIndexedTypes(Integer.class, Department.class),
+            "dep");
+
+        try (Connection conn = DriverManager.getConnection(BASE_URL)) {
+            Statement stmt = conn.createStatement();
+
+            stmt.execute("CREATE TABLE PUBLIC.TEST (ID INT, NAME VARCHAR(50) default 'default name', " +
+                "age int default 21, VAL VARCHAR(50), PRIMARY KEY (ID, NAME))");
+            stmt.execute("CREATE TABLE PUBLIC.\"Quoted\" (\"Id\" INT primary key, \"Name\" VARCHAR(50)) WITH WRAP_KEY");
+            stmt.execute("CREATE INDEX \"MyTestIndex quoted\" on PUBLIC.\"Quoted\" (\"Id\" DESC)");
+            stmt.execute("CREATE INDEX IDX ON PUBLIC.TEST (ID ASC)");
+            stmt.execute("CREATE TABLE PUBLIC.TEST_DECIMAL_COLUMN (ID INT primary key, DEC_COL DECIMAL(8, 3))");
+        }
     }
 
     /**
@@ -213,28 +228,28 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("PERSON", rs.getString("TABLE_NAME"));
 
             rs = meta.getTables(null, "org", "%", new String[]{"TABLE"});
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
 
             rs = meta.getTables(null, "pers", "%", null);
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("PERSON", rs.getString("TABLE_NAME"));
 
             rs = meta.getTables(null, "org", "%", null);
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
 
             rs = meta.getTables(null, "PUBLIC", "", new String[]{"WRONG"});
@@ -447,18 +462,30 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
      */
     @Test
     public void testPrimaryKeyMetadata() throws Exception {
-        try (Connection conn = DriverManager.getConnection(BASE_URL);
-             ResultSet rs = conn.getMetaData().getPrimaryKeys(null, "pers", "PERSON")) {
-
-            int cnt = 0;
-
-            while (rs.next()) {
-                assertEquals("_KEY", rs.getString("COLUMN_NAME"));
-
-                cnt++;
+        try (Connection conn = DriverManager.getConnection(BASE_URL)) {
+            ResultSet rs = conn.getMetaData().getPrimaryKeys(null, null, null);
+
+            // TABLE_SCHEM.TABLE_NAME.PK_NAME.COLUMN_NAME
+            Set<String> expectedPks = new HashSet<>(Arrays.asList(
+                "org.ORGANIZATION.PK_org_ORGANIZATION._KEY",
+                "pers.PERSON.PK_pers_PERSON._KEY",
+                "dep.DEPARTMENT.PK_dep_DEPARTMENT._KEY",
+                "PUBLIC.TEST.PK_PUBLIC_TEST.ID",
+                "PUBLIC.TEST.PK_PUBLIC_TEST.NAME",
+                "PUBLIC.Quoted.PK_PUBLIC_Quoted.Id",
+                "PUBLIC.TEST_DECIMAL_COLUMN.ID.ID",
+                "metaTest.METATEST.PK_metaTest_METATEST._KEY"));
+
+            Set<String> actualPks = new HashSet<>(expectedPks.size());
+
+            while(rs.next()) {
+                actualPks.add(rs.getString("TABLE_SCHEM") +
+                    '.' + rs.getString("TABLE_NAME") +
+                    '.' + rs.getString("PK_NAME") +
+                    '.' + rs.getString("COLUMN_NAME"));
             }
 
-            assertEquals(1, cnt);
+            assertEquals("Metadata contains unexpected primary keys info.", expectedPks, actualPks);
         }
     }
 
@@ -495,7 +522,7 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
         try (Connection conn = DriverManager.getConnection(BASE_URL)) {
             ResultSet rs = conn.getMetaData().getSchemas();
 
-            Set<String> expectedSchemas = new HashSet<>(Arrays.asList("pers", "org", "metaTest"));
+            Set<String> expectedSchemas = new HashSet<>(Arrays.asList("pers", "org", "metaTest", "dep", "PUBLIC"));
 
             Set<String> schemas = new HashSet<>();
 
@@ -503,7 +530,7 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
                 schemas.add(rs.getString(1));
 
                 assertEquals("There is only one possible catalog.",
-                    JdbcDatabaseMetadata.CATALOG_NAME, rs.getString(2));
+                    JdbcUtils.CATALOG_NAME, rs.getString(2));
             }
 
             assertEquals(expectedSchemas, schemas);
@@ -609,4 +636,27 @@ public class JdbcMetadataSelfTest extends GridCommonAbstractTest {
             this.decimal = decimal;
         }
     }
+
+    /**
+     * Department.
+     */
+    @SuppressWarnings("UnusedDeclaration")
+    private static class Department implements Serializable {
+        /** ID. */
+        @QuerySqlField
+        private final int id;
+
+        /** Name. */
+        @QuerySqlField(precision = 43)
+        private final String name;
+
+        /**
+         * @param id ID.
+         * @param name Name.
+         */
+        private Department(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+    }
 }
diff --git a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
index edbe6a5..13ae776 100644
--- a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
+++ b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
@@ -44,16 +44,16 @@ import org.apache.ignite.cache.affinity.AffinityKey;
 import org.apache.ignite.cache.query.annotations.QuerySqlField;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.IgniteVersionUtils;
-import org.apache.ignite.internal.jdbc.thin.JdbcThinDatabaseMetadata;
+import org.apache.ignite.internal.jdbc2.JdbcUtils;
 import org.apache.ignite.internal.processors.query.QueryEntityEx;
 import org.apache.ignite.internal.util.typedef.F;
 import org.junit.Test;
 
-import static java.sql.Types.INTEGER;
-import static java.sql.Types.VARCHAR;
-import static java.sql.Types.DECIMAL;
 import static java.sql.Types.DATE;
+import static java.sql.Types.DECIMAL;
+import static java.sql.Types.INTEGER;
 import static java.sql.Types.OTHER;
+import static java.sql.Types.VARCHAR;
 import static org.apache.ignite.cache.CacheMode.PARTITIONED;
 import static org.apache.ignite.cache.CacheWriteSynchronizationMode.FULL_SYNC;
 
@@ -128,8 +128,9 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
         personCache.put(new AffinityKey<>("p2", "o1"), new Person("Joe Black", 35, 1));
         personCache.put(new AffinityKey<>("p3", "o2"), new Person("Mike Green", 40, 2));
 
-        IgniteCache<Integer, Department> departmentCache = jcache(grid(0),
-            defaultCacheConfiguration().setIndexedTypes(Integer.class, Department.class), "dep");
+        jcache(grid(0),
+            defaultCacheConfiguration().setIndexedTypes(Integer.class, Department.class),
+            "dep");
 
         try (Connection conn = DriverManager.getConnection(URL)) {
             Statement stmt = conn.createStatement();
@@ -229,7 +230,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcThinDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("PERSON", rs.getString("TABLE_NAME"));
 
             assertFalse(rs.next());
@@ -238,7 +239,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcThinDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
 
             assertFalse(rs.next());
@@ -247,7 +248,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcThinDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("PERSON", rs.getString("TABLE_NAME"));
 
             assertFalse(rs.next());
@@ -256,7 +257,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
             assertNotNull(rs);
             assertTrue(rs.next());
             assertEquals("TABLE", rs.getString("TABLE_TYPE"));
-            assertEquals(JdbcThinDatabaseMetadata.CATALOG_NAME, rs.getString("TABLE_CAT"));
+            assertEquals(JdbcUtils.CATALOG_NAME, rs.getString("TABLE_CAT"));
             assertEquals("ORGANIZATION", rs.getString("TABLE_NAME"));
 
             assertFalse(rs.next());
@@ -611,8 +612,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
                     '.' + rs.getString("COLUMN_NAME"));
             }
 
-            assert expectedPks.equals(actualPks) : "expectedPks=" + expectedPks +
-                ", actualPks" + actualPks;
+            assertEquals("Metadata contains unexpected primary keys info.", expectedPks, actualPks);
         }
     }
 
@@ -657,7 +657,7 @@ public class JdbcThinMetadataSelfTest extends JdbcThinAbstractSelfTest {
                 schemas.add(rs.getString(1));
 
                 assertEquals("There is only one possible catalog.",
-                    JdbcThinDatabaseMetadata.CATALOG_NAME, rs.getString(2));
+                    JdbcUtils.CATALOG_NAME, rs.getString(2));
             }
 
             assert expectedSchemas.equals(schemas) : "Unexpected schemas: " + schemas +
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
index d1a5f6d..0de592e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc/thin/JdbcThinDatabaseMetadata.java
@@ -29,6 +29,7 @@ import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import org.apache.ignite.internal.IgniteVersionUtils;
+import org.apache.ignite.internal.jdbc2.JdbcUtils;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcColumnMeta;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcIndexMeta;
 import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetaColumnsRequest;
@@ -50,15 +51,17 @@ import static java.sql.ResultSet.CONCUR_READ_ONLY;
 import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
 import static java.sql.ResultSet.TYPE_FORWARD_ONLY;
 import static java.sql.RowIdLifetime.ROWID_UNSUPPORTED;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.CATALOG_NAME;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.columnRow;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.indexRows;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.primaryKeyRows;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.tableRow;
 
 /**
  * JDBC database metadata implementation.
  */
 @SuppressWarnings("RedundantCast")
 public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
-    /** The only possible name for catalog. */
-    public static final String CATALOG_NAME = "IGNITE";
-
     /** Driver name. */
     public static final String DRIVER_NAME = "Apache Ignite Thin JDBC Driver";
 
@@ -752,27 +755,6 @@ public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
         return new JdbcThinResultSet(rows, meta);
     }
 
-    /**
-     * @param tblMeta Table metadata.
-     * @return Table metadata row.
-     */
-    private List<Object> tableRow(JdbcTableMeta tblMeta) {
-        List<Object> row = new ArrayList<>(10);
-
-        row.add(CATALOG_NAME);
-        row.add(tblMeta.schemaName());
-        row.add(tblMeta.tableName());
-        row.add("TABLE");
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-
-        return row;
-    }
-
     /** {@inheritDoc} */
     @Override public ResultSet getSchemas() throws SQLException {
         return getSchemas(null, "%");
@@ -837,42 +819,6 @@ public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
         return new JdbcThinResultSet(rows, meta);
     }
 
-    /**
-     * @param colMeta Column metadata.
-     * @param pos Ordinal position.
-     * @return Column metadata row.
-     */
-    private List<Object> columnRow(JdbcColumnMeta colMeta, int pos) {
-        List<Object> row = new ArrayList<>(24);
-
-        row.add(CATALOG_NAME);                  // 1. TABLE_CAT
-        row.add(colMeta.schemaName());          // 2. TABLE_SCHEM
-        row.add(colMeta.tableName());           // 3. TABLE_NAME
-        row.add(colMeta.columnName());          // 4. COLUMN_NAME
-        row.add(colMeta.dataType());            // 5. DATA_TYPE
-        row.add(colMeta.dataTypeName());        // 6. TYPE_NAME
-        row.add(colMeta.precision() == -1 ? null : colMeta.precision()); // 7. COLUMN_SIZE
-        row.add((Integer)null);                 // 8. BUFFER_LENGTH
-        row.add(colMeta.scale() == -1 ? null : colMeta.scale());           // 9. DECIMAL_DIGITS
-        row.add(10);                            // 10. NUM_PREC_RADIX
-        row.add(colMeta.isNullable() ? columnNullable : columnNoNulls);  // 11. NULLABLE
-        row.add((String)null);                  // 12. REMARKS
-        row.add(colMeta.defaultValue());        // 13. COLUMN_DEF
-        row.add(colMeta.dataType());            // 14. SQL_DATA_TYPE
-        row.add((Integer)null);                 // 15. SQL_DATETIME_SUB
-        row.add(Integer.MAX_VALUE);             // 16. CHAR_OCTET_LENGTH
-        row.add(pos);                           // 17. ORDINAL_POSITION
-        row.add(colMeta.isNullable() ? "YES" : "NO"); // 18. IS_NULLABLE
-        row.add((String)null);                  // 19. SCOPE_CATALOG
-        row.add((String)null);                  // 20. SCOPE_SCHEMA
-        row.add((String)null);                  // 21. SCOPE_TABLE
-        row.add((Short)null);                   // 22. SOURCE_DATA_TYPE
-        row.add("NO");                          // 23. IS_AUTOINCREMENT
-        row.add("NO");                          // 23. IS_GENERATEDCOLUMN
-
-        return row;
-    }
-
     /** {@inheritDoc} */
     @Override public ResultSet getColumnPrivileges(String catalog, String schema, String tbl,
         String colNamePtrn) throws SQLException {
@@ -956,29 +902,6 @@ public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
         return new JdbcThinResultSet(rows, meta);
     }
 
-    /**
-     * @param pkMeta Primary key metadata.
-     * @return Result set rows for primary key.
-     */
-    private List<List<Object>> primaryKeyRows(JdbcPrimaryKeyMeta pkMeta) {
-        List<List<Object>> rows = new ArrayList<>(pkMeta.fields().size());
-
-        for (int i = 0; i < pkMeta.fields().size(); ++i) {
-            List<Object> row = new ArrayList<>(6);
-
-            row.add(CATALOG_NAME); // table catalog
-            row.add(pkMeta.schemaName());
-            row.add(pkMeta.tableName());
-            row.add(pkMeta.fields().get(i));
-            row.add((Integer)i + 1); // sequence number
-            row.add(pkMeta.name());
-
-            rows.add(row);
-        }
-
-        return rows;
-    }
-
     /** {@inheritDoc} */
     @Override public ResultSet getImportedKeys(String catalog, String schema, String tbl) throws SQLException {
         return new JdbcThinResultSet(Collections.<List<Object>>emptyList(), Arrays.asList(
@@ -1183,36 +1106,6 @@ public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
         return new JdbcThinResultSet(rows, meta);
     }
 
-    /**
-     * @param idxMeta Index metadata.
-     * @return List of result rows correspond to index.
-     */
-    private List<List<Object>> indexRows(JdbcIndexMeta idxMeta) {
-        List<List<Object>> rows = new ArrayList<>(idxMeta.fields().size());
-
-        for (int i = 0; i < idxMeta.fields().size(); ++i) {
-            List<Object> row = new ArrayList<>(13);
-
-            row.add(CATALOG_NAME); // table catalog
-            row.add(idxMeta.schemaName());
-            row.add(idxMeta.tableName());
-            row.add(true); // non unique
-            row.add(null); // index qualifier (index catalog)
-            row.add(idxMeta.indexName());
-            row.add((short)tableIndexOther); // type
-            row.add((Integer)i); // field ordinal position in index
-            row.add(idxMeta.fields().get(i));
-            row.add(idxMeta.fieldsAsc().get(i) ? "A" : "D");
-            row.add((Integer)0); // cardinality
-            row.add((Integer)0); // pages
-            row.add((String)null); // filer condition
-
-            rows.add(row);
-        }
-
-        return rows;
-    }
-
     /** {@inheritDoc} */
     @Override public boolean supportsResultSetType(int type) throws SQLException {
         return type == TYPE_FORWARD_ONLY;
@@ -1534,10 +1427,10 @@ public class JdbcThinDatabaseMetadata implements DatabaseMetaData {
     }
 
     /**
-     * Checks if specified catalog matches the only possible catalog value. See {@link #CATALOG_NAME}.
+     * Checks if specified catalog matches the only possible catalog value. See {@link JdbcUtils#CATALOG_NAME}.
      *
      * @param catalog Catalog name or {@code null}.
-     * @return {@code true} If catalog equal ignoring case to {@link #CATALOG_NAME} or null (which means any catalog).
+     * @return {@code true} If catalog equal ignoring case to {@link JdbcUtils#CATALOG_NAME} or null (which means any catalog).
      *  Otherwise returns {@code false}.
      */
     private static boolean isValidCatalog(String catalog) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
index 896deb7..0409c94 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcConnection.java
@@ -83,13 +83,13 @@ import static org.apache.ignite.IgniteJdbcDriver.PROP_LAZY;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_LOCAL;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_MULTIPLE_STMTS;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_NODE_ID;
+import static org.apache.ignite.IgniteJdbcDriver.PROP_SKIP_REDUCER_ON_UPDATE;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_ALLOW_OVERWRITE;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_FLUSH_FREQ;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_PER_NODE_BUF_SIZE;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_STREAMING_PER_NODE_PAR_OPS;
 import static org.apache.ignite.IgniteJdbcDriver.PROP_TX_ALLOWED;
-import static org.apache.ignite.IgniteJdbcDriver.PROP_SKIP_REDUCER_ON_UPDATE;
 import static org.apache.ignite.internal.jdbc2.JdbcUtils.convertToSqlException;
 import static org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode.createJdbcSqlException;
 
@@ -901,7 +901,7 @@ public class JdbcConnection implements Connection {
      *
      * @throws SQLException If connection is closed.
      */
-    private void ensureNotClosed() throws SQLException {
+    void ensureNotClosed() throws SQLException {
         if (closed)
             throw new SQLException("Connection is closed.", SqlStateCode.CONNECTION_CLOSED);
     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
index 663c988..cc58780 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcDatabaseMetadata.java
@@ -26,20 +26,19 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.UUID;
+import java.util.Set;
+import java.util.SortedSet;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCache;
 import org.apache.ignite.internal.IgniteVersionUtils;
 import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
-import org.apache.ignite.internal.processors.cache.query.GridCacheSqlIndexMetadata;
 import org.apache.ignite.internal.processors.cache.query.GridCacheSqlMetadata;
-import org.apache.ignite.internal.processors.odbc.SqlStateCode;
-import org.apache.ignite.internal.util.typedef.F;
-import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcColumnMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcIndexMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcMetadataInfo;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcPrimaryKeyMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcTableMeta;
 import org.apache.ignite.lang.IgniteCallable;
 import org.apache.ignite.resources.IgniteInstanceResource;
 
@@ -47,32 +46,31 @@ import static java.sql.Connection.TRANSACTION_NONE;
 import static java.sql.ResultSet.CONCUR_READ_ONLY;
 import static java.sql.ResultSet.HOLD_CURSORS_OVER_COMMIT;
 import static java.sql.RowIdLifetime.ROWID_UNSUPPORTED;
-import static org.apache.ignite.internal.jdbc2.JdbcUtils.convertToSqlException;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.CATALOG_NAME;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.columnRow;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.indexRows;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.primaryKeyRows;
+import static org.apache.ignite.internal.jdbc2.JdbcUtils.tableRow;
 
 /**
  * JDBC database metadata implementation.
  */
 public class JdbcDatabaseMetadata implements DatabaseMetaData {
-    /** The only possible name for catalog. */
-    public static final String CATALOG_NAME = "IGNITE";
-
     /** Driver name. */
     public static final String DRIVER_NAME = "Apache Ignite JDBC Driver";
 
     /** Connection. */
     private final JdbcConnection conn;
 
-    /** Metadata. */
-    private Map<String, Map<String, Map<String, ColumnInfo>>> meta;
-
-    /** Index info. */
-    private Collection<List<Object>> indexes;
+    private final JdbcMetadataInfo meta;
 
     /**
      * @param conn Connection.
      */
     JdbcDatabaseMetadata(JdbcConnection conn) {
         this.conn = conn;
+
+        meta = new JdbcMetadataInfo(conn.ignite().context());
     }
 
     /** {@inheritDoc} */
@@ -713,19 +711,19 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     /** {@inheritDoc} */
     @Override public ResultSet getTables(String catalog, String schemaPtrn, String tblNamePtrn,
         String[] tblTypes) throws SQLException {
-        updateMetaData();
-
-        List<List<?>> rows = new LinkedList<>();
-
-        if (isValidCatalog(catalog) && (tblTypes == null || Arrays.asList(tblTypes).contains("TABLE"))) {
-            for (Map.Entry<String, Map<String, Map<String, ColumnInfo>>> schema : meta.entrySet()) {
-                if (matches(schema.getKey(), schemaPtrn)) {
-                    for (String tbl : schema.getValue().keySet()) {
-                        if (matches(tbl, tblNamePtrn))
-                            rows.add(tableRow(schema.getKey(), tbl));
-                    }
-                }
-            }
+        conn.ensureNotClosed();
+
+        List<List<?>> rows = Collections.emptyList();
+
+        boolean areTypesValid = tblTypes == null || Arrays.asList(tblTypes).contains("TABLE");
+
+        if (isValidCatalog(catalog) && areTypesValid) {
+            List<JdbcTableMeta> tabMetas = meta.getTablesMeta(schemaPtrn, tblNamePtrn);
+
+            rows = new ArrayList<>(tabMetas.size());
+
+            for (JdbcTableMeta m : tabMetas)
+                rows.add(tableRow(m));
         }
 
         return new JdbcResultSet(true, null,
@@ -740,28 +738,6 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
         );
     }
 
-    /**
-     * @param schema Schema name.
-     * @param tbl Table name.
-     * @return Table metadata row.
-     */
-    private List<Object> tableRow(String schema, String tbl) {
-        List<Object> row = new ArrayList<>(10);
-
-        row.add(CATALOG_NAME);
-        row.add(schema);
-        row.add(tbl.toUpperCase());
-        row.add("TABLE");
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-        row.add(null);
-
-        return row;
-    }
-
     /** {@inheritDoc} */
     @Override public ResultSet getSchemas() throws SQLException {
         return getSchemas(null, "%");
@@ -793,26 +769,21 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     /** {@inheritDoc} */
     @Override public ResultSet getColumns(String catalog, String schemaPtrn, String tblNamePtrn,
         String colNamePtrn) throws SQLException {
-        updateMetaData();
+        conn.ensureNotClosed();
 
-        List<List<?>> rows = new LinkedList<>();
+        List<List<?>> rows = Collections.emptyList();
 
+        // FIXME: IGNITE-10745
         int cnt = 0;
 
         if (isValidCatalog(catalog)) {
-            for (Map.Entry<String, Map<String, Map<String, ColumnInfo>>> schema : meta.entrySet()) {
-                if (matches(schema.getKey(), schemaPtrn)) {
-                    for (Map.Entry<String, Map<String, ColumnInfo>> tbl : schema.getValue().entrySet()) {
-                        if (matches(tbl.getKey(), tblNamePtrn)) {
-                            for (Map.Entry<String, ColumnInfo> col : tbl.getValue().entrySet()) {
-                                rows.add(columnRow(schema.getKey(), tbl.getKey(), col.getKey(),
-                                    JdbcUtils.type(col.getValue().typeName()), JdbcUtils.typeName(col.getValue().typeName()),
-                                    !col.getValue().isNotNull(), ++cnt));
-                            }
-                        }
-                    }
-                }
-            }
+            Collection<JdbcColumnMeta> colMetas =
+                meta.getColumnsMeta(null /* latest */, schemaPtrn, tblNamePtrn, colNamePtrn);
+
+            rows = new ArrayList<>(colMetas.size());
+
+            for (JdbcColumnMeta col : colMetas)
+                rows.add(columnRow(col, ++cnt));
         }
 
         return new JdbcResultSet(true, null,
@@ -872,48 +843,6 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
         );
     }
 
-    /**
-     * @param schema Schema name.
-     * @param tbl Table name.
-     * @param col Column name.
-     * @param type Type.
-     * @param typeName Type name.
-     * @param nullable Nullable flag.
-     * @param pos Ordinal position.
-     * @return Column metadata row.
-     */
-    private List<Object> columnRow(String schema, String tbl, String col, int type, String typeName,
-        boolean nullable, int pos) {
-        List<Object> row = new ArrayList<>(24);
-
-        row.add(CATALOG_NAME);          // 1. TABLE_CAT
-        row.add(schema);                // 2. TABLE_SCHEM
-        row.add(tbl);                   // 3. TABLE_NAME
-        row.add(col);                   // 4. COLUMN_NAME
-        row.add(type);                  // 5. DATA_TYPE
-        row.add(typeName);              // 6. TYPE_NAME
-        row.add(null);                  // 7. COLUMN_SIZE
-        row.add(null);                  // 8. BUFFER_LENGTH
-        row.add(null);                  // 9. DECIMAL_DIGITS
-        row.add(10);                    // 10. NUM_PREC_RADIX
-        row.add(nullable ? columnNullable : columnNoNulls); // 11. NULLABLE
-        row.add(null);                  // 12. REMARKS
-        row.add(null);                  // 13. COLUMN_DEF
-        row.add(type);                  // 14. SQL_DATA_TYPE
-        row.add(null);                  // 15. SQL_DATETIME_SUB
-        row.add(Integer.MAX_VALUE);     // 16. CHAR_OCTET_LENGTH
-        row.add(pos);                   // 17. ORDINAL_POSITION
-        row.add(nullable ? "YES" : "NO"); // 18. IS_NULLABLE
-        row.add(null);                  // 19. SCOPE_CATALOG
-        row.add(null);                  // 20. SCOPE_SCHEMA
-        row.add(null);                  // 21. SCOPE_TABLE
-        row.add(null);                  // 22. SOURCE_DATA_TYPE
-        row.add("NO");                  // 23. IS_AUTOINCREMENT
-        row.add("NO");                  // 24. IS_GENERATEDCOLUMN
-
-        return row;
-    }
-
     /** {@inheritDoc} */
     @Override public ResultSet getColumnPrivileges(String catalog, String schema, String tbl,
         String colNamePtrn) throws SQLException {
@@ -968,20 +897,21 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     /** {@inheritDoc} */
     @Override public ResultSet getPrimaryKeys(String catalog, String schemaPtrn, String tblNamePtrn)
         throws SQLException {
-        updateMetaData();
+        conn.ensureNotClosed();
 
-        List<List<?>> rows = new LinkedList<>();
+        List<List<?>> rows;
 
         if (isValidCatalog(catalog)) {
-            for (Map.Entry<String, Map<String, Map<String, ColumnInfo>>> schema : meta.entrySet()) {
-                if (matches(schema.getKey(), schemaPtrn)) {
-                    for (Map.Entry<String, Map<String, ColumnInfo>> tbl : schema.getValue().entrySet()) {
-                        if (matches(tbl.getKey(), tblNamePtrn))
-                            rows.add(Arrays.<Object>asList(CATALOG_NAME, schema.getKey(), tbl.getKey(), "_KEY", 1, "_KEY"));
-                    }
-                }
-            }
+            Collection<JdbcPrimaryKeyMeta> tabsKeyInfo = meta.getPrimaryKeys(schemaPtrn, tblNamePtrn);
+
+            rows = new ArrayList<>();
+
+            for (JdbcPrimaryKeyMeta keyInfo : tabsKeyInfo)
+                rows.addAll(primaryKeyRows(keyInfo));
+
         }
+        else
+            rows = Collections.emptyList();
 
         return new JdbcResultSet(true, null,
             conn.createStatement0(),
@@ -1045,35 +975,18 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     /** {@inheritDoc} */
     @Override public ResultSet getIndexInfo(String catalog, String schema, String tbl, boolean unique,
         boolean approximate) throws SQLException {
-        updateMetaData();
+        conn.ensureNotClosed();
 
-        List<List<?>> rows = new ArrayList<>(indexes.size());
+        List<List<?>> rows = Collections.emptyList();
 
         if (isValidCatalog(catalog)) {
-            for (List<Object> idx : indexes) {
-                String idxSchema = (String)idx.get(0);
-                String idxTbl = (String)idx.get(1);
-
-                if ((schema == null || schema.equals(idxSchema)) && (tbl == null || tbl.equals(idxTbl))) {
-                    List<Object> row = new ArrayList<>(13);
-
-                    row.add(CATALOG_NAME);
-                    row.add(idxSchema);
-                    row.add(idxTbl);
-                    row.add(idx.get(2));
-                    row.add(null);
-                    row.add(idx.get(3));
-                    row.add((int)tableIndexOther);
-                    row.add(idx.get(4));
-                    row.add(idx.get(5));
-                    row.add((Boolean)idx.get(6) ? "D" : "A");
-                    row.add(0);
-                    row.add(0);
-                    row.add(null);
-
-                    rows.add(row);
-                }
-            }
+            // Currently we are treating schema and tbl as sql patterns.
+            SortedSet<JdbcIndexMeta> idxMetas = meta.getIndexesMeta(schema, tbl);
+
+            rows = new ArrayList<>(idxMetas.size());
+
+            for (JdbcIndexMeta idxMeta : idxMetas)
+                rows.addAll(indexRows(idxMeta));
         }
 
         return new JdbcResultSet(true, null,
@@ -1269,12 +1182,16 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
 
     /** {@inheritDoc} */
     @Override public ResultSet getSchemas(String catalog, String schemaPtrn) throws SQLException {
-        updateMetaData();
+        conn.ensureNotClosed();
 
-        List<List<?>> rows = new ArrayList<>(meta.size());
+        List<List<?>> rows = Collections.emptyList();
 
         if (isValidCatalog(catalog)) {
-            for (String schema : meta.keySet()) {
+            Set<String> schemas = meta.getSchemasMeta(schemaPtrn);
+
+            rows = new ArrayList<>(schemas.size());
+
+            for (String schema : schemas) {
                 if (matches(schema, schemaPtrn))
                     rows.add(Arrays.<Object>asList(schema, CATALOG_NAME));
             }
@@ -1376,72 +1293,6 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     }
 
     /**
-     * Updates meta data.
-     *
-     * @throws SQLException In case of error.
-     */
-    private void updateMetaData() throws SQLException {
-        if (conn.isClosed())
-            throw new SQLException("Connection is closed.", SqlStateCode.CONNECTION_CLOSED);
-
-        try {
-            Ignite ignite = conn.ignite();
-
-            UUID nodeId = conn.nodeId();
-
-            Collection<GridCacheSqlMetadata> metas;
-
-            UpdateMetadataTask task = new UpdateMetadataTask(conn.cacheName(), nodeId == null ? ignite : null);
-
-            metas = nodeId == null ? task.call() : ignite.compute(ignite.cluster().forNodeId(nodeId)).call(task);
-
-            meta = U.newHashMap(metas.size());
-
-            indexes = new ArrayList<>();
-
-            for (GridCacheSqlMetadata m : metas) {
-                String name = m.cacheName();
-
-                if (name == null)
-                    name = "PUBLIC";
-
-                Collection<String> types = m.types();
-
-                Map<String, Map<String, ColumnInfo>> typesMap = U.newHashMap(types.size());
-
-                for (String type : types) {
-                    Collection<String> notNullFields = m.notNullFields(type);
-
-                    Map<String, ColumnInfo> fields = new LinkedHashMap<>();
-
-                    for (Map.Entry<String, String> fld : m.fields(type).entrySet()) {
-                        ColumnInfo colInfo = new ColumnInfo(fld.getValue(),
-                            notNullFields == null ? false : notNullFields.contains(fld.getKey()));
-
-                        fields.put(fld.getKey(), colInfo);
-                    }
-
-                    typesMap.put(type.toUpperCase(), fields);
-
-                    for (GridCacheSqlIndexMetadata idx : m.indexes(type)) {
-                        int cnt = 0;
-
-                        for (String field : idx.fields()) {
-                            indexes.add(F.<Object>asList(name, type.toUpperCase(), !idx.unique(),
-                                idx.name(), ++cnt, field, idx.descending(field)));
-                        }
-                    }
-                }
-
-                meta.put(name, typesMap);
-            }
-        }
-        catch (Exception e) {
-            throw convertToSqlException(e, "Failed to get meta data from Ignite.");
-        }
-    }
-
-    /**
      * Checks whether string matches SQL pattern.
      *
      * @param str String.
@@ -1454,10 +1305,10 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     }
 
     /**
-     * Checks if specified catalog matches the only possible catalog value. See {@link #CATALOG_NAME}.
+     * Checks if specified catalog matches the only possible catalog value. See {@link JdbcUtils#CATALOG_NAME}.
      *
      * @param catalog Catalog name or {@code null}.
-     * @return {@code true} If catalog equal ignoring case to {@link #CATALOG_NAME} or null (which means any catalog).
+     * @return {@code true} If catalog equal ignoring case to {@link JdbcUtils#CATALOG_NAME} or null (which means any catalog).
      *  Otherwise returns {@code false}.
      */
     private static boolean isValidCatalog(String catalog) {
@@ -1465,7 +1316,9 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     }
 
     /**
+     * This class is held only for compatibility purposes and shouldn't be used;
      *
+     * @deprecated Use {@link JdbcMetadataInfo} instead.
      */
     private static class UpdateMetadataTask implements IgniteCallable<Collection<GridCacheSqlMetadata>> {
         /** Serial version uid. */
@@ -1497,7 +1350,11 @@ public class JdbcDatabaseMetadata implements DatabaseMetaData {
     }
 
     /**
+     * This class is held only for compatibility purposes and shouldn't be used;
+     *
      * Column info.
+     *
+     * @deprecated Use {@link JdbcMetadataInfo} instead.
      */
     private static class ColumnInfo {
         /** Class name. */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
index 80881d7..6f7d3a4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/jdbc2/JdbcUtils.java
@@ -23,16 +23,25 @@ import java.sql.SQLException;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.sql.Types;
+import java.util.ArrayList;
 import java.util.Date;
-
+import java.util.List;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcColumnMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcIndexMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcPrimaryKeyMeta;
+import org.apache.ignite.internal.processors.odbc.jdbc.JdbcTableMeta;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.QueryUtils;
 
+import static java.sql.DatabaseMetaData.columnNullable;
+import static java.sql.DatabaseMetaData.tableIndexOther;
+import static java.sql.ResultSetMetaData.columnNoNulls;
 import static java.sql.Types.BIGINT;
 import static java.sql.Types.BINARY;
 import static java.sql.Types.BOOLEAN;
 import static java.sql.Types.DATE;
+import static java.sql.Types.DECIMAL;
 import static java.sql.Types.DOUBLE;
 import static java.sql.Types.FLOAT;
 import static java.sql.Types.INTEGER;
@@ -42,11 +51,13 @@ import static java.sql.Types.TIME;
 import static java.sql.Types.TIMESTAMP;
 import static java.sql.Types.TINYINT;
 import static java.sql.Types.VARCHAR;
-import static java.sql.Types.DECIMAL;
 /**
  * Utility methods for JDBC driver.
  */
 public class JdbcUtils {
+    /** The only possible name for catalog. */
+    public static final String CATALOG_NAME = "IGNITE";
+
     /**
      * Converts Java class name to type from {@link Types}.
      *
@@ -188,4 +199,114 @@ public class JdbcUtils {
 
         return new SQLException(msgForUnknown, sqlStateForUnknown, e);
     }
+
+    /**
+     * @param colMeta Column metadata.
+     * @param pos Ordinal position.
+     * @return Column metadata row.
+     */
+    public static List<Object> columnRow(JdbcColumnMeta colMeta, int pos) {
+        List<Object> row = new ArrayList<>(24);
+
+        row.add(CATALOG_NAME);                  // 1. TABLE_CAT
+        row.add(colMeta.schemaName());          // 2. TABLE_SCHEM
+        row.add(colMeta.tableName());           // 3. TABLE_NAME
+        row.add(colMeta.columnName());          // 4. COLUMN_NAME
+        row.add(colMeta.dataType());            // 5. DATA_TYPE
+        row.add(colMeta.dataTypeName());        // 6. TYPE_NAME
+        row.add(colMeta.precision() == -1 ? null : colMeta.precision()); // 7. COLUMN_SIZE
+        row.add((Integer)null);                 // 8. BUFFER_LENGTH
+        row.add(colMeta.scale() == -1 ? null : colMeta.scale());           // 9. DECIMAL_DIGITS
+        row.add(10);                            // 10. NUM_PREC_RADIX
+        row.add(colMeta.isNullable() ? columnNullable : columnNoNulls);  // 11. NULLABLE
+        row.add((String)null);                  // 12. REMARKS
+        row.add(colMeta.defaultValue());        // 13. COLUMN_DEF
+        row.add(colMeta.dataType());            // 14. SQL_DATA_TYPE
+        row.add((Integer)null);                 // 15. SQL_DATETIME_SUB
+        row.add(Integer.MAX_VALUE);             // 16. CHAR_OCTET_LENGTH
+        row.add(pos);                           // 17. ORDINAL_POSITION
+        row.add(colMeta.isNullable() ? "YES" : "NO"); // 18. IS_NULLABLE
+        row.add((String)null);                  // 19. SCOPE_CATALOG
+        row.add((String)null);                  // 20. SCOPE_SCHEMA
+        row.add((String)null);                  // 21. SCOPE_TABLE
+        row.add((Short)null);                   // 22. SOURCE_DATA_TYPE
+        row.add("NO");                          // 23. IS_AUTOINCREMENT
+        row.add("NO");                          // 23. IS_GENERATEDCOLUMN
+
+        return row;
+    }
+
+    /**
+     * @param idxMeta Index metadata.
+     * @return List of result rows correspond to index.
+     */
+    public static List<List<Object>> indexRows(JdbcIndexMeta idxMeta) {
+        List<List<Object>> rows = new ArrayList<>(idxMeta.fields().size());
+
+        for (int i = 0; i < idxMeta.fields().size(); ++i) {
+            List<Object> row = new ArrayList<>(13);
+
+            row.add(CATALOG_NAME);              // TABLE_CAT
+            row.add(idxMeta.schemaName());      // TABLE_SCHEM
+            row.add(idxMeta.tableName());       // TABLE_NAME
+            row.add(true);                      // NON_UNIQUE
+            row.add(null);                      // INDEX_QUALIFIER (index catalog)
+            row.add(idxMeta.indexName());       // INDEX_NAME
+            row.add(tableIndexOther);           // TYPE
+            row.add(i + 1);                     // ORDINAL_POSITION
+            row.add(idxMeta.fields().get(i));   // COLUMN_NAME
+            row.add(idxMeta.fieldsAsc().get(i) ? "A" : "D");  // ASC_OR_DESC
+            row.add((Integer)0);                // CARDINALITY
+            row.add((Integer)0);                // PAGES
+            row.add((String)null);              // FILTER_CONDITION
+
+            rows.add(row);
+        }
+
+        return rows;
+    }
+
+    /**
+     * @param pkMeta Primary key metadata.
+     * @return Result set rows for primary key.
+     */
+    public static List<List<Object>> primaryKeyRows(JdbcPrimaryKeyMeta pkMeta) {
+        List<List<Object>> rows = new ArrayList<>(pkMeta.fields().size());
+
+        for (int i = 0; i < pkMeta.fields().size(); ++i) {
+            List<Object> row = new ArrayList<>(6);
+
+            row.add(CATALOG_NAME); // table catalog
+            row.add(pkMeta.schemaName());
+            row.add(pkMeta.tableName());
+            row.add(pkMeta.fields().get(i));
+            row.add(i + 1); // sequence number
+            row.add(pkMeta.name());
+
+            rows.add(row);
+        }
+
+        return rows;
+    }
+
+    /**
+     * @param tblMeta Table metadata.
+     * @return Table metadata row.
+     */
+    public static List<Object> tableRow(JdbcTableMeta tblMeta) {
+        List<Object> row = new ArrayList<>(10);
+
+        row.add(CATALOG_NAME);
+        row.add(tblMeta.schemaName());
+        row.add(tblMeta.tableName());
+        row.add("TABLE");
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+        row.add(null);
+
+        return row;
+    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcMetadataInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcMetadataInfo.java
new file mode 100644
index 0000000..77d61f7
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcMetadataInfo.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.odbc.jdbc;
+
+import java.sql.DatabaseMetaData;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion;
+import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
+import org.apache.ignite.internal.processors.query.GridQueryProperty;
+import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
+import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_3_0;
+import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_4_0;
+import static org.apache.ignite.internal.processors.odbc.jdbc.JdbcConnectionContext.VER_2_7_0;
+import static org.apache.ignite.internal.processors.query.QueryUtils.matches;
+
+/**
+ * Facade over {@link GridKernalContext} to get information about database entities in terms of JDBC.
+ */
+public class JdbcMetadataInfo {
+    /** Root context. Used to get all the database metadata. */
+    private final GridKernalContext ctx;
+
+    /** The only one possible value of table type. */
+    public static final String TABLE_TYPE = "TABLE";
+
+    /**
+     * Initializes info.
+     *
+     * @param ctx GridKernalContext
+     */
+    public JdbcMetadataInfo(GridKernalContext ctx) {
+        this.ctx = ctx;
+    }
+
+    /**
+     * See {@link DatabaseMetaData#getPrimaryKeys(String, String, String)} for details.
+     *
+     * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+     *
+     * @return Collection of primary keys information for tables that matches specified schema and table name patterns.
+     */
+    public Collection<JdbcPrimaryKeyMeta> getPrimaryKeys(String schemaNamePtrn, String tableNamePtrn) {
+        Collection<JdbcPrimaryKeyMeta> meta = new HashSet<>();
+
+        for (String cacheName : ctx.cache().publicCacheNames()) {
+            for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
+                if (!matches(table.schemaName(), schemaNamePtrn))
+                    continue;
+
+                if (!matches(table.tableName(), tableNamePtrn))
+                    continue;
+
+                List<String> fields = new ArrayList<>();
+
+                for (String field : table.fields().keySet()) {
+                    if (table.property(field).key())
+                        fields.add(field);
+                }
+
+                final String keyName = table.keyFieldName() == null ?
+                    "PK_" + table.schemaName() + "_" + table.tableName() :
+                    table.keyFieldName();
+
+                if (fields.isEmpty()) {
+                    String keyColName =
+                        table.keyFieldName() == null ? QueryUtils.KEY_FIELD_NAME : table.keyFieldName();
+
+                    meta.add(new JdbcPrimaryKeyMeta(table.schemaName(), table.tableName(), keyName,
+                        Collections.singletonList(keyColName)));
+                }
+                else
+                    meta.add(new JdbcPrimaryKeyMeta(table.schemaName(), table.tableName(), keyName, fields));
+            }
+        }
+
+        return meta;
+    }
+
+    /**
+     * See {@link DatabaseMetaData#getTables(String, String, String, String[])} for details.
+     *
+     * Ignite has only one possible value for CATALOG_NAME and has only one table type so these parameters are handled
+     * on the client (driver) side.
+     *
+     * Result is ordered by (schema name, table name).
+     *
+     * @param schemaPtrn sql pattern for schema name.
+     * @param tabPtrn sql pattern for table name.
+     * @return List of metadatas of tables that matches .
+     */
+    public List<JdbcTableMeta> getTablesMeta(String schemaPtrn, String tabPtrn) {
+        Comparator<JdbcTableMeta> bySchemaThenTabname = new Comparator<JdbcTableMeta>() {
+            @Override public int compare(JdbcTableMeta o1, JdbcTableMeta o2) {
+                int schemCmp = o1.schemaName().compareTo(o2.schemaName());
+
+                if (schemCmp != 0)
+                    return schemCmp;
+
+                return o1.tableName().compareTo(o2.tableName());
+            }
+        };
+
+        TreeSet<JdbcTableMeta> tabMetas = new TreeSet<>(bySchemaThenTabname);
+
+        for (String cacheName : ctx.cache().publicCacheNames()) {
+            for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
+                if (!matches(table.schemaName(), schemaPtrn))
+                    continue;
+
+                if (!matches(table.tableName(), tabPtrn))
+                    continue;
+
+                JdbcTableMeta tableMeta = new JdbcTableMeta(table.schemaName(), table.tableName(), TABLE_TYPE);
+
+                tabMetas.add(tableMeta);
+            }
+        }
+
+        return new ArrayList<>(tabMetas);
+    }
+
+    /**
+     * See {@link DatabaseMetaData#getColumns(String, String, String, String)} for details.
+     *
+     * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+     *
+     * @param protocolVer for what version of protocol to generate metadata. Early versions of protocol don't support
+     * some features like default values or precision/scale. If {@code null}, current version will be used.
+     * @return List of metadatas about columns that match specified schema/tablename/columnname criterias.
+     */
+    public Collection<JdbcColumnMeta> getColumnsMeta(@Nullable ClientListenerProtocolVersion protocolVer,
+        String schemaNamePtrn, String tableNamePtrn, String columnNamePtrn) {
+
+        boolean useNewest = protocolVer == null;
+
+        Collection<JdbcColumnMeta> metas = new LinkedHashSet<>();
+
+        for (String cacheName : ctx.cache().publicCacheNames()) {
+            for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
+                if (!matches(table.schemaName(), schemaNamePtrn))
+                    continue;
+
+                if (!matches(table.tableName(), tableNamePtrn))
+                    continue;
+
+                for (Map.Entry<String, Class<?>> field : table.fields().entrySet()) {
+                    String colName = field.getKey();
+
+                    Class<?> fieldCls = field.getValue();
+
+                    if (!matches(colName, columnNamePtrn))
+                        continue;
+
+                    JdbcColumnMeta columnMeta;
+
+                    if (useNewest || protocolVer.compareTo(VER_2_7_0) >= 0) {
+                        GridQueryProperty prop = table.property(colName);
+
+                        columnMeta = new JdbcColumnMetaV4(table.schemaName(), table.tableName(),
+                            colName, fieldCls, !prop.notNull(), prop.defaultValue(),
+                            prop.precision(), prop.scale());
+                    }
+                    else if (protocolVer.compareTo(VER_2_4_0) >= 0) {
+                        GridQueryProperty prop = table.property(colName);
+
+                        columnMeta = new JdbcColumnMetaV3(table.schemaName(), table.tableName(),
+                            colName, fieldCls, !prop.notNull(), prop.defaultValue());
+                    }
+                    else if (protocolVer.compareTo(VER_2_3_0) >= 0) {
+                        GridQueryProperty prop = table.property(colName);
+
+                        columnMeta = new JdbcColumnMetaV2(table.schemaName(), table.tableName(),
+                            colName, fieldCls, !prop.notNull());
+                    }
+                    else
+                        columnMeta = new JdbcColumnMeta(table.schemaName(), table.tableName(),
+                            colName, fieldCls);
+
+                    if (!metas.contains(columnMeta))
+                        metas.add(columnMeta);
+                }
+            }
+        }
+
+        return metas;
+    }
+
+    /**
+     * See {@link DatabaseMetaData#getSchemas(String, String)} for details.
+     *
+     * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side.
+     *
+     * @param schemaNamePtrn sql pattern for schema name filter.
+     * @return schema names that matches provided pattern.
+     */
+    public SortedSet<String> getSchemasMeta(String schemaNamePtrn) {
+        SortedSet<String> schemas = new TreeSet<>(); // to have values sorted.
+
+        for (String cacheName : ctx.cache().publicCacheNames()) {
+            for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
+                if (matches(table.schemaName(), schemaNamePtrn))
+                    schemas.add(table.schemaName());
+            }
+        }
+
+        return schemas;
+    }
+
+    /**
+     * See {@link DatabaseMetaData#getIndexInfo(String, String, String, boolean, boolean)} for details.
+     *
+     * Ignite has only one possible CATALOG_NAME, it is handled on the client (driver) side. Parameters {@code unique}
+     * {@code approximate} are ignored.
+     *
+     * @return Sorted by index name collection of index info, filtered according to specified criterias.
+     */
+    public SortedSet<JdbcIndexMeta> getIndexesMeta(String schemaNamePtrn, String tableNamePtrn) {
+        final Comparator<JdbcIndexMeta> byIndexName = new Comparator<JdbcIndexMeta>() {
+            @Override public int compare(JdbcIndexMeta o1, JdbcIndexMeta o2) {
+                return o1.indexName().compareTo(o2.indexName());
+            }
+        };
+
+        TreeSet<JdbcIndexMeta> meta = new TreeSet<>(byIndexName);
+
+        for (String cacheName : ctx.cache().publicCacheNames()) {
+            for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
+                if (!matches(table.schemaName(), schemaNamePtrn))
+                    continue;
+
+                if (!matches(table.tableName(), tableNamePtrn))
+                    continue;
+
+                for (GridQueryIndexDescriptor idxDesc : table.indexes().values())
+                    meta.add(new JdbcIndexMeta(table.schemaName(), table.tableName(), idxDesc));
+            }
+        }
+
+        return meta;
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
index 7f19491..da83515 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcRequestHandler.java
@@ -22,15 +22,12 @@ import java.sql.ParameterMetaData;
 import java.sql.Statement;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.PriorityQueue;
-import java.util.Set;
+import java.util.SortedSet;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.cache.configuration.Factory;
 import org.apache.ignite.IgniteCheckedException;
@@ -54,12 +51,7 @@ import org.apache.ignite.internal.processors.odbc.ClientListenerRequest;
 import org.apache.ignite.internal.processors.odbc.ClientListenerRequestHandler;
 import org.apache.ignite.internal.processors.odbc.ClientListenerResponse;
 import org.apache.ignite.internal.processors.odbc.ClientListenerResponseSender;
-import org.apache.ignite.internal.processors.odbc.SqlListenerUtils;
-import org.apache.ignite.internal.processors.odbc.odbc.OdbcQueryGetColumnsMetaRequest;
 import org.apache.ignite.internal.processors.query.GridQueryCancel;
-import org.apache.ignite.internal.processors.query.GridQueryIndexDescriptor;
-import org.apache.ignite.internal.processors.query.GridQueryProperty;
-import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.query.IgniteSQLException;
 import org.apache.ignite.internal.processors.query.NestedTxMode;
 import org.apache.ignite.internal.processors.query.QueryUtils;
@@ -148,6 +140,9 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
     /** Authentication context */
     private AuthorizationContext actx;
 
+    /** Facade that hides transformations internal cache api entities -> jdbc metadata. */
+    private final JdbcMetadataInfo meta;
+
     /** Register that keeps non-cancelled requests. */
     private Map<Long, JdbcQueryDescriptor> reqRegister = new HashMap<>();
 
@@ -175,6 +170,8 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
         this.ctx = ctx;
         this.sender = sender;
 
+        this.meta = new JdbcMetadataInfo(ctx);
+
         Factory<GridWorker> orderedFactory = new Factory<GridWorker>() {
             @Override public GridWorker create() {
                 return new OrderedBatchWorker();
@@ -983,24 +980,9 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
      */
     private JdbcResponse getTablesMeta(JdbcMetaTablesRequest req) {
         try {
-            List<JdbcTableMeta> meta = new ArrayList<>();
-
-            for (String cacheName : ctx.cache().publicCacheNames()) {
-                for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
-                    if (!matches(table.schemaName(), req.schemaName()))
-                        continue;
-
-                    if (!matches(table.tableName(), req.tableName()))
-                        continue;
-
-                    JdbcTableMeta tableMeta = new JdbcTableMeta(table.schemaName(), table.tableName(), "TABLE");
-
-                    if (!meta.contains(tableMeta))
-                        meta.add(tableMeta);
-                }
-            }
+            List<JdbcTableMeta> tabMetas = meta.getTablesMeta(req.schemaName(), req.tableName());
 
-            JdbcMetaTablesResult res = new JdbcMetaTablesResult(meta);
+            JdbcMetaTablesResult res = new JdbcMetaTablesResult(tabMetas);
 
             return new JdbcResponse(res);
         }
@@ -1012,70 +994,24 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
     }
 
     /**
-     * {@link OdbcQueryGetColumnsMetaRequest} command handler.
-     *
      * @param req Get columns metadata request.
      * @return Response.
      */
     private JdbcResponse getColumnsMeta(JdbcMetaColumnsRequest req) {
         try {
-            Collection<JdbcColumnMeta> meta = new LinkedHashSet<>();
-
-            for (String cacheName : ctx.cache().publicCacheNames()) {
-                for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
-                    if (!matches(table.schemaName(), req.schemaName()))
-                        continue;
-
-                    if (!matches(table.tableName(), req.tableName()))
-                        continue;
-
-                    for (Map.Entry<String, Class<?>> field : table.fields().entrySet()) {
-                        String colName = field.getKey();
-
-                        if (!matches(colName, req.columnName()))
-                            continue;
-
-                        JdbcColumnMeta columnMeta;
-
-                        if (protocolVer.compareTo(VER_2_7_0) >= 0) {
-                            GridQueryProperty prop = table.property(colName);
-
-                            columnMeta = new JdbcColumnMetaV4(table.schemaName(), table.tableName(),
-                                field.getKey(), field.getValue(), !prop.notNull(), prop.defaultValue(),
-                                prop.precision(), prop.scale());
-                        }
-                        else if (protocolVer.compareTo(VER_2_4_0) >= 0) {
-                            GridQueryProperty prop = table.property(colName);
-
-                            columnMeta = new JdbcColumnMetaV3(table.schemaName(), table.tableName(),
-                                field.getKey(), field.getValue(), !prop.notNull(), prop.defaultValue());
-                        }
-                        else if (protocolVer.compareTo(VER_2_3_0) >= 0) {
-                            GridQueryProperty prop = table.property(colName);
-
-                            columnMeta = new JdbcColumnMetaV2(table.schemaName(), table.tableName(),
-                                field.getKey(), field.getValue(), !prop.notNull());
-                        }
-                        else
-                            columnMeta = new JdbcColumnMeta(table.schemaName(), table.tableName(),
-                                field.getKey(), field.getValue());
-
-                        if (!meta.contains(columnMeta))
-                            meta.add(columnMeta);
-                    }
-                }
-            }
+            Collection<JdbcColumnMeta> colsMeta =
+                meta.getColumnsMeta(protocolVer, req.schemaName(), req.tableName(), req.columnName());
 
             JdbcMetaColumnsResult res;
 
             if (protocolVer.compareTo(VER_2_7_0) >= 0)
-                res = new JdbcMetaColumnsResultV4(meta);
+                res = new JdbcMetaColumnsResultV4(colsMeta);
             else if (protocolVer.compareTo(VER_2_4_0) >= 0)
-                res = new JdbcMetaColumnsResultV3(meta);
+                res = new JdbcMetaColumnsResultV3(colsMeta);
             else if (protocolVer.compareTo(VER_2_3_0) >= 0)
-                res = new JdbcMetaColumnsResultV2(meta);
+                res = new JdbcMetaColumnsResultV2(colsMeta);
             else
-                res = new JdbcMetaColumnsResult(meta);
+                res = new JdbcMetaColumnsResult(colsMeta);
 
             return new JdbcResponse(res);
         }
@@ -1092,22 +1028,9 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
      */
     private ClientListenerResponse getIndexesMeta(JdbcMetaIndexesRequest req) {
         try {
-            Collection<JdbcIndexMeta> meta = new HashSet<>();
-
-            for (String cacheName : ctx.cache().publicCacheNames()) {
-                for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
-                    if (!matches(table.schemaName(), req.schemaName()))
-                        continue;
+            Collection<JdbcIndexMeta> idxInfos = meta.getIndexesMeta(req.schemaName(), req.tableName());
 
-                    if (!matches(table.tableName(), req.tableName()))
-                        continue;
-
-                    for (GridQueryIndexDescriptor idxDesc : table.indexes().values())
-                        meta.add(new JdbcIndexMeta(table.schemaName(), table.tableName(), idxDesc));
-                }
-            }
-
-            return new JdbcResponse(new JdbcMetaIndexesResult(meta));
+            return new JdbcResponse(new JdbcMetaIndexesResult(idxInfos));
         }
         catch (Exception e) {
             U.error(log, "Failed to get parameters metadata [reqId=" + req.requestId() + ", req=" + req + ']', e);
@@ -1149,40 +1072,9 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
      */
     private ClientListenerResponse getPrimaryKeys(JdbcMetaPrimaryKeysRequest req) {
         try {
-            Collection<JdbcPrimaryKeyMeta> meta = new HashSet<>();
-
-            for (String cacheName : ctx.cache().publicCacheNames()) {
-                for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
-                    if (!matches(table.schemaName(), req.schemaName()))
-                        continue;
-
-                    if (!matches(table.tableName(), req.tableName()))
-                        continue;
-
-                    List<String> fields = new ArrayList<>();
-
-                    for (String field : table.fields().keySet()) {
-                        if (table.property(field).key())
-                            fields.add(field);
-                    }
-
-                    final String keyName = table.keyFieldName() == null ?
-                        "PK_" + table.schemaName() + "_" + table.tableName() :
-                        table.keyFieldName();
-
-                    if (fields.isEmpty()) {
-                        String keyColName =
-                            table.keyFieldName() == null ? QueryUtils.KEY_FIELD_NAME : table.keyFieldName();
-
-                        meta.add(new JdbcPrimaryKeyMeta(table.schemaName(), table.tableName(), keyName,
-                            Collections.singletonList(keyColName)));
-                    }
-                    else
-                        meta.add(new JdbcPrimaryKeyMeta(table.schemaName(), table.tableName(), keyName, fields));
-                }
-            }
+            Collection<JdbcPrimaryKeyMeta> pkMeta = meta.getPrimaryKeys(req.schemaName(), req.tableName());
 
-            return new JdbcResponse(new JdbcMetaPrimaryKeysResult(meta));
+            return new JdbcResponse(new JdbcMetaPrimaryKeysResult(pkMeta));
         }
         catch (Exception e) {
             U.error(log, "Failed to get parameters metadata [reqId=" + req.requestId() + ", req=" + req + ']', e);
@@ -1199,14 +1091,7 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
         try {
             String schemaPtrn = req.schemaName();
 
-            Set<String> schemas = new HashSet<>();
-
-            for (String cacheName : ctx.cache().publicCacheNames()) {
-                for (GridQueryTypeDescriptor table : ctx.query().types(cacheName)) {
-                    if (matches(table.schemaName(), schemaPtrn))
-                        schemas.add(table.schemaName());
-                }
-            }
+            SortedSet<String> schemas = meta.getSchemasMeta(schemaPtrn);
 
             return new JdbcResponse(new JdbcMetaSchemasResult(schemas));
         }
@@ -1218,25 +1103,6 @@ public class JdbcRequestHandler implements ClientListenerRequestHandler {
     }
 
     /**
-     * Checks whether string matches SQL pattern.
-     *
-     * @param str String.
-     * @param sqlPtrn Pattern.
-     * @return Whether string matches pattern.
-     */
-    private static boolean matches(String str, String sqlPtrn) {
-        if (str == null)
-            return false;
-
-        if (sqlPtrn == null)
-            return true;
-
-        String regex = SqlListenerUtils.translateSqlWildcardsToRegex(sqlPtrn);
-
-        return str.matches(regex);
-    }
-
-    /**
      * Create {@link JdbcResponse} bearing appropriate Ignite specific result code if possible
      *     from given {@link Exception}.
      *
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
index 322bd8d..2942713 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/QueryUtils.java
@@ -54,6 +54,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
 import org.apache.ignite.internal.processors.cache.GridCacheDefaultAffinityKeyMapper;
 import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
+import org.apache.ignite.internal.processors.odbc.SqlListenerUtils;
 import org.apache.ignite.internal.processors.query.property.QueryBinaryProperty;
 import org.apache.ignite.internal.processors.query.property.QueryClassProperty;
 import org.apache.ignite.internal.processors.query.property.QueryFieldAccessor;
@@ -177,6 +178,7 @@ public class QueryUtils {
         return indexName(tableName(entity), idx);
     }
 
+
     /**
      * Get index name.
      *
@@ -614,6 +616,7 @@ public class QueryUtils {
             d.addProperty(prop, false);
         }
 
+        // Sql-typed key/value doesn't have field property, but they may have precision and scale constraints.
         String keyFieldName = qryEntity.getKeyFieldName();
 
         if (keyFieldName == null)
@@ -803,8 +806,8 @@ public class QueryUtils {
      *      nested fields.
      * @param resType Result type.
      * @param aliases Aliases.
-     * @param isKeyField Key ownership flag, as defined in {@link QueryEntity#keyFields}: {@code true} if field belongs
-     *      to key, {@code false} if it belongs to value, {@code null} if QueryEntity#keyFields is null.
+     * @param isKeyField Key ownership flag, {@code true} if this property is a field of the key object. Note that key
+     * not a field of itself.
      * @param notNull {@code true} if {@code null} value is not allowed.
      * @param dlftVal Default value.
      * @param precision Precision.
@@ -1469,6 +1472,25 @@ public class QueryUtils {
     }
 
     /**
+     * Checks whether string matches SQL pattern.
+     *
+     * @param str String.
+     * @param sqlPtrn Pattern.
+     * @return Whether string matches pattern.
+     */
+    public static boolean matches(String str, String sqlPtrn) {
+        if (str == null)
+            return false;
+
+        if (sqlPtrn == null)
+            return true;
+
+        String regex = SqlListenerUtils.translateSqlWildcardsToRegex(sqlPtrn);
+
+        return str.matches(regex);
+    }
+
+    /**
      * Private constructor.
      */
     private QueryUtils() {
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
index 027ac7e..068e3b7 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/dml/UpdatePlanBuilder.java
@@ -294,13 +294,16 @@ public final class UpdatePlanBuilder {
             colTypes[i] = col.resultType().type();
 
             int colId = col.column().getColumnId();
+
             if (desc.isKeyColumn(colId)) {
                 keyColIdx = i;
+
                 continue;
             }
 
             if (desc.isValueColumn(colId)) {
                 valColIdx = i;
+
                 continue;
             }
 
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
index ae57880..716fdc2 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Cache/Query/CacheDmlQueriesTest.cs
@@ -284,7 +284,7 @@ namespace Apache.Ignite.Core.Tests.Cache.Query
                 Fields = new[]
                 {
                     /// Next two fieleds belong to the <see cref="Key"/> object, so should have been marked with <see cref="QueryField.IsKeyField"/>
-                    // But if we forgot to do this - all fields are treated as value fields. Key fields have default values and second insert fails. 
+                    // But if we forgot to do this - all fields are treated as value fields. Key fields have default values and second insert fails.
                     new QueryField("Lo", typeof(int)),
                     new QueryField("Hi", typeof(int)),