Skip to content

feat: run broadcast joins natively when an unsupported scan is on the build side - #6786

Merged
andygrove merged 4 commits into
apache:mainfrom
parthchandra:spark-to-columnar-auto
Oct 15, 2026
Merged

andygrove merged 4 commits into
apache:mainfrom
parthchandra:spark-to-columnar-auto

Conversation

@parthchandra

Copy link
Copy Markdown
Contributor

Auto-insert CometSparkToColumnarExec at an unsupported FileSourceScan/BatchScan on a broadcast join's build side when the probe side is natively scannable, so the BroadcastExchange and the join run natively over the large probe. Gated by spark.comet.sparkToColumnar.broadcastBuildSide.enabled (default true) together with the Comet broadcast-exchange config. Guard the AQE DPP rule (convertSAB) against wrapping a non-native DPP subquery build in a CometBroadcastExchangeExec.

Which issue does this PR close?

Part of #6008.

Rationale for this change

An unsupported leaf scan (for example a Text file source, [COMET: Unsupported file format Text])
on the build side of a broadcast join cascades: the build branch (scan → transforms →
BroadcastExchange) stays on Spark, so the exchange never becomes a CometBroadcastExchangeExec,
so the BroadcastHashJoin can't go native — even when the large probe side is a fully native
scan+filter. A tiny lookup table read from an unsupported format disqualifies native execution of
the entire join over the big stream.

Comet already has a row→Arrow bridge (CometSparkToColumnarExec, delivered as a CometScanWrapper
that is a CometNativeExec), but it is off by default and its supportedOperatorList excludes file
scans, so this path was never hit. This PR auto-inserts that bridge at an unsupported build-side
scan — but only when doing so actually lets the join run natively — so the bounded, small build side
is adapted to Arrow and the join over the large probe input is accelerated.

What changes are included in this PR?

  • New config spark.comet.sparkToColumnar.broadcastBuildSide.enabled (default true,
    CometConf.scala). Independent of the general spark.comet.sparkToColumnar.enabled opt-in.

  • Auto-bridge on broadcast build sides (CometExecRule.scala): a pre-pass
    (tagBroadcastBuildSideLeaves) tags the build-side FileSourceScanExec/BatchScanExec of a
    BroadcastHashJoinExec/BroadcastNestedLoopJoinExec, and shouldApplySparkToColumnar then
    bridges a tagged scan — only in the unsupported-format arms, so the per-format
    spark.comet.convert.{csv,json,parquet}.enabled opt-outs still take precedence.
    The bridge is applied only when:

    • the feature config and spark.comet.exec.broadcastExchange.enabled are both on
      (otherwise the broadcast can't go native, so the bridge would be pointless), and
    • the probe side is already natively scannable (hasOnlyNativeScans) — i.e. the join can
      actually become a fully native CometBroadcastHashJoinExec. Bridging a build side under a join
      that stays on Spark is wasteful and would change the build broadcast's subtree, breaking Spark's
      dynamic-partition-pruning (DPP) broadcast reuse.
      The tag descent walks the dimension's own filters/projects/aggregations but stops at a nested
      join, so a large streamed input of a nested join inside the build side is not bridged.
  • DPP guard (CometPlanAdaptiveDynamicPruningFilters.convertSAB): when the matched broadcast
    join is Comet, only build a CometBroadcastExchangeExec for the reused DPP subquery if that
    subquery's own build is native (isNativeBuildSide); otherwise fall back to a Spark
    BroadcastExchangeExec. This prevents an AQE+DPP crash
    (Comet execution only takes Arrow Arrays, but got ... OffHeapColumnVector) when the bridged
    dimension's separate DPP build copy is row-based. Mirrors the existing non-AQE guard in
    CometExecRule.rewriteInSubqueryPlan.

How are these changes tested?

  • New unit tests in CometExecSuite (#6008), covering: a Text build side going native (AQE on
    and off); the broadcast-exchange-disabled gate; an unsupported scan below a build-side aggregate
    (and that the shuffle query stage is not bridged); the per-format opt-out still winning; a DSv2
    (BatchScanExec) build side; BuildLeft and BroadcastNestedLoopJoin variants; a non-native
    probe leaving the build side un-bridged; and an AQE+DPP regression test (text dimension + a
    partitioned Parquet fact) that pins the DPP guard. All assert result-equivalence to Spark via
    checkSparkAnswer/checkSparkAnswerAndOperator plus the expected plan shape.
  • CometExecSuite passes on spark-4.1; the project builds on spark-4.0 and spark-4.1.
  • The Spark SQL suite via dev/local-ci.sh spark sql_core-1 is green, including the
    DynamicPartitionPruning V1/V2 suites that guard broadcast/DPP reuse (an earlier eager version of
    this change regressed those; the probe-native gate + DPP guard fix it).

@github-actions github-actions Bot added enhancement New feature or request area:joins Join operators and dynamic filter pushdown labels Oct 8, 2026
@andygrove

Copy link
Copy Markdown
Member

This seems like a great idea. Also, CometSparkToColumnarExec got faster lately. I am kicking off a first pass AI review next.

@andygrove andygrove added run-spark-4.1-tests Run the Spark 4.1 SQL tests on this pull request instead of waiting for the merge queue run-all-spark-profiles Run the Comet test suites against every Spark profile on this pull request, ahead of the merge queue labels Oct 9, 2026

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. Getting the join over the large probe side to run natively when a small lookup table is in a format Comet can't scan is a nice win. The ORC, Iceberg and text probes I ran all matched Spark. I did find three cases where the change makes an existing query slower than it is today, so I'm requesting changes for those. Details are inline. The new tests pass for me on 3.4, 3.5 and 4.2 as well.

Since this changes the planner, I've added run-spark-4.1-tests and run-all-spark-profiles. The local sql_core-1 run skips org.apache.spark.sql.execution.datasources.* and org.apache.spark.sql.connector.*, which run in sql_core-4. Those are the suites that cover ORC, text and V2 scans, and Hive ORC tables are covered in sql_hive. The profiles label runs the new tests on 3.4, where the DPP path is different.

docs/source/user-guide/latest/datasources.md still says these scans only convert with spark.comet.sparkToColumnar.enabled and the operator list. Could you add a paragraph there about the new default and how to turn it off?

The earlier [scans] failure was the listener race in the Iceberg write report test, which #6809 just fixed on main. The re-run passed.

@sunchao sunchao left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

  • Prior state and problem: An unsupported build-side scan prevented native broadcast joins even when the probe used a native scan. This PR enables that execution path automatically.
  • Design approach: Tag eligible build-side scans before conversion, reuse CometSparkToColumnarExec, and guard AQE DPP broadcasts against non-Arrow inputs.
  • Correctness: Reviewed the conversion, schema, input-file metadata, join eligibility and DPP paths against Spark sources. Existing join restrictions remain enforced. No additional introduced P1/P2 correctness issue was found beyond the previously reported blockers.
  • Compatibility analysis: Compared relevant Spark sources for 3.4.3, 3.5.9, 4.0.4, 4.1.3 and 4.2.0. Spark's DPP reuse depends on matching broadcast plans. The new scan tagging conflicts with the existing Spark 3.4 fallback, as already reported. Compared the touched paths with branch-1.1 at e9efd9f764ee0a59b7898ff028d6985d4a7a28e1. Automatic conversion is intentional. Lost pruning and broadcast reuse are unintended regressions.
  • Key design decisions: The feature defaults to enabled independently of general Spark-to-Arrow conversion. Broadcast-exchange conversion must also be enabled. CSV, JSON and Parquet conversion switches retain precedence, and tagging stops at nested joins.
  • Implementation sketch: CometExecRule tags build-side file/V2 scans when the probe contains only recognized native scans. The existing bridge converts their output. CometPlanAdaptiveDynamicPruningFilters checks the DPP build separately before choosing its exchange type. Nine tests cover positive paths, configuration gates, aggregation, V2 scans, build orientation and DPP execution.
  • Performance: Conversion copies scan output before filters and aggregates, so broadcast output size does not bound conversion work. The existing review substantiates lost pruning, retained conversions after join fallback, and duplicate DPP broadcasts. No benchmark establishes a net speedup for those cases, and I make no additional performance claim.
  • Design: Reusing the existing bridge keeps the execution mechanism simple. However, native scan eligibility does not guarantee native join eligibility. The existing request to restore the build subtree when the join declines addresses that mismatch.
  • Abstraction & complexity: The helpers are narrowly scoped and introduce no new execution framework. The new CometNativeExec test conflates class membership with Arrow-compatible output and excludes valid columnar operators such as CometUnionExec. This is covered by an existing blocker.
  • Behavioral changes worth calling out: Eligible unsupported file and V2 scans can now feed native broadcast joins by default. DPP can execute a separate Spark broadcast for an unbridged dimension. The new AQE guard also affects existing native queries when the feature switch is disabled.
  • Suggested improvements: Resolve the existing requests to honor the Spark 3.4 broadcast skip tag, undo conversions when a join declines, and recognize compatible Comet columnar DPP builds. Regression assertions should verify pruning and exchange reuse. No additional improvement meeting the P1/P2 reporting bar was identified.

Reviewed the full four-file PR diff for 8932aad407544a51fbfc38167c3bb46f29a67946 against supplied base e8c7ee739d3530b309d38388ba1c41decdb8abea, using merge base f9dd86d482d136426990186c2f1d42eef6a1ca59. GitHub's changed-file list matches this scope. The PR remains non-draft. Read the review, conversation comment, eight inline comments and eight unresolved threads before forming conclusions.

Routed skills: review-comet-pr and review-comet-ffi-pr. Also checked contributor guidance on FFI ownership, memory accounting, timezones, configuration, development and CI.

Existing blockers remain substantiated by the reported reproductions and source tracing: Spark 3.4 DPP loss, retained conversion and lost reuse after join fallback, and AQE DPP rejecting valid columnar builds. These are not duplicated as new findings.

Exact-head CI: Latest check runs associated with the reviewed SHA show 60 successes and 53 skips, with none failed or running. Comet suites passed across Spark 3.4–4.2, and all eight Spark 4.1 SQL/Hive shards passed. Retrieved execution logs confirm all nine new tests passed on 4.1. On 3.4, eight passed and the explicitly unsupported AQE DPP test was canceled. These jobs tested merge commits containing this head. The default job merged the supplied base, while the later profile run merged newer main.

Validation limits: No local JVM/native tests or benchmarks were run. This checkout has no compiled artifacts or Spark dependency cache. Runtime evidence comes from CI and the existing review's reproductions, corroborated by source inspection. macOS, dedicated Iceberg suites and other versions' full Spark SQL suites were skipped. git diff --check passed.

No additional introduced P1/P2 issues found within this review beyond the existing unresolved concerns.

@parthchandra parthchandra left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review @andygrove. Addressed your comments and also added a paragraph to datasources.md about the new default and how to turn it off

@parthchandra parthchandra added run-iceberg-tests run-spark-3.4-tests Run the Spark 3.4 SQL tests on this PR run-spark-4.1-hive-tests Run the Spark 4.1 sql_hive shards on this pull request labels Oct 9, 2026
@parthchandra
parthchandra force-pushed the spark-to-columnar-auto branch 2 times, most recently from 09fb41f to 3738210 Compare October 9, 2026 23:58

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround on these. I re-ran my probes against 3738210b84. The 3.4 fix works: with AQE the Iceberg fact reads 400 rows instead of 1000, and the broadcast is reused. The union dimension gets its ReusedExchange back under AQE, and turning off spark.comet.exec.broadcastHashJoin.enabled no longer leaves a bridge behind. The ORC and Iceberg probes still match Spark on 3.4 and 4.1.

The revert for joins that stay on Spark doesn't take effect yet, though. A FULL OUTER nested loop join still keeps the bridge under a Spark broadcast, and with DPP the dimension is still read twice. That's the slowdown from my first review, so I'm keeping the request for changes. Details are in the thread.

None of the three fixes has a test yet, so I've asked for one in each thread. CI is green, but none of the suites reaches the revert path.

parthchandra added a commit to parthchandra/datafusion-comet that referenced this pull request Oct 12, 2026
…apache#6008)

Round-2 review fixes for PR apache#6786:

- Replace the never-firing revertBridgedBroadcasts (the bridge is a
  CometScanWrapper leaf, so .exists never saw it, and the rebuild from
  originalPlan mishandled CometSinkPlaceHolder, unions and native scans)
  with clearWastedBuildSideBridges: after the transform, clear
  BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark
  BroadcastExchangeExec and transform once more. A declined broadcast join
  then rebuilds its build side exactly as it would without the feature, and
  the AQE stage pass no longer re-bridges the scan.
- Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide.
- Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a
  Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite.
- Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and
  register it in the PR build workflows.
@parthchandra
parthchandra force-pushed the spark-to-columnar-auto branch from 3738210 to 59463e1 Compare October 12, 2026 20:01
parthchandra added a commit to parthchandra/datafusion-comet that referenced this pull request Oct 12, 2026
…apache#6008)

Round-2 review fixes for PR apache#6786:

- Replace the never-firing revertBridgedBroadcasts (the bridge is a
  CometScanWrapper leaf, so .exists never saw it, and the rebuild from
  originalPlan mishandled CometSinkPlaceHolder, unions and native scans)
  with clearWastedBuildSideBridges: after the transform, clear
  BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark
  BroadcastExchangeExec and transform once more. A declined broadcast join
  then rebuilds its build side exactly as it would without the feature, and
  the AQE stage pass no longer re-bridges the scan.
- Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide.
- Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a
  Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite.
- Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and
  register it in the PR build workflows.
@parthchandra
parthchandra force-pushed the spark-to-columnar-auto branch from 59463e1 to 6ba6a5b Compare October 12, 2026 22:34
…apache#6008)

Round-2 review fixes for PR apache#6786:

- Replace the never-firing revertBridgedBroadcasts (the bridge is a
  CometScanWrapper leaf, so .exists never saw it, and the rebuild from
  originalPlan mishandled CometSinkPlaceHolder, unions and native scans)
  with clearWastedBuildSideBridges: after the transform, clear
  BROADCAST_BUILD_SIDE_TAG from scans still under a plain Spark
  BroadcastExchangeExec and transform once more. A declined broadcast join
  then rebuilds its build side exactly as it would without the feature, and
  the AQE stage pass no longer re-bridges the scan.
- Fix stale docstrings on broadcastBuildSideBridgeEnabled and isNativeBuildSide.
- Add FULL OUTER BNLJ revert tests (filter/aggregate/union build sides), a
  Spark 3.4 DPP test, and a UNION ALL AQE DPP reuse test to CometExecSuite.
- Add CometBroadcastBuildSideSuite (ORC and Iceberg build-side probes) and
  register it in the PR build workflows.
@parthchandra
parthchandra force-pushed the spark-to-columnar-auto branch from 6ba6a5b to 657044b Compare October 13, 2026 22:16

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for reworking this. I re-ran my probes against 657044b7a2, on 4.1 and 3.4. Every join that stays on Spark now gets the same plan as with the feature off, with AQE on and off. That covers the FULL OUTER and build-left LEFT OUTER nested loop joins, aggregate and union build sides, and a probe side with an operator that stays on Spark. DPP over a join that declines scans the dimension once and reuses the broadcast again, and on 3.4 the native Iceberg fact still reads 400 rows. The new tests catch the regressions too. Turning off the clear fails the three no-bridge tests, the old CometNativeExec check fails the union test, and removing the skip check and the clear together fails the Iceberg DPP test on 3.4.

I've dismissed my earlier request for changes, since the bridge it was about is fixed. I left three more comments below that I'd like to see addressed before approving.

@andygrove
andygrove dismissed their stale review October 13, 2026 22:24

Dismissing this since the bridge left under a Spark broadcast is fixed in 657044b7a2. My remaining comments are in the new review.

Round-3 review on PR apache#6786:

- clearScanTags now stops at a CometBroadcastExchangeExec, so a nested
  native broadcast join inside a Spark-staying join's build keeps its
  bridge, and it only counts a clear (and asks for the second transform)
  when the tagged scan sits under a CometSparkToColumnarExec. A tagged but
  unbridged CSV/JSON/Parquet scan (per-format convert off) no longer forces
  a wasted second transform.
- Recomment the 3.4 Parquet DPP test: on 3.4 the V1 fact with a DPP filter
  stays on Spark (CometScanRule.scala:199), so nothing is tagged - it covers
  the V1 fallback, while the Iceberg test covers the SKIP_COMET_BROADCAST_TAG
  path with a native fact.

@andygrove andygrove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick fixes. I re-ran my probes against 35a9aff95f on 3.4 and 4.1, and all three asks check out.

The CSV dimension no longer triggers the second transform. On the six-join query the count went from 94 to zero, and planning with the feature on now takes the same time as with it off, with AQE on and off.

The nested case keeps its bridge now. In the FULL OUTER nested loop join over a broadcast join of a Parquet table with a text table, the inner join runs as a CometBroadcastHashJoinExec, the outer join stays on Spark, and the answer matches Spark with AQE on and off. Every other shape I probed gives the same plan as last round, so the joins that stay on Spark still match the feature-off plan.

The renamed 3.4 test reads right to me now. I left two optional nits inline.

var cleared = false
def clearScanTags(node: SparkPlan, underBridge: Boolean): Unit = node match {
// A nested broadcast join that went native keeps its bridge.
case _: CometBroadcastExchangeExec =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, optional: nothing in the PR pins this case. With it removed, every #6008 test still passes on 3.4, and the two that only run on 3.5+ have no nested join. My nested probe does catch it, because the inner join goes back to Spark. Could we add that shape to the Spark-staying tests? A FULL OUTER nested loop join whose build side is a broadcast join of a Parquet table with a text table would do. It could assert that the inner join is a CometBroadcastHashJoinExec with a CometSparkToColumnarExec under its CometBroadcastExchangeExec, and that the outer join stays a Spark BroadcastNestedLoopJoinExec.


test("Spark 3.4 AQE DPP with a V1 Parquet fact and a text dimension (#6008)") {
// On Spark 3.4, CometScanRule keeps a V1 scan that carries an AQE DPP filter on Spark
// (CometScanRule.scala:199), so this Parquet fact is not native, hasOnlyNativeScans is false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, optional: the CometScanRule.scala:199 reference is my fault, since I quoted the line number. It will point at the wrong line after the next edit to that file. Could the comment name the check instead, for example the "AQE Dynamic Partition Pruning requires Spark 3.5+" fallback in CometScanRule.transformV1Scan?

…6008)

Optional nits from the PR apache#6786 approval:

- Add a Spark-staying test where a FULL OUTER nested loop join's build is a
  broadcast hash join of a Parquet table with a text table, asserting the
  inner join is a native CometBroadcastHashJoinExec that keeps its text
  bridge while the outer join stays a Spark BroadcastNestedLoopJoinExec.
  This pins clearWastedBuildSideBridges stopping at CometBroadcastExchangeExec.
- Replace the brittle CometScanRule.scala:199 line reference in the 3.4 test
  with the name of the check (the Spark 3.5+ AQE DPP fallback in
  CometScanRule.transformV1Scan).
@parthchandra

Copy link
Copy Markdown
Contributor Author

@andygrove, thank you for the approval. Addressed your latest comments as well. Will wait for ci and any further comments.

@andygrove
andygrove added this pull request to the merge queue Oct 15, 2026
Merged via the queue into apache:main with commit 7a42a35 Oct 15, 2026
107 checks passed
andygrove added a commit to andygrove/datafusion-comet that referenced this pull request Oct 20, 2026
…n configs

Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so
bridging a Text build side by default (apache#6786) could return different results from 1.1.0
(apache#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro,
Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back
to on by default, and Text gets its own configs:

- spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build
  side. Without it, the scan records a fallback reason that names the config.
- spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV,
  JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before.

The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark
requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives
the configs that keep min/max over CAST(binary AS STRING) in Spark.
pithecuse527 pushed a commit to pithecuse527/datafusion-comet that referenced this pull request Oct 24, 2026
…n configs (apache#7123)

* fix: default broadcast build side conversion to off and document invalid UTF-8 cases

Converting a Text scan on a broadcast build side to Arrow decodes invalid
UTF-8 to U+FFFD, so 1.2.0 could return different results from 1.1.0. Default
spark.comet.convert.broadcastBuildSide.enabled to false and name the
Text-source and CAST(binary AS STRING) cases in the compatibility guide.

* test: move the apache#6008 build-side tests into CometBroadcastBuildSideSuite

CometBroadcastBuildSideSuite turns the build-side bridge on for every test, so the tests no
longer need the bridgeTest helper, and a test added later cannot miss the setting. The two tests
that failed because they were not wrapped in bridgeTest now run with the bridge on, and the five
that asserted nothing was bridged now exercise the bridge again. The test bodies are unchanged.

* fix: keep Text build sides on Spark by default and add Text conversion configs

Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so
bridging a Text build side by default (apache#6786) could return different results from 1.1.0
(apache#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro,
Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back
to on by default, and Text gets its own configs:

- spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build
  side. Without it, the scan records a fallback reason that names the config.
- spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV,
  JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before.

The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark
requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives
the configs that keep min/max over CAST(binary AS STRING) in Spark.

* test: drop the QueryStageExec import that the test move left unused

* fix: convert a Text build side only with spark.comet.convert.text.enabled

Drop spark.comet.convert.broadcastBuildSide.text.enabled. A Text scan, on a
build side or not, is now converted only by spark.comet.convert.text.enabled,
like CSV, JSON and Parquet, and the build-side fallback reason names that
config. Text goes through isConversionEnabled, so naming FileSourceScan or
BatchScan in spark.comet.sparkToColumnar.supportedOperatorList still converts
it but is deprecated.

The apache#6008 build-side tests read ORC instead of Text, since a global Text switch
would convert their probes and no-bridge assertions too.

* docs: describe Text conversion through its own config only

Update the Text, build-side and compatibility sections for the removal of
spark.comet.convert.broadcastBuildSide.text.enabled, and note in the migration
guide that converting Text through sparkToColumnar is deprecated.

* fix: drop the unused SparkConf import and narrow the min/max workaround

The suite no longer overrides sparkConf, so its SparkConf import failed
Scalafix and the strict-warnings build. Reformat a CometExecRule comment
that Spotless rejected.

The three-config min/max workaround only keeps the decode out of the
query when the cast is the aggregate's own argument. A cast in a
projection below the aggregate runs natively and decodes first, so the
guide now says so and keeps disabling Comet as the general answer.
andygrove added a commit that referenced this pull request Oct 26, 2026
…n configs (#7123) (#7205)

* fix: default broadcast build side conversion to off and document invalid UTF-8 cases

Converting a Text scan on a broadcast build side to Arrow decodes invalid
UTF-8 to U+FFFD, so 1.2.0 could return different results from 1.1.0. Default
spark.comet.convert.broadcastBuildSide.enabled to false and name the
Text-source and CAST(binary AS STRING) cases in the compatibility guide.

* test: move the #6008 build-side tests into CometBroadcastBuildSideSuite

CometBroadcastBuildSideSuite turns the build-side bridge on for every test, so the tests no
longer need the bridgeTest helper, and a test added later cannot miss the setting. The two tests
that failed because they were not wrapped in bridgeTest now run with the bridge on, and the five
that asserted nothing was bridged now exercise the bridge again. The test bodies are unchanged.

* fix: keep Text build sides on Spark by default and add Text conversion configs

Converting a Text scan to Arrow replaces bytes that are not valid UTF-8 with U+FFFD, so
bridging a Text build side by default (#6786) could return different results from 1.1.0
(#7118). The other formats that reach the bridge either define strings as UTF-8 (ORC, Avro,
Iceberg) or are decoded by Spark, so spark.comet.convert.broadcastBuildSide.enabled goes back
to on by default, and Text gets its own configs:

- spark.comet.convert.broadcastBuildSide.text.enabled (default false) bridges a Text build
  side. Without it, the scan records a fallback reason that names the config.
- spark.comet.convert.text.enabled (default false) converts every Text scan, like the CSV,
  JSON and Parquet configs. The sparkToColumnar operator list still converts Text as before.

The compatibility guide now names Text rather than CSV and JSON, no longer says that Spark
requires Parquet strings to be valid UTF-8, adds Comet columnar shuffle as a path, and gives
the configs that keep min/max over CAST(binary AS STRING) in Spark.

* test: drop the QueryStageExec import that the test move left unused

* fix: convert a Text build side only with spark.comet.convert.text.enabled

Drop spark.comet.convert.broadcastBuildSide.text.enabled. A Text scan, on a
build side or not, is now converted only by spark.comet.convert.text.enabled,
like CSV, JSON and Parquet, and the build-side fallback reason names that
config. Text goes through isConversionEnabled, so naming FileSourceScan or
BatchScan in spark.comet.sparkToColumnar.supportedOperatorList still converts
it but is deprecated.

The #6008 build-side tests read ORC instead of Text, since a global Text switch
would convert their probes and no-bridge assertions too.

* docs: describe Text conversion through its own config only

Update the Text, build-side and compatibility sections for the removal of
spark.comet.convert.broadcastBuildSide.text.enabled, and note in the migration
guide that converting Text through sparkToColumnar is deprecated.

* fix: drop the unused SparkConf import and narrow the min/max workaround

The suite no longer overrides sparkConf, so its SparkConf import failed
Scalafix and the strict-warnings build. Reformat a CometExecRule comment
that Spotless rejected.

The three-config min/max workaround only keeps the decode out of the
query when the cast is the aggregate's own argument. A cast in a
projection below the aggregate runs natively and decodes first, so the
guide now says so and keeps disabling Comet as the general answer.

(cherry picked from commit 2b1c6a3)
Abhishek-Mohan added a commit to Abhishek-Mohan/datafusion-comet that referenced this pull request Oct 29, 2026
…dit mode

build() now reports a failed Sphinx run instead of comparing empty
warning lists, builds the working tree in audit mode, and uses a named
virtual environment, as does the H1 check. Audit mode gets a config-key
command, the stop rule no longer treats an empty search as an error, the
generated-content check says what to compare, and the apache#6786 case reads
the guide from main.

Generated-by: OpenCode
comphead pushed a commit to comphead/arrow-datafusion-comet that referenced this pull request Nov 6, 2026
* docs: add a documentation style guide

Add docs/source/contributor-guide/style_guide.md, which writes down the
vocabulary from apache#4419 (pipeline membership, implementation language and
data format), the rule for "native", how to name operators in prose,
and the page conventions that the docs-review skill in apache#4420 cites.

Each rule has a stable ID so that reviews can cite it. The guide follows
the code where apache#4419 and the code differ: "Hybrid" keeps the meaning it
already has in expressions.md, expressions are described separately
from plan nodes, and "Arrow-native" is a claim about data format only.
The Generated Content section names the generated text exactly, by the
markers GenerateDocs fills in, including the config tables built from
QueryPlanSerde and contrib config classes, and the cast tables built
from CometCast. P18 covers attribution of third-party content, which
apache#4420 lists among its checks. The guide's scope covers .doc strings in
contrib config classes, and its vocabulary and accuracy rules also apply
to doc comments, AGENTS.md and agent skills.

Link the page from the contributor guide toctree and from AGENTS.md.

Part of apache#4419.

Generated-by: OpenCode

* docs: add a docs-review skill

Add .ai/skills/review-comet-docs/SKILL.md, a sibling of review-comet-pr
for documentation. It has three modes: PR mode reviews the docs a pull
request changes, branch mode reviews a branch before it becomes a PR,
and audit mode reviews an existing file or directory, including files
not yet committed.

The skill reads the documentation style guide on every run and cites its
rule IDs rather than restating the rules. It finds the Apache remote and
the PR's real base, reads everything from the reviewed revision, checks
whether main has moved since, and checks only the added lines in PR and
branch mode. Mechanical checks come first: vague "native" (including in
Markdown italics), config keys (including keys built from a name at run
time), operator names, links, anchors, images, external links,
structure, redirects for moved pages, and version claims. Then it runs
judgment checks, the most important being whether each claim, and each
condition in it, still matches the code. Besides docs pages, it checks
changed .doc strings, doc comments, AGENTS.md and agent skills.

Findings are must-fix, should-fix, or nit, with nits dropped when the
skill runs as part of a review-comet-pr review. It never edits files or
posts comments. A short table of fixed cases says how to check the skill
after changing it.

In review-comet-pr, route docs changes to the new skill, mention
documentation in its description, and remove "docs only" from the list
of PRs it covers on its own. Fix two things in it that no longer match:
`gh pr checks --failed` no longer exists, and the opt-in key is
spark.comet.expression.<Name>.allowIncompatible. In review-comet-pr and
review-comet-expression-pr, point at the style guide for exactly which
docs text is generated.

Closes apache#4420.

Generated-by: OpenCode

* docs: make review-comet-docs runs reproducible and check links with Sphinx

- Pin the reviewed commit (HEAD_SHA) after checking it against
  headRefOid, and read everything at it, including the style guide.
- List changed files with git diff instead of gh pr view --json files,
  which stops at 100 files.
- Fail on a missing merge base (shallow clones) instead of diffing
  against an empty revision.
- Check drift against the PR's own target branch, not always main.
- Name the state file per run and quote its values, so concurrent
  reviews and paths with spaces work.
- Use git grep -E with perl instead of git grep -P, so Git builds
  without PCRE work, and set core.quotePath=false for non-ASCII names.
- Replace the hand-written link, anchor, toctree, and heading checks
  with a Sphinx build of the base and the head, compared by warning.
  It resolves the generated per-version pages the way docs/build.sh
  does, catches links that a renamed heading breaks on unchanged pages,
  handles reStructuredText, and resolves toctree entries properly.
  H1s are counted with markdown-it, which handles nested code fences.
- Route AGENTS.md and agent skills to the skill from review-comet-pr.

Part of apache#4420.

Generated-by: OpenCode

* docs: retry the merge base after deepening, sanitize the state file name, keep the N5 test case stable

The merge-base step now tries again after deepening and stops with an
error if there is still no base. Branch names with a slash no longer
break the state file path. The N5 case moves to the scratch page, so it
holds before and after apache#7151 rewrites understanding-comet-plans.md.

Generated-by: OpenCode

* docs: make the docs build and heading check fail loudly and run in audit mode

build() now reports a failed Sphinx run instead of comparing empty
warning lists, builds the working tree in audit mode, and uses a named
virtual environment, as does the H1 check. Audit mode gets a config-key
command, the stop rule no longer treats an empty search as an error, the
generated-content check says what to compare, and the apache#6786 case reads
the guide from main.

Generated-by: OpenCode

* docs: fix what seven agents hit when running the docs review skill

Dry runs on Claude, GPT, Gemini and Grok models in zsh on macOS found
these gaps. The venv now picks Python 3.10 or later and cleans up on
failure, since the macOS system Python cannot install the docs
requirements. Each call reloads the state with a given line, and a
failed docs build no longer reads as a reason to stop. The guide falls
back to the target when the head predates it. Audit mode reads the
working tree for the H1 check and code reads, and skips the rename
check. Code files are filtered to doc comments, SVG text is reviewed,
protobuf message names are not operators, and the build output no
longer carries the macOS /private prefix.

Generated-by: OpenCode

* docs: make the native rules testable

N1 now covers every form of the banned phrases, such as ran natively and
Comet-native. N2 allows the native shuffle path without pairing it with
JVM shuffle. N3 replaces and similar with a test, names user-guide/, and
says pages outside the contributor guide allow only the N2 compounds.
Agents running the guide could not apply the old wording consistently.

Generated-by: OpenCode
(cherry picked from commit 80d4af0)

* docs: allow Arrow-native in N2 and close the native rules

T7 lists Arrow-native, which N3's limit to N2 compounds outside the
contributor guide would otherwise forbid. N3 also says any other use of
native is a finding, so a phrase outside the lists has an outcome.

Generated-by: OpenCode
(cherry picked from commit ad78e9b)

* docs: save the file list, read the guide from the working tree as a last resort

The changed files that step 3 selects are now written to a file saved
in the state, so later calls pass them to Git instead of rebuilding the
list by hand. When neither the head nor the target has the guide, as for
a PR made before it existed, the skill reads the working tree's copy and
says so, instead of stopping. Audit mode leaves the revision out of the
code searches, because an empty quoted revision makes Git fail.

Generated-by: OpenCode

* docs: count audit files for the cap, skip test sources, name the guide fallback

Audit mode has no ADDED list, so the 10-file cap now counts the files
step 1 lists. Test sources no longer enter the file list, since their
string literals passed the doc-comment filter. The stop rule names the
step 2 guide lookup as a fallback, and the key notes cover the EXPRNAME
placeholder pages use.

Generated-by: OpenCode

* docs: limit the N2 scan compounds to Rust-implemented scans

A dry run read native cache scan as allowed by N2, but the in-memory
cache scan runs on the JVM. N2 now says the scan and writer compounds
apply only to Rust-implemented ones.

Generated-by: OpenCode
(cherry picked from commit e8b8000)

* docs: point N1 at T1 for not-a-fallback and at N5 for Spark

A dry run rewrote "falls back to its native execution" and "executes
natively (not falling back to Spark)" wrongly, because N1 offered only
T1 or T3. N1 now says not falling back is T1, since codegen dispatch is
not a fallback, and that Spark's own behavior is N5.

Generated-by: OpenCode
(cherry picked from commit 147f3f4)

* docs: save the audit file list as PATHS

Audit mode used an undefined <files> placeholder. It now saves the step
1 list as PATHS, as the other modes do, and says the mechanical checks
cover every file while only the judgment checks are capped.

Generated-by: OpenCode

* docs: apply the style guide to new and changed text

Existing pages move over as they are edited, so a review does not ask a
change to restyle text it leaves alone, unless the change makes that
text wrong (P16). Requested in review.

Generated-by: OpenCode
(cherry picked from commit fdff0b1)

* docs: keep paths with spaces whole, build each revision with its own requirements

The file list was passed as $(cat "$PATHS"), which splits a name such
as "My Page.md" into two pathspecs, so the page was silently skipped.
Commands now pass it through tr and xargs -0 -r. The docs build reused
one environment installed from the checkout's requirements; a new venv
function installs each revision's own docs/requirements.txt into an
environment named after its hash, so a change that updates a Sphinx
extension is built with it. Requested in review.

Generated-by: OpenCode

* docs: list audit files unquoted, note generated expression anchors

Audit mode listed files with Git's default path quoting, so a page such
as résumé.md became an escaped string and was skipped. The docs build
copies only the expression templates, so a link to a heading that
GenerateDocs adds to a per-version page warns although the published
page has it; the skill now says to check the serde's notes instead.
Requested in review.

Generated-by: OpenCode

* docs: print audit search results with unquoted file names

The audit searches now pass core.quotePath=false too, so a finding on a
page such as résumé.md names the file as written.

Generated-by: OpenCode

* docs: match the category page and Spark version for generated anchors

A heading GenerateDocs adds appears only on the category page whose
serde map holds the expression, at the Spark versions that register
it. The manual check now confirms both, so a link to the same heading
on another category page is reported as broken. Requested in review.

Generated-by: OpenCode
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:joins Join operators and dynamic filter pushdown enhancement New feature or request run-all-spark-profiles Run the Comet test suites against every Spark profile on this pull request, ahead of the merge queue run-iceberg-tests run-spark-3.4-tests Run the Spark 3.4 SQL tests on this PR run-spark-4.1-hive-tests Run the Spark 4.1 sql_hive shards on this pull request run-spark-4.1-tests Run the Spark 4.1 SQL tests on this pull request instead of waiting for the merge queue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants