github-actions[bot] commented on code in PR #66538:
URL: https://github.com/apache/doris/pull/66538#discussion_r3868468906


##########
be/src/format/table/iceberg_reader.cpp:
##########
@@ -183,13 +771,20 @@ Status IcebergTableReader::get_next_block_inner(Block* 
block, size_t* read_rows,
     RETURN_IF_ERROR(_expand_block_if_need(block));
 
     RETURN_IF_ERROR(_file_format_reader->get_next_block(block, read_rows, 
eof));
+    RETURN_IF_ERROR(_materialize_missing_table_columns(block));
+    RETURN_IF_ERROR(_materialize_missing_equality_delete_columns(block));
+    RETURN_IF_ERROR(_materialize_nested_equality_delete_columns(block));
+    RETURN_IF_ERROR(_validate_required_table_columns(block));

Review Comment:
   [P2] Validate required fields after equality deletes
   
   This validates the block before the equality-delete filter below removes 
logically invisible rows. If an older file has NULL in a field that is now 
required, but an equality delete on another key removes that row, forced V1 
returns `Required Iceberg field ... contains NULL`. Position deletes already 
run in the physical reader, and V2 likewise applies equality deletes before 
required-field validation, so the result currently depends on delete type and 
scanner. Apply the V1 equality filter first, then validate only its survivors.



##########
be/src/format/table/iceberg_reader.cpp:
##########
@@ -487,134 +1680,158 @@ Status IcebergParquetReader::init_reader(
         parquet_reader->set_row_lineage_columns(_row_lineage_columns);
     }
 
-    auto column_id_result = _create_column_ids(_data_file_field_desc, 
tuple_descriptor);
-    auto& column_ids = column_id_result.column_ids;
-    const auto& filter_column_ids = column_id_result.filter_column_ids;
-
-    RETURN_IF_ERROR(init_row_filters());
     _all_required_col_names = file_col_names;
+    for (const auto* slot : tuple_descriptor->slots()) {
+        _id_to_block_column_name.emplace(slot->col_unique_id(), 
slot->col_name());
+        _required_column_types.emplace(slot->col_name(), slot->type());
+    }
+    RETURN_IF_ERROR(init_row_filters());
 
     if (!_params.__isset.history_schema_info || 
_params.history_schema_info.empty()) [[unlikely]] {
         RETURN_IF_ERROR(BuildTableInfoUtil::by_parquet_name(
                 tuple_descriptor, *_data_file_field_desc, 
table_info_node_ptr));
     } else {
-        std::set<std::string> read_col_name_set(file_col_names.begin(), 
file_col_names.end());
+        
RETURN_IF_ERROR(BuildTableInfoUtil::by_parquet_field_id_with_name_mapping(
+                _params.history_schema_info.front().root_field, 
*_data_file_field_desc,
+                table_info_node_ptr, 
supports_iceberg_scan_semantics_v2(&_params)));
+    }
+    RETURN_IF_ERROR(_validate_projected_missing_required_fields());
 
-        bool exist_field_id = true;
-        for (int idx = 0; idx < _data_file_field_desc->size(); idx++) {
-            if (_data_file_field_desc->get_column(idx)->field_id == -1) {
-                // the data file may be from hive table migrated to iceberg, 
field id is missing
-                exist_field_id = false;
-                break;
-            }
-        }
-        const auto& table_schema = 
_params.history_schema_info.front().root_field;
-
-        table_info_node_ptr = 
std::make_shared<TableSchemaChangeHelper::StructNode>();
-        if (exist_field_id) {
-            // id -> table column name. columns that need read data file.
-            std::unordered_map<int, std::shared_ptr<schema::external::TField>> 
id_to_table_field;
-            for (const auto& table_field : table_schema.fields) {
-                auto field = table_field.field_ptr;
-                DCHECK(field->__isset.name);
-                if (!read_col_name_set.contains(field->name)) {
-                    continue;
-                }
-                id_to_table_field.emplace(field->id, field);
-            }
+    auto column_id_result =
+            _create_column_ids(_data_file_field_desc, tuple_descriptor, 
table_info_node_ptr);
+    auto& column_ids = column_id_result.column_ids;
+    const auto& filter_column_ids = column_id_result.filter_column_ids;
 
-            for (int idx = 0; idx < _data_file_field_desc->size(); idx++) {
-                const auto& data_file_field = 
_data_file_field_desc->get_column(idx);
-                auto data_file_column_id = 
_data_file_field_desc->get_column(idx)->field_id;
-
-                if (id_to_table_field.contains(data_file_column_id)) {
-                    const auto& table_field = 
id_to_table_field[data_file_column_id];
-
-                    std::shared_ptr<TableSchemaChangeHelper::Node> field_node 
= nullptr;
-                    RETURN_IF_ERROR(BuildTableInfoUtil::by_parquet_field_id(
-                            *table_field, *data_file_field, exist_field_id, 
field_node));
-                    table_info_node_ptr->add_children(table_field->name, 
data_file_field->name,
-                                                      field_node);
-
-                    _id_to_block_column_name.emplace(data_file_column_id, 
table_field->name);
-                    id_to_table_field.erase(data_file_column_id);
-                } else if 
(_equality_delete_col_ids.contains(data_file_column_id)) {
-                    // Columns that need to be read for equality delete.
-                    const static std::string EQ_DELETE_PRE = 
"__equality_delete_column__";
-
-                    // Construct table column names that avoid duplication 
with current table schema.
-                    // As the columns currently being read may have been 
deleted in the latest
-                    // table structure or have undergone a series of schema 
changes...
-                    std::string table_column_name = EQ_DELETE_PRE + 
data_file_field->name;
-                    table_info_node_ptr->add_children(
-                            table_column_name, data_file_field->name,
-                            
std::make_shared<TableSchemaChangeHelper::ConstNode>());
-
-                    _id_to_block_column_name.emplace(data_file_column_id, 
table_column_name);
-                    _expand_col_names.emplace_back(table_column_name);
-                    auto expand_data_type = 
make_nullable(data_file_field->data_type);
-                    _expand_columns.emplace_back(
-                            ColumnWithTypeAndName 
{expand_data_type->create_column(),
-                                                   expand_data_type, 
table_column_name});
-
-                    _all_required_col_names.emplace_back(table_column_name);
-                    column_ids.insert(data_file_field->get_column_id());
+    bool all_file_columns_have_field_ids = true;
+    bool any_file_column_has_field_id = false;
+    for (int index = 0; index < _data_file_field_desc->size(); ++index) {
+        const auto* field = _data_file_field_desc->get_column(index);
+        if (field == nullptr) {
+            continue;
+        }
+        if (field->field_id < 0) {
+            all_file_columns_have_field_ids = false;
+        }
+        if (parquet_subtree_has_iceberg_id(*field)) {
+            any_file_column_has_field_id = true;
+        }
+    }
+    const bool use_field_ids = supports_iceberg_scan_semantics_v2(&_params)
+                                       ? any_file_column_has_field_id
+                                       : all_file_columns_have_field_ids;
+    std::unordered_map<std::string, std::string> physical_root_sources;
+    std::vector<std::string> new_expand_col_names;
+    DORIS_CHECK(_expand_col_names.size() == _expand_col_field_ids.size());
+    DORIS_CHECK(_expand_col_names.size() == _expand_columns.size());
+    for (size_t index = 0; index < _expand_col_names.size(); ++index) {
+        const std::string old_name = _expand_col_names[index];
+        const int32_t field_id = _expand_col_field_ids[index];
+        const FieldSchema* file_column = nullptr;
+        ParquetEqualityFieldPath file_path;
+        bool complete_file_path = false;
+        if (use_field_ids) {
+            complete_file_path = 
find_parquet_equality_field_path_by_id(_data_file_field_desc,
+                                                                        
field_id, &file_path);
+            if (!complete_file_path && 
supports_iceberg_scan_semantics_v2(&_params)) {
+                const auto table_path = _find_schema_field_path(field_id);
+                if (!table_path.empty()) {
+                    complete_file_path = 
find_parquet_equality_field_prefix_by_id_path(
+                            _data_file_field_desc, table_path, &file_path);
                 }
             }
-            for (const auto& [id, table_field] : id_to_table_field) {
-                table_info_node_ptr->add_not_exist_children(table_field->name);
+            if (!file_path.fields.empty()) {
+                file_column = file_path.fields.front();
             }
         } else {
-            if (!_equality_delete_col_ids.empty()) [[unlikely]] {
-                return Status::InternalError(
-                        "Can not read missing field id data file when have 
equality delete");
-            }
-            std::map<std::string, size_t> file_column_idx_map;
-            for (size_t idx = 0; idx < _data_file_field_desc->size(); idx++) {
-                
file_column_idx_map.emplace(_data_file_field_desc->get_column(idx)->name, idx);
+            const auto table_path = _find_schema_field_path(field_id);
+            if (!table_path.empty()) {
+                complete_file_path = 
find_parquet_equality_field_prefix_by_name_path(
+                        _data_file_field_desc, table_path, old_name, 
&file_path);
+                if (!file_path.fields.empty()) {
+                    file_column = file_path.fields.front();
+                }
             }
+        }
 
-            for (const auto& table_field : table_schema.fields) {
-                DCHECK(table_field.__isset.field_ptr);
-                DCHECK(table_field.field_ptr->__isset.name);
-                const auto& table_column_name = table_field.field_ptr->name;
-                if (!read_col_name_set.contains(table_column_name)) {
-                    continue;
-                }
-                if (!table_field.field_ptr->__isset.name_mapping ||
-                    table_field.field_ptr->name_mapping.size() == 0) {
-                    return Status::DataQualityError(
-                            "name_mapping must be set when read missing field 
id data file.");
-                }
-                bool have_mapping = false;
-                for (const auto& mapped_name : 
table_field.field_ptr->name_mapping) {
-                    if (file_column_idx_map.contains(mapped_name)) {
-                        std::shared_ptr<TableSchemaChangeHelper::Node> 
field_node = nullptr;
-                        const auto& file_field = 
_data_file_field_desc->get_column(
-                                file_column_idx_map.at(mapped_name));
-                        
RETURN_IF_ERROR(BuildTableInfoUtil::by_parquet_field_id(
-                                *table_field.field_ptr, *file_field, 
exist_field_id, field_node));
-                        table_info_node_ptr->add_children(table_column_name, 
file_field->name,
-                                                          field_node);
-                        have_mapping = true;
-                        break;
-                    }
-                }
-                if (!have_mapping) {
-                    
table_info_node_ptr->add_not_exist_children(table_column_name);
-                }
+        const std::string block_name = _expand_columns[index].name;
+        const DataTypePtr target_leaf_type = _expand_columns[index].type;
+        new_expand_col_names.push_back(block_name);
+        if (file_column == nullptr) {
+            RETURN_IF_ERROR(_register_missing_equality_delete_column(field_id, 
block_name,
+                                                                     
target_leaf_type));
+            continue;
+        }
+
+        std::string source_block_name;
+        std::vector<size_t> source_child_indexes;
+        DataTypePtr source_leaf_type;
+        ColumnPtr missing_value;
+        bool reads_physical_root = false;
+        const auto table_path = _find_schema_field_path(field_id);
+        bool uses_projected_root = false;
+        if (!table_path.empty() && table_path.front()->__isset.id &&
+            _id_to_block_column_name.contains(table_path.front()->id)) {
+            RETURN_IF_ERROR(_get_projected_schema_equality_delete_path(
+                    field_id, &source_child_indexes, &source_leaf_type, 
&uses_projected_root));
+        }
+        if (uses_projected_root) {
+            source_block_name = 
_id_to_block_column_name.at(table_path.front()->id);
+        } else {
+            const std::string root_name = to_lower(file_column->name);
+            const auto root_source = physical_root_sources.find(root_name);
+            if (root_source == physical_root_sources.end()) {
+                source_block_name = block_name;
+                physical_root_sources.emplace(root_name, source_block_name);
+                reads_physical_root = true;
+                _expand_columns[index].type = 
make_nullable(file_column->data_type);
+                _expand_columns[index].column = 
_expand_columns[index].type->create_column();
+                table_info_node_ptr->add_children(
+                        block_name, file_column->name,
+                        TableSchemaChangeHelper::ConstNode::get_instance());
+            } else {
+                source_block_name = root_source->second;
+            }
+            source_child_indexes = file_path.child_indexes;
+            if (complete_file_path) {
+                source_leaf_type = 
make_nullable(file_path.fields.back()->data_type);
+            } else {
+                source_leaf_type = target_leaf_type;
+                RETURN_IF_ERROR(_create_missing_equality_delete_value(
+                        field_id, target_leaf_type, file_path.fields.size(), 
&missing_value));
             }
         }
+        if (!reads_physical_root) {
+            _physical_missing_equality_delete_columns.insert(block_name);
+        }
+        _nested_equality_delete_columns.push_back({
+                .field_id = field_id,
+                .block_name = block_name,
+                .source_block_name = source_block_name,
+                .source_leaf_type = source_leaf_type,
+                .leaf_type = target_leaf_type,
+                .child_indexes = std::move(source_child_indexes),
+                .missing_value = std::move(missing_value),
+                .cast_context = nullptr,
+        });
+        RETURN_IF_ERROR(
+                
_prepare_nested_equality_delete_column(&_nested_equality_delete_columns.back()));
+        for (uint64_t column_id = file_column->get_column_id();
+             column_id <= file_column->get_max_column_id(); ++column_id) {
+            column_ids.insert(column_id);
+        }
+        _all_required_col_names.push_back(block_name);
     }
+    _expand_col_names = std::move(new_expand_col_names);
 
     return parquet_reader->init_reader(
             _all_required_col_names, _col_name_to_block_idx, conjuncts, 
slot_id_to_predicates,

Review Comment:
   [P2] Defer predicates until requiredness validation
   
   V1 forwards these conjuncts into Parquet/ORC, so a predicate such as `WHERE 
strengthened_field > 0` can discard a historical explicit NULL before 
`_validate_required_table_columns` runs at line 777. That bypasses the new 
optional-to-required validation and makes forced V1 accept data that V2 
rejects; V2 explicitly marks `reject_null_value` mappings `FINALIZE_ONLY` for 
this reason. Keep predicates that touch such mappings above the physical reader 
until requiredness has been validated, for both Parquet and ORC.



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