From 071e166ab576810ce6561d87bab5787601f82edb Mon Sep 17 00:00:00 2001 From: wangwei <1261385937@qq.com> Date: Tue, 6 Dec 2022 16:47:52 +0800 Subject: [PATCH 1/2] add parameterized syntax, just syntax-sugar --- clickhouse/client.cpp | 16 -------- clickhouse/client.h | 95 +++++++++++++++++++++++++++++++++++++++++-- clickhouse/query.cpp | 5 +++ clickhouse/query.h | 1 + ut/client_ut.cpp | 28 ++++++++++++- 5 files changed, 123 insertions(+), 22 deletions(-) diff --git a/clickhouse/client.cpp b/clickhouse/client.cpp index e4b0c7ef..6b5800d6 100644 --- a/clickhouse/client.cpp +++ b/clickhouse/client.cpp @@ -902,22 +902,6 @@ void Client::Execute(const Query& query) { impl_->ExecuteQuery(query); } -void Client::Select(const std::string& query, SelectCallback cb) { - Execute(Query(query).OnData(std::move(cb))); -} - -void Client::Select(const std::string& query, const std::string& query_id, SelectCallback cb) { - Execute(Query(query, query_id).OnData(std::move(cb))); -} - -void Client::SelectCancelable(const std::string& query, SelectCancelableCallback cb) { - Execute(Query(query).OnDataCancelable(std::move(cb))); -} - -void Client::SelectCancelable(const std::string& query, const std::string& query_id, SelectCancelableCallback cb) { - Execute(Query(query, query_id).OnDataCancelable(std::move(cb))); -} - void Client::Select(const Query& query) { Execute(query); } diff --git a/clickhouse/client.h b/clickhouse/client.h index 2b315b00..794e5553 100644 --- a/clickhouse/client.h +++ b/clickhouse/client.h @@ -212,14 +212,43 @@ class Client { /// Intends for execute select queries. Data will be returned with /// one or more call of \p cb. - void Select(const std::string& query, SelectCallback cb); - void Select(const std::string& query, const std::string& query_id, SelectCallback cb); + /// Now it supports parameterized syntax, the placeholder should be ? + /// It is just syntax-sugar, all replacing happens on the client side + template + void Select(const std::string& query, SelectCallback cb, Parameterized&&... params) { + this->ExecuteInner(query, [this, &cb](auto&& new_query) { + Execute(Query(std::move(new_query)).OnData(std::move(cb))); + }, std::forward(params)...); + } + + template + void Select(const std::string& query, + const std::string& query_id, SelectCallback cb, Parameterized&&... params) { + this->ExecuteInner(query, [this, &cb, queryid = query_id](auto&& new_query) { + Execute(Query(std::move(new_query), std::move(queryid)).OnData(std::move(cb))); + }, std::forward(params)...); + } /// Executes a select query which can be canceled by returning false from /// the data handler function \p cb. - void SelectCancelable(const std::string& query, SelectCancelableCallback cb); - void SelectCancelable(const std::string& query, const std::string& query_id, SelectCancelableCallback cb); + /// Now it supports parameterized syntax, the placeholder should be ? + /// It is just syntax-sugar, all replacing happens on the client side + template + void SelectCancelable(const std::string& query, + SelectCancelableCallback cb, Parameterized&&... params) { + this->ExecuteInner(query, [this, &cb](auto&& new_query) { + Execute(Query(std::move(new_query)).OnDataCancelable(std::move(cb))); + }, std::forward(params)...); + } + template + void SelectCancelable(const std::string& query, + const std::string& query_id, SelectCancelableCallback cb, Parameterized&&... params) { + this->ExecuteInner(query, [this, &cb, queryid = query_id](auto&& new_query) { + Execute(Query(std::move(new_query), std::move(queryid)).OnDataCancelable(std::move(cb))); + }, std::forward(params)...); + } + /// Alias for Execute. void Select(const Query& query); @@ -235,6 +264,64 @@ class Client { const ServerInfo& GetServerInfo() const; +private: + template + void ExecuteInner(const std::string& query, Executor&& executor, Parameterized&&... params) { + constexpr auto params_count = sizeof...(params); + if constexpr (params_count == 0) { + executor(query); + } + else { + auto params_tup = std::forward_as_tuple(std::forward(params)...); + auto new_query = this->RecombineQuery(query, params_tup); + executor(new_query); + } + } + + template + static constexpr void ForEachTuple(F&& f, std::index_sequence) { + (std::forward(f)(std::integral_constant()), ...); + } + + template + std::string RecombineQuery(std::string_view query, ParamsTup&& tup) { + //Compute placeholder pos, 'find_first_of' for locating quickly + std::vector placeholder; + auto pos = query.find_first_of('?'); + if (pos == std::string_view::npos) { + throw ValidationError(std::string("params_count mismath placeholder")); + } + + placeholder.emplace_back(pos); + for (auto i = pos + 1; i < query.length(); ++i) { + if (query[i] == '?') { + placeholder.emplace_back(i); + } + } + //check placeholder and parameterized count + if (ParamsCount != placeholder.size()) { + throw ValidationError(std::string("params_count mismath placeholder")); + } + + //replace '?' with value + std::string new_query; + new_query.reserve(query.size() + ParamsCount * 16); + new_query.append(query); + this->ForEachTuple([&new_query, &tup, &placeholder](auto index) { + auto element = std::get(tup); + using type = std::remove_const_t>; + if constexpr ( + std::is_convertible_v || + std::is_same_v) { + new_query = new_query.replace(placeholder[index], 1, element); + } + else { + new_query = new_query.replace(placeholder[index], 1, std::to_string(element)); + } + }, std::make_index_sequence()); + return new_query; + } + private: const ClientOptions options_; diff --git a/clickhouse/query.cpp b/clickhouse/query.cpp index 3986064c..502b205d 100644 --- a/clickhouse/query.cpp +++ b/clickhouse/query.cpp @@ -19,6 +19,11 @@ Query::Query(const std::string& query, const std::string& query_id) { } +Query::Query(std::string&& query, std::string&& query_id) + : query_(std::move(query)) + , query_id_(std::move(query_id)) +{} + Query::~Query() { } diff --git a/clickhouse/query.h b/clickhouse/query.h index 21d7231f..c6495b93 100644 --- a/clickhouse/query.h +++ b/clickhouse/query.h @@ -86,6 +86,7 @@ class Query : public QueryEvents { Query(); Query(const char* query, const char* query_id = nullptr); Query(const std::string& query, const std::string& query_id = default_query_id); + Query(std::string&& query, std::string&& query_id = std::string(default_query_id)); ~Query() override; /// diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index 58d6304b..b1a304a3 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -889,6 +889,30 @@ TEST_P(ClientCase, Query_ID) { EXPECT_EQ(5u, total_count); } +TEST_P(ClientCase, parameterized_syntax) { + const std::string table_name = "test_clickhouse_cpp_parameterized_syntax"; + client_->Execute(Query("CREATE TEMPORARY TABLE IF NOT EXISTS " + table_name + " (a Int64)")); + + { + Block b; + b.AppendColumn("a", std::make_shared(std::vector{1, 2, 3})); + client_->Insert(table_name, b); + } + + size_t total_count = 0; + client_->Select("SELECT a FROM " + table_name + " WHERE a = ?", + [&total_count](const Block& block) { + total_count += block.GetRowCount(); + }, 4); + EXPECT_EQ(0u, total_count); + + client_->Select("SELECT a FROM " + table_name + " WHERE a > ?", + [&total_count](const Block& block) { + total_count += block.GetRowCount(); + }, 1); + EXPECT_EQ(2u, total_count); +} + // Spontaneosly fails on INSERTint data. TEST_P(ClientCase, DISABLED_ArrayArrayUInt64) { // Based on https://github.com/ClickHouse/clickhouse-cpp/issues/43 @@ -1201,7 +1225,7 @@ TEST_P(ClientCase, OnProfileEvents) { } const auto LocalHostEndpoint = ClientOptions() - .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost")) + .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "192.168.152.129")) .SetPort( getEnvOrDefault("CLICKHOUSE_PORT", "9000")) .SetUser( getEnvOrDefault("CLICKHOUSE_USER", "default")) .SetPassword( getEnvOrDefault("CLICKHOUSE_PASSWORD", "")) @@ -1243,7 +1267,7 @@ INSTANTIATE_TEST_SUITE_P(ClientLocalReadonly, ReadonlyClientTest, INSTANTIATE_TEST_SUITE_P(ClientLocalFailed, ConnectionFailedClientTest, ::testing::Values(ConnectionFailedClientTest::ParamType{ ClientOptions() - .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost")) + .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "192.168.152.129")) .SetPort( getEnvOrDefault("CLICKHOUSE_PORT", "9000")) .SetUser("non_existing_user_clickhouse_cpp_test") .SetPassword("wrongpwd") From 3cd8f153c888fbff22ec8cec8b612a49689fb488 Mon Sep 17 00:00:00 2001 From: wangwei <1261385937@qq.com> Date: Tue, 6 Dec 2022 19:12:10 +0800 Subject: [PATCH 2/2] fix ut ip --- ut/client_ut.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index b1a304a3..13442915 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -1225,7 +1225,7 @@ TEST_P(ClientCase, OnProfileEvents) { } const auto LocalHostEndpoint = ClientOptions() - .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "192.168.152.129")) + .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost")) .SetPort( getEnvOrDefault("CLICKHOUSE_PORT", "9000")) .SetUser( getEnvOrDefault("CLICKHOUSE_USER", "default")) .SetPassword( getEnvOrDefault("CLICKHOUSE_PASSWORD", "")) @@ -1267,7 +1267,7 @@ INSTANTIATE_TEST_SUITE_P(ClientLocalReadonly, ReadonlyClientTest, INSTANTIATE_TEST_SUITE_P(ClientLocalFailed, ConnectionFailedClientTest, ::testing::Values(ConnectionFailedClientTest::ParamType{ ClientOptions() - .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "192.168.152.129")) + .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost")) .SetPort( getEnvOrDefault("CLICKHOUSE_PORT", "9000")) .SetUser("non_existing_user_clickhouse_cpp_test") .SetPassword("wrongpwd")