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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/util/TypeCoercionUtils.java:
##########
@@ -1162,7 +1166,11 @@ private static Optional<DataType> 
findWiderComplexTypeForTwo(
                         leftFields.get(i).getDataType(), 
rightFields.get(i).getDataType(),
                         overflowToDouble, stringIsHighPriority);
                 if (newDataType.isPresent()) {
-                    
newFields.add(leftFields.get(i).withDataType(newDataType.get()));
+                    // The common layout must admit NULLs produced while 
either child is cast to it.

Review Comment:
   [P2] Apply the same nullability union to legacy CASE-WHEN
   
   This correction is missing from both legacy reducers used when 
`enable_new_type_coercion_behavior=false`. `findCommonComplexTypeForCaseWhen()` 
keeps only the left field's nullability, so a required `struct(1)` arm followed 
by a nullable struct arm is rejected while the reverse order succeeds. 
`LogicalSetOperation.getAssignmentCompatibleTypeLegacy()` unions declared 
nullability but still omits `Cast.castNullable`, so required 
`DATETIMEV2(3)`/`DATETIMEV2(6)` struct children produce a required common child 
and UNION/INTERSECT/EXCEPT insert a cast that the stricter `CheckCast` rejects. 
Please mirror the full declared-plus-cast nullability calculation in both 
legacy struct reducers and test them with the flag disabled.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java:
##########
@@ -730,6 +741,16 @@ private Plan 
bindIcebergTableSink(MatchingContext<UnboundIcebergTableSink<Plan>>
         IcebergExternalDatabase database = pair.first;
         IcebergExternalTable table = pair.second;
         LogicalPlan child = ((LogicalPlan) sink.child());
+        Optional<MvccSnapshot> targetSnapshot = 
ctx.cascadesContext.getStatementContext()

Review Comment:
   [P1] Fence rewrite tasks to the transaction's table generation
   
   Every rewrite group gets a fresh `StatementContext` and independently 
executes this latest-target lookup after `beginRewrite()` has already captured 
the shared transaction table. A metadata-only spec/schema change between task 
analyses can therefore make concurrent writers use different generations, while 
`finishRewrite()` converts all commit data with only 
`transaction.table().spec()/schema()/sortOrder()`. `validateFromSnapshot()` 
does not catch metadata-only evolution that leaves the data snapshot unchanged, 
and commit data carries no per-file spec identity to repair the mismatch. 
Please pass one frozen table generation through transaction start, every 
rewrite task/sink, and commit conversion, with a multi-group refresh-barrier 
test.



##########
be/src/format_v2/table_reader.h:
##########
@@ -1290,7 +1308,8 @@ class TableReader {
                                                      
mapping.file_column_name));
             }
         }
-        RETURN_IF_ERROR(_align_column_nullability(column, mapping.table_type));
+        RETURN_IF_ERROR(
+                _align_column_nullability(column, mapping.table_type, 
nullable_parent_null_map));

Review Comment:
   [P1] Preserve complex-container null masks until validation
   
   This validates only after complex rematerialization has consumed the masks 
it needs. For `outer NULLABLE STRUCT<inner STRUCT<a INT NOT NULL> NOT NULL>`, 
the required `inner` rematerializes without the ancestor mask and rejects valid 
masked `a` placeholders; trivial struct recursion likewise does not forward the 
mask. Conversely, ARRAY/MAP rematerializers unwrap their own nullable source 
root and reattach it only for a nullable target, so an optional-file NULL 
mapped to a required ARRAY/MAP becomes bare offsets/default payload instead of 
an error (and the top-level complex path returns without another alignment). 
Please carry both inherited and container-root masks through rematerialization, 
validate before dropping them, and test masked required-STRUCT descendants plus 
masked/unmasked required ARRAY/MAP roots.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/source/IcebergScanNode.java:
##########
@@ -697,6 +722,20 @@ public TableScan createTableScan() throws UserException {
         return icebergTableScan;
     }
 
+    private Table useFrozenEmptyTableGeneration(Table currentTable) {
+        Optional<MvccSnapshot> snapshot = getPinnedRelationSnapshot();
+        if (!isSystemTable && 
snapshot.filter(IcebergMvccSnapshot.class::isInstance).isPresent()) {
+            IcebergSnapshotCacheValue cacheValue =
+                    ((IcebergMvccSnapshot) 
snapshot.get()).getSnapshotCacheValue();
+            if (cacheValue.getSnapshot().getSnapshotId() < 0 && 
cacheValue.getIcebergTable().isPresent()) {

Review Comment:
   [P1] Reuse the frozen table for non-empty snapshots too
   
   The snapshot cache now retains the exact T0 `Table`, but this helper returns 
it only for the `-1` empty sentinel. A normal relation bound to T0/S0 still 
reloads T1 in `doInitialize()`, then runs `T1.newScan().useSnapshot(S0)` and 
reads T1's current spec/format/properties. For example, removing the last 
partition field in a metadata-only commit leaves S0 files on the old spec, but 
T1 makes `isPartitionedTable` false, so split construction omits S0's spec 
ID/partition JSON while the bound plan still treats that identity field as a 
partition column. Expiration can also make T1 reject S0 outright. Please use 
the retained generation for every snapshot-selectable normal scan and add a 
non-empty T0-bind/T1-replacement test.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/glue/translator/PhysicalPlanTranslator.java:
##########
@@ -585,7 +585,9 @@ public PlanFragment 
visitPhysicalIcebergTableSink(PhysicalIcebergTableSink<? ext
         List<Expr> outputExprs = Lists.newArrayList();
         icebergTableSink.getOutput().stream().map(Slot::getExprId)
                 .forEach(exprId -> 
outputExprs.add(context.findSlotRef(exprId)));
-        IcebergTableSink sink = new IcebergTableSink((IcebergExternalTable) 
icebergTableSink.getTargetTable());
+        IcebergTableSink sink = new IcebergTableSink(
+                (IcebergExternalTable) icebergTableSink.getTargetTable(),
+                icebergTableSink.getTargetIcebergTable());

Review Comment:
   [P1] Fence UPDATE/MERGE with the same target generation
   
   This retained target is added only to the ordinary Iceberg table sink. 
UPDATE/MERGE directly construct the parallel logical/physical merge sinks, 
whose planner sink and `beginMerge()` still independently call 
`getIcebergTable()`. If an ADD COLUMN refresh occurs after T0 outputs bind but 
before sink finalization, BE receives T1 `schema_json` while the output 
expressions still match T0, so the writer rejects the column count; spec-only 
evolution can also split distribution, writer, and transaction generations. 
Please capture one target before UPDATE/MERGE normalization and carry it 
through the merge logical/physical sinks, distribution, `IcebergMergeSink`, and 
`beginMerge()`, with a refresh-barrier test.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java:
##########
@@ -177,6 +180,11 @@ public PaimonScanNode(PlanNodeId id,
 
     @Override
     protected void doInitialize() throws UserException {
+        Optional<MvccSnapshot> relationSnapshot = getRelationSnapshot();
+        if (desc.getTable() instanceof PaimonExternalTable) {
+            // Rebuild the source before applying query options so both layers 
use this relation's snapshot.
+            source = new PaimonSource(desc, relationSnapshot);

Review Comment:
   [P1] Keep behavioral OPTIONS on this relation's source table
   
   Rebuilding `source` here does not fence the `OPTIONS` path: 
`getProcessedTable()` calls `source.getPaimonTable(scanParams)`, which bypasses 
`originTable` and rebuilds `statementTable` from 
`MvccUtil.getSnapshotFromContext(this)`. If a later relation of the same table 
binds a historical snapshot, an earlier latest relation with a behavioral 
option such as `scan.plan-sort-partition` applies that option to the historical 
table and reads old data; reversing relation order changes the result. Please 
resolve/apply behavioral options from this relation-local `originTable` and 
cover both relation orders.



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