Skip to content
Draft
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
2 changes: 0 additions & 2 deletions clickhouse/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ SET ( clickhouse-cpp-lib-src
columns/string.h
columns/time.h
columns/tuple.h
columns/utils.h
columns/uuid.h

types/bignum.h
Expand Down Expand Up @@ -257,7 +256,6 @@ INSTALL(FILES columns/map.h DESTINATION include/clickhouse/columns/)
INSTALL(FILES columns/string.h DESTINATION include/clickhouse/columns/)
INSTALL(FILES columns/time.h DESTINATION include/clickhouse/columns/)
INSTALL(FILES columns/tuple.h DESTINATION include/clickhouse/columns/)
INSTALL(FILES columns/utils.h DESTINATION include/clickhouse/columns/)
INSTALL(FILES columns/uuid.h DESTINATION include/clickhouse/columns/)

# types
Expand Down
63 changes: 51 additions & 12 deletions clickhouse/columns/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "column.h"
#include "numeric.h"
#include "utils.h"

#include <memory>

Expand Down Expand Up @@ -128,26 +127,66 @@ class ColumnArrayT : public ColumnArray {
: ColumnArrayT(std::make_shared<NestedColumnType>(std::forward<Args>(args)...))
{}

/** Create a ColumnArrayT from a ColumnArray, without copying data and offsets, but by 'stealing' those from `col`.
/** Create a ColumnArrayT that SHARES the internals of `col` (nested data and
* offsets) via shared_ptr, WITHOUT stealing or copying them.
*
* 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.
* The original `col` remains fully valid and usable. Both the original and the
* returned wrapper reference the same underlying columns, so mutations through
* one are visible through the other.
*
* 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.
* The two-argument overloads are non-throwing: on a type mismatch they return
* nullptr and, if `error` is non-null, assign a description to `*error`. The
* single-argument overloads throw ValidationError on a type mismatch instead.
*/
static auto Wrap(ColumnArray&& col) {
auto nested_data = WrapColumn<NestedColumnType>(col.GetData());
static std::shared_ptr<ColumnArrayT<NestedColumnType>> Wrap(const ColumnArray& col, ValidationError* error) {
auto nested_data = WrapColumn<NestedColumnType>(col.data_, error);
if (!nested_data) {
return nullptr;
}
return std::make_shared<ColumnArrayT<NestedColumnType>>(nested_data, col.offsets_);
}

static auto Wrap(Column&& col) {
return Wrap(std::move(dynamic_cast<ColumnArray&&>(col)));
static std::shared_ptr<ColumnArrayT<NestedColumnType>> Wrap(const Column& col, ValidationError* error) {
if (auto* c = dynamic_cast<const ColumnArray*>(&col)) {
return Wrap(*c, error);
}
if (error) {
*error = ValidationError("Can't wrap column of type " + col.GetType().GetName() + " as Array");
}
return nullptr;
}

// Helper to simplify integration with other APIs
static auto Wrap(ColumnRef&& col) {
return Wrap(std::move(*col->AsStrict<ColumnArray>()));
static std::shared_ptr<ColumnArrayT<NestedColumnType>> Wrap(const ColumnRef& col, ValidationError* error) {
return Wrap(*col, error);
}

static auto Wrap(const ColumnArray& col) {
ValidationError error;
auto result = Wrap(col, &error);
if (!result) {
throw error;
}
return result;
}

static auto Wrap(const Column& col) {
ValidationError error;
auto result = Wrap(col, &error);
if (!result) {
throw error;
}
return result;
}

// Helper to simplify integration with other APIs
static auto Wrap(const ColumnRef& col) {
ValidationError error;
auto result = Wrap(col, &error);
if (!result) {
throw error;
}
return result;
}

/// A single (row) value of the Array-column, i.e. readonly array of items.
Expand Down
114 changes: 99 additions & 15 deletions clickhouse/columns/column.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#include "../columns/itemview.h"
#include "../exceptions.h"

#include <algorithm>
#include <memory>
#include <stdexcept>
#include <vector>

namespace clickhouse {

Expand All @@ -24,26 +26,25 @@ class Column : public std::enable_shared_from_this<Column> {
virtual ~Column() {}

/// Downcast pointer to the specific column's subtype.
///
/// If T is a "wrappable" typed column (one exposing a static Wrap method, e.g.
/// ColumnArrayT/ColumnTupleT/ColumnMapT/ColumnNullableT/ColumnLowCardinalityT) and the
/// column is not already exactly T, this attempts to Wrap it as T (a storage-sharing
/// typed view). Returns nullptr when neither an exact cast nor a wrap is possible.
/// (Definitions are out-of-line below, after WrapColumn is declared.)
template <typename T>
inline std::shared_ptr<T> As() {
return std::dynamic_pointer_cast<T>(shared_from_this());
}
inline std::shared_ptr<T> As();

/// Downcast pointer to the specific column's subtype.
/// Const overload. Unlike the non-const As(), this does NOT wrap: it only performs an
/// exact downcast and returns nullptr on mismatch (even for a wrappable T). Wrapping is
/// intentionally disabled here because it would synthesize a mutable, storage-sharing
/// view from a const column (requiring a const_cast), which is not const-correct.
template <typename T>
inline std::shared_ptr<const T> As() const {
return std::dynamic_pointer_cast<const T>(shared_from_this());
}
inline std::shared_ptr<const T> As() const;

/// Downcast pointer to the specific column's subtype.
/// Like As(), but throws ValidationError instead of returning nullptr on failure.
template <typename T>
inline std::shared_ptr<T> AsStrict() {
auto result = std::dynamic_pointer_cast<T>(shared_from_this());
if (!result) {
throw ValidationError("Can't cast from " + type_->GetName());
}
return result;
}
inline std::shared_ptr<T> AsStrict();

/// Get type object of the column.
inline TypeRef Type() const { return type_; }
Expand Down Expand Up @@ -104,4 +105,87 @@ class Column : public std::enable_shared_from_this<Column> {
TypeRef type_;
};

template <typename T>
std::vector<T> SliceVector(const std::vector<T>& vec, size_t begin, size_t len) {
std::vector<T> result;

if (begin < vec.size()) {
len = std::min(len, vec.size() - begin);
result.assign(vec.begin() + begin, vec.begin() + (begin + len));
}

return result;
}

template <typename T>
struct HasWrapMethod {
private:
static int detect(...);
template <typename U>
static decltype(U::Wrap(std::move(std::declval<ColumnRef>()))) detect(const U&);

public:
static constexpr bool value = !std::is_same<int, decltype(detect(std::declval<T>()))>::value;
};

// Non-throwing: returns nullptr and (if `error` is non-null) fills `*error` when `column`
// can't be wrapped as T.
template <typename T>
inline std::shared_ptr<T> WrapColumn(const ColumnRef& column, ValidationError* error) {
if constexpr (HasWrapMethod<T>::value) {
return T::Wrap(column, error);
} else {
auto result = column->template As<T>();
if (!result && error) {
*error = ValidationError("Can't wrap column of type " + column->GetType().GetName());
}
return result;
}
}

// Throwing convenience wrapper.
template <typename T>
inline std::shared_ptr<T> WrapColumn(const ColumnRef& column) {
ValidationError error;
auto result = WrapColumn<T>(column, &error);
if (!result) {
throw error;
}
return result;
}

template <typename T>
inline std::shared_ptr<T> Column::As() {
if constexpr (HasWrapMethod<T>::value) {
if (auto exact = std::dynamic_pointer_cast<T>(shared_from_this())) {
return exact;
}
return WrapColumn<T>(shared_from_this(), nullptr);
} else {
return std::dynamic_pointer_cast<T>(shared_from_this());
}
}

template <typename T>
inline std::shared_ptr<const T> Column::As() const {
// No wrapping for the const overload (see declaration): exact downcast only.
return std::dynamic_pointer_cast<const T>(shared_from_this());
}

template <typename T>
inline std::shared_ptr<T> Column::AsStrict() {
if constexpr (HasWrapMethod<T>::value) {
if (auto exact = std::dynamic_pointer_cast<T>(shared_from_this())) {
return exact;
}
return WrapColumn<T>(shared_from_this());
} else {
auto result = std::dynamic_pointer_cast<T>(shared_from_this());
if (!result) {
throw ValidationError("Can't cast from " + type_->GetName());
}
return result;
}
}

} // namespace clickhouse
1 change: 0 additions & 1 deletion clickhouse/columns/enum.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#include "enum.h"
#include "utils.h"

#include "../base/input.h"
#include "../base/output.h"
Expand Down
18 changes: 15 additions & 3 deletions clickhouse/columns/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,13 +233,25 @@ static ColumnRef CreateColumnFromAst(const TypeAst& ast, CreateColumnByTypeSetti
}
}
else {
// Create the base ColumnLowCardinality (like Array/Nullable/Tuple/Map create their
// base types). Callers can obtain the strongly-typed ColumnLowCardinalityT<...> view
// on demand via Column::As<ColumnLowCardinalityT<...>>(), which wraps it.
switch (nested.code) {
// TODO (nemkov): update this to maximize code reuse.
case Type::String:
return std::make_shared<ColumnLowCardinalityT<ColumnString>>();
case Type::FixedString:
return std::make_shared<ColumnLowCardinalityT<ColumnFixedString>>(GetASTChildElement(nested, 0).value);
return std::make_shared<ColumnLowCardinality>(CreateColumnFromAst(nested, settings));
case Type::Nullable:
// Nullable needs its own case (it can't reuse the generic CreateColumnFromAst
// path above) for two reasons:
// 1. Constructor overload: ColumnLowCardinality has a dedicated
// ColumnLowCardinality(shared_ptr<ColumnNullable>) ctor that seeds the
// special NULL item at dictionary index 0 (via AppendNullItem()). We must
// pass a statically-typed shared_ptr<ColumnNullable> so that overload is
// selected; passing a ColumnRef would statically bind to the generic
// ColumnLowCardinality(ColumnRef) ctor, which only appends the default
// item and would omit the null item, producing an incorrect nullable
// dictionary.
// 2. It lets us construct the ColumnNullable with an explicit UInt8 null-map.
return std::make_shared<ColumnLowCardinality>(
std::make_shared<ColumnNullable>(
CreateColumnFromAst(GetASTChildElement(nested, 0), settings),
Expand Down
2 changes: 0 additions & 2 deletions clickhouse/columns/geo.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include "geo.h"

#include "utils.h"

namespace {
using namespace ::clickhouse;

Expand Down
16 changes: 9 additions & 7 deletions clickhouse/columns/lowcardinality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ ColumnLowCardinality::ColumnLowCardinality(ColumnRef dictionary_column)
: Column(Type::CreateLowCardinality(dictionary_column->Type())),
dictionary_column_(dictionary_column->CloneEmpty()), // safe way to get an column of the same type.
index_column_(std::make_shared<ColumnUInt32>()),
unique_items_map_(std::make_shared<UniqueItems>()),
index_type_code_(Type::UInt32)
{
Setup(dictionary_column);
Expand All @@ -168,6 +169,7 @@ ColumnLowCardinality::ColumnLowCardinality(std::shared_ptr<ColumnNullable> dicti
: Column(Type::CreateLowCardinality(dictionary_column->Type())),
dictionary_column_(dictionary_column->CloneEmpty()), // safe way to get an column of the same type.
index_column_(std::make_shared<ColumnUInt32>()),
unique_items_map_(std::make_shared<UniqueItems>()),
index_type_code_(Type::UInt32)
{
AppendNullItem();
Expand Down Expand Up @@ -381,7 +383,7 @@ bool ColumnLowCardinality::LoadBody(InputStream* input, size_t rows) {

dictionary_column_->Swap(*new_dictionary);
index_column_.swap(new_index);
unique_items_map_.swap(new_unique_items_map);
unique_items_map_->swap(new_unique_items_map);
index_type_code_ = index_column_->Type()->GetCode();

return true;
Expand Down Expand Up @@ -418,7 +420,7 @@ void ColumnLowCardinality::SaveBody(OutputStream* output) {
void ColumnLowCardinality::Clear() {
index_column_->Clear();
dictionary_column_->Clear();
unique_items_map_.clear();
unique_items_map_->clear();

if (auto columnNullable = dictionary_column_->As<ColumnNullable>()) {
AppendNullItem();
Expand Down Expand Up @@ -457,7 +459,7 @@ void ColumnLowCardinality::Swap(Column& other) {
dictionary_column_->Swap(*col.dictionary_column_);

index_column_.swap(col.index_column_);
unique_items_map_.swap(col.unique_items_map_);
unique_items_map_->swap(*col.unique_items_map_);
std::swap(index_type_code_, col.index_type_code_);
}

Expand All @@ -480,7 +482,7 @@ void ColumnLowCardinality::AppendUnsafe(const ItemView & value) {
const auto key = computeHashKey(value);
const auto initial_index_size = index_column_->Size();
// If the value is unique, then we are going to append it to a dictionary, hence new index is Size().
auto [iterator, is_new_item] = unique_items_map_.try_emplace(key, dictionary_column_->Size());
auto [iterator, is_new_item] = unique_items_map_->try_emplace(key, dictionary_column_->Size());
try {
// Order is important, adding to dictionary last, since it is much (MUCH!!!!) harder
// to remove item from dictionary column than from index column
Expand All @@ -497,7 +499,7 @@ void ColumnLowCardinality::AppendUnsafe(const ItemView & value) {
if (index_column_->Size() != initial_index_size)
removeLastIndex();
if (is_new_item)
unique_items_map_.erase(iterator);
unique_items_map_->erase(iterator);

throw;
}
Expand All @@ -507,13 +509,13 @@ void ColumnLowCardinality::AppendNullItem()
{
const auto null_item = GetNullItemForDictionary(dictionary_column_);
AppendToDictionary(*dictionary_column_, null_item);
unique_items_map_.emplace(computeHashKey(null_item), 0);
unique_items_map_->emplace(computeHashKey(null_item), 0);
}

void ColumnLowCardinality::AppendDefaultItem()
{
const auto defaultItem = GetDefaultItemForDictionary(dictionary_column_);
unique_items_map_.emplace(computeHashKey(defaultItem), dictionary_column_->Size());
unique_items_map_->emplace(computeHashKey(defaultItem), dictionary_column_->Size());
AppendToDictionary(*dictionary_column_, defaultItem);
}

Expand Down
Loading
Loading