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


##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java:
##########
@@ -206,16 +220,22 @@ public List<Slot> computeOutput() {
 
         if (table instanceof IcebergExternalTable) {
             // iceberg v3 need append row lineage columns
-            return computeIcebergOutput((IcebergExternalTable) table);
+            return computeIcebergOutput();
+        } else if (scanParams.isPresent() && scanParams.get().isOptions()
+                && (table instanceof PaimonExternalTable || table instanceof 
PaimonSysExternalTable)) {
+            List<Column> schema = table instanceof PaimonSysExternalTable
+                    ? ((PaimonSysExternalTable) 
table).getFullSchema(scanParams.get())
+                    : ((PaimonExternalTable) 
table).getFullSchema(scanParams.get());

Review Comment:
   [P1] Preload selector-free OPTIONS before locking
   
   A relation such as `p@options('scan.plan-sort-partition'='true')` is still 
marked non-latest merely because `scanParams` is present. For a Paimon table 
referenced only this way, `PreloadExternalMetadata` therefore skips 
latest-snapshot and schema warmup. `BindRelation` then calls 
`StatementContext.loadSnapshots` only after internal table locks are acquired; 
for this selector-free form, `PaimonExternalTable.loadSnapshot` still fetches 
the latest Paimon snapshot, so cold catalog/filesystem metadata I/O occurs 
inside the lock interval before this output is built. The execution path 
already treats this option as statement-pinned latest state, so the preload 
classification should use the same selector/startup distinction (or preload the 
exact relation state before locking). Please cover a mixed internal/Paimon 
query with selector-free OPTIONS and cold metadata.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java:
##########
@@ -206,16 +220,22 @@ public List<Slot> computeOutput() {
 
         if (table instanceof IcebergExternalTable) {
             // iceberg v3 need append row lineage columns
-            return computeIcebergOutput((IcebergExternalTable) table);
+            return computeIcebergOutput();
+        } else if (scanParams.isPresent() && scanParams.get().isOptions()

Review Comment:
   [P1] Exclude parameterized scans from MTMV rewrite
   
   This historical relation still looks identical to an ordinary latest 
`LogicalFileScan` to async-MV rewrite: `CollectRelation` collects MTMVs by the 
underlying table, `TableQueryOperatorChecker` rejects only file scans with 
`TABLESAMPLE`, and `RelationMapping` keys relations only by table identity. 
Thus an aggregate over `t@options('scan.snapshot-id'='1')` can be replaced by 
an MTMV refreshed from latest `t`, returning latest rows before a Paimon scan 
ever sees the requested snapshot. Please treat non-empty scan params as a table 
query operator (unless the MV proves identical semantics), and add a Paimon 
MTMV regression whose historical and latest aggregates differ.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonScanParams.java:
##########
@@ -0,0 +1,175 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.paimon;
+
+import org.apache.doris.analysis.TableScanParams;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.table.Table;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Validation and application rules for relation-scoped Paimon scan parameters.
+ */
+public final class PaimonScanParams {
+    private static final Set<String> QUERY_OPTION_KEYS = 
CoreOptions.getOptions().stream()
+            .map(option -> option.key())
+            .filter(key -> key.startsWith("scan."))
+            .collect(Collectors.toSet());
+
+    private static final Set<String> STARTUP_POSITION_KEYS = ImmutableSet.of(
+            CoreOptions.SCAN_TIMESTAMP.key(),
+            CoreOptions.SCAN_TIMESTAMP_MILLIS.key(),
+            CoreOptions.SCAN_WATERMARK.key(),
+            CoreOptions.SCAN_FILE_CREATION_TIME_MILLIS.key(),
+            CoreOptions.SCAN_CREATION_TIME_MILLIS.key(),
+            CoreOptions.SCAN_SNAPSHOT_ID.key(),
+            CoreOptions.SCAN_TAG_NAME.key(),
+            CoreOptions.SCAN_VERSION.key());
+
+    private static final Set<String> INHERITED_READ_STATE_KEYS = 
ImmutableSet.<String>builder()
+            .addAll(STARTUP_POSITION_KEYS)
+            .add(CoreOptions.SCAN_MODE.key())
+            .add(CoreOptions.SCAN_BOUNDED_WATERMARK.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN_TIMESTAMP.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN_SCAN_MODE.key())
+            .add(CoreOptions.INCREMENTAL_TO_AUTO_TAG.key())
+            .build();
+
+    private static final Set<String> INCREMENTAL_SYSTEM_TABLES = 
ImmutableSet.of(
+            "audit_log", "binlog", "row_tracking");
+
+    private static final Set<String> OPTIONS_SYSTEM_TABLES = ImmutableSet.of(
+            "audit_log", "binlog", "files", "manifests", "partitions", "ro", 
"row_tracking");
+
+    private PaimonScanParams() {
+    }
+
+    public static void validateOptions(Map<String, String> options) {
+        if (options.containsKey(CoreOptions.SCAN_FALLBACK_BRANCH.key())) {
+            throw new IllegalArgumentException("Paimon query option '"
+                    + CoreOptions.SCAN_FALLBACK_BRANCH.key()
+                    + "' is not supported because it requires rebuilding the 
table through the catalog factory.");
+        }
+
+        Set<String> unsupported = options.keySet().stream()
+                .filter(key -> !QUERY_OPTION_KEYS.contains(key))
+                .collect(Collectors.toSet());
+        if (!unsupported.isEmpty()) {
+            throw new IllegalArgumentException("Unsupported Paimon query 
option(s): " + unsupported);
+        }
+
+        long positionCount = 
options.keySet().stream().filter(STARTUP_POSITION_KEYS::contains).count();
+        if (positionCount > 1) {
+            throw new IllegalArgumentException(
+                    "Only one Paimon startup position can be specified: " + 
STARTUP_POSITION_KEYS);
+        }
+
+        if (options.containsKey(CoreOptions.SCAN_MODE.key()) && positionCount 
== 1) {
+            String position = options.keySet().stream()
+                    .filter(STARTUP_POSITION_KEYS::contains)
+                    .findFirst()
+                    .get();
+            String mode = 
options.get(CoreOptions.SCAN_MODE.key()).toLowerCase(Locale.ROOT);
+            if (!isCompatibleStartupMode(position, mode)) {
+                throw new IllegalArgumentException("Paimon scan mode '" + mode
+                        + "' is incompatible with startup position '" + 
position + "'.");
+            }
+        }
+    }
+
+    public static Table applyOptions(Table table, Map<String, String> options) 
{
+        validateOptions(options);
+        Map<String, String> isolatedOptions = new HashMap<>(options);
+        if (hasStartupOptions(options)) {
+            // Startup mode, position, and range form one inherited state 
family in Paimon. Clear
+            // absent members so one relation cannot accidentally reuse 
another relation's read state.
+            INHERITED_READ_STATE_KEYS.stream()

Review Comment:
   [P2] Clear Paimon's fallback scan keys too
   
   This isolation removes only canonical option names, but Paimon 1.3.1 still 
resolves `SCAN_MODE` from `log.scan` and `SCAN_TIMESTAMP_MILLIS` from 
`log.scan.timestamp-millis` when their canonical keys are absent. `Table.copy` 
removes exact string keys only, so a table or 
`paimon.table-default.log.scan=latest` survives this cleanup and makes 
`@options('scan.snapshot-id'='1')` conflict with `LATEST`; the legacy timestamp 
alias can similarly survive a different selector or an INCR isolation. Please 
clear each relevant `ConfigOption`'s fallback keys as part of the same state 
family and add legacy-default tests for ordinary OPTIONS and system-table INCR.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonExternalTable.java:
##########
@@ -106,6 +106,26 @@ public Table getPaimonTable(Optional<MvccSnapshot> 
snapshot) {
         }
     }
 
+    public Table getPaimonTable(TableScanParams scanParams) {
+        if (scanParams != null && scanParams.isOptions()) {
+            Map<String, String> options = scanParams.getMapParams();
+            // Behavioral options must retain the statement-pinned handle; 
only an explicit startup
+            // choice is allowed to replace that MVCC position with 
relation-local read state.
+            Table table = PaimonScanParams.hasStartupOptions(options)

Review Comment:
   [P1] Pin live-resolved startup options across both phases
   
   `hasStartupOptions()` sends every mode or position to this live base handle. 
In Paimon 1.3.1 batch planning, mode-only `latest`, `default`, and `full` scan 
whatever snapshot is latest when `newScan()` runs, so an alias can read S+1 
while another alias remains on statement-pinned S. A fixed future 
`scan.timestamp-millis=T` is also resolved separately during output binding and 
scan initialization; an intervening S+1 commit below T makes the unchanged 
option bind S but read S+1. Creation-time forms likewise remain live until 
planning. Please keep mode-only latest state on statement MVCC and resolve 
every time-relative relation to one immutable snapshot/schema carried through 
execution, including system-table wrappers. Add commit-between-phases tests for 
mode-only latest and a fixed future timestamp.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/PaimonScanParams.java:
##########
@@ -0,0 +1,175 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.paimon;
+
+import org.apache.doris.analysis.TableScanParams;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.paimon.CoreOptions;
+import org.apache.paimon.table.Table;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Validation and application rules for relation-scoped Paimon scan parameters.
+ */
+public final class PaimonScanParams {
+    private static final Set<String> QUERY_OPTION_KEYS = 
CoreOptions.getOptions().stream()
+            .map(option -> option.key())
+            .filter(key -> key.startsWith("scan."))
+            .collect(Collectors.toSet());
+
+    private static final Set<String> STARTUP_POSITION_KEYS = ImmutableSet.of(
+            CoreOptions.SCAN_TIMESTAMP.key(),
+            CoreOptions.SCAN_TIMESTAMP_MILLIS.key(),
+            CoreOptions.SCAN_WATERMARK.key(),
+            CoreOptions.SCAN_FILE_CREATION_TIME_MILLIS.key(),
+            CoreOptions.SCAN_CREATION_TIME_MILLIS.key(),
+            CoreOptions.SCAN_SNAPSHOT_ID.key(),
+            CoreOptions.SCAN_TAG_NAME.key(),
+            CoreOptions.SCAN_VERSION.key());
+
+    private static final Set<String> INHERITED_READ_STATE_KEYS = 
ImmutableSet.<String>builder()
+            .addAll(STARTUP_POSITION_KEYS)
+            .add(CoreOptions.SCAN_MODE.key())
+            .add(CoreOptions.SCAN_BOUNDED_WATERMARK.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN_TIMESTAMP.key())
+            .add(CoreOptions.INCREMENTAL_BETWEEN_SCAN_MODE.key())
+            .add(CoreOptions.INCREMENTAL_TO_AUTO_TAG.key())
+            .build();
+
+    private static final Set<String> INCREMENTAL_SYSTEM_TABLES = 
ImmutableSet.of(

Review Comment:
   [P2] Retain INCR for range-sensitive system tables
   
   This allowlist now rejects `$files`, `$partitions`, and `$ro`, although 
their Paimon 1.3.1 implementations copy the wrapped data table and consume its 
scan state. `FilesRead` replans the copied table, `PartitionsRead` calls its 
`newScan().listPartitionEntries()`, and `ReadOptimizedTable.newScan()` builds a 
batch scan from the copied options, so `incremental-between` changes each 
result. This also prevents the processed-table fix requested in the existing 
`$files@incr` thread from ever being exercised. Please derive capability from 
wrapper behavior or include these three types, and add exact range-result tests.



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