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


##########
be/src/core/column/variant_v2/column_variant_v2.cpp:
##########
@@ -497,6 +554,9 @@ void ColumnVariantV2::for_each_subcolumn(ColumnCallback 
callback) const {
 }
 
 void ColumnVariantV2::mutate_subcolumns() {
+    if (_shredded) {
+        ensure_encoded();

Review Comment:
   Fixed. Output detachment now preserves the immutable shredded state instead 
of forcing canonical encoding. The end-to-end leaf projection path remains 
shredded until the Variant element consumer reads it.



##########
be/src/core/column/variant_v2/column_variant_v2.cpp:
##########
@@ -724,6 +792,20 @@ void ColumnVariantV2::insert_range_from( // 
NOLINT(readability-function-size)
         return;
     }
 
+    if (!_shredded && !_typed && empty() && _metadatas->empty() && 
source._shredded && start == 0 &&
+        length == source.size()) {
+        _shredded = source._shredded;
+        _check_invariants();
+        return;
+    }
+    if (_shredded) {
+        ensure_encoded();
+    }
+    if (source._shredded) {
+        insert_range_from(source._shredded->materialized_column(), start, 
length);

Review Comment:
   Fixed. Shredded states now implement native range and index selection, so 
proper subset cuts stay shredded. The widened-batch coverage exercises this 
path.



##########
be/src/format_v2/parquet/native_schema_desc.cpp:
##########
@@ -55,6 +56,180 @@ static bool is_map_node(const tparquet::SchemaElement& 
schema) {
            (schema.__isset.logicalType && schema.logicalType.__isset.MAP);
 }
 
+static bool is_variant_node(const tparquet::SchemaElement& schema) {
+    return schema.__isset.logicalType && schema.logicalType.__isset.VARIANT;
+}
+
+class ScopedBoolOverride {
+public:
+    ScopedBoolOverride(bool& target, bool value) : _target(target), 
_original(target) {
+        _target = value;
+    }
+    ~ScopedBoolOverride() { _target = _original; }
+
+private:
+    bool& _target;
+    bool _original;
+};
+
+static Status validate_variant_layout(const tparquet::SchemaElement& 
group_schema,
+                                      const NativeFieldSchema& group_field) {
+    const auto& annotation = group_schema.logicalType.VARIANT;
+    if (annotation.__isset.specification_version && 
annotation.specification_version != 1) {
+        return Status::NotSupported("Parquet Variant specification version {} 
is not supported",
+                                    annotation.specification_version);
+    }
+    if (group_field.children.size() < 2 || group_field.children.size() > 3) {
+        return Status::Corruption(
+                "Parquet Variant {} must contain metadata, value, and optional 
typed_value",
+                group_schema.name);
+    }
+
+    const NativeFieldSchema* metadata = nullptr;
+    const NativeFieldSchema* value = nullptr;
+    const NativeFieldSchema* typed_value = nullptr;
+    for (const auto& child : group_field.children) {
+        const NativeFieldSchema** target = nullptr;
+        if (child.name == "metadata") {
+            target = &metadata;
+        } else if (child.name == "value") {
+            target = &value;
+        } else if (child.name == "typed_value") {
+            target = &typed_value;
+        } else {
+            return Status::Corruption("Parquet Variant {} has unexpected child 
{}",
+                                      group_schema.name, child.name);
+        }
+        if (*target != nullptr) {
+            return Status::Corruption("Parquet Variant {} has duplicate child 
{}",
+                                      group_schema.name, child.name);
+        }
+        *target = &child;
+    }
+    if (metadata == nullptr || value == nullptr) {
+        return Status::Corruption("Parquet Variant {} requires metadata and 
value children",
+                                  group_schema.name);
+    }
+    if (!metadata->children.empty() || metadata->physical_type != 
tparquet::Type::BYTE_ARRAY ||
+        metadata->parquet_schema.repetition_type != 
tparquet::FieldRepetitionType::REQUIRED) {
+        return Status::Corruption("Parquet Variant {} metadata must be a 
required BYTE_ARRAY",
+                                  group_schema.name);
+    }
+    const auto expected_value_repetition = typed_value == nullptr
+                                                   ? 
tparquet::FieldRepetitionType::REQUIRED
+                                                   : 
tparquet::FieldRepetitionType::OPTIONAL;
+    // SQL nullability belongs to the outer Variant group. Only shredding 
makes value optional,
+    // because typed_value may carry all or part of the logical value instead.
+    if (!value->children.empty() || value->physical_type != 
tparquet::Type::BYTE_ARRAY ||
+        value->parquet_schema.repetition_type != expected_value_repetition) {
+        return Status::Corruption("Parquet Variant {} value must be a {} 
BYTE_ARRAY",
+                                  group_schema.name,
+                                  typed_value == nullptr ? "required" : 
"optional");
+    }
+    if (typed_value != nullptr &&
+        typed_value->parquet_schema.repetition_type != 
tparquet::FieldRepetitionType::OPTIONAL) {
+        return Status::Corruption("Parquet Variant {} typed_value must be 
optional",
+                                  group_schema.name);
+    }
+
+    std::function<Status(const NativeFieldSchema&)> validate_typed_value;
+    std::function<Status(const NativeFieldSchema&)> validate_wrapper;
+    validate_wrapper = [&](const NativeFieldSchema& wrapper) -> Status {
+        if (!wrapper.parquet_schema.__isset.repetition_type ||
+            wrapper.parquet_schema.repetition_type != 
tparquet::FieldRepetitionType::REQUIRED) {
+            return Status::Corruption("Parquet Variant shredded wrapper {} 
must be required",
+                                      wrapper.name);
+        }
+        const NativeFieldSchema* fallback = nullptr;
+        const NativeFieldSchema* typed = nullptr;
+        for (const auto& child : wrapper.children) {
+            if (child.name == "value") {
+                fallback = &child;
+            } else if (child.name == "typed_value") {
+                typed = &child;
+            } else {
+                return Status::Corruption("Parquet Variant wrapper {} has 
unexpected child {}",
+                                          wrapper.name, child.name);
+            }
+        }
+        if (fallback == nullptr || typed == nullptr || 
!fallback->children.empty() ||

Review Comment:
   Fixed. Schema validation now accepts the context-valid omitted 
value/typed_value children, validates every present child, and recurses only 
through present typed values. Coverage includes value-only object fields and 
typed-only/value-only array elements.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -1200,6 +1200,11 @@ private List<Split> doGetSplits(int numBackends) throws 
UserException {
             return doGetSystemTableSplits();
         }
 
+        boolean projectsVariant = !isTableLevelCountStarPushdown()
+                && desc.getSlots().stream()
+                        .anyMatch(slot -> 
IcebergUtils.containsVariant(slot.getColumn().getType()));

Review Comment:
   Fixed. The smooth-upgrade capability gate now uses each materialized slot 
effective type, so a non-Variant sibling projection is allowed while semantic 
Variant projections remain gated. Added FE coverage.



##########
be/src/exec/scan/file_scanner_v2.cpp:
##########
@@ -627,8 +672,15 @@ Status FileScannerV2::_create_table_reader_for_format(
 
 Status FileScannerV2::_prepare_table_reader_split(const TFileRangeDesc& range,
                                                   std::map<std::string, Field> 
partition_values) {
+    const auto format_type = get_range_format_type(*_params, range);
+    const bool projects_variant =
+            !_local_state->is_count_star_pushdown() &&
+            std::ranges::any_of(_projected_columns, [](const 
format::ColumnDefinition& column) {
+                return contains_variant_type(column.type);
+            });
+    RETURN_IF_ERROR(validate_iceberg_variant_file_format(range, format_type, 
projects_variant));

Review Comment:
   Fixed. Unsupported-format rejection now occurs only after per-file mapping 
proves that the surviving split physically supplies a requested Variant. 
Evolved ORC files synthesize NULL, and mixed ORC/Parquet scans read the Parquet 
Variant. The regression covers both cases.



##########
be/src/core/column/variant_v2/column_variant_v2.cpp:
##########
@@ -724,6 +794,37 @@ void ColumnVariantV2::insert_range_from( // 
NOLINT(readability-function-size)
         return;
     }
 
+    if (!_shredded && !_typed && empty() && _metadatas->empty() && 
source._shredded) {
+        // IColumn::cut() inserts into an empty clone. Select the physical 
tree directly because an
+        // incomplete leaf projection cannot be reconstructed merely to copy a 
row range.
+        _shredded = start == 0 && length == source.size()
+                            ? source._shredded
+                            : source._shredded->select_range(start, length);
+        _check_invariants();
+        return;
+    }
+    if (_shredded && source._shredded) {
+        auto selected_source = start == 0 && length == source.size()
+                                       ? source._shredded
+                                       : source._shredded->select_range(start, 
length);
+        if (!_shredded.unique()) {

Review Comment:
   Fixed. Replaced shared_ptr::unique() with the repository-compatible 
use_count() ownership check while preserving the copy-on-write invariant. The 
rebased BE build passes locally; macOS CI has been re-triggered.



##########
be/src/core/column/variant_v2/column_variant_v2.cpp:
##########
@@ -724,6 +794,37 @@ void ColumnVariantV2::insert_range_from( // 
NOLINT(readability-function-size)
         return;
     }
 
+    if (!_shredded && !_typed && empty() && _metadatas->empty() && 
source._shredded) {
+        // IColumn::cut() inserts into an empty clone. Select the physical 
tree directly because an
+        // incomplete leaf projection cannot be reconstructed merely to copy a 
row range.
+        _shredded = start == 0 && length == source.size()
+                            ? source._shredded
+                            : source._shredded->select_range(start, length);
+        _check_invariants();
+        return;
+    }
+    if (_shredded && source._shredded) {
+        auto selected_source = start == 0 && length == source.size()
+                                       ? source._shredded
+                                       : source._shredded->select_range(start, 
length);
+        if (!_shredded.unique()) {
+            _shredded = _shredded->select_range(0, size());
+        }
+        // A partial physical projection has no metadata/value pair to encode. 
Preserve that
+        // invariant by merging compatible scanner batches before the 
canonical fallback below.
+        if (_shredded->try_append(*selected_source)) {

Review Comment:
   Fixed. FileScannerV2 now projects each physical block before reading and 
padding another split whenever the output contains Variant, so complete, 
partial, and file-local shredding states are never merged raw. Added a two-file 
complete-plus-leaf regression; test_iceberg_variant_read passes locally on 
freshly rebuilt binaries (1 suite, 0 failures).



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