dalingmeng commented on code in PR #340:
URL: https://github.com/apache/paimon-cpp/pull/340#discussion_r4023120854


##########
src/paimon/common/utils/arrow/arrow_utils.cpp:
##########
@@ -90,6 +89,153 @@ bool HasUndeclaredDictionaryChild(const 
std::shared_ptr<arrow::DataType>& type,
     return false;
 }
 
+// Positions in the current array which remain visible through every nullable 
ancestor.
+struct VisibleRange {
+    int64_t offset;
+    int64_t length;
+};
+
+using VisibleRanges = std::vector<VisibleRange>;
+
+void AppendVisibleRange(int64_t offset, int64_t length, VisibleRanges* ranges) 
{
+    if (length == 0) {
+        return;
+    }
+    if (!ranges->empty()) {
+        VisibleRange& last = ranges->back();
+        if (last.offset + last.length == offset) {
+            last.length += length;
+            return;
+        }
+    }
+    ranges->push_back({offset, length});
+}
+
+Status NullabilityMismatch(const arrow::Field& field) {
+    return Status::Invalid(fmt::format(
+        "CheckNullabilityMatch failed, field {} not nullable while data have 
null value",
+        field.name()));
+}
+
+Result<VisibleRanges> IntersectWithValidity(const arrow::Array& array,
+                                            const VisibleRanges& 
visible_ranges,
+                                            const arrow::Field& field) {
+    const bool nullable = field.nullable();
+    if (visible_ranges.empty()) {
+        return VisibleRanges{};
+    }
+
+    const int64_t null_count = array.null_count();
+    if (null_count == 0) {
+        return visible_ranges;
+    }
+    if (null_count == array.length()) {
+        if (!nullable) {
+            return NullabilityMismatch(field);
+        }
+        return VisibleRanges{};
+    }
+
+    VisibleRanges valid_ranges;
+    for (const VisibleRange& range : visible_ranges) {
+        int64_t run_start = -1;
+        const int64_t range_end = range.offset + range.length;
+        for (int64_t i = range.offset; i < range_end; ++i) {
+            if (array.IsValid(i)) {
+                if (run_start == -1) {
+                    run_start = i;
+                }
+            } else {
+                if (!nullable) {
+                    return NullabilityMismatch(field);
+                }
+                if (run_start != -1) {
+                    AppendVisibleRange(run_start, i - run_start, 
&valid_ranges);
+                    run_start = -1;
+                }
+            }
+        }
+        if (run_start != -1) {
+            AppendVisibleRange(run_start, range_end - run_start, 
&valid_ranges);
+        }
+    }
+    return valid_ranges;
+}
+
+template <typename ListArray>
+VisibleRanges GetVisibleValueRanges(const ListArray& array,
+                                    const VisibleRanges& 
visible_parent_ranges) {
+    VisibleRanges value_ranges;
+    for (const VisibleRange& range : visible_parent_ranges) {
+        int64_t value_offset = array.value_offset(range.offset);
+        int64_t value_end = array.value_offset(range.offset + range.length);
+        AppendVisibleRange(value_offset, value_end - value_offset, 
&value_ranges);
+    }
+    return value_ranges;
+}
+
+Status CheckFieldNullability(const std::shared_ptr<arrow::Field>& field,
+                             const std::shared_ptr<arrow::Array>& data,
+                             const VisibleRanges& visible_ranges) {
+    const std::shared_ptr<arrow::DataType>& type = field->type();
+    const bool needs_child_validation =
+        type->id() == arrow::Type::STRUCT || type->id() == arrow::Type::LIST ||
+        type->id() == arrow::Type::FIXED_SIZE_LIST || type->id() == 
arrow::Type::MAP;
+    if (!needs_child_validation &&
+        (field->nullable() || visible_ranges.empty() || data->null_count() == 
0)) {
+        return Status::OK();
+    }
+
+    PAIMON_ASSIGN_OR_RAISE(VisibleRanges valid_ranges,
+                           IntersectWithValidity(*data, visible_ranges, 
*field));
+
+    if (type->id() == arrow::Type::STRUCT) {
+        auto struct_type = checked_pointer_cast<arrow::StructType>(type);
+        auto struct_array = checked_pointer_cast<arrow::StructArray>(data);
+        for (int32_t i = 0; i < struct_type->num_fields(); ++i) {
+            PAIMON_RETURN_NOT_OK(
+                CheckFieldNullability(struct_type->field(i), 
struct_array->field(i), valid_ranges));
+        }
+    } else if (type->id() == arrow::Type::LIST) {
+        auto list_type = checked_pointer_cast<arrow::ListType>(type);
+        auto list_array = checked_pointer_cast<arrow::ListArray>(data);
+        VisibleRanges value_ranges = GetVisibleValueRanges(*list_array, 
valid_ranges);
+        PAIMON_RETURN_NOT_OK(
+            CheckFieldNullability(list_type->value_field(), 
list_array->values(), value_ranges));
+    } else if (type->id() == arrow::Type::FIXED_SIZE_LIST) {
+        auto vector_type = 
checked_pointer_cast<arrow::FixedSizeListType>(type);
+        auto vector_array = 
checked_pointer_cast<arrow::FixedSizeListArray>(data);
+        int32_t vector_length = vector_type->list_size();
+        int64_t required_values = (vector_array->offset() + 
vector_array->length()) * vector_length;
+        if (vector_array->values()->length() < required_values) {
+            return Status::Invalid(fmt::format(
+                "VECTOR field {} is invalid: VECTOR holds {} elements while {} 
rows of dimension "
+                "{} require {}",
+                field->name(), vector_array->values()->length(), 
vector_array->length(),
+                vector_length, required_values));
+        }
+
+        VisibleRanges value_ranges = GetVisibleValueRanges(*vector_array, 
valid_ranges);
+        // Paimon VECTOR values cannot contain null elements, irrespective of 
the Arrow child
+        // field's declared nullability.
+        std::shared_ptr<arrow::Field> value_field = 
vector_type->value_field()->WithNullable(false);

Review Comment:
   Nit / non-blocking: I noticed this replaces the previous delegation to 
VectorUtils::ValidateVectorElements with an inline implementation. Two small 
things I wanted to flag for your consideration (not required — up to you):
   Error message: the inline version uses the generic nullability message, 
whereas the original ValidateVectorElements reported VECTOR cannot contain null 
elements, found one at row {i} position {j}, which pinpoints the offending 
element. That's handy when tracking down bad data, so this is a slight 
regression in diagnostics.
   Read vs. write paths: the read path 
([vector_file_batch_reader.cpp:121,175](file:///home/menglingda.mld/workspace/apache/paimon-cpp/src/paimon/core/io/vector_file_batch_reader.cpp#L121))
 still calls ValidateVectorElements, so the same VECTOR with a null element now 
surfaces different messages depending on whether it's being read or written. 
Functionally fine — just a little inconsistent on the diagnostics side.
   If you'd like to converge these, one low-cost option would be to add an 
overload of ValidateVectorElements that takes the visible ranges: the write 
path passes the ranges through, the read path keeps using the existing 
overload, and both sides share one implementation with a consistent error 
message. That said, if you feel the current implementation is good enough, feel 
free to disregard this.



-- 
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]

Reply via email to