diff --git a/clickhouse/columns/lowcardinality.h b/clickhouse/columns/lowcardinality.h index 3d8581fc..afadae22 100644 --- a/clickhouse/columns/lowcardinality.h +++ b/clickhouse/columns/lowcardinality.h @@ -53,9 +53,16 @@ class ColumnLowCardinality : public Column { UniqueItems unique_items_map_; public: + ColumnLowCardinality(ColumnLowCardinality&& col) = default; // c-tor makes a deep copy of the dictionary_column. explicit ColumnLowCardinality(ColumnRef dictionary_column); explicit ColumnLowCardinality(std::shared_ptr dictionary_column); + + template + explicit ColumnLowCardinality(std::shared_ptr> dictionary_column) + : ColumnLowCardinality(dictionary_column->template As()) + {} + ~ColumnLowCardinality(); /// Appends another LowCardinality column to the end of this one, updating dictionary. @@ -117,6 +124,13 @@ class ColumnLowCardinalityT : public ColumnLowCardinality { // Type this column takes as argument of Append and returns with At() and operator[] using ValueType = typename DictionaryColumnType::ValueType; + explicit ColumnLowCardinalityT(ColumnLowCardinality&& col) + : ColumnLowCardinality(std::move(col)) + , typed_dictionary_(dynamic_cast(*GetDictionary())) + , type_(GetTypeCode(typed_dictionary_)) + { + } + template explicit ColumnLowCardinalityT(Args &&... args) : ColumnLowCardinalityT(std::make_shared(std::forward(args)...)) @@ -124,9 +138,9 @@ class ColumnLowCardinalityT : public ColumnLowCardinality { // Create LC column from existing T-column, making a deep copy of all contents. explicit ColumnLowCardinalityT(std::shared_ptr dictionary_col) - : ColumnLowCardinality(dictionary_col), - typed_dictionary_(dynamic_cast(*GetDictionary())), - type_(typed_dictionary_.Type()->GetCode()) + : ColumnLowCardinality(dictionary_col) + , typed_dictionary_(dynamic_cast(*GetDictionary())) + , type_(GetTypeCode(typed_dictionary_)) {} /// Extended interface to simplify reading/adding individual items. @@ -145,7 +159,15 @@ class ColumnLowCardinalityT : public ColumnLowCardinality { using ColumnLowCardinality::Append; inline void Append(const ValueType & value) { - AppendUnsafe(ItemView{type_, value}); + if constexpr (IsNullable) { + if (value.has_value()) { + AppendUnsafe(ItemView{type_, *value}); + } else { + AppendUnsafe(ItemView{}); + } + } else { + AppendUnsafe(ItemView{type_, value}); + } } template @@ -154,6 +176,41 @@ class ColumnLowCardinalityT : public ColumnLowCardinality { Append(item); } } + + /** Create a ColumnLowCardinalityT from a ColumnLowCardinality, without copying data and offsets, but by + * 'stealing' those from `col`. + * + * Ownership of column internals is transferred to returned object, original (argument) object + * MUST NOT BE USED IN ANY WAY, it is only safe to dispose it. + * + * Throws an exception if `col` is of wrong type, it is safe to use original col in this case. + * This is a static method to make such conversion verbose. + */ + static auto Wrap(ColumnLowCardinality&& col) { + return std::make_shared>(std::move(col)); + } + + static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast(col))); } + + // Helper to simplify integration with other APIs + static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict())); } + + ColumnRef Slice(size_t begin, size_t size) const override { + return Wrap(ColumnLowCardinality::Slice(begin, size)); + } + + ColumnRef CloneEmpty() const override { return Wrap(ColumnLowCardinality::CloneEmpty()); } + +private: + + template + static auto GetTypeCode(T& column) { + if constexpr (IsNullable) { + return GetTypeCode(*column.Nested()->template AsStrict()); + } else { + return column.Type()->GetCode(); + } + } }; } diff --git a/clickhouse/columns/nullable.h b/clickhouse/columns/nullable.h index 41806624..c1924af0 100644 --- a/clickhouse/columns/nullable.h +++ b/clickhouse/columns/nullable.h @@ -3,6 +3,8 @@ #include "column.h" #include "numeric.h" +#include + namespace clickhouse { /** @@ -42,7 +44,7 @@ class ColumnNullable : public Column { /// Clear column data . void Clear() override; - + /// Returns count of rows in the column. size_t Size() const override; @@ -58,4 +60,91 @@ class ColumnNullable : public Column { std::shared_ptr nulls_; }; +template +class ColumnNullableT : public ColumnNullable { +public: + using NestedColumnType = ColumnType; + using ValueType = std::optional().At(0))>>; + + ColumnNullableT(std::shared_ptr data, std::shared_ptr nulls) + : ColumnNullable(data, nulls) + , typed_nested_data_(data) + {} + + explicit ColumnNullableT(std::shared_ptr data) + : ColumnNullableT(data, FillNulls(data->Size())) + {} + + template + explicit ColumnNullableT(Args &&... args) + : ColumnNullableT(std::make_shared(std::forward(args)...)) + {} + + inline ValueType At(size_t index) const { + return IsNull(index) ? ValueType{} : ValueType{typed_nested_data_->At(index)}; + } + + inline ValueType operator[](size_t index) const { return At(index); } + + /// Appends content of given column to the end of current one. + void Append(ColumnRef column) override { + ColumnNullable::Append(std::move(column)); + } + + inline void Append(ValueType value) { + ColumnNullable::Append(!value.has_value()); + if (value.has_value()) { + typed_nested_data_->Append(std::move(*value)); + } else { + typed_nested_data_->Append(typename ValueType::value_type{}); + } + } + + /** Create a ColumnNullableT from a ColumnNullable, without copying data and offsets, but by + * 'stealing' those from `col`. + * + * Ownership of column internals is transferred to returned object, original (argument) object + * MUST NOT BE USED IN ANY WAY, it is only safe to dispose it. + * + * Throws an exception if `col` is of wrong type, it is safe to use original col in this case. + * This is a static method to make such conversion verbose. + */ + static auto Wrap(ColumnNullable&& col) { + return std::make_shared>( + col.Nested()->AsStrict(), + col.Nulls()->AsStrict()) ; + } + + static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast(col))); } + + // Helper to simplify integration with other APIs + static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict())); } + + ColumnRef Slice(size_t begin, size_t size) const override { + return Wrap(ColumnNullable::Slice(begin, size)); + } + + ColumnRef CloneEmpty() const override { return Wrap(ColumnNullable::CloneEmpty()); } + + void Swap(Column& other) override { + auto& col = dynamic_cast&>(other); + typed_nested_data_.swap(col.typed_nested_data_); + ColumnNullable::Swap(other); + } + +private: + static inline auto FillNulls(size_t n){ + auto result = std::make_shared(); + for (size_t i = 0; i < n; ++i) { + result->Append(0); + } + return result; + } + + std::shared_ptr typed_nested_data_; +}; + +template +constexpr bool IsNullable = std::is_base_of_v; + } diff --git a/ut/CMakeLists.txt b/ut/CMakeLists.txt index 86066965..cf19056d 100644 --- a/ut/CMakeLists.txt +++ b/ut/CMakeLists.txt @@ -20,6 +20,7 @@ SET ( clickhouse-cpp-ut-src CreateColumnByType_ut.cpp Column_ut.cpp roundtrip_column.cpp + roundtrip_tests.cpp utils.cpp value_generators.cpp diff --git a/ut/Column_ut.cpp b/ut/Column_ut.cpp index 51cd4980..544be975 100644 --- a/ut/Column_ut.cpp +++ b/ut/Column_ut.cpp @@ -106,6 +106,28 @@ class GenericColumnTest : public testing::Test { return std::tuple{column, values}; } + + static std::optional SkipTest(clickhouse::Client& client) { + if constexpr (std::is_same_v) { + // Date32 first appeared in v21.9.2.17-stable + const auto server_info = client.GetServerInfo(); + if (versionNumber(server_info) < versionNumber(21, 9)) { + std::stringstream buffer; + buffer << "Date32 is available since v21.9.2.17-stable and can't be tested against server: " << server_info; + return buffer.str(); + } + } + + if constexpr (std::is_same_v) { + const auto server_info = client.GetServerInfo(); + if (versionNumber(server_info) < versionNumber(21, 7)) { + std::stringstream buffer; + buffer << "ColumnInt128 is available since v21.7.2.7-stable and can't be tested against server: " << server_info; + return buffer.str(); + } + } + return std::nullopt; + } }; using ValueColumns = ::testing::Types< @@ -279,21 +301,33 @@ TYPED_TEST(GenericColumnTest, RoundTrip) { clickhouse::Client client(LocalHostEndpoint); - if constexpr (std::is_same_v) { - // Date32 first appeared in v21.9.2.17-stable - const auto server_info = client.GetServerInfo(); - if (versionNumber(server_info) < versionNumber(21, 9)) { - GTEST_SKIP() << "Date32 is available since v21.9.2.17-stable and can't be tested against server: " << server_info; - } + if (auto message = this->SkipTest(client)) { + GTEST_SKIP() << *message; } - if constexpr (std::is_same_v) { - const auto server_info = client.GetServerInfo(); - if (versionNumber(server_info) < versionNumber(21, 7)) { - GTEST_SKIP() << "ColumnInt128 is available since v21.7.2.7-stable and can't be tested against server: " << server_info; + auto result_typed = RoundtripColumnValues(client, column)->template AsStrict(); + EXPECT_TRUE(CompareRecursive(*column, *result_typed)); +} + +TYPED_TEST(GenericColumnTest, NulableT_RoundTrip) { + using NullableType = ColumnNullableT; + auto column = std::make_shared(this->MakeColumn()); + auto values = this->GenerateValues(100); + FromVectorGenerator is_null({true, false}); + for (size_t i = 0; i < values.size(); ++i) { + if (is_null(i)) { + column->Append(std::nullopt); + } else { + column->Append(values[i]); } } - auto result_typed = RoundtripColumnValues(client, column)->template AsStrict(); + clickhouse::Client client(LocalHostEndpoint); + + if (auto message = this->SkipTest(client)) { + GTEST_SKIP() << *message; + } + + auto result_typed = WrapColumn(RoundtripColumnValues(client, column)); EXPECT_TRUE(CompareRecursive(*column, *result_typed)); } diff --git a/ut/client_ut.cpp b/ut/client_ut.cpp index aad7d882..6e758f97 100644 --- a/ut/client_ut.cpp +++ b/ut/client_ut.cpp @@ -3,7 +3,6 @@ #include "readonly_client_test.h" #include "connection_failed_client_test.h" #include "utils.h" -#include "roundtrip_column.h" #include @@ -67,21 +66,6 @@ class ClientCase : public testing::TestWithParam { } } - std::string GetSettingValue(const std::string& name) { - std::string result; - client_->Select("SELECT value FROM system.settings WHERE name = \'" + name + "\'", - [&result](const Block& block) - { - if (block.GetRowCount() == 0) { - return; - } - result = block[0]->AsStrict()->At(0); - } - ); - return result; - } - - std::unique_ptr client_; const std::string table_name = "test_clickhouse_cpp_test_ut_table"; const std::string column_name = "test_column"; @@ -978,167 +962,6 @@ TEST_P(ClientCase, DISABLED_ArrayArrayUInt64) { } } -TEST_P(ClientCase, RoundtripArrayTUint64) { - auto array = std::make_shared>(); - array->Append({0, 1, 2}); - - auto result = RoundtripColumnValues(*client_, array)->AsStrict(); - auto row = result->GetAsColumn(0)->As(); - - EXPECT_EQ(0u, row->At(0)); - EXPECT_EQ(1u, (*row)[1]); - EXPECT_EQ(2u, (*row)[2]); -} - -TEST_P(ClientCase, RoundtripArrayTArrayTUint64) { - const std::vector> row_values = { - {1, 2, 3}, - {4, 5, 6}, - {7, 8, 9, 10} - }; - - auto array = std::make_shared>>(); - array->Append(row_values); - - auto result_typed = ColumnArrayT>::Wrap(RoundtripColumnValues(*client_, array)); - EXPECT_TRUE(CompareRecursive(*array, *result_typed)); -} - -TEST_P(ClientCase, RoundtripArrayTArrayTArrayTUint64) { - using ColumnType = ColumnArrayT>>; - const std::vector>> row_values = { - {{1, 2, 3}, {3, 2, 1}}, - {{4, 5, 6}, {6, 5, 4}}, - {{7, 8, 9, 10}, {}}, - {{}, {10, 9, 8, 7}} - }; - - auto array = std::make_shared(); - array->Append(row_values); - - auto result_typed = ColumnType::Wrap(RoundtripColumnValues(*client_, array)); - EXPECT_TRUE(CompareRecursive(*array, *result_typed)); -} - - -TEST_P(ClientCase, RoundtripArrayTFixedString) { - auto array = std::make_shared>(6); - array->Append({"hello", "world"}); - - auto result_typed = ColumnArrayT::Wrap(RoundtripColumnValues(*client_, array)); - EXPECT_TRUE(CompareRecursive(*array, *result_typed)); -} - -TEST_P(ClientCase, RoundtripArrayTString) { - auto array = std::make_shared>(); - array->Append({"hello", "world"}); - - auto result_typed = ColumnArrayT::Wrap(RoundtripColumnValues(*client_, array)); - EXPECT_TRUE(CompareRecursive(*array, *result_typed)); -} - -TEST_P(ClientCase, RoundtripMapTUint64String) { - using Map = ColumnMapT; - auto map = std::make_shared(std::make_shared(), std::make_shared()); - - std::map row; - row[1] = "hello"; - row[2] = "world"; - map->Append(row); - - auto result_typed = Map::Wrap(RoundtripColumnValues(*client_, map)); - EXPECT_TRUE(CompareRecursive(*map, *result_typed)); -} - -TEST_P(ClientCase, RoundtripMapUUID_Tuple_String_Array_Uint64) { - using Tuple = ColumnTupleT>; - using Map = ColumnMapT; - auto map = std::make_shared(std::make_shared(), std::make_shared( - std::make_tuple(std::make_shared(), std::make_shared>()))); - - - std::map>> row; - row[UUID{1, 1}] = std::make_tuple("hello", std::vector{1, 2, 3}) ; - row[UUID{2, 2}] = std::make_tuple("world", std::vector{4, 5, 6}) ; - map->Append(row); - - auto result_typed = Map::Wrap(RoundtripColumnValues(*client_, map)); - EXPECT_TRUE(CompareRecursive(*map, *result_typed)); -} - -TEST_P(ClientCase, RoundtripPoint) { - if (GetSettingValue("allow_experimental_geo_types") != "1") { - GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; - } - - auto col = std::make_shared(); - col->Append({1.0, 2.0}); - col->Append({0.1, 0.2}); - - auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); - EXPECT_TRUE(CompareRecursive(*col, *result_typed)); -} - -TEST_P(ClientCase, RoundtripRing) { - if (GetSettingValue("allow_experimental_geo_types") != "1") { - GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; - } - - auto col = std::make_shared(); - { - std::vector ring{{1.0, 2.0}, {3.0, 4.0}}; - col->Append(ring); - } - { - std::vector ring{{0.1, 0.2}, {0.3, 0.4}}; - col->Append(ring); - } - auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); - EXPECT_TRUE(CompareRecursive(*col, *result_typed)); -} - -TEST_P(ClientCase, RoundtripPolygon) { - if (GetSettingValue("allow_experimental_geo_types") != "1") { - GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; - } - - auto col = std::make_shared(); - { - std::vector> polygon - {{{1.0, 2.0}, {3.0, 4.0}}, {{5.0, 6.0}, {7.0, 8.0}}}; - col->Append(polygon); - } - { - std::vector> polygon - {{{0.1, 0.2}, {0.3, 0.4}}, {{0.5, 0.6}, {0.7, 0.8}}}; - col->Append(polygon); - } - auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); - EXPECT_TRUE(CompareRecursive(*col, *result_typed)); -} - -TEST_P(ClientCase, RoundtripMultiPolygon) { - if (GetSettingValue("allow_experimental_geo_types") != "1") { - GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; - } - - auto col = std::make_shared(); - { - std::vector>> multi_polygon - {{{{1.0, 2.0}, {3.0, 4.0}}, {{5.0, 6.0}, {7.0, 8.0}}}, - {{{1.1, 2.2}, {3.3, 4.4}}, {{5.5, 6.6}, {7.7, 8.8}}}}; - col->Append(multi_polygon); - } - { - std::vector>> multi_polygon - {{{{0.1, 0.2}, {0.3, 0.4}}, {{0.5, 0.6}, {0.7, 0.8}}}, - {{{1.1, 1.2}, {1.3, 1.4}}, {{1.5, 1.6}, {1.7, 1.8}}}}; - col->Append(multi_polygon); - } - auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); - EXPECT_TRUE(CompareRecursive(*col, *result_typed)); -} - TEST_P(ClientCase, OnProgress) { Block block; createTableWithOneColumn(block); diff --git a/ut/roundtrip_tests.cpp b/ut/roundtrip_tests.cpp new file mode 100644 index 00000000..6cf3ce3c --- /dev/null +++ b/ut/roundtrip_tests.cpp @@ -0,0 +1,303 @@ +#include + +#include "utils.h" +#include "roundtrip_column.h" + +#include +#include + +using namespace clickhouse; + +// Use value-parameterized tests to run same tests with different client +// options. +class RoundtripCase : public testing::TestWithParam { +protected: + void SetUp() override { + client_ = std::make_unique(GetParam()); + } + + void TearDown() override { + client_.reset(); + } + + std::string GetSettingValue(const std::string& name) { + std::string result; + client_->Select("SELECT value FROM system.settings WHERE name = \'" + name + "\'", + [&result](const Block& block) + { + if (block.GetRowCount() == 0) { + return; + } + result = block[0]->AsStrict()->At(0); + } + ); + return result; + } + + + std::unique_ptr client_; +}; + +TEST_P(RoundtripCase, ArrayTUint64) { + auto array = std::make_shared>(); + array->Append({0, 1, 2}); + + auto result = RoundtripColumnValues(*client_, array)->AsStrict(); + auto row = result->GetAsColumn(0)->As(); + + EXPECT_EQ(0u, row->At(0)); + EXPECT_EQ(1u, (*row)[1]); + EXPECT_EQ(2u, (*row)[2]); +} + +TEST_P(RoundtripCase, ArrayTArrayTUint64) { + const std::vector> row_values = { + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9, 10} + }; + + auto array = std::make_shared>>(); + array->Append(row_values); + + auto result_typed = ColumnArrayT>::Wrap(RoundtripColumnValues(*client_, array)); + EXPECT_TRUE(CompareRecursive(*array, *result_typed)); +} + +TEST_P(RoundtripCase, ArrayTArrayTArrayTUint64) { + using ColumnType = ColumnArrayT>>; + const std::vector>> row_values = { + {{1, 2, 3}, {3, 2, 1}}, + {{4, 5, 6}, {6, 5, 4}}, + {{7, 8, 9, 10}, {}}, + {{}, {10, 9, 8, 7}} + }; + + auto array = std::make_shared(); + array->Append(row_values); + + auto result_typed = ColumnType::Wrap(RoundtripColumnValues(*client_, array)); + EXPECT_TRUE(CompareRecursive(*array, *result_typed)); +} + + +TEST_P(RoundtripCase, ArrayTFixedString) { + auto array = std::make_shared>(6); + array->Append({"hello", "world"}); + + auto result_typed = ColumnArrayT::Wrap(RoundtripColumnValues(*client_, array)); + EXPECT_TRUE(CompareRecursive(*array, *result_typed)); +} + +TEST_P(RoundtripCase, ArrayTString) { + auto array = std::make_shared>(); + array->Append({"hello", "world"}); + + auto result_typed = ColumnArrayT::Wrap(RoundtripColumnValues(*client_, array)); + EXPECT_TRUE(CompareRecursive(*array, *result_typed)); +} + +TEST_P(RoundtripCase, MapTUint64String) { + using Map = ColumnMapT; + auto map = std::make_shared(std::make_shared(), std::make_shared()); + + std::map row; + row[1] = "hello"; + row[2] = "world"; + map->Append(row); + + auto result_typed = Map::Wrap(RoundtripColumnValues(*client_, map)); + EXPECT_TRUE(CompareRecursive(*map, *result_typed)); +} + +TEST_P(RoundtripCase, MapUUID_Tuple_String_Array_Uint64) { + using Tuple = ColumnTupleT>; + using Map = ColumnMapT; + auto map = std::make_shared(std::make_shared(), std::make_shared( + std::make_tuple(std::make_shared(), std::make_shared>()))); + + + std::map>> row; + row[UUID{1, 1}] = std::make_tuple("hello", std::vector{1, 2, 3}) ; + row[UUID{2, 2}] = std::make_tuple("world", std::vector{4, 5, 6}) ; + map->Append(row); + + auto result_typed = Map::Wrap(RoundtripColumnValues(*client_, map)); + EXPECT_TRUE(CompareRecursive(*map, *result_typed)); +} + +TEST_P(RoundtripCase, Point) { + if (GetSettingValue("allow_experimental_geo_types") != "1") { + GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; + } + + auto col = std::make_shared(); + col->Append({1.0, 2.0}); + col->Append({0.1, 0.2}); + + auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, Ring) { + if (GetSettingValue("allow_experimental_geo_types") != "1") { + GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; + } + + auto col = std::make_shared(); + { + std::vector ring{{1.0, 2.0}, {3.0, 4.0}}; + col->Append(ring); + } + { + std::vector ring{{0.1, 0.2}, {0.3, 0.4}}; + col->Append(ring); + } + auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, Polygon) { + if (GetSettingValue("allow_experimental_geo_types") != "1") { + GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; + } + + auto col = std::make_shared(); + { + std::vector> polygon + {{{1.0, 2.0}, {3.0, 4.0}}, {{5.0, 6.0}, {7.0, 8.0}}}; + col->Append(polygon); + } + { + std::vector> polygon + {{{0.1, 0.2}, {0.3, 0.4}}, {{0.5, 0.6}, {0.7, 0.8}}}; + col->Append(polygon); + } + auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, MultiPolygon) { + if (GetSettingValue("allow_experimental_geo_types") != "1") { + GTEST_SKIP() << "Test is skipped because experimental geo types are not allowed. Set setting allow_experimental_geo_types = 1 in order to allow it." << std::endl; + } + + auto col = std::make_shared(); + { + std::vector>> multi_polygon + {{{{1.0, 2.0}, {3.0, 4.0}}, {{5.0, 6.0}, {7.0, 8.0}}}, + {{{1.1, 2.2}, {3.3, 4.4}}, {{5.5, 6.6}, {7.7, 8.8}}}}; + col->Append(multi_polygon); + } + { + std::vector>> multi_polygon + {{{{0.1, 0.2}, {0.3, 0.4}}, {{0.5, 0.6}, {0.7, 0.8}}}, + {{{1.1, 1.2}, {1.3, 1.4}}, {{1.5, 1.6}, {1.7, 1.8}}}}; + col->Append(multi_polygon); + } + auto result_typed = RoundtripColumnValues(*client_, col)->AsStrict(); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, LowCardinalityTString) { + using TestColumn = ColumnLowCardinalityT; + auto col = std::make_shared(); + col->Append("abc"); + col->Append("def"); + col->Append("abc"); + col->Append("abc"); + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, LowCardinalityTNullableString) { + using TestColumn = ColumnLowCardinalityT>; + auto col = std::make_shared(); + col->Append("abc"); + col->Append("def"); + col->Append("abc"); + col->Append("abc"); + col->Append(std::nullopt); + col->Append(std::nullopt); + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, ArrayTNullableString) { + using TestColumn = ColumnArrayT>; + auto col = std::make_shared(); + col->Append({std::nullopt, std::nullopt, std::nullopt}); + col->Append(std::vector>{"abc", std::nullopt}); + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, TupleTNullableString) { + using TestColumn = ColumnTupleT>; + auto col = std::make_shared(std::make_tuple(std::make_shared>())); + col->Append(std::make_tuple(std::nullopt)); + col->Append(std::make_tuple("abc")); + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, Map_TString_TNullableString) { + using Key = ColumnString; + using Value = ColumnNullableT; + using TestColumn = ColumnMapT; + auto col = std::make_shared(std::make_shared(), std::make_shared()); + { + std::map> value; + value["1"]= "one"; + value["2"]= std::nullopt; + col->Append(value); + } + { + std::map> value; + value["4"]= "one"; + value["2"]= std::nullopt; + col->Append(value); + } + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +TEST_P(RoundtripCase, Map_LowCardinalityTString_LowCardinalityTNullableString) { + using Key = ColumnLowCardinalityT; + using Value = ColumnLowCardinalityT>; + using TestColumn = ColumnMapT; + auto col = std::make_shared(std::make_shared(), std::make_shared()); + { + std::map> value; + value["1"]= "one"; + value["2"]= std::nullopt; + col->Append(value); + } + { + std::map> value; + value["4"]= "one"; + value["2"]= std::nullopt; + col->Append(value); + } + auto result_typed = WrapColumn(RoundtripColumnValues(*client_, col)); + EXPECT_TRUE(CompareRecursive(*col, *result_typed)); +} + +const auto LocalHostEndpoint = ClientOptions() + .SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost")) + .SetPort( getEnvOrDefault("CLICKHOUSE_PORT", "9000")) + .SetUser( getEnvOrDefault("CLICKHOUSE_USER", "default")) + .SetPassword( getEnvOrDefault("CLICKHOUSE_PASSWORD", "")) + .SetDefaultDatabase(getEnvOrDefault("CLICKHOUSE_DB", "default")); + +INSTANTIATE_TEST_SUITE_P( + Roundtrip, RoundtripCase, + ::testing::Values( + ClientOptions(LocalHostEndpoint) + .SetPingBeforeQuery(true) + .SetBakcwardCompatibilityFeatureLowCardinalityAsWrappedColumn(false), + ClientOptions(LocalHostEndpoint) + .SetPingBeforeQuery(false) + .SetCompressionMethod(CompressionMethod::LZ4) + .SetBakcwardCompatibilityFeatureLowCardinalityAsWrappedColumn(false) + )); diff --git a/ut/utils.h b/ut/utils.h index a0d136fd..f3740fc7 100644 --- a/ut/utils.h +++ b/ut/utils.h @@ -6,6 +6,7 @@ #include "utils_meta.h" #include "utils_comparison.h" +#include #include #include #include @@ -112,6 +113,15 @@ template inline ostream & operator<<(ostream & ostr, const tuple & t) { return printTuple(ostr, t); } + +template +inline ostream & operator<<(ostream & ostr, const optional & t) { + if (t.has_value()) { + return ostr << *t; + } else { + return ostr << "NULL"; + } +} }