| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you under the Apache License, Version 2.0 (the | |
| * "License"); you may not use this file except in compliance | |
| * with the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, | |
| * software distributed under the License is distributed on an | |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| * KIND, either express or implied. See the License for the | |
| * specific language governing permissions and limitations | |
| * under the License. | |
| */ | |
| #include "iceberg/row/arrow_array_wrapper.h" | |
| #include <cstring> | |
| #include <nanoarrow/nanoarrow.h> | |
| #include "iceberg/arrow_c_data_guard_internal.h" | |
| #include "iceberg/result.h" | |
| #include "iceberg/util/macros.h" | |
| namespace iceberg { | |
| #define NANOARROW_RETURN_IF_NOT_OK(status) \ | |
| if (status != NANOARROW_OK) [[unlikely]] { \ | |
| return InvalidArrowData("Nanoarrow error: {}", error.message); \ | |
| } | |
| namespace { | |
| // TODO(gangwu): Reuse created ArrowArrayStructLike and others with cache. | |
| Result<Scalar> ExtractValue(const ArrowSchema* schema, const ArrowArray* array, | |
| const ArrowArrayView* array_view, int64_t index) { | |
| if (ArrowArrayViewIsNull(array_view, index)) { | |
| return std::monostate{}; | |
| } | |
| switch (array_view->storage_type) { | |
| case NANOARROW_TYPE_NA: | |
| return std::monostate{}; | |
| case NANOARROW_TYPE_BOOL: | |
| return static_cast<bool>(ArrowArrayViewGetIntUnsafe(array_view, index)); | |
| case NANOARROW_TYPE_INT32: | |
| case NANOARROW_TYPE_DATE32: | |
| return static_cast<int32_t>(ArrowArrayViewGetIntUnsafe(array_view, index)); | |
| case NANOARROW_TYPE_INT64: | |
| case NANOARROW_TYPE_TIME64: | |
| case NANOARROW_TYPE_TIMESTAMP: | |
| return ArrowArrayViewGetIntUnsafe(array_view, index); | |
| case NANOARROW_TYPE_FLOAT: | |
| return static_cast<float>(ArrowArrayViewGetDoubleUnsafe(array_view, index)); | |
| case NANOARROW_TYPE_DOUBLE: | |
| return ArrowArrayViewGetDoubleUnsafe(array_view, index); | |
| case NANOARROW_TYPE_STRING: | |
| case NANOARROW_TYPE_BINARY: | |
| case NANOARROW_TYPE_FIXED_SIZE_BINARY: | |
| case NANOARROW_TYPE_STRING_VIEW: | |
| case NANOARROW_TYPE_BINARY_VIEW: | |
| case NANOARROW_TYPE_LARGE_STRING: | |
| case NANOARROW_TYPE_LARGE_BINARY: { | |
| ArrowStringView value = ArrowArrayViewGetStringUnsafe(array_view, index); | |
| return std::string_view(value.data, value.size_bytes); | |
| } | |
| case NANOARROW_TYPE_DECIMAL128: { | |
| ArrowError error; | |
| ArrowSchemaView schema_view; | |
| NANOARROW_RETURN_IF_NOT_OK(ArrowSchemaViewInit(&schema_view, schema, &error)); | |
| ArrowDecimal value; | |
| ArrowDecimalInit(&value, schema_view.decimal_bitwidth, | |
| schema_view.decimal_precision, schema_view.decimal_scale); | |
| ArrowArrayViewGetDecimalUnsafe(array_view, index, &value); | |
| if (value.n_words != 2) { | |
| return InvalidArrowData("Unsupported Arrow decimal words: {}", value.n_words); | |
| } | |
| int128_t int_value{0}; | |
| std::memcpy(&int_value, value.words, sizeof(int128_t)); | |
| return Decimal(int_value); | |
| } | |
| case NANOARROW_TYPE_STRUCT: { | |
| ICEBERG_ASSIGN_OR_RAISE(std::shared_ptr<StructLike> struct_like, | |
| ArrowArrayStructLike::Make(*schema, *array, index)); | |
| return struct_like; | |
| } | |
| case NANOARROW_TYPE_LIST: { | |
| ICEBERG_ASSIGN_OR_RAISE(std::shared_ptr<ArrayLike> array_like, | |
| ArrowArrayArrayLike::Make(*schema, *array, index)); | |
| return array_like; | |
| } | |
| case NANOARROW_TYPE_MAP: { | |
| ICEBERG_ASSIGN_OR_RAISE(std::shared_ptr<MapLike> map_like, | |
| ArrowArrayMapLike::Make(*schema, *array, index)); | |
| return map_like; | |
| } | |
| case NANOARROW_TYPE_EXTENSION: | |
| // TODO(gangwu): Handle these types properly | |
| default: | |
| return NotImplemented("Unsupported Arrow type: {}", | |
| static_cast<int>(array_view->storage_type)); | |
| } | |
| } | |
| } // namespace | |
| // ArrowArrayStructLike Implementation | |
| class ArrowArrayStructLike::Impl { | |
| public: | |
| Impl(const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) | |
| : schema_(schema), array_(std::cref(array)), row_index_(row_index) {} | |
| ~Impl() = default; | |
| Result<Scalar> GetField(size_t pos) const { | |
| // NOLINTNEXTLINE(modernize-use-integer-sign-comparison) | |
| if (pos >= static_cast<size_t>(schema_.n_children)) { | |
| return InvalidArgument("Field index {} out of range (size: {})", pos, | |
| schema_.n_children); | |
| } | |
| if (row_index_ < 0 || row_index_ >= array_.get().length) { | |
| return InvalidArgument("Row index {} out of range (length: {})", row_index_, | |
| array_.get().length); | |
| } | |
| const ArrowSchema* child_schema = schema_.children[pos]; | |
| const ArrowArray* child_array = array_.get().children[pos]; | |
| const ArrowArrayView* child_view = array_view_.children[pos]; | |
| return ExtractValue(child_schema, child_array, child_view, | |
| array_view_.offset + row_index_); | |
| } | |
| size_t num_fields() const { return static_cast<size_t>(schema_.n_children); } | |
| Status Reset(const ArrowArray& array, int64_t row_index) { | |
| array_ = std::cref(array); | |
| row_index_ = row_index; | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return {}; | |
| } | |
| Status Reset(int64_t row_index) { | |
| row_index_ = row_index; | |
| return {}; | |
| } | |
| Status Init() { | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewInitFromSchema(&array_view_, &schema_, &error)); | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return {}; | |
| } | |
| private: | |
| ArrowArrayView array_view_; | |
| internal::ArrowArrayViewGuard array_view_guard_{&array_view_}; | |
| const ArrowSchema& schema_; | |
| std::reference_wrapper<const ArrowArray> array_; | |
| int64_t row_index_; | |
| }; | |
| Result<std::unique_ptr<ArrowArrayStructLike>> ArrowArrayStructLike::Make( | |
| const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) { | |
| auto impl = std::make_unique<Impl>(schema, array, row_index); | |
| ICEBERG_RETURN_UNEXPECTED(impl->Init()); | |
| return std::unique_ptr<ArrowArrayStructLike>(new ArrowArrayStructLike(std::move(impl))); | |
| } | |
| ArrowArrayStructLike::ArrowArrayStructLike(std::unique_ptr<Impl> impl) | |
| : impl_(std::move(impl)) {} | |
| ArrowArrayStructLike::~ArrowArrayStructLike() = default; | |
| Result<Scalar> ArrowArrayStructLike::GetField(size_t pos) const { | |
| return impl_->GetField(pos); | |
| } | |
| size_t ArrowArrayStructLike::num_fields() const { return impl_->num_fields(); } | |
| Status ArrowArrayStructLike::Reset(int64_t row_index) { return impl_->Reset(row_index); } | |
| Status ArrowArrayStructLike::Reset(const ArrowArray& array, int64_t row_index) { | |
| return impl_->Reset(array, row_index); | |
| } | |
| // ArrowArrayArrayLike Implementation | |
| class ArrowArrayArrayLike::Impl { | |
| public: | |
| Impl(const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) | |
| : schema_(schema), array_(std::cref(array)), row_index_(row_index) {} | |
| ~Impl() = default; | |
| Result<Scalar> GetElement(size_t pos) const { | |
| // NOLINTNEXTLINE(modernize-use-integer-sign-comparison) | |
| if (pos >= static_cast<size_t>(length_)) { | |
| return InvalidArgument("Element index {} out of range (length: {})", pos, length_); | |
| } | |
| const ArrowSchema* child_schema = schema_.children[0]; | |
| const ArrowArray* child_array = array_.get().children[0]; | |
| const ArrowArrayView* child_view = array_view_.children[0]; | |
| return ExtractValue(child_schema, child_array, child_view, | |
| offset_ + static_cast<int64_t>(pos)); | |
| } | |
| size_t size() const { return static_cast<size_t>(length_); } | |
| Status Reset(const ArrowArray& array, int64_t row_index) { | |
| array_ = std::cref(array); | |
| row_index_ = row_index; | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return UpdateOffsets(); | |
| } | |
| Status Reset(int64_t row_index) { | |
| row_index_ = row_index; | |
| return UpdateOffsets(); | |
| } | |
| Status Init() { | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewInitFromSchema(&array_view_, &schema_, &error)); | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return UpdateOffsets(); | |
| } | |
| private: | |
| Status UpdateOffsets() { | |
| if (row_index_ < 0 || row_index_ >= array_.get().length) { | |
| return InvalidArgument("Row index {} out of range (length: {})", row_index_, | |
| array_.get().length); | |
| } | |
| const int64_t row = array_view_.offset + row_index_; | |
| offset_ = ArrowArrayViewListChildOffset(&array_view_, row); | |
| length_ = ArrowArrayViewListChildOffset(&array_view_, row + 1) - offset_; | |
| return {}; | |
| } | |
| ArrowArrayView array_view_; | |
| internal::ArrowArrayViewGuard array_view_guard_{&array_view_}; | |
| const ArrowSchema& schema_; | |
| std::reference_wrapper<const ArrowArray> array_; | |
| int64_t row_index_; | |
| int64_t offset_ = 0; | |
| int64_t length_ = 0; | |
| }; | |
| Result<std::unique_ptr<ArrowArrayArrayLike>> ArrowArrayArrayLike::Make( | |
| const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) { | |
| auto impl = std::make_unique<Impl>(schema, array, row_index); | |
| ICEBERG_RETURN_UNEXPECTED(impl->Init()); | |
| return std::unique_ptr<ArrowArrayArrayLike>(new ArrowArrayArrayLike(std::move(impl))); | |
| } | |
| ArrowArrayArrayLike::ArrowArrayArrayLike(std::unique_ptr<Impl> impl) | |
| : impl_(std::move(impl)) {} | |
| ArrowArrayArrayLike::~ArrowArrayArrayLike() = default; | |
| Result<Scalar> ArrowArrayArrayLike::GetElement(size_t pos) const { | |
| return impl_->GetElement(pos); | |
| } | |
| size_t ArrowArrayArrayLike::size() const { return impl_->size(); } | |
| Status ArrowArrayArrayLike::Reset(int64_t row_index) { return impl_->Reset(row_index); } | |
| Status ArrowArrayArrayLike::Reset(const ArrowArray& array, int64_t row_index) { | |
| return impl_->Reset(array, row_index); | |
| } | |
| // ArrowArrayMapLike Implementation | |
| class ArrowArrayMapLike::Impl { | |
| public: | |
| Impl(const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) | |
| : schema_(schema), array_(std::cref(array)), row_index_(row_index) {} | |
| ~Impl() = default; | |
| Result<Scalar> GetKey(size_t pos) const { | |
| // NOLINTNEXTLINE(modernize-use-integer-sign-comparison) | |
| if (pos >= static_cast<size_t>(length_)) { | |
| return InvalidArgument("Key index {} out of range (length: {})", pos, length_); | |
| } | |
| const ArrowSchema* keys_schema = schema_.children[0]->children[0]; | |
| const ArrowArray* keys_array = array_.get().children[0]->children[0]; | |
| const ArrowArrayView* keys_view = array_view_.children[0]->children[0]; | |
| return ExtractValue(keys_schema, keys_array, keys_view, | |
| EntryIndex(static_cast<int64_t>(pos))); | |
| } | |
| Result<Scalar> GetValue(size_t pos) const { | |
| // NOLINTNEXTLINE(modernize-use-integer-sign-comparison) | |
| if (pos >= static_cast<size_t>(length_)) { | |
| return InvalidArgument("Value index {} out of range (length: {})", pos, length_); | |
| } | |
| const ArrowSchema* values_schema = schema_.children[0]->children[1]; | |
| const ArrowArray* values_array = array_.get().children[0]->children[1]; | |
| const ArrowArrayView* values_view = array_view_.children[0]->children[1]; | |
| return ExtractValue(values_schema, values_array, values_view, | |
| EntryIndex(static_cast<int64_t>(pos))); | |
| } | |
| size_t size() const { return static_cast<size_t>(length_); } | |
| Status Reset(const ArrowArray& array, int64_t row_index) { | |
| array_ = std::cref(array); | |
| row_index_ = row_index; | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return UpdateOffsets(); | |
| } | |
| Status Reset(int64_t row_index) { | |
| row_index_ = row_index; | |
| return UpdateOffsets(); | |
| } | |
| Status Init() { | |
| ArrowError error; | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewInitFromSchema(&array_view_, &schema_, &error)); | |
| NANOARROW_RETURN_IF_NOT_OK( | |
| ArrowArrayViewSetArray(&array_view_, &array_.get(), &error)); | |
| return UpdateOffsets(); | |
| } | |
| private: | |
| Status UpdateOffsets() { | |
| if (row_index_ < 0 || row_index_ >= array_.get().length) { | |
| return InvalidArgument("Row index {} out of range (length: {})", row_index_, | |
| array_.get().length); | |
| } | |
| // XXX: ArrowArrayViewListChildOffset does not work for map types. | |
| // We need to directly access the offsets buffer instead. | |
| auto* offsets_buffer = array_view_.buffer_views[1].data.as_int32; | |
| const int64_t row = array_view_.offset + row_index_; | |
| offset_ = offsets_buffer[row]; | |
| length_ = offsets_buffer[row + 1] - offset_; | |
| return {}; | |
| } | |
| int64_t EntryIndex(int64_t pos) const { | |
| return array_view_.children[0]->offset + offset_ + pos; | |
| } | |
| ArrowArrayView array_view_; | |
| internal::ArrowArrayViewGuard array_view_guard_{&array_view_}; | |
| const ArrowSchema& schema_; | |
| std::reference_wrapper<const ArrowArray> array_; | |
| int64_t row_index_; | |
| int64_t offset_ = 0; | |
| int64_t length_ = 0; | |
| }; | |
| Result<std::unique_ptr<ArrowArrayMapLike>> ArrowArrayMapLike::Make( | |
| const ArrowSchema& schema, const ArrowArray& array, int64_t row_index) { | |
| auto impl = std::make_unique<Impl>(schema, array, row_index); | |
| ICEBERG_RETURN_UNEXPECTED(impl->Init()); | |
| return std::unique_ptr<ArrowArrayMapLike>(new ArrowArrayMapLike(std::move(impl))); | |
| } | |
| ArrowArrayMapLike::ArrowArrayMapLike(std::unique_ptr<Impl> impl) | |
| : impl_(std::move(impl)) {} | |
| ArrowArrayMapLike::~ArrowArrayMapLike() = default; | |
| Result<Scalar> ArrowArrayMapLike::GetKey(size_t pos) const { return impl_->GetKey(pos); } | |
| Result<Scalar> ArrowArrayMapLike::GetValue(size_t pos) const { | |
| return impl_->GetValue(pos); | |
| } | |
| size_t ArrowArrayMapLike::size() const { return impl_->size(); } | |
| Status ArrowArrayMapLike::Reset(int64_t row_index) { return impl_->Reset(row_index); } | |
| Status ArrowArrayMapLike::Reset(const ArrowArray& array, int64_t row_index) { | |
| return impl_->Reset(array, row_index); | |
| } | |
| } // namespace iceberg |