schenksj commented on PR #4952:
URL: 
https://github.com/apache/datafusion-comet/pull/4952#issuecomment-5063375635

   Thanks for the thorough review, @parthchandra — the "core must stay 
format-agnostic" framing is
   exactly right, and following the `PlanDataInjector` ServiceLoader pattern 
made the whole thing
   simpler than the reflective bridge it replaces. I've pushed a refactor 
addressing every thread.
   A few notes below, including two places where I deviated slightly from the 
literal suggestion and
   why.
   
   ## What changed (per thread)
   
   - **`CometScanContrib` SPI.** `DeltaIntegration.scala` (the reflective 
bridge, ~200 lines) is
     deleted and replaced by the generic trait + ServiceLoader object you 
sketched, in
     `spark/.../rules/CometScanContrib.scala`. Discovery mirrors 
`PlanDataInjector.injectors`
     exactly (`ServiceLoader.load` + `NonFatal` fallback to empty). The Delta 
side is
     `DeltaScanRuleContrib implements CometScanContrib` plus a
     `META-INF/services/org.apache.comet.rules.CometScanContrib` resource, both 
packaged only under
     `-Pcontrib-delta`. Core names no contrib and carries none on a default 
build.
   
   - **`CometContribScanMarker`.** Added as you described; `CometExecRule` is 
now a plain type test
     (`case marker: CometContribScanMarker => convertToComet(marker, 
marker.scanHandler)`) with no
     reflective handler lookup.
   
   - **Proto extension point.** `delta_scan = 118` is gone, replaced by a 
single permanent
     `contrib_scan = 200` envelope (`+ reserved 118`), dispatched by `type_url` 
on the Rust side.
     Core's oneof never grows per-format again and the 118 collision with #4633 
is resolved. The
     `DeltaScan` messages stay in `operator.proto` for now (relocating them 
into a contrib `.proto`
     needs a prost build pipeline the contrib crate doesn't have yet — happy to 
do that as a
     follow-up).
   
   - **`CometScanRule`.** Outer `transformScan` match order restored to match 
`main`, and the
     delegation is now `CometScanContrib.tryTransformV1(...)`.
   
   ## Two deliberate deviations
   
   **1. Hand-rolled envelope instead of `google.protobuf.Any`.** The field 
layout is *identical* to
   `Any` (`type_url` + packed `value`), so the JVM can still populate it 
straight from
   `Any.pack(...)` and the Rust side routes purely on `type_url` — the 
architecture you asked for is
   unchanged. I couldn't use the well-known type directly because Comet 
compiles this `.proto` with
   two toolchains: Rust `prost-build` handles `Any` fine, but the Maven 
`protoc-jar` plugin can't
   resolve `import "google/protobuf/any.proto"` with a downloaded 
`<protocArtifact>`, and
   `includeStdTypes=true` NPEs inside the plugin. The hand-rolled message needs 
no WKT import, no
   `prost-types` dep, and no plugin workaround. If you'd prefer the real `Any`, 
I'm glad to switch —
   it just means bundling `any.proto` locally or pinning a plugin version that 
doesn't hit the NPE.
   
   **2. `CometContribScanMarker extends SparkPlan` rather than a `this: 
SparkPlan =>` self-type.**
   With the self-type, a value statically typed as the trait isn't a 
`SparkPlan`, so
   `convertToComet(marker, ...)` (which takes `SparkPlan`) and 
`getOrElse(marker)` (which must yield
   one) don't typecheck. Extending `SparkPlan` gives the bound pattern variable 
the identity the call
   site needs; a contrib mixes it into its scan-exec node, which already 
extends `SparkPlan`, so
   linearization stays consistent.
   
   ## One subtlety on the metadata-column guard (threads at 
`CometScanRule.scala:122` / `:177`)
   
   You're right that with a generic hook the outer reorder is unnecessary and 
the re-applied
   `metadataCols` check is redundant — I removed both. But keeping the outer 
guard strictly *before*
   the contrib is consulted turns out to conflict with the other half of your 
comment ("the Delta
   implementation decides whether it can handle metadata columns"): the Delta 
reader synthesizes
   `_metadata.*` itself, so if the outer guard rejects a `_metadata` scan 
before the contrib ever
   sees it, Delta loses that capability. This isn't visible in this PR (it's 
inert), which is exactly
   why I want to flag it.
   
   So I kept your outer match order, but moved the metadata bailout out of the 
outer match and into
   the head of each built-in transform path (`transformV1Scan` / 
`transformV2Scan`), right after that
   path's contrib hook declines. Net effect: identical behavior for default 
builds — `isSupportedScanNode`
   admits only V1/V2, and both now apply the guard — while a contrib still gets 
first crack, which is
   what your comment intended. I confirmed the exact fallback string is 
unchanged on the default build
   (`CometIcebergNativeSuite` "should report unsupported metadata columns" 
passes).
   
   ## Validation
   
   Because this PR is inert, I didn't want to land the contract change on its 
own evidence — an
   empty registry exercises none of it. So I carried the new contract through 
the rest of the Delta
   split stack (the driver/executor Rust, the Scala claim/decline + serde, CDF, 
and the test
   battery) and ran it end to end:
   
   - `dev/verify-contrib-delta-gate.sh` passes: default `libcomet` has **0 
Delta symbols** and is
     13 MB smaller; default build compiles **no** contrib classes and — new 
check — packages **no**
     `META-INF/services` contrib files (registration, not class presence, is 
what turns a contrib on
     now).
   - Delta contrib suites: **194 tests, 0 failures** across all 33 suites, 
exercising the full
     `ContribScan` round-trip (JVM pack → `type_url` dispatch → native decode) 
on real reads.
   - `CometIcebergNativeSuite` on a **default** build: 75/75, confirming no 
default-path regression.
   - JVM compiles on Scala 2.12 (spark-3.5) and 2.13 (spark-4.0) with 
`-Pcontrib-delta`; clippy at
     `-D warnings` and `cargo fmt` clean on both crates.
   
   That pass caught a couple of things the inert PR couldn't show on its own — 
most importantly the
   metadata-guard interaction above, and that a throwing contrib needs to 
degrade to a decline
   (the old bridge funneled `InvocationTargetException` → log + fall back; the 
direct SPI call
   doesn't, so `CometScanContrib` now wraps each hook in `NonFatal`). Both are 
folded into this PR.
   
   ## Lance (#4633) coordination
   
   This trait is intended to subsume the `CometScanContrib` #4633 was defining 
— Lance would
   implement `tryTransformV2` on this same trait and pack a `LanceScan` into 
the shared
   `contrib_scan` envelope, so the two land coherently and there's no second 
oneof field to collide.
   Happy to coordinate the sequencing however you'd prefer.
   
   Thanks again — this is a cleaner design than what I started with.
   
   ---
   🤖 This reply was drafted with [Claude Code](https://claude.com/claude-code).
   


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