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
7 changes: 5 additions & 2 deletions clickhouse/columns/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,11 @@ size_t ColumnArray::Size() const {

void ColumnArray::Swap(Column& other) {
auto & col = dynamic_cast<ColumnArray &>(other);
data_.swap(col.data_);
offsets_.swap(col.offsets_);
// Swap sub-column CONTENTS in place (never rebind the shared_ptr slots), so the data and
// offsets objects keep their identity and any As<>/Wrap views of this column stay coherent.
// The nested Swap also type-checks the element columns and throws on a mismatch.
data_->Swap(*col.data_);
offsets_->Swap(*col.offsets_);
}

void ColumnArray::OffsetsIncrease(size_t n) {
Expand Down
45 changes: 30 additions & 15 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 @@ -107,7 +106,7 @@ class ColumnArray : public Column {
};

template <typename ColumnType>
class ColumnArrayT : public ColumnArray {
class ColumnArrayT : public ColumnArray, public WrappableColumn<ColumnArrayT<ColumnType>, ColumnArray> {
public:
class ArrayValueView;
using ValueType = ArrayValueView;
Expand All @@ -128,28 +127,43 @@ 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);
}

// Throwing single-argument overloads (concrete type / Column& / ColumnRef&).
using WrappableColumn<ColumnArrayT<ColumnType>, ColumnArray>::Wrap;

/// A single (row) value of the Array-column, i.e. readonly array of items.
class ArrayValueView {
const std::shared_ptr<NestedColumnType> typed_nested_data_;
Expand Down Expand Up @@ -312,8 +326,9 @@ class ColumnArrayT : public ColumnArray {

void Swap(Column& other) override {
auto & col = dynamic_cast<ColumnArrayT<NestedColumnType> &>(other);
typed_nested_data_.swap(col.typed_nested_data_);
ColumnArray::Swap(other);
// Base swaps sub-column contents in place, preserving object identity, so the cached
// typed_nested_data_ still points at the correct object and must NOT be repointed.
ColumnArray::Swap(col);
}

private:
Expand Down
181 changes: 166 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,32 @@ 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. Behaves like the non-const As(): for a wrappable T it first tries an
/// exact downcast, then falls back to Wrap, returning a storage-sharing view typed as
/// shared_ptr<const T> (read-only surface). Wrapping never mutates the source column; it
/// does require an internal const_pointer_cast because Wrap builds a mutable wrapper.
/// Caveat: the read-only guarantee is shallow -- the wrapper still holds non-const
/// references to the shared sub-columns, so a deliberate const_pointer_cast<T> could
/// still reach the underlying storage. Casual const use stays read-only.
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();

/// Const overload of AsStrict(); wraps like As() const and throws on failure.
template <typename T>
inline std::shared_ptr<const T> AsStrict() const;

/// Get type object of the column.
inline TypeRef Type() const { return type_; }
Expand Down Expand Up @@ -104,4 +112,147 @@ 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;
}

// Provides the throwing single-argument Wrap overloads for typed columns.
// Derived must supply the non-throwing two-argument Wrap(col, ValidationError*) overloads.
// BaseColumn is Derived's concrete base (e.g. ColumnArray) so an already-typed
// argument takes the direct path instead of the dynamic_cast'ing Column& overload.
template <typename Derived, typename BaseColumn>
struct WrappableColumn {
static auto Wrap(const BaseColumn& col) {
ValidationError error;
auto result = Derived::Wrap(col, &error);
if (!result) {
throw error;
}
return result;
}

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

static auto Wrap(const ColumnRef& col) {
ValidationError error;
auto result = Derived::Wrap(col, &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 {
if constexpr (HasWrapMethod<T>::value) {
if (auto exact = std::dynamic_pointer_cast<const T>(shared_from_this())) {
return exact;
}
// Wrap needs a mutable ColumnRef to build a storage-sharing view; the result is
// returned as shared_ptr<const T> so the caller keeps read-only access, and
// wrapping never mutates the source column.
return WrapColumn<T>(std::const_pointer_cast<Column>(shared_from_this()), nullptr);
} else {
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;
}
}

template <typename T>
inline std::shared_ptr<const T> Column::AsStrict() const {
if constexpr (HasWrapMethod<T>::value) {
if (auto exact = std::dynamic_pointer_cast<const T>(shared_from_this())) {
return exact;
}
// Throwing WrapColumn: raises ValidationError on a type mismatch.
return WrapColumn<T>(std::const_pointer_cast<Column>(shared_from_this()));
} else {
auto result = std::dynamic_pointer_cast<const 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
Loading
Loading