From bd2fa26438967503b216667d9e48c467407ab65e Mon Sep 17 00:00:00 2001 From: Johannes Misch Date: Tue, 21 Apr 2026 13:02:05 +0200 Subject: [PATCH 1/2] Add clangd cache directory to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8def1549..10fcc112 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,6 @@ BUCKAROO_DEPS # Vim *.swp *.swo + +# clangd cache +/.cache/clangd From a0c98cd05a7f54a52ee38d7e73173f404e0b475a Mon Sep 17 00:00:00 2001 From: Johannes Misch Date: Tue, 21 Apr 2026 14:01:26 +0200 Subject: [PATCH 2/2] Introduce `Bool` type Previously the library implicitly converted bool columns to `Uint8`, loosing type information. This commit introduces a "strong type" for `Bool` that is distinct from `bool`. This allows complete re-use of `ColumnVector` without triggering the `std::vector` specialization. This new type is guarded behind a CMake variable/preprocessor define `CH_MAP_BOOL_TO_UINT8`, which is enabled by default in order to keep compatibility for library users. In the future the default should be switched first and the variable then removed later. --- CMakeLists.txt | 1 + README.md | 7 +++++-- clickhouse/CMakeLists.txt | 5 +++++ clickhouse/columns/factory.cpp | 4 ++++ clickhouse/columns/itemview.cpp | 3 +++ clickhouse/columns/itemview.h | 14 ++++++++++++-- clickhouse/columns/numeric.cpp | 3 +++ clickhouse/columns/numeric.h | 3 +++ clickhouse/types/type_parser.cpp | 4 ++++ clickhouse/types/types.cpp | 9 +++++++++ clickhouse/types/types.h | 24 ++++++++++++++++++++++++ ut/CreateColumnByType_ut.cpp | 11 +++++++++++ ut/client_ut.cpp | 32 ++++++++++++++++++++++++-------- ut/types_ut.cpp | 21 +++++++++++++++++++++ ut/utils.cpp | 5 +++++ 15 files changed, 134 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eab3e7f..dfa7ccba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ OPTION (WITH_SYSTEM_ZSTD "Use system ZSTD" OFF) OPTION (DEBUG_DEPENDENCIES "Print debug info about dependencies duting build" ON) OPTION (CHECK_VERSION "Check that version number corresponds to git tag, usefull in CI/CD to validate that new version published on GitHub has same version in sources" OFF) OPTION (DISABLE_CLANG_LIBC_WORKAROUND "Disable linking compiler-rt & gcc_s if using clang & libstdc++" OFF) +OPTION (CH_MAP_BOOL_TO_UINT8 "Map ClickHouse Bool type to UInt8 instead of exposing a distinct Bool API." ON) PROJECT (CLICKHOUSE-CLIENT VERSION "${CLICKHOUSE_CPP_VERSION}" diff --git a/README.md b/README.md index a191c506..a878b97c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ C++ client for [ClickHouse](https://clickhouse.com/). ## Supported data types * Array(T) +* Bool (mapped to UInt8 by default; use `-DCH_MAP_BOOL_TO_UINT8=OFF` for a distinct `clickhouse::Bool`/`ColumnBool` API) * Date * DateTime, DateTime64 * DateTime([timezone]), DateTime64(N, [timezone]) @@ -24,6 +25,10 @@ C++ client for [ClickHouse](https://clickhouse.com/). * Map * Point, Ring, Polygon, MultiPolygon +The distinct `Bool` type will become the default in some future version. The +current mapping to `UInt8` is provided only for compatibility and controlled via +`DCH_MAP_BOOL_TO_UINT8`. + ## Dependencies In the most basic case one needs only: - a C++-17-complaint compiler, @@ -256,5 +261,3 @@ client.Insert("default.test", block); ```sql ALTER USER insert_account SETTINGS async_insert=1,wait_for_async_insert=1,async_insert_use_adaptive_busy_timeout=0,async_insert_busy_timeout_ms=5000,async_insert_max_data_size=104857600 ``` - - diff --git a/clickhouse/CMakeLists.txt b/clickhouse/CMakeLists.txt index 0ab1a487..dcd4b92e 100644 --- a/clickhouse/CMakeLists.txt +++ b/clickhouse/CMakeLists.txt @@ -120,6 +120,11 @@ TARGET_LINK_LIBRARIES (clickhouse-cpp-lib TARGET_INCLUDE_DIRECTORIES (clickhouse-cpp-lib PUBLIC ${PROJECT_SOURCE_DIR} ) +IF (CH_MAP_BOOL_TO_UINT8) + TARGET_COMPILE_DEFINITIONS (clickhouse-cpp-lib PUBLIC CH_MAP_BOOL_TO_UINT8=1) +ELSE () + TARGET_COMPILE_DEFINITIONS (clickhouse-cpp-lib PUBLIC CH_MAP_BOOL_TO_UINT8=0) +ENDIF () IF (NOT BUILD_SHARED_LIBS) ADD_LIBRARY (clickhouse-cpp-lib-static ALIAS clickhouse-cpp-lib) diff --git a/clickhouse/columns/factory.cpp b/clickhouse/columns/factory.cpp index 460d66fa..56845fd5 100644 --- a/clickhouse/columns/factory.cpp +++ b/clickhouse/columns/factory.cpp @@ -49,6 +49,10 @@ static ColumnRef CreateTerminalColumn(const TypeAst& ast) { case Type::Void: return std::make_shared(); +#if !CH_MAP_BOOL_TO_UINT8 + case Type::Bool: + return std::make_shared(); +#endif case Type::UInt8: return std::make_shared(); case Type::UInt16: diff --git a/clickhouse/columns/itemview.cpp b/clickhouse/columns/itemview.cpp index 0116070a..66c0ad16 100644 --- a/clickhouse/columns/itemview.cpp +++ b/clickhouse/columns/itemview.cpp @@ -44,6 +44,9 @@ void ItemView::ValidateData(Type::Code type, DataType data) { case Type::Code::Int8: case Type::Code::UInt8: case Type::Code::Enum8: +#if !CH_MAP_BOOL_TO_UINT8 + case Type::Code::Bool: +#endif return AssertSize({1}); case Type::Code::Int16: diff --git a/clickhouse/columns/itemview.h b/clickhouse/columns/itemview.h index 199994b6..3a2b2ab0 100644 --- a/clickhouse/columns/itemview.h +++ b/clickhouse/columns/itemview.h @@ -28,7 +28,12 @@ struct ItemView { inline auto ConvertToStorageValue(const T& t) { if constexpr (std::is_same_v || std::is_same_v) { return std::string_view{t}; - } else if constexpr (std::is_fundamental_v || std::is_same_v> || std::is_same_v>) { + } else if constexpr (std::is_fundamental_v +#if !CH_MAP_BOOL_TO_UINT8 + || std::is_same_v> +#endif + || std::is_same_v> + || std::is_same_v>) { return std::string_view{reinterpret_cast(&t), sizeof(T)}; } else { static_assert(!std::is_same_v, "Unknown type, which can't be stored in ItemView"); @@ -65,7 +70,12 @@ struct ItemView { using ValueType = std::remove_cv_t>; if constexpr (std::is_same_v || std::is_same_v) { return data; - } else if constexpr (std::is_fundamental_v || std::is_same_v || std::is_same_v) { + } else if constexpr (std::is_fundamental_v +#if !CH_MAP_BOOL_TO_UINT8 + || std::is_same_v +#endif + || std::is_same_v + || std::is_same_v) { if (sizeof(ValueType) == data.size()) { return *reinterpret_cast(data.data()); } else { diff --git a/clickhouse/columns/numeric.cpp b/clickhouse/columns/numeric.cpp index 4819f37a..d6b09e3e 100644 --- a/clickhouse/columns/numeric.cpp +++ b/clickhouse/columns/numeric.cpp @@ -113,6 +113,9 @@ template class ColumnVector; template class ColumnVector; template class ColumnVector; +#if !CH_MAP_BOOL_TO_UINT8 +template class ColumnVector; +#endif template class ColumnVector; template class ColumnVector; template class ColumnVector; diff --git a/clickhouse/columns/numeric.h b/clickhouse/columns/numeric.h index 5187b727..317e8d9f 100644 --- a/clickhouse/columns/numeric.h +++ b/clickhouse/columns/numeric.h @@ -70,6 +70,9 @@ using Int128 = absl::int128; using UInt128 = absl::uint128; using Int64 = int64_t; +#if !CH_MAP_BOOL_TO_UINT8 +using ColumnBool = ColumnVector; +#endif using ColumnUInt8 = ColumnVector; using ColumnUInt16 = ColumnVector; using ColumnUInt32 = ColumnVector; diff --git a/clickhouse/types/type_parser.cpp b/clickhouse/types/type_parser.cpp index d488a079..94df872e 100644 --- a/clickhouse/types/type_parser.cpp +++ b/clickhouse/types/type_parser.cpp @@ -32,7 +32,11 @@ static const std::unordered_map kTypeCode = { { "Int16", Type::Int16 }, { "Int32", Type::Int32 }, { "Int64", Type::Int64 }, +#if CH_MAP_BOOL_TO_UINT8 { "Bool", Type::UInt8 }, +#else + { "Bool", Type::Bool }, +#endif { "UInt8", Type::UInt8 }, { "UInt16", Type::UInt16 }, { "UInt32", Type::UInt32 }, diff --git a/clickhouse/types/types.cpp b/clickhouse/types/types.cpp index a5588c68..e7a6fef0 100644 --- a/clickhouse/types/types.cpp +++ b/clickhouse/types/types.cpp @@ -54,6 +54,9 @@ const char* Type::TypeName(Type::Code code) { case Type::Code::MultiPolygon: return "MultiPolygon"; case Type::Code::Time: return "Time"; case Type::Code::Time64: return "Time64"; +#if !CH_MAP_BOOL_TO_UINT8 + case Type::Code::Bool: return "Bool"; +#endif } return "Unknown type"; @@ -85,6 +88,9 @@ std::string Type::GetName() const { case Ring: case Polygon: case MultiPolygon: +#if !CH_MAP_BOOL_TO_UINT8 + case Bool: +#endif return TypeName(code_); case Time64: return As()->GetName(); @@ -146,6 +152,9 @@ uint64_t Type::GetTypeUniqueId() const { case Ring: case Polygon: case MultiPolygon: +#if !CH_MAP_BOOL_TO_UINT8 + case Bool: +#endif // For simple types, unique ID is the same as Type::Code return code_; diff --git a/clickhouse/types/types.h b/clickhouse/types/types.h index 2275cfba..4687502d 100644 --- a/clickhouse/types/types.h +++ b/clickhouse/types/types.h @@ -9,12 +9,26 @@ #include #include +#ifndef CH_MAP_BOOL_TO_UINT8 +#define CH_MAP_BOOL_TO_UINT8 1 +#endif + namespace clickhouse { using Int128 = absl::int128; using UInt128 = absl::uint128; using Int64 = int64_t; +#if !CH_MAP_BOOL_TO_UINT8 +/// Distinct type for the ClickHouse Bool type. Backed by `bool` so it has the +/// same single-byte layout as `uint8_t` without std::vector's +/// bit-packing, while remaining a type distinct from all integer types. +enum Bool : bool { + false_ = false, + true_ = true, +}; +#endif + using TypeRef = std::shared_ptr; class Type { @@ -59,6 +73,9 @@ class Type { MultiPolygon, Time, Time64, +#if !CH_MAP_BOOL_TO_UINT8 + Bool, +#endif }; using EnumItem = std::pair; @@ -384,6 +401,13 @@ inline TypeRef Type::CreateSimple() { return TypeRef(new Type(UInt64)); } +#if !CH_MAP_BOOL_TO_UINT8 +template <> +inline TypeRef Type::CreateSimple() { + return TypeRef(new Type(Bool)); +} +#endif + template <> inline TypeRef Type::CreateSimple() { return TypeRef(new Type(Float32)); diff --git a/ut/CreateColumnByType_ut.cpp b/ut/CreateColumnByType_ut.cpp index 556dfc36..738f1113 100644 --- a/ut/CreateColumnByType_ut.cpp +++ b/ut/CreateColumnByType_ut.cpp @@ -62,7 +62,15 @@ class CreateColumnByTypeWithName : public ::testing::TestWithParamGetType().GetName(), "UInt8"); + EXPECT_EQ(col->GetType().GetCode(), Type::UInt8); + EXPECT_NE(nullptr, col->As()); +#else + EXPECT_EQ(col->GetType().GetName(), "Bool"); + EXPECT_EQ(col->GetType().GetCode(), Type::Bool); + EXPECT_NE(nullptr, col->As()); +#endif } TEST_P(CreateColumnByTypeWithName, CreateColumnByType) @@ -75,6 +83,9 @@ TEST_P(CreateColumnByTypeWithName, CreateColumnByType) INSTANTIATE_TEST_SUITE_P(Basic, CreateColumnByTypeWithName, ::testing::Values( "Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", +#if !CH_MAP_BOOL_TO_UINT8 + "Bool", +#endif "String", "Date", "DateTime", "UUID", "Int128", "UInt128" )); diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index 29b0d47b..ddb45163 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -22,6 +22,22 @@ using namespace clickhouse; +#if CH_MAP_BOOL_TO_UINT8 +using ClientBoolColumn = ColumnUInt8; +using ClientBoolValue = uint8_t; +#else +using ClientBoolColumn = ColumnBool; +using ClientBoolValue = Bool; +#endif + +ClientBoolValue MakeClientBoolValue(bool value) { +#if CH_MAP_BOOL_TO_UINT8 + return static_cast(value); +#else + return static_cast(value); +#endif +} + template std::shared_ptr createTableWithOneColumn(Client & client, const std::string & table_name, const std::string & column_name) { @@ -400,11 +416,11 @@ TEST_P(ClientCase, Generic) { auto id = std::make_shared(); auto name = std::make_shared(); - auto f = std::make_shared (); + auto f = std::make_shared(); for (auto const& td : TEST_DATA) { id->Append(td.id); name->Append(td.name); - f->Append(td.f); + f->Append(MakeClientBoolValue(td.f)); } block.AppendColumn("id" , id); @@ -426,7 +442,7 @@ TEST_P(ClientCase, Generic) { for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { EXPECT_EQ(TEST_DATA[row].id, (*block[0]->As())[c]); EXPECT_EQ(TEST_DATA[row].name, (*block[1]->As())[c]); - EXPECT_EQ(TEST_DATA[row].f, (*block[2]->As())[c]); + EXPECT_EQ(MakeClientBoolValue(TEST_DATA[row].f), (*block[2]->As())[c]); } } ); @@ -468,13 +484,13 @@ TEST_P(ClientCase, InsertData) { // Fetch the derived columns. auto id = block[0]->As(); auto name = block[1]->As(); - auto f = block[2]->As(); + auto f = block[2]->As(); // Insert some values. for (auto const& td : TEST_DATA) { id->Append(td.id); name->Append(td.name); - f->Append(td.f); + f->Append(MakeClientBoolValue(td.f)); } block.RefreshRowCount(); client_->SendInsertBlock(block); @@ -484,7 +500,7 @@ TEST_P(ClientCase, InsertData) { for (auto const& td : TEST_DATA2) { id->Append(td.id); name->Append(td.name); - f->Append(td.f); + f->Append(MakeClientBoolValue(td.f)); } block.RefreshRowCount(); client_->SendInsertBlock(block); @@ -509,13 +525,13 @@ TEST_P(ClientCase, InsertData) { for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { EXPECT_EQ(TEST_DATA[row].id, (*block[0]->As())[c]); EXPECT_EQ(TEST_DATA[row].name, (*block[1]->As())[c]); - EXPECT_EQ(TEST_DATA[row].f, (*block[2]->As())[c]); + EXPECT_EQ(MakeClientBoolValue(TEST_DATA[row].f), (*block[2]->As())[c]); } } else { for (size_t c = 0; c < block.GetRowCount(); ++c, ++row) { EXPECT_EQ(TEST_DATA2[row-block_two_row_num].id, (*block[0]->As())[c]); EXPECT_EQ(TEST_DATA2[row-block_two_row_num].name, (*block[1]->As())[c]); - EXPECT_EQ(TEST_DATA2[row-block_two_row_num].f, (*block[2]->As())[c]); + EXPECT_EQ(MakeClientBoolValue(TEST_DATA2[row-block_two_row_num].f), (*block[2]->As())[c]); } } } diff --git a/ut/types_ut.cpp b/ut/types_ut.cpp index 7af343b5..841aa8d6 100644 --- a/ut/types_ut.cpp +++ b/ut/types_ut.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -34,7 +35,27 @@ TEST(TypesCase, TypeName) { ); ASSERT_EQ(Type::CreateMap(Type::CreateSimple(), Type::CreateString())->GetName(), "Map(Int32, String)"); + +#if !CH_MAP_BOOL_TO_UINT8 + ASSERT_EQ(Type::CreateSimple()->GetName(), "Bool"); +#endif +} + +#if !CH_MAP_BOOL_TO_UINT8 +TEST(TypesCase, ColumnBool) { + auto col = std::make_shared(); + col->Append(true_); + col->Append(false_); + col->Append(true_); + + ASSERT_EQ(col->Size(), 3u); + ASSERT_EQ(col->At(0), true_); + ASSERT_EQ(col->At(1), false_); + ASSERT_EQ(col->At(2), true_); + ASSERT_EQ(col->GetType().GetName(), "Bool"); + ASSERT_EQ(col->GetType().GetCode(), Type::Bool); } +#endif TEST(TypesCase, NullableType) { TypeRef nested = Type::CreateSimple(); diff --git a/ut/utils.cpp b/ut/utils.cpp index 5c0dec92..e0c62104 100644 --- a/ut/utils.cpp +++ b/ut/utils.cpp @@ -361,6 +361,11 @@ std::ostream& operator<<(std::ostream& ostr, const ItemView& item_view) { case Type::UInt8: ostr << static_cast(item_view.get()); break; +#if !CH_MAP_BOOL_TO_UINT8 + case Type::Bool: + ostr << (item_view.get() ? "true" : "false"); + break; +#endif case Type::UInt16: ostr << static_cast(item_view.get()); break;