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


##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java:
##########
@@ -349,6 +370,58 @@ public MvccSnapshot loadSnapshot(Optional<TableSnapshot> 
tableSnapshot, Optional
         return new 
PaimonMvccSnapshot(getPaimonSnapshotCacheValue(tableSnapshot, scanParams));
     }
 
+    @Override
+    public MvccSnapshot loadLatestSnapshotFence() {
+        return new 
PaimonMvccSnapshot(PaimonUtils.loadLatestSnapshotFence(this));
+    }
+
+    @Override
+    public boolean requiresLatestSnapshotFence(
+            Optional<TableSnapshot> tableSnapshot, Optional<TableScanParams> 
scanParams) {
+        return !tableSnapshot.isPresent()
+                && scanParams.isPresent()
+                && scanParams.get().isOptions()
+                && 
PaimonScanParams.usesStatementSnapshot(scanParams.get().getMapParams());
+    }
+
+    @Override
+    public MvccSnapshot loadSnapshot(
+            Optional<TableSnapshot> tableSnapshot,
+            Optional<TableScanParams> scanParams,
+            Optional<MvccSnapshot> latestSnapshotFence) {
+        if (latestSnapshotFence.isPresent() && !tableSnapshot.isPresent() && 
!scanParams.isPresent()) {
+            PaimonSnapshot fence = ((PaimonMvccSnapshot) 
latestSnapshotFence.get())
+                    .getSnapshotCacheValue().getSnapshot();
+            return new 
PaimonMvccSnapshot(PaimonUtils.loadSnapshotAtFence(this, fence));
+        }
+        if (!latestSnapshotFence.isPresent()
+                || !requiresLatestSnapshotFence(tableSnapshot, scanParams)) {
+            return loadSnapshot(tableSnapshot, scanParams);
+        }
+        PaimonMvccSnapshot fence = (PaimonMvccSnapshot) 
latestSnapshotFence.get();
+        PaimonSnapshotCacheValue fenceValue = fence.getSnapshotCacheValue();
+        PaimonSnapshot fenceSnapshot = fenceValue.getSnapshot();
+        long snapshotId = fenceSnapshot.getSnapshotId();
+        TableScanParams params = scanParams.get();
+        Map<String, String> rawOptions = params.getMapParams();
+        params.reuseResolvedMapParams(PaimonScanParams.pinOptionsToSnapshot(

Review Comment:
   [P1] Preserve the fence's schema generation when reusing this synthetic 
snapshot pin. `pinOptionsToSnapshot` stores an ordinary `scan.snapshot-id`; 
later `getPaimonTable(scanParams)` treats that resolved key as a user schema 
selector, switches to `getBasePaimonTable()`, and `applyOptions` runs Paimon's 
normal `copy`, which time-travels schema. If S has schema A and a schema-only 
ALTER creates B, the fence correctly captures (S, B), but schema binding/scan 
decoration reverts this OPTIONS alias to A while a plain alias stays on B. This 
is downstream of the loader fix in r3689955956: the corrected fence is already 
returned before this copy undoes it. Mark synthetic pins or use 
`applyOptionsWithoutTimeTravel` against the fenced table, and test 
`getFullSchema` plus split planning after a schema-only ALTER.



##########
fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonExternalMetaCacheTest.java:
##########
@@ -68,8 +96,238 @@ public void 
testLatestSnapshotUsesLatestSchemaForPinnedRead() {
         Assert.assertEquals(12L, value.getSnapshot().getSnapshotId());
         Assert.assertEquals(4L, value.getSnapshot().getSchemaId());
         Assert.assertSame(pinnedTable, value.getSnapshot().getTable());
-        
Mockito.verify(latestSchemaTable).copyWithoutTimeTravel(Collections.singletonMap(
-                CoreOptions.SCAN_SNAPSHOT_ID.key(), "12"));
+        Mockito.verify(latestSchemaTable).copyWithoutTimeTravel(
+                Mockito.argThat(options -> 
"12".equals(options.get(CoreOptions.SCAN_SNAPSHOT_ID.key()))
+                        && options.entrySet().stream()
+                                .filter(entry -> entry.getValue() != null)
+                                .count() == 1));
+    }
+
+    @Test
+    public void 
testFullLatestProjectionCapsManifestParallelismBeforePartitionLoad() throws 
AnalysisException {
+        // Keep the test aligned with the loader contract: partition loading 
may report analysis failures.
+        int localCapacity = Runtime.getRuntime().availableProcessors();
+        Assume.assumeTrue(localCapacity < 256);
+        PaimonPartitionInfoLoader partitionLoader = 
Mockito.mock(PaimonPartitionInfoLoader.class);
+        Mockito.when(partitionLoader.load(Mockito.any(), Mockito.any(), 
Mockito.any()))
+                .thenReturn(PaimonPartitionInfo.EMPTY);
+        PaimonLatestSnapshotProjectionLoader loader = new 
PaimonLatestSnapshotProjectionLoader(
+                partitionLoader,
+                (nameMapping, schemaId) -> new PaimonSchemaCacheValue(
+                        Collections.emptyList(), Collections.emptyList(), 
null));
+        NameMapping nameMapping = new NameMapping(1L, "db", "table", 
"remote_db", "remote_table");
+        FileStoreTable baseTable = Mockito.mock(FileStoreTable.class);
+        FileStoreTable latestSchemaTable = Mockito.mock(FileStoreTable.class);
+        FileStoreTable pinnedTable = Mockito.mock(FileStoreTable.class);
+        Snapshot snapshot = Mockito.mock(Snapshot.class);
+        SchemaManager schemaManager = Mockito.mock(SchemaManager.class);
+        TableSchema latestSchema = Mockito.mock(TableSchema.class);
+        
Mockito.when(baseTable.copyWithLatestSchema()).thenReturn(latestSchemaTable);
+        
Mockito.when(latestSchemaTable.options()).thenReturn(Collections.singletonMap(
+                CoreOptions.SCAN_MANIFEST_PARALLELISM.key(), "256"));
+        
Mockito.when(latestSchemaTable.latestSnapshot()).thenReturn(Optional.of(snapshot));
+        Mockito.when(snapshot.id()).thenReturn(12L);
+        
Mockito.when(latestSchemaTable.copyWithoutTimeTravel(Mockito.anyMap())).thenReturn(pinnedTable);

Review Comment:
   [P1] Mock the post-snapshot table that production actually normalizes. 
`resolveLatestSnapshot` first gets `pinnedTable = 
latestSchemaTable.copyWithoutTimeTravel(snapshotOptions)` and then calls 
`runtimeSafeTable(pinnedTable)`. This test puts 256 only on 
`latestSchemaTable`; `pinnedTable.options()` defaults empty, so on every host 
satisfying `localCapacity < 256`, normalization returns `pinnedTable` 
unchanged. The verified cap-bearing call on `latestSchemaTable` never happens 
(its only copy carries the snapshot selector), and this test fails. Give 
`pinnedTable` the inherited value, stub its cap copy to a separate capped 
table, and verify the partition loader receives that table.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/merge/MergeIntoCommand.java:
##########
@@ -121,6 +121,14 @@ public MergeIntoCommand(List<String> targetNameParts, 
Optional<String> targetAli
                 Objects.requireNonNull(notMatchedClauses, "notMatchedClauses 
should not be null"));
     }
 
+    /** Return every relation root retained across prepared executions. */
+    public List<LogicalPlan> getRelationRoots() {

Review Comment:
   [P1] Expose subquery plans retained by MERGE's own expressions. This method 
returns only CTE/source, but `onClause` and matched/not-matched expressions can 
contain `SubqueryExpr`, and those query plans are neither plan children nor 
expressions under the returned roots. `ExecuteCommand` therefore never clears 
an OPTIONS relation in a prepared MERGE ON/WHEN subquery. After the first 
EXECUTE pins S and a later commit advances latest, the second EXECUTE resets 
`StatementContext` but retains `resolvedMapParams=S`, causing a conflict or 
stale selector resolution. This is distinct from r3694727320's source/CTE 
omission. Traverse every command-owned expression's subquery plan and test two 
EXECUTEs separated by a commit.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonSysExternalTable.java:
##########
@@ -135,23 +153,117 @@ public Table getSysPaimonTable() {
     }
 
     public Table getSysPaimonTable(TableScanParams scanParams) {
-        Table table = getSysPaimonTable();
+        return getSysPaimonTable(getRawSysPaimonDataTable(), scanParams);
+    }
+
+    public Table getSysPaimonTable(FileStoreTable dataTable, TableScanParams 
scanParams) {
         if (scanParams == null || !scanParams.isOptions()) {
-            return table;
+            FileStoreTable safeDataTable = (FileStoreTable) 
PaimonReaderOptions.runtimeSafeTable(dataTable);

Review Comment:
   [P2] Apply data-reader validation only to system types that open data 
readers. This unconditional check rejects a physical `read.batch-size=0` for 
`$schemas`, `$options`, and `$partitions`, although Doris classifies them as 
non-Paimon-reader paths and their implementations only read schema/partition 
metadata. `$schemas` and `$options` also reject OPTIONS, so a legacy/physical 
unsafe reader value cannot be overridden for an otherwise safe metadata query. 
The same capability-blind check is repeated in ScanNode/TVF and the old-FE Java 
backstop. Keep manifest-planning checks where appropriate, but gate batch/async 
reader validation on the system type's reader capability, and cover allowed 
`$schemas`/`$partitions` versus reader-backed `$audit_log`.



##########
fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java:
##########
@@ -519,69 +622,271 @@ static Optional<Long> parseDataSizeBytes(String value) {
         if (value == null || value.trim().isEmpty()) {
             return Optional.empty();
         }
-        String normalized = value.trim().toLowerCase(Locale.ROOT).replace("_", 
"").replace(" ", "");
-        int unitStart = 0;
-        while (unitStart < normalized.length()
-                && (Character.isDigit(normalized.charAt(unitStart)) || 
normalized.charAt(unitStart) == '.')) {
-            unitStart++;
-        }
-        if (unitStart == 0) {
-            return Optional.empty();
-        }
         try {
-            double number = Double.parseDouble(normalized.substring(0, 
unitStart));
-            String unit = normalized.substring(unitStart);
-            long multiplier;
-            switch (unit) {
-                case "":
-                case "b":
-                case "byte":
-                case "bytes":
-                    multiplier = 1L;
-                    break;
-                case "k":
-                case "kb":
-                case "kib":
-                    multiplier = 1024L;
-                    break;
-                case "m":
-                case "mb":
-                case "mib":
-                    multiplier = 1024L * 1024L;
-                    break;
-                case "g":
-                case "gb":
-                case "gib":
-                    multiplier = 1024L * 1024L * 1024L;
-                    break;
-                case "t":
-                case "tb":
-                case "tib":
-                    multiplier = 1024L * 1024L * 1024L * 1024L;
-                    break;
-                default:
-                    return Optional.empty();
-            }
-            return Optional.of((long) (number * multiplier));
-        } catch (NumberFormatException e) {
+            // Keep the BE guard's accepted grammar identical to the Paimon 
option parser that will
+            // consume this value; accepting a superset lets invalid 
serialized options reach scans.
+            return Optional.of(MemorySize.parse(value).getBytes());
+        } catch (IllegalArgumentException e) {
             return Optional.empty();
         }
     }
 
     private void initTable() {
         Preconditions.checkState(params.containsKey("serialized_table"));
         table = PaimonUtils.deserialize(params.get("serialized_table"));
+        String encodedSystemSource = params.get(PAIMON_OPTION_PREFIX + 
DORIS_SERIALIZED_SYSTEM_SOURCE);
+        FileStoreTable systemSource = encodedSystemSource == null
+                ? null : PaimonUtils.deserialize(encodedSystemSource);
+        table = applyBackendManifestParallelism(table,
+                params.get(PAIMON_OPTION_PREFIX + 
DORIS_MANIFEST_PARALLELISM_CAP),
+                Runtime.getRuntime().availableProcessors(), systemSource,
+                params.get(PAIMON_OPTION_PREFIX + DORIS_SYSTEM_TABLE_TYPE));
+        table = applyDefaultReadBatchSize(table, batchSize);
+        paimonAllFieldNames = PaimonUtils.getFieldNames(this.table.rowType());
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("paimonAllFieldNames:{}", paimonAllFieldNames);
+        }
+    }
+
+    static Table applyDefaultReadBatchSize(Table table, int dorisBatchSize) {
+        validateSerializedReaderOptions(table);
+        if (hasReadBatchSize(table)) {
+            // Doris' output block size and Paimon's reader batch are 
independent controls; an
+            // explicitly validated value on any hidden reader must survive 
transport unchanged.
+            return table;
+        }
         // The serialized table may pin an older data snapshot while carrying 
the latest schema
         // after a schema change. Applying a normal copy would time travel to 
that snapshot's
         // schema again and make renamed or newly added columns disappear.
         Map<String, String> readOptions = Collections.singletonMap(
-                CoreOptions.READ_BATCH_SIZE.key(), String.valueOf(batchSize));
-        table = table instanceof FileStoreTable
+                CoreOptions.READ_BATCH_SIZE.key(), 
String.valueOf(dorisBatchSize));
+        return table instanceof FileStoreTable

Review Comment:
   [P1] Preserve the transported schema generation when injecting the default 
batch size into a system wrapper. The preceding rebuild can produce 
`AuditLogTable`/`BinlogTable`/`RowTrackingTable`/`ReadOptimizedTable` over a 
source fenced at (snapshot S, latest schema B), but this non-FileStore branch 
calls the wrapper's normal `copy`. In Paimon 1.3.1 each wrapper delegates to 
`wrapped.copy`, whose time-travel path replaces B with S's schema A. With no 
explicit batch option, BE initialization can therefore lose a newly 
added/renamed field after FE already bound B. This is a later BE-only copy, 
distinct from the FE system-wrapper thread r3698463998. Apply the option with 
`copyWithoutTimeTravel` to the hidden/transported source and rebuild the 
wrapper; test a reader-backed system scan across a schema-only ALTER.



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