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


##########
fe/be-java-extensions/paimon-scanner/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java:
##########
@@ -588,9 +596,35 @@ private static String[] splitParam(String value, String 
delimiter) {
     private Map<String, String> buildTableOptions(Map<String, String> 
tableOptions) {
         Map<String, String> options = new HashMap<>(tableOptions);
         options.put(CoreOptions.READ_BATCH_SIZE.key(), 
String.valueOf(batchSize));
+        String localMillis = params.get(PAIMON_OPTION_PREFIX + 
DORIS_FILE_CREATION_TIME_LOCAL_MILLIS);
+        if (localMillis != null) {
+            long lowerBound = Long.parseLong(localMillis);
+            long epochMillis = toFileCreationTimeEpochMillis(lowerBound, 
ZoneId.systemDefault());
+            String existingMillis = params.get(
+                    PAIMON_OPTION_PREFIX + 
DORIS_FILE_CREATION_TIME_EXISTING_MILLIS);
+            if (existingMillis != null) {
+                // The query predicate is an additional restriction and must 
never weaken a
+                // stronger cutoff already configured on the wrapped 
FileStoreTable.
+                epochMillis = Math.max(epochMillis, 
Long.parseLong(existingMillis));
+            }
+            options.put(CoreOptions.SCAN_FILE_CREATION_TIME_MILLIS.key(), 
String.valueOf(epochMillis));

Review Comment:
   **P1: Skip this pushdown for incompatible Paimon startup modes.** A wrapped 
table with a valid setting such as `scan.mode=latest-full` now fails to open: 
`FilesTable.copy` delegates to `FileStoreTable.copyInternal`, this option is 
merged into the existing schema, and Paimon 1.3.1 
`SchemaValidation.validateStartupMode` requires 
`scan.file-creation-time-millis` to be absent for `latest-full`, snapshot, 
timestamp, and incremental modes. Before this change the same `$files` query 
ran and relied on the retained Doris predicate; now JNI initialization throws. 
Please inspect the exact wrapped table's startup configuration and omit the 
optimization unless it is compatible with `FROM_FILE_CREATION_TIME`, with real 
`FilesTable` tests for conflicting modes. This is distinct from the existing 
stronger-cutoff thread: validation fails before the two cutoff values can be 
compared.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java:
##########
@@ -928,4 +968,76 @@ private Table getProcessedTable() throws UserException {
         }
         return baseTable;
     }
+
+    @VisibleForTesting
+    static OptionalLong extractFileCreationTimeLowerBound(List<Expr> 
conjuncts) {
+        OptionalLong result = OptionalLong.empty();
+        for (Expr conjunct : conjuncts) {
+            result = maxLowerBound(result, 
extractFileCreationTimeLowerBound(conjunct));
+        }
+        return result;
+    }
+
+    private static OptionalLong extractFileCreationTimeLowerBound(Expr expr) {
+        if (expr instanceof CompoundPredicate) {
+            CompoundPredicate predicate = (CompoundPredicate) expr;
+            if (predicate.getOp() != CompoundPredicate.Operator.AND) {
+                return OptionalLong.empty();
+            }
+            return 
maxLowerBound(extractFileCreationTimeLowerBound(predicate.getChild(0)),
+                    extractFileCreationTimeLowerBound(predicate.getChild(1)));
+        }
+        if (!(expr instanceof BinaryPredicate)) {
+            return OptionalLong.empty();
+        }
+
+        BinaryPredicate predicate = (BinaryPredicate) expr;
+        BinaryPredicate.Operator operator = predicate.getOp();
+        SlotRef slot = convertToDirectSlotRef(predicate.getChild(0));
+        LiteralExpr literal = convertToLiteral(predicate.getChild(1));
+        if (slot == null || literal == null) {
+            slot = convertToDirectSlotRef(predicate.getChild(1));
+            literal = convertToLiteral(predicate.getChild(0));
+            operator = operator.commutative();
+        }
+        if (slot == null || literal == null
+                || 
!PAIMON_FILE_CREATION_TIME_COLUMN.equalsIgnoreCase(slot.getColumnName())
+                || (operator != BinaryPredicate.Operator.GE && operator != 
BinaryPredicate.Operator.GT)) {
+            return OptionalLong.empty();
+        }
+
+        Object value = new TimestampType(3).accept(new 
PaimonValueConverter(literal));

Review Comment:
   **P1: Keep pre-epoch cutoffs from moving forward.** This reuses 
`PaimonValueConverter.visit(TimestampType)`, which leaves 
`Calendar.MILLISECOND` populated and computes `getTimeInMillis() / 1000 * 1000 
+ micros / 1000`; Java division truncates negative values toward zero. For 
`creation_time >= '1969-12-31 23:59:59.500'`, the intended zone-free cutoff is 
`-500`, but the converter can emit `+500`. On a UTC BE, Paimon's manifest 
filter then drops a supported file timestamp such as epoch `100`, although the 
retained Doris predicate accepts it. Please derive this cutoff with proleptic 
local-time arithmetic such as Paimon's `Timestamp.fromLocalDateTime(...)` (or 
equivalent `java.time` conversion) and add a pre-epoch boundary test. This is 
separate from the timezone/DST threads because it reproduces in UTC before zone 
conversion.



##########
fe/be-java-extensions/paimon-scanner/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java:
##########
@@ -588,9 +596,35 @@ private static String[] splitParam(String value, String 
delimiter) {
     private Map<String, String> buildTableOptions(Map<String, String> 
tableOptions) {
         Map<String, String> options = new HashMap<>(tableOptions);
         options.put(CoreOptions.READ_BATCH_SIZE.key(), 
String.valueOf(batchSize));
+        String localMillis = params.get(PAIMON_OPTION_PREFIX + 
DORIS_FILE_CREATION_TIME_LOCAL_MILLIS);
+        if (localMillis != null) {
+            long lowerBound = Long.parseLong(localMillis);
+            long epochMillis = toFileCreationTimeEpochMillis(lowerBound, 
ZoneId.systemDefault());
+            String existingMillis = params.get(
+                    PAIMON_OPTION_PREFIX + 
DORIS_FILE_CREATION_TIME_EXISTING_MILLIS);
+            if (existingMillis != null) {
+                // The query predicate is an additional restriction and must 
never weaken a
+                // stronger cutoff already configured on the wrapped 
FileStoreTable.
+                epochMillis = Math.max(epochMillis, 
Long.parseLong(existingMillis));
+            }
+            options.put(CoreOptions.SCAN_FILE_CREATION_TIME_MILLIS.key(), 
String.valueOf(epochMillis));

Review Comment:
   **P1: Preserve fallback-branch partition ownership.** `FilesTable.copy` 
propagates this cutoff through `FallbackReadFileStoreTable.copy` into both the 
main and fallback children. When `FilesRead` later expands the split, 
`FallbackReadScan.plan()` marks a partition as main-owned only if the cutoff 
left a surviving main split. For example, with a main file at time `100`, a 
fallback file for the same partition at `1000`, and `creation_time >= 500`, the 
old scan selects the main partition and the retained predicate returns no row; 
this pushdown first removes the main split, activates the fallback partition, 
and returns its `1000` row. Please skip this optimization for fallback-read 
tables (or fix ownership before applying the cutoff) and add a real 
fallback-backed `$files` test. This uses compatible startup options and one 
exact table revision, so it is separate from the validation and cache-drift 
findings.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java:
##########
@@ -918,6 +947,17 @@ private Table getProcessedTable() throws UserException {
             if (getQueryTableSnapshot() != null) {
                 throw new UserException("Paimon system tables do not support 
time travel.");
             }
+            PaimonSysExternalTable systemTable = (PaimonSysExternalTable) 
source.getExternalTable();
+            if 
(PAIMON_FILES_SYSTEM_TABLE_TYPE.equalsIgnoreCase(systemTable.getSysTableType()))
 {
+                fileCreationTimeLowerBound = 
extractFileCreationTimeLowerBound(conjuncts);
+                String existingLowerBound = systemTable.getTableProperties()

Review Comment:
   **P1: Read the existing cutoff from the same table revision that is 
serialized.** The `$files` wrapper comes from a direct 
`PaimonExternalCatalog.getPaimonTable(...)` load, while this value comes 
through the source table's independent `PaimonExternalMetaCache` handle. After 
metadata drift those handles can disagree. For example, a cached cutoff of 
`1000` plus a newly loaded wrapper cutoff of `2000` and query cutoff `500` 
sends `1000`; the BE copy then overwrites the wrapper's actual `2000`, 
admitting files in `[1000,2000)` that its scan restriction excluded. Reverse 
drift can over-prune. Please derive the existing value from the exact wrapped 
table/revision (or skip the optimization when identity is not guaranteed) and 
test deliberately divergent handles. This is distinct from the prior 
stronger-cutoff thread: the new preservation value itself can be stale.



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