LuciferYang opened a new pull request, #9515:
URL: https://github.com/apache/paimon/pull/9515

   ### Purpose
   
   close #9514
   
   Hilbert clustering maps each order column to a long before placing it on the 
curve, and `PRIMITIVE_EMPTY` (`Long.MAX_VALUE`) is the value every type uses 
for null. The boolean mapping used it for TRUE, in both copies of the mapping: 
`HilbertIndexer` in paimon-common and `SparkHilbertUDF.booleanToOrderedLongUDF` 
in paimon-spark-common. A TRUE value and a NULL value therefore landed on the 
same point of the curve, so rows that differ on that column clustered as if 
they were equal, and a `a = true` predicate lost the file skipping the 
clustering was supposed to buy. TRUE now maps to 1 and FALSE stays at 0 on both 
sides.
   
   The Spark UDF was also the only one of the nine in that file without a null 
check. `functions.udf((Boolean value) -> ...)` has no input encoders, so 
Spark's `HandleNullInputsForUDF` does not insert a null short circuit and the 
lambda receives `null` directly, where `value ?` unboxed it into a 
`NullPointerException` on the executor. Clustering a nullable boolean column 
through `sys.compact` failed outright. #7451 added this guard to the string and 
binary UDFs in the same two files and left boolean out.
   
   The hilbert value is a transient sort key: `HilbertSorter` adds it, sorts on 
it and drops it, on both engines. Nothing on disk records it, so existing 
tables read back unchanged and only the layout produced by a future 
sort-compact or clustered write differs.
   
   ### Tests
   
   - `HilbertIndexerTest.testBooleanValuesDistinctFromNull` (new file) asserts 
the curve position of a FALSE row, a TRUE row and a NULL row against 
`hilbertCurvePosBytes` of `{0, 0}`, `{1, 1}` and `{MAX_VALUE, MAX_VALUE}`. 
Pinning the exact positions pins the mapping, so an inverted mapping that keeps 
the three distinct cannot pass and drift away from the Spark side; the three 
pairwise-distinct assertions are kept as an explicit statement of the collision 
itself.
   - 
`SparkHilbertUDFTest.testBooleanColumnMapsNullFalseAndTrueToDistinctValues` 
(new file) runs a local `SparkSession` over a nullable BOOLEAN column with 
true, false and null rows through `sortedLexicographically(col, BooleanType)` 
and asserts null maps to `Long.MAX_VALUE`, TRUE to 1 and FALSE to 0. It lives 
in paimon-spark-common next to `SortedIndexTopoBuilderTest`, which already 
builds a local session there, and runs in about three seconds.
   
   Verified red before the change: the indexer test fails on the exact-position 
assertion, and the Spark test fails with `java.lang.NullPointerException`, 
which also confirms that Spark really does hand `null` to that lambda.
   
   `mvn -pl paimon-common test` on JDK 8: 12466 tests, 0 failures. `mvn -pl 
paimon-spark/paimon-spark-common 
-Dtest='SparkHilbertUDFTest,SortedIndexTopoBuilderTest' test`: 5 tests, 0 
failures, both session-creating classes green in one JVM. checkstyle, spotless, 
enforcer and rat run clean on both modules.
   


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