Gabriel39 commented on code in PR #69668:
URL: https://github.com/apache/doris/pull/69668#discussion_r4386483798


##########
be/src/format_v2/arrow/arrow_stream_reader.cpp:
##########
@@ -0,0 +1,500 @@
+// 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 "format_v2/arrow/arrow_stream_reader.h"
+
+#include <arrow/array.h>
+#include <arrow/buffer.h>
+#include <arrow/builder.h>
+#include <arrow/compute/api.h>
+#include <arrow/io/interfaces.h>
+#include <arrow/ipc/reader.h>
+#include <arrow/record_batch.h>
+#include <arrow/type.h>
+
+#include <utility>
+
+#include "core/block/block.h"
+#include "core/column/column_fixed_length_object.h"
+#include "core/data_type/data_type_agg_state.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_struct.h"
+#include "format/arrow/arrow_array_normalizer.h"
+#include "format/arrow/arrow_row_batch.h"
+#include "format/arrow/arrow_utils.h"
+#include "format/parquet/arrow_memory_pool.h"
+#include "format_v2/column_mapper.h"
+#include "format_v2/materialized_reader_util.h"
+#include "io/fs/tracing_file_reader.h"
+#include "runtime/descriptors.h"
+#include "runtime/file_scan_profile.h"
+#include "runtime/runtime_state.h"
+
+namespace doris::format::arrow_stream {
+
+namespace {
+
+class ArrowLoadSchemaConvertor final : public DorisArrowSchemaConvertor {
+public:
+    using DorisArrowSchemaConvertor::DorisArrowSchemaConvertor;
+
+    Status convert_to_arrow_type(const DataTypePtr& origin_type,
+                                 std::shared_ptr<arrow::DataType>* result) 
const override {
+        const auto type = remove_nullable(origin_type);
+        // VARIANT output may be UTF-8, but its input SerDe requires the 
native binary carrier,
+        // including when VARIANT is nested in an ARRAY, MAP, or STRUCT.
+        if (type->get_primitive_type() == TYPE_VARIANT) {
+            *result = arrow::struct_({arrow::field("metadata", 
arrow::binary(), false),
+                                      arrow::field("value", arrow::binary(), 
false)});
+            return Status::OK();
+        }
+        if (type->get_primitive_type() == TYPE_AGG_STATE) {
+            const auto& state = assert_cast<const DataTypeAggState&>(*type);
+            const auto serialized_type = 
remove_nullable(state.get_serialized_type());
+            // FixedLengthObject's type omits its width. The serialization 
column owns that
+            // contract (e.g. count(INT NOT NULL) uses eight bytes), not the 
runtime state size.
+            if (serialized_type->get_primitive_type() == 
TYPE_FIXED_LENGTH_OBJECT) {
+                const auto column = state.create_column();
+                const auto width = assert_cast<const 
ColumnFixedLengthObject&>(*column).item_size();
+                *result = arrow::fixed_size_binary(cast_set<int32_t>(width));
+                return Status::OK();
+            }
+            return convert_to_arrow_type(serialized_type, result);
+        }
+        return DorisArrowSchemaConvertor::convert_to_arrow_type(type, result);
+    }
+};
+
+arrow::Result<std::shared_ptr<arrow::DataType>> input_binding_type(
+        const std::shared_ptr<arrow::DataType>& wire,
+        const std::shared_ptr<arrow::DataType>& declared, const DataTypePtr& 
doris_type) {
+    if (wire->Equals(declared)) {
+        return declared;
+    }
+    // SerDe owns datetime rounding and naive/aware interpretation. Casting 
timestamp[us]
+    // to the declared timestamp[ms] first would reject .123789 instead of 
rounding to .124.
+    if (wire->id() == arrow::Type::TIMESTAMP && declared->id() == 
arrow::Type::TIMESTAMP) {
+        return wire;
+    }
+    // DATE64 SerDe checks whole days and the Doris year range; DATE32 
rebinding would bypass it.
+    if (wire->id() == arrow::Type::DATE64 && declared->id() == 
arrow::Type::DATE32) {
+        return wire;
+    }
+    const auto type = 
remove_nullable(get_serialized_type(remove_nullable(doris_type)));
+    // A native VARIANT carrier is a logical leaf, not a Doris STRUCT. 
Preserve its storage or
+    // extension unchanged and let its SerDe validate the carrier shape and 
encoded payload.
+    if (type->get_primitive_type() == TYPE_VARIANT) {
+        return wire;
+    }
+    // IPv4 is a 32-bit bit pattern, including addresses above INT32_MAX. 
Numeric INT columns
+    // still use the checked conversion below instead of inheriting this 
storage exception.
+    if (type->get_primitive_type() == TYPE_IPV4 && wire->id() == 
arrow::Type::UINT32) {
+        return wire;
+    }
+    if (declared->id() == arrow::Type::LIST &&
+        (wire->id() == arrow::Type::LIST || wire->id() == 
arrow::Type::LARGE_LIST ||
+         wire->id() == arrow::Type::FIXED_SIZE_LIST)) {
+        const auto& source = static_cast<const arrow::BaseListType&>(*wire);
+        const auto& target = static_cast<const arrow::ListType&>(*declared);
+        const auto& array = assert_cast<const DataTypeArray&>(*type);
+        ARROW_ASSIGN_OR_RAISE(auto child,
+                              input_binding_type(source.value_type(), 
target.value_type(),
+                                                 array.get_nested_type()));
+        return arrow::list(source.value_field()->WithType(child));
+    }
+    if (wire->id() == arrow::Type::MAP && declared->id() == arrow::Type::MAP) {
+        const auto& source = static_cast<const arrow::MapType&>(*wire);
+        const auto& target = static_cast<const arrow::MapType&>(*declared);
+        const auto& map = assert_cast<const DataTypeMap&>(*type);
+        ARROW_ASSIGN_OR_RAISE(auto key, input_binding_type(source.key_type(), 
target.key_type(),
+                                                           
map.get_key_type()));
+        ARROW_ASSIGN_OR_RAISE(auto item, 
input_binding_type(source.item_type(), target.item_type(),
+                                                            
map.get_value_type()));
+        return 
std::make_shared<arrow::MapType>(source.key_field()->WithType(key),
+                                                
source.item_field()->WithType(item),
+                                                source.keys_sorted());
+    }
+    if (wire->id() == arrow::Type::STRUCT && declared->id() == 
arrow::Type::STRUCT) {
+        if (wire->num_fields() != declared->num_fields()) {
+            return arrow::Status::Invalid(
+                    "Arrow STRUCT field count does not match the load schema");
+        }
+        const auto& structure = assert_cast<const DataTypeStruct&>(*type);
+        arrow::FieldVector fields;
+        for (int i = 0; i < wire->num_fields(); ++i) {
+            ARROW_ASSIGN_OR_RAISE(auto child, 
input_binding_type(wire->field(i)->type(),
+                                                                 
declared->field(i)->type(),
+                                                                 
structure.get_elements()[i]));
+            // Struct SerDe binds children by position. Arrow casts bind by 
name, so retain the
+            // wire names to prevent a case/name difference from synthesizing 
NULL children.
+            fields.push_back(wire->field(i)->WithType(child));
+        }
+        return arrow::struct_(fields);
+    }
+    return declared;
+}
+
+arrow::Result<std::shared_ptr<arrow::Array>> mask_hidden_children(
+        std::shared_ptr<arrow::Array> array, const 
std::shared_ptr<arrow::DataType>& binding,
+        arrow::compute::ExecContext* context);
+
+arrow::Result<std::shared_ptr<arrow::Array>> mask_struct_children(
+        const std::shared_ptr<arrow::Array>& array, const 
std::shared_ptr<arrow::DataType>& binding,
+        arrow::compute::ExecContext* context) {
+    const auto& structure = static_cast<const arrow::StructArray&>(*array);
+    auto data = array->data()->Copy();
+    for (int i = 0; i < structure.num_fields(); ++i) {
+        // A NULL struct can have valid child payloads. Combine ancestor 
validity before
+        // checked casts can inspect those logically hidden values.
+        ARROW_ASSIGN_OR_RAISE(auto child, structure.GetFlattenedField(i, 
context->memory_pool()));
+        ARROW_ASSIGN_OR_RAISE(
+                child, mask_hidden_children(std::move(child), 
binding->field(i)->type(), context));
+        data->child_data[i] = child->data();
+    }
+    return arrow::MakeArray(data);
+}
+
+arrow::Result<std::shared_ptr<arrow::Array>> compact_null_containers(
+        std::shared_ptr<arrow::Array> array, arrow::compute::ExecContext* 
context) {
+    if (array->null_count() == 0) {
+        return array;
+    }
+    // NULL lists/maps can reference nonempty child ranges. Take drops those 
ranges without
+    // making map keys nullable, and masks the hidden children of fixed-size 
lists.
+    arrow::Int64Builder indices(context->memory_pool());
+    ARROW_RETURN_NOT_OK(indices.Reserve(array->length()));
+    for (int64_t i = 0; i < array->length(); ++i) {
+        ARROW_RETURN_NOT_OK(array->IsNull(i) ? indices.AppendNull() : 
indices.Append(i));
+    }
+    ARROW_ASSIGN_OR_RAISE(auto positions, indices.Finish());
+    ARROW_ASSIGN_OR_RAISE(
+            auto compacted,
+            arrow::compute::Take(array, positions, 
arrow::compute::TakeOptions::NoBoundsCheck(),

Review Comment:
   Fixed in 213c27920f. NULL fixed-size lists are first viewed with 64-bit 
variable offsets, retaining the original child field contract and logical 
parent validity. The existing tracked Take then drops entire NULL-parent ranges 
instead of synthesizing NULLs inside non-nullable STRUCT fields or native 
VARIANT carriers. Final LIST offset narrowing remains checked.
   
   Added load-level cases for the original non-nullable STRUCT reproduction, 
all-NULL parents, hidden overflowing STRUCT values with width two, and native 
VARIANT storage/extensions. All 221 focused ASAN tests and six regression 
suites passed with the latest locally built BE. clang-format 16, clang-tidy, 
and build hygiene also passed.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to