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 5a084b5c54 IGNITE-19206 Re-factor ODBC connection info handling (#2191)
5a084b5c54 is described below

commit 5a084b5c54802d86e62bc5816282a6b7fe4e1b9d
Author: Igor Sapego <[email protected]>
AuthorDate: Tue Jun 20 10:14:32 2023 +0400

    IGNITE-19206 Re-factor ODBC connection info handling (#2191)
---
 modules/platforms/cpp/ignite/client/CMakeLists.txt |   1 +
 modules/platforms/cpp/ignite/odbc/CMakeLists.txt   |  62 +-
 .../cpp/ignite/odbc/config/config_tools.cpp        | 174 ++--
 .../cpp/ignite/odbc/config/config_tools.h          |  75 +-
 .../cpp/ignite/odbc/config/config_tools_test.cpp   |  80 ++
 .../cpp/ignite/odbc/config/configuration.cpp       | 127 +--
 .../cpp/ignite/odbc/config/configuration.h         | 128 +--
 .../cpp/ignite/odbc/config/connection_info.cpp     | 155 ++--
 .../cpp/ignite/odbc/config/connection_info.h       |  16 +-
 .../ignite/odbc/config/connection_info_test.cpp    | 222 +++++
 .../odbc/config/connection_string_parser.cpp       | 175 ----
 .../ignite/odbc/config/connection_string_parser.h  | 104 ---
 .../{settable_value.h => value_with_default.h}     |  30 +-
 .../platforms/cpp/ignite/odbc/sql_connection.cpp   |  21 +-
 modules/platforms/cpp/ignite/odbc/sql_connection.h |   1 +
 modules/platforms/cpp/ignite/odbc/ssl_mode.cpp     |   8 +-
 modules/platforms/cpp/ignite/odbc/string_utils.h   |  94 +-
 .../cpp/ignite/odbc/string_utils_test.cpp          | 137 +++
 .../platforms/cpp/tests/odbc-test/CMakeLists.txt   |   1 +
 .../cpp/tests/odbc-test/api_robustness_test.cpp    | 950 +++++++++++++++++++++
 .../cpp/tests/odbc-test/connection_test.cpp        |  95 +--
 modules/platforms/cpp/tests/odbc-test/odbc_suite.h | 150 ++++
 .../cpp/tests/odbc-test/odbc_test_utils.h          | 100 +++
 .../platforms/cpp/tests/test-common/CMakeLists.txt |   2 -
 24 files changed, 2056 insertions(+), 852 deletions(-)

diff --git a/modules/platforms/cpp/ignite/client/CMakeLists.txt 
b/modules/platforms/cpp/ignite/client/CMakeLists.txt
index 26c616cf88..9284057e96 100644
--- a/modules/platforms/cpp/ignite/client/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/client/CMakeLists.txt
@@ -64,6 +64,7 @@ set(PUBLIC_HEADERS
 add_library(${TARGET} SHARED ${SOURCES})
 
 set_target_properties(${TARGET} PROPERTIES VERSION ${CMAKE_PROJECT_VERSION})
+set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE 1)
 
 if (WIN32)
     set_target_properties(${TARGET} PROPERTIES OUTPUT_NAME "ignite-client")
diff --git a/modules/platforms/cpp/ignite/odbc/CMakeLists.txt 
b/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
index 3018076822..775d2a0268 100644
--- a/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
+++ b/modules/platforms/cpp/ignite/odbc/CMakeLists.txt
@@ -29,7 +29,6 @@ set(SOURCES
     config/config_tools.cpp
     config/configuration.cpp
     config/connection_info.cpp
-    config/connection_string_parser.cpp
     diagnostic/diagnosable_adapter.cpp
     diagnostic/diagnostic_record.cpp
     diagnostic/diagnostic_record_storage.cpp
@@ -47,36 +46,53 @@ set(SOURCES
     log.cpp
 )
 
-if (WIN32)
-    list(APPEND SOURCES
-        module.def
-    )
-endif ()
+add_compile_definitions(CMAKE_PROJECT_VERSION="${CMAKE_PROJECT_VERSION}")
 
-add_library(${TARGET} SHARED ${SOURCES})
-target_link_libraries(${TARGET} ignite-common ignite-tuple ignite-network 
ignite-protocol ${ODBC_LIBRARIES})
+add_library(${TARGET}-obj OBJECT ${SOURCES})
+target_include_directories(${TARGET}-obj PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
 
-set_target_properties(${TARGET} PROPERTIES VERSION ${CMAKE_PROJECT_VERSION})
-set_target_properties(${TARGET} PROPERTIES POSITION_INDEPENDENT_CODE 1)
+add_library(${TARGET} SHARED $<TARGET_OBJECTS:${TARGET}-obj> module.def)
 
-if (WIN32)
-    target_link_libraries(${TARGET} odbccp32 shlwapi)
+set(LIBRARIES
+    ignite-common
+    ignite-tuple
+    ignite-network
+    ignite-protocol
+    ${ODBC_LIBRARIES}
+)
+
+set(_target_libs ${TARGET} ${TARGET}-obj)
+
+foreach(_target_lib IN LISTS _target_libs)
+    set_target_properties(${_target_lib} PROPERTIES VERSION 
${CMAKE_PROJECT_VERSION})
+    set_target_properties(${_target_lib} PROPERTIES POSITION_INDEPENDENT_CODE 
1)
+
+    if (WIN32)
+        list(APPEND LIBRARIES odbccp32 shlwapi)
 
-    remove_definitions(-DUNICODE=1)
+        remove_definitions(-DUNICODE=1)
 
-    if (MSVC_VERSION GREATER_EQUAL 1900)
-        target_link_libraries(${TARGET} legacy_stdio_definitions)
+        if (MSVC_VERSION GREATER_EQUAL 1900)
+            list(APPEND LIBRARIES legacy_stdio_definitions)
+        endif()
+    else()
+        list(APPEND LIBRARIES odbcinst)
     endif()
-else()
-    target_link_libraries(${TARGET} odbcinst)
-endif()
 
-target_include_directories(${TARGET} SYSTEM INTERFACE ${ODBC_INCLUDE_DIRS})
+    target_include_directories(${_target_lib} SYSTEM INTERFACE 
${ODBC_INCLUDE_DIRS})
+    target_link_libraries(${_target_lib} ${LIBRARIES})
+endforeach()
+unset(_target_libs)
 
 if (${INSTALL_IGNITE_FILES})
     install(TARGETS ${TARGET}
-        RUNTIME DESTINATION bin
-        ARCHIVE DESTINATION lib
-        LIBRARY DESTINATION lib
-    )
+            RUNTIME DESTINATION bin
+            ARCHIVE DESTINATION lib
+            LIBRARY DESTINATION lib
+            )
 endif()
+
+ignite_test(connection_info_test config/connection_info_test.cpp LIBS 
${TARGET}-obj ${LIBRARIES})
+ignite_test(config_tools_test config/config_tools_test.cpp LIBS ${TARGET}-obj 
${LIBRARIES})
+ignite_test(string_utils_test string_utils_test.cpp LIBS ${TARGET}-obj 
${LIBRARIES})
+
diff --git a/modules/platforms/cpp/ignite/odbc/config/config_tools.cpp 
b/modules/platforms/cpp/ignite/odbc/config/config_tools.cpp
index 5393db9eb8..af0a448566 100644
--- a/modules/platforms/cpp/ignite/odbc/config/config_tools.cpp
+++ b/modules/platforms/cpp/ignite/odbc/config/config_tools.cpp
@@ -18,12 +18,45 @@
 #include "ignite/odbc/string_utils.h"
 #include "ignite/odbc/config/config_tools.h"
 #include "ignite/odbc/config/configuration.h"
+#include "ignite/odbc/odbc_error.h"
 
 #include <ignite/common/utils.h>
 
 #include <algorithm>
 #include <sstream>
 
+namespace {
+
+using namespace ignite;
+
+config_map::value_type parse_attribute_pair(std::string_view attribute) {
+    auto res = split_once(attribute, '=');
+
+    auto key = normalize_argument_string(res.first);
+    auto value = trim(res.second);
+
+    return config_map::value_type{key, value};
+}
+
+config_map parse_connection_string(std::string_view connect_str, char 
delimiter) {
+    std::string_view parsed_str{connect_str};
+
+    // Stripping zeroes from the end of the string
+    while (!parsed_str.empty() && parsed_str.back() == '\0')
+        parsed_str.remove_suffix(1);
+
+    config_map res;
+    for_every_delimited(parsed_str, delimiter, [&res](std::string_view 
attr_pair) {
+        auto parsed_pair = parse_attribute_pair(attr_pair);
+        if (!parsed_pair.first.empty())
+            res.emplace(std::move(parsed_pair));
+    });
+
+    return res;
+}
+
+} // anonymous namespace
+
 namespace ignite {
 
 std::string addresses_to_string(const std::vector<end_point>& addresses)
@@ -45,136 +78,85 @@ std::string addresses_to_string(const 
std::vector<end_point>& addresses)
     return stream.str();
 }
 
-std::vector<end_point> parse_address(const std::string& value, 
diagnostic_record_storage* diag)
+std::vector<end_point> parse_address(std::string_view value)
 {
     std::size_t addr_num = std::count(value.begin(), value.end(), ',') + 1;
 
     std::vector<end_point> end_points;
     end_points.reserve(addr_num);
 
-    std::string parsed_addr(value);
-
-    while (!parsed_addr.empty())
-    {
-        std::size_t addr_begin_pos = parsed_addr.rfind(',');
-
-        if (addr_begin_pos == std::string::npos)
-            addr_begin_pos = 0;
-        else
-            ++addr_begin_pos;
-
-        const char* addr_begin = parsed_addr.data() + addr_begin_pos;
-        const char* addr_end = parsed_addr.data() + parsed_addr.size();
-
-        std::string addr = strip_surrounding_whitespaces(addr_begin, addr_end);
+    for_every_delimited(value, ',', [&end_points](auto addr) {
+        addr = trim(addr);
+        if (addr.empty())
+            return;
 
-        if (!addr.empty())
-        {
-            end_point ep;
-            bool success = parse_single_address(addr, ep, diag);
-            if (success)
-                end_points.push_back(ep);
-        }
-
-        if (!addr_begin_pos)
-            break;
-
-        parsed_addr.erase(addr_begin_pos - 1);
-    }
+        end_points.emplace_back(parse_single_address(addr));
+    });
 
     return end_points;
 }
 
-bool parse_single_address(const std::string& value, end_point &addr, 
diagnostic_record_storage* diag)
+end_point parse_single_address(std::string_view value)
 {
-    std::int64_t colon_num = std::count(value.begin(), value.end(), ':');
+    auto colon_num = std::count(value.begin(), value.end(), ':');
 
     if (colon_num == 0)
-    {
-        addr.host = value;
-        addr.port = configuration::default_value::port;
-
-        return true;
-    }
+        return {std::string(value), configuration::default_value::port};
 
     if (colon_num != 1)
     {
-        std::stringstream stream;
-
-        stream << "Unexpected number of ':' characters in the following 
address: '"
-            << value << "'. Ignoring address.";
-
-        if (diag)
-            diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
-
-        return false;
+        throw odbc_error(sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE,
+            "Unexpected number of ':' characters in the following address: '" 
+ std::string(value));
     }
 
-    std::size_t colon_pos = value.find(':');
-    addr.host = value.substr(0, colon_pos);
+    auto colon_pos = value.find(':');
+    auto host = value.substr(0, colon_pos);
 
     if (colon_pos == value.size() - 1)
     {
-        std::stringstream stream;
-        stream << "Port is missing in the following address: '" << value << 
"'. Ignoring address.";
-
-        if (diag)
-            diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
-
-        return false;
+        throw odbc_error(sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE,
+            "Port is missing in the following address: '" + 
std::string(value));
     }
 
-    std::string port_str = value.substr(colon_pos + 1);
-    addr.port = parse_port(port_str, diag);
-    if (!addr.port)
-        return false;
+    auto port_str = value.substr(colon_pos + 1);
+    auto port = parse_port(port_str);
 
-    return true;
+    return {std::string(host), port};
 }
 
-std::uint16_t parse_port(const std::string& value, diagnostic_record_storage* 
diag)
-{
-    std::string port = strip_surrounding_whitespaces(value.begin(), 
value.end());
-    if (!all_digits(port))
-    {
-        std::stringstream stream;
-        stream << "Unexpected port characters: '" << port << "'. Ignoring 
address.";
-
-        if (diag)
-            diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
-
-        return 0;
-    }
-
-    if (port.size() >= sizeof("65535"))
-    {
-        std::stringstream stream;
-        stream << "Port value is too large: '" << port << "'. Ignoring 
address.";
-
-        if (diag)
-            diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
+std::optional<std::int64_t> parse_int64(std::string_view value) {
+    auto value_str = trim(value);
+    if (!std::all_of(value_str.begin(), value_str.end(), [] (char c) { return 
std::isdigit(c) || c == '-'; }))
+        return std::nullopt;
+    return lexical_cast<std::int64_t>(value_str);
+}
 
-        return 0;
+std::uint16_t parse_port(std::string_view value)
+{
+    auto port_opt = parse_int<std::uint16_t>(value);
+    if (!port_opt || *port_opt == 0) {
+        throw odbc_error(sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE,
+            "Invalid port value: " + std::string(value));
     }
+    return *port_opt;
+}
 
-    std::int32_t int_port = 0;
-    std::stringstream conv;
-
-    conv << port;
-    conv >> int_port;
+config_map parse_connection_string(std::string_view str) {
+    return ::parse_connection_string(str, ';');
+}
 
-    if (int_port <= 0 || int_port > 0xFFFF)
-    {
-        std::stringstream stream;
-        stream << "Port value is out of range: '" << port << "'. Ignoring 
address.";
+config_map parse_config_attributes(const char *str) {
+    size_t len = 0;
 
-        if (diag)
-            diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
+    // Getting a list length. A list is terminated by two '\0'.
+    while (str[len] || str[len + 1])
+        ++len;
 
-        return 0;
-    }
+    return ::parse_connection_string({str, len}, '\0');
+}
 
-    return static_cast<std::uint16_t>(int_port);
+std::string normalize_argument_string(std::string_view value) {
+    return to_lower(std::string{trim(value)});
 }
 
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/config_tools.h 
b/modules/platforms/cpp/ignite/odbc/config/config_tools.h
index 7be5409791..28ecaee625 100644
--- a/modules/platforms/cpp/ignite/odbc/config/config_tools.h
+++ b/modules/platforms/cpp/ignite/odbc/config/config_tools.h
@@ -21,58 +21,97 @@
 
 #include <string>
 #include <vector>
+#include <map>
+#include <limits>
+#include <optional>
 
 namespace ignite
 {
 
-// Forward declaration.
-class diagnostic_record_storage;
-
 /**
  * Convert address list to string.
  *
  * @param addresses Addresses.
  * @return Resulting string.
  */
-std::string addresses_to_string(const std::vector<end_point>& addresses);
+[[nodiscard]] std::string addresses_to_string(const std::vector<end_point>& 
addresses);
 
 /**
  * Parse address.
  *
+ * @throw odbc_error on error.
  * @param value String value to parse.
- * @param diag Diagnostics collector.
  * @return End points list.
  */
-std::vector<end_point> parse_address(const std::string& value, 
diagnostic_record_storage* diag);
+[[nodiscard]] std::vector<end_point> parse_address(std::string_view value);
 
 /**
  * Parse single address.
  *
- * @param value String value to parse.
+ * @throw odbc_error On error.
  * @param addr End pont.
- * @param diag Diagnostics collector.
- * @return @c true, if parsed successfully, and @c false otherwise.
  */
-bool parse_single_address(const std::string& value, end_point& addr, 
diagnostic_record_storage* diag);
+[[nodiscard]] end_point parse_single_address(std::string_view value);
 
 /**
- * Parse single network port.
+ * Parse integer value.
  *
  * @param value String value to parse.
- * @param port Port range begin.
- * @param range Number of ports in range.
- * @param diag Diagnostics collector.
- * @return @c Port value on success and zero on failure.
+ * @return @c Int value on success and std::nullopt on failure.
+ */
+[[nodiscard]] std::optional<std::int64_t> parse_int64(std::string_view value);
+
+/**
+ * Parse integer value.
+ *
+ * @param value String value to parse.
+ * @return @c Int value on success and std::nullopt on failure.
  */
-bool parse_port_range(const std::string& value, std::uint16_t& port, 
std::uint16_t& range, diagnostic_record_storage* diag);
+template <typename T>
+[[nodiscard]] std::optional<T> parse_int(std::string_view value) {
+    auto i64 = parse_int64(value);
+    if (!i64)
+        return std::nullopt;
+
+    if (*i64 > std::numeric_limits<T>::max() || *i64 < 
std::numeric_limits<T>::min())
+        return std::nullopt;
+
+    return T(*i64);
+}
 
 /**
  * Parse single network port.
  *
  * @param value String value to parse.
- * @param diag Diagnostics collector.
  * @return @c Port value on success and zero on failure.
  */
-std::uint16_t parse_port(const std::string& value, diagnostic_record_storage* 
diag);
+[[nodiscard]] std::uint16_t parse_port(std::string_view value);
+
+/** Configuration options map */
+typedef std::map<std::string, std::string> config_map;
+
+/**
+ * Parse connection string into a map containing configuration attributes.
+ *
+ * @param str Connection string.
+ * @return A map containing configuration attributes.
+ */
+[[nodiscard]] config_map parse_connection_string(std::string_view str);
+
+/**
+ * Parse DSN configuration string into a map containing configuration 
attributes.
+ *
+ * @param str DSN string. Must be terminated with two subsequent '\0'.
+ * @return A map containing configuration attributes.
+ */
+[[nodiscard]] config_map parse_config_attributes(const char* str);
+
+/**
+ * Normalize argument string, i.e. strip leading and trailing whitespaces and 
convert to lowercase.
+ *
+ * @param value Value.
+ * @return Normalized string.
+ */
+[[nodiscard]] std::string normalize_argument_string(std::string_view value);
 
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/config_tools_test.cpp 
b/modules/platforms/cpp/ignite/odbc/config/config_tools_test.cpp
new file mode 100644
index 0000000000..637ddfbb17
--- /dev/null
+++ b/modules/platforms/cpp/ignite/odbc/config/config_tools_test.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#include "config_tools.h"
+#include "ignite/odbc/common_types.h"
+
+#include <gtest/gtest.h>
+
+using namespace ignite;
+
+/**
+ * Test suite.
+ */
+class config_tools_test : public ::testing::Test {};
+
+TEST_F(config_tools_test, parse_address_basic)
+{
+    auto test_parse_address = [] (const std::vector<end_point>& exp, 
std::string_view in) {
+        auto res = parse_address(in);
+        ASSERT_EQ(exp.size(), res.size());
+
+        for (size_t i = 0; i < exp.size(); ++i) {
+            EXPECT_EQ(exp[i],res[i]) << "Vectors differ at index " << i;
+        }
+    };
+
+    test_parse_address({{"127.0.0.1", 10800}}, "127.0.0.1");
+    test_parse_address({{"127.0.0.1", 10800}, {"127.0.0.1", 10800}}, 
"127.0.0.1,127.0.0.1");
+    test_parse_address({{"127.0.0.1", 42}}, "127.0.0.1:42");
+
+    test_parse_address({{"127.0.0.1", 42}, {"localhost", 1550}, {"0.0.0.0", 
10800}},
+        "127.0.0.1:42, localhost:1550,0.0.0.0    ");
+
+    test_parse_address({}, "");
+    test_parse_address({}, ",,,");
+    test_parse_address({}, ",,,");
+    test_parse_address({{"127.0.0.1", 10800}}, ",,,,127.0.0.1,,,,");
+}
+
+TEST_F(config_tools_test, normalize_argument_string_basic)
+{
+    auto test_normalize_argument_string = [] (std::string_view exp, 
std::string_view in) {
+        EXPECT_EQ(normalize_argument_string(in), exp);
+    };
+
+    test_normalize_argument_string("", "");
+    test_normalize_argument_string("abc", "abc");
+    test_normalize_argument_string("abc", "Abc");
+    test_normalize_argument_string("abc", "ABC");
+    test_normalize_argument_string("a b c", " A B C ");
+}
+
+TEST_F(config_tools_test, parse_connection_string_basic)
+{
+    auto test_parse_connection_string = [] (const config_map& exp, 
std::string_view in) {
+        EXPECT_EQ(parse_connection_string(in), exp);
+    };
+
+    test_parse_connection_string({{"key1", "value1"}, {"key2", "value2"}}, 
"key1=value1;key2 = value2;");
+
+    test_parse_connection_string({}, "");
+    test_parse_connection_string({}, ";;");
+
+    test_parse_connection_string({{"k1", "v1"}}, "k1=v1;k1=v2;k1=v3");
+}
+
diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp 
b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
index 211cca27ff..98afe1eb17 100644
--- a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
+++ b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp
@@ -18,110 +18,61 @@
 #include "ignite/odbc/string_utils.h"
 #include "ignite/odbc/config/config_tools.h"
 #include "ignite/odbc/config/configuration.h"
-#include "ignite/odbc/config/connection_string_parser.h"
+#include "ignite/odbc/odbc_error.h"
 
 #include <string>
-#include <sstream>
 
-namespace ignite {
-
-const std::string configuration::default_value::driver{"Apache Ignite"};
-const std::string configuration::default_value::address{};
-const std::int32_t configuration::default_value::page_size{1024};
-const std::uint16_t configuration::default_value::port{10800};
-
-std::string configuration::to_connection_string() const
-{
-    argument_map arguments;
-
-    to_map(arguments);
-
-    std::stringstream connect_string_buffer;
-
-    for (argument_map::const_iterator it = arguments.begin(); it != 
arguments.end(); ++it)
-    {
-        const std::string& key = it->first;
-        const std::string& value = it->second;
+/** Configuration keys . */
+namespace key {
+/** Key for fetch results page size attribute. */
+static inline const std::string page_size{"page_size"};
 
-        if (value.empty())
-            continue;
+/** Key for Driver attribute. */
+static inline const std::string host{"host"};
 
-        if (value.find(' ') == std::string::npos)
-            connect_string_buffer << key << '=' << value << ';';
-        else
-            connect_string_buffer << key << "={" << value << "};";
-    }
-
-    return connect_string_buffer.str();
-}
+/** Key for TCP port attribute. */
+static inline const std::string port{"port"};
 
-const std::string& configuration::get_driver() const
-{
-    return m_driver.get_value();
-}
-
-void configuration::set_driver(const std::string& driver)
-{
-    m_driver.set_value(driver);
-}
+/** Key for address attribute. */
+static inline const std::string address{"address"};
 
-const std::vector<end_point>& configuration::get_addresses() const
-{
-    return m_end_points.get_value();
-}
+} // namespace key;
 
-void configuration::set_addresses(const std::vector<end_point>& end_points)
-{
-    m_end_points.set_value(end_points);
-}
 
-bool configuration::is_addresses_set() const
-{
-    return m_end_points.is_set();
-}
+namespace ignite {
 
-void configuration::set_page_size(int32_t size)
-{
-    m_page_size.set_value(size);
-}
+void configuration::from_config_map(const config_map &config_params) {
+    *this = configuration();
 
-bool configuration::is_page_size_set() const
-{
-    return m_page_size.is_set();
-}
+    auto page_size_it = config_params.find(key::page_size);
+    if (page_size_it != config_params.end()) {
+        auto page_size_opt = parse_int<std::int32_t>(page_size_it->second);
+        if (!page_size_opt)
+            throw 
odbc_error(sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE,
+                "Invalid page size value: " + page_size_it->second);
 
-int32_t configuration::get_page_size() const
-{
-    return m_page_size.get_value();
-}
+        m_page_size = {*page_size_opt, true};
+    }
 
-void configuration::to_map(argument_map& res) const
-{
-    add_to_map(res, connection_string_parser::key::driver, m_driver);
-    add_to_map(res, connection_string_parser::key::address, m_end_points);
-    add_to_map(res, connection_string_parser::key::page_size, m_page_size);
-}
+    auto address_it = config_params.find(key::address);
+    if (address_it != config_params.end())
+        m_end_points = {parse_address(address_it->second), true};
+    else {
+        end_point ep;
+        auto host_it = config_params.find(key::host);
+        if (host_it == config_params.end())
+            throw 
odbc_error(sql_state::S01S00_INVALID_CONNECTION_STRING_ATTRIBUTE,
+                "Connection address is not specified");
 
-template<>
-void configuration::add_to_map(argument_map& map, const std::string& key, 
const settable_value<int32_t>& value)
-{
-    if (value.is_set())
-        map[key] = lexical_cast<std::string>(value.get_value());
-}
+        auto host = host_it->second;
+        uint16_t port = default_value::port;
 
-template<>
-void configuration::add_to_map(argument_map& map, const std::string& key, 
const settable_value<std::string>& value)
-{
-    if (value.is_set())
-        map[key] = value.get_value();
-}
+        auto port_it = config_params.find(key::port);
+        if (port_it != config_params.end())
+            port = parse_port(port_it->second);
 
-template<>
-void configuration::add_to_map(argument_map& map, const std::string& key,
-    const settable_value< std::vector<end_point> >& value)
-{
-    if (value.is_set())
-        map[key] = addresses_to_string(value.get_value());
+        m_end_points = {{{host, port}}, true};
+    }
 }
 
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.h 
b/modules/platforms/cpp/ignite/odbc/config/configuration.h
index e81a5f0118..c8dcf3c182 100644
--- a/modules/platforms/cpp/ignite/odbc/config/configuration.h
+++ b/modules/platforms/cpp/ignite/odbc/config/configuration.h
@@ -17,17 +17,14 @@
 
 #pragma once
 
-#include "ignite/odbc/config/settable_value.h"
-#include "ignite/odbc/protocol_version.h"
-#include "ignite/odbc/ssl_mode.h"
+#include "ignite/odbc/config/value_with_default.h"
+#include "ignite/odbc/config/config_tools.h"
 
 #include "ignite/common/end_point.h"
 
 #include <cstdint>
 #include <string>
-#include <map>
 
-// TODO: IGNITE-19206 needs to be replaced. Every option should be represented 
by a class instance.
 namespace ignite
 {
 
@@ -37,134 +34,57 @@ namespace ignite
 class configuration
 {
 public:
-    /** Argument map type. */
-    typedef std::map<std::string, std::string> argument_map;
-
     /** Default values for configuration. */
     struct default_value
     {
-        /** Default value for Driver attribute. */
-        static const std::string driver;
-
-        /** Default value for address attribute. */
-        static const std::string address;
-
         /** Default value for fetch results page size attribute. */
-        static const std::int32_t page_size;
-
-        /** Default value for TCP port attribute. */
-        static const std::uint16_t port;
-    };
+        static inline const std::int32_t page_size{1024};
 
-    /**
-     * Default constructor.
-     */
-    configuration()
-        : m_driver(default_value::driver)
-        , m_page_size(default_value::page_size)
-        , m_end_points({}) { }
+        /** Default value for Driver attribute. */
+        static inline const std::string host{"localhost"};
 
-    /**
-     * Convert configure to connect string.
-     *
-     * @return Connect string.
-     */
-    [[nodiscard]] std::string to_connection_string() const;
+        /** Default value for TCP port attribute. */
+        static inline const std::uint16_t port{10800};
 
-    /**
-     * Get Driver.
-     *
-     * @return Driver name.
-     */
-    [[nodiscard]] const std::string& get_driver() const;
+        /** Default value for address attribute. */
+        static inline const std::vector<end_point> address{{host, port}};
+    };
 
-    /**
-     * Set driver.
-     *
-     * @param driver Driver.
-     */
-    void set_driver(const std::string& driver);
+    // Default.
+    configuration() = default;
 
     /**
      * Get addresses.
      *
      * @return Addresses.
      */
-    [[nodiscard]] const std::vector<end_point>& get_addresses() const;
-
-    /**
-     * Set addresses to connect to.
-     *
-     * @param end_points Addresses.
-     */
-    void set_addresses(const std::vector<end_point>& end_points);
-
-    /**
-     * Check if the value set.
-     *
-     * @return @true if the value set.
-     */
-    [[nodiscard]] bool is_addresses_set() const;
+    [[nodiscard]] const value_with_default<std::vector<end_point>>& 
get_address() const {
+        return m_end_points;
+    }
 
     /**
      * Get fetch results page size.
      *
      * @return Fetch results page size.
      */
-    [[nodiscard]] std::int32_t get_page_size() const;
-
-    /**
-     * Set fetch results page size.
-     *
-     * @param size Fetch results page size.
-     */
-    void set_page_size(std::int32_t size);
+    [[nodiscard]] value_with_default<std::int32_t> get_page_size() const {
+        return m_page_size;
+    }
 
     /**
-     * Check if the value set.
+     * Fill from configuration params.
      *
-     * @return @true if the value set.
+     * @throw odbc_error On parsing error.
+     * @param config_params Configuration params
      */
-    [[nodiscard]] bool is_page_size_set() const;
-
-    /**
-     * Get argument map.
-     *
-     * @param res Resulting argument map.
-     */
-    void to_map(argument_map& res) const;
+    void from_config_map(const config_map &config_params);
 
 private:
-    /**
-     * Add key and value to the argument map.
-     *
-     * @param map Map.
-     * @param key Key.
-     * @param value Value.
-     */
-    template<typename T>
-    static void add_to_map(argument_map& map, const std::string& key, const 
settable_value<T>& value);
-
-    /** Driver name. */
-    settable_value<std::string> m_driver;
-
     /** Request and response page size. */
-    settable_value<int32_t> m_page_size;
+    value_with_default<int32_t> m_page_size{default_value::page_size, false};
 
     /** Connection end-points. */
-    settable_value< std::vector<end_point> > m_end_points;
+    value_with_default<std::vector<end_point>> 
m_end_points{default_value::address, false};
 };
 
-template<>
-void configuration::add_to_map<std::string>(argument_map& map, const 
std::string& key,
-    const settable_value<std::string>& value);
-
-template<>
-void configuration::add_to_map<int32_t>(argument_map& map, const std::string& 
key,
-    const settable_value<int32_t>& value);
-
-template<>
-void configuration::add_to_map< std::vector<end_point> >(argument_map& map, 
const std::string& key,
-    const settable_value< std::vector<end_point> >& value);
-
 } // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/connection_info.cpp 
b/modules/platforms/cpp/ignite/odbc/config/connection_info.cpp
index 3413ea4139..82e7ea1e49 100644
--- a/modules/platforms/cpp/ignite/odbc/config/connection_info.cpp
+++ b/modules/platforms/cpp/ignite/odbc/config/connection_info.cpp
@@ -16,8 +16,10 @@
  */
 
 #include "ignite/odbc/config/connection_info.h"
+#include "ignite/odbc/string_utils.h"
 #include "ignite/odbc/system/odbc_constants.h"
 #include "ignite/odbc/utility.h"
+#include <iomanip>
 
 // Temporary workaround.
 #ifndef SQL_ASYNC_NOTIFICATION
@@ -36,7 +38,7 @@ namespace ignite {
 
 #define DBG_STR_CASE(x) case x: return #x
 
-const char * connection_info::info_type_to_string(info_type type)
+const char *connection_info::info_type_to_string(info_type type)
 {
     switch (type)
     {
@@ -578,7 +580,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 
     // Driver name.
 #ifdef SQL_DRIVER_NAME
-    m_str_params[SQL_DRIVER_NAME] = "Apache Ignite";
+    m_str_params[SQL_DRIVER_NAME] = "Apache Ignite ODBC Driver";
 #endif // SQL_DRIVER_NAME
 #ifdef SQL_DBMS_NAME
     m_str_params[SQL_DBMS_NAME]   = "Apache Ignite";
@@ -593,10 +595,15 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // Driver version. At a minimum, the version is of the form ##.##.####, 
where the first two digits are
     // the major version, the next two digits are the minor version, and the 
last four digits are the
     // release version.
-    m_str_params[SQL_DRIVER_VER] = "02.04.0000";
+    m_str_params[SQL_DRIVER_VER] = get_formatted_project_version();
 #endif // SQL_DRIVER_VER
 #ifdef SQL_DBMS_VER
-    m_str_params[SQL_DBMS_VER] = "02.04.0000";
+    // A character string that indicates the version of the DBMS product 
accessed by the driver. The version is of
+    // the form ##.##.####, where the first two digits are the major version, 
the next two digits are the minor version,
+    // and the last four digits are the release version. The driver must 
render the DBMS product version in this form
+    // but can also append the DBMS product-specific version. For example, 
"04.01.0000 Rdb 4.1".
+    // TODO: IGNITE-19720 Get current server version on handshake to report 
here.
+    m_str_params[SQL_DBMS_VER] = get_formatted_project_version();
 #endif // SQL_DBMS_VER
 
 #ifdef SQL_COLUMN_ALIAS
@@ -629,11 +636,10 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // "directory". This string can be in upper, lower, or mixed case. This 
info_type has been renamed for
     // ODBC 3.0 from the ODBC 2.0 info_type SQL_QUALIFIER_TERM.
     m_str_params[SQL_CATALOG_TERM] = "";
-#endif // SQL_CATALOG_TERM
-
-#ifdef SQL_QUALIFIER_TERM
+#elif defined(SQL_QUALIFIER_TERM)
+    // Alias
     m_str_params[SQL_QUALIFIER_TERM] = "";
-#endif // SQL_QUALIFIER_TERM
+#endif // SQL_CATALOG_TERM
 
 #ifdef SQL_TABLE_TERM
     // A character string with the data source vendor's name for a table; for 
example, "table" or "file".
@@ -659,7 +665,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #endif // SQL_ACCESSIBLE_PROCEDURES
 
 #ifdef SQL_ACCESSIBLE_TABLES
-    // A character string: "Y" if the user is guaranteed SELECT privileges to 
all tables returned by
+    // A character string: "Y" if the user is guaranteed, SELECT privileges to 
all tables returned by
     // SQLTables; "N" if there may be tables returned that the user cannot 
access.
     m_str_params[SQL_ACCESSIBLE_TABLES] = "Y";
 #endif // SQL_ACCESSIBLE_TABLES
@@ -685,6 +691,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // called SQLDriverConnect or SQLBrowseConnect, this is the value of the 
DSN keyword in the connection
     // string passed to the driver. If the connection string did not contain 
the DSN keyword (such as when
     // it contains the DRIVER keyword), this is an empty string.
+    // TODO IGNITE-19210: Implement DSNs
     m_str_params[SQL_DATA_SOURCE_NAME] = "";
 #endif // SQL_DATA_SOURCE_NAME
 
@@ -708,19 +715,17 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // A character string: "Y" if m_parameters can be described; "N", if not.
     // An SQL-92 Full level-conformant driver will usually return "Y" because 
it will support the DESCRIBE
     // INPUT statement. Because this does not directly specify the underlying 
SQL support, however,
-    // describing m_parameters might not be supported, even in a SQL-92 Full 
level-conformant driver.
+    // describing m_parameters might not be supported, even in an SQL-92 Full 
level-conformant driver.
     m_str_params[SQL_DESCRIBE_PARAMETER] = "N";
 #endif // SQL_DESCRIBE_PARAMETER
 
 #ifdef SQL_EXPRESSIONS_IN_ORDERBY
-    // A character string: "Y" if the data source supports expressions in the 
ORDER BY list; "N" if it does
-    // not.
+    // A character string: "Y" if the data source supports expressions in the 
ORDER BY list; "N" if it does not.
     m_str_params[SQL_EXPRESSIONS_IN_ORDERBY] = "Y";
 #endif // SQL_EXPRESSIONS_IN_ORDERBY
 
 #ifdef SQL_INTEGRITY
-    // A character string: "Y" if the data source supports the Integrity 
Enhancement Facility; "N" if it
-    // does not.
+    // A character string: "Y" if the data source supports the Integrity 
Enhancement Facility; "N" if it does not.
     m_str_params[SQL_INTEGRITY] = "N";
 #endif // SQL_INTEGRITY
 
@@ -729,13 +734,13 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // list does not contain keywords specific to ODBC or keywords used by 
both the data source and ODBC.
     // This list represents all the reserved keywords; interoperable 
applications should not use these words
     // in object names.
-    // The #define value SQL_ODBC_KEYWORDS contains a comma - separated list 
of ODBC keywords.
+    // The #define value SQL_ODBC_KEYWORDS contains a comma-separated list of 
ODBC keywords.
     m_str_params[SQL_KEYWORDS] = 
"LIMIT,MINUS,OFFSET,ROWNUM,SYSDATE,SYSTIME,SYSTIMESTAMP,TODAY";
 #endif // SQL_KEYWORDS
 
 #ifdef SQL_LIKE_ESCAPE_CLAUSE
     // A character string: "Y" if the data source supports an escape character 
for the percent character (%)
-    // and underscore character (_) in a LIKE predicate and the driver 
supports the ODBC syntax for defining
+    // and underscore character (_) in a LIKE predicate, and the driver 
supports the ODBC syntax for defining
     // a LIKE predicate escape character; "N" otherwise.
     m_str_params[SQL_LIKE_ESCAPE_CLAUSE] = "N";
 #endif // SQL_LIKE_ESCAPE_CLAUSE
@@ -743,7 +748,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_MAX_ROW_SIZE_INCLUDES_LONG
     // A character string: "Y" if the maximum row size returned for the 
SQL_MAX_ROW_SIZE information type
     // includes the length of all SQL_LONGVARCHAR and SQL_LONGVARBINARY 
columns in the row; "N" otherwise.
-    m_str_params[SQL_MAX_ROW_SIZE_INCLUDES_LONG] = "Y";
+    m_str_params[SQL_MAX_ROW_SIZE_INCLUDES_LONG] = "N";
 #endif // SQL_MAX_ROW_SIZE_INCLUDES_LONG
 
 #ifdef SQL_MULT_RESULT_SETS
@@ -758,20 +763,20 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #endif // SQL_MULTIPLE_ACTIVE_TXN
 
 #ifdef SQL_ORDER_BY_COLUMNS_IN_SELECT
-    // A character string: "Y" if the columns in the ORDER BY clause must be 
in the select list;
-    // otherwise, "N".
+    // A character string: "Y" if the columns in the ORDER BY clause must be 
in the select list, otherwise, "N".
     m_str_params[SQL_ORDER_BY_COLUMNS_IN_SELECT] = "N";
 #endif // SQL_ORDER_BY_COLUMNS_IN_SELECT
 
 #ifdef SQL_PROCEDURE_TERM
-    // A character string with the data source vendor's name for a procedure; 
for example,
-    // "database procedure", "stored procedure", "procedure", "package", or 
"stored query".
+    // A character string with the data source vendor's name for a procedure; 
for example, "database procedure",
+    // "stored procedure", "procedure", "package", or "stored query".
     m_str_params[SQL_PROCEDURE_TERM] = "stored procedure";
 #endif // SQL_PROCEDURE_TERM
 
 #ifdef SQL_PROCEDURE_TERM
-    // A character string: "Y" if the data source supports procedures and the 
driver supports the ODBC
-    // procedure invocation syntax; "N" otherwise.
+    // A character string:
+    // "Y" if the data source supports procedures and the driver supports the 
ODBC procedure invocation syntax;
+    // "N" otherwise.
     m_str_params[SQL_PROCEDURES] = "N";
 #endif // SQL_PROCEDURE_TERM
 
@@ -798,13 +803,14 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_SERVER_NAME
     // A character string with the actual data source-specific server name; 
useful when a data source name
     // is used during SQLConnect, SQLDriverConnect, and SQLBrowseConnect.
-    m_str_params[SQL_SERVER_NAME] = "Apache Ignite";
+    // TODO: IGNITE-19720 Get current server name on handshake to report here.
+    m_str_params[SQL_SERVER_NAME] = "Apache Ignite 3";
 #endif // SQL_SERVER_NAME
 
 #ifdef SQL_USER_NAME
-    // A character string with the name used in a particular database, which 
can be different from the login
-    // name.
-    m_str_params[SQL_USER_NAME] = "apache_ignite_user";
+    // A character string with the name used in a particular database, which 
can be different from the login name.
+    // TODO: IGNITE-19722 Report username here.
+    m_str_params[SQL_USER_NAME] = "ignite";
 #endif // SQL_USER_NAME
 
     //
@@ -824,9 +830,9 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     //    associated with a given connection handle are in asynchronous mode 
or all are in synchronous mode.
     //    A statement handle on a connection cannot be in asynchronous mode 
while another statement handle
     //    on the same connection is in synchronous mode, and vice versa.
-    // SQL_AM_STATEMENT = statement level asynchronous execution is 
supported.Some statement handles
-    //    associated with a connection handle can be in asynchronous mode, 
while other statement handles on
-    //    the same connection are in synchronous mode.
+    // SQL_AM_STATEMENT = statement level asynchronous execution is 
supported.Some statement handles associated
+    //    with a connection handle can be in asynchronous mode, while the 
other statement handles on the same
+    //    connection are in synchronous mode.
     // SQL_AM_NONE = Asynchronous mode is not supported.
     m_int_params[SQL_ASYNC_MODE] = SQL_AM_NONE;
 #endif // SQL_ASYNC_MODE
@@ -848,7 +854,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // bitmasks are used together with the information type:
     // SQL_BRC_ROLLED_UP = Row counts for consecutive INSERT, DELETE, or 
UPDATE statements are rolled up
     //     into one. If this bit is not set, row counts are available for each 
statement.
-    // SQL_BRC_PROCEDURES = Row counts, if any, are available when a batch is 
executed in a stored
+    // SQL_BRC_PROCEDURES = Row counts, if any are available when a batch is 
executed in a stored
     //     procedure. If row counts are available, they can be rolled up or 
individually available,
     //     depending on the SQL_BRC_ROLLED_UP bit.
     // SQL_BRC_EXPLICIT = Row counts, if any, are available when a batch is 
executed directly by calling
@@ -931,15 +937,15 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_CATALOG_USAGE
     // Bitmask enumerating the statements in which catalogs can be used.
     // The following bitmasks are used to determine where catalogs can be used:
-    // SQL_CU_DML_STATEMENTS = Catalogs are supported in all Data Manipulation 
Language statements :
+    // SQL_CU_DML_STATEMENTS = Catalogs are supported in all Data Manipulation 
Language statements:
     //     SELECT, INSERT, UPDATE, DELETE, and if supported, SELECT FOR UPDATE 
and positioned update and
     //     delete statements.
     // SQL_CU_PROCEDURE_INVOCATION = Catalogs are supported in the ODBC 
procedure invocation statement.
-    // SQL_CU_TABLE_DEFINITION = Catalogs are supported in all table 
definition statements : CREATE TABLE,
+    // SQL_CU_TABLE_DEFINITION = Catalogs are supported in all table 
definition statements: CREATE TABLE,
     //     CREATE VIEW, ALTER TABLE, DROP TABLE, and DROP VIEW.
-    // SQL_CU_INDEX_DEFINITION = Catalogs are supported in all index 
definition statements : CREATE INDEX
+    // SQL_CU_INDEX_DEFINITION = Catalogs are supported in all index 
definition statements: CREATE INDEX
     //     and DROP INDEX.
-    // SQL_CU_PRIVILEGE_DEFINITION = Catalogs are supported in all privilege 
definition statements : GRANT
+    // SQL_CU_PRIVILEGE_DEFINITION = Catalogs are supported in all privilege 
definition statements: GRANT
     //     and REVOKE.
     //
     // A value of 0 is returned if catalogs are not supported by the data 
source.To determine whether
@@ -1042,7 +1048,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_SQL92_NUMERIC_VALUE_FUNCTIONS
     // Bitmask enumerating the numeric value scalar functions that are 
supported by the driver and the
     // associated data source, as defined in SQL-92.
-    // The following bitmasks are used to determine which numeric functions 
are supported :
+    // The following bitmasks are used to determine which numeric functions 
are supported:
     // SQL_SNVF_BIT_LENGTH
     // SQL_SNVF_CHAR_LENGTH
     // SQL_SNVF_CHARACTER_LENGTH
@@ -1346,7 +1352,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // The SQL - 92 or FIPS conformance level at which this feature must be 
supported is shown in
     // parentheses next to each bitmask.
     //
-    // The following bitmasks are used to determine which clauses are 
supported :
+    // The following bitmasks are used to determine which clauses are 
supported:
     // SQL_AD_ADD_DOMAIN_CONSTRAINT = Adding a domain constraint is supported 
(Full level).
     // SQL_AD_ADD_DOMAIN_DEFAULT = <alter domain> <set domain default clause> 
is supported (Full level).
     // SQL_AD_CONSTRAINT_NAME_DEFINITION = <constraint name definition clause> 
is supported for naming
@@ -1355,7 +1361,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // SQL_AD_DROP_DOMAIN_DEFAULT = <alter domain> <drop domain default 
clause> is supported (Full level).
     //
     // The following bits specify the supported <constraint attributes> if 
<add domain constraint> is
-    // supported (the SQL_AD_ADD_DOMAIN_CONSTRAINT bit is set) :
+    // supported (the SQL_AD_ADD_DOMAIN_CONSTRAINT bit is set):
     // SQL_AD_ADD_CONSTRAINT_DEFERRABLE (Full level)
     // SQL_AD_ADD_CONSTRAINT_NON_DEFERRABLE (Full level)
     // SQL_AD_ADD_CONSTRAINT_INITIALLY_DEFERRED (Full level)
@@ -1422,7 +1428,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_CREATE_CHARACTER_SET
     // Bitmask enumerating the clauses in the CREATE CHARACTER SET statement, 
as defined in SQL-92,
     // supported by the data source.
-    // The following bitmasks are used to determine which clauses are 
supported :
+    // The following bitmasks are used to determine which clauses are 
supported:
     // SQL_CCS_CREATE_CHARACTER_SET
     // SQL_CCS_COLLATE_CLAUSE
     // SQL_CCS_LIMITED_COLLATION
@@ -1435,7 +1441,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_CREATE_COLLATION
     // Bitmask enumerating the clauses in the CREATE COLLATION statement, as 
defined in SQL-92, supported by
     // the data source.
-    // The following bitmask is used to determine which clauses are supported :
+    // The following bitmask is used to determine which clauses are supported:
     // SQL_CCOL_CREATE_COLLATION
     // An SQL - 92 Full level-conformant driver will always return this option 
as supported.A return value
     // of "0" means that the CREATE COLLATION statement is not supported.
@@ -1445,10 +1451,10 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_CREATE_DOMAIN
     // Bitmask enumerating the clauses in the CREATE DOMAIN statement, as 
defined in SQL-92, supported by
     // the data source.
-    // The following bitmasks are used to determine which clauses are 
supported :
-    // SQL_CDO_CREATE_DOMAIN = The CREATE DOMAIN statement is 
supported(Intermediate level).
+    // The following bitmasks are used to determine which clauses are 
supported:
+    // SQL_CDO_CREATE_DOMAIN = The CREATE DOMAIN statement is supported 
(Intermediate level).
     // SQL_CDO_CONSTRAINT_NAME_DEFINITION = <constraint name definition> is 
supported for naming domain
-    //     constraints(Intermediate level).
+    //     constraints (Intermediate level).
     //
     // The following bits specify the ability to create column constraints :
     // SQL_CDO_DEFAULT = Specifying domain constraints is supported 
(Intermediate level)
@@ -1469,7 +1475,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #ifdef SQL_CREATE_SCHEMA
     // Bitmask enumerating the clauses in the CREATE SCHEMA statement, as 
defined in SQL-92, supported by
     // the data source.
-    // The following bitmasks are used to determine which clauses are 
supported :
+    // The following bitmasks are used to determine which clauses are 
supported:
     // SQL_CS_CREATE_SCHEMA
     // SQL_CS_AUTHORIZATION
     // SQL_CS_DEFAULT_CHARACTER_SET
@@ -1487,13 +1493,13 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // The SQL - 92 or FIPS conformance level at which this feature must be 
supported is shown in
     // parentheses next to each bitmask.
     //
-    // The following bitmasks are used to determine which clauses are 
supported :
+    // The following bitmasks are used to determine which clauses are 
supported:
     // SQL_CT_CREATE_TABLE = The CREATE TABLE statement is supported. (Entry 
level)
     // SQL_CT_TABLE_CONSTRAINT = Specifying table constraints is supported 
(FIPS Transitional level)
     // SQL_CT_CONSTRAINT_NAME_DEFINITION = The <constraint name definition> 
clause is supported for naming
     //     column and table constraints (Intermediate level)
     //
-    // The following bits specify the ability to create temporary tables :
+    // The following bits specify the ability to create temporary tables:
     // SQL_CT_COMMIT_PRESERVE = Deleted rows are preserved on commit. (Full 
level)
     // SQL_CT_COMMIT_DELETE = Deleted rows are deleted on commit. (Full level)
     // SQL_CT_GLOBAL_TEMPORARY = Global temporary tables can be created. (Full 
level)
@@ -1517,7 +1523,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // Bitmask enumerating the clauses in the CREATE TRANSLATION statement, as 
defined in SQL-92, supported
     // by the data source.
     //
-    // The following bitmask is used to determine which clauses are supported :
+    // The following bitmask is used to determine which clauses are supported:
     // SQL_CTR_CREATE_TRANSLATION
     //
     // An SQL - 92 Full level-conformant driver will always return these 
options as supported. A return
@@ -1529,7 +1535,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
     // Bitmask enumerating the clauses in the CREATE VIEW statement, as 
defined in SQL-92, supported by the
     // data source.
     //
-    // The following bitmasks are used to determine which clauses are 
supported :
+    // The following bitmasks are used to determine which clauses are 
supported:
     // SQL_CV_CREATE_VIEW
     // SQL_CV_CHECK_OPTION
     // SQL_CV_CASCADEDSQL_CV_LOCAL
@@ -2495,7 +2501,7 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #endif // SQL_MAX_TABLES_IN_SELECT
 
 #ifdef SQL_MAX_USER_NAME_LEN
-    // Value that specifies the maximum length of a user name in the data 
source. If there is no maximum
+    // Value that specifies the maximum length of a username in the data 
source. If there is no maximum
     // length or the length is unknown, this value is set to zero.
     m_short_params[SQL_MAX_USER_NAME_LEN] = 0;
 #endif // SQL_MAX_USER_NAME_LEN
@@ -2522,9 +2528,23 @@ connection_info::connection_info(const configuration& 
config) : config(config)
 #endif // SQL_NULL_COLLATION
 }
 
-connection_info::~connection_info()
-{
-    // No-op.
+std::string connection_info::get_formatted_project_version() {
+    std::string_view project_ver = CMAKE_PROJECT_VERSION;
+
+    auto res = split_once(project_ver, '.');
+    auto major = lexical_cast<int32_t>(res.first);
+
+    res = split_once(res.second, '.');
+    auto minor = lexical_cast<int32_t>(res.first);
+
+    res = split_once(res.second, '.');
+    auto patch = lexical_cast<int32_t>(res.first);
+
+    std::stringstream buf;
+    buf << std::setfill('0') << std::setw(2) << major
+        << std::setfill('0') << std::setw(2) << minor
+        << std::setfill('0') << std::setw(4) << patch;
+    return buf.str();
 }
 
 sql_result connection_info::get_info(info_type type, void* buf,
@@ -2533,41 +2553,34 @@ sql_result connection_info::get_info(info_type type, 
void* buf,
     if (!buf)
         return sql_result::AI_ERROR;
 
-    string_info_map::const_iterator itStr = m_str_params.find(type);
-
-    if (itStr != m_str_params.end())
+    auto str_it = m_str_params.find(type);
+    if (str_it != m_str_params.end())
     {
         if (!buffer_len)
             return sql_result::AI_ERROR;
 
-        unsigned short strlen = static_cast<short>(
-            copy_string_to_buffer(itStr->second,
-                reinterpret_cast<char*>(buf), buffer_len));
+        auto str_len = short(copy_string_to_buffer(str_it->second, 
reinterpret_cast<char*>(buf), buffer_len));
 
         if (result_len)
-            *result_len = strlen;
+            *result_len = str_len;
 
         return sql_result::AI_SUCCESS;
     }
 
-    uint_info_map::const_iterator itInt = m_int_params.find(type);
-
-    if (itInt != m_int_params.end())
+    auto int_it = m_int_params.find(type);
+    if (int_it != m_int_params.end())
     {
-        unsigned int *res = reinterpret_cast<unsigned int*>(buf);
-
-        *res = itInt->second;
+        auto *res = reinterpret_cast<unsigned int*>(buf);
+        *res = int_it->second;
 
         return sql_result::AI_SUCCESS;
     }
 
-    ushort_info_map::const_iterator itShort = m_short_params.find(type);
-
-    if (itShort != m_short_params.end())
+    auto short_it = m_short_params.find(type);
+    if (short_it != m_short_params.end())
     {
-        unsigned short *res = reinterpret_cast<unsigned short*>(buf);
-
-        *res = itShort->second;
+        auto *res = reinterpret_cast<unsigned short*>(buf);
+        *res = short_it->second;
 
         return sql_result::AI_SUCCESS;
     }
diff --git a/modules/platforms/cpp/ignite/odbc/config/connection_info.h 
b/modules/platforms/cpp/ignite/odbc/config/connection_info.h
index 9476b1a62a..e4a2fe5ca7 100644
--- a/modules/platforms/cpp/ignite/odbc/config/connection_info.h
+++ b/modules/platforms/cpp/ignite/odbc/config/connection_info.h
@@ -34,13 +34,20 @@ public:
     /** Info type. */
     typedef unsigned short info_type;
 
+    /**
+     * Get formatted project version in a format XX.XX.XXXX 
(major.minor.patch).
+     *
+     * @return Formatted string version.
+     */
+    [[nodiscard]] static std::string get_formatted_project_version();
+
     /**
      * Convert type to string containing its name.
      * Debug function.
      * @param type Info type.
      * @return Null-terminated string containing types name.
      */
-    [[nodiscard]] static const char* info_type_to_string(info_type type);
+    [[nodiscard]] static const char *info_type_to_string(info_type type);
 
     /**
      * Constructor.
@@ -49,10 +56,8 @@ public:
      */
     explicit connection_info(const configuration& config);
 
-    /**
-     * Destructor.
-     */
-    ~connection_info();
+    // Default
+    ~connection_info() = default;
 
     connection_info(connection_info &&) = delete;
     connection_info(const connection_info &) = delete;
@@ -61,6 +66,7 @@ public:
 
     /**
      * Get info of any type.
+     *
      * @param type Info type.
      * @param buf Result buffer pointer.
      * @param buffer_len Result buffer length.
diff --git a/modules/platforms/cpp/ignite/odbc/config/connection_info_test.cpp 
b/modules/platforms/cpp/ignite/odbc/config/connection_info_test.cpp
new file mode 100644
index 0000000000..c4e977269c
--- /dev/null
+++ b/modules/platforms/cpp/ignite/odbc/config/connection_info_test.cpp
@@ -0,0 +1,222 @@
+/*
+ * 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.
+ */
+
+#ifdef _WIN32
+#   include <windows.h>
+#endif
+
+#include <sql.h>
+#include <sqlext.h>
+
+#include "configuration.h"
+#include "connection_info.h"
+#include "ignite/odbc/common_types.h"
+
+#include <gtest/gtest.h>
+
+using namespace ignite;
+
+/**
+ * Test suite.
+ */
+class connection_info_test : public ::testing::Test {};
+
+TEST_F(connection_info_test, supported_info)
+{
+    char buffer[4096];
+    short res_len = 0;
+
+    configuration cfg;
+    connection_info info(cfg);
+
+    sql_result result;
+
+#ifdef SQL_DRIVER_NAME
+    result = info.get_info(SQL_DRIVER_NAME, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DRIVER_NAME
+
+#ifdef SQL_DBMS_NAME
+    result = info.get_info(SQL_DBMS_NAME, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DBMS_NAME
+
+#ifdef SQL_DRIVER_ODBC_VER
+    result = info.get_info(SQL_DRIVER_ODBC_VER, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DRIVER_ODBC_VER
+
+#ifdef SQL_DBMS_VER
+    result = info.get_info(SQL_DBMS_VER, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DBMS_VER
+
+#ifdef SQL_DRIVER_VER
+    result = info.get_info(SQL_DRIVER_VER, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DRIVER_VER
+
+#ifdef SQL_COLUMN_ALIAS
+    result = info.get_info(SQL_COLUMN_ALIAS, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_COLUMN_ALIAS
+
+#ifdef SQL_IDENTIFIER_QUOTE_CHAR
+    result = info.get_info(SQL_IDENTIFIER_QUOTE_CHAR, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_IDENTIFIER_QUOTE_CHAR
+
+#ifdef SQL_CATALOG_NAME_SEPARATOR
+    result = info.get_info(SQL_CATALOG_NAME_SEPARATOR, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CATALOG_NAME_SEPARATOR
+
+#ifdef SQL_SPECIAL_CHARACTERS
+    result = info.get_info(SQL_SPECIAL_CHARACTERS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_SPECIAL_CHARACTERS
+
+#ifdef SQL_CATALOG_TERM
+    result = info.get_info(SQL_CATALOG_TERM, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CATALOG_TERM
+
+#ifdef SQL_TABLE_TERM
+    result = info.get_info(SQL_TABLE_TERM, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_TABLE_TERM
+
+#ifdef SQL_SCHEMA_TERM
+    result = info.get_info(SQL_SCHEMA_TERM, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_SCHEMA_TERM
+
+#ifdef SQL_ASYNC_DBC_FUNCTIONS
+    result = info.get_info(SQL_ASYNC_DBC_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_ASYNC_DBC_FUNCTIONS
+
+#ifdef SQL_GETDATA_EXTENSIONS
+    result = info.get_info(SQL_GETDATA_EXTENSIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_GETDATA_EXTENSIONS
+
+#ifdef SQL_ODBC_INTERFACE_CONFORMANCE
+    result = info.get_info(SQL_ODBC_INTERFACE_CONFORMANCE, buffer, 
sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_ODBC_INTERFACE_CONFORMANCE
+
+#ifdef SQL_SQL_CONFORMANCE
+    result = info.get_info(SQL_SQL_CONFORMANCE, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_SQL_CONFORMANCE
+
+#ifdef SQL_CATALOG_USAGE
+    result = info.get_info(SQL_CATALOG_USAGE, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CATALOG_USAGE
+
+#ifdef SQL_SCHEMA_USAGE
+    result = info.get_info(SQL_SCHEMA_USAGE, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_SCHEMA_USAGE
+
+#ifdef SQL_MAX_IDENTIFIER_LEN
+    result = info.get_info(SQL_MAX_IDENTIFIER_LEN, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_MAX_IDENTIFIER_LEN
+
+#ifdef SQL_AGGREGATE_FUNCTIONS
+    result = info.get_info(SQL_AGGREGATE_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_AGGREGATE_FUNCTIONS
+
+#ifdef SQL_AGGREGATE_FUNCTIONS
+    result = info.get_info(SQL_NUMERIC_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_AGGREGATE_FUNCTIONS
+
+#ifdef SQL_STRING_FUNCTIONS
+    result = info.get_info(SQL_STRING_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_STRING_FUNCTIONS
+
+#ifdef SQL_TIMEDATE_FUNCTIONS
+    result = info.get_info(SQL_TIMEDATE_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_TIMEDATE_FUNCTIONS
+
+#ifdef SQL_TIMEDATE_ADD_INTERVALS
+    result = info.get_info(SQL_TIMEDATE_ADD_INTERVALS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_TIMEDATE_ADD_INTERVALS
+
+#ifdef SQL_TIMEDATE_DIFF_INTERVALS
+    result = info.get_info(SQL_TIMEDATE_DIFF_INTERVALS, buffer, 
sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_TIMEDATE_DIFF_INTERVALS
+
+#ifdef SQL_DATETIME_LITERALS
+    result = info.get_info(SQL_DATETIME_LITERALS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_DATETIME_LITERALS
+
+#ifdef SQL_SYSTEM_FUNCTIONS
+    result = info.get_info(SQL_SYSTEM_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_SYSTEM_FUNCTIONS
+
+#ifdef SQL_CONVERT_FUNCTIONS
+    result = info.get_info(SQL_CONVERT_FUNCTIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CONVERT_FUNCTIONS
+
+#ifdef SQL_OJ_CAPABILITIES
+    result = info.get_info(SQL_OJ_CAPABILITIES, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_OJ_CAPABILITIES
+
+#ifdef SQL_POS_OPERATIONS
+    result = info.get_info(SQL_POS_OPERATIONS, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_POS_OPERATIONS
+
+#ifdef SQL_MAX_CONCURRENT_ACTIVITIES
+    result = info.get_info(SQL_MAX_CONCURRENT_ACTIVITIES, buffer, 
sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_MAX_CONCURRENT_ACTIVITIES
+
+#ifdef SQL_CURSOR_COMMIT_BEHAVIOR
+    result = info.get_info(SQL_CURSOR_COMMIT_BEHAVIOR, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CURSOR_COMMIT_BEHAVIOR
+
+#ifdef SQL_CURSOR_ROLLBACK_BEHAVIOR
+    result = info.get_info(SQL_CURSOR_ROLLBACK_BEHAVIOR, buffer, 
sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_CURSOR_ROLLBACK_BEHAVIOR
+
+#ifdef SQL_TXN_CAPABLE
+    result = info.get_info(SQL_TXN_CAPABLE, buffer, sizeof(buffer), &res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_TXN_CAPABLE
+
+#ifdef SQL_QUOTED_IDENTIFIER_CASE
+    result = info.get_info(SQL_QUOTED_IDENTIFIER_CASE, buffer, sizeof(buffer), 
&res_len);
+    EXPECT_EQ(result, sql_result::AI_SUCCESS);
+#endif //SQL_QUOTED_IDENTIFIER_CASE
+}
diff --git 
a/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.cpp 
b/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.cpp
deleted file mode 100644
index 22ae515dde..0000000000
--- a/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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.
- */
-
-#include "ignite/odbc/config/config_tools.h"
-#include "ignite/odbc/config/connection_string_parser.h"
-#include "ignite/odbc/ssl_mode.h"
-#include "ignite/odbc/utility.h"
-#include "ignite/odbc/string_utils.h"
-
-#include <vector>
-
-namespace ignite {
-
-const std::string connection_string_parser::key::driver{"driver"};
-const std::string connection_string_parser::key::address{"address"};
-const std::string connection_string_parser::key::page_size{"page_size"};
-
-void connection_string_parser::parse_connection_string(const char* str, size_t 
len, char delimiter,
-    diagnostic_record_storage* diag)
-{
-    std::string connect_str(str, len);
-
-    while (connect_str.rbegin() != connect_str.rend() && *connect_str.rbegin() 
== 0)
-        connect_str.erase(connect_str.size() - 1);
-
-    while (!connect_str.empty())
-    {
-        size_t attr_begin = connect_str.rfind(delimiter);
-
-        if (attr_begin == std::string::npos)
-            attr_begin = 0;
-        else
-            ++attr_begin;
-
-        size_t attr_eq_pos = connect_str.rfind('=');
-
-        if (attr_eq_pos == std::string::npos)
-            attr_eq_pos = 0;
-
-        if (attr_begin < attr_eq_pos)
-        {
-            const char* key_begin = connect_str.data() + attr_begin;
-            const char* key_end = connect_str.data() + attr_eq_pos;
-
-            const char* value_begin = connect_str.data() + attr_eq_pos + 1;
-            const char* value_end = connect_str.data() + connect_str.size();
-
-            std::string key = strip_surrounding_whitespaces(key_begin, 
key_end);
-            std::string value = strip_surrounding_whitespaces(value_begin, 
value_end);
-
-            if (value[0] == '{' && value[value.size() - 1] == '}')
-                value = value.substr(1, value.size() - 2);
-
-            handle_attribute_pair(key, value, diag);
-        }
-
-        if (!attr_begin)
-            break;
-
-        connect_str.erase(attr_begin - 1);
-    }
-}
-
-void connection_string_parser::parse_connection_string(const std::string& str, 
diagnostic_record_storage* diag)
-{
-    parse_connection_string(str.data(), str.size(), ';', diag);
-}
-
-void connection_string_parser::parse_config_attributes(const char* str, 
diagnostic_record_storage* diag)
-{
-    size_t len = 0;
-
-    // Getting a list length. A list is terminated by two '\0'.
-    while (str[len] || str[len + 1])
-        ++len;
-
-    ++len;
-
-    parse_connection_string(str, len, '\0', diag);
-}
-
-void connection_string_parser::handle_attribute_pair(const std::string &key, 
const std::string &value,
-    diagnostic_record_storage* diag)
-{
-    std::string lower_key = to_lower(key);
-
-    if (lower_key == key::address)
-    {
-        std::vector<end_point> end_points = parse_address(value, diag);
-
-        m_cfg.set_addresses(end_points);
-    }
-    else if (lower_key == key::page_size)
-    {
-        if (!all_digits(value))
-        {
-            if (diag)
-            {
-                diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED,
-                    make_error_message("Page size attribute value contains 
unexpected characters."
-                        " Using default value.", key, value));
-            }
-
-            return;
-        }
-
-        if (value.size() >= sizeof("4294967295"))
-        {
-            if (diag)
-            {
-                diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED,
-                    make_error_message("Page size attribute value is too 
large. Using default value.", key, value));
-            }
-
-            return;
-        }
-
-        int64_t numValue = 0;
-        std::stringstream conv;
-
-        conv << value;
-        conv >> numValue;
-
-        if (numValue <= 0 || numValue > 0xFFFFFFFFL)
-        {
-            if (diag)
-            {
-                diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED,
-                    make_error_message("Page size attribute value is out of 
range.  Using default value.", key, value));
-            }
-
-            return;
-        }
-
-        m_cfg.set_page_size(static_cast<int32_t>(numValue));
-    }
-    else if (lower_key == key::driver)
-    {
-        m_cfg.set_driver(value);
-    }
-    else if (diag)
-    {
-        std::stringstream stream;
-
-        stream << "Unknown attribute: '" << key << "'. Ignoring.";
-
-        diag->add_status_record(sql_state::S01S02_OPTION_VALUE_CHANGED, 
stream.str());
-    }
-}
-
-std::string connection_string_parser::make_error_message(const std::string& 
msg, const std::string& key,
-    const std::string& value)
-{
-    std::stringstream stream;
-
-    stream << msg << " [key='" << key << "', value='" << value << "']";
-
-    return stream.str();
-}
-
-} // namespace ignite
diff --git 
a/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.h 
b/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.h
deleted file mode 100644
index c84d71cefc..0000000000
--- a/modules/platforms/cpp/ignite/odbc/config/connection_string_parser.h
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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/config/configuration.h"
-#include "ignite/odbc/diagnostic/diagnostic_record_storage.h"
-
-#include <string>
-
-namespace ignite
-{
-
-/**
- * ODBC configuration parser abstraction.
- */
-class connection_string_parser
-{
-public:
-    /** Connection attribute keywords. */
-    struct key
-    {
-        /** Connection attribute keyword for Driver attribute. */
-        static const std::string driver;
-
-        /** Connection attribute keyword for address attribute. */
-        static const std::string address;
-
-        /** Connection attribute keyword for fetch results page size 
attribute. */
-        static const std::string page_size;
-    };
-
-    /**
-     * Constructor.
-     *
-     * @param cfg Configuration.
-     */
-    explicit connection_string_parser(configuration& cfg) : m_cfg(cfg) {}
-
-    /**
-     * Parse connect string.
-     *
-     * @param str String to parse.
-     * @param len String length.
-     * @param delimiter delimiter.
-     * @param diag Diagnostics collector.
-     */
-    void parse_connection_string(const char* str, size_t len, char delimiter, 
diagnostic_record_storage* diag);
-
-    /**
-     * Parse connect string.
-     *
-     * @param str String to parse.
-     * @param diag Diagnostics collector.
-     */
-    void parse_connection_string(const std::string& str, 
diagnostic_record_storage* diag);
-
-    /**
-     * Parse config attributes.
-     *
-     * @param str String to parse.
-     * @param diag Diagnostics collector.
-     */
-    void parse_config_attributes(const char* str, diagnostic_record_storage* 
diag);
-
-private:
-    /**
-     * Handle new attribute pair callback.
-     *
-     * @param key Key.
-     * @param value Value.
-     * @param diag Diagnostics collector.
-     */
-    void handle_attribute_pair(const std::string& key, const std::string& 
value, diagnostic_record_storage* diag);
-
-    /**
-     * Convert string to boolean value.
-     *
-     * @param msg Error message.
-     * @param key Key.
-     * @param value Value.
-     * @return Resulting error message.
-     */
-    static std::string make_error_message(const std::string& msg, const 
std::string& key, const std::string& value);
-
-    /** Configuration. */
-    configuration& m_cfg;
-};
-
-} // namespace ignite
diff --git a/modules/platforms/cpp/ignite/odbc/config/settable_value.h 
b/modules/platforms/cpp/ignite/odbc/config/value_with_default.h
similarity index 73%
rename from modules/platforms/cpp/ignite/odbc/config/settable_value.h
rename to modules/platforms/cpp/ignite/odbc/config/value_with_default.h
index ea28bf30f2..e7fa5169b1 100644
--- a/modules/platforms/cpp/ignite/odbc/config/settable_value.h
+++ b/modules/platforms/cpp/ignite/odbc/config/value_with_default.h
@@ -17,6 +17,8 @@
 
 #pragma once
 
+#include <utility>
+
 namespace ignite {
 
 /**
@@ -25,7 +27,7 @@ namespace ignite {
  * @tparam T Type of the value.
  */
 template<typename T>
-class settable_value
+class value_with_default
 {
 public:
     /** Type of the value. */
@@ -34,38 +36,26 @@ public:
     /**
      * Constructor.
      *
-     * @param default_value Default value to return when is not set.
-     */
-    explicit settable_value(const value_type& default_value)
-        : m_value(default_value) { }
-
-    /**
-     * Set non-default value.
-     *
-     * @param value Value.
-     * @param dflt Set value as default or not.
+     * @param value Value to return.
+     * @param set Flag indicating whether value was set by user or is default.
      */
-    void set_value(const value_type& value, bool dflt = false)
-    {
-        m_value = value;
-        m_set = !dflt;
-    }
+    value_with_default(value_type value, bool set)
+        : m_value(std::move(value))
+        , m_set(set) {}
 
     /**
      * Get value.
      *
      * @return Value or default value if not set.
      */
-    const value_type& get_value() const
-    {
+    const value_type& get_value() const {
         return m_value;
     }
 
     /**
      * Check whether value is set to non-default.
      */
-    [[nodiscard]] bool is_set() const
-    {
+    [[nodiscard]] bool is_set() const {
         return m_set;
     }
 
diff --git a/modules/platforms/cpp/ignite/odbc/sql_connection.cpp 
b/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
index e33f293236..bc78a11bf5 100644
--- a/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
+++ b/modules/platforms/cpp/ignite/odbc/sql_connection.cpp
@@ -16,16 +16,16 @@
  */
 
 #include "ignite/odbc/sql_connection.h"
+#include "ignite/odbc/config/config_tools.h"
 #include "ignite/odbc/config/configuration.h"
-#include "ignite/odbc/config/connection_string_parser.h"
 #include "ignite/odbc/log.h"
 #include "ignite/odbc/sql_environment.h"
 #include "ignite/odbc/sql_statement.h"
 #include "ignite/odbc/ssl_mode.h"
 #include "ignite/odbc/utility.h"
 
-#include <ignite/network/network.h>
 #include <ignite/common/bytes.h>
+#include <ignite/network/network.h>
 
 #include <algorithm>
 #include <cstring>
@@ -64,7 +64,7 @@ std::string hex_dump(const void* data, std::size_t count)
 
 std::vector<ignite::end_point> collect_addresses(const ignite::configuration& 
cfg)
 {
-    std::vector<ignite::end_point> end_points = cfg.get_addresses();
+    std::vector<ignite::end_point> end_points = cfg.get_address().get_value();
 
     std::random_device device;
     std::mt19937 generator(device());
@@ -107,14 +107,17 @@ void sql_connection::establish(const std::string& 
connect_str, void* parent_wind
 
 sql_result sql_connection::internal_establish(const std::string& connect_str, 
void* parent_window)
 {
-    connection_string_parser parser(m_config);
-    parser.parse_connection_string(connect_str, &get_diagnostic_records());
+    try {
+        auto config_params = parse_connection_string(connect_str);
+        m_config.from_config_map(config_params);
+    } catch (const odbc_error& err) {
+        add_status_record(err);
+        return sql_result::AI_ERROR;
+    }
 
-    if (parent_window)
-    {
+    if (parent_window) {
         // TODO: IGNITE-19210 Implement UI for connection
         add_status_record(sql_state::SHYC00_OPTIONAL_FEATURE_NOT_IMPLEMENTED, 
"Connection using UI is not supported");
-
         return sql_result::AI_ERROR;
     }
 
@@ -138,7 +141,7 @@ sql_result sql_connection::internal_establish(const 
configuration& cfg)
 {
     m_config = cfg;
 
-    if (!m_config.is_addresses_set() || m_config.get_addresses().empty())
+    if (!m_config.get_address().is_set() || 
m_config.get_address().get_value().empty())
     {
         add_status_record("No valid address to connect.");
 
diff --git a/modules/platforms/cpp/ignite/odbc/sql_connection.h 
b/modules/platforms/cpp/ignite/odbc/sql_connection.h
index 241d4a961f..b9e6159230 100644
--- a/modules/platforms/cpp/ignite/odbc/sql_connection.h
+++ b/modules/platforms/cpp/ignite/odbc/sql_connection.h
@@ -20,6 +20,7 @@
 #include "ignite/odbc/config/configuration.h"
 #include "ignite/odbc/config/connection_info.h"
 #include "ignite/odbc/diagnostic/diagnosable_adapter.h"
+#include "ignite/odbc/protocol_version.h"
 #include "ignite/odbc/odbc_error.h"
 
 #include "ignite/network/socket_client.h"
diff --git a/modules/platforms/cpp/ignite/odbc/ssl_mode.cpp 
b/modules/platforms/cpp/ignite/odbc/ssl_mode.cpp
index 19b9d41017..44c62d5742 100644
--- a/modules/platforms/cpp/ignite/odbc/ssl_mode.cpp
+++ b/modules/platforms/cpp/ignite/odbc/ssl_mode.cpp
@@ -16,7 +16,7 @@
  */
 
 #include "ignite/odbc/ssl_mode.h"
-#include "ignite/odbc/string_utils.h"
+#include "ignite/odbc/config/config_tools.h"
 
 #include <algorithm>
 
@@ -32,11 +32,9 @@ const std::string UNKNOWN_TOKEN{"unknown"};
 namespace ignite
 {
 
-ssl_mode ssl_mode_from_string(const std::string& val, ssl_mode dflt)
+ssl_mode ssl_mode_from_string(std::string_view val, ssl_mode dflt)
 {
-    // TODO: Make a function for this (e.g. normalize)
-    std::string lower_val = strip_surrounding_whitespaces(val.begin(), 
val.end());
-    std::transform(lower_val.begin(), lower_val.end(), lower_val.begin(), 
::tolower);
+    std::string lower_val = normalize_argument_string(val);
 
     if (lower_val == DISABLE_TOKEN)
         return ssl_mode::DISABLE;
diff --git a/modules/platforms/cpp/ignite/odbc/string_utils.h 
b/modules/platforms/cpp/ignite/odbc/string_utils.h
index 750c7daa0d..fdfe183d33 100644
--- a/modules/platforms/cpp/ignite/odbc/string_utils.h
+++ b/modules/platforms/cpp/ignite/odbc/string_utils.h
@@ -18,81 +18,79 @@
 #pragma once
 
 #include <algorithm>
+#include <functional>
 #include <sstream>
 
 namespace ignite {
 
 /**
- * Check if all characters are digits.
+ * Remove leading spaces.
  *
- * @param val Value to check.
+* @param str String view.
+* @return String view without leading spaces.
  */
-inline bool all_digits(const std::string &val)
+inline std::string_view ltrim(std::string_view str)
 {
-    std::string::const_iterator i = val.begin();
-    while (i != val.end() && isdigit(*i))
-        ++i;
+    while (!str.empty() && std::isspace(str.front()))
+        str.remove_prefix(1);
 
-    return i == val.end();
+    return str;
 }
 
 /**
- * Skip leading spaces.
+ * Remove trailing spaces.
  *
- * @param begin Iterator to the beginning of the character sequence.
- * @param end Iterator to the end of the character sequence.
- * @return Iterator to first non-blanc character.
+* @param str String view.
+* @return String view without trailing spaces.
  */
-template<typename Iterator>
-Iterator skip_leading_spaces(Iterator begin, Iterator end)
+inline std::string_view rtrim(std::string_view str)
 {
-    Iterator res = begin;
-    while (isspace(*res) && res != end)
-        ++res;
+    while (!str.empty() && std::isspace(str.back()))
+        str.remove_suffix(1);
 
-    return res;
+    return str;
 }
 
 /**
- * Skip trailing spaces.
+ * Remove leading and trailing spaces.
  *
- * @param begin Iterator to the beginning of the character sequence.
- * @param end Iterator to the end of the character sequence.
- * @return Iterator to last non-blanc character.
+ * @param str String view.
+ * @return String view without leading and trailing spaces.
  */
-template<typename Iterator>
-Iterator skip_trailing_spaces(Iterator begin, Iterator end)
+inline std::string_view trim(std::string_view str)
 {
-    Iterator res = end - 1;
-    while (isspace(*res) && res != begin - 1)
-        --res;
-
-    return res + 1;
+    return ltrim(rtrim(str));
 }
 
 /**
- * Remove leading and trailing spaces.
- *
- * @param begin Iterator to the beginning of the character sequence.
- * @param end Iterator to the end of the character sequence.
- * @return String without leading and trailing spaces.
+ * Split string once by delimiter.
+ * If string has no delimiter, the second returned string is empty.
+ * @param delimiter Delimiter.
+ * @return Pair of values split by the first encountered delimiter. Delimiter 
itself is not included.
  */
-template<typename Iterator>
-std::string strip_surrounding_whitespaces(Iterator begin, Iterator end)
-{
-    std::string res;
-
-    if (begin >= end)
-        return res;
-
-    Iterator skipped_leading = skip_leading_spaces(begin, end);
-    Iterator skipped_trailing = skip_trailing_spaces(skipped_leading, end);
-
-    res.reserve(skipped_trailing - skipped_leading);
-
-    std::copy(skipped_leading, skipped_trailing, std::back_inserter(res));
+inline std::pair<std::string_view, std::string_view> 
split_once(std::string_view str, char delimiter) {
+    auto delim_pos = str.find(delimiter);
+    if (delim_pos == decltype(str)::npos)
+        return {str, {}};
+
+    std::string_view first{str.data(), delim_pos};
+    std::string_view second{str};
+    second.remove_prefix(delim_pos + 1);
+    return {first, second};
+}
 
-    return res;
+/**
+ * Call action for every substring separated by delimiter.
+ * @param str String.
+ * @param delimiter Delimiter.
+ * @param action Action to perform.
+ */
+inline void for_every_delimited(std::string_view str, char delimiter, const 
std::function<void(std::string_view)> &action) {
+    while (!str.empty()) {
+        auto res = split_once(str, delimiter);
+        action(res.first);
+        str = res.second;
+    }
 }
 
 
diff --git a/modules/platforms/cpp/ignite/odbc/string_utils_test.cpp 
b/modules/platforms/cpp/ignite/odbc/string_utils_test.cpp
new file mode 100644
index 0000000000..e57ee97175
--- /dev/null
+++ b/modules/platforms/cpp/ignite/odbc/string_utils_test.cpp
@@ -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.
+ */
+
+#include "string_utils.h"
+#include "ignite/odbc/common_types.h"
+
+#include <gtest/gtest.h>
+
+using namespace ignite;
+
+/**
+ * Test suite.
+ */
+class string_utils_test : public ::testing::Test {};
+
+TEST_F(string_utils_test, ltrim_basic)
+{
+    auto test_ltrim = [] (std::string_view expected, std::string_view in) {
+        EXPECT_EQ(ltrim(in), expected);
+    };
+
+    test_ltrim("", "");
+    test_ltrim("", " ");
+    test_ltrim("", "      ");
+    test_ltrim("abc", "abc");
+    test_ltrim("abc ", "abc ");
+    test_ltrim("abc", " abc");
+    test_ltrim("abc ", " abc ");
+    test_ltrim("abc     ", "     abc     ");
+    test_ltrim("a b  c ", " a b  c ");
+}
+
+TEST_F(string_utils_test, rtrim_basic)
+{
+    auto test_rtrim = [] (std::string_view expected, std::string_view in) {
+        EXPECT_EQ(rtrim(in), expected);
+    };
+
+    test_rtrim("", "");
+    test_rtrim("", " ");
+    test_rtrim("", "      ");
+    test_rtrim("abc", "abc");
+    test_rtrim("abc", "abc ");
+    test_rtrim(" abc", " abc");
+    test_rtrim(" abc", " abc ");
+    test_rtrim("     abc", "     abc     ");
+    test_rtrim(" a b  c", " a b  c ");
+}
+
+TEST_F(string_utils_test, trim_basic)
+{
+    auto test_trim = [] (std::string_view expected, std::string_view in) {
+        EXPECT_EQ(trim(in), expected);
+    };
+
+    test_trim("", "");
+    test_trim("", " ");
+    test_trim("", "      ");
+    test_trim("abc", "abc");
+    test_trim("abc", "abc ");
+    test_trim("abc", " abc");
+    test_trim("abc", " abc ");
+    test_trim("abc", "     abc     ");
+    test_trim("a b  c", " a b  c ");
+}
+
+TEST_F(string_utils_test, split_once_basic)
+{
+    auto test_split_once = [] (std::string_view p1, std::string_view p2, 
std::string_view in, char d) {
+        auto res = split_once(in, d);
+        EXPECT_EQ(p1, res.first);
+        EXPECT_EQ(p2, res.second);
+    };
+
+    test_split_once("a1", "a2,a3,a4,a5", "a1,a2,a3,a4,a5", ',');
+    test_split_once("a2", "a3,a4,a5", "a2,a3,a4,a5", ',');
+    test_split_once("a3", "a4,a5", "a3,a4,a5", ',');
+    test_split_once("a4", "a5", "a4,a5", ',');
+    test_split_once("a5", "", "a5", ',');
+    test_split_once("", "", "", ',');
+
+    test_split_once("", ",,", ",,,", ',');
+
+    test_split_once("a1", "a2;a3;a4;a5", "a1;a2;a3;a4;a5", ';');
+    test_split_once("a1;a2;a3;a4;a5", "", "a1;a2;a3;a4;a5", ',');
+}
+
+TEST_F(string_utils_test, split_basic)
+{
+    auto test_split = [] (std::vector<std::string_view> exp, std::string_view 
in, char d) {
+        std::vector<std::string_view> res;
+        for_every_delimited(in, d, [&res] (auto s) {
+            res.push_back(s);
+        });
+
+        ASSERT_EQ(exp.size(), res.size());
+
+        for (size_t i = 0; i < exp.size(); ++i) {
+            EXPECT_EQ(exp[i],res[i]) << "Vectors differ at index " << i;
+        }
+    };
+
+    test_split({"a", "b"}, "a,b", ',');
+    test_split({"a", "b", "c", "d", "bar"}, "a,b,c,d,bar", ',');
+
+    test_split({}, "", ',');
+    test_split({"abc"}, "abc", ',');
+    test_split({"abc"}, "abc,", ',');
+    test_split({"", "abc"}, ",abc", ',');
+    test_split({"a", "", "b"}, "a,,b", ',');
+}
+
+TEST_F(string_utils_test, to_lower_basic)
+{
+    auto test_to_lower = [] (std::string_view exp, std::string in) {
+        EXPECT_EQ(to_lower(std::move(in)), exp);
+    };
+
+    test_to_lower("lorem ipsum", "Lorem Ipsum");
+    test_to_lower("lorem ipsum", "LOREM IPSUM");
+    test_to_lower("lorem ipsum", "LoRem IpsUM");
+    test_to_lower("lorem ipsum", "lorem ipsum");
+}
diff --git a/modules/platforms/cpp/tests/odbc-test/CMakeLists.txt 
b/modules/platforms/cpp/tests/odbc-test/CMakeLists.txt
index 1b725c3d9f..e93a944bf0 100644
--- a/modules/platforms/cpp/tests/odbc-test/CMakeLists.txt
+++ b/modules/platforms/cpp/tests/odbc-test/CMakeLists.txt
@@ -22,6 +22,7 @@ set(TARGET ${PROJECT_NAME})
 find_package(ODBC REQUIRED)
 
 set(SOURCES
+    api_robustness_test.cpp
     connection_test.cpp
     main.cpp
 )
diff --git a/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp 
b/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp
new file mode 100644
index 0000000000..cee3072d1d
--- /dev/null
+++ b/modules/platforms/cpp/tests/odbc-test/api_robustness_test.cpp
@@ -0,0 +1,950 @@
+/*
+ * 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.
+ */
+
+#include "ignite/common/config.h"
+#include "odbc_suite.h"
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+// Using NULLs as specified by ODBC
+#ifdef __JETBRAINS_IDE__
+# pragma ide diagnostic ignored "modernize-use-nullptr"
+#endif // __JETBRAINS_IDE__
+
+using namespace ignite;
+
+/**
+ * Test suite.
+ */
+class api_robustness_test : public odbc_suite {};
+
+std::vector<SQLSMALLINT> unsupported_c_types = {
+    SQL_C_INTERVAL_YEAR,
+    SQL_C_INTERVAL_MONTH,
+    SQL_C_INTERVAL_DAY,
+    SQL_C_INTERVAL_HOUR,
+    SQL_C_INTERVAL_MINUTE,
+    SQL_C_INTERVAL_SECOND,
+    SQL_C_INTERVAL_YEAR_TO_MONTH,
+    SQL_C_INTERVAL_DAY_TO_HOUR,
+    SQL_C_INTERVAL_DAY_TO_MINUTE,
+    SQL_C_INTERVAL_DAY_TO_SECOND,
+    SQL_C_INTERVAL_HOUR_TO_MINUTE,
+    SQL_C_INTERVAL_HOUR_TO_SECOND,
+    SQL_C_INTERVAL_MINUTE_TO_SECOND
+};
+
+std::vector<SQLSMALLINT> unsupported_sql_types = {
+    SQL_WVARCHAR,
+    SQL_WLONGVARCHAR,
+    SQL_REAL,
+    SQL_NUMERIC,
+    SQL_INTERVAL_MONTH,
+    SQL_INTERVAL_YEAR,
+    SQL_INTERVAL_YEAR_TO_MONTH,
+    SQL_INTERVAL_DAY,
+    SQL_INTERVAL_HOUR,
+    SQL_INTERVAL_MINUTE,
+    SQL_INTERVAL_SECOND,
+    SQL_INTERVAL_DAY_TO_HOUR,
+    SQL_INTERVAL_DAY_TO_MINUTE,
+    SQL_INTERVAL_DAY_TO_SECOND,
+    SQL_INTERVAL_HOUR_TO_MINUTE,
+    SQL_INTERVAL_HOUR_TO_SECOND,
+    SQL_INTERVAL_MINUTE_TO_SECOND
+};
+
+TEST_F(api_robustness_test, sql_driver_connect)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    prepare_environment();
+
+    auto connect_str = to_sqlchar(get_basic_connection_string());
+
+    SQLCHAR out_str[ODBC_BUFFER_SIZE]{};
+    SQLSMALLINT out_str_len{};
+
+    // Normal connect.
+    SQLRETURN ret = SQLDriverConnect(m_conn, NULL, connect_str.data(), 
SQLSMALLINT(connect_str.size()),
+        out_str, sizeof(out_str), &out_str_len, SQL_DRIVER_COMPLETE);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
+
+    SQLDisconnect(m_conn);
+
+    // Null out string resulting length.
+    SQLDriverConnect(m_conn, NULL, connect_str.data(), 
SQLSMALLINT(connect_str.size()),
+        out_str, sizeof(out_str), 0, SQL_DRIVER_COMPLETE);
+
+    SQLDisconnect(m_conn);
+
+    // Null out string buffer length.
+    SQLDriverConnect(m_conn, NULL, connect_str.data(), 
SQLSMALLINT(connect_str.size()),
+        out_str, 0, &out_str_len, SQL_DRIVER_COMPLETE);
+
+    SQLDisconnect(m_conn);
+
+    // Null output string.
+    SQLDriverConnect(m_conn, NULL, connect_str.data(), 
SQLSMALLINT(connect_str.size()), 0,
+        sizeof(out_str), &out_str_len, SQL_DRIVER_COMPLETE);
+
+    SQLDisconnect(m_conn);
+
+    // Null all.
+    SQLDriverConnect(m_conn, NULL, connect_str.data(), 
SQLSMALLINT(connect_str.size()), 0, 0, 0, SQL_DRIVER_COMPLETE);
+
+    SQLDisconnect(m_conn);
+}
+
+TEST_F(api_robustness_test, sql_get_info)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLSMALLINT resLen = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLGetInfo(m_conn, SQL_DRIVER_NAME, buffer, 
ODBC_BUFFER_SIZE, &resLen);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_DBC, m_conn);
+
+    // The Resulting length is null.
+    SQLGetInfo(m_conn, SQL_DRIVER_NAME, buffer, ODBC_BUFFER_SIZE, 0);
+
+    // Buffer length is null.
+    SQLGetInfo(m_conn, SQL_DRIVER_NAME, buffer, 0, &resLen);
+
+    // Buffer is null.
+    SQLGetInfo(m_conn, SQL_DRIVER_NAME, 0, ODBC_BUFFER_SIZE, &resLen);
+
+    // Unknown info.
+    SQLGetInfo(m_conn, -1, buffer, ODBC_BUFFER_SIZE, &resLen);
+
+    // All nulls.
+    SQLGetInfo(m_conn, SQL_DRIVER_NAME, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_connect_failed_dsn)
+{
+    // Tests that SQLConnect using DSN doesn't fail with link error 
(especially on linux).
+    prepare_environment();
+
+    SQLCHAR dsn_conn_str[] = "DSN=IGNITE_TEST";
+
+    SQLRETURN ret = SQLConnect(m_conn, dsn_conn_str, sizeof(dsn_conn_str), 0, 
0, 0, 0);
+
+    EXPECT_FALSE(SQL_SUCCEEDED(ret));
+    EXPECT_EQ(get_odbc_error_state(SQL_HANDLE_DBC, m_conn), "IM002");
+}
+
+TEST_F(api_robustness_test, sql_prepare)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLPrepare(m_statement, sql, sizeof(sql));
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCloseCursor(m_statement);
+
+    // Value length is null.
+    SQLPrepare(m_statement, sql, 0);
+
+    SQLCloseCursor(m_statement);
+
+    // Value is null.
+    SQLPrepare(m_statement, 0, sizeof(sql));
+
+    SQLCloseCursor(m_statement);
+
+    // All nulls.
+    SQLPrepare(m_statement, 0, 0);
+
+    SQLCloseCursor(m_statement);
+}
+
+TEST_F(api_robustness_test, sql_exec_direct)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLExecDirect(m_statement, sql, sizeof(sql));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19212: Uncomment once query execution is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCloseCursor(m_statement);
+
+    // Value length is null.
+    SQLExecDirect(m_statement, sql, 0);
+
+    SQLCloseCursor(m_statement);
+
+    // Value is null.
+    SQLExecDirect(m_statement, 0, sizeof(sql));
+
+    SQLCloseCursor(m_statement);
+
+    // All nulls.
+    SQLExecDirect(m_statement, 0, 0);
+
+    SQLCloseCursor(m_statement);
+}
+
+TEST_F(api_robustness_test, sql_tables)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR catalogName[] = "";
+    SQLCHAR schemaName[] = "";
+    SQLCHAR tableName[] = "";
+    SQLCHAR tableType[] = "";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLTables(m_statement, catalogName, sizeof(catalogName), 
schemaName,
+        sizeof(schemaName), tableName, sizeof(tableName), tableType, 
sizeof(tableType));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once table metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    // Sizes are nulls.
+    SQLTables(m_conn, catalogName, 0, schemaName, 0, tableName, 0, tableType, 
0);
+
+    // Values are nulls.
+    SQLTables(m_conn, 0, sizeof(catalogName), 0, sizeof(schemaName), 0, 
sizeof(tableName), 0, sizeof(tableType));
+
+    // All nulls.
+    SQLTables(m_conn, 0, 0, 0, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_columns)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR catalogName[] = "";
+    SQLCHAR schemaName[] = "";
+    SQLCHAR tableName[] = "";
+    SQLCHAR columnName[] = "";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLColumns(m_statement, catalogName, sizeof(catalogName), 
schemaName,
+        sizeof(schemaName), tableName, sizeof(tableName), columnName, 
sizeof(columnName));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    // Sizes are nulls.
+    SQLColumns(m_conn, catalogName, 0, schemaName, 0, tableName, 0, 
columnName, 0);
+
+    // Values are nulls.
+    SQLColumns(m_conn, 0, sizeof(catalogName), 0, sizeof(schemaName), 0, 
sizeof(tableName), 0, sizeof(columnName));
+
+    // All nulls.
+    SQLColumns(m_conn, 0, 0, 0, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_bind_col)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLINTEGER ind1;
+    SQLLEN len1 = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLBindCol(m_statement, 1, SQL_C_SLONG, &ind1, 
sizeof(ind1), &len1);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    //Unsupported data types
+    for (short c_type : unsupported_c_types)
+    {
+        ret = SQLBindCol(m_statement, 1, c_type, &ind1, sizeof(ind1), &len1);
+        ASSERT_EQ(ret, SQL_ERROR);
+        EXPECT_EQ(get_statement_error_state(), "HY003");
+    }
+
+    // Size is negative.
+    ret = SQLBindCol(m_statement, 1, SQL_C_SLONG, &ind1, -1, &len1);
+    ASSERT_EQ(ret, SQL_ERROR);
+    EXPECT_EQ(get_statement_error_state(), "HY090");
+
+    // Size is null.
+    SQLBindCol(m_statement, 1, SQL_C_SLONG, &ind1, 0, &len1);
+
+    // Res size is null.
+    SQLBindCol(m_statement, 2, SQL_C_SLONG, &ind1, sizeof(ind1), 0);
+
+    // Value is null.
+    SQLBindCol(m_statement, 3, SQL_C_SLONG, 0, sizeof(ind1), &len1);
+
+    // All nulls.
+    SQLBindCol(m_statement, 4, SQL_C_SLONG, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_bind_parameter)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLINTEGER ind1;
+    SQLLEN len1 = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLBindParameter(m_statement, 1, SQL_PARAM_INPUT,
+        SQL_C_SLONG, SQL_INTEGER, 100, 100, &ind1, sizeof(ind1), &len1);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    //Unsupported parameter type: output
+    SQLBindParameter(m_statement, 2, SQL_PARAM_OUTPUT, SQL_C_SLONG, 
SQL_INTEGER,
+        100, 100, &ind1, sizeof(ind1), &len1);
+
+    EXPECT_EQ(get_statement_error_state(), "HY105");
+
+    //Unsupported parameter type: input/output
+    SQLBindParameter(m_statement, 2, SQL_PARAM_INPUT_OUTPUT, SQL_C_SLONG, 
SQL_INTEGER,
+        100, 100, &ind1, sizeof(ind1), &len1);
+    
+    EXPECT_EQ(get_statement_error_state(), "HY105");
+
+    //Unsupported data types
+    for (short sql_type : unsupported_sql_types)
+    {
+        ret = SQLBindParameter(m_statement, 2, SQL_PARAM_INPUT, SQL_C_SLONG,
+            sql_type, 100, 100, &ind1, sizeof(ind1), &len1);
+
+        ASSERT_EQ(ret, SQL_ERROR);
+        // TODO IGNITE-19212: Uncomment once column binding is implemented.
+        //EXPECT_EQ(get_statement_error_state(), "HY105");
+    }
+
+    // Size is null.
+    SQLBindParameter(m_statement, 2, SQL_PARAM_INPUT, SQL_C_SLONG, 
SQL_INTEGER, 100, 100, &ind1, 0, &len1);
+
+    // Res size is null.
+    SQLBindParameter(m_statement, 3, SQL_PARAM_INPUT, SQL_C_SLONG, 
SQL_INTEGER, 100, 100, &ind1, sizeof(ind1), 0);
+
+    // Value is null.
+    SQLBindParameter(m_statement, 4, SQL_PARAM_INPUT, SQL_C_SLONG, 
SQL_INTEGER, 100, 100, 0, sizeof(ind1), &len1);
+
+    // All nulls.
+    SQLBindParameter(m_statement, 5, SQL_PARAM_INPUT, SQL_C_SLONG, 
SQL_INTEGER, 100, 100, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_native_sql)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLINTEGER resLen = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLNativeSql(m_conn, sql, sizeof(sql), buffer, 
sizeof(buffer), &resLen);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    // Value size is null.
+    SQLNativeSql(m_conn, sql, 0, buffer, sizeof(buffer), &resLen);
+
+    // Buffer size is null.
+    SQLNativeSql(m_conn, sql, sizeof(sql), buffer, 0, &resLen);
+
+    // Res size is null.
+    SQLNativeSql(m_conn, sql, sizeof(sql), buffer, sizeof(buffer), 0);
+
+    // Value is null.
+    SQLNativeSql(m_conn, sql, 0, buffer, sizeof(buffer), &resLen);
+
+    // Buffer is null.
+    SQLNativeSql(m_conn, sql, sizeof(sql), 0, sizeof(buffer), &resLen);
+
+    // All nulls.
+    SQLNativeSql(m_conn, sql, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_col_attribute)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    SQLRETURN ret = SQLExecDirect(m_statement, sql, sizeof(sql));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLSMALLINT resLen = 0;
+    SQLLEN numericAttr = 0;
+
+    // Everything is ok. Character attribute.
+    ret = SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, buffer, 
sizeof(buffer), &resLen, &numericAttr);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    // Everything is ok. Numeric attribute.
+    ret = SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, buffer, 
sizeof(buffer), &resLen, &numericAttr);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, buffer, 
sizeof(buffer), &resLen, 0);
+    SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, buffer, 
sizeof(buffer), 0, &numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, buffer, 0, &resLen, 
&numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, 0, sizeof(buffer), 
&resLen, &numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_COLUMN_TABLE_NAME, 0, 0, 0, 0);
+
+    SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, buffer, sizeof(buffer), 
&resLen, 0);
+    SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, buffer, sizeof(buffer), 0, 
&numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, buffer, 0, &resLen, 
&numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, 0, sizeof(buffer), 
&resLen, &numericAttr);
+    SQLColAttribute(m_statement, 1, SQL_DESC_COUNT, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_describe_col)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    SQLRETURN ret = SQLExecDirect(m_statement, sql, sizeof(sql));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCHAR columnName[ODBC_BUFFER_SIZE];
+    SQLSMALLINT columnNameLen = 0;
+    SQLSMALLINT dataType = 0;
+    SQLULEN columnSize = 0;
+    SQLSMALLINT decimalDigits = 0;
+    SQLSMALLINT nullable = 0;
+
+    // Everything is ok.
+    ret = SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName),
+        &columnNameLen, &dataType, &columnSize, &decimalDigits, &nullable);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19214: Uncomment once column metadata is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLDescribeCol(m_statement, 1, 0, sizeof(columnName), &columnNameLen, 
&dataType, &columnSize, &decimalDigits, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, 0, &columnNameLen, &dataType, 
&columnSize, &decimalDigits, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName), 0, 
&dataType, &columnSize, &decimalDigits, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName), 
&columnNameLen, 0, &columnSize, &decimalDigits, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName), 
&columnNameLen, &dataType, 0, &decimalDigits, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName), 
&columnNameLen, &dataType, &columnSize, 0, &nullable);
+    SQLDescribeCol(m_statement, 1, columnName, sizeof(columnName), 
&columnNameLen, &dataType, &columnSize, &decimalDigits, 0);
+    SQLDescribeCol(m_statement, 1, 0, 0, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_row_count)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    SQLRETURN ret = SQLExecDirect(m_statement, sql, sizeof(sql));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19212: Uncomment once query execution is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLLEN rows = 0;
+
+    // Everything is ok.
+    ret = SQLRowCount(m_statement, &rows);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19212: Uncomment once query execution is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLRowCount(m_statement, 0);
+}
+
+TEST_F(api_robustness_test, sql_foreign_keys)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR catalogName[] = "";
+    SQLCHAR schemaName[] = "cache";
+    SQLCHAR tableName[] = "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));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19217: Uncomment once foreign keys query is implemented.
+    //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));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, 0, schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 
tableName, sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), 0, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 
tableName, sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
0, tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 
tableName, sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), 0, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 
tableName, sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, 0, catalogName,
+        sizeof(catalogName), schemaName, sizeof(schemaName), tableName, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        0, sizeof(catalogName), schemaName, sizeof(schemaName), tableName, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, 0, schemaName, sizeof(schemaName), tableName, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), 0, sizeof(schemaName), tableName, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, 0, tableName, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 0, 
sizeof(tableName));
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, catalogName, sizeof(catalogName), schemaName, 
sizeof(schemaName), tableName, sizeof(tableName),
+        catalogName, sizeof(catalogName), schemaName, sizeof(schemaName), 
tableName, 0);
+
+    SQLCloseCursor(m_statement);
+
+    SQLForeignKeys(m_statement, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+    SQLCloseCursor(m_statement);
+}
+
+TEST_F(api_robustness_test, sql_get_stmt_attr)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLINTEGER resLen = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLGetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 
buffer, sizeof(buffer), &resLen);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLGetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 0, sizeof(buffer), 
&resLen);
+    SQLGetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, buffer, 0, &resLen);
+    SQLGetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, buffer, 
sizeof(buffer), 0);
+    SQLGetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_set_stmt_attr)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLULEN val = 1;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLSetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 
reinterpret_cast<SQLPOINTER>(val), sizeof(val));
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLSetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 0, sizeof(val));
+    SQLSetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 
reinterpret_cast<SQLPOINTER>(val), 0);
+    SQLSetStmtAttr(m_statement, SQL_ATTR_ROW_ARRAY_SIZE, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_primary_keys)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR catalogName[] = "";
+    SQLCHAR schemaName[] = "cache";
+    SQLCHAR tableName[] = "TestType";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLPrimaryKeys(m_statement, catalogName, 
sizeof(catalogName), schemaName, sizeof(schemaName),
+        tableName, sizeof(tableName));
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19219: Uncomment once primary keys query execution is 
implemented.
+    //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, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_num_params)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT strField FROM TestType";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLPrepare(m_statement, sql, sizeof(sql));
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLSMALLINT params;
+
+    // Everything is ok.
+    ret = SQLNumParams(m_statement, &params);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19219: Uncomment once query execution is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLNumParams(m_statement, 0);
+}
+
+TEST_F(api_robustness_test, sql_num_params_escaped)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR sql[] = "SELECT {fn NOW()}";
+
+    // Everything is ok.
+    SQLRETURN ret = SQLPrepare(m_statement, sql, sizeof(sql));
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLSMALLINT params;
+
+    // Everything is ok.
+    ret = SQLNumParams(m_statement, &params);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19219: Uncomment once query execution is implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLNumParams(m_statement, 0);
+}
+
+TEST_F(api_robustness_test, sql_get_diag_field)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    // Should fail.
+    SQLRETURN ret = SQLGetTypeInfo(m_statement, SQL_INTERVAL_MONTH);
+
+    ASSERT_EQ(ret, SQL_ERROR);
+
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLSMALLINT resLen = 0;
+
+    // Everything is ok
+    ret = SQLGetDiagField(SQL_HANDLE_STMT, m_statement, 1, 
SQL_DIAG_MESSAGE_TEXT, buffer, sizeof(buffer), &resLen);
+
+    ASSERT_EQ(ret, SQL_SUCCESS);
+
+    SQLGetDiagField(SQL_HANDLE_STMT, m_statement, 1, SQL_DIAG_MESSAGE_TEXT, 0, 
sizeof(buffer), &resLen);
+    SQLGetDiagField(SQL_HANDLE_STMT, m_statement, 1, SQL_DIAG_MESSAGE_TEXT, 
buffer, 0, &resLen);
+    SQLGetDiagField(SQL_HANDLE_STMT, m_statement, 1, SQL_DIAG_MESSAGE_TEXT, 
buffer, sizeof(buffer), 0);
+    SQLGetDiagField(SQL_HANDLE_STMT, m_statement, 1, SQL_DIAG_MESSAGE_TEXT, 0, 
0, 0);
+}
+
+TEST_F(api_robustness_test, sql_get_diag_rec)
+{
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR state[ODBC_BUFFER_SIZE];
+    SQLINTEGER nativeError = 0;
+    SQLCHAR message[ODBC_BUFFER_SIZE];
+    SQLSMALLINT messageLen = 0;
+
+    // Generating error.
+    SQLRETURN ret = SQLGetTypeInfo(m_statement, SQL_INTERVAL_MONTH);
+    ASSERT_EQ(ret, SQL_ERROR);
+
+    // Everything is ok.
+    ret = SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 
message, sizeof(message), &messageLen);
+    ASSERT_EQ(ret, SQL_SUCCESS);
+
+    // Should return error.
+    ret = SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 
message, -1, &messageLen);
+    ASSERT_EQ(ret, SQL_ERROR);
+
+    // Should return message length.
+    ret = SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 
message, 1, &messageLen);
+    ASSERT_EQ(ret, SQL_SUCCESS_WITH_INFO);
+
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, 0, &nativeError, message, 
sizeof(message), &messageLen);
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, 0, message, 
sizeof(message), &messageLen);
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 0, 
sizeof(message), &messageLen);
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 
message, 0, &messageLen);
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, state, &nativeError, 
message, sizeof(message), 0);
+    SQLGetDiagRec(SQL_HANDLE_STMT, m_statement, 1, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_get_env_attr)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR buffer[ODBC_BUFFER_SIZE];
+    SQLINTEGER resLen = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLGetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, buffer, 
sizeof(buffer), &resLen);
+
+    ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_ENV, m_env);
+
+    SQLGetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, 0, sizeof(buffer), &resLen);
+    SQLGetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, buffer, 0, &resLen);
+    SQLGetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, buffer, sizeof(buffer), 0);
+    SQLGetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_special_columns)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR catalogName[] = "";
+    SQLCHAR schemaName[] = "cache";
+    SQLCHAR tableName[] = "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);
+
+    UNUSED_VALUE ret;
+    // TODO IGNITE-19218: Uncomment once special columns query execution is 
implemented.
+    //ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement);
+
+    SQLCloseCursor(m_statement);
+
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, 0, sizeof(catalogName), 
schemaName, sizeof(schemaName), tableName,
+        sizeof(tableName), 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);
+
+    SQLCloseCursor(m_statement);
+
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, 
sizeof(catalogName), 0, sizeof(schemaName), tableName,
+        sizeof(tableName), 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);
+
+    SQLCloseCursor(m_statement);
+
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, catalogName, 
sizeof(catalogName), schemaName, sizeof(schemaName),
+        0, sizeof(tableName), 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);
+
+    SQLCloseCursor(m_statement);
+
+    SQLSpecialColumns(m_statement, SQL_BEST_ROWID, 0, 0, 0, 0, 0, 0, 
SQL_SCOPE_CURROW, SQL_NO_NULLS);
+
+    SQLCloseCursor(m_statement);
+}
+
+TEST_F(api_robustness_test, sql_error)
+{
+    // There are no checks because we do not really care what is the result of 
these
+    // calls as long as they do not cause segmentation fault.
+
+    odbc_connect(get_basic_connection_string());
+
+    SQLCHAR state[6] = { 0 };
+    SQLINTEGER nativeCode = 0;
+    SQLCHAR message[ODBC_BUFFER_SIZE] = { 0 };
+    SQLSMALLINT messageLen = 0;
+
+    // Everything is ok.
+    SQLRETURN ret = SQLError(m_env, 0, 0, state, &nativeCode, message, 
sizeof(message), &messageLen);
+
+    if (ret != SQL_SUCCESS && ret != SQL_NO_DATA)
+        FAIL() << "Unexpected error";
+
+    ret = SQLError(0, m_conn, 0, state, &nativeCode, message, sizeof(message), 
&messageLen);
+
+    if (ret != SQL_SUCCESS && ret != SQL_NO_DATA)
+        FAIL() << "Unexpected error";
+
+    ret = SQLError(0, 0, m_statement, state, &nativeCode, message, 
sizeof(message), &messageLen);
+
+    if (ret != SQL_SUCCESS && ret != SQL_NO_DATA)
+        FAIL() << "Unexpected error";
+
+    SQLError(0, 0, 0, state, &nativeCode, message, sizeof(message), 
&messageLen);
+
+    SQLError(0, 0, m_statement, 0, &nativeCode, message, sizeof(message), 
&messageLen);
+
+    SQLError(0, 0, m_statement, state, 0, message, sizeof(message), 
&messageLen);
+
+    SQLError(0, 0, m_statement, state, &nativeCode, 0, sizeof(message), 
&messageLen);
+
+    SQLError(0, 0, m_statement, state, &nativeCode, message, 0, &messageLen);
+
+    SQLError(0, 0, m_statement, state, &nativeCode, message, sizeof(message), 
0);
+
+    SQLError(0, 0, m_statement, 0, 0, 0, 0, 0);
+
+    SQLError(0, 0, 0, 0, 0, 0, 0, 0);
+}
+
+TEST_F(api_robustness_test, sql_diagnostic_records)
+{
+    odbc_connect(get_basic_connection_string());
+
+    SQLHANDLE hnd;
+
+    SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_DESC, m_conn, &hnd);
+    ASSERT_EQ(ret, SQL_ERROR);
+    EXPECT_EQ(get_connection_error_state(), "IM001");
+
+    ret = SQLFreeStmt(m_statement, 4);
+    ASSERT_EQ(ret, SQL_ERROR);
+    EXPECT_EQ(get_statement_error_state(), "HY092");
+}
+
+TEST_F(api_robustness_test, many_fds)
+{
+    enum { FDS_NUM = 2000 };
+
+    std::FILE* fds[FDS_NUM];
+
+    for (auto &fd : fds)
+        fd = tmpfile();
+
+    odbc_connect(get_basic_connection_string());
+
+    for (auto fd : fds)
+    {
+        if (fd)
+            fclose(fd);
+    }
+}
+
diff --git a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp 
b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
index 3a50d5c2e2..de36f0f8e2 100644
--- a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
+++ b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp
@@ -1,11 +1,12 @@
 /*
- * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ * 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
  *
- * Licensed under the GridGain Community Edition License (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     
https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *      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,
@@ -14,91 +15,17 @@
  * limitations under the License.
  */
 
-#ifdef _WIN32
-#   include <windows.h>
-#endif
-
-#include <sql.h>
-#include <sqlext.h>
-
-#include <string>
+#include "odbc_suite.h"
 
 #include <gtest/gtest.h>
 
-#include "test_utils.h"
-#include "ignite_runner.h"
-
-
-constexpr size_t ODBC_BUFFER_SIZE = 1024;
-
-std::string get_odbc_error_message(SQLSMALLINT handleType, SQLHANDLE handle, 
SQLSMALLINT idx = 1)
-{
-    SQLCHAR sqlstate[7] = {};
-    SQLINTEGER nativeCode;
-
-    SQLCHAR message[ODBC_BUFFER_SIZE];
-    SQLSMALLINT real_len = 0;
-
-    SQLGetDiagRec(handleType, handle, idx, sqlstate, &nativeCode, message, 
ODBC_BUFFER_SIZE, &real_len);
-
-    std::string res(reinterpret_cast<char*>(sqlstate));
-
-    if (!res.empty())
-        res.append(": ").append(reinterpret_cast<char*>(message), real_len);
-    else
-        res = "No results";
-
-    return res;
-}
-
-void odbc_connect(const std::string& connect_str) {
-    // Allocate an environment handle
-    SQLHENV env;
-    SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env);
-
-    EXPECT_TRUE(env != nullptr);
-
-    // We want ODBC 3 support
-    SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION, 
reinterpret_cast<void*>(SQL_OV_ODBC3), 0);
-
-    // Allocate a connection handle
-    SQLHDBC conn;
-    SQLAllocHandle(SQL_HANDLE_DBC, env, &conn);
-
-    EXPECT_TRUE(conn != nullptr);
-
-    // Connect string
-    std::vector<SQLCHAR> connect_str0(connect_str.begin(), connect_str.end());
-
-    SQLCHAR outstr[ODBC_BUFFER_SIZE];
-    SQLSMALLINT outstrlen;
-
-    // Connecting to ODBC server.
-    SQLRETURN ret = SQLDriverConnect(conn, NULL, &connect_str0[0], 
static_cast<SQLSMALLINT>(connect_str0.size()),
-        outstr, sizeof(outstr), &outstrlen, SQL_DRIVER_COMPLETE);
-
-    if (!SQL_SUCCEEDED(ret)) {
-        FAIL() << get_odbc_error_message(SQL_HANDLE_DBC, conn);
-    }
-
-    // Allocate a statement handle
-    SQLHSTMT statement;
-    SQLAllocHandle(SQL_HANDLE_STMT, conn, &statement);
-
-    EXPECT_TRUE(statement != nullptr);
-}
+#include <string>
 
 /**
  * Test suite.
  */
-class connection_test : public ::testing::Test {};
+class connection_test : public ignite::odbc_suite {};
 
 TEST_F(connection_test, connection_success) {
-    std::string addr_str;
-    auto addrs = ignite::ignite_runner::get_node_addrs();
-    for (auto &addr : addrs)
-        addr_str += addr + ",";
-    addr_str.pop_back();
-
-    odbc_connect("DRIVER={Apache Ignite 3};ADDRESS=" + addr_str);
+    odbc_connect(get_basic_connection_string());
 }
diff --git a/modules/platforms/cpp/tests/odbc-test/odbc_suite.h 
b/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
new file mode 100644
index 0000000000..b639fb180f
--- /dev/null
+++ b/modules/platforms/cpp/tests/odbc-test/odbc_suite.h
@@ -0,0 +1,150 @@
+/*
+ * 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
+
+#ifdef _WIN32
+#   include <windows.h>
+#endif
+
+#include "ignite_runner.h"
+#include "odbc_test_utils.h"
+#include "test_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string_view>
+
+#include <sql.h>
+#include <sqlext.h>
+
+namespace ignite {
+
+using namespace std::string_view_literals;
+
+/**
+ * Test suite.
+ */
+class odbc_suite : public ::testing::Test {
+public:
+    static constexpr std::string_view TABLE_1 = "tbl1"sv;
+    static constexpr std::string_view TABLE_NAME_ALL_COLUMNS = 
"tbl_all_columns"sv;
+    static constexpr std::string_view TABLE_NAME_ALL_COLUMNS_SQL = 
"tbl_all_columns_sql"sv;
+
+    static constexpr const char *KEY_COLUMN = "key";
+    static constexpr const char *VAL_COLUMN = "val";
+
+    static inline const std::string DRIVER_NAME = "Apache Ignite 3";
+
+    /**
+     * Get node addresses to use for tests.
+     *
+     * @return Addresses.
+     */
+    static std::string get_nodes_address() {
+        std::string res;
+        for (const auto &addr : ignite_runner::get_node_addrs())
+            res += addr + ',';
+
+        return res;
+    }
+
+    /**
+     * Get node addresses to use for tests.
+     *
+     * @return Addresses.
+     */
+    static std::string get_basic_connection_string() {
+        return "driver={" + DRIVER_NAME + "};address=" + get_nodes_address() + 
';';
+    }
+
+    /**
+     * Prepare handles for connection.
+     */
+    void prepare_environment(){
+        // Allocate an environment handle
+        SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_env);
+
+        EXPECT_TRUE(m_env != nullptr);
+
+        // We want ODBC 3 support
+        SQLSetEnvAttr(m_env, SQL_ATTR_ODBC_VERSION, 
reinterpret_cast<void*>(SQL_OV_ODBC3), 0);
+
+        // Allocate a connection handle
+        SQLAllocHandle(SQL_HANDLE_DBC, m_env, &m_conn);
+
+        EXPECT_TRUE(m_conn != nullptr);
+    }
+
+    /**
+     * ODBC connect.
+     *
+     * @param connect_str Connect string.
+     */
+    void odbc_connect(std::string_view connect_str) {
+        prepare_environment();
+
+        // Connect string
+        std::vector<SQLCHAR> connect_str0(connect_str.begin(), 
connect_str.end());
+
+        SQLCHAR out_str[ODBC_BUFFER_SIZE];
+        SQLSMALLINT out_str_len;
+
+        // Connecting to ODBC server.
+        SQLRETURN ret = SQLDriverConnect(m_conn, NULL, &connect_str0[0], 
static_cast<SQLSMALLINT>(connect_str0.size()),
+            out_str, sizeof(out_str), &out_str_len, SQL_DRIVER_COMPLETE);
+
+        if (!SQL_SUCCEEDED(ret)) {
+            FAIL() << get_odbc_error_message(SQL_HANDLE_DBC, m_conn);
+        }
+
+        // Allocate a statement handle
+        SQLAllocHandle(SQL_HANDLE_STMT, m_conn, &m_statement);
+
+        EXPECT_TRUE(m_statement != nullptr);
+    }
+
+    /**
+     * Get statement error state.
+     *
+     * @return Statement error state.
+     */
+    [[nodiscard]] std::string get_statement_error_state() const {
+        return get_odbc_error_state(SQL_HANDLE_STMT, m_statement);
+    }
+
+    /**
+     * Get connection error state.
+     *
+     * @return Connection error state.
+     */
+    [[nodiscard]] std::string get_connection_error_state() const {
+        return get_odbc_error_state(SQL_HANDLE_DBC, m_conn);
+    }
+
+    /** Environment handle. */
+    SQLHENV m_env{SQL_NULL_HANDLE};
+
+    /** Connection handle. */
+    SQLHDBC m_conn{SQL_NULL_HANDLE};
+
+    /** Statement handle. */
+    SQLHSTMT m_statement{SQL_NULL_HANDLE};
+};
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h 
b/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
new file mode 100644
index 0000000000..2828f04921
--- /dev/null
+++ b/modules/platforms/cpp/tests/odbc-test/odbc_test_utils.h
@@ -0,0 +1,100 @@
+/*
+ * 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
+
+#ifdef _WIN32
+#   include <windows.h>
+#endif
+
+#include "test_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string_view>
+
+#include <sql.h>
+#include <sqlext.h>
+
+#define ODBC_FAIL_ON_ERROR(ret, type, handle)           \
+    if (!SQL_SUCCEEDED(ret))                            \
+        FAIL() << get_odbc_error_message(type, handle)
+
+namespace ignite {
+
+constexpr size_t ODBC_BUFFER_SIZE = 1024;
+
+/**
+ * Get ODBC error state code.
+ *
+ * @param handle_type Type of the handle.
+ * @param handle Handle.
+ * @param idx Index of record to get.
+ * @return Error state code.
+ */
+[[nodiscard]] inline std::string get_odbc_error_state(SQLSMALLINT handle_type, 
SQLHANDLE handle, SQLSMALLINT idx = 1)
+{
+    SQLCHAR sqlstate[7] = {};
+    SQLINTEGER native_code;
+    
+    SQLCHAR message[ODBC_BUFFER_SIZE];
+    SQLSMALLINT real_len = 0;
+
+    SQLGetDiagRec(handle_type, handle, idx, sqlstate, &native_code, message, 
ODBC_BUFFER_SIZE, &real_len);
+
+    return {reinterpret_cast<char*>(sqlstate)};
+}
+
+/**
+ * Get ODBC error message.
+ *
+ * @param handle_type Type of the handle.
+ * @param handle Handle.
+ * @param idx Index of record to get.
+ * @return Error message.
+ */
+[[nodiscard]] inline std::string get_odbc_error_message(SQLSMALLINT 
handle_type, SQLHANDLE handle, SQLSMALLINT idx = 1)
+{
+    SQLCHAR sqlstate[7] = {};
+    SQLINTEGER native_code{};
+
+    SQLCHAR message[ODBC_BUFFER_SIZE] = {};
+    SQLSMALLINT real_len{};
+
+    SQLGetDiagRec(handle_type, handle, idx, sqlstate, &native_code, message, 
ODBC_BUFFER_SIZE, &real_len);
+
+    std::string res(reinterpret_cast<char*>(sqlstate));
+
+    if (!res.empty())
+        res.append(": ").append(reinterpret_cast<char*>(message), real_len);
+    else
+        res = "No results";
+
+    return res;
+}
+
+/**
+ * Convert string to SQLCHAR array.
+ * @param str String.
+ * @return SQLCHAR vector.
+ */
+[[nodiscard]] inline std::vector<SQLCHAR> to_sqlchar(std::string_view str) {
+    return {str.begin(), str.end()};
+}
+
+} // namespace ignite
diff --git a/modules/platforms/cpp/tests/test-common/CMakeLists.txt 
b/modules/platforms/cpp/tests/test-common/CMakeLists.txt
index 838ce7053e..c3eb4c0451 100644
--- a/modules/platforms/cpp/tests/test-common/CMakeLists.txt
+++ b/modules/platforms/cpp/tests/test-common/CMakeLists.txt
@@ -19,8 +19,6 @@ project(ignite-test-common)
 
 set(TARGET ${PROJECT_NAME})
 
-include_directories(include)
-
 set(SOURCES
     cmd_process.h
     ignite_runner.cpp ignite_runner.h

Reply via email to