You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2023/10/17 06:30:43 UTC

[ignite-3] branch main updated: IGNITE-19219 Implement primary keys query in ODBC (#2699)

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

isapego 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 b49dd9611c IGNITE-19219  Implement primary keys query in ODBC (#2699)
b49dd9611c is described below

commit b49dd9611c2dc5a732d41a7f7cc725339ecb27b9
Author: Igor Sapego <is...@apache.org>
AuthorDate: Tue Oct 17 10:30:38 2023 +0400

    IGNITE-19219  Implement primary keys query in ODBC (#2699)
---
 modules/platforms/cpp/ignite/odbc/CMakeLists.txt   |   1 +
 .../ignite/odbc/query/column_metadata_query.cpp    |   6 +-
 ...e_metadata_query.cpp => primary_keys_query.cpp} | 204 +++++++++++----------
 .../cpp/ignite/odbc/query/primary_keys_query.h     | 137 ++++++++++++++
 modules/platforms/cpp/ignite/odbc/query/query.h    |   3 +
 .../cpp/ignite/odbc/query/table_metadata_query.cpp |   6 +-
 .../platforms/cpp/ignite/odbc/sql_statement.cpp    |   9 +-
 modules/platforms/cpp/ignite/odbc/sql_statement.h  |   2 +-
 .../cpp/ignite/protocol/client_operation.h         |   3 +
 .../cpp/tests/odbc-test/api_robustness_test.cpp    | 146 ++++++++-------
 .../cpp/tests/odbc-test/meta_queries_test.cpp      |  75 ++++++++
 11 files changed, 413 insertions(+), 179 deletions(-)

diff --git a/modules/platforms/cpp/ignite/odbc/CMakeLists.txt b/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
index 3fea05ef88..581b3b89af 100644
--- a/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
@@ -38,6 +38,7 @@ set(SOURCES
     query/column_metadata_query.cpp
     query/data_query.cpp
     query/foreign_keys_query.cpp
+    query/primary_keys_query.cpp
     query/table_metadata_query.cpp
     query/type_info_query.cpp
     odbc.cpp
diff --git a/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp b/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
index fb31c62911..9edab15e3d 100644
--- a/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/column_metadata_query.cpp
@@ -167,10 +167,8 @@ sql_result column_metadata_query::fetch_next_row(column_binding_map &column_bind
     if (m_cursor == m_meta.end())
         return sql_result::AI_NO_DATA;
 
-    column_binding_map::iterator it;
-
-    for (it = column_bindings.begin(); it != column_bindings.end(); ++it)
-        get_column(it->first, it->second);
+    for (auto &binding : column_bindings)
+        get_column(binding.first, binding.second);
 
     return sql_result::AI_SUCCESS;
 }
diff --git a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
similarity index 53%
copy from modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
copy to modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
index 710ca8eb28..6d81f467ac 100644
--- a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.cpp
@@ -18,10 +18,8 @@
 #include <utility>
 
 #include "ignite/odbc/log.h"
-#include "ignite/odbc/odbc_error.h"
-#include "ignite/odbc/query/table_metadata_query.h"
+#include "ignite/odbc/query/primary_keys_query.h"
 #include "ignite/odbc/sql_connection.h"
-#include "ignite/odbc/string_utils.h"
 #include "ignite/odbc/type_traits.h"
 
 namespace {
@@ -36,26 +34,65 @@ enum class result_column {
     /** Table name. */
     TABLE_NAME,
 
-    /** Table type. */
-    TABLE_TYPE,
+    /** Column name. */
+    COLUMN_NAME,
 
-    /** A description of the column. */
-    REMARKS
+    /** Column sequence number in key. */
+    KEY_SEQ,
+
+    /** Primary key name. */
+    PK_NAME
 };
 
-} // namespace
+using namespace ignite;
+
+/**
+ * Reads primary keys.
+ *
+ * @param reader Reader.
+ * @return Primary keys meta.
+ */
+primary_key_meta_vector read_meta(protocol::reader &reader) {
+    auto has_no_data = reader.try_read_nil();
+    if (has_no_data)
+        return {};
+
+    auto tables_num = reader.read_int32();
+
+    primary_key_meta_vector keys;
+    keys.reserve(tables_num);
+
+    for (std::int32_t table_idx = 0; table_idx < tables_num; ++table_idx) {
+        auto schema_name = reader.read_string();
+        auto table_name = reader.read_string();
+        auto key_name = reader.read_string();
+
+        auto have_no_fields = reader.try_read_nil();
+        if (have_no_fields)
+            continue;
+
+        auto fields_num = reader.read_int32();
+        for (std::int32_t field_idx = 0; field_idx < fields_num; ++field_idx) {
+            auto field_name = reader.read_string();
+
+            keys.emplace_back("", schema_name, table_name, field_name, std::int16_t(field_idx + 1), key_name);
+        }
+    }
+
+    return keys;
+}
+
+} // anonymous namespace
 
 namespace ignite {
 
-table_metadata_query::table_metadata_query(diagnosable_adapter &diag, sql_connection &connection, std::string catalog,
-    std::string schema, std::string table, std::string table_type)
-    : query(diag, query_type::TABLE_METADATA)
+primary_keys_query::primary_keys_query(
+    diagnosable_adapter &diag, sql_connection &connection, std::string schema, std::string table)
+    : query(diag, query_type::PRIMARY_KEYS)
     , m_connection(connection)
-    , m_catalog(std::move(catalog))
     , m_schema(std::move(schema))
-    , m_table(std::move(table))
-    , m_table_type(std::move(table_type)) {
-    m_columns_meta.reserve(5);
+    , m_table(std::move(table)) {
+    m_columns_meta.reserve(6);
 
     const std::string sch;
     const std::string tbl;
@@ -63,15 +100,16 @@ table_metadata_query::table_metadata_query(diagnosable_adapter &diag, sql_connec
     m_columns_meta.emplace_back(sch, tbl, "TABLE_CAT", ignite_type::STRING);
     m_columns_meta.emplace_back(sch, tbl, "TABLE_SCHEM", ignite_type::STRING);
     m_columns_meta.emplace_back(sch, tbl, "TABLE_NAME", ignite_type::STRING);
-    m_columns_meta.emplace_back(sch, tbl, "TABLE_TYPE", ignite_type::STRING);
-    m_columns_meta.emplace_back(sch, tbl, "REMARKS", ignite_type::STRING);
+    m_columns_meta.emplace_back(sch, tbl, "COLUMN_NAME", ignite_type::STRING);
+    m_columns_meta.emplace_back(sch, tbl, "KEY_SEQ", ignite_type::INT16);
+    m_columns_meta.emplace_back(sch, tbl, "PK_NAME", ignite_type::STRING);
 }
 
-sql_result table_metadata_query::execute() {
+sql_result primary_keys_query::execute() {
     if (m_executed)
         close();
 
-    sql_result result = make_request_get_tables_meta();
+    sql_result result = make_request_get_primary_keys();
 
     if (result == sql_result::AI_SUCCESS) {
         m_executed = true;
@@ -83,11 +121,45 @@ sql_result table_metadata_query::execute() {
     return result;
 }
 
-const column_meta_vector *table_metadata_query::get_meta() {
-    return &m_columns_meta;
+sql_result primary_keys_query::make_request_get_primary_keys() {
+    auto success = m_diag.catch_errors([&] {
+        auto response =
+            m_connection.sync_request(protocol::client_operation::JDBC_PK_META, [&](protocol::writer &writer) {
+                writer.write(m_schema);
+                writer.write(m_table);
+            });
+
+        protocol::reader reader{response.get_bytes_view()};
+        bool has_result_set = reader.read_bool();
+
+        auto status = reader.read_int32();
+        auto err_msg = reader.read_string_nullable();
+        if (err_msg)
+            throw odbc_error(response_status_to_sql_state(status), *err_msg);
+
+        if (has_result_set)
+            m_meta = read_meta(reader);
+
+        m_executed = true;
+    });
+
+    if (!success)
+        return sql_result::AI_ERROR;
+
+    std::size_t i = 0;
+    for (const auto &meta : m_meta) {
+        LOG_MSG("[" << i << "] SchemaName:  " << meta.get_schema_name());
+        LOG_MSG("[" << i << "] TableName:   " << meta.get_table_name());
+        LOG_MSG("[" << i << "] ColumnName:  " << meta.get_column_name());
+        LOG_MSG("[" << i << "] KeyName:     " << meta.get_key_name());
+        LOG_MSG("[" << i << "] KeySequence: " << meta.get_key_seq());
+        ++i;
+    }
+
+    return sql_result::AI_SUCCESS;
 }
 
-sql_result table_metadata_query::fetch_next_row(column_binding_map &column_bindings) {
+sql_result primary_keys_query::fetch_next_row(column_binding_map &column_bindings) {
     if (!m_executed) {
         m_diag.add_status_record(sql_state::SHY010_SEQUENCE_ERROR, "Query was not executed.");
 
@@ -102,28 +174,23 @@ sql_result table_metadata_query::fetch_next_row(column_binding_map &column_bindi
     if (m_cursor == m_meta.end())
         return sql_result::AI_NO_DATA;
 
-    column_binding_map::iterator it;
-
-    for (it = column_bindings.begin(); it != column_bindings.end(); ++it)
-        get_column(it->first, it->second);
+    for (auto &binding : column_bindings)
+        get_column(binding.first, binding.second);
 
     return sql_result::AI_SUCCESS;
 }
 
-sql_result table_metadata_query::get_column(std::uint16_t column_idx, application_data_buffer &buffer) {
+sql_result primary_keys_query::get_column(std::uint16_t column_idx, application_data_buffer &buffer) {
     if (!m_executed) {
         m_diag.add_status_record(sql_state::SHY010_SEQUENCE_ERROR, "Query was not executed.");
 
         return sql_result::AI_ERROR;
     }
 
-    if (m_cursor == m_meta.end()) {
-        m_diag.add_status_record(sql_state::S24000_INVALID_CURSOR_STATE, "Cursor has reached end of the result set.");
-
-        return sql_result::AI_ERROR;
-    }
+    if (m_cursor == m_meta.end())
+        return sql_result::AI_NO_DATA;
 
-    const table_meta &current_column = *m_cursor;
+    auto &current_column = *m_cursor;
 
     switch (result_column(column_idx)) {
         case result_column::TABLE_CAT: {
@@ -141,13 +208,18 @@ sql_result table_metadata_query::get_column(std::uint16_t column_idx, applicatio
             break;
         }
 
-        case result_column::TABLE_TYPE: {
-            buffer.put_string(current_column.get_table_type());
+        case result_column::COLUMN_NAME: {
+            buffer.put_string(current_column.get_column_name());
+            break;
+        }
+
+        case result_column::KEY_SEQ: {
+            buffer.put_int16(current_column.get_key_seq());
             break;
         }
 
-        case result_column::REMARKS: {
-            buffer.put_null();
+        case result_column::PK_NAME: {
+            buffer.put_string(current_column.get_key_name());
             break;
         }
 
@@ -158,7 +230,7 @@ sql_result table_metadata_query::get_column(std::uint16_t column_idx, applicatio
     return sql_result::AI_SUCCESS;
 }
 
-sql_result table_metadata_query::close() {
+sql_result primary_keys_query::close() {
     m_meta.clear();
 
     m_executed = false;
@@ -166,60 +238,4 @@ sql_result table_metadata_query::close() {
     return sql_result::AI_SUCCESS;
 }
 
-bool table_metadata_query::is_data_available() const {
-    return m_cursor != m_meta.end();
-}
-
-int64_t table_metadata_query::affected_rows() const {
-    return 0;
-}
-
-sql_result table_metadata_query::next_result_set() {
-    return sql_result::AI_NO_DATA;
-}
-
-sql_result table_metadata_query::make_request_get_tables_meta() {
-    auto table_types = split(m_table_type, ',');
-
-    auto success = m_diag.catch_errors([&] {
-        auto response =
-            m_connection.sync_request(protocol::client_operation::JDBC_TABLE_META, [&](protocol::writer &writer) {
-                writer.write(m_schema);
-                writer.write(m_table);
-
-                writer.write(static_cast<int32_t>(table_types.size()));
-                for (auto table_type : table_types) {
-                    writer.write(table_type);
-                }
-            });
-
-        protocol::reader reader{response.get_bytes_view()};
-        m_has_result_set = reader.read_bool();
-
-        auto status = reader.read_int32();
-        auto err_msg = reader.read_string_nullable();
-        if (err_msg)
-            throw odbc_error(response_status_to_sql_state(status), *err_msg);
-
-        if (m_has_result_set) {
-            m_meta = read_table_meta_vector(reader);
-        }
-
-        m_executed = true;
-    });
-
-    if (!success)
-        return sql_result::AI_ERROR;
-
-    int idx = 0;
-    for (auto &column : m_meta) {
-        LOG_MSG("\n[" << idx << "] catalog_name: " << column.get_catalog_name() << "\n[" << idx << "] schema_name:  "
-                      << column.get_schema_name() << "\n[" << idx << "] table_name:   " << column.get_table_name()
-                      << "\n[" << idx << "] table_type:   " << column.get_table_type());
-        ++idx;
-    }
-
-    return sql_result::AI_SUCCESS;
-}
-
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.h b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.h
new file mode 100644
index 0000000000..c7b2e3e48d
--- /dev/null
+++ b/modules/platforms/cpp/ignite/odbc/query/primary_keys_query.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "ignite/odbc/meta/primary_key_meta.h"
+#include "ignite/odbc/query/query.h"
+#include "ignite/odbc/sql_connection.h"
+
+namespace ignite {
+
+/**
+ * Primary keys query.
+ */
+class primary_keys_query : public query {
+public:
+    /**
+     * Constructor.
+     *
+     * @param diag Diagnostics collector.
+     * @param connection Statement-associated connection.
+     * @param schema Schema name.
+     * @param table Table name.
+     */
+    primary_keys_query(diagnosable_adapter &diag, sql_connection &connection, std::string schema, std::string table);
+
+    /**
+     * Destructor.
+     */
+    ~primary_keys_query() override = default;
+
+    /**
+     * Execute query.
+     *
+     * @return True on success.
+     */
+    sql_result execute() override;
+
+    /**
+     * Get column metadata.
+     *
+     * @return Column metadata.
+     */
+    const column_meta_vector *get_meta() override { return &m_columns_meta; }
+
+    /**
+     * Fetch next result row to application buffers.
+     *
+     * @return Operation result.
+     */
+    sql_result fetch_next_row(column_binding_map &column_bindings) override;
+
+    /**
+     * Get data of the specified column in the result set.
+     *
+     * @param columnIdx Column index.
+     * @param buffer Buffer to put column data to.
+     * @return Operation result.
+     */
+    sql_result get_column(std::uint16_t columnIdx, application_data_buffer &buffer) override;
+
+    /**
+     * Close query.
+     *
+     * @return True on success.
+     */
+    sql_result close() override;
+
+    /**
+     * Check if data is available.
+     *
+     * @return True if data is available.
+     */
+    bool is_data_available() const override { return m_cursor != m_meta.end(); }
+
+    /**
+     * Get number of rows affected by the statement.
+     *
+     * @return Number of rows affected by the statement.
+     */
+    std::int64_t affected_rows() const override { return 0; }
+
+    /**
+     * Move to the next result set.
+     *
+     * @return Operation result.
+     */
+    sql_result next_result_set() override { return sql_result::AI_NO_DATA; }
+
+private:
+    /**
+     * Make get primary keys requests and use response to set internal state.
+     *
+     * @return Operation result.
+     */
+    sql_result make_request_get_primary_keys();
+
+    /** Connection associated with the statement. */
+    sql_connection &m_connection;
+
+    /** Schema name. */
+    std::string m_schema;
+
+    /** Table name. */
+    std::string m_table;
+
+    /** Query executed. */
+    bool m_executed{false};
+
+    /** Result set fetched. */
+    bool m_fetched{false};
+
+    /** Columns metadata. */
+    column_meta_vector m_columns_meta;
+
+    /** Primary keys metadata. */
+    primary_key_meta_vector m_meta;
+
+    /** Result set cursor. */
+    primary_key_meta_vector::iterator m_cursor;
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/query/query.h b/modules/platforms/cpp/ignite/odbc/query/query.h
index 8a1450996f..1833fdd019 100644
--- a/modules/platforms/cpp/ignite/odbc/query/query.h
+++ b/modules/platforms/cpp/ignite/odbc/query/query.h
@@ -42,6 +42,9 @@ enum class query_type {
 
     /** Foreign keys. */
     FOREIGN_KEYS,
+
+    /** Primary keys. */
+    PRIMARY_KEYS,
 };
 
 /**
diff --git a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp b/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
index 710ca8eb28..e7ca838bd1 100644
--- a/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
+++ b/modules/platforms/cpp/ignite/odbc/query/table_metadata_query.cpp
@@ -102,10 +102,8 @@ sql_result table_metadata_query::fetch_next_row(column_binding_map &column_bindi
     if (m_cursor == m_meta.end())
         return sql_result::AI_NO_DATA;
 
-    column_binding_map::iterator it;
-
-    for (it = column_bindings.begin(); it != column_bindings.end(); ++it)
-        get_column(it->first, it->second);
+    for (auto &binding : column_bindings)
+        get_column(binding.first, binding.second);
 
     return sql_result::AI_SUCCESS;
 }
diff --git a/modules/platforms/cpp/ignite/odbc/sql_statement.cpp b/modules/platforms/cpp/ignite/odbc/sql_statement.cpp
index e94477e848..b115b799e0 100644
--- a/modules/platforms/cpp/ignite/odbc/sql_statement.cpp
+++ b/modules/platforms/cpp/ignite/odbc/sql_statement.cpp
@@ -22,6 +22,7 @@
 #include "ignite/odbc/query/column_metadata_query.h"
 #include "ignite/odbc/query/data_query.h"
 #include "ignite/odbc/query/foreign_keys_query.h"
+#include "ignite/odbc/query/primary_keys_query.h"
 #include "ignite/odbc/query/table_metadata_query.h"
 #include "ignite/odbc/query/type_info_query.h"
 #include "ignite/odbc/sql_statement.h"
@@ -607,15 +608,13 @@ void sql_statement::execute_get_primary_keys_query(
 sql_result sql_statement::internal_execute_get_primary_keys_query(
     const std::string &catalog, const std::string &schema, const std::string &table) {
     UNUSED_VALUE catalog;
-    UNUSED_VALUE schema;
-    UNUSED_VALUE table;
 
     if (m_current_query)
         m_current_query->close();
 
-    // TODO: IGNITE-19219 Implement primary keys query
-    add_status_record(sql_state::SHYC00_OPTIONAL_FEATURE_NOT_IMPLEMENTED, "Primary keys query is not supported.");
-    return sql_result::AI_ERROR;
+    m_current_query = std::make_unique<primary_keys_query>(*this, m_connection, schema, table);
+
+    return m_current_query->execute();
 }
 
 void sql_statement::execute_special_columns_query(uint16_t type, const std::string &catalog, const std::string &schema,
diff --git a/modules/platforms/cpp/ignite/odbc/sql_statement.h b/modules/platforms/cpp/ignite/odbc/sql_statement.h
index 17bc46a5da..cd37b0c5c0 100644
--- a/modules/platforms/cpp/ignite/odbc/sql_statement.h
+++ b/modules/platforms/cpp/ignite/odbc/sql_statement.h
@@ -628,7 +628,7 @@ private:
     sql_result update_params_meta();
 
     /**
-     * Convert SQLRESULT to SQL_ROW_RESULT.
+     * Convert sql_result to SQL_ROW_RESULT.
      *
      * @return Operation result.
      */
diff --git a/modules/platforms/cpp/ignite/protocol/client_operation.h b/modules/platforms/cpp/ignite/protocol/client_operation.h
index 1652977482..6cb7cc086e 100644
--- a/modules/platforms/cpp/ignite/protocol/client_operation.h
+++ b/modules/platforms/cpp/ignite/protocol/client_operation.h
@@ -86,6 +86,9 @@ enum class client_operation {
     /** Get column metadata. */
     JDBC_COLUMN_META = 39,
 
+    /** Get primary key metadata. */
+    JDBC_PK_META = 41,
+
     /** Begin transaction. */
     TX_BEGIN = 43,
 
diff --git a/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp b/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp
index cd88abb09a..f169c23b18 100644
--- a/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp
+++ b/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp
@@ -236,22 +236,22 @@ TEST_F(api_robustness_test, sql_columns) {
 
     odbc_connect(get_basic_connection_string());
 
-    SQLCHAR catalogName[] = "";
-    SQLCHAR schemaName[] = "";
-    SQLCHAR tableName[] = "";
+    SQLCHAR catalog_name[] = "";
+    SQLCHAR schema_name[] = "";
+    SQLCHAR table_name[] = "";
     SQLCHAR columnName[] = "";
 
     // Everything is ok.
-    SQLRETURN ret = SQLColumns(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), columnName, sizeof(columnName));
+    SQLRETURN ret = SQLColumns(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, sizeof(table_name), columnName, sizeof(columnName));
 
     ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
 
     // Sizes are nulls.
-    SQLColumns(m_conn, catalogName, 0, schemaName, 0, tableName, 0, columnName, 0);
+    SQLColumns(m_conn, catalog_name, 0, schema_name, 0, table_name, 0, columnName, 0);
 
     // Values are nulls.
-    SQLColumns(m_conn, 0, sizeof(catalogName), 0, sizeof(schemaName), 0, sizeof(tableName), 0, sizeof(columnName));
+    SQLColumns(m_conn, 0, sizeof(catalog_name), 0, sizeof(schema_name), 0, sizeof(table_name), 0, sizeof(columnName));
 
     // All nulls.
     SQLColumns(m_conn, 0, 0, 0, 0, 0, 0, 0, 0);
@@ -480,76 +480,80 @@ TEST_F(api_robustness_test, sql_foreign_keys) {
 
     odbc_connect(get_basic_connection_string());
 
-    SQLCHAR catalogName[] = "";
-    SQLCHAR schemaName[] = "cache";
-    SQLCHAR tableName[] = "TestType";
+    SQLCHAR catalog_name[] = "";
+    SQLCHAR schema_name[] = "cache";
+    SQLCHAR table_name[] = "TestType";
 
     // Everything is ok.
-    SQLRETURN ret = SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName),
-        tableName, sizeof(tableName), catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName));
+    SQLRETURN ret = SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, sizeof(table_name));
 
     ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, 0, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName),
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, 0, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, 0, schemaName, sizeof(schemaName), tableName, sizeof(tableName),
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, 0, schema_name, sizeof(schema_name), table_name, sizeof(table_name),
+        catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), 0, sizeof(schemaName), tableName, sizeof(tableName),
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), 0, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 0, tableName, sizeof(tableName),
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, 0, table_name, sizeof(table_name),
+        catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 0, sizeof(tableName),
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), 0,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, 0,
-        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, 0,
+        catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), 0, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), 0, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), catalogName, 0, schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, 0, schema_name, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), catalogName, sizeof(catalogName), 0, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), 0, sizeof(schema_name), table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), catalogName, sizeof(catalogName), schemaName, 0, tableName, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, 0, table_name, sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 0, sizeof(tableName));
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), 0,
+        sizeof(table_name));
 
     SQLCloseCursor(m_statement);
 
-    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, 0);
+    SQLForeignKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, 0);
 
     SQLCloseCursor(m_statement);
 
@@ -603,24 +607,25 @@ TEST_F(api_robustness_test, sql_primary_keys) {
 
     odbc_connect(get_basic_connection_string());
 
-    SQLCHAR catalogName[] = "";
-    SQLCHAR schemaName[] = "cache";
-    SQLCHAR tableName[] = "TestType";
+    SQLCHAR catalog_name[] = "";
+    SQLCHAR schema_name[] = "cache";
+    SQLCHAR table_name[] = "TestType";
 
     // Everything is ok.
-    SQLRETURN ret = SQLPrimaryKeys(
-        m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
+    SQLRETURN ret = SQLPrimaryKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, sizeof(table_name));
 
-    UNUSED_VALUE ret;
-    // TODO IGNITE-19219: Uncomment once primary keys query execution is implemented.
-    // ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
 
-    SQLPrimaryKeys(m_statement, 0, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, sizeof(tableName));
-    SQLPrimaryKeys(m_statement, catalogName, 0, schemaName, sizeof(schemaName), tableName, sizeof(tableName));
-    SQLPrimaryKeys(m_statement, catalogName, sizeof(catalogName), 0, sizeof(schemaName), tableName, sizeof(tableName));
-    SQLPrimaryKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 0, tableName, sizeof(tableName));
-    SQLPrimaryKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 0, sizeof(tableName));
-    SQLPrimaryKeys(m_statement, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, 0);
+    SQLPrimaryKeys(
+        m_statement, 0, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, sizeof(table_name));
+    SQLPrimaryKeys(m_statement, catalog_name, 0, schema_name, sizeof(schema_name), table_name, sizeof(table_name));
+    SQLPrimaryKeys(
+        m_statement, catalog_name, sizeof(catalog_name), 0, sizeof(schema_name), table_name, sizeof(table_name));
+    SQLPrimaryKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, 0, table_name, sizeof(table_name));
+    SQLPrimaryKeys(
+        m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), 0, sizeof(table_name));
+    SQLPrimaryKeys(m_statement, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name), table_name, 0);
     SQLPrimaryKeys(m_statement, 0, 0, 0, 0, 0, 0);
 }
 
@@ -642,8 +647,7 @@ TEST_F(api_robustness_test, sql_num_params) {
     // Everything is ok.
     ret = SQLNumParams(m_statement, &params);
 
-    UNUSED_VALUE ret;
-    // TODO IGNITE-19219: Uncomment once query execution is implemented.
+    // TODO IGNITE-19854: Uncomment once query execution is implemented.
     // ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
 
     SQLNumParams(m_statement, 0);
@@ -668,7 +672,7 @@ TEST_F(api_robustness_test, sql_num_params_escaped) {
     ret = SQLNumParams(m_statement, &params);
 
     UNUSED_VALUE ret;
-    // TODO IGNITE-19219: Uncomment once query execution is implemented.
+    // TODO IGNITE-19854: Uncomment once query execution is implemented.
     // ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
 
     SQLNumParams(m_statement, 0);
@@ -759,13 +763,13 @@ TEST_F(api_robustness_test, sql_special_columns) {
 
     odbc_connect(get_basic_connection_string());
 
-    SQLCHAR catalogName[] = "";
-    SQLCHAR schemaName[] = "cache";
-    SQLCHAR tableName[] = "TestType";
+    SQLCHAR catalog_name[] = "";
+    SQLCHAR schema_name[] = "cache";
+    SQLCHAR table_name[] = "TestType";
 
     // Everything is ok.
-    SQLRETURN ret = SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, sizeof(catalogName), schemaName,
-        sizeof(schemaName), tableName, sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLRETURN ret = SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, sizeof(catalog_name), schema_name,
+        sizeof(schema_name), table_name, sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     UNUSED_VALUE ret;
     // TODO IGNITE-19218: Uncomment once special columns query execution is implemented.
@@ -773,33 +777,33 @@ TEST_F(api_robustness_test, sql_special_columns) {
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, 0, sizeof(catalogName), schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, 0, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, 0, schemaName, sizeof(schemaName), tableName,
-        sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, 0, schema_name, sizeof(schema_name), table_name,
+        sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, sizeof(catalogName), 0, sizeof(schemaName), tableName,
-        sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, sizeof(catalog_name), 0, sizeof(schema_name),
+        table_name, sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, sizeof(catalogName), schemaName, 0, tableName,
-        sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, sizeof(catalog_name), schema_name, 0, table_name,
+        sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 0,
-        sizeof(tableName), SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        0, sizeof(table_name), SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
-    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, sizeof(catalogName), schemaName, sizeof(schemaName),
-        tableName, 0, SQL_SCOPE_CURROW, SQL_NO_NULLS);
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalog_name, sizeof(catalog_name), schema_name, sizeof(schema_name),
+        table_name, 0, SQL_SCOPE_CURROW, SQL_NO_NULLS);
 
     SQLCloseCursor(m_statement);
 
diff --git a/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp b/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp
index 11af9248b4..f70e32104e 100644
--- a/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp
+++ b/modules/platforms/cpp/tests/odbc-test/meta_queries_test.cpp
@@ -867,3 +867,78 @@ TEST_F(meta_queries_test, sqlcol_attribute_precision_and_scale_after_prepare) {
 TEST_F(meta_queries_test, sqlcol_attribute_precision_and_scale_after_exec) {
     check_col_precision_and_scale(&odbc_suite::exec_query, &check_column_meta_with_sqlcol_attribute);
 }
+
+TEST_F(meta_queries_test, primary_keys_single_column) {
+    odbc_connect(get_basic_connection_string());
+
+    auto ret = exec_query("drop table if exists primary_keys_single_column");
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    ret = exec_query("create table if not exists primary_keys_single_column(ID int primary key, TEST_COLUMN varchar)");
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCHAR any[] = "%";
+    SQLCHAR table[] = "PRIMARY_KEYS_SINGLE_COLUMN";
+
+    ret = SQLPrimaryKeys(m_statement, any, SQL_NTS, any, SQL_NTS, table, SQL_NTS);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    ret = SQLFetch(m_statement);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    check_string_column(m_statement, 1, "");
+    check_string_column(m_statement, 2, "PUBLIC");
+    check_string_column(m_statement, 3, "PRIMARY_KEYS_SINGLE_COLUMN");
+    check_string_column(m_statement, 4, "ID");
+    check_string_column(m_statement, 5, "1");
+    check_string_column(m_statement, 6, "PK_PRIMARY_KEYS_SINGLE_COLUMN");
+
+    ret = SQLFetch(m_statement);
+
+    ASSERT_EQ(ret, SQL_NO_DATA);
+}
+
+TEST_F(meta_queries_test, primary_keys_multiple_columns) {
+    odbc_connect(get_basic_connection_string());
+
+    auto ret = exec_query("drop table if exists primary_keys_multiple_columns");
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    ret = exec_query(
+        "create table if not exists primary_keys_multiple_columns(ID1 int, ID2 varchar, primary key (ID1, ID2))");
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCHAR any[] = "%";
+    SQLCHAR table[] = "PRIMARY_KEYS_MULTIPLE_COLUMNS";
+
+    ret = SQLPrimaryKeys(m_statement, any, SQL_NTS, any, SQL_NTS, table, SQL_NTS);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    ret = SQLFetch(m_statement);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    check_string_column(m_statement, 1, "");
+    check_string_column(m_statement, 2, "PUBLIC");
+    check_string_column(m_statement, 3, "PRIMARY_KEYS_MULTIPLE_COLUMNS");
+    check_string_column(m_statement, 4, "ID1");
+    check_string_column(m_statement, 5, "1");
+    check_string_column(m_statement, 6, "PK_PRIMARY_KEYS_MULTIPLE_COLUMNS");
+
+    ret = SQLFetch(m_statement);
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    check_string_column(m_statement, 1, "");
+    check_string_column(m_statement, 2, "PUBLIC");
+    check_string_column(m_statement, 3, "PRIMARY_KEYS_MULTIPLE_COLUMNS");
+    check_string_column(m_statement, 4, "ID2");
+    check_string_column(m_statement, 5, "2");
+    check_string_column(m_statement, 6, "PK_PRIMARY_KEYS_MULTIPLE_COLUMNS");
+
+    ret = SQLFetch(m_statement);
+
+    ASSERT_EQ(ret, SQL_NO_DATA);
+}