andygrove opened a new pull request, #6682:
URL: https://github.com/apache/datafusion-comet/pull/6682

   **Stacked on #5854.** The first commits are #5854's; review 41ce5c61a. I'll 
rebase onto main and mark this ready once #5854 lands. cc @peterxcli, since 
this builds on your #5854.
   
   ## Which issue does this PR close?
   
   Closes #6549.
   
   ## Rationale for this change
   
   Spark's `ArrayBasedMapBuilder` finds duplicate keys in a `HashMap` of boxed 
keys. `Double.equals` and `Float.equals` compare `doubleToLongBits` and 
`floatToIntBits`, so every NaN is one key while `-0.0` and `0.0` are two, and 
the map keeps each key as it first occurred. From Spark 4.0 the builder also 
normalizes a `FLOAT` or `DOUBLE` key before it looks it up, unless 
`spark.sql.legacy.disableMapKeyNormalization` is set, and stores the normalized 
key. `ArrayBasedMapBuilder.from` is the one exception to the storing: when no 
key repeats, it returns its input arrays untouched, so `map_from_arrays` keeps 
a `-0.0` key and `map_from_entries` does not. I checked `ArrayBasedMapBuilder` 
at 3.4.3, 3.5.9, 4.0.4, 4.1.3 and 4.2.0. 3.4 and 3.5 are identical, and 4.0 
through 4.2 differ only in how they compare the policy string.
   
   The `datafusion-spark` kernels behind #5854's wrappers find duplicates by 
comparing `ScalarValue`s, which compare floats by their bits. So on every Spark 
version they kept two NaNs with different bits apart, where Spark raises 
`DUPLICATED_MAP_KEY` or keeps one entry under `LAST_WIN`. On 4.0+ they also 
kept `0.0` and `-0.0` apart, and `map_from_entries` returned `-0.0` where Spark 
returns `0.0`. #5854 documents this as a compatibility note, and this PR 
removes the difference.
   
   ## What changes are included in this PR?
   
   - **Native:** `SparkMapFromArrays` and `SparkMapFromEntries` take a 
`MapFloatKeys` rule, `Boxed` or `Normalized`, registered under their own names. 
For a `FLOAT` or `DOUBLE` key, the wrapper hands the kernel the keys as Spark 
compares them: `canonicalize_nans`, new in `float_semantics` next to 
`normalize_floats`, or `normalize_floats`. It then puts back the keys Spark 
stores. Under `Boxed` that is each key as it first occurred, bits included. 
Under `Normalized` it is the normalized key, except for a `map_from_arrays` row 
with no repeated key. A `DUPLICATED_MAP_KEY` names the repeated key with 
`Double.toString` / `Float.toString` (`-0.0`, `1.0`), as Spark does, where the 
kernel wrote `1`. All of this applies only to float key types. When no key's 
bits change (no non-canonical NaN, and no `-0.0` under normalization), the cost 
is one pass over the keys and a byte comparison per batch.
   - **Serde:** `MapBuilderSupport.nativeFunction` keeps the version policy in 
Scala, as #6385 proposes. It picks `map_from_arrays_normalized_keys` / 
`map_from_entries_normalized_keys` on Spark 4.0+ unless the legacy flag is set, 
and the plain names otherwise.
   - **Strict mode:** a top-level float key no longer makes the builders 
`Incompatible` under `spark.comet.exec.strictFloatingPoint`, since nothing 
differs any more. A struct or array key holding a float still declines, as in 
#5854.
   - **Docs:** the map_funcs audit and `expressions.md`.
   
   `map(k1, v1, ...)` (`CreateMap`) and `map_concat` already run through the 
codegen dispatcher, so they follow Spark and are unchanged.
   
   ## How are these changes tested?
   
   - **Rust:** 10 new unit tests in `map_builders.rs` cover the following:
     - NaNs merging and zeros staying apart under `Boxed`, with the first 
occurrence's bits kept;
     - `0.0`/`-0.0` collisions under `Normalized`, named `-0.0`;
     - a batch whose `map_from_arrays` rows keep or normalize their keys 
depending on whether a key repeats;
     - `map_from_entries` storage under both rules;
     - NULL rows, a sliced list, an all-scalar call, and the duplicate naming 
for `DOUBLE` and `FLOAT`, including one reported ahead of a `NULL` key.
   - **SQL fixtures:** four new files. `map_builders_float_keys.sql` and 
`map_builders_float_keys_last_win.sql` are gated on 4.0+. They cover the #6549 
repros (`map_from_entries(arrays_zip(k, v))` and the `explode(map_keys(...))` 
one), `DUPLICATED_MAP_KEY` for both builders, with the key pinned in the 
message, and a NaN with the sign bit set from negating a stored NaN. 
`map_builders_float_keys_legacy.sql` and 
`map_builders_float_keys_legacy_last_win.sql` set the legacy flag. They run on 
every version, since 3.4 and 3.5 have no such flag and behave the same way 
without it. `map_builders_strict_fp.sql` now expects all four queries to run 
natively.
   - **`CometFloatSemanticsSuite`:** #5854's two #6549 known gaps are gone. 
They are strict xfails, so their ten cases now have to match.
   - **Mutation:** with the native rewrite disabled, all four new fixtures and 
those ten cases fail.
   - **Results:**
     - Spark 4.1: `CometFloatSemanticsSuite`, `CometMapExpressionSuite` and the 
`expressions/map/` fixtures passed 505 tests.
     - Spark 3.5: the same suites passed 500. The 5 canceled are existing Spark 
4-only cases.
     - Rust: all 1127 `spark-expr` tests passed, workspace clippy 
(`--all-targets -D warnings`) passed, and `cargo fmt` is clean.
     - 3.4, 4.0 and 4.2 were not run locally. I'll add `run-all-spark-profiles` 
when this comes out of draft.
   


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