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


##########
be/src/format_v2/table_reader.cpp:
##########
@@ -488,6 +490,7 @@ Status TableReader::annotate_projected_column(const 
TFileScanSlotInfo& slot_info
     context->schema_column = 
build_schema_column_from_external_field(*schema_field, column->type);
     column->identifier = context->schema_column->identifier;
     column->name_mapping = context->schema_column->name_mapping;
+    column->has_name_mapping = context->schema_column->has_name_mapping;

Review Comment:
   [P1] Preserve binary initial-default metadata on projected roots
   
   `build_schema_column_from_external_field` has the payload and Base64 marker, 
but this annotation copies only ID/name metadata. The projected column already 
has a generic FE `default_expr` built from the Base64 carrier; when the 
physical field is absent, `_create_mapping_for_column` takes that expression 
before the new decoding branch and returns text such as `AAEC/w==` instead of 
the binary bytes. V1's projected fill has the same generic-expression behavior; 
the new format helpers only cover nested children. This is separate from the 
existing thread at this anchor, which covered the mapping-presence bit rather 
than default metadata. Please propagate the initial-default metadata, give it 
precedence for Iceberg missing fields, and add top-level BINARY/FIXED/UUID 
result tests.



##########
be/src/format_v2/column_mapper.cpp:
##########
@@ -52,11 +52,59 @@
 #include "format_v2/schema_projection.h"
 #include "format_v2/table_reader.h"
 #include "gen_cpp/Exprs_types.h"
+#include "util/url_coding.h"
 
 namespace doris::format {
 
 namespace {
 
+Status parse_initial_default(const ColumnDefinition& column, 
std::optional<Field>* value) {
+    DORIS_CHECK(value != nullptr);
+    value->reset();
+    if (!column.initial_default_value.has_value()) {
+        return Status::OK();
+    }
+    const auto nested_type = remove_nullable(column.type);
+    Field parsed;
+    if (column.initial_default_value_is_base64 ||
+        nested_type->get_primitive_type() == TYPE_VARBINARY) {
+        std::string decoded;
+        if (!base64_decode(*column.initial_default_value, &decoded)) {
+            return Status::InvalidArgument("Invalid Base64 Iceberg initial 
default for field {}",
+                                           column.name);
+        }
+        parsed = nested_type->get_primitive_type() == TYPE_VARBINARY
+                         ? 
Field::create_field<TYPE_VARBINARY>(StringView(decoded))

Review Comment:
   [P1] Keep decoded VARBINARY defaults alive until materialization
   
   `Field::create_field<TYPE_VARBINARY>(StringView(decoded))` does not own 
payloads longer than 12 bytes: `StringView` stores only a pointer. `decoded` is 
destroyed at the end of this block, before V2 builds the `VLiteral`; the 
parallel Parquet and ORC helpers also destroy it before `create_column_const`. 
A UUID default is always 16 bytes, so this can read freed storage and return 
corrupted bytes or trip ASan. The new tests use `DataTypeString` and four 
bytes, so they miss the branch. The V1/V2 equality-delete default helpers 
contain the same borrowed-view lifetime. Please materialize into owned 
VARBINARY storage at every one of these boundaries and add UUID/long FIXED 
coverage.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/ExternalUtil.java:
##########
@@ -199,7 +213,8 @@ private static TField getExternalSchema(Type columnType, 
Column dorisColumn,
                 TFieldPtr fieldPtr = new TFieldPtr();
                 Column subColumn = subNameToSubColumn.get(subField.getName());
                 fieldPtr.setFieldPtr(getExternalSchema(

Review Comment:
   [P1] Serialize non-binary defaults for nested fields
   
   This recursive call cannot carry an `initialDefault` for an INT/STRING/DATE 
child: `IcebergUtils.parseSchema` assigns the serialized default only to 
top-level `Column`s, `createChildrenColumn` creates children without defaults, 
and `updateIcebergColumnUniqueId` copies only IDs. The separate map repairs 
UUID/BINARY/FIXED only. Consequently, when an older file lacks `s.added_b` with 
initial default `7`, both new missing-child paths receive no value and 
materialize NULL/type-default instead of `7`. This is separate from 
r3609650017's binary Base64 transport. Please propagate every nested Iceberg 
initial default and add FE-to-V1/V2 result coverage for a non-binary child.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -532,13 +534,14 @@ private List<String> getOrderedPathPartitionKeys() {
     public void createScanRangeLocations() throws UserException {
         super.createScanRangeLocations();
         // Extract name mapping from Iceberg table properties
-        Map<Integer, List<String>> nameMapping = extractNameMapping();
+        Optional<Map<Integer, List<String>>> nameMapping = 
extractNameMapping();
 
         // 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, getBase64EncodedInitialDefaultsForScan());
+                nameMapping.orElse(Collections.emptyMap()), 
nameMapping.isPresent(),
+                getBase64EncodedInitialDefaultsForScan());

Review Comment:
   [P1] Use the current schema for ordinary-scan default metadata
   
   For an ordinary scan, `createTableScan().snapshot()` is the current data 
snapshot, whose schema ID can remain old after a schema-only update, while 
`source.getTargetTable().getColumns()` is deliberately built from 
`table.schema()`. A nested BINARY/UUID/FIXED field added after that snapshot is 
therefore absent from `getBase64EncodedInitialDefaultsForScan()`—the child's 
only default transport—and both new missing-child paths emit NULL/type-default. 
This is a distinct residual from r3609650017: that fix cannot preserve a field 
omitted before the map is built. Use the selected snapshot schema only for 
explicit time travel/ref scans; otherwise use the current schema that produced 
the target Columns.



##########
be/src/format_v2/column_mapper.cpp:
##########
@@ -2084,7 +2145,19 @@ const ColumnDefinition* 
TableColumnMapper::_find_file_field(
         });
         return field_it == file_schema.end() ? nullptr : &*field_it;
     }
-    return matcher_for_mode(_options.mode).find(table_column, file_schema);
+    const auto* matched = matcher_for_mode(_options.mode).find(table_column, 
file_schema);
+    if (matched != nullptr || _options.mode != 
TableColumnMappingMode::BY_FIELD_ID ||
+        !_options.allow_idless_complex_wrapper_projection || 
table_column.children.empty()) {
+        return matched;
+    }
+    const auto* wrapper = find_column_by_name(table_column, file_schema);
+    if (wrapper == nullptr || wrapper->has_identifier_field_id() || 
wrapper->children.empty() ||
+        !has_shared_descendant_field_id(table_column, *wrapper)) {
+        return nullptr;
+    }
+    // Iceberg Parquet's PruneColumns retains an ID-less complex wrapper when 
a nested field ID is

Review Comment:
   [P1] Apply the Parquet wrapper fallback recursively
   
   This fallback only runs for a root passed to `_find_file_field`. Once 
`outer#10` matches, `_create_direct_mapping` still resolves its `inner#20` 
child through the strict field-ID matcher, so a physical `outer#10 -> inner(no 
id) -> leaf#30` is marked missing without ever reaching `leaf#30`. Iceberg's 
Parquet projection retains that ID-less wrapper because the selected descendant 
survives. This is distinct from r3609650016's root-wrapper case: here the root 
already matched by ID and the failure is in the recursive child matcher. Please 
thread the Parquet wrapper capability through recursive struct matching and add 
a two-level reader-result test.



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