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 87211743d1 IGNITE-21605 C++ Client: Pass client time zone to server (#3753) 87211743d1 is described below commit 87211743d1f325b478c5eea959b0c5ac8ef89582 Author: Igor Sapego <isap...@apache.org> AuthorDate: Tue May 14 18:11:40 2024 +0400 IGNITE-21605 C++ Client: Pass client time zone to server (#3753) --- .idea/inspectionProfiles/Project_Default.xml | 7 ++-- .../cpp/ignite/client/detail/sql/sql_impl.cpp | 13 +++++--- .../cpp/ignite/client/detail/utils_test.cpp | 2 +- .../cpp/ignite/client/sql/sql_statement.h | 29 ++++++++++++++-- .../cpp/ignite/odbc/config/configuration.cpp | 4 +++ .../cpp/ignite/odbc/config/configuration.h | 17 ++++++++-- .../platforms/cpp/ignite/odbc/query/data_query.cpp | 8 ++++- modules/platforms/cpp/ignite/odbc/string_utils.h | 3 ++ modules/platforms/cpp/ignite/odbc/utility.cpp | 1 + .../platforms/cpp/tests/client-test/sql_test.cpp | 20 +++++++++++ .../cpp/tests/odbc-test/connection_test.cpp | 39 +++++++++++++++++++++- 11 files changed, 128 insertions(+), 15 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 3ac91d24c4..451a802506 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -139,7 +139,6 @@ <inspection_tool class="AndroidLintAlwaysShowAction" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintAnimatorKeep" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintAppCompatCustomView" enabled="false" level="ERROR" enabled_by_default="false" /> - <inspection_tool class="AndroidLintAppCompatMethod" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintAppCompatResource" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintAppLinkUrlError" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintApplySharedPref" enabled="false" level="WARNING" enabled_by_default="false" /> @@ -255,7 +254,6 @@ <inspection_tool class="AndroidLintMenuTitle" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintMergeMarker" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintMergeRootFrame" enabled="false" level="WARNING" enabled_by_default="false" /> - <inspection_tool class="AndroidLintMinSdkTooLow" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintMipmapIcons" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintMissingApplicationIcon" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintMissingBackupPin" enabled="false" level="WARNING" enabled_by_default="false" /> @@ -312,7 +310,6 @@ <inspection_tool class="AndroidLintRecycle" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintRecyclerView" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintReferenceType" enabled="false" level="ERROR" enabled_by_default="false" /> - <inspection_tool class="AndroidLintRegistered" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintRelativeOverlap" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintRequiredSize" enabled="false" level="ERROR" enabled_by_default="false" /> <inspection_tool class="AndroidLintResAuto" enabled="false" level="ERROR" enabled_by_default="false" /> @@ -1056,7 +1053,9 @@ <inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" /> - <inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true" /> + <inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true"> + <option name="ignoreSupers" value="true" /> + </inspection_tool> <inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" /> <inspection_tool class="TestNGDataProvider" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="TestOnlyProblems" enabled="true" level="WARNING" enabled_by_default="true" /> diff --git a/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp index 7f15a72144..9da64e6a6e 100644 --- a/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp +++ b/modules/platforms/cpp/ignite/client/detail/sql/sql_impl.cpp @@ -15,12 +15,11 @@ * limitations under the License. */ -#include "sql_impl.h" - +#include "ignite/client/detail/sql/sql_impl.h" #include "ignite/client/detail/sql/result_set_impl.h" #include "ignite/client/detail/utils.h" -#include <ignite/tuple/binary_tuple_builder.h> +#include "ignite/tuple/binary_tuple_builder.h" namespace ignite::detail { @@ -29,7 +28,13 @@ void write_statement(protocol::writer &writer, const sql_statement &statement) { writer.write(statement.page_size()); writer.write(std::int64_t(statement.timeout().count())); writer.write_nil(); // Session timeout (unused, session is closed by the server immediately). - writer.write_nil(); // TODO: IGNITE-21605 Time zone id. + + const auto &timezone = statement.timezone_id(); + if (!timezone.empty()) { + writer.write(timezone); + } else { + writer.write_nil(); + } const auto &properties = statement.properties(); auto props_num = std::int32_t(properties.size()); diff --git a/modules/platforms/cpp/ignite/client/detail/utils_test.cpp b/modules/platforms/cpp/ignite/client/detail/utils_test.cpp index b152ced610..6706eb8b55 100644 --- a/modules/platforms/cpp/ignite/client/detail/utils_test.cpp +++ b/modules/platforms/cpp/ignite/client/detail/utils_test.cpp @@ -89,4 +89,4 @@ TEST(client_utils, tuple_write_read_random_order_key_only) { EXPECT_EQ(std::string("Test value"), res_tuple.get(0)); EXPECT_EQ(std::int32_t(1337), res_tuple.get(1)); -} +} \ No newline at end of file diff --git a/modules/platforms/cpp/ignite/client/sql/sql_statement.h b/modules/platforms/cpp/ignite/client/sql/sql_statement.h index a2fc9dcb7a..badde50259 100644 --- a/modules/platforms/cpp/ignite/client/sql/sql_statement.h +++ b/modules/platforms/cpp/ignite/client/sql/sql_statement.h @@ -51,16 +51,18 @@ public: * @param schema Schema. * @param page_size Page size. * @param properties Properties list. + * @param timezone_id Timezone ID. */ sql_statement( // NOLINT(google-explicit-constructor) std::string query, std::chrono::milliseconds timeout = DEFAULT_TIMEOUT, std::string schema = DEFAULT_SCHEMA, std::int32_t page_size = DEFAULT_PAGE_SIZE, - std::initializer_list<std::pair<const std::string, primitive>> properties = {}) + std::initializer_list<std::pair<const std::string, primitive>> properties = {}, std::string timezone_id = {}) : m_query(std::move(query)) , m_timeout(timeout) , m_schema(std::move(schema)) , m_page_size(page_size) - , m_properties(properties) {} + , m_properties(properties) + , m_timezone_id(std::move(timezone_id)) {} /** * Gets the query text. @@ -132,6 +134,26 @@ public: */ void properties(std::initializer_list<std::pair<const std::string, primitive>> val) { m_properties = val; } + /** + * Gets the Timezone ID. + * + * @return Timezone ID. + */ + [[nodiscard]] const std::string &timezone_id() const { return m_timezone_id; } + + /** + * Sets the Timezone ID. + * + * Examples: "America/New_York", "UTC+3". + * Affects time-related SQL functions (e.g. @c CURRENT_TIME) and string literal conversions + * (e.g. <tt>TIMESTAMP WITH LOCAL TIME ZONE '1992-01-18 02:30:00.123'</tt>). + * + * If left empty, defaults to server time zone. + * + * @param val Timezone ID. + */ + void timezone_id(std::string val) { m_timezone_id = std::move(val); } + private: /** Query text. */ std::string m_query; @@ -147,6 +169,9 @@ private: /** Properties. */ std::unordered_map<std::string, primitive> m_properties; + + /** Timezone ID. */ + std::string m_timezone_id; }; } // namespace ignite diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp index 4468e8c7d0..bd75d20a09 100644 --- a/modules/platforms/cpp/ignite/odbc/config/configuration.cpp +++ b/modules/platforms/cpp/ignite/odbc/config/configuration.cpp @@ -45,6 +45,9 @@ static inline const std::string identity{"identity"}; /** Key for authentication secret. */ static inline const std::string secret{"secret"}; +/** Key for timezone. */ +static inline const std::string timezone{"timezone"}; + } // namespace key namespace ignite { @@ -93,6 +96,7 @@ void configuration::from_config_map(const config_map &config_params) { try_get_string_param(m_schema, config_params, key::schema); try_get_string_param(m_auth_identity, config_params, key::identity); try_get_string_param(m_auth_secret, config_params, key::secret); + try_get_string_param(m_timezone, config_params, key::timezone); } } // namespace ignite diff --git a/modules/platforms/cpp/ignite/odbc/config/configuration.h b/modules/platforms/cpp/ignite/odbc/config/configuration.h index c40f1c2b09..5dc663921c 100644 --- a/modules/platforms/cpp/ignite/odbc/config/configuration.h +++ b/modules/platforms/cpp/ignite/odbc/config/configuration.h @@ -43,11 +43,14 @@ public: /** Default value for TCP port attribute. */ static inline const std::uint16_t port{10800}; - /** Default value for address attribute. */ + /** Default value for Address attribute. */ static inline const std::vector<end_point> address{{host, port}}; - /** Default value for Driver attribute. */ + /** Default value for Schema attribute. */ static inline const std::string schema{"PUBLIC"}; + + /** Default value for Timezone attribute. */ + static inline const std::string timezone{}; }; // Default. @@ -100,6 +103,13 @@ public: */ [[nodiscard]] const value_with_default<std::string> &get_auth_secret() const { return m_auth_secret; }; + /** + * Get Timezone. + * + * @return Timezone. + */ + [[nodiscard]] const value_with_default<std::string> &get_timezone() const { return m_timezone; }; + /** * Fill from configuration params. * @@ -126,6 +136,9 @@ private: /** Secret. */ value_with_default<std::string> m_auth_secret{"", false}; + + /** Timezone. */ + value_with_default<std::string> m_timezone{"", false}; }; } // namespace ignite diff --git a/modules/platforms/cpp/ignite/odbc/query/data_query.cpp b/modules/platforms/cpp/ignite/odbc/query/data_query.cpp index 00b46b9e1c..7fea78dfeb 100644 --- a/modules/platforms/cpp/ignite/odbc/query/data_query.cpp +++ b/modules/platforms/cpp/ignite/odbc/query/data_query.cpp @@ -274,7 +274,13 @@ sql_result data_query::make_request_execute() { writer.write(m_connection.get_configuration().get_page_size().get_value()); writer.write(std::int64_t(m_connection.get_timeout()) * 1000); writer.write_nil(); // Session timeout (unused, session is closed by the server immediately). - writer.write_nil(); // TODO: IGNITE-21605 Time zone id. + + auto timezone = m_connection.get_configuration().get_timezone(); + if (timezone.is_set()) { + writer.write(timezone.get_value()); + } else { + writer.write_nil(); + } // Properties are not used for now. writer.write(0); diff --git a/modules/platforms/cpp/ignite/odbc/string_utils.h b/modules/platforms/cpp/ignite/odbc/string_utils.h index daeea585a6..1961883fda 100644 --- a/modules/platforms/cpp/ignite/odbc/string_utils.h +++ b/modules/platforms/cpp/ignite/odbc/string_utils.h @@ -19,8 +19,11 @@ #include <algorithm> #include <functional> +#include <string_view> #include <sstream> +#include <cctype> + namespace ignite { /** diff --git a/modules/platforms/cpp/ignite/odbc/utility.cpp b/modules/platforms/cpp/ignite/odbc/utility.cpp index a3dd49b3c0..009b351961 100644 --- a/modules/platforms/cpp/ignite/odbc/utility.cpp +++ b/modules/platforms/cpp/ignite/odbc/utility.cpp @@ -18,6 +18,7 @@ #include "ignite/odbc/utility.h" #include "ignite/odbc/system/odbc_constants.h" +#include <algorithm> #include <cstring> namespace ignite { diff --git a/modules/platforms/cpp/tests/client-test/sql_test.cpp b/modules/platforms/cpp/tests/client-test/sql_test.cpp index 23bcd762b3..c6860082b1 100644 --- a/modules/platforms/cpp/tests/client-test/sql_test.cpp +++ b/modules/platforms/cpp/tests/client-test/sql_test.cpp @@ -79,6 +79,13 @@ protected: // remove all } + primitive execute_statement_one_result(const sql_statement &statement) { + auto result_set0 = m_client.get_sql().execute(nullptr, statement, {}); + + EXPECT_TRUE(result_set0.has_rowset()); + return result_set0.current_page().front().get(0); + } + /** Ignite client. */ ignite_client m_client; }; @@ -479,3 +486,16 @@ TEST_F(sql_test, execute_script_fail) { EXPECT_EQ(ignite_type::INT32, value.get_type()); EXPECT_EQ(1, value.get<std::int32_t>()); } + +TEST_F(sql_test, timezone_passed) { + sql_statement statement{"SELECT CURRENT_TIMESTAMP"}; + statement.timezone_id("UTC+8"); + auto ts0 = execute_statement_one_result(statement); + EXPECT_EQ(ignite_type::DATETIME, ts0.get_type()); + + statement.timezone_id("UTC-2"); + auto ts1 = execute_statement_one_result(statement); + EXPECT_EQ(ignite_type::DATETIME, ts1.get_type()); + + EXPECT_NE(ts0.get<ignite_date_time>(), ts1.get<ignite_date_time>()); +} diff --git a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp index 052542dfa9..5f3d3ccca8 100644 --- a/modules/platforms/cpp/tests/odbc-test/connection_test.cpp +++ b/modules/platforms/cpp/tests/odbc-test/connection_test.cpp @@ -59,4 +59,41 @@ TEST_F(connection_test, dbms_cluster_name) { // Test cluster name: see PlatformTestNodeRunner. EXPECT_EQ(std::string("cluster"), std::string(reinterpret_cast<char *>(buffer))); -} \ No newline at end of file +} + +TEST_F(connection_test, timezone_passed) { + EXPECT_NO_THROW(odbc_connect_throw(get_basic_connection_string()+ "timezone=UTC+5;")); + auto ret = exec_query("SELECT CURRENT_TIMESTAMP"); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + SQLCHAR buffer[1024]; + SQLLEN column_len = sizeof(buffer); + + ret = SQLBindCol(m_statement, 1, SQL_C_CHAR, &buffer, column_len, &column_len); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + ret = SQLFetch(m_statement); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + ASSERT_GT(column_len, 0); + ASSERT_LT(column_len, 1024); + std::string ts0((char*)buffer, column_len); + odbc_clean_up(); + + EXPECT_NO_THROW(odbc_connect_throw(get_basic_connection_string()+ "timezone=UTC-8;")); + ret = exec_query("SELECT CURRENT_TIMESTAMP"); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + ret = SQLBindCol(m_statement, 1, SQL_C_CHAR, &buffer, column_len, &column_len); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + ret = SQLFetch(m_statement); + ODBC_FAIL_ON_ERROR(ret, SQL_HANDLE_STMT, m_statement); + + ASSERT_GT(column_len, 0); + ASSERT_LT(column_len, 1024); + std::string ts1((char*)buffer, column_len); + odbc_clean_up(); + + EXPECT_NE(ts0, ts1); +}