@@ -42,7 +42,7 @@ import org.apache.spark.sql.execution.columnar.InMemoryTableScanExec
4242import org .apache .spark .sql .execution .datasources .SchemaColumnConvertNotSupportedException
4343import org .apache .spark .sql .execution .exchange .{ReusedExchangeExec , ShuffleExchangeLike }
4444import org .apache .spark .sql .execution .joins .{ShuffledHashJoinExec , SortMergeJoinExec }
45- import org .apache .spark .sql .functions .{count , lit , sum }
45+ import org .apache .spark .sql .functions .{array , count , lit , struct , sum }
4646import org .apache .spark .sql .internal .SQLConf
4747import org .apache .spark .sql .types .{ArrayType , DoubleType , IntegerType , LongType , MetadataBuilder , StructField , StructType }
4848
@@ -2526,8 +2526,8 @@ class CometJoinSuite extends CometTestBase {
25262526 // SortedInputExec passes the rows through, so the order is physically Spark's, but the serde
25272527 // and the native sort expression handle the key, and the sorted aggregate above the join finds
25282528 // its group boundaries by comparing it. The timestamps run in a session time zone that is not
2529- // UTC and include values before 1970. Struct , array and map keys are out of scope, since
2530- // exprToProto rejects them by default and such a join stays in Spark .
2529+ // UTC and include values before 1970. Double , array and struct keys have tests of their own,
2530+ // and Spark cannot sort by a map .
25312531 val keyTypes = Seq (
25322532 " decimal(20,2)" -> Seq (
25332533 " -999999999999999999.99" ,
@@ -2682,6 +2682,69 @@ class CometJoinSuite extends CometTestBase {
26822682 }
26832683 }
26842684
2685+ test(" outer hash joins keep a streamed order that starts with an array or a struct" ) {
2686+ // exprToProto takes an array or a struct sort key, so a join whose streamed order starts with
2687+ // one sends it, stays native, and the planner declares it on the JVM input. Spark drops a sort
2688+ // right below a join, so the sorted rows come from a cache.
2689+ withSQLConf(
2690+ SQLConf .ADAPTIVE_EXECUTION_ENABLED .key -> " false" ,
2691+ SQLConf .AUTO_BROADCASTJOIN_THRESHOLD .key -> " -1" ,
2692+ CometConf .COMET_EXEC_IN_MEMORY_CACHE_ENABLED .key -> " false" ,
2693+ CometConf .COMET_CONVERT_FROM_IN_MEMORY_CACHE_ENABLED .key -> " true" ) {
2694+ withParquetTable((0 until 2000 ).map(i => (i % 40 , i)), " big" ) {
2695+ withParquetTable((0 until 40 by 2 ).map(k => (k, k / 2 )), " small" ) {
2696+ val build = spark.table(" small" )
2697+ // An array of strings, the one array type that Comet reads from Spark's cache.
2698+ Seq (
2699+ " array" -> array(($" _1" % 7 ).cast(" string" ), $" _1" .cast(" string" )),
2700+ " struct" -> struct($" _1" % 7 , $" _1" )).foreach { case (keyName, key) =>
2701+ try {
2702+ val streamed = spark
2703+ .table(" big" )
2704+ .select(key.as(" s" ), $" _1" , $" _2" )
2705+ .repartition(2 , $" _1" )
2706+ .sortWithinPartitions(" s" )
2707+ .cache()
2708+ streamed.count()
2709+ val order = SortOrder (
2710+ BoundReference (0 , streamed.schema(" s" ).dataType, nullable = true ),
2711+ Ascending )
2712+ Seq (" left_outer" , " right_outer" ).foreach { joinType =>
2713+ def joined : DataFrame = {
2714+ val hinted = build.hint(" broadcast" )
2715+ if (joinType == " left_outer" ) {
2716+ streamed.join(hinted, streamed(" _1" ) === build(" _1" ), joinType)
2717+ } else {
2718+ hinted.join(streamed, build(" _1" ) === streamed(" _1" ), joinType)
2719+ }
2720+ }
2721+ def df : DataFrame = joined
2722+ .select(streamed(" s" ), streamed(" _2" ).as(" v" ), build(" _2" ).as(" w" ))
2723+ .sortWithinPartitions(" s" )
2724+ val plan = df.queryExecution.executedPlan
2725+ val context = s " $keyName $joinType\n $plan"
2726+ val joins = collect(plan) { case j : CometBroadcastHashJoinExec => j.nativeOp }
2727+ assert(joins.size == 1 , context)
2728+ assert(joins.head.getHashJoin.getStreamedSortOrdersCount > 0 , context)
2729+ assert(
2730+ collect(plan) { case s @ (_ : CometSortExec | _ : SortExec ) => s }.isEmpty,
2731+ context)
2732+ sameRowsAsSpark(df)
2733+ assert(partitionsInOrder(df, order), context)
2734+ sameRowsAsSpark(
2735+ joined
2736+ .groupBy(streamed(" s" ))
2737+ .agg(count(lit(1 )), sum(streamed(" _2" )), count(build(" _2" ))))
2738+ }
2739+ } finally {
2740+ spark.catalog.clearCache()
2741+ }
2742+ }
2743+ }
2744+ }
2745+ }
2746+ }
2747+
26852748 // The setup several cache tests share: `big(_1, _2)` with 2000 rows over 40 sparse keys and
26862749 // `small` with a row for every even key, and `big` as `cached(k, v)`, partitioned on the key,
26872750 // sorted within each partition and cached, with 8-row batches and the native cache on. `views`
0 commit comments