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


##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergScanPlanProvider.java:
##########
@@ -1098,28 +1098,40 @@ private TableScan buildScan(Table table, 
IcebergTableHandle handle, Optional<Con
         TableScan scan = table.newScan();
         // MVCC / time-travel pin: a tag/branch pins by REF (so a later commit 
to the ref is honored, legacy
         // parity), else by snapshot id (legacy createTableScan: useRef when 
info.getRef()!=null else useSnapshot).
-        if (handle.hasSnapshotPin()) {
+        if (handle.hasSnapshotPin() && supportsSnapshotSelection(handle)) {
             if (handle.getRef() != null) {
                 scan = scan.useRef(handle.getRef());
             } else {
                 scan = scan.useSnapshot(handle.getSnapshotId());
             }
         }
         if (filter.isPresent()) {
-            // Predicate conversion uses the table's CURRENT schema, matching 
legacy createTableScan:589
-            // (convertToIcebergExpr(conjunct, icebergTable.schema())) — NOT 
the pinned schema. A predicate on a
-            // column renamed since the pinned snapshot then resolves to no 
field and drops to BE residual,
-            // exactly like legacy; the common no-rename case is identical 
(the pinned name == the current name),
-            // and the unbound expression still binds against the pinned 
snapshot's schema at plan time.
+            // Historical predicates must resolve names to the field ids of 
the generation used for binding;
+            // using the current schema drops renamed predicates or can bind a 
later reused name incorrectly.
+            Schema predicateSchema = handle.hasSnapshotPin() ? 
pinnedSchema(table, handle) : table.schema();
             List<Expression> predicates =
-                    new IcebergPredicateConverter(table.schema(), 
resolveSessionZone(session)).convert(filter.get());
+                    new IcebergPredicateConverter(predicateSchema, 
resolveSessionZone(session)).convert(filter.get());
             for (Expression predicate : predicates) {
                 scan = scan.filter(predicate);
             }
         }
         return scan;
     }
 
+    /** Whether this table type's scan can actually honor Iceberg's 
snapshot/ref selection APIs. */
+    private static boolean supportsSnapshotSelection(IcebergTableHandle 
handle) {
+        if (!handle.isSystemTable()) {
+            return true;
+        }
+        MetadataTableType type = 
MetadataTableType.from(handle.getSysTableName());
+        // Static metadata tables enumerate metadata history independently of 
the selected snapshot. Calling
+        // useSnapshot/useRef only rejects expired pins while leaving their 
returned rows unfenced.
+        return type != MetadataTableType.HISTORY
+                && type != MetadataTableType.SNAPSHOTS
+                && type != MetadataTableType.REFS
+                && type != MetadataTableType.METADATA_LOG_ENTRIES;

Review Comment:
   [P1] Exclude the `ALL_*` metadata tables from snapshot selection too
   
   This guard still returns true for `ALL_DATA_FILES`, `ALL_DELETE_FILES`, 
`ALL_FILES`, `ALL_MANIFESTS`, and `ALL_ENTRIES`. Those scans inherit Iceberg 
1.10.1's 
[`BaseAllMetadataTableScan`](https://github.com/apache/iceberg/blob/apache-iceberg-1.10.1/core/src/main/java/org/apache/iceberg/BaseAllMetadataTableScan.java),
 whose `useSnapshot` and `useRef` both throw `UnsupportedOperationException` 
because they enumerate files/manifests reachable from all tracked snapshots.
   
   Since the system handle retains the base snapshot/ref pin, a query such as 
`t$all_data_files FOR VERSION AS OF ...` reaches `buildScan()` and fails here 
before planning. Please classify the `ALL_*` types as unsupported alongside the 
four static tables and add a pinned `all_data_files` (plus ref if practical) 
test. This is the plugin-path counterpart of the earlier legacy-path thread 
that explicitly called out leaving `ALL_*` tables unfenced.



##########
regression-test/data/external_table_p0/iceberg/iceberg_query_tag_branch.out:
##########
@@ -6,7 +6,7 @@
 1
 
 -- !branch_3 --
-1      \N      \N
+1

Review Comment:
   [P1] Preserve every explicitly projected branch column
   
   `qt_branch_3` still runs `select c1,c2,c3 ...`, but this new oracle row 
contains only one field. The same truncation appears in `branch_18` and the 
six-expression branch joins. The regression writer emits every JDBC row element 
and does not strip trailing NULLs; the old oracle correctly represented this 
row as `1\t\N\t\N`.
   
   Iceberg's branch schema is the table's current schema 
([`SnapshotUtil.schemaFor(table, 
branch)`](https://github.com/apache/iceberg/blob/apache-iceberg-1.10.1/core/src/main/java/org/apache/iceberg/util/SnapshotUtil.java)
 deliberately returns `table.schema()`), so `c2` and `c3` remain projected 
columns even when the branch's old files supply NULL values. Please keep all 
SELECT-list fields and regenerate the output. If this shortened row was 
generated from the patched code, the underlying projection/result-metadata 
regression needs to be fixed rather than recorded as expected behavior.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java:
##########
@@ -417,7 +417,16 @@ && check(((MapType) originalType).getValueType(), 
((MapType) targetType).getValu
                 return false;
             }
             for (int i = 0; i < targetFields.size(); i++) {
-                if (originalFields.get(i).isNullable() != 
targetFields.get(i).isNullable()) {
+                // A nullable target can safely accept a required source, but 
the inverse would
+                // allow a possible NULL into a required nested field.
+                if (originalFields.get(i).isNullable() && 
!targetFields.get(i).isNullable()) {
+                    return false;
+                }
+                // A required source field is not sufficient: value conversion 
itself can still
+                // produce NULL, so required targets only accept casts that 
preserve non-nullness.
+                if (!targetFields.get(i).isNullable()

Review Comment:
   [P2] Keep valid strict casts into required struct fields
   
   This new guard is correct for non-strict casts, where a failed child 
conversion becomes NULL, but it also runs when `isStrictMode` is true. In 
strict mode a bad conversion throws instead of producing NULL, so a successful 
required `STRING -> INT` child remains required.
   
   For example:
   
   ```text
   Cast(target STRUCT<metric:INT NOT NULL>)
     named_struct('metric', '1')  // source field is required STRING
   ```
   
   The scalar strict cast is allowed and yields `1`, but 
`Cast.castNullable(false, STRING, INT)` returns true here and rejects the 
enclosing struct during analysis. Please gate the fallible-nullability 
rejection to non-strict mode (or use a strict-aware predicate) and assert both 
`CheckCast.check(requiredString, requiredInt, true)` and the existing 
non-strict rejection.



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