zhuxiangyi commented on code in PR #9423:
URL: https://github.com/apache/paimon/pull/9423#discussion_r4056327736


##########
paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java:
##########
@@ -534,35 +573,100 @@ private static PrimitiveType timestampPrimitiveType(
                 || timestampType.isAdjustedToUTC() != expectedAdjustedToUtc) {
             throw new UnsupportedOperationException();
         }
-        return primitiveType;
+        return column;
     }
 
-    private static PrimitiveType primitiveType(
+    /**
+     * The column the file holds for {@code fieldRef}, with the file's own 
spelling of the path.
+     * Callers that build a parquet column must use {@link FileColumn#path}: a 
{@link PrimitiveType}
+     * only knows its own leaf name, so rebuilding the column from it drops 
the enclosing path and
+     * addresses a column the file does not have.
+     */
+    private static FileColumn fileColumn(
             FieldRef fieldRef, MessageType fileSchema, boolean caseSensitive) {
-        PrimitiveType matched = findPrimitiveType(fieldRef, fileSchema, 
caseSensitive);
+        FileColumn matched = findFileColumn(fieldRef, fileSchema, 
caseSensitive);
         if (matched == null) {
             throw new UnsupportedOperationException();
         }
         return matched;
     }
 
+    /** A column the file actually holds: its own spelling of the path, and 
its physical type. */
+    private static class FileColumn {
+
+        private final String path;
+        private final PrimitiveType type;
+
+        private FileColumn(String path, PrimitiveType type) {
+            this.path = path;
+            this.type = type;
+        }
+    }
+
     /**
      * The file's column for {@code fieldRef}, or null when the file has no 
such column. A column
      * that exists but is not primitive cannot carry a predicate at all, so it 
is rejected outright.
+     *
+     * <p>{@code fieldRef} names a nested field with dots ({@code addr.city}), 
which is resolved by
+     * descending the file's groups. A top-level column matching the whole 
name wins over that walk,
+     * keeping flat columns spelled with dots resolving as they always did. 
parquet-mr identifies
+     * columns by dot-joined path too, so it cannot tell the two apart either 
way.
      */
     @Nullable
-    private static PrimitiveType findPrimitiveType(
+    private static FileColumn findFileColumn(
             FieldRef fieldRef, MessageType fileSchema, boolean caseSensitive) {
-        // Paimon predicates currently reference top-level fields only. Nested 
field
-        // predicates are rejected before reaching the format reader.
-        for (Type field : fileSchema.getFields()) {
+        Type matched = findChild(fileSchema, fieldRef.name(), caseSensitive);
+        if (matched != null) {
+            return toFileColumn(matched.getName(), matched);
+        }
+
+        String[] parts = fieldRef.name().split("\\.");

Review Comment:
   Confirmed, and reproduced exactly as described: a declared top-level `s.a` 
(BIGINT) absent from a file that only holds nested `s -> a` (INT32), `s.a IS 
NULL` pruned both rows instead of keeping them.
   
   `findFileColumn`'s walk now only descends groups for a `FieldRef` 
synthesized from `NestedFieldTransform` (its index left at `UNUSED_INDEX`); an 
ordinary field's dotted name with no exact match is treated as a genuinely 
missing column. My first attempt stopped there, but that alone wasn't enough - 
whatever name ends up handed to `FilterApi` gets re-split by parquet-mr the 
same way regardless of how we resolved it on our side, so a 
missing-but-walkable name was still silently binding to the unrelated column 
(this time as a type mismatch crash rather than the earlier read). The walk 
stays in place as a collision check: for an ordinary field, if it would still 
find something down there, the pushdown is refused outright instead of risking 
it; only when the walk also finds nothing is the column safe to leave to the 
existing missing-column fallback.
   
   Tests:
   
   - 
`ParquetFormatReadWriteTest.testMissingDottedTopLevelFieldIsTreatedAsNullNotAsANestedPath`
 - reproduces the false pruning before the fix.
   - 
`ParquetFormatReadWriteTest.testMissingDottedTopLevelFieldWithNoCollisionIsStillPushedDown`
 - companion control: a dotted, missing column with nothing to collide with 
must still be pushed down, not rejected outright.



##########
paimon-common/src/main/java/org/apache/paimon/predicate/PredicateBuilder.java:
##########
@@ -286,6 +286,10 @@ public Predicate notIn(int idx, List<Object> literals) {
         return in(idx, literals).negate().get();
     }
 
+    public Predicate notIn(Transform transform, List<Object> literals) {
+        return in(transform, literals).negate().get();

Review Comment:
   Confirmed. `in(int, List)` special-cases an empty list; `in(Transform, 
List)` never did, so it fell through to `or(equals)` with an empty `equals`, 
which throws - and `notIn` inherits that through `negate()`.
   
   `in(Transform, List)` now takes the same branch on an empty list that 
`in(int, List)` does, so both `in` and `notIn` on the `Transform` overload 
match their `idx` counterparts.
   
   Test: 
`PredicateBuilderTest.testInAndNotInTransformWithEmptyLiteralsMatchTheIdxOverloads`.



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

Reply via email to