#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils.h" #include #include #include namespace { using namespace clickhouse; template std::shared_ptr Create2DArray(const ValuesContainer& values) { auto result = std::make_shared(std::make_shared(std::make_shared())); for (size_t i = 0; i < values.size(); ++i) { auto array_col = std::make_shared(std::make_shared()); for (size_t j = 0; j < values[i].size(); ++j) array_col->AppendAsColumn(std::make_shared(values[i][j])); result->AppendAsColumn(array_col); } return result; } template std::shared_ptr CreateArray(const ValuesContainer& values) { auto result = std::make_shared(std::make_shared()); for (size_t i = 0; i < values.size(); ++i) { result->AppendAsColumn(std::make_shared(values[i])); } return result; } } TEST(ColumnArray, Append) { auto arr1 = std::make_shared(std::make_shared()); auto arr2 = std::make_shared(std::make_shared()); auto id = std::make_shared(); id->Append(1); arr1->AppendAsColumn(id); id->Append(3); arr2->AppendAsColumn(id); arr1->Append(arr2); auto col = arr1->GetAsColumn(1); ASSERT_EQ(arr1->Size(), 2u); ASSERT_EQ(col->As()->At(0), 1u); ASSERT_EQ(col->As()->At(1), 3u); } TEST(ColumnArray, ArrayOfDecimal) { auto column = std::make_shared(18, 10); auto array = std::make_shared(column->CloneEmpty()); column->Append("1"); column->Append("2"); EXPECT_EQ(2u, column->Size()); array->AppendAsColumn(column); ASSERT_EQ(1u, array->Size()); EXPECT_EQ(2u, array->GetAsColumn(0)->Size()); } TEST(ColumnArray, GetAsColumn) { // Verify that result of GetAsColumn // - is of proper type // - has expected length // - values match ones predefined ones const std::vector> values = { {1u, 2u, 3u}, {4u, 5u, 6u, 7u, 8u, 9u}, {0u}, {}, {13, 14} }; auto array = CreateArray(values); ASSERT_EQ(values.size(), array->Size()); for (size_t i = 0; i < values.size(); ++i) { auto row = array->GetAsColumn(i); std::shared_ptr typed_row; EXPECT_NO_THROW(typed_row = row->As()); EXPECT_TRUE(CompareRecursive(values[i], *typed_row)); } EXPECT_THROW(array->GetAsColumn(array->Size()), ValidationError); EXPECT_THROW(array->GetAsColumn(array->Size() + 1), ValidationError); } TEST(ColumnArray, Slice) { const std::vector> values = { {1u, 2u, 3u}, {4u, 5u, 6u, 7u, 8u, 9u}, {0u}, {}, {13, 14, 15} }; std::shared_ptr untyped_array = CreateArray(values); for (size_t i = 0; i < values.size() - 1; ++i) { auto slice = untyped_array->Slice(i, 1)->AsStrict(); EXPECT_EQ(1u, slice->Size()); EXPECT_TRUE(CompareRecursive(values[i], *slice->GetAsColumnTyped(0))); } EXPECT_EQ(0u, untyped_array->Slice(0, 0)->Size()); EXPECT_ANY_THROW(untyped_array->Slice(values.size(), 1)); EXPECT_ANY_THROW(untyped_array->Slice(0, values.size() + 1)); } TEST(ColumnArray, Slice_2D) { // Verify that ColumnArray::Slice on 2D Array produces a 2D Array of proper type, size and contents. // Also check that slices can be of any size. const std::vector>> values = { {{1u, 2u}, {3u}}, {{4u}, {5u, 6u, 7u}, {8u, 9u}, {}}, {{0u}}, {{}}, {{13}, {14, 15}} }; std::shared_ptr untyped_array = Create2DArray(values); for (size_t i = 0; i < values.size() - 1; ++i) { for (size_t slice_size = 0; slice_size < values.size() - i; ++slice_size) { auto slice = untyped_array->Slice(i, slice_size)->AsStrict(); EXPECT_EQ(slice_size, slice->Size()); for (size_t slice_row = 0; slice_row < slice_size; ++slice_row) { SCOPED_TRACE(::testing::Message() << "i: " << i << " slice_size:" << slice_size << " row:" << slice_row); auto val = slice->GetAsColumnTyped(slice_row); ASSERT_EQ(values[i + slice_row].size(), val->Size()); for (size_t j = 0; j < values[i + slice_row].size(); ++j) { ASSERT_TRUE(CompareRecursive(values[i + slice_row][j], *val->GetAsColumnTyped(j))); } } } } } template auto AppendRowAndTest(ArrayTSpecialization& array, const RowValuesContainer& values) { const size_t prev_size = array.Size(); array.Append(values); EXPECT_EQ(prev_size + 1u, array.Size()); EXPECT_TRUE(CompareRecursive(values, array.At(prev_size))); EXPECT_TRUE(CompareRecursive(values, array[prev_size])); // Check that both subscript and At() work properly. const auto & new_row = array.At(prev_size); for (size_t i = 0; i < values.size(); ++i) { EXPECT_TRUE(CompareRecursive(*(values.begin() + i), new_row[i])) << " at pos: " << i; EXPECT_TRUE(CompareRecursive(*(values.begin() + i), new_row.At(i))) << " at pos: " << i; } EXPECT_THROW(new_row.At(new_row.size() + 1), clickhouse::ValidationError); }; template auto CreateAndTestColumnArrayT(const AllValuesContainer& all_values) { auto array = std::make_shared>(); for (const auto & row : all_values) { EXPECT_NO_FATAL_FAILURE(AppendRowAndTest(*array, row)); } EXPECT_TRUE(CompareRecursive(all_values, *array)); EXPECT_THROW(array->At(array->Size() + 1), clickhouse::ValidationError); return array; } TEST(ColumnArrayT, SimpleUInt64) { auto array = std::make_shared>(); array->Append({0, 1, 2}); ASSERT_EQ(1u, array->Size()); EXPECT_EQ(0u, array->At(0).At(0)); EXPECT_EQ(1u, (*array)[0][1]); EXPECT_THROW(array->At(2), ValidationError); EXPECT_THROW(array->At(0).At(3), ValidationError); EXPECT_THROW((*array)[0].At(3), ValidationError); } TEST(ColumnArrayT, SimpleFixedString) { using namespace std::literals; auto array = std::make_shared>(6); array->Append({"hello", "world"}); // Additional \0 since strings are padded from right with zeros in FixedString(6). EXPECT_EQ("hello\0"sv, array->At(0).At(0)); auto row = array->At(0); EXPECT_EQ("hello\0"sv, row.At(0)); EXPECT_EQ(6u, row[0].length()); EXPECT_EQ("hello", row[0].substr(0, 5)); EXPECT_EQ("world\0"sv, (*array)[0][1]); } TEST(ColumnArrayT, SimpleUInt64_2D) { // Nested 2D-arrays are supported too: auto array = std::make_shared>>(); array->Append(std::vector>{{0}, {1, 1}, {2, 2, 2}}); ASSERT_EQ(1u, array->Size()); EXPECT_EQ(0u, array->At(0).At(0).At(0)); EXPECT_EQ(1u, (*array)[0][1][1]); EXPECT_THROW(array->At(2), ValidationError); } TEST(ColumnArrayT, UInt64) { // Check inserting\reading back data from clickhouse::ColumnArrayT const std::vector> values = { {1u, 2u, 3u}, {4u, 5u, 6u, 7u, 8u, 9u}, {0u}, {}, {13, 14} }; CreateAndTestColumnArrayT(values); } TEST(ColumnArrayT, UInt64_2D) { // Check inserting\reading back data from 2D array: ColumnArrayT> const std::vector>> values = { {{1u, 2u, 3u}, {4u, 5u, 6u}}, {{4u, 5u, 6u}, {7u, 8u, 9u}, {10u, 11u}}, {{}, {}}, {}, {{13, 14}, {}} }; auto array_ptr = CreateAndTestColumnArrayT>(values); const auto & array = *array_ptr; { EXPECT_EQ(0u, array[3].size()); // operator[] doesn't check bounds. // On empty rows attempt to access out-of-bound elements // would actually cause access to the elements of the next row. // hence non-0 value of `array[3][0].size()`, // it is effectively the same as `array[3 + 1][0].size()` EXPECT_EQ(2u, array[3][0].size()); EXPECT_EQ(14u, array[3][0][1]); EXPECT_EQ(0u, array[3][1].size()); } } TEST(ColumnArrayT, Wrap_UInt64) { // Check that ColumnArrayT can wrap a pre-existing ColumnArray. const std::vector> values = { {1u, 2u, 3u}, {4u, 5u, 6u, 7u, 8u, 9u}, {0u}, {}, {13, 14} }; auto wrapped_array = ColumnArrayT::Wrap(CreateArray(values)); const auto & array = *wrapped_array; EXPECT_TRUE(CompareRecursive(values, array)); } TEST(ColumnArrayT, Wrap_UInt64_2D) { // Check that ColumnArrayT can wrap a pre-existing ColumnArray. const std::vector>> values = { {{1u, 2u}, {3u}}, {{4u}, {5u, 6u, 7u}, {8u, 9u}, {}}, {{0u}}, {{}}, {{13}, {14, 15}} }; auto wrapped_array = ColumnArrayT>::Wrap(Create2DArray(values)); const auto & array = *wrapped_array; EXPECT_TRUE(CompareRecursive(values, array)); } TEST(ColumnArrayT, left_value_no_move) { std::string value0 = "000000000000000000"; std::string value1 = "111111111111111111"; std::string value2 = "222222222222222222"; std::vector> all_values{ { value0, value1, value2}, { value0, value1, value2}, { value0, value1, value2} }; size_t origin_size = 3; auto array = std::make_shared>>(); array->Append(all_values); EXPECT_EQ(3u, (*array)[0][0].size()); EXPECT_EQ(3u, (*array)[0][1].size()); EXPECT_EQ(3u, (*array)[0][2].size()); EXPECT_EQ(value0, (*array)[0][0][0]); EXPECT_EQ(value1, (*array)[0][1][1]); EXPECT_EQ(value2, (*array)[0][2][2]); for (const auto& values : all_values) { EXPECT_EQ(origin_size, values.size()); } EXPECT_EQ(origin_size, all_values.size()); for (const auto& values : all_values) { EXPECT_EQ(values[0], value0); EXPECT_EQ(values[1], value1); EXPECT_EQ(values[2], value2); } } TEST(ColumnArrayT, right_value_move) { std::string value0 = "000000000000000000"; std::string value1 = "111111111111111111"; std::string value2 = "222222222222222222"; std::vector> all_values{ { value0, value1, value2}, { value0, value1, value2}, { value0, value1, value2} }; auto array = std::make_shared>>(); array->Append(std::move(all_values)); EXPECT_EQ(3u, (*array)[0][0].size()); EXPECT_EQ(3u, (*array)[0][1].size()); EXPECT_EQ(3u, (*array)[0][2].size()); EXPECT_EQ(value0, (*array)[0][0][0]); EXPECT_EQ(value1, (*array)[0][1][1]); EXPECT_EQ(value2, (*array)[0][2][2]); // Here we don't care about the size of the container from which all values were moved-out. //EXPECT_EQ(0u, all_values.size()); } TEST(ColumnArrayT, const_right_value_no_move) { std::string value0 = "000000000000000000"; std::string value1 = "111111111111111111"; std::string value2 = "222222222222222222"; const std::vector> all_values{ { value0, value1, value2}, { value0, value1, value2}, { value0, value1, value2} }; size_t origin_size = 3; auto array = std::make_shared>>(); array->Append(std::move(all_values)); EXPECT_EQ(3u, (*array)[0][0].size()); EXPECT_EQ(3u, (*array)[0][1].size()); EXPECT_EQ(3u, (*array)[0][2].size()); EXPECT_EQ(value0, (*array)[0][0][0]); EXPECT_EQ(value1, (*array)[0][1][1]); EXPECT_EQ(value2, (*array)[0][2][2]); for (const auto& values : all_values) { EXPECT_EQ(origin_size, values.size()); } EXPECT_EQ(origin_size, all_values.size()); for (const auto& values : all_values) { EXPECT_EQ(values[0], value0); EXPECT_EQ(values[1], value1); EXPECT_EQ(values[2], value2); } }