diff --git a/clickhouse/client.cpp b/clickhouse/client.cpp index b5d287bc..dc0d9c09 100644 --- a/clickhouse/client.cpp +++ b/clickhouse/client.cpp @@ -531,6 +531,10 @@ Block Client::Impl::BeginInsert(Query query) { throw ValidationError("cannot execute query while executing another operation"); } + if (query.HasEventCallbacks()) { + throw ValidationError("Query callbacks are not supported in BeginInsert"); + } + EnsureNull en(static_cast(&query), &events_); if (options_.ping_before_query) { @@ -546,7 +550,7 @@ Block Client::Impl::BeginInsert(Query query) { return true; }); - SendQuery(query.GetText()); + SendQuery(query); // Wait for a data packet and return uint64_t server_packet = 0; @@ -1373,8 +1377,8 @@ void Client::Insert(const std::string& table_name, const std::string& query_id, impl_->Insert(table_name, query_id, block); } -Block Client::BeginInsert(const std::string& query) { - return impl_->BeginInsert(Query(query)); +Block Client::BeginInsert(const Query& query) { + return impl_->BeginInsert(query); } Block Client::BeginInsert(const std::string& query, const std::string& query_id) { diff --git a/clickhouse/client.h b/clickhouse/client.h index a55fd748..7e7bac65 100644 --- a/clickhouse/client.h +++ b/clickhouse/client.h @@ -302,7 +302,10 @@ class Client { void Insert(const std::string& table_name, const std::string& query_id, const Block& block); /// Start an \p INSERT statement, insert batches of data, then finish the insert. - Block BeginInsert(const std::string& query); + /// A bare string converts to a Query implicitly; use BeginInsert(const Query&) + /// to pass per-insert settings, params, and query_id. Event callbacks on the + /// Query are rejected (throws ValidationError): BeginInsert owns the data path. + Block BeginInsert(const Query& query); Block BeginInsert(const std::string& query, const std::string& query_id); /// Insert data using a \p block returned by \p BeginInsert. diff --git a/clickhouse/query.h b/clickhouse/query.h index 0bec97f6..e701638b 100644 --- a/clickhouse/query.h +++ b/clickhouse/query.h @@ -179,6 +179,14 @@ class Query : public QueryEvents { return *this; } + /// True if any server-event handler is installed. BeginInsert rejects such + /// a Query: it drives its own block-capture OnData and would otherwise let + /// the caller's handlers fire from the insert handshake's packet loop. + inline bool HasEventCallbacks() const { + return exception_cb_ || progress_cb_ || select_cb_ || select_cancelable_cb_ + || select_server_log_cb_ || profile_events_callback_cb_ || profile_callback_cb_; + } + static const std::string default_query_id; private: diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index 3b6b2d5c..a1bdd39e 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -541,6 +541,45 @@ TEST_P(ClientCase, InsertData) { EXPECT_EQ(exp, row); } +TEST_P(ClientCase, BeginInsertQuery) { + client_->Execute( + "CREATE TEMPORARY TABLE IF NOT EXISTS test_clickhouse_cpp_begin_insert_query (id UInt64)"); + + /// A Query carrying any event callback is rejected: BeginInsert drives its + /// own data path and the caller's handlers would otherwise fire from the + /// insert handshake's packet loop. + { + Query q("INSERT INTO test_clickhouse_cpp_begin_insert_query VALUES"); + q.OnException([](const Exception&) {}); + EXPECT_THROW(client_->BeginInsert(q), ValidationError); + } + + /// A Query with per-insert settings and a query_id (no callbacks) streams + /// normally through the overload. + { + Query q("INSERT INTO test_clickhouse_cpp_begin_insert_query VALUES", "begin-insert-query-id"); + q.SetSetting("max_block_size", {"100"}); + + auto block = client_->BeginInsert(q); + ASSERT_EQ(size_t(1), block.GetColumnCount()); + + auto id = block[0]->As(); + id->Append(42); + block.RefreshRowCount(); + client_->SendInsertBlock(block); + client_->EndInsert(); + } + + size_t row = 0; + client_->Select("SELECT id FROM test_clickhouse_cpp_begin_insert_query", + [&row](const Block& block) { + for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { + EXPECT_EQ(uint64_t(42), (*block[0]->As())[c]); + } + }); + EXPECT_EQ(size_t(1), row); +} + TEST_P(ClientCase, Nullable) { /// Create a table. client_->Execute(