Repository navigation
feat: add support for comet cartesian join #6938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,7 +35,7 @@ import org.apache.spark.sql.catalyst.expressions.{And, Ascending, AttributeRefer | |
| import org.apache.spark.sql.catalyst.optimizer.{BuildLeft, BuildRight, BuildSide} | ||
| import org.apache.spark.sql.catalyst.plans.{Inner, LeftOuter, LeftSemi} | ||
| import org.apache.spark.sql.catalyst.plans.logical.Join | ||
| import org.apache.spark.sql.comet.{CometBroadcastExchangeExec, CometBroadcastHashJoinExec, CometBroadcastNestedLoopJoinExec, CometFilterExec, CometHashJoin, CometHashJoinExec, CometNativeScanExec, CometProjectExec, CometRangeExec, CometSampleExec, CometSortExec, CometSortMergeJoinExec, CometSparkToColumnarExec, CometUnionExec, CometWindowExec} | ||
| import org.apache.spark.sql.comet.{CometBroadcastExchangeExec, CometBroadcastHashJoinExec, CometBroadcastNestedLoopJoinExec, CometCartesianProductExec, CometFilterExec, CometHashJoin, CometHashJoinExec, CometNativeScanExec, CometProjectExec, CometRangeExec, CometSampleExec, CometSortExec, CometSortMergeJoinExec, CometSparkToColumnarExec, CometUnionExec, CometWindowExec} | ||
| import org.apache.spark.sql.execution.{ColumnarToRowTransition, InputAdapter, LocalTableScanExec, SortExec, SparkPlan, SQLExecution, WholeStageCodegenExec} | ||
| import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanExec, AQEShuffleReadExec, QueryStageExec} | ||
| import org.apache.spark.sql.execution.aggregate.SortAggregateExec | ||
|
|
@@ -154,6 +154,60 @@ class CometJoinSuite extends CometTestBase { | |
| } | ||
| } | ||
|
|
||
| test("cross join (CartesianProduct) runs natively") { | ||
| withSQLConf( | ||
| CometConf.COMET_EXEC_CARTESIAN_PRODUCT_ENABLED.key -> "true", | ||
| // Disable broadcast so Spark plans a CartesianProductExec rather than a | ||
| // BroadcastNestedLoopJoinExec for the keyless join. | ||
| SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1", | ||
| SQLConf.ADAPTIVE_AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1") { | ||
| withParquetTable(Seq((0, 10), (1, 11), (2, 12)), "l") { | ||
| withParquetTable(Seq((0, 100), (1, 101)), "r") { | ||
| checkSparkAnswerAndOperator( | ||
| sql( | ||
| "SELECT * FROM (SELECT /*+ COALESCE(1) */ * FROM l) l " + | ||
| "CROSS JOIN (SELECT /*+ COALESCE(1) */ * FROM r) r"), | ||
| Seq(classOf[CometCartesianProductExec])) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("cross join with a join condition falls back to Spark") { | ||
| withSQLConf( | ||
| CometConf.COMET_EXEC_CARTESIAN_PRODUCT_ENABLED.key -> "true", | ||
| SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1", | ||
| SQLConf.ADAPTIVE_AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1") { | ||
| withParquetTable(Seq((0, 10), (1, 11), (2, 12)), "l") { | ||
| withParquetTable(Seq((0, 100), (1, 101)), "r") { | ||
| // A non-equi inner join plans a CartesianProductExec that carries the predicate; Comet | ||
| // has no native post-product filter, so results must still match Spark via fallback. | ||
| checkSparkAnswer(sql("SELECT * FROM l JOIN r ON l._2 > r._2")) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test cannot fail on the condition guard. Both sides are plain parquet scans, which report |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("cross join stays on Spark by default (native path is opt-in)") { | ||
| // Native CrossJoinExec can't spill, so it's opt-in; by default the cross join stays on Spark. | ||
| withSQLConf( | ||
| SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1", | ||
| SQLConf.ADAPTIVE_AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1", | ||
| SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "false") { | ||
| withParquetTable(Seq((0, 10), (1, 11), (2, 12)), "l") { | ||
| withParquetTable(Seq((0, 100), (1, 101)), "r") { | ||
| val df = sql( | ||
| "SELECT * FROM (SELECT /*+ COALESCE(1) */ * FROM l) l " + | ||
| "CROSS JOIN (SELECT /*+ COALESCE(1) */ * FROM r) r") | ||
| checkSparkAnswer(df) | ||
| assert( | ||
| !df.queryExecution.executedPlan.exists(_.isInstanceOf[CometCartesianProductExec]), | ||
| "cross join must stay on Spark when cartesianProduct.enabled is false") | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| test("join - self join") { | ||
| val df1 = testData.select(testData("key")).as("df1") | ||
| val df2 = testData.select(testData("key")).as("df2") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, no plain file scan passes this check, since non-bucketed scans report
UnknownPartitioning(0). Inputs qualify only aftercoalesce(1),repartition(1), a single-slicerangeor aSinglePartitionexchange, which is what every benchmark case forces. Under AQE this rule runs before shuffle partitions are coalesced, so a shuffle that later shrinks to one partition is probably still rejected, though I have not run that. Could the config note and theoperators.mdentry say this, so users know when enabling it helps? Separately, for the large-left, small-right case, DataFusion 55.2CrossJoinExec::build_batchemits one batch per left row, sized by the right batch, so 262144 x 64 produces 262144 batches of 64 rows. That probably explains why that case gains 3.1X against 18.6X for the square one. I read this from the source and did not measure it.