Skip to content

Commit ad26fe9

Browse files
committed
ColumnNulableT
1 parent 5bc1985 commit ad26fe9

File tree

7 files changed

+520
-218
lines changed

7 files changed

+520
-218
lines changed

clickhouse/columns/lowcardinality.h

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,16 @@ class ColumnLowCardinality : public Column {
5353
UniqueItems unique_items_map_;
5454

5555
public:
56+
ColumnLowCardinality(ColumnLowCardinality&& col) = default;
5657
// c-tor makes a deep copy of the dictionary_column.
5758
explicit ColumnLowCardinality(ColumnRef dictionary_column);
5859
explicit ColumnLowCardinality(std::shared_ptr<ColumnNullable> dictionary_column);
60+
61+
template <typename T>
62+
explicit ColumnLowCardinality(std::shared_ptr<ColumnNullableT<T>> dictionary_column)
63+
: ColumnLowCardinality(dictionary_column->template As<ColumnNullable>())
64+
{}
65+
5966
~ColumnLowCardinality();
6067

6168
/// Appends another LowCardinality column to the end of this one, updating dictionary.
@@ -117,16 +124,23 @@ class ColumnLowCardinalityT : public ColumnLowCardinality {
117124
// Type this column takes as argument of Append and returns with At() and operator[]
118125
using ValueType = typename DictionaryColumnType::ValueType;
119126

127+
explicit ColumnLowCardinalityT(ColumnLowCardinality&& col)
128+
: ColumnLowCardinality(std::move(col))
129+
, typed_dictionary_(dynamic_cast<DictionaryColumnType &>(*GetDictionary()))
130+
, type_(GetTypeCode(typed_dictionary_))
131+
{
132+
}
133+
120134
template <typename ...Args>
121135
explicit ColumnLowCardinalityT(Args &&... args)
122136
: ColumnLowCardinalityT(std::make_shared<DictionaryColumnType>(std::forward<Args>(args)...))
123137
{}
124138

125139
// Create LC<T> column from existing T-column, making a deep copy of all contents.
126140
explicit ColumnLowCardinalityT(std::shared_ptr<DictionaryColumnType> dictionary_col)
127-
: ColumnLowCardinality(dictionary_col),
128-
typed_dictionary_(dynamic_cast<DictionaryColumnType &>(*GetDictionary())),
129-
type_(typed_dictionary_.Type()->GetCode())
141+
: ColumnLowCardinality(dictionary_col)
142+
, typed_dictionary_(dynamic_cast<DictionaryColumnType &>(*GetDictionary()))
143+
, type_(GetTypeCode(typed_dictionary_))
130144
{}
131145

132146
/// Extended interface to simplify reading/adding individual items.
@@ -145,7 +159,15 @@ class ColumnLowCardinalityT : public ColumnLowCardinality {
145159
using ColumnLowCardinality::Append;
146160

147161
inline void Append(const ValueType & value) {
148-
AppendUnsafe(ItemView{type_, value});
162+
if constexpr (IsNullable<WrappedColumnType>) {
163+
if (value.has_value()) {
164+
AppendUnsafe(ItemView{type_, *value});
165+
} else {
166+
AppendUnsafe(ItemView{});
167+
}
168+
} else {
169+
AppendUnsafe(ItemView{type_, value});
170+
}
149171
}
150172

151173
template <typename T>
@@ -154,6 +176,41 @@ class ColumnLowCardinalityT : public ColumnLowCardinality {
154176
Append(item);
155177
}
156178
}
179+
180+
/** Create a ColumnLowCardinalityT from a ColumnLowCardinality, without copying data and offsets, but by
181+
* 'stealing' those from `col`.
182+
*
183+
* Ownership of column internals is transferred to returned object, original (argument) object
184+
* MUST NOT BE USED IN ANY WAY, it is only safe to dispose it.
185+
*
186+
* Throws an exception if `col` is of wrong type, it is safe to use original col in this case.
187+
* This is a static method to make such conversion verbose.
188+
*/
189+
static auto Wrap(ColumnLowCardinality&& col) {
190+
return std::make_shared<ColumnLowCardinalityT<WrappedColumnType>>(std::move(col));
191+
}
192+
193+
static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast<ColumnLowCardinality&&>(col))); }
194+
195+
// Helper to simplify integration with other APIs
196+
static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict<ColumnLowCardinality>())); }
197+
198+
ColumnRef Slice(size_t begin, size_t size) const override {
199+
return Wrap(ColumnLowCardinality::Slice(begin, size));
200+
}
201+
202+
ColumnRef CloneEmpty() const override { return Wrap(ColumnLowCardinality::CloneEmpty()); }
203+
204+
private:
205+
206+
template <typename T>
207+
static auto GetTypeCode(T& column) {
208+
if constexpr (IsNullable<T>) {
209+
return GetTypeCode(*column.Nested()->template AsStrict<typename T::NestedColumnType>());
210+
} else {
211+
return column.Type()->GetCode();
212+
}
213+
}
157214
};
158215

159216
}

clickhouse/columns/nullable.h

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include "column.h"
44
#include "numeric.h"
55

6+
#include <optional>
7+
68
namespace clickhouse {
79

810
/**
@@ -42,7 +44,7 @@ class ColumnNullable : public Column {
4244

4345
/// Clear column data .
4446
void Clear() override;
45-
47+
4648
/// Returns count of rows in the column.
4749
size_t Size() const override;
4850

@@ -58,4 +60,91 @@ class ColumnNullable : public Column {
5860
std::shared_ptr<ColumnUInt8> nulls_;
5961
};
6062

63+
template <typename ColumnType>
64+
class ColumnNullableT : public ColumnNullable {
65+
public:
66+
using NestedColumnType = ColumnType;
67+
using ValueType = std::optional<std::decay_t<decltype(std::declval<NestedColumnType>().At(0))>>;
68+
69+
ColumnNullableT(std::shared_ptr<NestedColumnType> data, std::shared_ptr<ColumnUInt8> nulls)
70+
: ColumnNullable(data, nulls)
71+
, typed_nested_data_(data)
72+
{}
73+
74+
explicit ColumnNullableT(std::shared_ptr<NestedColumnType> data)
75+
: ColumnNullableT(data, FillNulls(data->Size()))
76+
{}
77+
78+
template <typename ...Args>
79+
explicit ColumnNullableT(Args &&... args)
80+
: ColumnNullableT(std::make_shared<NestedColumnType>(std::forward<Args>(args)...))
81+
{}
82+
83+
inline ValueType At(size_t index) const {
84+
return IsNull(index) ? ValueType{} : ValueType{typed_nested_data_->At(index)};
85+
}
86+
87+
inline ValueType operator[](size_t index) const { return At(index); }
88+
89+
/// Appends content of given column to the end of current one.
90+
void Append(ColumnRef column) override {
91+
ColumnNullable::Append(std::move(column));
92+
}
93+
94+
inline void Append(ValueType value) {
95+
ColumnNullable::Append(!value.has_value());
96+
if (value.has_value()) {
97+
typed_nested_data_->Append(std::move(*value));
98+
} else {
99+
typed_nested_data_->Append(typename ValueType::value_type{});
100+
}
101+
}
102+
103+
/** Create a ColumnNullableT from a ColumnNullable, without copying data and offsets, but by
104+
* 'stealing' those from `col`.
105+
*
106+
* Ownership of column internals is transferred to returned object, original (argument) object
107+
* MUST NOT BE USED IN ANY WAY, it is only safe to dispose it.
108+
*
109+
* Throws an exception if `col` is of wrong type, it is safe to use original col in this case.
110+
* This is a static method to make such conversion verbose.
111+
*/
112+
static auto Wrap(ColumnNullable&& col) {
113+
return std::make_shared<ColumnNullableT<NestedColumnType>>(
114+
col.Nested()->AsStrict<NestedColumnType>(),
115+
col.Nulls()->AsStrict<ColumnUInt8>()) ;
116+
}
117+
118+
static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast<ColumnNullable&&>(col))); }
119+
120+
// Helper to simplify integration with other APIs
121+
static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict<ColumnNullable>())); }
122+
123+
ColumnRef Slice(size_t begin, size_t size) const override {
124+
return Wrap(ColumnNullable::Slice(begin, size));
125+
}
126+
127+
ColumnRef CloneEmpty() const override { return Wrap(ColumnNullable::CloneEmpty()); }
128+
129+
void Swap(Column& other) override {
130+
auto& col = dynamic_cast<ColumnNullableT<NestedColumnType>&>(other);
131+
typed_nested_data_.swap(col.typed_nested_data_);
132+
ColumnNullable::Swap(other);
133+
}
134+
135+
private:
136+
static inline auto FillNulls(size_t n){
137+
auto result = std::make_shared<ColumnUInt8>();
138+
for (size_t i = 0; i < n; ++i) {
139+
result->Append(0);
140+
}
141+
return result;
142+
}
143+
144+
std::shared_ptr<NestedColumnType> typed_nested_data_;
145+
};
146+
147+
template <typename T>
148+
constexpr bool IsNullable = std::is_base_of_v<ColumnNullable, T>;
149+
61150
}

ut/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ SET ( clickhouse-cpp-ut-src
2020
CreateColumnByType_ut.cpp
2121
Column_ut.cpp
2222
roundtrip_column.cpp
23+
roundtrip_tests.cpp
2324

2425
utils.cpp
2526
value_generators.cpp

ut/Column_ut.cpp

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,28 @@ class GenericColumnTest : public testing::Test {
106106

107107
return std::tuple{column, values};
108108
}
109+
110+
static std::optional<std::string> SkipTest(clickhouse::Client& client) {
111+
if constexpr (std::is_same_v<ColumnType, ColumnDate32>) {
112+
// Date32 first appeared in v21.9.2.17-stable
113+
const auto server_info = client.GetServerInfo();
114+
if (versionNumber(server_info) < versionNumber(21, 9)) {
115+
std::stringstream buffer;
116+
buffer << "Date32 is available since v21.9.2.17-stable and can't be tested against server: " << server_info;
117+
return buffer.str();
118+
}
119+
}
120+
121+
if constexpr (std::is_same_v<ColumnType, ColumnInt128>) {
122+
const auto server_info = client.GetServerInfo();
123+
if (versionNumber(server_info) < versionNumber(21, 7)) {
124+
std::stringstream buffer;
125+
buffer << "ColumnInt128 is available since v21.7.2.7-stable and can't be tested against server: " << server_info;
126+
return buffer.str();
127+
}
128+
}
129+
return std::nullopt;
130+
}
109131
};
110132

111133
using ValueColumns = ::testing::Types<
@@ -279,21 +301,33 @@ TYPED_TEST(GenericColumnTest, RoundTrip) {
279301

280302
clickhouse::Client client(LocalHostEndpoint);
281303

282-
if constexpr (std::is_same_v<typename TestFixture::ColumnType, ColumnDate32>) {
283-
// Date32 first appeared in v21.9.2.17-stable
284-
const auto server_info = client.GetServerInfo();
285-
if (versionNumber(server_info) < versionNumber(21, 9)) {
286-
GTEST_SKIP() << "Date32 is available since v21.9.2.17-stable and can't be tested against server: " << server_info;
287-
}
304+
if (auto message = this->SkipTest(client)) {
305+
GTEST_SKIP() << *message;
288306
}
289307

290-
if constexpr (std::is_same_v<typename TestFixture::ColumnType, ColumnInt128>) {
291-
const auto server_info = client.GetServerInfo();
292-
if (versionNumber(server_info) < versionNumber(21, 7)) {
293-
GTEST_SKIP() << "ColumnInt128 is available since v21.7.2.7-stable and can't be tested against server: " << server_info;
308+
auto result_typed = RoundtripColumnValues(client, column)->template AsStrict<typename TestFixture::ColumnType>();
309+
EXPECT_TRUE(CompareRecursive(*column, *result_typed));
310+
}
311+
312+
TYPED_TEST(GenericColumnTest, NulableT_RoundTrip) {
313+
using NullableType = ColumnNullableT<TypeParam>;
314+
auto column = std::make_shared<NullableType>(this->MakeColumn());
315+
auto values = this->GenerateValues(100);
316+
FromVectorGenerator<bool> is_null({true, false});
317+
for (size_t i = 0; i < values.size(); ++i) {
318+
if (is_null(i)) {
319+
column->Append(std::nullopt);
320+
} else {
321+
column->Append(values[i]);
294322
}
295323
}
296324

297-
auto result_typed = RoundtripColumnValues(client, column)->template AsStrict<typename TestFixture::ColumnType>();
325+
clickhouse::Client client(LocalHostEndpoint);
326+
327+
if (auto message = this->SkipTest(client)) {
328+
GTEST_SKIP() << *message;
329+
}
330+
331+
auto result_typed = WrapColumn<NullableType>(RoundtripColumnValues(client, column));
298332
EXPECT_TRUE(CompareRecursive(*column, *result_typed));
299333
}

0 commit comments

Comments
 (0)