// 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.

// Adapted from Apache Arrow
// https://github.com/apache/arrow/blob/apache-arrow-17.0.0/cpp/src/arrow/adapters/orc/util.cc

#include "paimon/format/orc/orc_adapter.h"

#include <cassert>
#include <cstdint>
#include <cstring>
#include <exception>
#include <limits>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>

#include "arrow/api.h"
#include "arrow/array/array_base.h"
#include "arrow/array/array_decimal.h"
#include "arrow/array/array_nested.h"
#include "arrow/array/builder_base.h"
#include "arrow/array/builder_binary.h"
#include "arrow/array/builder_decimal.h"
#include "arrow/array/builder_primitive.h"
#include "arrow/array/data.h"
#include "arrow/array/util.h"
#include "arrow/buffer_builder.h"
#include "arrow/result.h"
#include "arrow/status.h"
#include "arrow/type.h"
#include "arrow/type_fwd.h"
#include "arrow/type_traits.h"
#include "arrow/util/bitmap_ops.h"
#include "arrow/util/checked_cast.h"
#include "arrow/util/decimal.h"
#include "arrow/util/key_value_metadata.h"
#include "arrow/util/range.h"
#include "arrow/visit_data_inline.h"
#include "fmt/format.h"
#include "orc/Int128.hh"
#include "orc/MemoryPool.hh"
#include "orc/Type.hh"
#include "orc/Vector.hh"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/data/timestamp.h"

namespace paimon::orc {
namespace {
template <typename T>
class OrcBackedArrowBuffer : public arrow::ResizableBuffer {
 public:
    explicit OrcBackedArrowBuffer(::orc::DataBuffer<T>&& buffer)
        : ResizableBuffer(reinterpret_cast<uint8_t*>(buffer.data()), buffer.size() * sizeof(T)),
          orc_buffer_(std::move(buffer)) {}

    arrow::Status Resize(const int64_t new_size, bool shrink_to_fit) override {
        try {
            // arrow buffer size is equal to orc buffer size * sizeof(T)
            orc_buffer_.resize(new_size / sizeof(T));
        } catch (const std::exception& e) {
            return arrow::Status::Invalid(
                fmt::format("OrcBackedArrowBuffer resize failed for {}", e.what()));
        }
        arrow::ResizableBuffer::data_ = reinterpret_cast<uint8_t*>(orc_buffer_.data());
        arrow::ResizableBuffer::size_ = orc_buffer_.size() * sizeof(T);
        arrow::ResizableBuffer::capacity_ = orc_buffer_.capacity() * sizeof(T);
        return arrow::Status::OK();
    }

    arrow::Status Reserve(const int64_t new_capacity) override {
        try {
            // arrow buffer capacity is equal to orc buffer capacity * sizeof(T)
            orc_buffer_.reserve(new_capacity / sizeof(T));
        } catch (const std::exception& e) {
            return arrow::Status::Invalid(
                fmt::format("OrcBackedArrowBuffer reserve failed for {}", e.what()));
        }
        arrow::ResizableBuffer::data_ = reinterpret_cast<uint8_t*>(orc_buffer_.data());
        arrow::ResizableBuffer::capacity_ = orc_buffer_.capacity() * sizeof(T);
        return arrow::Status::OK();
    }

 private:
    ::orc::DataBuffer<T> orc_buffer_;
};

class EmptyBuilder : public arrow::ArrayBuilder {
 public:
    using arrow::ArrayBuilder::SetNotNull;
    explicit EmptyBuilder(arrow::MemoryPool* pool) : arrow::ArrayBuilder(pool) {}
    arrow::Status AppendNulls(int64_t length) override {
        return arrow::Status::NotImplemented("AppendNulls is not implemented");
    }

    arrow::Status AppendNull() override {
        return arrow::Status::NotImplemented("AppendNull is not implemented");
    }

    arrow::Status AppendEmptyValue() override {
        return arrow::Status::NotImplemented("AppendEmptyValue is not implemented");
    }

    arrow::Status AppendEmptyValues(int64_t length) override {
        return arrow::Status::NotImplemented("AppendEmptyValues is not implemented");
    }

    // when null_count == 0 in array, null_bitmap buffer can be null, thus, there is no need to
    // construct null bitmap, use IncreaseLength() to increase the length_ of array
    virtual void IncreaseLength(int64_t length) {
        length_ += length;
        has_nulls_ = false;
    }

 protected:
    bool has_nulls_ = true;
};

class UnPooledBooleanBuilder : public EmptyBuilder {
 public:
    explicit UnPooledBooleanBuilder(arrow::MemoryPool* pool)
        : EmptyBuilder(pool),
          type_(arrow::boolean()),
          data_builder_(std::make_shared<arrow::TypedBufferBuilder<bool>>(pool)) {}

    std::shared_ptr<arrow::DataType> type() const override {
        return type_;
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }

    arrow::Status SetData(const uint8_t* data, int64_t length) {
        ARROW_RETURN_NOT_OK(data_builder_->Reserve(length));
        data_builder_->UnsafeAppend(data, length);
        return arrow::Status::OK();
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_ASSIGN_OR_RAISE(null_bitmap, null_bitmap_builder_.FinishWithLength(length_));
        }
        ARROW_ASSIGN_OR_RAISE(auto data, data_builder_->FinishWithLength(length_));
        *out = arrow::ArrayData::Make(type_, length_, {null_bitmap, data}, null_count_);
        Reset();
        return arrow::Status::OK();
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    const std::shared_ptr<arrow::DataType> type_;
    std::shared_ptr<arrow::TypedBufferBuilder<bool>> data_builder_;
};

template <typename Type>
class UnPooledPrimitiveBuilder : public arrow::NumericBuilder<Type> {
 public:
    UnPooledPrimitiveBuilder(const std::shared_ptr<arrow::DataType>& type, arrow::MemoryPool* pool)
        : arrow::NumericBuilder<Type>(type, pool) {}

    void SetData(const std::shared_ptr<arrow::Buffer>& data) {
        data_ = data;
    }

    void IncreaseLength(int64_t length) {
        has_nulls_ = false;
        this->length_ += length;
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(this->null_bitmap_builder_.Finish(&null_bitmap));
        }
        *out = arrow::ArrayData::Make(this->type_, this->length_, {null_bitmap, data_},
                                      this->null_count_);
        Reset();
        return arrow::Status::OK();
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    bool has_nulls_ = true;
    std::shared_ptr<arrow::Buffer> data_;
};

class UnPooledLargeBinaryBuilder : public arrow::LargeBinaryBuilder {
 public:
    using arrow::ArrayBuilder::SetNotNull;
    UnPooledLargeBinaryBuilder(const std::shared_ptr<arrow::DataType>& type,
                               arrow::MemoryPool* pool)
        : arrow::LargeBinaryBuilder(pool), type_(type) {}

    void SetOffsets(const std::shared_ptr<arrow::Buffer>& offsets) {
        offsets_ = offsets;
    }
    void SetData(const std::shared_ptr<arrow::Buffer>& data) {
        data_ = data;
    }
    void IncreaseLength(int64_t length) {
        has_nulls_ = false;
        length_ += length;
    }
    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }
    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(null_bitmap_builder_.Finish(&null_bitmap));
        }
        *out = arrow::ArrayData::Make(type_, length_, {null_bitmap, offsets_, data_}, null_count_);
        Reset();
        return arrow::Status::OK();
    }
    std::shared_ptr<arrow::DataType> type() const override {
        return type_;
    }
    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    bool has_nulls_ = true;
    std::shared_ptr<arrow::DataType> type_;
    std::shared_ptr<arrow::Buffer> offsets_;
    std::shared_ptr<arrow::Buffer> data_;
};

class UnPooledBinaryBuilder : public arrow::BinaryBuilder {
 public:
    using arrow::ArrayBuilder::SetNotNull;
    UnPooledBinaryBuilder(const std::shared_ptr<arrow::DataType>& type, arrow::MemoryPool* pool)
        : arrow::BinaryBuilder(pool), type_(type) {}

    void SetOffsets(const std::shared_ptr<arrow::Buffer>& offsets) {
        offsets_ = offsets;
    }

    void SetData(const std::shared_ptr<arrow::Buffer>& data) {
        data_ = data;
    }

    void IncreaseLength(int64_t length) {
        has_nulls_ = false;
        length_ += length;
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(null_bitmap_builder_.Finish(&null_bitmap));
        }
        *out = arrow::ArrayData::Make(type_, length_, {null_bitmap, offsets_, data_}, null_count_);
        Reset();
        return arrow::Status::OK();
    }

    std::shared_ptr<arrow::DataType> type() const override {
        return type_;
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    bool has_nulls_ = true;
    std::shared_ptr<arrow::DataType> type_;
    std::shared_ptr<arrow::Buffer> offsets_;
    std::shared_ptr<arrow::Buffer> data_;
};

class UnPooledListBuilder : public EmptyBuilder {
 public:
    UnPooledListBuilder(const std::shared_ptr<arrow::DataType>& type,
                        const std::shared_ptr<arrow::ArrayBuilder>& value_builder,
                        arrow::MemoryPool* pool)
        : EmptyBuilder(pool), type_(type), value_builder_(value_builder) {}

    std::shared_ptr<arrow::DataType> type() const override {
        return arrow::list(value_builder_->type());
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }

    void SetOffsets(const std::shared_ptr<arrow::Buffer>& offsets) {
        offsets_ = offsets;
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        // Offset padding zeroed by BufferBuilder
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(null_bitmap_builder_.Finish(&null_bitmap));
        }
        std::shared_ptr<arrow::ArrayData> items;
        ARROW_RETURN_NOT_OK(value_builder_->FinishInternal(&items));

        *out =
            arrow::ArrayData::Make(type(), length_, {null_bitmap, offsets_}, {items}, null_count_);
        Reset();
        return arrow::Status::OK();
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    std::shared_ptr<arrow::DataType> type_;
    std::shared_ptr<arrow::Buffer> offsets_;
    std::shared_ptr<arrow::ArrayBuilder> value_builder_;
};
class UnPooledStructBuilder : public EmptyBuilder {
 public:
    UnPooledStructBuilder(const std::shared_ptr<arrow::DataType>& type,
                          const std::vector<std::shared_ptr<arrow::ArrayBuilder>>& field_builders,
                          arrow::MemoryPool* pool)
        : EmptyBuilder(pool), type_(type), children_(field_builders) {}

    std::shared_ptr<arrow::DataType> type() const override {
        arrow::FieldVector fields;
        fields.reserve(children_.size());
        for (size_t i = 0; i < children_.size(); i++) {
            fields.emplace_back(type_->field(i)->WithType(children_[i]->type()));
        }
        return arrow::struct_(fields);
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(null_bitmap_builder_.Finish(&null_bitmap));
        }
        std::vector<std::shared_ptr<arrow::ArrayData>> child_data(children_.size());
        for (size_t i = 0; i < children_.size(); i++) {
            ARROW_RETURN_NOT_OK(children_[i]->FinishInternal(&child_data[i]));
        }

        *out = arrow::ArrayData::Make(type(), length_, {null_bitmap}, null_count_);
        (*out)->child_data = std::move(child_data);
        Reset();
        return arrow::Status::OK();
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    std::shared_ptr<arrow::DataType> type_;
    std::vector<std::shared_ptr<arrow::ArrayBuilder>> children_;
};

class UnPooledMapBuilder : public EmptyBuilder {
 public:
    UnPooledMapBuilder(const std::shared_ptr<arrow::DataType>& type,
                       const std::shared_ptr<arrow::ArrayBuilder>& struct_builder,
                       arrow::MemoryPool* pool)
        : EmptyBuilder(pool), type_(type) {
        list_builder_ = std::make_shared<UnPooledListBuilder>(arrow::list(struct_builder->type()),
                                                              struct_builder, pool);
    }

    std::shared_ptr<arrow::DataType> type() const override {
        auto map_type = arrow::internal::checked_cast<arrow::MapType*>(type_.get());
        auto list_type =
            arrow::internal::checked_pointer_cast<arrow::ListType>(list_builder_->type());
        return std::make_shared<arrow::MapType>(arrow::field("entries", list_type->value_type()),
                                                map_type->keys_sorted());
    }

    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        return list_builder_->SetNulls(valid_bytes, length);
    }

    void IncreaseLength(int64_t length) override {
        list_builder_->IncreaseLength(length);
    }

    void SetOffsets(const std::shared_ptr<arrow::Buffer>& offsets) {
        list_builder_->SetOffsets(offsets);
    }

    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        ARROW_RETURN_NOT_OK(list_builder_->FinishInternal(out));
        (*out)->type = type();
        Reset();
        return arrow::Status::OK();
    }

    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    std::shared_ptr<arrow::DataType> type_;
    std::shared_ptr<UnPooledListBuilder> list_builder_;
};

template <typename BuilderType>
Status BuilderSetNulls(::orc::ColumnVectorBatch* column_vector_batch, BuilderType* builder) {
    if (column_vector_batch->hasNulls) {
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            builder->SetNulls(reinterpret_cast<uint8_t*>(column_vector_batch->notNull.data()),
                              column_vector_batch->numElements));
    } else {
        builder->IncreaseLength(column_vector_batch->numElements);
    }
    return Status::OK();
}

class OrcStringDictionary : public arrow::ArrayData {
 public:
    OrcStringDictionary(const std::shared_ptr<arrow::ArrayData>& data,
                        const std::shared_ptr<::orc::StringDictionary>& orc_dictionary)
        : arrow::ArrayData(*data), orc_dictionary_(orc_dictionary) {}

 private:
    const std::shared_ptr<::orc::StringDictionary> orc_dictionary_;
};

class UnPooledStringDictionaryBuilder : public EmptyBuilder {
 public:
    explicit UnPooledStringDictionaryBuilder(arrow::MemoryPool* pool)
        : EmptyBuilder(pool), pool_(pool) {}

    arrow::Status SetDictionary(const std::shared_ptr<::orc::StringDictionary>& orc_dictionary) {
        auto dict_builder =
            std::make_shared<UnPooledLargeBinaryBuilder>(arrow::large_utf8(), pool_);
        // We do not transfer buffer ownership to arrow here. The memory still
        // owned by dictionary which is hold in a shared_ptr.
        const auto& dict_offset = orc_dictionary->dictionaryOffset;
        const auto& dict_data = orc_dictionary->dictionaryBlob;
        auto offsets =
            std::make_shared<arrow::Buffer>(reinterpret_cast<const uint8_t*>(dict_offset.data()),
                                            dict_offset.size() * sizeof(int64_t));
        auto data = std::make_shared<arrow::Buffer>(
            reinterpret_cast<const uint8_t*>(dict_data.data()), dict_data.size());
        dict_builder->SetData(data);
        dict_builder->SetOffsets(offsets);
        dict_builder->IncreaseLength(dict_offset.size() - 1);
        std::shared_ptr<arrow::Array> dictionary;
        ARROW_RETURN_NOT_OK(dict_builder->Finish(&dictionary));
        dictionary_ = std::make_shared<OrcStringDictionary>(dictionary->data(), orc_dictionary);
        return arrow::Status::OK();
    }

    void SetIndices(const std::shared_ptr<arrow::Buffer>& indices) {
        indices_ = indices;
    }
    arrow::Status SetNulls(const uint8_t* valid_bytes, int64_t length) {
        ARROW_RETURN_NOT_OK(this->Reserve(length));
        this->UnsafeAppendToBitmap(valid_bytes, length);
        return arrow::Status::OK();
    }
    arrow::Status FinishInternal(std::shared_ptr<arrow::ArrayData>* out) override {
        std::shared_ptr<arrow::Buffer> null_bitmap;
        if (has_nulls_) {
            ARROW_RETURN_NOT_OK(null_bitmap_builder_.Finish(&null_bitmap));
        }
        *out = arrow::ArrayData::Make(type(), length_, {null_bitmap, indices_}, null_count_);
        (*out)->dictionary = dictionary_;
        Reset();
        return arrow::Status::OK();
    }
    std::shared_ptr<arrow::DataType> type() const override {
        static std::shared_ptr<arrow::DataType> dict_type =
            arrow::dictionary(arrow::int64(), arrow::large_utf8());
        return dict_type;
    }
    void Reset() override {
        arrow::ArrayBuilder::Reset();
    }

 private:
    arrow::MemoryPool* pool_;
    std::shared_ptr<arrow::Buffer> indices_;
    std::shared_ptr<arrow::ArrayData> dictionary_;
};

Status CheckOutOfBounds(const ::orc::StringVectorBatch* string_vector_batch) {
    int64_t total_length = 0;
    bool has_nulls = string_vector_batch->hasNulls;
    for (uint64_t i = 0; i < string_vector_batch->numElements; ++i) {
        if (has_nulls && !string_vector_batch->notNull[i]) {
            continue;
        }
        if (string_vector_batch->length[i] > std::numeric_limits<int32_t>::max()) {
            return Status::Invalid(fmt::format("index {} with length {} is out-of-bounds", i,
                                               string_vector_batch->length[i]));
        }
        total_length += string_vector_batch->length[i];
        if (total_length > std::numeric_limits<int32_t>::max()) {
            return Status::Invalid("total length is out-of-bounds");
        }
    }
    return Status::OK();
}

Result<std::shared_ptr<arrow::Buffer>> PrepareOffsetsBufferForString(
    const ::orc::StringVectorBatch* string_vector_batch, arrow::MemoryPool* pool) {
    // TODO(liancheng.lsz): length for StringVectorBatch in orc is int64, offset for string in arrow
    // array is int32, we only support int32 for now. large_string & large_binary will be supported
    // in the future.
    PAIMON_RETURN_NOT_OK(CheckOutOfBounds(string_vector_batch));
    int64_t buffer_size = (string_vector_batch->numElements + 1) * sizeof(int32_t);
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Buffer> data_buffer,
                                      arrow::AllocateBuffer(buffer_size, pool));
    auto* offsets_data = data_buffer->mutable_data_as<int32_t>();
    int32_t cur_offset = 0;
    bool has_nulls = string_vector_batch->hasNulls;
    for (size_t i = 0; i < string_vector_batch->numElements; i++) {
        int32_t tmp_length =
            (!has_nulls) ? string_vector_batch->length[i]
                         : (string_vector_batch->notNull[i] ? string_vector_batch->length[i] : 0);
        offsets_data[i] = cur_offset;
        cur_offset += tmp_length;
    }
    offsets_data[string_vector_batch->numElements] = cur_offset;
    return data_buffer;
}

Result<std::shared_ptr<arrow::Buffer>> DeepCopyDataBufferForString(
    const ::orc::StringVectorBatch* string_vector_batch, arrow::MemoryPool* pool) {
    int64_t data_size = 0;
    bool has_nulls = string_vector_batch->hasNulls;
    for (uint64_t i = 0; i < string_vector_batch->numElements; ++i) {
        if (!has_nulls || string_vector_batch->notNull[i]) {
            data_size += string_vector_batch->length[i];
        }
    }
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Buffer> data_buffer,
                                      arrow::AllocateBuffer(data_size, pool));
    char* data = data_buffer->mutable_data_as<char>();
    int32_t dest_offset = 0;
    for (uint64_t i = 0; i < string_vector_batch->numElements; ++i) {
        if (!has_nulls || string_vector_batch->notNull[i]) {
            memcpy(data + dest_offset, string_vector_batch->data[i],
                   string_vector_batch->length[i]);
            dest_offset += string_vector_batch->length[i];
        }
    }
    return data_buffer;
}

template <typename BuilderType, typename BatchType, typename ValueType>
Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedPrimitiveBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    auto typed_batch = dynamic_cast<BatchType*>(column_vector_batch);
    assert(typed_batch);
    auto builder = std::make_shared<BuilderType>(type, pool);
    builder->SetData(
        std::make_shared<OrcBackedArrowBuffer<ValueType>>(std::move(typed_batch->data)));
    PAIMON_RETURN_NOT_OK(BuilderSetNulls<BuilderType>(column_vector_batch, builder.get()));
    return builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedBooleanBuilder(
    ::orc::ColumnVectorBatch* column_vector_batch, arrow::MemoryPool* pool) {
    auto typed_batch = dynamic_cast<::orc::ByteVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto builder = std::make_shared<UnPooledBooleanBuilder>(pool);
    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->SetData(
        reinterpret_cast<uint8_t*>(typed_batch->data.data()), typed_batch->numElements));
    PAIMON_RETURN_NOT_OK(
        BuilderSetNulls<UnPooledBooleanBuilder>(column_vector_batch, builder.get()));
    return builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedDate32Builder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    auto typed_batch = dynamic_cast<::orc::LongVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto builder = std::make_shared<arrow::Date32Builder>(type, pool);

    const uint8_t* valid_bytes = typed_batch->hasNulls
                                     ? reinterpret_cast<const uint8_t*>(typed_batch->notNull.data())
                                     : nullptr;
    auto transform_iter = arrow::internal::MakeLazyRange(
        [&typed_batch](int64_t index) {
            return static_cast<int32_t>(typed_batch->data.data()[index]);
        },
        typed_batch->numElements);

    PAIMON_RETURN_NOT_OK_FROM_ARROW(
        builder->AppendValues(transform_iter.begin(), transform_iter.end(), valid_bytes));
    return builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedTimestampBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    auto typed_batch = dynamic_cast<::orc::TimestampVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto builder = std::make_shared<arrow::TimestampBuilder>(type, pool);

    const uint8_t* valid_bytes = typed_batch->hasNulls
                                     ? reinterpret_cast<const uint8_t*>(typed_batch->notNull.data())
                                     : nullptr;
    const int64_t* seconds = typed_batch->data.data();
    const int64_t* nanos = typed_batch->nanoseconds.data();
    const bool has_nulls = typed_batch->hasNulls;
    const auto* not_null = typed_batch->notNull.data();
    auto is_null = [has_nulls, not_null](int64_t index) { return has_nulls && !not_null[index]; };
    auto timestamp_type = arrow::internal::checked_pointer_cast<arrow::TimestampType>(type);
    assert(timestamp_type);
    int32_t precision = DateTimeUtils::GetPrecisionFromType(timestamp_type);
    // TODO(lisizhuo.lsz): check nano overflow in arrow
    if (precision == Timestamp::MIN_PRECISION) {
        auto transform_iter = arrow::internal::MakeLazyRange(
            [seconds, is_null](int64_t index) { return is_null(index) ? 0 : seconds[index]; },
            typed_batch->numElements);
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            builder->AppendValues(transform_iter.begin(), transform_iter.end(), valid_bytes));
        return builder;
    } else if (precision == Timestamp::MILLIS_PRECISION) {
        auto transform_iter = arrow::internal::MakeLazyRange(
            [seconds, nanos, is_null](int64_t index) {
                if (is_null(index)) {
                    return int64_t{0};
                }
                return seconds[index] *
                           DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::TimeType::MILLISECOND] +
                       nanos[index] /
                           DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::TimeType::MICROSECOND];
            },
            typed_batch->numElements);
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            builder->AppendValues(transform_iter.begin(), transform_iter.end(), valid_bytes));
        return builder;
    } else if (precision == Timestamp::DEFAULT_PRECISION) {
        auto transform_iter = arrow::internal::MakeLazyRange(
            [seconds, nanos, is_null](int64_t index) {
                if (is_null(index)) {
                    return int64_t{0};
                }
                return seconds[index] *
                           DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::TimeType::MICROSECOND] +
                       nanos[index] /
                           DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::TimeType::MILLISECOND];
            },
            typed_batch->numElements);
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            builder->AppendValues(transform_iter.begin(), transform_iter.end(), valid_bytes));
        return builder;
    } else if (precision == Timestamp::MAX_PRECISION) {
        auto transform_iter = arrow::internal::MakeLazyRange(
            [seconds, nanos, is_null](int64_t index) {
                if (is_null(index)) {
                    return int64_t{0};
                }
                return seconds[index] *
                           DateTimeUtils::CONVERSION_FACTORS[DateTimeUtils::TimeType::NANOSECOND] +
                       nanos[index];
            },
            typed_batch->numElements);
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            builder->AppendValues(transform_iter.begin(), transform_iter.end(), valid_bytes));
        return builder;
    }
    return Status::Invalid(fmt::format("invalid timestamp precision {}", precision));
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedDecimal128Builder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    auto builder = std::make_shared<arrow::Decimal128Builder>(type, pool);
    const bool has_nulls = column_vector_batch->hasNulls;
    auto decimal_type = arrow::internal::checked_cast<arrow::Decimal128Type*>(type.get());
    assert(decimal_type);
    if (decimal_type->precision() == 0 || decimal_type->precision() > 18) {
        auto typed_batch =
            arrow::internal::checked_cast<const ::orc::Decimal128VectorBatch*>(column_vector_batch);
        for (size_t i = 0; i < typed_batch->numElements; i++) {
            if (!has_nulls || typed_batch->notNull[i]) {
                int64_t high_bits = typed_batch->values[i].getHighBits();
                uint64_t low_bits = typed_batch->values[i].getLowBits();
                PAIMON_RETURN_NOT_OK_FROM_ARROW(
                    builder->Append(arrow::Decimal128(high_bits, low_bits)));
            } else {
                PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->AppendNull());
            }
        }
    } else {
        auto typed_batch =
            arrow::internal::checked_cast<const ::orc::Decimal64VectorBatch*>(column_vector_batch);
        for (size_t i = 0; i < typed_batch->numElements; i++) {
            if (!has_nulls || typed_batch->notNull[i]) {
                PAIMON_RETURN_NOT_OK_FROM_ARROW(
                    builder->Append(arrow::Decimal128(typed_batch->values[i])));
            } else {
                PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->AppendNull());
            }
        }
    }
    return builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedBinaryBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    assert(column_vector_batch->numElements > 0);
    auto typed_batch = dynamic_cast<::orc::StringVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto encoded_batch = dynamic_cast<::orc::EncodedStringVectorBatch*>(typed_batch);
    if (typed_batch->blob.size() != 0) {
        assert(!typed_batch->isEncoded);
        // condition1: orc batch is normal string, return arrow::string
        auto data = std::make_shared<OrcBackedArrowBuffer<char>>(std::move(typed_batch->blob));
        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Buffer> offsets,
                               PrepareOffsetsBufferForString(typed_batch, pool));
        auto builder = std::make_shared<UnPooledBinaryBuilder>(type, pool);
        builder->SetData(data);
        builder->SetOffsets(offsets);
        PAIMON_RETURN_NOT_OK(
            BuilderSetNulls<UnPooledBinaryBuilder>(column_vector_batch, builder.get()));
        return builder;
    } else if (encoded_batch && encoded_batch->dictionary) {
        assert(type->id() == arrow::Type::type::STRING);
        // condition2: orc batch is dict batch, return arrow::dict
        auto builder = std::make_shared<UnPooledStringDictionaryBuilder>(pool);
        PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->SetDictionary(encoded_batch->dictionary));
        auto indices =
            std::make_shared<OrcBackedArrowBuffer<int64_t>>(std::move(encoded_batch->index));
        builder->SetIndices(indices);
        PAIMON_RETURN_NOT_OK(
            BuilderSetNulls<UnPooledStringDictionaryBuilder>(column_vector_batch, builder.get()));
        return builder;
    } else {
        // condition3: disable dict batch in orc, while blob is also invalid, degrade to deep copy
        // version
        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Buffer> data,
                               DeepCopyDataBufferForString(typed_batch, pool));
        PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Buffer> offsets,
                               PrepareOffsetsBufferForString(typed_batch, pool));
        auto builder = std::make_shared<UnPooledBinaryBuilder>(type, pool);
        builder->SetData(data);
        builder->SetOffsets(offsets);
        PAIMON_RETURN_NOT_OK(
            BuilderSetNulls<UnPooledBinaryBuilder>(column_vector_batch, builder.get()));
        return builder;
    }
    return Status::Invalid("convert to arrow array failed, because of invalid string vector batch");
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeArrowBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool);

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedListBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    using OffsetType = arrow::ListType::offset_type;
    auto typed_batch = dynamic_cast<::orc::ListVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto list_type = arrow::internal::checked_cast<arrow::ListType*>(type.get());
    PAIMON_ASSIGN_OR_RAISE(
        std::shared_ptr<arrow::ArrayBuilder> elements_builder,
        MakeArrowBuilder(list_type->value_type(), typed_batch->elements.get(), pool));
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
        std::shared_ptr<arrow::Buffer> offsets,
        arrow::AllocateBuffer(sizeof(OffsetType) * (typed_batch->numElements + 1), pool));
    auto* typed_offsets = offsets->mutable_data_as<OffsetType>();
    for (size_t i = 0; i < typed_batch->numElements + 1; i++) {
        typed_offsets[i] = static_cast<OffsetType>(typed_batch->offsets[i]);
    }
    auto list_builder = std::make_shared<UnPooledListBuilder>(type, elements_builder, pool);
    list_builder->SetOffsets(std::move(offsets));
    PAIMON_RETURN_NOT_OK(
        BuilderSetNulls<UnPooledListBuilder>(column_vector_batch, list_builder.get()));
    return list_builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedStructBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    auto typed_batch = dynamic_cast<::orc::StructVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto struct_type = arrow::internal::checked_cast<arrow::StructType*>(type.get());
    std::vector<std::shared_ptr<arrow::ArrayBuilder>> children_builders;
    children_builders.reserve(typed_batch->fields.size());
    for (size_t i = 0; i < typed_batch->fields.size(); i++) {
        PAIMON_ASSIGN_OR_RAISE(
            std::shared_ptr<arrow::ArrayBuilder> child_builder,
            MakeArrowBuilder(struct_type->field(i)->type(), typed_batch->fields[i], pool));
        children_builders.push_back(child_builder);
    }
    auto struct_builder = std::make_shared<UnPooledStructBuilder>(type, children_builders, pool);
    PAIMON_RETURN_NOT_OK(
        BuilderSetNulls<UnPooledStructBuilder>(column_vector_batch, struct_builder.get()));
    return struct_builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeOrcBackedMapBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    using OffsetType = arrow::ListType::offset_type;
    auto typed_batch = dynamic_cast<::orc::MapVectorBatch*>(column_vector_batch);
    assert(typed_batch);
    auto map_type = arrow::internal::checked_cast<arrow::MapType*>(type.get());
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::ArrayBuilder> key_builder,
                           MakeArrowBuilder(map_type->key_type(), typed_batch->keys.get(), pool));
    PAIMON_ASSIGN_OR_RAISE(
        std::shared_ptr<arrow::ArrayBuilder> item_builder,
        MakeArrowBuilder(map_type->item_type(), typed_batch->elements.get(), pool));
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
        std::shared_ptr<arrow::Buffer> offsets,
        arrow::AllocateBuffer(sizeof(OffsetType) * (typed_batch->numElements + 1), pool));
    auto* typed_offsets = offsets->mutable_data_as<OffsetType>();
    for (size_t i = 0; i < typed_batch->numElements + 1; i++) {
        typed_offsets[i] = static_cast<OffsetType>(typed_batch->offsets[i]);
    }

    std::vector<std::shared_ptr<arrow::ArrayBuilder>> child_builders = {key_builder, item_builder};
    auto struct_builder =
        std::make_shared<UnPooledStructBuilder>(map_type->value_type(), child_builders, pool);
    // key cannot be null
    struct_builder->IncreaseLength(key_builder->length());

    auto map_builder = std::make_shared<UnPooledMapBuilder>(type, struct_builder, pool);
    map_builder->SetOffsets(std::move(offsets));
    PAIMON_RETURN_NOT_OK(
        BuilderSetNulls<UnPooledMapBuilder>(column_vector_batch, map_builder.get()));
    return map_builder;
}

Result<std::shared_ptr<arrow::ArrayBuilder>> MakeArrowBuilder(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* column_vector_batch,
    arrow::MemoryPool* pool) {
    if (column_vector_batch->numElements == 0) {
        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::ArrayBuilder> builder,
                                          arrow::MakeBuilder(type, pool));
        return builder;
    }
    arrow::Type::type kind = type->id();
    switch (kind) {
        case arrow::Type::type::BOOL:
            return MakeOrcBackedBooleanBuilder(column_vector_batch, pool);
        case arrow::Type::type::INT8:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::Int8Type>,
                                                 ::orc::ByteVectorBatch, int8_t>(
                type, column_vector_batch, pool);
        case arrow::Type::type::INT16:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::Int16Type>,
                                                 ::orc::ShortVectorBatch, int16_t>(
                type, column_vector_batch, pool);
        case arrow::Type::type::INT32:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::Int32Type>,
                                                 ::orc::IntVectorBatch, int32_t>(
                type, column_vector_batch, pool);

        case arrow::Type::type::INT64:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::Int64Type>,
                                                 ::orc::LongVectorBatch, int64_t>(
                type, column_vector_batch, pool);
        case arrow::Type::type::FLOAT:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::FloatType>,
                                                 ::orc::FloatVectorBatch, float>(
                type, column_vector_batch, pool);
        case arrow::Type::type::DOUBLE:
            return MakeOrcBackedPrimitiveBuilder<UnPooledPrimitiveBuilder<arrow::DoubleType>,
                                                 ::orc::DoubleVectorBatch, double>(
                type, column_vector_batch, pool);
        case arrow::Type::type::BINARY:
        case arrow::Type::type::STRING:
            return MakeOrcBackedBinaryBuilder(type, column_vector_batch, pool);
        case arrow::Type::type::DATE32:
            return MakeOrcBackedDate32Builder(type, column_vector_batch, pool);
        case arrow::Type::type::TIMESTAMP:
            return MakeOrcBackedTimestampBuilder(type, column_vector_batch, pool);
        case arrow::Type::type::DECIMAL128:
            return MakeOrcBackedDecimal128Builder(type, column_vector_batch, pool);
        case arrow::Type::type::STRUCT:
            return MakeOrcBackedStructBuilder(type, column_vector_batch, pool);
        case arrow::Type::type::LIST:
            return MakeOrcBackedListBuilder(type, column_vector_batch, pool);
        case arrow::Type::type::MAP:
            return MakeOrcBackedMapBuilder(type, column_vector_batch, pool);
        default: {
            return Status::NotImplemented("Unknown or unsupported Arrow type: ", type->ToString());
        }
    }
}

}  // namespace

Result<std::shared_ptr<arrow::Array>> OrcAdapter::AppendBatch(
    const std::shared_ptr<arrow::DataType>& type, ::orc::ColumnVectorBatch* batch,
    arrow::MemoryPool* pool) {
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::ArrayBuilder> builder,
                           MakeArrowBuilder(type, batch, pool));
    std::shared_ptr<arrow::Array> array;
    PAIMON_RETURN_NOT_OK_FROM_ARROW(builder->Finish(&array));
    PAIMON_RETURN_NOT_OK_FROM_ARROW(array->Validate());
    return array;
}

namespace {

arrow::Status WriteBatch(const arrow::Array& array, ::orc::ColumnVectorBatch* column_vector_batch);

// Make sure children of StructArray have appropriate null.
arrow::Result<std::shared_ptr<arrow::Array>> NormalizeArray(
    const std::shared_ptr<arrow::Array>& array) {
    arrow::Type::type kind = array->type_id();
    switch (kind) {
        case arrow::Type::type::STRUCT: {
            auto struct_array = arrow::internal::checked_cast<arrow::StructArray*>(array.get());
            const std::shared_ptr<arrow::Buffer> bitmap = struct_array->null_bitmap();
            std::shared_ptr<arrow::DataType> struct_type = struct_array->type();
            std::size_t size = struct_type->fields().size();
            arrow::ArrayVector new_children(size, nullptr);
            for (std::size_t i = 0; i < size; i++) {
                auto child_length = struct_array->data()->child_data[i]->length;
                auto child_offset = struct_array->data()->child_data[i]->offset;
                // field function will change length & offset in child data
                std::shared_ptr<arrow::Array> child = struct_array->field(static_cast<int32_t>(i));
                const std::shared_ptr<arrow::Buffer> child_bitmap = child->null_bitmap();
                std::shared_ptr<arrow::Buffer> final_child_bitmap;
                if (child_bitmap == nullptr && bitmap == nullptr) {
                    final_child_bitmap = nullptr;
                } else if (child_bitmap == nullptr) {
                    final_child_bitmap = bitmap;
                } else if (bitmap == nullptr) {
                    final_child_bitmap = child_bitmap;
                } else {
                    ARROW_ASSIGN_OR_RAISE(final_child_bitmap,
                                          arrow::internal::BitmapAnd(
                                              arrow::default_memory_pool(), bitmap->data(), 0,
                                              child_bitmap->data(), 0, struct_array->length(), 0));
                }
                std::shared_ptr<arrow::ArrayData> child_array_data = child->data();
                std::vector<std::shared_ptr<arrow::Buffer>> child_buffers =
                    child_array_data->buffers;
                child_buffers[0] = final_child_bitmap;
                // When slicing, we do not know the null count of the sliced range without
                // doing some computation. To avoid doing this eagerly, we set the null count
                // to -1.
                std::shared_ptr<arrow::ArrayData> new_child_array_data = arrow::ArrayData::Make(
                    child->type(), child_length, child_buffers, child_array_data->child_data,
                    child_array_data->dictionary,
                    /*null_count=*/arrow::kUnknownNullCount, child_offset);
                ARROW_ASSIGN_OR_RAISE(new_children[i],
                                      NormalizeArray(arrow::MakeArray(new_child_array_data)));
            }
            return std::make_shared<arrow::StructArray>(
                struct_type, struct_array->length(), new_children, bitmap,
                struct_array->null_count(), struct_array->offset());
        }
        case arrow::Type::type::LIST: {
            auto list_array = arrow::internal::checked_cast<arrow::ListArray*>(array.get());
            ARROW_ASSIGN_OR_RAISE(auto value_array, NormalizeArray(list_array->values()));
            return std::make_shared<arrow::ListArray>(
                list_array->type(), list_array->length(), list_array->value_offsets(), value_array,
                list_array->null_bitmap(), list_array->null_count(), list_array->offset());
        }
        case arrow::Type::type::MAP: {
            auto map_array = arrow::internal::checked_cast<arrow::MapArray*>(array.get());
            ARROW_ASSIGN_OR_RAISE(auto key_array, NormalizeArray(map_array->keys()));
            ARROW_ASSIGN_OR_RAISE(auto item_array, NormalizeArray(map_array->items()));
            return std::make_shared<arrow::MapArray>(
                map_array->type(), map_array->length(), map_array->value_offsets(), key_array,
                item_array, map_array->null_bitmap(), map_array->null_count(), map_array->offset());
        }
        default: {
            return array;
        }
    }
}

template <class DataType, class BatchType, typename Enable = void>
struct Appender {};

// Types for long/double-like Appender, that is, numeric, boolean or date32
template <typename T>
using is_generic_type = std::integral_constant<bool, arrow::is_number_type<T>::value ||
                                                         std::is_same_v<arrow::Date32Type, T> ||
                                                         arrow::is_boolean_type<T>::value>;
template <typename T, typename R = void>
using enable_if_generic = arrow::enable_if_t<is_generic_type<T>::value, R>;

// Number-like
template <class DataType, class BatchType>
struct Appender<DataType, BatchType, enable_if_generic<DataType>> {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    using ValueType = typename arrow::TypeTraits<DataType>::CType;
    arrow::Status VisitNull() {
        batch->notNull[running_orc_offset] = false;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    arrow::Status VisitValue(ValueType v) {
        batch->data[running_orc_offset] = array.Value(running_arrow_offset);
        batch->notNull[running_orc_offset] = true;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    const ArrayType& array;
    BatchType* batch;
    int64_t running_orc_offset, running_arrow_offset;
};

// Binary
template <class DataType>
struct Appender<DataType, ::orc::StringVectorBatch> {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    using COffsetType = typename arrow::TypeTraits<DataType>::OffsetType::c_type;
    arrow::Status VisitNull() {
        batch->notNull[running_orc_offset] = false;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    arrow::Status VisitValue(std::string_view v) {
        batch->notNull[running_orc_offset] = true;
        COffsetType data_length = 0;
        batch->data[running_orc_offset] = reinterpret_cast<char*>(
            const_cast<uint8_t*>(array.GetValue(running_arrow_offset, &data_length)));
        batch->length[running_orc_offset] = data_length;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    const ArrayType& array;
    ::orc::StringVectorBatch* batch;
    int64_t running_orc_offset, running_arrow_offset;
};

// Decimal
template <>
struct Appender<arrow::Decimal128Type, ::orc::Decimal64VectorBatch> {
    arrow::Status VisitNull() {
        batch->notNull[running_orc_offset] = false;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    arrow::Status VisitValue(std::string_view v) {
        batch->notNull[running_orc_offset] = true;
        const arrow::Decimal128 dec_value(array.GetValue(running_arrow_offset));
        batch->values[running_orc_offset] = static_cast<int64_t>(dec_value.low_bits());
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    const arrow::Decimal128Array& array;
    ::orc::Decimal64VectorBatch* batch;
    int64_t running_orc_offset, running_arrow_offset;
};

template <>
struct Appender<arrow::Decimal128Type, ::orc::Decimal128VectorBatch> {
    arrow::Status VisitNull() {
        batch->notNull[running_orc_offset] = false;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    arrow::Status VisitValue(std::string_view v) {
        batch->notNull[running_orc_offset] = true;
        const arrow::Decimal128 dec_value(array.GetValue(running_arrow_offset));
        batch->values[running_orc_offset] =
            ::orc::Int128(dec_value.high_bits(), dec_value.low_bits());
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    const arrow::Decimal128Array& array;
    ::orc::Decimal128VectorBatch* batch;
    int64_t running_orc_offset, running_arrow_offset;
};

// Timestamp
template <class DataType>
struct TimestampAppender {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    arrow::Status VisitNull() {
        batch->notNull[running_orc_offset] = false;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    arrow::Status VisitValue(int64_t v) {
        int64_t data = array.Value(running_arrow_offset);
        batch->notNull[running_orc_offset] = true;
        auto [second, nanosecond] = DateTimeUtils::TimestampConverter(
            data, time_type, DateTimeUtils::SECOND, DateTimeUtils::NANOSECOND);
        batch->data[running_orc_offset] = second;
        batch->nanoseconds[running_orc_offset] = nanosecond;
        running_orc_offset++;
        batch->numElements = running_orc_offset;
        running_arrow_offset++;
        return arrow::Status::OK();
    }
    DateTimeUtils::TimeType time_type = DateTimeUtils::TimeType::MICROSECOND;
    const ArrayType& array;
    ::orc::TimestampVectorBatch* batch;
    int64_t running_orc_offset, running_arrow_offset;
};

template <class DataType, class BatchType>
arrow::Status ShallowCopyGenericBatch(const arrow::Array& array,
                                      ::orc::ColumnVectorBatch* column_vector_batch) {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    using value_type = typename ArrayType::value_type;
    const auto& array_(arrow::internal::checked_cast<const ArrayType&>(array));
    auto batch = arrow::internal::checked_cast<BatchType*>(column_vector_batch);
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    int64_t length = array_.length();
    for (int64_t i = 0; i < length; i++) {
        batch->notNull[i] = array_.IsValid(i) ? static_cast<char>(1) : static_cast<char>(0);
    }
    auto* raw_values = const_cast<value_type*>(array_.raw_values());
    batch->data.setData(raw_values, sizeof(value_type) * length);
    batch->numElements = length;
    return arrow::Status::OK();
}

// static_cast from int64_t or double to itself shouldn't introduce overhead
// Please see
// https://stackoverflow.com/questions/19106826/
// can-static-cast-to-same-type-introduce-runtime-overhead
template <class DataType, class BatchType>
arrow::Status WriteGenericBatch(const arrow::Array& array,
                                ::orc::ColumnVectorBatch* column_vector_batch) {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    const auto& array_(arrow::internal::checked_cast<const ArrayType&>(array));
    auto batch = arrow::internal::checked_cast<BatchType*>(column_vector_batch);
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    Appender<DataType, BatchType> appender{array_, batch, /*orc_offset=*/0, 0};
    arrow::ArraySpanVisitor<DataType> visitor;
    ARROW_RETURN_NOT_OK(visitor.Visit(*array_.data(), &appender));
    return arrow::Status::OK();
}

template <class DataType>
arrow::Status WriteTimestampBatch(const arrow::Array& array,
                                  ::orc::ColumnVectorBatch* column_vector_batch) {
    using ArrayType = typename arrow::TypeTraits<DataType>::ArrayType;
    const auto& array_(arrow::internal::checked_cast<const ArrayType&>(array));
    auto batch = arrow::internal::checked_cast<::orc::TimestampVectorBatch*>(column_vector_batch);
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    auto timestamp_type = arrow::internal::checked_pointer_cast<DataType>(array.type());
    auto time_type = DateTimeUtils::GetTimeTypeFromArrowType(timestamp_type);
    TimestampAppender<DataType> appender{time_type, array_, batch,
                                         /*orc_offset=*/0, 0};
    arrow::ArraySpanVisitor<DataType> visitor;
    ARROW_RETURN_NOT_OK(visitor.Visit(*array_.data(), &appender));
    return arrow::Status::OK();
}

arrow::Status WriteStructBatch(const arrow::Array& array,
                               ::orc::ColumnVectorBatch* column_vector_batch) {
    std::shared_ptr<arrow::Array> array_ = arrow::MakeArray(array.data());
    auto* struct_array = arrow::internal::checked_cast<arrow::StructArray*>(array_.get());
    assert(struct_array);
    auto batch = arrow::internal::checked_cast<::orc::StructVectorBatch*>(column_vector_batch);
    std::size_t size = array.type()->fields().size();
    int64_t arrow_length = array.length();
    batch->numElements = arrow_length;
    int64_t running_arrow_offset = 0, running_orc_offset = 0;
    // First fill fields of ColumnVectorBatch
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    for (; running_arrow_offset < arrow_length; running_orc_offset++, running_arrow_offset++) {
        batch->notNull[running_orc_offset] =
            array.IsValid(running_arrow_offset) ? static_cast<char>(1) : static_cast<char>(0);
    }
    // Fill the fields
    for (std::size_t i = 0; i < size; i++) {
        batch->fields[i]->resize(arrow_length);
        ARROW_RETURN_NOT_OK(
            WriteBatch(*(struct_array->field(static_cast<int32_t>(i))), batch->fields[i]));
    }
    return arrow::Status::OK();
}

template <class ArrayType>
arrow::Status WriteListBatch(const arrow::Array& array,
                             ::orc::ColumnVectorBatch* column_vector_batch) {
    const auto& list_array(arrow::internal::checked_cast<const ArrayType&>(array));
    auto batch = arrow::internal::checked_cast<::orc::ListVectorBatch*>(column_vector_batch);
    ::orc::ColumnVectorBatch* element_batch = (batch->elements).get();
    int64_t arrow_length = array.length();
    batch->numElements = arrow_length;
    int64_t running_arrow_offset = 0, running_orc_offset = 0;
    if (running_orc_offset == 0) {
        batch->offsets[0] = 0;
    }
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    element_batch->resize(list_array.values()->length());
    ARROW_RETURN_NOT_OK(WriteBatch(*list_array.values(), element_batch));
    // TODO(liancheng.lsz): if array large list, can memcpy offsets
    for (; running_arrow_offset < arrow_length; running_orc_offset++, running_arrow_offset++) {
        if (array.IsValid(running_arrow_offset)) {
            batch->notNull[running_orc_offset] = true;
            batch->offsets[running_orc_offset + 1] =
                batch->offsets[running_orc_offset] +
                list_array.value_offset(running_arrow_offset + 1) -
                list_array.value_offset(running_arrow_offset);
        } else {
            batch->notNull[running_orc_offset] = false;
            batch->offsets[running_orc_offset + 1] = batch->offsets[running_orc_offset];
        }
    }
    return arrow::Status::OK();
}

arrow::Status WriteMapBatch(const arrow::Array& array,
                            ::orc::ColumnVectorBatch* column_vector_batch) {
    const auto& map_array(arrow::internal::checked_cast<const arrow::MapArray&>(array));
    auto batch = arrow::internal::checked_cast<::orc::MapVectorBatch*>(column_vector_batch);
    ::orc::ColumnVectorBatch* key_batch = (batch->keys).get();
    ::orc::ColumnVectorBatch* element_batch = (batch->elements).get();
    std::shared_ptr<arrow::Array> key_array = map_array.keys();
    std::shared_ptr<arrow::Array> element_array = map_array.items();
    int64_t arrow_length = array.length();
    batch->numElements = arrow_length;
    int64_t running_arrow_offset = 0, running_orc_offset = 0;
    if (running_orc_offset == 0) {
        batch->offsets[0] = 0;
    }
    if (array.null_count()) {
        batch->hasNulls = true;
    }
    key_batch->resize(key_array->length());
    element_batch->resize(element_array->length());
    ARROW_RETURN_NOT_OK(WriteBatch(*key_array, key_batch));
    ARROW_RETURN_NOT_OK(WriteBatch(*element_array, element_batch));
    for (; running_arrow_offset < arrow_length; running_orc_offset++, running_arrow_offset++) {
        if (array.IsValid(running_arrow_offset)) {
            batch->notNull[running_orc_offset] = true;
            batch->offsets[running_orc_offset + 1] =
                batch->offsets[running_orc_offset] +
                map_array.value_offset(running_arrow_offset + 1) -
                map_array.value_offset(running_arrow_offset);
        } else {
            batch->notNull[running_orc_offset] = false;
            batch->offsets[running_orc_offset + 1] = batch->offsets[running_orc_offset];
        }
    }
    return arrow::Status::OK();
}

arrow::Status WriteBatch(const arrow::Array& array, ::orc::ColumnVectorBatch* column_vector_batch) {
    arrow::Type::type kind = array.type_id();
    column_vector_batch->numElements = 0;
    switch (kind) {
        case arrow::Type::type::BOOL:
            return WriteGenericBatch<arrow::BooleanType, ::orc::ByteVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::INT8:
            return ShallowCopyGenericBatch<arrow::Int8Type, ::orc::ByteVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::INT16:
            return ShallowCopyGenericBatch<arrow::Int16Type, ::orc::ShortVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::INT32:
            return ShallowCopyGenericBatch<arrow::Int32Type, ::orc::IntVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::INT64:
            return ShallowCopyGenericBatch<arrow::Int64Type, ::orc::LongVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::FLOAT:
            return ShallowCopyGenericBatch<arrow::FloatType, ::orc::FloatVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::DOUBLE:
            return ShallowCopyGenericBatch<arrow::DoubleType, ::orc::DoubleVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::BINARY:
            return WriteGenericBatch<arrow::BinaryType, ::orc::StringVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::LARGE_BINARY:
            return WriteGenericBatch<arrow::LargeBinaryType, ::orc::StringVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::STRING:
            return WriteGenericBatch<arrow::StringType, ::orc::StringVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::DATE32:
            return WriteGenericBatch<arrow::Date32Type, ::orc::LongVectorBatch>(
                array, column_vector_batch);
        case arrow::Type::type::TIMESTAMP:
            return WriteTimestampBatch<arrow::TimestampType>(array, column_vector_batch);
        case arrow::Type::type::DECIMAL128: {
            int32_t precision =
                arrow::internal::checked_cast<arrow::Decimal128Type*>(array.type().get())
                    ->precision();
            if (precision > 18 || precision == 0) {
                return WriteGenericBatch<arrow::Decimal128Type, ::orc::Decimal128VectorBatch>(
                    array, column_vector_batch);
            } else {
                return WriteGenericBatch<arrow::Decimal128Type, ::orc::Decimal64VectorBatch>(
                    array, column_vector_batch);
            }
        }
        case arrow::Type::type::STRUCT:
            return WriteStructBatch(array, column_vector_batch);
        case arrow::Type::type::LIST:
            return WriteListBatch<arrow::ListArray>(array, column_vector_batch);
        case arrow::Type::type::MAP:
            return WriteMapBatch(array, column_vector_batch);
        default: {
            return arrow::Status::NotImplemented("Unknown or unsupported Arrow type: ",
                                                 array.type()->ToString());
        }
    }
}

void SetAttributes(const std::shared_ptr<arrow::Field>& field, ::orc::Type* type) {
    if (field->HasMetadata()) {
        const auto& metadata = field->metadata();
        for (int64_t i = 0; i < metadata->size(); i++) {
            type->setAttribute(metadata->key(i), metadata->value(i));
        }
    }
}

arrow::Result<std::unique_ptr<::orc::Type>> GetOrcType(const arrow::DataType& type) {
    arrow::Type::type kind = type.id();
    switch (kind) {
        case arrow::Type::type::BOOL:
            return ::orc::createPrimitiveType(::orc::TypeKind::BOOLEAN);
        case arrow::Type::type::INT8:
            return ::orc::createPrimitiveType(::orc::TypeKind::BYTE);
        case arrow::Type::type::INT16:
            return ::orc::createPrimitiveType(::orc::TypeKind::SHORT);
        case arrow::Type::type::INT32:
            return ::orc::createPrimitiveType(::orc::TypeKind::INT);
        case arrow::Type::type::INT64:
            return ::orc::createPrimitiveType(::orc::TypeKind::LONG);
        case arrow::Type::type::FLOAT:
            return ::orc::createPrimitiveType(::orc::TypeKind::FLOAT);
        case arrow::Type::type::DOUBLE:
            return ::orc::createPrimitiveType(::orc::TypeKind::DOUBLE);
        // Use STRING instead of VARCHAR for now, both use UTF-8
        case arrow::Type::type::STRING:
            return ::orc::createPrimitiveType(::orc::TypeKind::STRING);
        case arrow::Type::type::BINARY:
        case arrow::Type::type::LARGE_BINARY:
            return ::orc::createPrimitiveType(::orc::TypeKind::BINARY);
        case arrow::Type::type::DATE32:
            return ::orc::createPrimitiveType(::orc::TypeKind::DATE);
        case arrow::Type::type::TIMESTAMP: {
            const auto& timestamp_type =
                arrow::internal::checked_cast<const arrow::TimestampType&>(type);
            if (timestamp_type.timezone().empty()) {
                return ::orc::createPrimitiveType(::orc::TypeKind::TIMESTAMP);
            }
            return ::orc::createPrimitiveType(::orc::TypeKind::TIMESTAMP_INSTANT);
        }
        case arrow::Type::type::DECIMAL128: {
            const auto precision = static_cast<uint64_t>(
                arrow::internal::checked_cast<const arrow::Decimal128Type&>(type).precision());
            const auto scale = static_cast<uint64_t>(
                arrow::internal::checked_cast<const arrow::Decimal128Type&>(type).scale());
            return ::orc::createDecimalType(precision, scale);
        }
        case arrow::Type::type::LIST: {
            const auto& value_field =
                arrow::internal::checked_cast<const arrow::BaseListType&>(type).value_field();
            ARROW_ASSIGN_OR_RAISE(auto orc_subtype, paimon::orc::GetOrcType(*value_field->type()));
            SetAttributes(value_field, orc_subtype.get());
            return ::orc::createListType(std::move(orc_subtype));
        }
        case arrow::Type::type::STRUCT: {
            std::unique_ptr<::orc::Type> out_type = ::orc::createStructType();
            arrow::FieldVector arrow_fields =
                arrow::internal::checked_cast<const arrow::StructType&>(type).fields();
            for (auto& arrow_field : arrow_fields) {
                std::string field_name = arrow_field->name();
                ARROW_ASSIGN_OR_RAISE(auto orc_subtype, GetOrcType(*arrow_field->type()));
                SetAttributes(arrow_field, orc_subtype.get());
                out_type->addStructField(field_name, std::move(orc_subtype));
            }
            return out_type;
        }
        case arrow::Type::type::MAP: {
            const auto& key_field =
                arrow::internal::checked_cast<const arrow::MapType&>(type).key_field();
            const auto& item_field =
                arrow::internal::checked_cast<const arrow::MapType&>(type).item_field();
            ARROW_ASSIGN_OR_RAISE(auto key_orc_type, GetOrcType(*key_field->type()));
            ARROW_ASSIGN_OR_RAISE(auto item_orc_type, GetOrcType(*item_field->type()));
            SetAttributes(key_field, key_orc_type.get());
            SetAttributes(item_field, item_orc_type.get());
            return ::orc::createMapType(std::move(key_orc_type), std::move(item_orc_type));
        }
        default: {
            return arrow::Status::NotImplemented("Unknown or unsupported Arrow type: ",
                                                 type.ToString());
        }
    }
}
}  // namespace

Status OrcAdapter::WriteBatch(const std::shared_ptr<arrow::Array>& array,
                              ::orc::ColumnVectorBatch* column_vector_batch) {
    PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> normal_array,
                                      NormalizeArray(array));
    if (static_cast<uint64_t>(normal_array->length()) > column_vector_batch->capacity) {
        return Status::Invalid(
            fmt::format("need to copy {} rows of arrow array, while orc batch has only {} capacity",
                        normal_array->length(), column_vector_batch->capacity));
    }
    uint64_t num_written_elements = normal_array->length();
    if (num_written_elements > 0) {
        PAIMON_RETURN_NOT_OK_FROM_ARROW(
            paimon::orc::WriteBatch(*normal_array, column_vector_batch));
    }
    column_vector_batch->numElements = num_written_elements;
    return Status::OK();
}

Result<std::shared_ptr<arrow::DataType>> OrcAdapter::GetArrowType(const ::orc::Type* type) {
    // When subselecting fields on read, orc will set some nodes to nullptr,
    // so we need to check for nullptr before progressing
    if (type == nullptr) {
        return arrow::null();
    }
    ::orc::TypeKind kind = type->getKind();
    const auto subtype_count = static_cast<int32_t>(type->getSubtypeCount());

    switch (kind) {
        case ::orc::BOOLEAN:
            return arrow::boolean();
        case ::orc::BYTE:
            return arrow::int8();
        case ::orc::SHORT:
            return arrow::int16();
        case ::orc::INT:
            return arrow::int32();
        case ::orc::LONG:
            return arrow::int64();
        case ::orc::FLOAT:
            return arrow::float32();
        case ::orc::DOUBLE:
            return arrow::float64();
        case ::orc::CHAR:
        case ::orc::VARCHAR:
        case ::orc::STRING:
            return arrow::utf8();
        case ::orc::BINARY:
            return arrow::binary();
        case ::orc::TIMESTAMP:
            // Values of TIMESTAMP type are stored in the writer timezone in the Orc file.
            // Values are read back in the reader timezone. However, the writer timezone
            // information in the Orc stripe footer is optional and may be missing. What is
            // more, stripes in the same Orc file may have different writer timezones (though
            // unlikely). So we cannot tell the exact timezone of values read back in the
            // arrow::TimestampArray. In the adapter implementations, we set both writer and
            // reader timezone to UTC to avoid any conversion so users can get the same values
            // as written. To get rid of this burden, TIMESTAMP_INSTANT type is always preferred
            // over TIMESTAMP type.
            // for timestamp type, precision info is missing from orc type
            return timestamp(arrow::TimeUnit::NANO);
        case ::orc::TIMESTAMP_INSTANT: {
            auto timezone = DateTimeUtils::GetLocalTimezoneName();
            return timestamp(arrow::TimeUnit::NANO, timezone);
        }
        case ::orc::DATE:
            return arrow::date32();
        case ::orc::DECIMAL: {
            const auto precision = static_cast<int32_t>(type->getPrecision());
            const auto scale = static_cast<int32_t>(type->getScale());
            return arrow::decimal128(precision, scale);
        }
        case ::orc::LIST: {
            if (subtype_count != 1) {
                return Status::TypeError("Invalid Orc List type");
            }
            PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> elem_field,
                                   GetArrowField("item", type->getSubtype(0)));
            return list(std::move(elem_field));
        }
        case ::orc::MAP: {
            if (subtype_count != 2) {
                return Status::TypeError("Invalid Orc Map type");
            }
            PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> key_field,
                                   GetArrowField("key", type->getSubtype(0), /*nullable=*/false));
            PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> value_field,
                                   GetArrowField("value", type->getSubtype(1)));
            return std::make_shared<arrow::MapType>(std::move(key_field), std::move(value_field));
        }
        case ::orc::STRUCT: {
            arrow::FieldVector fields(subtype_count);
            for (int32_t child = 0; child < subtype_count; ++child) {
                const auto& name = type->getFieldName(child);
                PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::Field> elem_field,
                                       GetArrowField(name, type->getSubtype(child)));
                fields[child] = std::move(elem_field);
            }
            return arrow::struct_(std::move(fields));
        }
        default:
            return Status::TypeError("Unknown Orc type kind: ", type->toString());
    }
}

Result<std::unique_ptr<::orc::Type>> OrcAdapter::GetOrcType(const arrow::Schema& schema) {
    int32_t numFields = schema.num_fields();
    std::unique_ptr<::orc::Type> out_type = ::orc::createStructType();
    for (int32_t i = 0; i < numFields; i++) {
        const auto& field = schema.field(i);
        PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::unique_ptr<::orc::Type> orc_subtype,
                                          paimon::orc::GetOrcType(*field->type()));
        SetAttributes(field, orc_subtype.get());
        out_type->addStructField(field->name(), std::move(orc_subtype));
    }
    return out_type;
}

Result<std::shared_ptr<const arrow::KeyValueMetadata>> OrcAdapter::GetFieldMetadata(
    const ::orc::Type* type) {
    if (type == nullptr) {
        return std::shared_ptr<const arrow::KeyValueMetadata>();
    }
    const auto keys = type->getAttributeKeys();
    if (keys.empty()) {
        return std::shared_ptr<const arrow::KeyValueMetadata>();
    }
    auto metadata = std::make_shared<arrow::KeyValueMetadata>();
    for (const auto& key : keys) {
        metadata->Append(key, type->getAttributeValue(key));
    }
    return std::const_pointer_cast<const arrow::KeyValueMetadata>(metadata);
}

Result<std::shared_ptr<arrow::Field>> OrcAdapter::GetArrowField(const std::string& name,
                                                                const ::orc::Type* type,
                                                                bool nullable) {
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType> arrow_type, GetArrowType(type));
    PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<const arrow::KeyValueMetadata> metadata,
                           GetFieldMetadata(type));
    return field(name, std::move(arrow_type), nullable, std::move(metadata));
}

}  // namespace paimon::orc
