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

   ## Which issue does this PR close?
   
   Closes #5210.
   
   ## Rationale for this change
   
   DataFusion 55.1.0, which Comet already pins, carries `unnest_outer`
   (apache/datafusion#22100) as `NullHandling::PreserveAndExpandEmpty`: an 
empty input list
   produces one NULL output row, exactly like a NULL list does.
   
   That is Spark's `explode_outer` semantics, so Comet no longer needs its own 
bridge. Before
   this PR the planner wrapped the array child in `ListEmptyToNullExpr`, a 
Comet-only
   `PhysicalExpr` that rewrote every empty row to NULL so that `preserve_nulls` 
would pad it.
   The planner can now ask DataFusion for the semantics directly.
   
   No user-visible behavior changes. `explode_outer` and `posexplode_outer` 
already ran
   natively.
   
   ## What changes are included in this PR?
   
   Planner (`native/core/src/execution/planner.rs`):
   
   - Drops the `ListEmptyToNullExpr` wrapper.
   - Selects `NullHandling::PreserveAndExpandEmpty` when `explode.outer`, and
     `NullHandling::Drop` otherwise. The non-outer arm is unchanged, since
     `with_preserve_nulls(false)` already mapped to `Drop`.
   
   Operator (`native/core/src/execution/operators/explode.rs`):
   
   - Ports upstream's empty-row bump into `find_longest_length`, which serves 
`posexplode`.
   - Makes the matching change in `list_output_lens`, Comet's fused 
single-`List` fast path,
     which serves `explode`. The two must agree element for element, because
     `predict_output_lens` uses the result both to place chunk boundaries and as
     `precomputed_lengths` for `build_batch`.
   - No change to `unnest_list_array` or `create_take_indices`. Upstream 
carries the whole
     feature in the length array, and the existing pad loop already emits the 
NULL. The
     contiguous-run fast path self-disqualifies for a padded row, because an 
empty row adds 1
     to `capacity` and 0 to the offset span.
   
   Deletes `native/core/src/execution/expressions/list_empty_to_null.rs` (311 
lines).
   
   The module docs on the fork were rewritten. They said to delete the fork 
once Comet moved
   to a release carrying apache/datafusion#24384. Comet is now on such a 
release, but the fork
   still carries two paths that were never upstreamed: `list_output_lens` and 
the
   contiguous-run slice in `unnest_list_array`. The docs now say that, so the 
next person does
   not delete the file and silently regress `explode`.
   
   ## How are these changes tested?
   
   Existing coverage is the real gate, and it is unchanged: `explode.sql` and 
`posexplode.sql`
   cover `explode` / `explode_outer` / `posexplode` / `posexplode_outer` across 
every primitive
   element type, nested arrays and structs, NULL and empty arrays, `LATERAL 
VIEW [OUTER]`, and
   a map `expect_fallback`. `CometGenerateExecSuite` covers batch boundaries, 
sliced inputs
   from limit/offset, and single rows that exceed the batch size.
   
   The native planner test `explode_evaluates_array_once_per_batch` already 
asserts the full
   Spark contract over `[[10, null], [], null, [20]]` for all eight 
combinations of
   `(outer, position, computed)`, including `pos = [0, 1, null, null, 0]`.
   
   Added in this PR:
   
   - `output_lens_substitute_per_null_handling` pins the absolute per-row 
lengths for all three
     `NullHandling` modes and checks the fused and general kernels against 
them, so a mistake
     made in both at once cannot pass.
   - `output_lens_handle_a_sliced_input` gains a sliced fixture that also has a 
validity buffer.
   - `contiguous_unnest_covers_empty_rows_and_dropped_nulls` gains the outer 
case, asserting
     that the fast path is rejected once rows are padded.
   - The chunking-equivalence tests now include empty rows and run under the 
two modes the
     planner can actually produce.
   
   Both length functions were mutation-tested: disabling the bump in either one 
fails the new
   tests.
   
   Spark behavior was verified against the Spark source rather than assumed. 
`ExplodeBase.eval`
   returns an empty collection for both a NULL and an empty array, and 
`GenerateExec`
   substitutes an all-NULL generator row, so the two cases are 
indistinguishable and `pos` is a
   true NULL rather than 0 or -1. The codegen path reaches the same result 
through an
   `index == -1` sentinel. This is unchanged across 3.4.3, 3.5.8, 4.0.x, and 
4.1.1.
   
   Performance: `native/core/benches/explode.rs`, comparing this branch against 
its merge base.
   No regression on any arm. The `explode_outer_with_nulls/bigint` arm, which 
exercises the
   per-row change most directly, came in at -1.7% to -3.1% over three runs. 
Run-to-run spread
   is wide enough that this is best read as no regression rather than as a win. 
The benchmark
   constructs `ExplodeExec` directly, so it does not capture the 
`ProjectionExec` and expression
   evaluation that this PR removes from the planned query.
   
   One plan-shape change: `posexplode_outer` over a plain column no longer 
needs the
   pre-projection, because the wrapper was what made the child a non-`Column`. 
That is one
   fewer `ProjectionExec`. No plan-stability golden file covers `explode`.
   


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