From 4f139a654c8294bc92d287e7d4fd04bc1c0514dd Mon Sep 17 00:00:00 2001 From: Tyagiquamar Date: Wed, 7 Oct 2026 22:38:58 +0530 Subject: [PATCH] fix(client): quote table names in Insert queries Client::Insert() now quotes bare and qualified table identifiers so hyphenated or reserved names work without manual backticks. Fixes ClickHouse/clickhouse-cpp#560 --- clickhouse/client.cpp | 27 ++++++++++++++++++++++++++- clickhouse/client.h | 3 +++ ut/client_ut.cpp | 29 +++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/clickhouse/client.cpp b/clickhouse/client.cpp index 531498c0..37ec8bce 100644 --- a/clickhouse/client.cpp +++ b/clickhouse/client.cpp @@ -484,6 +484,29 @@ std::string NameToQueryString(const std::string &input) return output; } +std::string TableNameToQueryString(const std::string& input) +{ + if (input.size() >= 2 && input.front() == '`' && input.back() == '`') { + return input; + } + + std::string output; + size_t start = 0; + bool first = true; + for (size_t i = 0; i <= input.size(); ++i) { + if (i == input.size() || input[i] == '.') { + if (!first) { + output.push_back('.'); + } + output += NameToQueryString(input.substr(start, i - start)); + first = false; + start = i + 1; + } + } + + return output; +} + void Client::Impl::Insert(const std::string& table_name, const std::string& query_id, const Block& block) { if (state_ == State::Inserting) { throw ValidationError("cannot execute query while inserting, use SendInsertData instead"); @@ -509,7 +532,9 @@ void Client::Impl::Insert(const std::string& table_name, const std::string& quer } } - Query query("INSERT INTO " + table_name + " ( " + fields_section.str() + " ) VALUES", query_id); + Query query( + "INSERT INTO " + TableNameToQueryString(table_name) + " ( " + fields_section.str() + " ) VALUES", + query_id); SendQuery(query); // Wait for a data packet and return diff --git a/clickhouse/client.h b/clickhouse/client.h index dfe31536..2a0d9157 100644 --- a/clickhouse/client.h +++ b/clickhouse/client.h @@ -313,6 +313,9 @@ class Client { bool IsSelecting() const; /// Intends for insert block of data into a table \p table_name. + /// \p table_name is a bare table identifier, or a qualified \c database.table name. + /// Each identifier part is quoted automatically. Pass an already backtick-quoted + /// identifier to use it verbatim (for example a table name that contains a dot). void Insert(const std::string& table_name, const Block& block); void Insert(const std::string& table_name, const std::string& query_id, const Block& block); diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index 79ee9ca8..baedfd04 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -406,6 +406,35 @@ TEST_P(ClientCase, LowCardinality) { ASSERT_EQ(total_rows, data.size()); } +TEST_P(ClientCase, InsertQuotesBareTableName) { + const std::string hyphenated_table = "test_clickhouse_cpp_my-table"; + + client_->Execute("DROP TABLE IF EXISTS `" + hyphenated_table + "`"); + client_->Execute("CREATE TABLE `" + hyphenated_table + "` (id Int64) ENGINE = Memory"); + + Block block; + auto col = std::make_shared(); + col->Append(42); + block.AppendColumn("id", col); + block.RefreshRowCount(); + + client_->Insert(hyphenated_table, block); + + int64_t value = 0; + client_->Select( + "SELECT id FROM `" + hyphenated_table + "`", + [&value](const Block& result) { + if (result.GetRowCount() == 0) { + return; + } + + value = result[0]->As()->At(0); + }); + + EXPECT_EQ(value, 42); + client_->Execute("DROP TABLE IF EXISTS `" + hyphenated_table + "`"); +} + TEST_P(ClientCase, LowCardinality_InsertAfterClear) { // User can successfully insert values after invoking Clear() on LC column. Block block;