[python] Add array predicates to PredicateBuilder - #10175
jackylee-ch wants to merge 4 commits into
Conversation
|
Reviewed 1e800e9. The ARRAY predicates have real end-to-end functional value: the new read-path tests return the exact matching row IDs, and the row-level implementations agree with Java for null and empty-literal cases. One correctness gap needs attention: [P2] Reject non-array fields instead of silently applying Python containment. The new builder methods only check the field name, and Validation: |
1e800e9 to
08b63f7
Compare
Purpose:
Java's PredicateBuilder has arrayContains / arraysOverlap /
arrayContainsAll (Spark pushes them down), but PyPaimon's PredicateBuilder
had no array predicates. A filter on an ARRAY<STRING> label column
("samples tagged cat") could not be expressed against a Paimon table and
had to be written by the caller against the materialized result. These add
the predicates so the filter runs on Paimon's row-level filter path. Array
element bounds are not tracked in stats, so this does not prune files or
reduce table I/O; it makes the filter part of the read plan and returns
exactly the matching rows.
Change:
- Add array_contains / arrays_overlap / array_contains_all to
PredicateBuilder and the matching Predicate testers, mirroring Java
ArrayContains / ArraysOverlap / ArrayContainsAll row semantics (null
array or null element -> no match; arrays_overlap needs one shared
non-null element; array_contains_all is vacuously true for an empty
literal list on a non-null array).
- Validate the field is ARRAY (mirroring Java ArrayContains.elementType)
so an array predicate on a STRING/INT column raises instead of
silently running `literal in value` and selecting wrong rows.
- Arrays have no dataset-expression form, so mark the three methods
arrow-unsafe: they run on Paimon's exact row-level filter path (like
startsWith/contains/like) instead of a no-op truthy Arrow expression.
Stats testers return True (array element bounds are not tracked, so no
file is pruned).
Tests:
- Row-level tester parity with Java (match/no-match, null array, null
element, empty literals) and that the three methods are not
arrow-pushable.
- End-to-end: filter an ARRAY<STRING> column and assert the exact ids
returned (a dropped/ignored filter would return every row); a negative
case asserts a non-ARRAY field is rejected.
Written with Claude Code; verification is mine.
08b63f7 to
6f4b683
Compare
|
Thanks for the review. Addressed both points:
Force-pushed. |
|
Reviewed head [P2] Compare array elements using the declared element type ( I reproduced these through actual table writes, commits, reloads and reads in Parquet, Avro, ORC and row formats, projecting only the ID column. Please retain the element type, normalize literals accordingly, and use the same typed comparison contract as Java; alternatively reject unsupported element types explicitly. Add numeric-array tests for all three methods. This finding is scoped to the newly accepted array-predicate paths, not the existing scalar Python predicates. Validation: 86 Python array/predicate/read/projection tests passed; 22 Java PredicateBuilder tests passed on JDK 8 with normal Maven checks. Across all four formats, 24 additional string-array controls passed for exact row IDs, projection, compound OR, empty literal lists and nulls; the numeric mismatches above reproduced in every format. Configured flake8, Python 3.6 grammar and diff checks passed; current-head CI is green. The no-file-pruning limitation is now accurately described in the PR. |
|
Fixed in de846e4. The array predicates now compare elements with Java's element contract instead of plain Python membership:
Non-float element types keep exact |
|
Reviewed current head [P2] Normalize DOUBLE numeric literals before the signed-zero comparison ( On actual written, committed and reopened tables with negative-zero-only rows 3 and 10, all three methods ( For the Java reference, I explicitly normalized Validation: 68 Python array/predicate/read/projection tests and 22 Java PredicateBuilder tests pass; Java tests ran on JDK 8 with normal Maven checks. Configured flake8 and Python 3.6 grammar checks pass. The additional actual-file probes reproduce this mismatch in Parquet, Avro, ORC and row format; current-head CI is green. |
|
Thanks — fixed in 02d5140. Added |
02d5140 to
b771638
Compare
JingsongLi
left a comment
There was a problem hiding this comment.
Requirement fit: SUPPORTED. The ARRAY read-plan filters have end-to-end value; non-ARRAY validation, FLOAT 0.1 precision and NaN handling are fixed. Implementation: FINDINGS: one accepted-float normalization boundary remains. Signed-zero equivalence is excluded from this review as requested. Validation: 106 Python tests pass in the pinned-compatible runtime, 22 Java PredicateBuilder tests pass with normal Maven checks, and configured flake8 passes. Actual reopened Parquet/Avro/ORC/row tables matched the Java oracle for 444 additional queries; each format still has three query-construction failures described below. A conversion-only control passes all 480 queries. CI is green.
| def _to_float32(value: Any) -> Any: | ||
| if not isinstance(value, (int, float)) or isinstance(value, bool): | ||
| return value | ||
| return struct.unpack('<f', struct.pack('<f', float(value)))[0] |
There was a problem hiding this comment.
[P2] Handle finite FLOAT literal overflow during normalization
This new conversion accepts Python int/float literals but struct.pack("<f", ...) raises OverflowError for a finite double such as 1e40. On actual committed and reopened ARRAY tables, array_contains("floats", 1e40), arrays_overlap("floats", [1e40]) and array_contains_all("floats", [1e40]) all fail before reading, in Parquet, Avro, ORC and row formats. The same APIs with float("inf") correctly match the stored positive-infinity row. This is inconsistent with the typed normalization used for 0.1: Java PredicateBuilder.convertJavaObject(FLOAT, Double.valueOf(1e40)) uses Number.floatValue() and yields positive infinity, as does PyArrow float32 conversion. A source-free control changing only overflow conversion to the corresponding signed infinity makes the three queries return the correct row IDs, including ID-only projection and compound OR. Please preserve that float32 conversion behavior for finite overflow (both signs) and add a regression test for the three public methods. This concerns finite float overflow, independent of the excluded signed-zero comparison issue.
Normalizing a finite double beyond the float32 range (e.g. 1e40) to a
FLOAT array's element type raised OverflowError from struct.pack('<f'),
aborting the predicate build before the read. Java Number.floatValue()
and PyArrow float32 instead narrow such a value to the signed infinity.
Catch OverflowError in _to_float32 and return +/-inf accordingly, so an
array_contains / arrays_overlap / array_contains_all on an out-of-range
literal matches the stored +inf / -inf element. Adds a 3-method
regression test over FLOAT arrays holding +inf and -inf.
|
Good catch, fixed in 581c4b1. |
Purpose
Java's
PredicateBuilderhasarrayContains/arraysOverlap/arrayContainsAll(Spark pushes them down), but PyPaimon'sPredicateBuilderhad no array predicates. A filter on anARRAY<STRING>label column ("samples taggedcat") could not beexpressed against a Paimon table and had to be written by the caller
against the materialized result. Array element bounds aren't tracked in
stats, so these do not prune files or reduce table I/O; they make the
filter part of the read plan and return exactly the matching rows.
Change
array_contains/arrays_overlap/array_contains_alltoPredicateBuilderand the matchingPredicatetesters, mirroring JavaArrayContains/ArraysOverlap/ArrayContainsAllrow semantics(null array or null element → no match;
arrays_overlapneeds oneshared non-null element;
array_contains_allis vacuously true for anempty literal list on a non-null array).
ARRAY(mirroring JavaArrayContains.elementType) so an array predicate on a STRING/INTcolumn raises instead of silently running
literal in valueandselecting wrong rows.
arrow-unsafe and run on Paimon's exact row-level filter path (like
startsWith/contains/like) instead of a no-op truthy Arrowexpression. Stats testers return
True(no file is pruned).Tests
element, empty literals) and that the three methods are not
arrow-pushable.
ARRAY<STRING>column and assert the exact idsreturned; a negative case asserts a non-
ARRAYfield is rejected.Written with Claude Code; verification is mine.