Repository navigation
fix: keep Text build sides on Spark by default and add Text conversion configs - #7123
Conversation
…lid 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.
sunchao
left a comment
There was a problem hiding this comment.
Reviewed the entire five-file diff from 3355602e268819750884aa0ce0dd47002c6ea580 to 18707d2dcc6bed4d5470e156f1fc5d565852c5a3. The PR is not a draft. Followed AGENTS.md and review-comet-pr. No sibling skill applies to these configuration, documentation and test changes. The snapshot and live discussion contained no existing review concerns.
Summary
- Prior state and problem: Automatic broadcast build-side conversion was enabled by default. Converting Text containing invalid UTF-8 could replace original bytes and change query results.
- Design approach: Default the existing bridge configuration to
false, explain the compatibility boundary, and explicitly enable conversion in bridge tests. - Correctness: The planner checks the configuration before tagging and converting scans, including scans retaining old tags. One P2 issue is reproduced: two existing tests still require bridging without enabling it.
- Compatibility analysis: Checked Spark sources for 3.4.3, 3.5.9, 4.0.4, 4.1.3 and 4.2.0. Text readers preserve bytes, binary-to-string casts retain them, and binary string ordering explains the documented examples. Compared with
branch-1.1, this restores the earlier default Text build-side fallback. The stringmin/maxdivergence already exists at the supplied base. - Key design decisions: Preserve explicit opt-in and document the unchanged binary-cast aggregation behavior. Other conversion settings retain their existing behavior.
- Implementation sketch: Change one configuration default and its description, update two guide pages, enable the bridge in
CometBroadcastBuildSideSuite, and introduce scopedbridgeTestregistrations inCometExecSuite. - Performance: No new per-row work or allocation is introduced. Default fallback avoids conversion but forgoes native-join acceleration for affected sources. No additional performance defect was established.
- Design: Reusing the existing gate makes the conservative default straightforward to understand. The documentation explains the correctness tradeoff.
- Abstraction & complexity: The small by-name
bridgeTesthelper appropriately scopes configuration to test execution. Its adoption is incomplete, causing the reported failures. - Behavioral changes worth calling out: Automatic conversion becomes opt-in for unsupported file and V2 build scans, including Text, ORC and non-native Iceberg scans. Explicitly enabled conversion continues to decode invalid UTF-8.
- Suggested improvements: Apply
bridgeTestto the two remaining tests that require native bridging, while retaining the default-off assertion.
Exact-head CI: run 39358471685 had 19 successful checks, six running checks and 17 skipped checks at final inspection. No completed check reported failure. The four Comet test shards and TPC-H/TPC-DS checks were still running. Spark SQL, Iceberg and macOS checks were skipped.
Validation: Reactor test-compile passed. On Spark 4.1.3/JDK 21, the two focused tests failed with the new default and both passed with the bridge explicitly enabled. Tests used exact-head JVM sources and the base CI native artifact, whose native/common source trees are unchanged. Bounded Spark UTF8String and Comet decoder runs confirmed the documented byte, length and ordering examples. The full Comet matrix and upstream Spark SQL/Iceberg suites were not run locally.
| "guide.") | ||
| .booleanConf | ||
| .createWithDefault(true) | ||
| .createWithDefault(false) |
There was a problem hiding this comment.
[P2] Enable the remaining bridge-dependent tests explicitly. The Text nested-loop join test and nested native-join test in CometExecSuite (registrations at lines 616 and 806) still use plain test. With this new default, their Text scans correctly remain on Spark, but their assertions require a bridge and native join. Both fail on Spark 4.1.3/JDK 21 at lines 633 and 835. Explicitly enabling the configuration makes both pass. This breaks the CI exec suite. Please register these tests through bridgeTest while keeping the production default off.
Evidence: Reproduced with ./mvnw test -Dtest=none '-Dsuites=org.apache.comet.exec.CometExecSuite @broadcast nested loop join build side with a text source goes native (#6008),org.apache.comet.exec.CometExecSuite @a native broadcast join nested in a Spark-staying join keeps its bridge (#6008)' using the local build settings described above. Default configuration: 0 passed, 2 failed, with expected the text build side to be bridged and the inner join should be a CometBroadcastHashJoinExec. Repeating the same selection with JAVA_TOOL_OPTIONS=-Dspark.comet.convert.broadcastBuildSide.enabled=true produced 2 passed, 0 failed.
viirya
left a comment
There was a problem hiding this comment.
Thanks for picking this up before the 1.2.0 branch cut. Defaulting the build-side bridge to off is the simplest way to get the 1.1.0 results back for Text build sides. The gate is checked both when tagging and when bridging, so a leftover tag can't bring the conversion back. The compatibility guide also needed the more honest wording about where invalid UTF-8 comes from.
On the tests, sunchao already pointed out the two CometExecSuite tests that still need the bridge, and the [exec] shard fails on exactly those two. Beyond that, five more #6008 tests keep passing but stop testing anything now that the default is off. Details are inline on bridgeTest.
The new compatibility section needs a few corrections, also inline. The Parquet sentence contradicts the #4121 paragraph right below it. CSV and JSON don't carry invalid UTF-8 into Comet, because Spark has already decoded them. The list leaves out Comet columnar shuffle, which is the default way a Text table's bytes reach the decode. And there is a per-expression config that looks like a narrower workaround for the min/max case than turning Comet off.
| * A test that runs with the broadcast build side bridge on. The bridge is off by default | ||
| * because it decodes invalid UTF-8 (see #7118). | ||
| */ | ||
| private def bridgeTest(name: String)(body: => Unit): Unit = |
There was a problem hiding this comment.
Building on sunchao's comment: besides the two tests that now fail, five more #6008 tests keep passing but no longer test anything.
broadcast join with an unsupported probe stays on Sparkbroadcast join that stays on Spark leaves no build-side bridgeSpark-staying broadcast join with an aggregate build side leaves no bridgeSpark-staying broadcast join with a union build side leaves no bridgeSpark 3.4 AQE DPP with a V1 Parquet fact and a text dimension
Each one asserts that nothing is bridged or tagged. With the default off, tagBroadcastBuildSideLeaves returns early at CometExecRule.scala:1577, so those assertions hold before hasOnlyNativeScans or clearWastedBuildSideBridges ever runs. The filter/aggregate/union revert tests came out of the #6786 review rounds, so it would be a shame to lose them without anyone noticing. Could these use bridgeTest too?
Another option is moving the #6008 tests into CometBroadcastBuildSideSuite, which already turns the bridge on for the whole suite. Then this helper can go away, and a test added later can't pick up the wrong default by accident.
| Parquet or produced by string expressions. Consistent handling of invalid UTF-8 across all native | ||
| string paths is tracked by | ||
| Both differences require string data that is not valid UTF-8. Such data does not occur for text read | ||
| from Parquet, which Spark requires to be valid UTF-8, or produced by most string expressions. It |
There was a problem hiding this comment.
"which Spark requires to be valid UTF-8" conflicts with the last paragraph of this section. That paragraph says the native Parquet scan rejects such columns "rather than reading them like Spark" (#4121). Spark's Parquet reader wraps the stored bytes with UTF8String.fromBytes and doesn't validate them. Can we drop that clause, or say that Comet's native Parquet scan never produces such strings?
| from Parquet, which Spark requires to be valid UTF-8, or produced by most string expressions. It | ||
| can reach Comet in these ways, which are the ones to check before relying on a result: | ||
|
|
||
| - **Text and other row-based sources.** A Text, CSV or JSON file in a non-UTF-8 encoding such as |
There was a problem hiding this comment.
I don't think CSV and JSON belong here. Spark's CSV path decodes each line with new String(bytes, charset) and builds the value with UTF8String.fromString. JSON goes through an InputStreamReader, or through Jackson's byte parser, which rejects invalid UTF-8. So a Latin-1 CSV or JSON file has already been decoded to U+FFFD by Spark before the rows reach Comet, and Comet's decode doesn't change the result. Text keeps the raw bytes, which is why it's the case that matters. Could this bullet name Text, and other sources that wrap bytes without validating them, instead?
Separately, the list reads as the complete set of paths to check, but a Text scan's rows can also reach the decode through Comet columnar shuffle with the default settings. In auto mode, columnarShuffleFailureReasons accepts a row-based Spark child, and the row-to-Arrow writer decodes strings with decode_utf8_spark_lossy in get_string. That code has been there since 1.1.0 (#4524). So a GROUP BY or sort-merge join over a Latin-1 Text table would see U+FFFD on both releases. The opening paragraph of this section already mentions columnar shuffle. Could the list include it, so readers don't conclude that turning off the build-side switch is enough to keep Text bytes intact?
| over strings run natively, a cast below them is decoded before the comparison, and the two can swap. | ||
| With `b` holding `X'80'` and `X'C3A9'`, Spark returns `min(CAST(b AS STRING))` = the `0x80` string | ||
| and `max` = `é`, whereas Comet returns `min` = `é` and `max` = `U+FFFD`. This is the ordering | ||
| difference above and is not controlled by a config; disable Comet for the query if the cast input can hold |
There was a problem hiding this comment.
"is not controlled by a config; disable Comet for the query" seems broader than needed. The aggregate serde checks spark.comet.expression.<Name>.enabled (QueryPlanSerde.scala:830). Would setting spark.comet.expression.Min.enabled=false and spark.comet.expression.Max.enabled=false keep the aggregate, and the cast inside its argument, in Spark for this case? If so, that's a much narrower workaround to document.
…ideSuite 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.
…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.
|
@sunchao @viirya thanks for the reviews. I've reworked this PR based on them, so it needs a fresh look. Instead of turning the build-side bridge off for every format, the bridge stays on by default and Text is the exception. Of Spark's built-in sources that reach the bridge, Text is the only one whose strings carry no UTF-8 guarantee. ORC, Avro and Iceberg define strings as UTF-8, and Parquet, CSV and JSON never reach the bridge because they have their own Tests:
Compatibility guide: I took all four of viirya's points and checked each one with a probe against Spark 4.1.
I've updated the PR description and added |
sunchao
left a comment
There was a problem hiding this comment.
Reviewed commit e8fe7583bc8d70b3a2646752ed38ed85839a0273. New P1/P2 findings are detailed inline.
| // A query stage (the build-side shuffle) must never be wrapped in the bridge. | ||
| collect(cometPlan) { case s: CometSparkToColumnarExec => s.child }.foreach { child => | ||
| assert( | ||
| !child.isInstanceOf[QueryStageExec], |
There was a problem hiding this comment.
[P2] Remove the stale QueryStageExec import from CometExecSuite.scala:42 when moving this assertion. This relocation removes its final use in that suite, but leaves the import behind. Running the standard Spark 3.5 strict-warning build now fails during test compilation with Unused import, and all six Java-lint jobs fail Scalafix on the same import. Removing it from the original suite restores the build checks while retaining this assertion.
Evidence: Exact-head CI reproduced the failure with ./mvnw -B test-compile -Pspark-3.5,delta -Pstrict-warnings -DskipTests: CometExecSuite.scala:42: Unused import, followed by one error found and BUILD FAILURE. Strict compilation log: https://github.com/apache/datafusion-comet/actions/runs/39414444629/job/118601923006. The Spark 4.1 lint log explicitly proposes removing only QueryStageExec from that import: https://github.com/apache/datafusion-comet/actions/runs/39414444629/job/118601923427. All six lint-job logs show the same fix.
There was a problem hiding this comment.
Good catch, thanks. The move took its last real use, and the two remaining mentions are in a test name and a comment. Fixed in 67dc4c1 by dropping QueryStageExec from the import. Locally, the strict-warnings test-compile on Spark 3.5 (-Pspark-3.5,delta -Pstrict-warnings) and the Scalafix check on 4.1 now pass.
|
@parthchandra pinging you as well, since this changes the default for the case in #6008. The build-side bridge from #6786 stays on by default, but a Text build side is now bridged only when Does this work for your use case? I'd appreciate a review. |
viirya
left a comment
There was a problem hiding this comment.
Thanks for the rework. Keeping the bridge on and carving out Text is a good fit for the problem in #7118. The gate goes through isBroadcastBuildSideLeaf, so a stale tag can't bring the conversion back, and the fallback reason makes the new opt-in easy to discover from the explain output. Moving the #6008 tests into CometBroadcastBuildSideSuite also fixes the two failing tests and the five that had stopped testing anything, without needing a helper. The compatibility guide changes cover everything raised in the last round, and the probe results behind each statement are appreciated.
I have three things inline.
- The PR adds two Text configs where one seems enough. If Text followed the per-format model that CSV, JSON and Parquet already use, the planner branch, the config docs and the guide would all get simpler.
- If
spark.comet.convert.text.enabledstays, two existing docs still describeFileSourceScaninsparkToColumnaras the way to convert formats other than Parquet, JSON and CSV. Text also keeps that path without the deprecation warning that the other leaves with their ownspark.comet.convertconfig log. - The
min/maxworkaround in the guide seems to hold only when the cast is the aggregate's own argument.
| .booleanConf | ||
| .createWithDefault(true) | ||
|
|
||
| val COMET_CONVERT_BROADCAST_BUILD_SIDE_TEXT_ENABLED: ConfigEntry[Boolean] = |
There was a problem hiding this comment.
Do we need both new Text configs? spark.comet.convert.text.enabled already covers the build side, so this one only adds the option of converting a Text build side while every other Text scan stays on Spark.
If Text followed the CSV, JSON and Parquet model, where a build side is converted only by the format's own switch, the datasources paragraph could say "Parquet, JSON, CSV and Text build sides are converted only by their own switches". The fallback reason could point at spark.comet.convert.text.enabled, and shouldConvertTextScan would lose its middle branch. Most of the Latin-1 explanation that is now repeated across the two config docs and the scaladocs on BROADCAST_BUILD_SIDE_TAG, tagBroadcastBuildSideLeaves and shouldConvertTextScan would go away too. And we would not start a pattern of one broadcastBuildSide.<format>.enabled for each format we later need to exclude.
The #6008 allowlist case would set spark.comet.convert.text.enabled=true instead. Is there a workload where converting the build side but not other Text scans matters? @parthchandra's answer on the #6008 use case may settle this.
There was a problem hiding this comment.
Yes, this makes sense. I think the extra config is confusing.
There was a problem hiding this comment.
No, I don't see a workload that needs it. Converting the build side while every other Text scan stays on Spark is only useful if the build side is the one place a Latin-1 file is safe, and nothing makes that so. I dropped spark.comet.convert.broadcastBuildSide.text.enabled. A Text scan, including one on a build side, is now converted only by spark.comet.convert.text.enabled, like CSV, JSON and Parquet. The fallback reason now names that config. The datasources paragraph says "Parquet, JSON, CSV and Text build sides are converted only by their own switches". The Latin-1 explanation now lives in the spark.comet.convert.text.enabled doc and the compatibility guide. The #6008 allowlist case sets spark.comet.convert.text.enabled=true. @parthchandra, please say if that doesn't work for you.
This also changed the #6008 tests. With a global Text switch they can't use Text any more, because a Text probe would be converted too and the "leaves no bridge" assertions would stop meaning anything. They now use ORC, which is still an unsupported format that gets bridged. The #7118 test keeps the Latin-1 Text file and covers the default and spark.comet.convert.text.enabled, for V1 and V2 with AQE on and off.
There was a problem hiding this comment.
@andygrove sorry for the tardy response. Yes, this change would be fine.
| .booleanConf | ||
| .createWithDefault(false) | ||
|
|
||
| val COMET_CONVERT_FROM_TEXT_ENABLED: ConfigEntry[Boolean] = |
There was a problem hiding this comment.
If this config stays, two existing docs still describe FileSourceScan in spark.comet.sparkToColumnar.supportedOperatorList as the way to convert "file formats other than Parquet, JSON and CSV": the supportedOperatorList doc further down in this file, and the "To convert any other leaf operator" paragraph in datasources.md. Could they mention Text?
Related to that, the other leaves that gained their own spark.comet.convert config (Range, InMemoryTableScan, RDDScan, OneRowRelation) keep the sparkToColumnar path through isConversionEnabled, which logs a deprecation warning. shouldConvertTextScan keeps the path for Text without one. Is Text meant to stay supported there long term, or should it be deprecated the same way? The docs should say which.
There was a problem hiding this comment.
Good catch on both. Text now goes through isConversionEnabled, like Range, InMemoryTableScan, RDDScan and OneRowRelation. Naming FileSourceScan or BatchScan for a Text scan still converts it, but logs the same deprecation warning. The supportedOperatorList doc and the "To convert any other leaf operator" paragraph in datasources.md now say "other than Parquet, JSON, CSV and Text". Both also say that naming those operators to convert Text is deprecated. Naming FileSourceScan for ORC and other formats without a config of their own is unchanged.
| With `b` holding `X'80'` and `X'C3A9'`, Spark returns `min(CAST(b AS STRING))` = the `0x80` string | ||
| and `max` = `é`, whereas Comet returns `min` = `é` and `max` = `U+FFFD`. This is the ordering | ||
| difference above. If the cast input can hold bytes that are not valid UTF-8, disable Comet for the | ||
| query, or set `spark.comet.expression.Min.enabled=false`, `spark.comet.expression.Max.enabled=false` |
There was a problem hiding this comment.
The three-config workaround seems to depend on the cast being the aggregate's own argument. If the cast is in a projection below the aggregate, for example SELECT min(s) FROM (SELECT CAST(b AS STRING) AS s FROM t) over a native Parquet scan, Spark keeps that Project, because CollapseProject does not fold a Project into an Aggregate above it. Comet then runs the projection natively and decodes b before the Spark aggregate sees it. Did the probe cover that shape? If the workaround does not hold there, could the sentence say the three configs apply when the cast is directly inside min or max, and keep disabling Comet as the general answer?
There was a problem hiding this comment.
Right, the probe only covered the cast as the direct argument of min and max. With the cast in a projection below the aggregate, as in your example, Comet runs that projection natively and decodes b before the Spark aggregate sees it, so the three configs do not help. In 0e66bc0 the guide keeps disabling Comet as the general answer, gives the three configs only for the case where the cast is the direct argument of min or max, and names the projection shape as one they do not cover.
…bled 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.
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.
sunchao
left a comment
There was a problem hiding this comment.
Reviewed commit 510496429ab8f2634e4d2b91641cafccbbb5a56d. New P1/P2 findings are detailed inline.
| import java.nio.charset.StandardCharsets.UTF_8 | ||
| import java.nio.file.Files | ||
|
|
||
| import org.apache.spark.SparkConf |
There was a problem hiding this comment.
[P2] [P2] Remove the unused SparkConf import. This revision removes the suite’s sparkConf override, leaving this import without a use. All six exact-head Java-lint jobs now fail Scalafix, and compiling the suite with unused-import warnings promoted to errors also fails. The build should remain compilable after the override is removed. Please drop this import so the required checks can pass.
Evidence: Exact-head Spark 4.2 CI ran ./mvnw -B package -DskipTests scalafix:scalafix -Dscalafix.mode=CHECK -Psemanticdb -Pspark-4.2 and proposed removing precisely import org.apache.spark.SparkConf before failing: https://github.com/apache/datafusion-comet/actions/runs/39919227954/job/120301609004. The other five Java-lint logs show the same removal. Local Scala 2.13.17 compilation of the exact suite with -Ywarn-unused:imports -Xfatal-warnings exited 1 with CometBroadcastBuildSideSuite.scala:26: warning: Unused import. A disposable copy removing only that line compiled successfully.
There was a problem hiding this comment.
Fixed in 0e66bc0 by dropping the import. The same CI run also failed Spotless on a comment in CometExecRule.shouldConvertTextScan, which that commit reformats. A local compile test-compile scalafix:scalafix -Psemanticdb and spotless:apply now leave the tree unchanged.
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.
comphead
left a comment
There was a problem hiding this comment.
The test move to CometBroadcastBuildSideSuite is faithful, since the bodies match the removed ones apart from swapping Text for ORC. A default-off spark.comet.convert.text.enabled named like the CSV, JSON and Parquet ones fits config_conventions.md. The title still says "add Text conversion configs" but after the rework there is one config, so the squash commit would describe one that does not exist. One note outside the diff. the deprecated sparkToColumnar settings warn once for each config they stand in for in CometExecRuleSuite covers the other conversion configs but not spark.comet.convert.text.enabled, and read Text file only checks that the deprecated path still converts, not that it warns. The migration guide promises that warning for Text, so a Text case there would pin it. The warning text uses Utils.getSimpleName(op.getClass), so for a Text scan it names FileSourceScanExec, the same name as a scan the docs say is not deprecated for ORC, and the case could also check that the message names the Text config.
viirya
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround. All three points from my last review are addressed. Text now follows the same model as CSV, JSON and Parquet, so the planner branch is a single check plus the fallback reason, and the config docs and the guide got simpler with it. Routing Text through isConversionEnabled gives it the same deprecation path as the other leaves, and the docs now say so in all three places. The min/max paragraph is scoped correctly now. Switching the #6008 tests to ORC makes sense too, since a global Text switch would also convert a Text probe and empty out the "leaves no bridge" assertions. The #7118 test also checks the decoded values with spark.comet.convert.text.enabled now, which ties the test to the numbers the guide quotes.
+1 to @comphead on the title. With one config left, it should be updated before merge so the squash commit matches the change. His suggestion to cover the Text deprecation warning in CometExecRuleSuite is worth adding as well. Approving with those in mind.
parthchandra
left a comment
There was a problem hiding this comment.
Thanks for this fix @andygrove !
Which issue does this PR close?
Closes #7118.
Rationale for this change
#6786 made Comet convert a Text scan on a broadcast join's build side to Arrow by default, where 1.1.0 left it on Spark. Spark's Text reader keeps the bytes of each line as they are, and Arrow strings must be valid UTF-8, so Comet replaces each ill-formed sequence with
U+FFFD. For a Latin-1 file,hex,length, hashing, equality and ordering can then disagree with Spark, so 1.2.0 could return different results from 1.1.0 for the same query.The other formats that reach the bridge don't have this problem: ORC, Avro and Iceberg define strings as UTF-8, and Parquet, CSV and JSON have their own conversion configs that the bridge never overrides. So instead of turning the bridge off for every format, this keeps it on by default and makes Text follow the same model as CSV, JSON and Parquet: a Text scan, on a build side or anywhere else, is converted only by its own config. The compatibility guide also said that invalid UTF-8 "does not occur for text read from Parquet", and it left out some of the paths through which such strings reach Comet.
What changes are included in this PR?
New
spark.comet.convert.text.enabled(defaultfalse) converts every Text scan, V1 and V2, likespark.comet.convert.csv.enabled,jsonandparquet.spark.comet.convert.broadcastBuildSide.enabledstays on by default, as onmain, but a Text build side is bridged only whenspark.comet.convert.text.enabledis set. When the bridge skips a Text build side, the scan records a fallback reason that names that config.Text goes through
isConversionEnabled, like Range, InMemoryTableScan, RDDScan and OneRowRelation. NamingFileSourceScanorBatchScaninspark.comet.sparkToColumnar.supportedOperatorListstill converts Text, as in 1.1.0, but is deprecated and logs the same warning. The config docs,datasources.mdand the migration guide say so.compatibility/index.md: the "Strings with non-UTF-8 bytes" section lists the ways such strings reach Comet:CAST(binary AS STRING). Formin/maxover such a cast, the guide gives the configs that keep the result in Spark.The Parquet sentence now says that Comet's native Parquet scan never produces such strings.
datasources.md: a Text section for the new config, and the build-side paragraph says Parquet, JSON, CSV and Text build sides are converted only by their own switches.The Text-format source on a broadcast build side blocks native
BroadcastHashJoin#6008 build-side tests move fromCometExecSuiteintoCometBroadcastBuildSideSuite, in their own commit with unchanged bodies. A later commit switches them from Text to ORC, because with a per-format Text switch a Text probe would be converted too and the "leaves no bridge" assertions would stop testing anything. ORC is still an unsupported format that the bridge converts.How are these changes tested?
CometBroadcastBuildSideSuitetest for Text broadcast build sides and string min/max apply the invalid UTF-8 decode where 1.1.0 stayed on Spark #7118 reads a Latin-1 Text build side through V1 and V2, with AQE on and off:hexandlengthmatch Spark, and the fallback reason is reported.spark.comet.convert.text.enabled, the build side is converted and returns the decoded values that the compatibility guide quotes.BroadcastHashJoin#6008 tests inCometBroadcastBuildSideSuiterun against an ORC build side.CometExecSuitetest, "read Text file", checks that a Text scan is not converted by default and thatspark.comet.convert.text.enabledand thesparkToColumnaroperator list convert it, for V1 and V2.EFBFBDwhere Spark keepsE9.spark.comet.expression.Min.enabled=falseandspark.comet.expression.Max.enabled=false,min/maxoverCAST(binary AS STRING)still swaps across two input partitions, because the columnar shuffle decodes the partial results. Addingspark.comet.shuffle.mode=nativegives Spark's answer.636166EFBFBDfor a Latin-1caf\xE9. Its JSON reader returns null for the field and keeps the raw bytes only in_corrupt_record.npx prettier --checkpasses on the docs files.