#pragma once #include "column.h" #include "numeric.h" #include "utils.h" #include namespace clickhouse { template class ColumnArrayT; /** * Represents column of Array(T). */ class ColumnArray : public Column { public: using ValueType = ColumnRef; /** Create an array of given type. * * `data` is used internally (and modified) by ColumnArray. * Users are strongly advised against supplying non-empty columns and/or modifying * contents of `data` afterwards. */ explicit ColumnArray(ColumnRef data); /** Create an array of given type, with actual values and offsets. * * Both `data` and `offsets` are used (and modified) internally bye ColumnArray. * Users are strongly advised against modifying contents of `data` or `offsets` afterwards. */ ColumnArray(ColumnRef data, std::shared_ptr offsets); /// Converts input column to array and appends as one row to the current column. void AppendAsColumn(ColumnRef array); /// Converts array at pos n to column. /// Type of element of result column same as type of array element. ColumnRef GetAsColumn(size_t n) const; /// Shorthand to get a column casted to a proper type. template auto GetAsColumnTyped(size_t n) const { return GetAsColumn(n)->AsStrict(); } public: /// Increase the capacity of the column for large block insertion. void Reserve(size_t new_cap) override; /// Appends content of given column to the end of current one. void Append(ColumnRef column) override; /// Loads column prefix from input stream. bool LoadPrefix(InputStream* input, size_t rows) override; /// Loads column data from input stream. bool LoadBody(InputStream* input, size_t rows) override; /// Saves column prefix to output stream. void SavePrefix(OutputStream* output) override; /// Saves column data to output stream. void SaveBody(OutputStream* output) override; /// Clear column data . void Clear() override; /// Returns count of rows in the column. size_t Size() const override; /// Makes slice of the current column. ColumnRef Slice(size_t, size_t) const override; ColumnRef CloneEmpty() const override; void Swap(Column&) override; void OffsetsIncrease(size_t); protected: template friend class ColumnArrayT; ColumnArray(ColumnArray&& array); size_t GetOffset(size_t n) const; size_t GetSize(size_t n) const; ColumnRef GetData(); void AddOffset(size_t n); void Reset(); private: ColumnRef data_; std::shared_ptr offsets_; }; template class ColumnArrayT : public ColumnArray { public: class ArrayValueView; using ValueType = ArrayValueView; using NestedColumnType = ColumnType; explicit ColumnArrayT(std::shared_ptr data) : ColumnArray(data) , typed_nested_data_(data) {} ColumnArrayT(std::shared_ptr data, std::shared_ptr offsets) : ColumnArray(data, offsets) , typed_nested_data_(data) {} template explicit ColumnArrayT(Args &&... args) : ColumnArrayT(std::make_shared(std::forward(args)...)) {} /** Create a ColumnArrayT from a ColumnArray, 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(ColumnArray&& col) { auto nested_data = WrapColumn(col.GetData()); return std::make_shared>(nested_data, col.offsets_); } static auto Wrap(Column&& col) { return Wrap(std::move(dynamic_cast(col))); } // Helper to simplify integration with other APIs static auto Wrap(ColumnRef&& col) { return Wrap(std::move(*col->AsStrict())); } /// A single (row) value of the Array-column, i.e. readonly array of items. class ArrayValueView { const std::shared_ptr typed_nested_data_; const size_t offset_; const size_t size_; public: using ValueType = std::decay_t().At(0))>; ArrayValueView(std::shared_ptr data, size_t offset = 0, size_t size = std::numeric_limits::max()) : typed_nested_data_(data) , offset_(offset) , size_(std::min(typed_nested_data_->Size() - offset, size)) {} inline auto operator[](size_t index) const { return (*typed_nested_data_)[offset_ + index]; } inline auto At(size_t index) const { if (index >= size_) throw ValidationError("ColumnArray value index out of bounds: " + std::to_string(index) + ", max is " + std::to_string(size_)); return typed_nested_data_->At(offset_ + index); } class Iterator { const std::shared_ptr typed_nested_data_; const size_t offset_; const size_t size_; size_t index_; public: Iterator() = default; Iterator(std::shared_ptr typed_nested_data, size_t offset, size_t size, size_t index) : typed_nested_data_(typed_nested_data) , offset_(offset) , size_(size) , index_(index) {} using ValueType = typename ArrayValueView::ValueType; inline auto operator*() const { return typed_nested_data_->At(offset_ + index_); } inline Iterator& operator++() { ++index_; return *this; } inline bool operator==(const Iterator& other) const { return this->typed_nested_data_ == other.typed_nested_data_ && this->offset_ == other.offset_ && this->size_ == other.size_ && this->index_ == other.index_; } inline bool operator!=(const Iterator& other) const { return !(*this == other); } }; // minimalistic stl-like container interface, hence the lowercase inline Iterator begin() const { return Iterator{typed_nested_data_, offset_, size_, 0}; } inline Iterator cbegin() const { return Iterator{typed_nested_data_, offset_, size_, 0}; } inline Iterator end() const { return Iterator{typed_nested_data_, offset_, size_, size_}; } inline Iterator cend() const { return Iterator{typed_nested_data_, offset_, size_, size_}; } inline size_t size() const { return size_; } // It is ugly to have both size() and Size(), but it is for compatitability with both STL and rest of the clickhouse-cpp. inline size_t Size() const { return size_; } inline bool operator==(const ArrayValueView& other) const { if (size() != other.size()) { return false; } for (size_t i = 0; i < size_; ++i) { if ((*this)[i] != other[i]) { return false; } } return true; } inline bool operator!=(const ArrayValueView& other) const { return !(*this == other); } }; inline auto At(size_t index) const { if (index >= Size()) throw ValidationError("ColumnArray row index out of bounds: " + std::to_string(index) + ", max is " + std::to_string(Size())); return ArrayValueView{typed_nested_data_, GetOffset(index), GetSize(index)}; } inline auto operator[](size_t index) const { return ArrayValueView{typed_nested_data_, GetOffset(index), GetSize(index)}; } using ColumnArray::Append; template inline void Append(Container&& container) { using container_type = decltype(container); if constexpr (std::is_lvalue_reference_v || std::is_const_v>) { Append(std::begin(container), std::end(container)); } else { Append(std::make_move_iterator(std::begin(container)), std::make_move_iterator(std::end(container))); } } template inline void Append(const std::initializer_list& container) { Append(std::begin(container), std::end(container)); } template inline void Append(Begin begin, End end) { auto & nested_data = *typed_nested_data_; size_t counter = 0; while (begin != end) { nested_data.Append(*begin); ++begin; ++counter; } // Even if there are 0 items, increase counter, creating empty array item. AddOffset(counter); } ColumnRef Slice(size_t begin, size_t size) const override { return Wrap(ColumnArray::Slice(begin, size)); } ColumnRef CloneEmpty() const override { return Wrap(ColumnArray::CloneEmpty()); } void Swap(Column& other) override { auto & col = dynamic_cast &>(other); typed_nested_data_.swap(col.typed_nested_data_); ColumnArray::Swap(other); } private: /// Helper to allow wrapping a "typeless" ColumnArray ColumnArrayT(ColumnArray&& array, std::shared_ptr nested_data) : ColumnArray(std::move(array)) , typed_nested_data_(std::move(nested_data)) {} private: std::shared_ptr typed_nested_data_; }; }