Skip to content

Commit 338cc06

Browse files
committed
Add typed tests for LowCardinality over all supported inner types
Cover String, FixedString, Int8-Int128, UInt8-UInt128, Float32/64, Date, Date32, DateTime, IPv4, IPv6 and UUID, each both as LowCardinality(T) and LowCardinality(Nullable(T)): fill a base column, append it to a generic ColumnLowCardinality, insert into the DB, select as ColumnLowCardinalityT<T> and verify the data matches row by row. Also add the ValueType alias to ColumnUUID, which is required to instantiate ColumnLowCardinalityT<ColumnUUID>.
1 parent f88228c commit 338cc06

4 files changed

Lines changed: 213 additions & 0 deletions

File tree

clickhouse/columns/uuid.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ namespace clickhouse {
1212
*/
1313
class ColumnUUID : public Column {
1414
public:
15+
using ValueType = UUID;
16+
1517
ColumnUUID();
1618

1719
explicit ColumnUUID(ColumnRef data);

ut/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ cc_test(
105105
"connection_failed_client_test.cpp",
106106
"connection_failed_client_test.h",
107107
"low_cardinality_nullable_tests.cpp",
108+
"low_cardinality_types_ut.cpp",
108109
"performance_tests.cpp",
109110
"readonly_client_test.cpp",
110111
"readonly_client_test.h",

ut/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ SET ( clickhouse-cpp-ut-src
1010
column_as_ut.cpp
1111
column_array_ut.cpp
1212
itemview_ut.cpp
13+
low_cardinality_types_ut.cpp
1314
socket_ut.cpp
1415
stream_ut.cpp
1516
type_parser_ut.cpp

ut/low_cardinality_types_ut.cpp

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// Server round-trip tests for LowCardinality over all supported inner types:
2+
//
3+
// String, FixedString (originally supported)
4+
// Int8/16/32/64/128, UInt8/16/32/64/128, Float32/64,
5+
// Date, Date32, DateTime, IPv4, IPv6, UUID (added later)
6+
//
7+
// Every type is tested twice, once as LowCardinality(T) and once as
8+
// LowCardinality(Nullable(T)), with the same scenario:
9+
//
10+
// 1. Create a column of the base type and fill it with values.
11+
// 2. Create a generic ColumnLowCardinality and append the data from the column of step 1.
12+
// 3. Send the column of step 2 through a ClickHouse server (INSERT + SELECT) and
13+
// convert the returned column to ColumnLowCardinalityT<TheType>.
14+
// 4. Expect the data of step 1 to be equal to the data of step 3.
15+
16+
#include <clickhouse/client.h>
17+
#include <clickhouse/columns/date.h>
18+
#include <clickhouse/columns/ip4.h>
19+
#include <clickhouse/columns/ip6.h>
20+
#include <clickhouse/columns/lowcardinality.h>
21+
#include <clickhouse/columns/nullable.h>
22+
#include <clickhouse/columns/numeric.h>
23+
#include <clickhouse/columns/string.h>
24+
#include <clickhouse/columns/uuid.h>
25+
26+
#include <gtest/gtest.h>
27+
28+
#include "ut/roundtrip_column.h"
29+
#include "ut/utils.h"
30+
#include "ut/utils_comparison.h"
31+
#include "ut/value_generators.h"
32+
33+
#include <algorithm>
34+
#include <cmath>
35+
#include <ctime>
36+
#include <memory>
37+
#include <optional>
38+
#include <type_traits>
39+
#include <vector>
40+
41+
namespace {
42+
43+
using namespace clickhouse;
44+
45+
// ColumnDate32 (unlike ColumnDate) also supports pre-epoch dates,
46+
// extend the common Date values with one.
47+
std::vector<std::time_t> MakeDates32AsSeconds() {
48+
auto result = MakeDates<std::time_t>();
49+
result.push_back(std::time_t(-2) * 86400);
50+
return result;
51+
}
52+
53+
// A single test case: base column type + value generator (+ optional column
54+
// constructor arguments, e.g. the width of a FixedString).
55+
template <typename ColumnType, auto ValueGenerator, size_t ...ConstructorArgs>
56+
struct TestCase {
57+
using BaseColumn = ColumnType;
58+
59+
static auto MakeColumn() { return std::make_shared<ColumnType>(ConstructorArgs...); }
60+
61+
static auto MakeValues() {
62+
auto values = ValueGenerator();
63+
64+
// The floating-point generators produce NaNs, which never compare
65+
// equal to themselves, drop them to keep plain EXPECT_EQ verification.
66+
using ValueType = typename decltype(values)::value_type;
67+
if constexpr (std::is_floating_point_v<ValueType>) {
68+
values.erase(std::remove_if(
69+
values.begin(),
70+
values.end(),
71+
[](ValueType v) {
72+
return std::isnan(v);
73+
}),
74+
values.end());
75+
}
76+
77+
// Duplicate a value so that the dictionary/deduplication code path is exercised too.
78+
values.push_back(values.front());
79+
return values;
80+
}
81+
};
82+
83+
} // namespace
84+
85+
template <typename Case>
86+
class LowCardinalityTypedTest : public ::testing::Test {
87+
protected:
88+
void SetUp() override {
89+
client_ = std::make_unique<Client>(
90+
ClientOptions()
91+
.SetHost( getEnvOrDefault("CLICKHOUSE_HOST", "localhost"))
92+
.SetPort( getEnvOrDefault<size_t>("CLICKHOUSE_PORT", "9000"))
93+
.SetUser( getEnvOrDefault("CLICKHOUSE_USER", "default"))
94+
.SetPassword( getEnvOrDefault("CLICKHOUSE_PASSWORD", ""))
95+
.SetDefaultDatabase(getEnvOrDefault("CLICKHOUSE_DB", "default"))
96+
.SetPingBeforeQuery(true));
97+
}
98+
99+
void TearDown() override {
100+
client_.reset();
101+
}
102+
103+
std::unique_ptr<Client> client_;
104+
};
105+
106+
using LowCardinalityTestCases = ::testing::Types<
107+
TestCase<ColumnString, &MakeStrings>,
108+
TestCase<ColumnFixedString, &MakeFixedStrings<4>, 4>,
109+
110+
TestCase<ColumnInt8, &MakeNumbers<int8_t>>,
111+
TestCase<ColumnInt16, &MakeNumbers<int16_t>>,
112+
TestCase<ColumnInt32, &MakeNumbers<int32_t>>,
113+
TestCase<ColumnInt64, &MakeNumbers<int64_t>>,
114+
115+
TestCase<ColumnUInt8, &MakeNumbers<uint8_t>>,
116+
TestCase<ColumnUInt16, &MakeNumbers<uint16_t>>,
117+
TestCase<ColumnUInt32, &MakeNumbers<uint32_t>>,
118+
TestCase<ColumnUInt64, &MakeNumbers<uint64_t>>,
119+
120+
TestCase<ColumnInt128, &MakeInt128s>,
121+
TestCase<ColumnUInt128, &MakeUInt128s>,
122+
123+
TestCase<ColumnFloat32, &MakeNumbers<float>>,
124+
TestCase<ColumnFloat64, &MakeNumbers<double>>,
125+
126+
TestCase<ColumnDate, &MakeDates<std::time_t>>,
127+
TestCase<ColumnDate32, &MakeDates32AsSeconds>,
128+
TestCase<ColumnDateTime, &MakeDateTimes>,
129+
130+
TestCase<ColumnIPv4, &MakeIPv4s>,
131+
TestCase<ColumnIPv6, &MakeIPv6s>,
132+
TestCase<ColumnUUID, &MakeUUIDs>
133+
>;
134+
135+
TYPED_TEST_SUITE(LowCardinalityTypedTest, LowCardinalityTestCases);
136+
137+
// LowCardinality(T)
138+
TYPED_TEST(LowCardinalityTypedTest, RoundtripAndReadThroughTypedView) {
139+
using Case = TypeParam;
140+
using BaseColumn = typename Case::BaseColumn;
141+
using TypedLowCardinality = ColumnLowCardinalityT<BaseColumn>;
142+
143+
// Step 1: base column with values.
144+
auto source = Case::MakeColumn();
145+
for (const auto& value : Case::MakeValues()) {
146+
source->Append(value);
147+
}
148+
ASSERT_GT(source->Size(), 0u);
149+
150+
// Step 2: generic LowCardinality column, append data of the base column.
151+
auto low_cardinality = std::make_shared<ColumnLowCardinality>(Case::MakeColumn());
152+
low_cardinality->Append(source);
153+
ASSERT_EQ(source->Size(), low_cardinality->Size());
154+
155+
// Step 3: send through the server (INSERT + SELECT) and convert the
156+
// returned column to typed ColumnLowCardinalityT<BaseColumn>.
157+
auto returned = RoundtripColumnValues(*this->client_, low_cardinality);
158+
auto typed = returned->template AsStrict<TypedLowCardinality>();
159+
ASSERT_EQ(source->Size(), typed->Size());
160+
161+
// Step 4: data of step 1 must equal data of step 3.
162+
for (size_t i = 0; i < source->Size(); ++i) {
163+
SCOPED_TRACE(::testing::Message("at row ") << i);
164+
EXPECT_EQ(source->At(i), typed->At(i));
165+
EXPECT_EQ((*source)[i], (*typed)[i]);
166+
}
167+
}
168+
169+
// LowCardinality(Nullable(T))
170+
TYPED_TEST(LowCardinalityTypedTest, RoundtripNullableAndReadThroughTypedView) {
171+
using Case = TypeParam;
172+
using BaseColumn = typename Case::BaseColumn;
173+
using NullableColumn = ColumnNullableT<BaseColumn>;
174+
using TypedLowCardinality = ColumnLowCardinalityT<NullableColumn>;
175+
176+
// Step 1: nullable base column with values interleaved with NULLs.
177+
auto source = std::make_shared<NullableColumn>(Case::MakeColumn());
178+
for (const auto& value : Case::MakeValues()) {
179+
source->Append(typename NullableColumn::ValueType{value});
180+
source->Append(std::nullopt);
181+
}
182+
ASSERT_GT(source->Size(), 0u);
183+
184+
// Step 2: generic LowCardinality column over a Nullable dictionary,
185+
// append data of the nullable column.
186+
auto nullable = std::make_shared<NullableColumn>(Case::MakeColumn());
187+
auto low_cardinality = std::make_shared<ColumnLowCardinality>(nullable);
188+
low_cardinality->Append(source);
189+
ASSERT_EQ(source->Size(), low_cardinality->Size());
190+
191+
// Step 3: send through the server (INSERT + SELECT) and convert the
192+
// returned column to typed ColumnLowCardinalityT<ColumnNullableT<BaseColumn>>.
193+
auto returned = RoundtripColumnValues(*this->client_, low_cardinality);
194+
auto typed = returned->template AsStrict<TypedLowCardinality>();
195+
ASSERT_EQ(source->Size(), typed->Size());
196+
197+
// Step 4: data of step 1 must equal data of step 3, NULLs included.
198+
for (size_t i = 0; i < source->Size(); ++i) {
199+
SCOPED_TRACE(::testing::Message("at row ") << i);
200+
201+
const auto expected = source->At(i);
202+
const auto actual = typed->At(i);
203+
204+
ASSERT_EQ(expected.has_value(), actual.has_value());
205+
if (expected.has_value()) {
206+
EXPECT_EQ(*expected, *actual);
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)