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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -454,20 +454,12 @@ public void createScanRangeLocations() throws 
UserException {
         // Extract name mapping from Iceberg table properties
         Map<Integer, List<String>> nameMapping = extractNameMapping();
 
-        boolean haveTopnLazyMatCol = false;
-        for (SlotDescriptor slot : desc.getSlots()) {
-            String colName = slot.getColumn().getName();
-            if (colName.startsWith(Column.GLOBAL_ROWID_COL)) {
-                haveTopnLazyMatCol = true;
-                break;
-            }
-        }
-        if (haveTopnLazyMatCol) {
-            ExternalUtil.initSchemaInfoForAllColumn(params, -1L, 
source.getTargetTable().getColumns(), nameMapping);
-        } else {
-            // Use new initSchemaInfo method that only includes needed columns 
based on slots and pruned type
-            ExternalUtil.initSchemaInfoForPrunedColumn(params, -1L, 
desc.getSlots(), nameMapping);
-        }
+        // Equality-delete keys are hidden scan dependencies and need not 
appear in the query
+        // projection. Both scanners need the complete current schema to 
resolve field ids,
+        // historical names, types, and initial defaults when an old data file 
lacks such a key.
+        ExternalUtil.initSchemaInfoForAllColumn(
+                params, -1L, source.getTargetTable().getColumns(), nameMapping,
+                
IcebergUtils.getBase64EncodedInitialDefaultFieldIds(icebergTable.schema()));

Review Comment:
   This uses the selected scan columns from 
`source.getTargetTable().getColumns()`, but computes the Base64-default 
field-id set from `icebergTable.schema()`, which is the table's latest schema. 
Time-travel/branch/tag scans load the requested snapshot into 
`StatementContext`; `IcebergExternalTable.getFullSchema()` then comes from that 
snapshot schema via `IcebergUtils.getIcebergSchema()`. If the selected snapshot 
still has a UUID/BINARY/FIXED field with an initial default that is absent or 
changed in the latest schema, `ExternalUtil` will send the default string 
without `initial_default_value_base64_encoded`. BE then treats the Base64 text 
as the literal string/VARBINARY default instead of decoding to the raw equality 
key bytes, so missing-key equality deletes can be missed. Please derive the 
Base64 field-id set from the same Iceberg schema/schema id used to build the 
scan columns, or carry this encoding bit with each parsed `Column`, and add 
snapshot/branch coverage for a binary-lik
 e initial default.



##########
be/src/format/table/iceberg_reader_mixin.h:
##########
@@ -634,6 +663,82 @@ Status 
IcebergReaderMixin<BaseReader>::_expand_block_if_need(Block* block) {
     return Status::OK();
 }
 
+template <typename BaseReader>
+Status 
IcebergReaderMixin<BaseReader>::_register_missing_equality_delete_column(
+        int32_t field_id, const std::string& name, const DataTypePtr& 
delete_key_type) {
+    DORIS_CHECK(delete_key_type != nullptr);
+    const schema::external::TField* table_field = nullptr;
+    const auto& scan_params = this->get_scan_params();
+    DORIS_CHECK(scan_params.__isset.history_schema_info);
+    DORIS_CHECK(!scan_params.history_schema_info.empty());
+    const auto* current_schema = &scan_params.history_schema_info.front();
+    if (scan_params.__isset.current_schema_id) {
+        const auto schema_it = std::ranges::find_if(
+                scan_params.history_schema_info, [&](const 
schema::external::TSchema& schema) {
+                    return schema.__isset.schema_id &&
+                           schema.schema_id == scan_params.current_schema_id;
+                });
+        DORIS_CHECK(schema_it != scan_params.history_schema_info.end());
+        current_schema = &*schema_it;
+    }
+    DORIS_CHECK(current_schema->__isset.root_field);
+    for (const auto& field_ptr : current_schema->root_field.fields) {
+        if (field_ptr.__isset.field_ptr && field_ptr.field_ptr != nullptr &&
+            field_ptr.field_ptr->__isset.id && field_ptr.field_ptr->id == 
field_id) {
+            table_field = field_ptr.field_ptr.get();
+            break;
+        }
+    }
+    DORIS_CHECK(table_field != nullptr);
+
+    Field value;
+    if (table_field->__isset.initial_default_value) {
+        const auto nested_type = remove_nullable(delete_key_type);
+        if (table_field->__isset.initial_default_value_base64_encoded &&
+            table_field->initial_default_value_base64_encoded) {
+            std::string decoded_default;
+            if (!base64_decode(table_field->initial_default_value, 
&decoded_default)) {
+                return Status::InvalidArgument(
+                        "Invalid Base64 Iceberg initial default for field {}", 
table_field->name);
+            }
+            if (nested_type->get_primitive_type() == TYPE_VARBINARY) {
+                value = 
Field::create_field<TYPE_VARBINARY>(StringView(decoded_default));
+            } else {
+                DORIS_CHECK(nested_type->get_primitive_type() == TYPE_CHAR ||
+                            nested_type->get_primitive_type() == TYPE_VARCHAR 
||
+                            nested_type->get_primitive_type() == TYPE_STRING);
+                value = 
Field::create_field<TYPE_STRING>(std::move(decoded_default));
+            }
+        } else {
+            RETURN_IF_ERROR(nested_type->get_serde()->from_fe_string(
+                    table_field->initial_default_value, value));
+        }
+    }
+    _missing_equality_delete_values.emplace(name, 
delete_key_type->create_column_const(1, value));
+    return Status::OK();
+}
+
+template <typename BaseReader>
+Status 
IcebergReaderMixin<BaseReader>::_materialize_missing_equality_delete_columns(Block*
 block,
+                                                                               
     size_t rows) {
+    for (const auto& [name, value] : _missing_equality_delete_values) {
+        if (!this->col_name_to_block_idx_ref()->contains(name)) {
+            const auto expand_col = std::ranges::find_if(
+                    _expand_columns,
+                    [&](const ColumnWithTypeAndName& col) { return col.name == 
name; });
+            DORIS_CHECK(expand_col != _expand_columns.end());
+            (*this->col_name_to_block_idx_ref())[name] = block->columns();
+            block->insert({value->clone_resized(rows), expand_col->type, 
name});

Review Comment:
   This leaves the synthesized missing-key column as a `ColumnConst`. That 
works for single-column equality deletes because `SimpleEqualityDelete` expands 
consts before probing, but the V1 multi-key path hashes data columns directly 
in `MultiEqualityDelete::filter_data_block()` and then compares them directly 
in `_equal()`. `ColumnConst` does not implement the batch 
`update_hashes_with_value()` method and its `compare_at()` expects the RHS to 
also be const, while delete-file columns are materialized normal columns. A 
delete file on `(id, added_key)` against an old data file where `added_key` is 
logically the initial default will fail before it can remove matching rows. 
Please insert a full column here (or normalize consts in `MultiEqualityDelete`) 
and add a V1 multi-column equality-delete test with one absent/defaulted key.



##########
be/src/format/table/iceberg_reader.cpp:
##########
@@ -279,6 +279,12 @@ Status 
IcebergParquetReader::on_before_init_reader(ReaderInitContext* ctx) {
             _expand_columns[i].name = table_col_name;
         }
 
+        if (it == field_id_to_file_col_name.end()) {

Review Comment:
   At this point `ctx->table_info_node` has already been built with the Iceberg 
name mapping and can resolve id-less files by alias, but the hidden 
equality-key remap ignores it and only probes `field_id_to_file_col_name`. For 
migrated Parquet/ORC files without field IDs, projected columns are still 
readable through `*_field_id_with_name_mapping()`, while an unprojected 
equality-delete key falls into this branch and gets materialized as 
NULL/default. A delete on a renamed field such as table `current_id` mapped to 
physical `legacy_id` can therefore leave matching rows visible. Please resolve 
the hidden key through the same name-mapping node before treating it as 
missing, and add V1 coverage for an unprojected equality key in an 
id-less/name-mapped file.



##########
be/src/format/table/iceberg_reader_mixin.h:
##########
@@ -634,6 +663,82 @@ Status 
IcebergReaderMixin<BaseReader>::_expand_block_if_need(Block* block) {
     return Status::OK();
 }
 
+template <typename BaseReader>
+Status 
IcebergReaderMixin<BaseReader>::_register_missing_equality_delete_column(
+        int32_t field_id, const std::string& name, const DataTypePtr& 
delete_key_type) {
+    DORIS_CHECK(delete_key_type != nullptr);
+    const schema::external::TField* table_field = nullptr;
+    const auto& scan_params = this->get_scan_params();
+    DORIS_CHECK(scan_params.__isset.history_schema_info);
+    DORIS_CHECK(!scan_params.history_schema_info.empty());
+    const auto* current_schema = &scan_params.history_schema_info.front();
+    if (scan_params.__isset.current_schema_id) {
+        const auto schema_it = std::ranges::find_if(
+                scan_params.history_schema_info, [&](const 
schema::external::TSchema& schema) {
+                    return schema.__isset.schema_id &&
+                           schema.schema_id == scan_params.current_schema_id;
+                });
+        DORIS_CHECK(schema_it != scan_params.history_schema_info.end());
+        current_schema = &*schema_it;
+    }
+    DORIS_CHECK(current_schema->__isset.root_field);
+    for (const auto& field_ptr : current_schema->root_field.fields) {
+        if (field_ptr.__isset.field_ptr && field_ptr.field_ptr != nullptr &&
+            field_ptr.field_ptr->__isset.id && field_ptr.field_ptr->id == 
field_id) {
+            table_field = field_ptr.field_ptr.get();
+            break;
+        }
+    }
+    DORIS_CHECK(table_field != nullptr);

Review Comment:
   This new path assumes the FE scan descriptor always carries the full current 
schema. That is true for the new `IcebergScanNode`, but older/pruned 
descriptors used `initSchemaInfoForPrunedColumn()` and only included projected 
slots, so an unprojected equality-delete key can be absent from 
`history_schema_info` during a rolling upgrade or with an older descriptor. V2 
handles that case by falling back to projected metadata or the delete-file 
name/type when full schema metadata is unavailable; V1 reaches this 
`DORIS_CHECK(table_field != nullptr)` and aborts before it can apply even the 
NULL/default-free semantics. Please add the same compatibility fallback or 
return a controlled non-crashing error for missing schema metadata, and cover a 
pruned-descriptor V1 missing-key case.



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