Skip to content

[EPIC] Codegen-dispatch coverage audit: expressions that fall back to Spark where the JVM dispatcher would work #5572

Description

@andygrove

What / Why

Comet has a JVM codegen dispatcher (CometScalaUDF.emitJvmCodegenDispatch, spark/src/main/scala/org/apache/comet/serde/CometScalaUDF.scala:70) that compiles a Spark expression's own doGenCode output into a per-batch kernel reading and writing Arrow vectors directly. When a serde has no native path for some input, routing through the dispatcher keeps the whole operator inside Comet and still matches Spark byte for byte, instead of failing the enclosing projection back to Spark.

A serde opts in by mixing in CodegenDispatchFallback (spark/src/main/scala/org/apache/comet/serde/CometExpressionSerde.scala:125). QueryPlanSerde then tries the dispatcher for that serde's Unsupported and non-opt-in Incompatible results (QueryPlanSerde.scala:941 and :968) before giving up.

Adoption is uneven. I swept all 261 expression serdes under spark/src/main/scala/org/apache/comet/serde/ plus the version shims. 98 reach the dispatcher; 63 decline at least one case without one. This EPIC collects the cases where the mixin is applicable and worth adding, and — just as importantly — records the cases where it is not applicable, so they aren't re-litigated.

Several of the gaps are plain asymmetries against an already-dispatched sibling: bround dispatches but round on a double falls back; to_unix_timestamp dispatches but unix_timestamp on a string falls back; to_json, from_csv and schema_of_csv dispatch but to_csv never runs in Comet by default at all.

What gates a dispatch

CometBatchKernelCodegen.canHandle (spark/src/main/scala/org/apache/comet/codegen/CometBatchKernelCodegen.scala:119) rejects AggregateFunction, Generator, Unevaluable, and any bound reference or output type outside isSupportedDataType (:85) — notably NullType, ObjectType and Variant. Everything else is admitted, including CodegenFallback, nondeterministic and stateful expressions, HigherOrderFunction, and subquery expressions.

Every item below was checked against that gate.

Prerequisites

Both should land before the catch-all in the StaticInvoke item below.

High value

Each of these is a small change, and most close a documented asymmetry against an already-dispatched sibling.

Worthwhile

Considered and rejected — structurally impossible

These are recorded so the sweep doesn't get repeated on them:

  • All 25 aggregate serdes. Blocked twice over: canHandle rejects AggregateFunction outright, and CodegenDispatchFallback's self-type is CometExpressionSerde[_], so it cannot be mixed into CometAggregateExpressionSerde at all. This covers percentile with an array of percentages, approx_percentile on non-numeric input, collect_list / collect_set, bloom_filter_agg, avg / sum on intervals, and the bit_and / bit_or / bit_xor family.
  • CometHours / CometDays (serde/datetime.scala:813, :850). Verified against the Spark 4.0.1 bytecode: Hours and Days extend PartitionTransformExpression, which implements Unevaluable, so canHandle rejects them by construction.
  • CometLiteral, CometAttributeReference, CometKnownFloatingPointNormalized. What they decline is exactly the set of types that cannot cross the Arrow FFI boundary — the same set isSupportedDataType rejects. The dispatcher would decline them again.
  • Null-element array cases. The "null elements fall back" half of the array_position / flatten / shuffle notes in the compatibility guide is not dispatchable: NullType is absent from isSupportedDataType. Only the binary/struct half of those notes is actionable.
  • CometSortOrder. Sort keys are ordering specifications consumed by the native Sort and Window operators, not value expressions, so there is nothing to dispatch.
  • CometScalarFunction's ANSI guard (serde/CometScalarFunction.scala:30). A developer mis-wiring check, not a runtime input case.

Considered and deferred — marginal

Not filed, but recorded so the reasoning survives: negative-scale decimal in CometCeil / CometFloor / CometRound (only reachable with spark.sql.legacy.allowNegativeScaleOfDecimal=true); CometToPrettyString (only on the df.show() path); non-literal seeds in CometRandStr / CometRand / CometRandn; the deliberate all-foldable declines in CometConcatWs and CometArrayPosition, which are a handoff to ConstantFolding and should stay; and the unreachable sanity checks in CometDivide, CometCheckOverflow, CometMakeDecimal, CometSize, CometPreciseTimestampConversion, CometLeft and CometRight.

Suggested sequencing

  1. The two prerequisites. The closure-serialize guard in particular blocks the StaticInvoke catch-all.
  2. The high-value items. Each is a small change and each closes a documented asymmetry against an already-dispatched sibling, which makes them easy to review.
  3. The rest, in any order — they are independent and mostly good first issues.

The per-expression items below are all mixin changes: they report Unsupported from getSupportLevel, which routes through dispatchIfFallback before any ancestor has converted. That ordering is what makes them safe, and it is exactly what the rejected .orElse variant on #5574 gave up. None of them is blocked or made redundant by that issue.

Verification note

This sweep is static analysis of the serde definitions, the dispatcher's canHandle gate, and the generated docs/source/user-guide/latest/expressions.md. Nothing here has been reproduced against a running cluster yet, so each issue should confirm the fallback with a test before the fix lands.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions