From e6f5122b5ef4a1112695bcb3d7fda44c08f93b88 Mon Sep 17 00:00:00 2001 From: Johannes Misch Date: Tue, 21 Apr 2026 14:01:26 +0200 Subject: [PATCH 1/6] Introduce `Bool` type Previously the library implicitly converted bool columns to `Uint8`, loosing type information. This commit introduces a dedicated ColumnBool type that internally wraps an UInt8 column, dispatching to its member functions. 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 | 7 +++ clickhouse/columns/bool.cpp | 79 ++++++++++++++++++++++++++++++++ clickhouse/columns/bool.h | 63 +++++++++++++++++++++++++ clickhouse/columns/factory.cpp | 5 ++ clickhouse/columns/itemview.cpp | 3 ++ clickhouse/columns/itemview.h | 8 +++- clickhouse/types/type_parser.cpp | 4 ++ clickhouse/types/types.cpp | 9 ++++ clickhouse/types/types.h | 15 ++++++ ut/CreateColumnByType_ut.cpp | 12 +++++ ut/client_ut.cpp | 33 +++++++++---- ut/types_ut.cpp | 22 +++++++++ ut/utils.cpp | 5 ++ 15 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 clickhouse/columns/bool.cpp create mode 100644 clickhouse/columns/bool.h 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 43282e54..b83453d5 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]) @@ -26,6 +27,10 @@ C++ client for [ClickHouse](https://clickhouse.com/). * JSON - experimental support; requires output_format_native_write_json_as_string=1; data is passed as strings +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, @@ -258,5 +263,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 c759dfcb..7b68fb6e 100644 --- a/clickhouse/CMakeLists.txt +++ b/clickhouse/CMakeLists.txt @@ -8,6 +8,7 @@ SET ( clickhouse-cpp-lib-src base/endpoints_iterator.cpp columns/array.cpp + columns/bool.cpp columns/column.cpp columns/date.cpp columns/decimal.cpp @@ -53,6 +54,7 @@ SET ( clickhouse-cpp-lib-src base/wire_format.h columns/array.h + columns/bool.h columns/column.h columns/date.h columns/decimal.h @@ -122,6 +124,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/bool.cpp b/clickhouse/columns/bool.cpp new file mode 100644 index 00000000..a524fd99 --- /dev/null +++ b/clickhouse/columns/bool.cpp @@ -0,0 +1,79 @@ +#include "bool.h" + +#include "../types/types.h" + +#if !CH_MAP_BOOL_TO_UINT8 + +namespace clickhouse { + +ColumnBool::ColumnBool() + : Column(Type::CreateSimple()) + , data_() +{ +} + +ColumnBool::ColumnBool(std::vector data) + : Column(Type::CreateSimple()) + , data_(std::move(data)) +{ +} + +void ColumnBool::Reserve(size_t new_cap) { + data_.Reserve(new_cap); +} + +void ColumnBool::Append(bool value) { + data_.Append(static_cast(value)); +} + +bool ColumnBool::At(size_t n) const { + return static_cast(data_.At(n)); +} + +void ColumnBool::Append(ColumnRef column) { + if (auto col = column->As()) { + auto& src = col->data_.GetWritableData(); + data_.GetWritableData().insert(data_.GetWritableData().end(), src.begin(), src.end()); + } else if (auto col = column->As()) { + auto& src = col->GetWritableData(); + data_.GetWritableData().insert(data_.GetWritableData().end(), src.begin(), src.end()); + } +} + +bool ColumnBool::LoadBody(InputStream* input, size_t rows) { + return data_.LoadBody(input, rows); +} + +void ColumnBool::SaveBody(OutputStream* output) { + data_.SaveBody(output); +} + +void ColumnBool::Clear() { + data_.Clear(); +} + +size_t ColumnBool::Size() const { + return data_.Size(); +} + +ColumnRef ColumnBool::Slice(size_t begin, size_t len) const { + auto sliced = std::static_pointer_cast(data_.Slice(begin, len)); + return std::make_shared(std::move(sliced->GetWritableData())); +} + +ColumnRef ColumnBool::CloneEmpty() const { + return std::make_shared(); +} + +void ColumnBool::Swap(Column& other) { + auto& col = dynamic_cast(other); + data_.Swap(col.data_); +} + +ItemView ColumnBool::GetItem(size_t index) const { + return ItemView{Type::Bool, data_.At(index)}; +} + +} // namespace clickhouse + +#endif // !CH_MAP_BOOL_TO_UINT8 diff --git a/clickhouse/columns/bool.h b/clickhouse/columns/bool.h new file mode 100644 index 00000000..fcb0e63a --- /dev/null +++ b/clickhouse/columns/bool.h @@ -0,0 +1,63 @@ +#pragma once + +#include "column.h" +#include "numeric.h" + +#include "../types/types.h" + +#if !CH_MAP_BOOL_TO_UINT8 + +#include + +namespace clickhouse { + +class ColumnBool : public Column { +public: + using ValueType = bool; + + ColumnBool(); + explicit ColumnBool(std::vector data); + + /// Increase the capacity of the column for large block insertion. + void Reserve(size_t new_cap) override; + + /// Appends one element to the end of column. + void Append(bool value); + + /// Returns element at given row number. + bool At(size_t n) const; + + /// Returns element at given row number. + bool operator[](size_t n) const { return At(n); } + +public: + /// Appends content of given column to the end of current one. + /// Accepts ColumnBool or ColumnUInt8. + void Append(ColumnRef column) override; + + /// Loads column data from input stream. + bool LoadBody(InputStream* input, size_t rows) override; + + /// Saves column data to output stream. + void SaveBody(OutputStream* output) override; + + /// Clear column data. + void Clear() override; + + /// Returns count of rows in the column. + size_t Size() const override; + + /// Makes slice of the current column. + ColumnRef Slice(size_t begin, size_t len) const override; + ColumnRef CloneEmpty() const override; + void Swap(Column& other) override; + + ItemView GetItem(size_t index) const override; + +private: + ColumnUInt8 data_; +}; + +} // namespace clickhouse + +#endif // !CH_MAP_BOOL_TO_UINT8 diff --git a/clickhouse/columns/factory.cpp b/clickhouse/columns/factory.cpp index 399551ee..364eacfe 100644 --- a/clickhouse/columns/factory.cpp +++ b/clickhouse/columns/factory.cpp @@ -13,6 +13,7 @@ #include "map.h" #include "nothing.h" #include "nullable.h" +#include "bool.h" #include "numeric.h" #include "string.h" #include "./time.h" // `./` avoids possible conflicts with standard C time.h @@ -50,6 +51,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 610db030..1ea86ba5 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..0fd7428d 100644 --- a/clickhouse/columns/itemview.h +++ b/clickhouse/columns/itemview.h @@ -28,7 +28,9 @@ 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 + || 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 +67,9 @@ 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 + || std::is_same_v + || std::is_same_v) { if (sizeof(ValueType) == data.size()) { return *reinterpret_cast(data.data()); } else { diff --git a/clickhouse/types/type_parser.cpp b/clickhouse/types/type_parser.cpp index 3c60c03e..82492412 100644 --- a/clickhouse/types/type_parser.cpp +++ b/clickhouse/types/type_parser.cpp @@ -34,7 +34,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 4435133e..da62cd25 100644 --- a/clickhouse/types/types.cpp +++ b/clickhouse/types/types.cpp @@ -55,6 +55,9 @@ const char* Type::TypeName(Type::Code code) { case Type::Code::Time: return "Time"; case Type::Code::Time64: return "Time64"; case Type::Code::JSON: return "JSON"; +#if !CH_MAP_BOOL_TO_UINT8 + case Type::Code::Bool: return "Bool"; +#endif } return "Unknown type"; @@ -87,6 +90,9 @@ std::string Type::GetName() const { case Polygon: case MultiPolygon: case JSON: +#if !CH_MAP_BOOL_TO_UINT8 + case Bool: +#endif return TypeName(code_); case Time64: return As()->GetName(); @@ -149,6 +155,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 9cdaa99d..84aac919 100644 --- a/clickhouse/types/types.h +++ b/clickhouse/types/types.h @@ -9,12 +9,17 @@ #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; + using TypeRef = std::shared_ptr; class Type { @@ -60,6 +65,9 @@ class Type { Time, Time64, JSON, +#if !CH_MAP_BOOL_TO_UINT8 + Bool, +#endif }; using EnumItem = std::pair; @@ -394,6 +402,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 c00e4bad..c343a019 100644 --- a/ut/CreateColumnByType_ut.cpp +++ b/ut/CreateColumnByType_ut.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -63,7 +64,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) @@ -76,6 +85,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 5449c6af..3b6b2d5c 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -1,4 +1,5 @@ #include +#include #include "clickhouse/base/socket.h" #include "clickhouse/version.h" @@ -22,6 +23,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 value; +#endif +} + template std::shared_ptr createTableWithOneColumn(Client & client, const std::string & table_name, const std::string & column_name) { @@ -400,11 +417,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 +443,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 +485,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 +501,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 +526,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 b0029cd7..09deddfe 100644 --- a/ut/types_ut.cpp +++ b/ut/types_ut.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include @@ -34,7 +36,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 989f7a5c..d776797d 100644 --- a/ut/utils.cpp +++ b/ut/utils.cpp @@ -362,6 +362,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; From ef809749b384e7c84959db8dae9050be3948bc53 Mon Sep 17 00:00:00 2001 From: Andrew Slabko Date: Tue, 19 May 2026 08:35:40 +0200 Subject: [PATCH 2/6] Fix MSVC build --- ut/CreateColumnByType_ut.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ut/CreateColumnByType_ut.cpp b/ut/CreateColumnByType_ut.cpp index c343a019..e312c116 100644 --- a/ut/CreateColumnByType_ut.cpp +++ b/ut/CreateColumnByType_ut.cpp @@ -85,12 +85,12 @@ 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" )); +#if !CH_MAP_BOOL_TO_UINT8 +INSTANTIATE_TEST_SUITE_P(BasicBool, CreateColumnByTypeWithName, ::testing::Values("Bool")); +#endif INSTANTIATE_TEST_SUITE_P(Parametrized, CreateColumnByTypeWithName, ::testing::Values( "FixedString(0)", "FixedString(10000)", From adef072a7643c9292df3745fac416a97fda93963 Mon Sep 17 00:00:00 2001 From: Andrew Slabko Date: Tue, 19 May 2026 09:04:30 +0200 Subject: [PATCH 3/6] Disable CH_MAP_BOOL_TO_UINT8 on CI --- .github/workflows/linux.yml | 1 + .github/workflows/macos.yml | 1 + .github/workflows/windows_mingw.yml | 2 +- .github/workflows/windows_msvc.yml | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4b79fd84..8dde1cd8 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -98,6 +98,7 @@ jobs: -D CMAKE_C_COMPILER=${{matrix.C_COMPILER}} \ -D CMAKE_CXX_COMPILER=${{matrix.CXX_COMPILER}} \ -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -D CH_MAP_BOOL_TO_UINT8=OFF \ -D BUILD_TESTS=ON \ ${{matrix.SSL_CMAKE_OPTION}} \ ${{matrix.DEPENDENCIES_CMAKE_OPTIONS}} \ diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 1fc91061..8deea625 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -48,6 +48,7 @@ jobs: run: | cmake \ -D CMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \ + -D CH_MAP_BOOL_TO_UINT8=OFF \ -D BUILD_TESTS=ON \ ${{matrix.SSL_CMAKE_OPTION}} \ -S ${{github.workspace}} \ diff --git a/.github/workflows/windows_mingw.yml b/.github/workflows/windows_mingw.yml index 0567527b..f5699d8f 100644 --- a/.github/workflows/windows_mingw.yml +++ b/.github/workflows/windows_mingw.yml @@ -52,7 +52,7 @@ jobs: tar - name: Configure CMake - run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -DCHECK_VERSION=OFF + run: cmake -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCH_MAP_BOOL_TO_UINT8=OFF -DBUILD_TESTS=ON -DCHECK_VERSION=OFF # -DWITH_OPENSSL=ON was not able to make it work (some strange issues with CA paths, need debug) # -DCHECK_VERSION=OFF since it requires git, which can't be found from within cmake for some reason. diff --git a/.github/workflows/windows_msvc.yml b/.github/workflows/windows_msvc.yml index 12695dd5..16208211 100644 --- a/.github/workflows/windows_msvc.yml +++ b/.github/workflows/windows_msvc.yml @@ -28,7 +28,7 @@ jobs: - uses: ilammy/msvc-dev-cmd@v1 - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCH_MAP_BOOL_TO_UINT8=OFF -DBUILD_TESTS=ON - name: Build run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} From f26bc30867f77713a4d9e3caa5fcc96681d0ef5a Mon Sep 17 00:00:00 2001 From: Andrew Slabko Date: Tue, 19 May 2026 16:42:23 +0200 Subject: [PATCH 4/6] Reduce use of CH_MAP_BOOL_TO_UINT8 --- clickhouse/columns/bool.cpp | 4 ---- clickhouse/columns/bool.h | 4 ---- clickhouse/columns/factory.cpp | 2 -- clickhouse/columns/itemview.cpp | 2 -- clickhouse/types/types.cpp | 6 ------ clickhouse/types/types.h | 9 --------- 6 files changed, 27 deletions(-) diff --git a/clickhouse/columns/bool.cpp b/clickhouse/columns/bool.cpp index a524fd99..24bd4573 100644 --- a/clickhouse/columns/bool.cpp +++ b/clickhouse/columns/bool.cpp @@ -2,8 +2,6 @@ #include "../types/types.h" -#if !CH_MAP_BOOL_TO_UINT8 - namespace clickhouse { ColumnBool::ColumnBool() @@ -75,5 +73,3 @@ ItemView ColumnBool::GetItem(size_t index) const { } } // namespace clickhouse - -#endif // !CH_MAP_BOOL_TO_UINT8 diff --git a/clickhouse/columns/bool.h b/clickhouse/columns/bool.h index fcb0e63a..3fc06ea0 100644 --- a/clickhouse/columns/bool.h +++ b/clickhouse/columns/bool.h @@ -5,8 +5,6 @@ #include "../types/types.h" -#if !CH_MAP_BOOL_TO_UINT8 - #include namespace clickhouse { @@ -59,5 +57,3 @@ class ColumnBool : public Column { }; } // namespace clickhouse - -#endif // !CH_MAP_BOOL_TO_UINT8 diff --git a/clickhouse/columns/factory.cpp b/clickhouse/columns/factory.cpp index 364eacfe..a01304f8 100644 --- a/clickhouse/columns/factory.cpp +++ b/clickhouse/columns/factory.cpp @@ -51,10 +51,8 @@ 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 1ea86ba5..c34ac5b0 100644 --- a/clickhouse/columns/itemview.cpp +++ b/clickhouse/columns/itemview.cpp @@ -44,9 +44,7 @@ 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/types/types.cpp b/clickhouse/types/types.cpp index da62cd25..e12342c8 100644 --- a/clickhouse/types/types.cpp +++ b/clickhouse/types/types.cpp @@ -55,9 +55,7 @@ const char* Type::TypeName(Type::Code code) { case Type::Code::Time: return "Time"; case Type::Code::Time64: return "Time64"; case Type::Code::JSON: return "JSON"; -#if !CH_MAP_BOOL_TO_UINT8 case Type::Code::Bool: return "Bool"; -#endif } return "Unknown type"; @@ -90,9 +88,7 @@ std::string Type::GetName() const { case Polygon: case MultiPolygon: case JSON: -#if !CH_MAP_BOOL_TO_UINT8 case Bool: -#endif return TypeName(code_); case Time64: return As()->GetName(); @@ -155,9 +151,7 @@ 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 84aac919..5a2fa53e 100644 --- a/clickhouse/types/types.h +++ b/clickhouse/types/types.h @@ -9,17 +9,12 @@ #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; - using TypeRef = std::shared_ptr; class Type { @@ -65,9 +60,7 @@ class Type { Time, Time64, JSON, -#if !CH_MAP_BOOL_TO_UINT8 Bool, -#endif }; using EnumItem = std::pair; @@ -402,12 +395,10 @@ 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() { From 0a4c024d4fb0a8fced34674cbc96e9e3bbb6e296 Mon Sep 17 00:00:00 2001 From: Andrew Slabko Date: Tue, 19 May 2026 16:42:49 +0200 Subject: [PATCH 5/6] Add tests for Bool, add missing Capacity function --- clickhouse/client.h | 1 + clickhouse/columns/bool.cpp | 4 ++++ clickhouse/columns/bool.h | 3 +++ ut/Column_ut.cpp | 4 ++++ ut/column_array_ut.cpp | 13 +++++++++++++ ut/columns_ut.cpp | 21 +++++++++++++++++++++ ut/itemview_ut.cpp | 5 +++++ ut/type_parser_ut.cpp | 13 +++++++++++++ ut/types_ut.cpp | 21 +++------------------ ut/utils.cpp | 2 -- ut/utils_ut.cpp | 2 ++ 11 files changed, 69 insertions(+), 20 deletions(-) diff --git a/clickhouse/client.h b/clickhouse/client.h index 17e1e404..a55fd748 100644 --- a/clickhouse/client.h +++ b/clickhouse/client.h @@ -20,6 +20,7 @@ #include "columns/tuple.h" #include "columns/time.h" #include "columns/uuid.h" +#include "columns/bool.h" #include #include diff --git a/clickhouse/columns/bool.cpp b/clickhouse/columns/bool.cpp index 24bd4573..c9cd8719 100644 --- a/clickhouse/columns/bool.cpp +++ b/clickhouse/columns/bool.cpp @@ -20,6 +20,10 @@ void ColumnBool::Reserve(size_t new_cap) { data_.Reserve(new_cap); } +size_t ColumnBool::Capacity() const { + return data_.Capacity(); +} + void ColumnBool::Append(bool value) { data_.Append(static_cast(value)); } diff --git a/clickhouse/columns/bool.h b/clickhouse/columns/bool.h index 3fc06ea0..5ba42bab 100644 --- a/clickhouse/columns/bool.h +++ b/clickhouse/columns/bool.h @@ -28,6 +28,9 @@ class ColumnBool : public Column { /// Returns element at given row number. bool operator[](size_t n) const { return At(n); } + /// Returns the capacity of the column + size_t Capacity() const; + public: /// Appends content of given column to the end of current one. /// Accepts ColumnBool or ColumnUInt8. diff --git a/ut/Column_ut.cpp b/ut/Column_ut.cpp index 78852831..c0fdc1ac 100644 --- a/ut/Column_ut.cpp +++ b/ut/Column_ut.cpp @@ -193,6 +193,10 @@ using TestCases = ::testing::Types< NumberColumnTestCase, NumberColumnTestCase, +#if !CH_MAP_BOOL_TO_UINT8 + GenericColumnTestCase, uint8_t, &MakeBools>, +#endif + GenericColumnTestCase, std::string, &MakeStrings>, GenericColumnTestCase, std::string, &MakeFixedStrings<12>>, GenericColumnTestCase, std::string, &MakeJSONs>, diff --git a/ut/column_array_ut.cpp b/ut/column_array_ut.cpp index 2925eedf..887f5047 100644 --- a/ut/column_array_ut.cpp +++ b/ut/column_array_ut.cpp @@ -337,6 +337,19 @@ TEST(ColumnArrayT, Wrap_UInt64_2D) { EXPECT_TRUE(CompareRecursive(values, array)); } +TEST(ColumnArrayT, Bool) { + // Check inserting\reading back data from clickhouse::ColumnArrayT + + const std::vector> values = { + {1u, 0u, 0u}, + {0u, 1u, 1u, 0u, 1u, 0u}, + {0u}, + {}, + {1u, 0u} + }; + CreateAndTestColumnArrayT(values); +} + TEST(ColumnArrayT, left_value_no_move) { std::string value0 = "000000000000000000"; std::string value1 = "111111111111111111"; diff --git a/ut/columns_ut.cpp b/ut/columns_ut.cpp index 489067b8..3e931132 100644 --- a/ut/columns_ut.cpp +++ b/ut/columns_ut.cpp @@ -137,6 +137,27 @@ TEST(ColumnsCase, StringAppend) { ASSERT_EQ(col->At(2), "11"); } +TEST(ColumnsCase, BoolInit) +{ + auto values = MakeBools(); + auto col = std::make_shared(values); + + ASSERT_EQ(col->Size(), values.size()); + ASSERT_EQ(col->At(0), 1); + ASSERT_EQ(col->At(3), 0); +} + +TEST(ColumnsCase, BoolAppend) +{ + auto col = std::make_shared(); + col->Append(true); + col->Append(false); + + ASSERT_EQ(col->Size(), 2u); + ASSERT_EQ(col->At(0), true); + ASSERT_EQ(col->At(1), false); +} + TEST(ColumnsCase, JSONInit) { auto values = MakeJSONs(); auto col = std::make_shared(values); diff --git a/ut/itemview_ut.cpp b/ut/itemview_ut.cpp index a932a0b5..5c6b5b58 100644 --- a/ut/itemview_ut.cpp +++ b/ut/itemview_ut.cpp @@ -29,6 +29,11 @@ TEST(ItemView, StorableTypes) { TEST_ITEMVIEW_TYPE_VALUE(TypeCode, NativeType, std::numeric_limits::max() - 1); \ TEST_ITEMVIEW_TYPE_VALUE(TypeCode, NativeType, std::numeric_limits::max()); + TEST_ITEMVIEW_TYPE_VALUES(Type::Code::Bool, int8_t); + TEST_ITEMVIEW_TYPE_VALUES(Type::Code::Bool, bool); + TEST_ITEMVIEW_TYPE_VALUE(Type::Code::Bool, bool, false); + TEST_ITEMVIEW_TYPE_VALUE(Type::Code::Bool, bool, true); + TEST_ITEMVIEW_TYPE_VALUES(Type::Code::Int8, int8_t); TEST_ITEMVIEW_TYPE_VALUES(Type::Code::Int16, int16_t); TEST_ITEMVIEW_TYPE_VALUES(Type::Code::Int32, int32_t); diff --git a/ut/type_parser_ut.cpp b/ut/type_parser_ut.cpp index c538abdb..f593de2c 100644 --- a/ut/type_parser_ut.cpp +++ b/ut/type_parser_ut.cpp @@ -24,6 +24,19 @@ TEST(TypeParserCase, ParseFixedString) { ASSERT_EQ(ast.elements.front().value, 24U); } +TEST(TypeParserCase, ParseBool) { + TypeAst ast; + TypeParser("Bool").Parse(&ast); + + ASSERT_EQ(ast.meta, TypeAst::Terminal); + ASSERT_EQ(ast.name, "Bool"); +#if !CH_MAP_BOOL_TO_UINT8 + ASSERT_EQ(ast.code, Type::Bool); +#else + ASSERT_EQ(ast.code, Type::UInt8); +#endif +} + TEST(TypeParserCase, ParseJSON) { TypeAst ast; TypeParser("JSON").Parse(&ast); diff --git a/ut/types_ut.cpp b/ut/types_ut.cpp index 09deddfe..2a18241a 100644 --- a/ut/types_ut.cpp +++ b/ut/types_ut.cpp @@ -37,26 +37,8 @@ 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(); @@ -162,6 +144,9 @@ TEST(TypesCase, DecimalTypes) { TEST(TypesCase, IsEqual) { const std::string type_names[] = { +#if !CH_MAP_BOOL_TO_UINT8 + "Bool", +#endif "UInt8", "Int8", // "UInt128", diff --git a/ut/utils.cpp b/ut/utils.cpp index d776797d..f52b17cf 100644 --- a/ut/utils.cpp +++ b/ut/utils.cpp @@ -362,11 +362,9 @@ 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; diff --git a/ut/utils_ut.cpp b/ut/utils_ut.cpp index e1de32fb..db8b887f 100644 --- a/ut/utils_ut.cpp +++ b/ut/utils_ut.cpp @@ -237,6 +237,8 @@ TEST(ItemView, OutputToOstream_VALID) { EXPECTED_SERIALIZATION("FixedString : \"string\" (6 bytes)", ColumnFixedString(6), "string"); EXPECTED_SERIALIZATION(R"(JSON : "{"key": "value"}" (16 bytes))", ColumnJSON(), R"({"key": "value"})"); + EXPECTED_SERIALIZATION("Bool : false", ColumnBool(), false); + EXPECTED_SERIALIZATION("Bool : true", ColumnBool(), true); EXPECTED_SERIALIZATION("Int8 : -123", ColumnInt8(), -123); EXPECTED_SERIALIZATION("Int16 : -1234", ColumnInt16(), -1234); EXPECTED_SERIALIZATION("Int32 : -12345", ColumnInt32(), -12345); From 71c48e3650976827990e93f7c3dd21e3baf26ec5 Mon Sep 17 00:00:00 2001 From: Johannes Misch Date: Tue, 19 May 2026 17:37:23 +0200 Subject: [PATCH 6/6] Clarify Bool type in Readme --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b83453d5..5d398a7e 100644 --- a/README.md +++ b/README.md @@ -6,7 +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) +* Bool \* (by default, mapped to UInt8 when receiving data) * Date * DateTime, DateTime64 * DateTime([timezone]), DateTime64(N, [timezone]) @@ -26,10 +26,13 @@ C++ client for [ClickHouse](https://clickhouse.com/). * Point, Ring, Polygon, MultiPolygon * JSON - experimental support; requires output_format_native_write_json_as_string=1; data is passed as strings - -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`. +\*: There exists a distinct `ColumnBool` and entry in the `Type` enumeration that +can be used. +By default, data received from the server will map Bool columns to UInt8. This is +a backwards compatibility feature. +If you want the library to produce ColumnBool, set the CMake variable `DCH_MAP_BOOL_TO_UINT8=OFF`. +The default for this variable will switch, with it being subsequently removed, in +a future release. ## Dependencies In the most basic case one needs only: