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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/hudi/HudiUtils.java:
##########
@@ -268,6 +269,25 @@ public static HudiMvccSnapshot 
getHudiMvccSnapshot(Optional<TableSnapshot> table
         return new 
HudiMvccSnapshot(HudiUtils.getPartitionValues(tableSnapshot, hmsTable), 
timestamp);
     }
 
+    /**
+     * Resolve the instant used for split and schema planning.
+     */
+    public static Optional<String> resolveQueryInstant(Optional<MvccSnapshot> 
relationSnapshot,
+            Optional<TableSnapshot> tableSnapshot, HoodieTimeline timeline) {
+        if 
(relationSnapshot.filter(HudiMvccSnapshot.class::isInstance).isPresent()) {
+            long timestamp = ((HudiMvccSnapshot) 
relationSnapshot.get()).getTimestamp();
+            // The pinned timestamp is authoritative even if the active 
timeline advances later.

Review Comment:
   [P1] Capture Hudi partitions at the same instant
   
   This now makes the snapshot's timestamp authoritative for schema and split 
planning, but `getHudiMvccSnapshot()` builds that object with two independent 
latest lookups: `getLastTimeStamp()` can observe cached meta-client A/S0, then 
a concurrent table-cache invalidation/refresh can make 
`getPartitionValues(Optional.empty(), ...)` obtain client B/S1 and cache S1's 
partitions. The relation consequently prunes with S1 partitions while 
`queryInstant` enumerates files before/on S0; a partition removed or added by 
S1 can silently omit or mis-prune S0 data. Please capture the completed instant 
once and load the timestamp-keyed partition view for that exact instant, with a 
barrier test that swaps the cached generation between the two current phases.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergMvccSnapshot.java:
##########
@@ -29,4 +29,15 @@ public IcebergMvccSnapshot(IcebergSnapshotCacheValue 
snapshotCacheValue) {
     public IcebergSnapshotCacheValue getSnapshotCacheValue() {
         return snapshotCacheValue;
     }
+
+    @Override
+    public boolean isSameSnapshot(MvccSnapshot other) {
+        if (!(other instanceof IcebergMvccSnapshot)) {
+            return false;
+        }
+        IcebergSnapshot left = snapshotCacheValue.getSnapshot();
+        IcebergSnapshot right = ((IcebergMvccSnapshot) 
other).snapshotCacheValue.getSnapshot();
+        // A branch can retain its data snapshot while adopting a newer 
current schema.
+        return left.getSnapshotId() == right.getSnapshotId() && 
left.getSchemaId() == right.getSchemaId();

Review Comment:
   [P1] Include frozen Iceberg semantic metadata in scan equality
   
   Snapshot and schema IDs do not uniquely identify the relation semantics this 
patch now freezes. `default.name.mapping` can change in a property-only 
metadata commit without advancing either ID, and `IcebergScanNode` deliberately 
reads that relation-local mapping because it changes how ID-less legacy file 
columns resolve. Two ref/time-travel relations bound around that refresh 
compare equal here, so `PullUpJoinFromUnionAll` may hoist one scan and apply 
its aliases to both arms, changing values to or from NULL. Please include the 
frozen mapping (or a full immutable metadata generation identity) in equality 
and add a rewrite test with equal snapshot/schema IDs but different mappings.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -1408,6 +1432,24 @@ public IcebergTableQueryInfo getSpecifiedSnapshot() 
throws UserException {
         TableSnapshot tableSnapshot = getQueryTableSnapshot();
         TableScanParams scanParams = getScanParams();
         Optional<TableScanParams> params = Optional.ofNullable(scanParams);
+        Optional<MvccSnapshot> snapshot = getPinnedRelationSnapshot();
+        if (snapshot.isPresent() && snapshot.get() instanceof 
IcebergMvccSnapshot) {
+            if (source.getTargetTable() instanceof IcebergSysExternalTable
+                    && !((IcebergSysExternalTable) 
source.getTargetTable()).supportsSnapshotSelection()) {
+                return null;
+            }
+            IcebergSnapshot pinnedSnapshot =
+                    ((IcebergMvccSnapshot) 
snapshot.get()).getSnapshotCacheValue().getSnapshot();
+            // Empty Iceberg tables use -1 as a cache sentinel; passing it to 
useSnapshot would
+            // turn a valid empty scan (including metadata tables) into an 
unknown-snapshot error.
+            if (pinnedSnapshot.getSnapshotId() < 0) {

Review Comment:
   [P1] Preserve the bound empty state through scan planning
   
   Returning `null` here turns the bound empty generation into “use whatever 
snapshot the scan table currently has.” `doInitialize()` still loads that Table 
independently through the refreshable source, so an empty S0 relation followed 
by the first commit/cache refresh before physical init calls `newScan()` on S1 
and returns rows that did not exist when the relation was bound. The added test 
holds one unchanged empty mock and only proves that `useSnapshot(-1)` is 
skipped. Please keep empty as an executable scan state (for example, 
short-circuit normal data planning) or use the frozen Table generation, and add 
an empty-bind/first-commit barrier test.



##########
be/src/format_v2/table_reader.h:
##########
@@ -1486,8 +1505,8 @@ class TableReader {
                     _file_child_ordinal_for_mapping(mapping, *child_mapping, 
file_ordered_children);
             DORIS_CHECK(file_child_idx < file_struct->get_columns().size());
             ColumnPtr child_column = 
file_struct->get_column_ptr(file_child_idx);
-            
RETURN_IF_ERROR(_materialize_present_child_mapping_column(*child_mapping, 
child_column,
-                                                                      rows, 
&child_column));
+            RETURN_IF_ERROR(_materialize_present_child_mapping_column(
+                    *child_mapping, child_column, rows, &child_column, 
parent_null_map));

Review Comment:
   [P1] Reject source parent NULLs for a required target struct
   
   This passes the file parent's null map to required-child validation even 
when `mapping.table_type` makes the target struct itself non-nullable. The new 
waiver therefore accepts the child's NULL placeholder on a source row where the 
optional parent is NULL, but the result path below preserves that parent map 
only for a nullable target; otherwise it returns a bare `ColumnStruct`, turning 
the row into a non-null struct containing the child's nested default value. 
That silently satisfies neither source nor target semantics. Please allow this 
mask only when the target parent is nullable (and reject any source parent NULL 
otherwise), with optional-file to required-table tests for both a NULL row and 
a present row.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/check/CheckCast.java:
##########
@@ -414,7 +414,9 @@ && 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()) {

Review Comment:
   [P1] Reject fallible casts into required struct fields
   
   This accepts any declared non-null source field for a required target, but 
declared nullability is not enough for a fallible conversion. For example, 
`STRUCT('bad')` is now inferred with a required child, so a non-strict cast to 
`STRUCT<metric:INT NOT NULL>` passes this check. During literal folding, 
`checkedCastWithStrictChecking()` converts the invalid string to `NullLiteral`, 
while `StructLiteral` still labels the result with the required target type; BE 
then tries to materialize that NULL in a non-nullable child column. Please 
reject required-target children when `Cast.castNullable(false, sourceType, 
targetType)` is true, and cover a failing non-strict INSERT alongside the 
successful widening 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