|
39 | 39 | #include "arrow/util/compression.h" |
40 | 40 | #include "fmt/format.h" |
41 | 41 | #include "paimon/common/utils/arrow/status_utils.h" |
42 | | -#include "paimon/common/utils/arrow/vector_utils.h" |
43 | 42 | #include "paimon/common/utils/checked_cast.h" |
44 | 43 | #include "paimon/common/utils/string_utils.h" |
45 | 44 | #include "paimon/core/casting/casting_utils.h" |
@@ -97,6 +96,153 @@ bool HasUndeclaredDictionaryChild(const std::shared_ptr<arrow::DataType>& type, |
97 | 96 | return false; |
98 | 97 | } |
99 | 98 |
|
| 99 | +// Positions in the current array which remain visible through every nullable ancestor. |
| 100 | +struct VisibleRange { |
| 101 | + int64_t offset; |
| 102 | + int64_t length; |
| 103 | +}; |
| 104 | + |
| 105 | +using VisibleRanges = std::vector<VisibleRange>; |
| 106 | + |
| 107 | +void AppendVisibleRange(int64_t offset, int64_t length, VisibleRanges* ranges) { |
| 108 | + if (length == 0) { |
| 109 | + return; |
| 110 | + } |
| 111 | + if (!ranges->empty()) { |
| 112 | + VisibleRange& last = ranges->back(); |
| 113 | + if (last.offset + last.length == offset) { |
| 114 | + last.length += length; |
| 115 | + return; |
| 116 | + } |
| 117 | + } |
| 118 | + ranges->push_back({offset, length}); |
| 119 | +} |
| 120 | + |
| 121 | +Status NullabilityMismatch(const arrow::Field& field) { |
| 122 | + return Status::Invalid(fmt::format( |
| 123 | + "CheckNullabilityMatch failed, field {} not nullable while data have null value", |
| 124 | + field.name())); |
| 125 | +} |
| 126 | + |
| 127 | +Result<VisibleRanges> IntersectWithValidity(const arrow::Array& array, |
| 128 | + const VisibleRanges& visible_ranges, |
| 129 | + const arrow::Field& field) { |
| 130 | + const bool nullable = field.nullable(); |
| 131 | + if (visible_ranges.empty()) { |
| 132 | + return VisibleRanges{}; |
| 133 | + } |
| 134 | + |
| 135 | + const int64_t null_count = array.null_count(); |
| 136 | + if (null_count == 0) { |
| 137 | + return visible_ranges; |
| 138 | + } |
| 139 | + if (null_count == array.length()) { |
| 140 | + if (!nullable) { |
| 141 | + return NullabilityMismatch(field); |
| 142 | + } |
| 143 | + return VisibleRanges{}; |
| 144 | + } |
| 145 | + |
| 146 | + VisibleRanges valid_ranges; |
| 147 | + for (const VisibleRange& range : visible_ranges) { |
| 148 | + int64_t run_start = -1; |
| 149 | + const int64_t range_end = range.offset + range.length; |
| 150 | + for (int64_t i = range.offset; i < range_end; ++i) { |
| 151 | + if (array.IsValid(i)) { |
| 152 | + if (run_start == -1) { |
| 153 | + run_start = i; |
| 154 | + } |
| 155 | + } else { |
| 156 | + if (!nullable) { |
| 157 | + return NullabilityMismatch(field); |
| 158 | + } |
| 159 | + if (run_start != -1) { |
| 160 | + AppendVisibleRange(run_start, i - run_start, &valid_ranges); |
| 161 | + run_start = -1; |
| 162 | + } |
| 163 | + } |
| 164 | + } |
| 165 | + if (run_start != -1) { |
| 166 | + AppendVisibleRange(run_start, range_end - run_start, &valid_ranges); |
| 167 | + } |
| 168 | + } |
| 169 | + return valid_ranges; |
| 170 | +} |
| 171 | + |
| 172 | +template <typename ListArray> |
| 173 | +VisibleRanges GetVisibleValueRanges(const ListArray& array, |
| 174 | + const VisibleRanges& visible_parent_ranges) { |
| 175 | + VisibleRanges value_ranges; |
| 176 | + for (const VisibleRange& range : visible_parent_ranges) { |
| 177 | + int64_t value_offset = array.value_offset(range.offset); |
| 178 | + int64_t value_end = array.value_offset(range.offset + range.length); |
| 179 | + AppendVisibleRange(value_offset, value_end - value_offset, &value_ranges); |
| 180 | + } |
| 181 | + return value_ranges; |
| 182 | +} |
| 183 | + |
| 184 | +Status CheckFieldNullability(const std::shared_ptr<arrow::Field>& field, |
| 185 | + const std::shared_ptr<arrow::Array>& data, |
| 186 | + const VisibleRanges& visible_ranges) { |
| 187 | + const std::shared_ptr<arrow::DataType>& type = field->type(); |
| 188 | + const bool needs_child_validation = |
| 189 | + type->id() == arrow::Type::STRUCT || type->id() == arrow::Type::LIST || |
| 190 | + type->id() == arrow::Type::FIXED_SIZE_LIST || type->id() == arrow::Type::MAP; |
| 191 | + if (!needs_child_validation && |
| 192 | + (field->nullable() || visible_ranges.empty() || data->null_count() == 0)) { |
| 193 | + return Status::OK(); |
| 194 | + } |
| 195 | + |
| 196 | + PAIMON_ASSIGN_OR_RAISE(VisibleRanges valid_ranges, |
| 197 | + IntersectWithValidity(*data, visible_ranges, *field)); |
| 198 | + |
| 199 | + if (type->id() == arrow::Type::STRUCT) { |
| 200 | + auto struct_type = checked_pointer_cast<arrow::StructType>(type); |
| 201 | + auto struct_array = checked_pointer_cast<arrow::StructArray>(data); |
| 202 | + for (int32_t i = 0; i < struct_type->num_fields(); ++i) { |
| 203 | + PAIMON_RETURN_NOT_OK( |
| 204 | + CheckFieldNullability(struct_type->field(i), struct_array->field(i), valid_ranges)); |
| 205 | + } |
| 206 | + } else if (type->id() == arrow::Type::LIST) { |
| 207 | + auto list_type = checked_pointer_cast<arrow::ListType>(type); |
| 208 | + auto list_array = checked_pointer_cast<arrow::ListArray>(data); |
| 209 | + VisibleRanges value_ranges = GetVisibleValueRanges(*list_array, valid_ranges); |
| 210 | + PAIMON_RETURN_NOT_OK( |
| 211 | + CheckFieldNullability(list_type->value_field(), list_array->values(), value_ranges)); |
| 212 | + } else if (type->id() == arrow::Type::FIXED_SIZE_LIST) { |
| 213 | + auto vector_type = checked_pointer_cast<arrow::FixedSizeListType>(type); |
| 214 | + auto vector_array = checked_pointer_cast<arrow::FixedSizeListArray>(data); |
| 215 | + int32_t vector_length = vector_type->list_size(); |
| 216 | + int64_t required_values = (vector_array->offset() + vector_array->length()) * vector_length; |
| 217 | + if (vector_array->values()->length() < required_values) { |
| 218 | + return Status::Invalid(fmt::format( |
| 219 | + "VECTOR field {} is invalid: VECTOR holds {} elements while {} rows of dimension " |
| 220 | + "{} require {}", |
| 221 | + field->name(), vector_array->values()->length(), vector_array->length(), |
| 222 | + vector_length, required_values)); |
| 223 | + } |
| 224 | + |
| 225 | + VisibleRanges value_ranges = GetVisibleValueRanges(*vector_array, valid_ranges); |
| 226 | + // Paimon VECTOR values cannot contain null elements, irrespective of the Arrow child |
| 227 | + // field's declared nullability. |
| 228 | + std::shared_ptr<arrow::Field> value_field = vector_type->value_field()->WithNullable(false); |
| 229 | + Status status = CheckFieldNullability(value_field, vector_array->values(), value_ranges); |
| 230 | + if (!status.ok()) { |
| 231 | + return Status::Invalid( |
| 232 | + fmt::format("VECTOR field {} is invalid: {}", field->name(), status.message())); |
| 233 | + } |
| 234 | + } else if (type->id() == arrow::Type::MAP) { |
| 235 | + auto map_type = checked_pointer_cast<arrow::MapType>(type); |
| 236 | + auto map_array = checked_pointer_cast<arrow::MapArray>(data); |
| 237 | + VisibleRanges value_ranges = GetVisibleValueRanges(*map_array, valid_ranges); |
| 238 | + PAIMON_RETURN_NOT_OK( |
| 239 | + CheckFieldNullability(map_type->key_field(), map_array->keys(), value_ranges)); |
| 240 | + PAIMON_RETURN_NOT_OK( |
| 241 | + CheckFieldNullability(map_type->item_field(), map_array->items(), value_ranges)); |
| 242 | + } |
| 243 | + return Status::OK(); |
| 244 | +} |
| 245 | + |
100 | 246 | bool NeedsNormalization(const std::shared_ptr<arrow::ArrayData>& data) { |
101 | 247 | if (data->offset != 0) { |
102 | 248 | return true; |
@@ -407,7 +553,9 @@ Status ArrowUtils::CheckNullabilityMatch(const std::shared_ptr<arrow::Schema>& s |
407 | 553 | struct_array->num_fields(), schema->num_fields())); |
408 | 554 | } |
409 | 555 | for (int32_t i = 0; i < schema->num_fields(); i++) { |
410 | | - PAIMON_RETURN_NOT_OK(InnerCheckNullabilityMatch(schema->field(i), struct_array->field(i))); |
| 556 | + const std::shared_ptr<arrow::Array>& field_array = struct_array->field(i); |
| 557 | + PAIMON_RETURN_NOT_OK( |
| 558 | + CheckFieldNullability(schema->field(i), field_array, {{0, field_array->length()}})); |
411 | 559 | } |
412 | 560 | return Status::OK(); |
413 | 561 | } |
@@ -533,42 +681,6 @@ bool ArrowUtils::EqualsIgnoreNullable(const std::shared_ptr<arrow::DataType>& ty |
533 | 681 | return true; |
534 | 682 | } |
535 | 683 |
|
536 | | -Status ArrowUtils::InnerCheckNullabilityMatch(const std::shared_ptr<arrow::Field>& field, |
537 | | - const std::shared_ptr<arrow::Array>& data) { |
538 | | - if (PAIMON_UNLIKELY(!field->nullable() && data->null_count() != 0)) { |
539 | | - return Status::Invalid(fmt::format( |
540 | | - "CheckNullabilityMatch failed, field {} not nullable while data have null value", |
541 | | - field->name())); |
542 | | - } |
543 | | - auto type = field->type(); |
544 | | - if (type->id() == arrow::Type::STRUCT) { |
545 | | - auto struct_type = checked_pointer_cast<arrow::StructType>(field->type()); |
546 | | - auto struct_array = checked_pointer_cast<arrow::StructArray>(data); |
547 | | - for (int32_t i = 0; i < struct_type->num_fields(); ++i) { |
548 | | - PAIMON_RETURN_NOT_OK( |
549 | | - InnerCheckNullabilityMatch(struct_type->field(i), struct_array->field(i))); |
550 | | - } |
551 | | - } else if (type->id() == arrow::Type::LIST) { |
552 | | - auto list_type = checked_pointer_cast<arrow::ListType>(field->type()); |
553 | | - auto list_array = checked_pointer_cast<arrow::ListArray>(data); |
554 | | - PAIMON_RETURN_NOT_OK( |
555 | | - InnerCheckNullabilityMatch(list_type->value_field(), list_array->values())); |
556 | | - } else if (type->id() == arrow::Type::FIXED_SIZE_LIST) { |
557 | | - Status status = VectorUtils::ValidateVectorElements(*data); |
558 | | - if (!status.ok()) { |
559 | | - return Status::Invalid( |
560 | | - fmt::format("VECTOR field {} is invalid: {}", field->name(), status.message())); |
561 | | - } |
562 | | - } else if (type->id() == arrow::Type::MAP) { |
563 | | - auto map_type = checked_pointer_cast<arrow::MapType>(field->type()); |
564 | | - auto map_array = checked_pointer_cast<arrow::MapArray>(data); |
565 | | - PAIMON_RETURN_NOT_OK(InnerCheckNullabilityMatch(map_type->key_field(), map_array->keys())); |
566 | | - PAIMON_RETURN_NOT_OK( |
567 | | - InnerCheckNullabilityMatch(map_type->item_field(), map_array->items())); |
568 | | - } |
569 | | - return Status::OK(); |
570 | | -} |
571 | | - |
572 | 684 | Result<std::shared_ptr<arrow::StructArray>> ArrowUtils::RemoveFieldFromStructArray( |
573 | 685 | const std::shared_ptr<arrow::StructArray>& struct_array, const std::string& field_name) { |
574 | 686 | auto struct_type = checked_pointer_cast<arrow::StructType>(struct_array->type()); |
|
0 commit comments