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, ¶ms);
+
+ 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, ¶ms);
+
+ 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