Skip to content

ColumnNulableT and wrap for LowCardinalityT #269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions clickhouse/columns/lowcardinality.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<ColumnNullable> dictionary_column);

template <typename T>
explicit ColumnLowCardinality(std::shared_ptr<ColumnNullableT<T>> dictionary_column)
: ColumnLowCardinality(dictionary_column->template As<ColumnNullable>())
{}

~ColumnLowCardinality();

/// Appends another LowCardinality column to the end of this one, updating dictionary.
Expand Down Expand Up @@ -117,16 +124,23 @@ 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<DictionaryColumnType &>(*GetDictionary()))
, type_(GetTypeCode(typed_dictionary_))
{
}

template <typename ...Args>
explicit ColumnLowCardinalityT(Args &&... args)
: ColumnLowCardinalityT(std::make_shared<DictionaryColumnType>(std::forward<Args>(args)...))
{}

// Create LC<T> column from existing T-column, making a deep copy of all contents.
explicit ColumnLowCardinalityT(std::shared_ptr<DictionaryColumnType> dictionary_col)
: ColumnLowCardinality(dictionary_col),
typed_dictionary_(dynamic_cast<DictionaryColumnType &>(*GetDictionary())),
type_(typed_dictionary_.Type()->GetCode())
: ColumnLowCardinality(dictionary_col)
, typed_dictionary_(dynamic_cast<DictionaryColumnType &>(*GetDictionary()))
, type_(GetTypeCode(typed_dictionary_))
{}

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

inline void Append(const ValueType & value) {
AppendUnsafe(ItemView{type_, value});
if constexpr (IsNullable<WrappedColumnType>) {
if (value.has_value()) {
AppendUnsafe(ItemView{type_, *value});
} else {
AppendUnsafe(ItemView{});
}
} else {
AppendUnsafe(ItemView{type_, value});
}
}

template <typename T>
Expand All @@ -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<ColumnLowCardinalityT<WrappedColumnType>>(std::move(col));
}

static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast<ColumnLowCardinality&&>(col))); }

// Helper to simplify integration with other APIs
static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict<ColumnLowCardinality>())); }

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 <typename T>
static auto GetTypeCode(T& column) {
if constexpr (IsNullable<T>) {
return GetTypeCode(*column.Nested()->template AsStrict<typename T::NestedColumnType>());
} else {
return column.Type()->GetCode();
}
}
};

}
91 changes: 90 additions & 1 deletion clickhouse/columns/nullable.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "column.h"
#include "numeric.h"

#include <optional>

namespace clickhouse {

/**
Expand Down Expand Up @@ -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;

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

template <typename ColumnType>
class ColumnNullableT : public ColumnNullable {
public:
using NestedColumnType = ColumnType;
using ValueType = std::optional<std::decay_t<decltype(std::declval<NestedColumnType>().At(0))>>;

ColumnNullableT(std::shared_ptr<NestedColumnType> data, std::shared_ptr<ColumnUInt8> nulls)
: ColumnNullable(data, nulls)
, typed_nested_data_(data)
{}

explicit ColumnNullableT(std::shared_ptr<NestedColumnType> data)
: ColumnNullableT(data, FillNulls(data->Size()))
{}

template <typename ...Args>
explicit ColumnNullableT(Args &&... args)
: ColumnNullableT(std::make_shared<NestedColumnType>(std::forward<Args>(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<ColumnNullableT<NestedColumnType>>(
col.Nested()->AsStrict<NestedColumnType>(),
col.Nulls()->AsStrict<ColumnUInt8>()) ;
}

static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast<ColumnNullable&&>(col))); }

// Helper to simplify integration with other APIs
static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict<ColumnNullable>())); }

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<ColumnNullableT<NestedColumnType>&>(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<ColumnUInt8>();
for (size_t i = 0; i < n; ++i) {
result->Append(0);
}
return result;
}

std::shared_ptr<NestedColumnType> typed_nested_data_;
};

template <typename T>
constexpr bool IsNullable = std::is_base_of_v<ColumnNullable, T>;

}
1 change: 1 addition & 0 deletions ut/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 45 additions & 11 deletions ut/Column_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,28 @@ class GenericColumnTest : public testing::Test {

return std::tuple{column, values};
}

static std::optional<std::string> SkipTest(clickhouse::Client& client) {
if constexpr (std::is_same_v<ColumnType, ColumnDate32>) {
// 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<ColumnType, ColumnInt128>) {
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<
Expand Down Expand Up @@ -279,21 +301,33 @@ TYPED_TEST(GenericColumnTest, RoundTrip) {

clickhouse::Client client(LocalHostEndpoint);

if constexpr (std::is_same_v<typename TestFixture::ColumnType, ColumnDate32>) {
// 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<typename TestFixture::ColumnType, ColumnInt128>) {
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<typename TestFixture::ColumnType>();
EXPECT_TRUE(CompareRecursive(*column, *result_typed));
}

TYPED_TEST(GenericColumnTest, NulableT_RoundTrip) {
using NullableType = ColumnNullableT<TypeParam>;
auto column = std::make_shared<NullableType>(this->MakeColumn());
auto values = this->GenerateValues(100);
FromVectorGenerator<bool> 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<typename TestFixture::ColumnType>();
clickhouse::Client client(LocalHostEndpoint);

if (auto message = this->SkipTest(client)) {
GTEST_SKIP() << *message;
}

auto result_typed = WrapColumn<NullableType>(RoundtripColumnValues(client, column));
EXPECT_TRUE(CompareRecursive(*column, *result_typed));
}
Loading