bgiesbrecht opened a new issue, #59434:
URL: https://github.com/apache/spark/issues/59434

   **Version:** Apache Spark 4.1.3 (PySpark 4.1.3, `local[1]`, Java 17, Linux 
x86_64 and macOS). Other versions not tested.
   
   **Component:** SQL (adaptive query execution). A correctness fault: the row 
count of a LIMIT is not honoured.
   
   ## Description
   
   A query with one `LIMIT ... OFFSET` over another, over a LEFT JOIN, now and 
then returns more rows than
   the outer LIMIT allows. It happens only with 
`spark.sql.adaptive.enabled=true` (the default). With
   adaptive execution off, every run returns the right number of rows.
   
   ```sql
   SELECT x FROM (
     SELECT x FROM (
       SELECT p.id AS x
       FROM p LEFT JOIN (
         SELECT p.id AS anchor FROM p INNER JOIN k ON p.id = k.a INNER JOIN p 
AS q ON k.b = q.id
       ) o ON p.id = o.anchor
       LIMIT 6 OFFSET 2
     ) LIMIT 3 OFFSET 1
   )
   ```
   
   `p` has 7 rows and `k` has 8; the join yields more than 8 rows, so the inner 
slice yields 6 rows and the
   outer one must yield exactly 3. There is no ORDER BY, so WHICH rows come 
back may vary; the COUNT may not.
   
   ## Observed
   
   The script below runs the query 40 times per setting and tallies the row 
counts.
   
   | machine | adaptive on | adaptive off |
   |---|---|---|
   | Linux x86_64, Java 17 | 3 rows: 37 runs, **5 rows: 3 runs** | 3 rows: 40 
runs |
   | macOS, Java 17 | 3 rows: 37 runs, **5 rows: 3 runs** | 3 rows: 40 runs |
   | a third run of an earlier form of the script | 3 rows: 36, **5 rows: 4** | 
3 rows: 40 |
   
   Expected: 3 rows in every run.
   
   ## Plans
   
   Both settings plan the same limits: a `GlobalLimit` with arguments `8, 2` 
(limit 8, offset 2) under a
   `CollectLimit` with arguments `4, 1`, and a `LocalLimit 8` pushed to the 
left side of the outer join.
   With adaptive execution on, the `GlobalLimit` reads a `ShuffleQueryStage` 
and the `CollectLimit` sits in
   a `ResultQueryStage`:
   
   ```
   AdaptiveSparkPlan
   +- == Final Plan ==
      ResultQueryStage
      +- CollectLimit                 Arguments: 4, 1
         +- * GlobalLimit             Arguments: 8, 2
            +- ShuffleQueryStage
               +- Exchange
                  +- * LocalLimit     Arguments: 8
                     +- * Project
                        +- * SortMergeJoin LeftOuter
                           :- * Sort
                           :  +- AQEShuffleRead
                           :     +- ShuffleQueryStage
                           :        +- Exchange
                           :           +- * LocalLimit   Arguments: 8
                           ...
   ```
   
   With it off:
   
   ```
   CollectLimit                       Arguments: 4, 1
   +- * GlobalLimit                   Arguments: 8, 2
      +- Exchange
         +- * LocalLimit              Arguments: 8
            +- * Project
               +- * SortMergeJoin LeftOuter
                  ...
   ```
   
   Five rows is what `CollectLimit 4, 1` would give if the `GlobalLimit 8, 2` 
under it had not been applied
   (8 rows, less the 2 and then the 1 skipped, is 5; with it applied, 6 rows, 
limit 4 offset 1, is 3). We
   have not traced it further than that. The full formatted plans for both 
settings are at the end of this issue.
   
   A related form, from a real query: an inner `LIMIT 6 OFFSET 1` below a LEFT 
JOIN and an aggregate, with an
   `ORDER BY ... LIMIT 2` above, returned 5 or 6 rows for the LIMIT 2 in 12 of 
20 runs with adaptive execution
   on and 0 of 20 with it off; there the non-adaptive physical plan keeps 
`TakeOrderedAndProject(limit=2)`.
   
   ## To reproduce
   
   PySpark only; the two tables are inline; about a minute.
   
   ```python
   """Apache Spark 4.1.3: with adaptive query execution on, a LIMIT ... OFFSET 
over another LIMIT ... OFFSET
   over a LEFT JOIN sometimes returns more rows than its LIMIT. Self-contained: 
pyspark only, tables inline.
   
       python spark_aqe_limit_offset.py        (Java 17; PYSPARK_PYTHON set to 
this Python)
   
   Expected: every run returns 3 rows (LIMIT 3). Seen: with 
spark.sql.adaptive.enabled=true some runs return 5;
   with it false every run returns 3. Each mode is run 40 times and the row 
counts are tallied; then the
   formatted physical plan of each mode is printed.
   """
   import collections
   
   import pyspark
   from pyspark.sql import SparkSession
   
   P = [(1, "a", 35, "x"), (2, "b", 15, "y"), (3, "c", 45, "x"), (4, "d", 65, 
"y"),
        (5, "e", 5, "x"), (6, "f", 55, "y"), (7, "g", 25, "z")]
   K = [(1, 2), (1, 3), (2, 3), (3, 4), (4, 1), (5, 1), (6, 7), (6, 4)]
   
   spark = (SparkSession.builder.master("local[1]").appName("aqe-limit-offset")
            .config("spark.ui.enabled", 
"false").config("spark.sql.shuffle.partitions", "2").getOrCreate())
   spark.sparkContext.setLogLevel("OFF")
   spark.createDataFrame(P, "id BIGINT, name STRING, age BIGINT, g 
STRING").createOrReplaceTempView("p")
   spark.createDataFrame(K, "a BIGINT, b BIGINT").createOrReplaceTempView("k")
   
   INNER = ("SELECT p.id AS x FROM p LEFT JOIN (SELECT p.id AS anchor FROM p 
INNER JOIN k ON p.id = k.a "
            "INNER JOIN p AS q ON k.b = q.id) o ON p.id = o.anchor")
   QUERY = f"SELECT x FROM (SELECT x FROM ({INNER} LIMIT 6 OFFSET 2) LIMIT 3 
OFFSET 1)"
   RUNS = 40
   
   print("pyspark", pyspark.__version__, "| spark", spark.version)
   print("query:", QUERY)
   for aqe in ("true", "false"):
       spark.conf.set("spark.sql.adaptive.enabled", aqe)
       counts = collections.Counter(len(spark.sql(QUERY).collect()) for _ in 
range(RUNS))
       print(f"spark.sql.adaptive.enabled={aqe}: expected 3 rows; row count: 
times in {RUNS} runs = {dict(counts)}")
   for aqe in ("true", "false"):
       spark.conf.set("spark.sql.adaptive.enabled", aqe)
       df = spark.sql(QUERY)
       df.collect()
       print(f"\n===== physical plan, spark.sql.adaptive.enabled={aqe} (after a 
run) =====")
       df.explain(mode="formatted")
   spark.stop()
   ```
   
   ## Workaround
   
   `spark.sql.adaptive.enabled=false` for the statement.
   
   <details><summary>Output of the script: tallies and both formatted physical 
plans</summary>
   
   ```
   pyspark 4.1.3 | spark 4.1.3
   query: SELECT x FROM (SELECT x FROM (SELECT p.id AS x FROM p LEFT JOIN 
(SELECT p.id AS anchor FROM p INNER JOIN k ON p.id = k.a INNER JOIN p AS q ON 
k.b = q.id) o ON p.id = o.anchor LIMIT 6 OFFSET 2) LIMIT 3 OFFSET 1)
   spark.sql.adaptive.enabled=true: expected 3 rows; row count: times in 40 
runs = {5: 3, 3: 37}
   spark.sql.adaptive.enabled=false: expected 3 rows; row count: times in 40 
runs = {3: 40}
   
   ===== physical plan, spark.sql.adaptive.enabled=true (after a run) =====
   == Physical Plan ==
   AdaptiveSparkPlan (61)
   +- == Final Plan ==
      ResultQueryStage (35)
      +- CollectLimit (34)
         +- * GlobalLimit (33)
            +- ShuffleQueryStage (32), Statistics(sizeInBytes=128.0 B, 
rowCount=8)
               +- Exchange (31)
                  +- * LocalLimit (30)
                     +- * Project (29)
                        +- * SortMergeJoin LeftOuter (28)
                           :- * Sort (7)
                           :  +- AQEShuffleRead (6)
                           :     +- ShuffleQueryStage (5), 
Statistics(sizeInBytes=112.0 B, rowCount=7)
                           :        +- Exchange (4)
                           :           +- * LocalLimit (3)
                           :              +- * Project (2)
                           :                 +- * Scan ExistingRDD (1)
                           +- * Sort (27)
                              +- * Project (26)
                                 +- * BroadcastHashJoin Inner BuildRight (25)
                                    :- * Project (22)
                                    :  +- * BroadcastHashJoin Inner BuildLeft 
(21)
                                    :     :- BroadcastQueryStage (15), 
Statistics(sizeInBytes=1024.1 KiB, rowCount=7)
                                    :     :  +- BroadcastExchange (14)
                                    :     :     +- AQEShuffleRead (13)
                                    :     :        +- ShuffleQueryStage (12), 
Statistics(sizeInBytes=112.0 B, rowCount=7)
                                    :     :           +- Exchange (11)
                                    :     :              +- * Project (10)
                                    :     :                 +- * Filter (9)
                                    :     :                    +- * Scan 
ExistingRDD (8)
                                    :     +- AQEShuffleRead (20)
                                    :        +- ShuffleQueryStage (19), 
Statistics(sizeInBytes=192.0 B, rowCount=8)
                                    :           +- Exchange (18)
                                    :              +- * Filter (17)
                                    :                 +- * Scan ExistingRDD (16)
                                    +- BroadcastQueryStage (24), 
Statistics(sizeInBytes=1024.1 KiB, rowCount=7)
                                       +- ReusedExchange (23)
   +- == Initial Plan ==
      CollectLimit (60)
      +- GlobalLimit (59)
         +- Exchange (58)
            +- LocalLimit (57)
               +- Project (56)
                  +- SortMergeJoin LeftOuter (55)
                     :- Sort (37)
                     :  +- Exchange (36)
                     :     +- LocalLimit (3)
                     :        +- Project (2)
                     :           +- Scan ExistingRDD (1)
                     +- Sort (54)
                        +- Exchange (53)
                           +- Project (52)
                              +- SortMergeJoin Inner (51)
                                 :- Sort (45)
                                 :  +- Exchange (44)
                                 :     +- Project (43)
                                 :        +- SortMergeJoin Inner (42)
                                 :           :- Sort (39)
                                 :           :  +- Exchange (38)
                                 :           :     +- Project (10)
                                 :           :        +- Filter (9)
                                 :           :           +- Scan ExistingRDD (8)
                                 :           +- Sort (41)
                                 :              +- Exchange (40)
                                 :                 +- Filter (17)
                                 :                    +- Scan ExistingRDD (16)
                                 +- Sort (50)
                                    +- Exchange (49)
                                       +- Project (48)
                                          +- Filter (47)
                                             +- Scan ExistingRDD (46)
   
   
   (1) Scan ExistingRDD [codegen id : 1]
   Output [4]: [id#0L, name#1, age#2L, g#3]
   Arguments: [id#0L, name#1, age#2L, g#3], MapPartitionsRDD[4] at 
applySchemaToPythonRDD at NativeMethodAccessorImpl.java:0, ExistingRDD, 
UnknownPartitioning(0)
   
   (2) Project [codegen id : 1]
   Output [1]: [id#0L]
   Input [4]: [id#0L, name#1, age#2L, g#3]
   
   (3) LocalLimit [codegen id : 1]
   Input [1]: [id#0L]
   Arguments: 8
   
   (4) Exchange
   Input [1]: [id#0L]
   Arguments: hashpartitioning(id#0L, 2), ENSURE_REQUIREMENTS, [plan_id=33878]
   
   (5) ShuffleQueryStage
   Output [1]: [id#0L]
   Arguments: 0
   
   (6) AQEShuffleRead
   Input [1]: [id#0L]
   Arguments: coalesced
   
   (7) Sort [codegen id : 5]
   Input [1]: [id#0L]
   Arguments: [id#0L ASC NULLS FIRST], false, 0
   
   (8) Scan ExistingRDD [codegen id : 2]
   Output [4]: [id#808L, name#809, age#810L, g#811]
   Arguments: [id#808L, name#809, age#810L, g#811], MapPartitionsRDD[4] at 
applySchemaToPythonRDD at NativeMethodAccessorImpl.java:0, ExistingRDD, 
UnknownPartitioning(0)
   
   (9) Filter [codegen id : 2]
   Input [4]: [id#808L, name#809, age#810L, g#811]
   Condition : isnotnull(id#808L)
   
   (10) Project [codegen id : 2]
   Output [1]: [id#808L]
   Input [4]: [id#808L, name#809, age#810L, g#811]
   
   (11) Exchange
   Input [1]: [id#808L]
   Arguments: hashpartitioning(id#808L, 2), ENSURE_REQUIREMENTS, [plan_id=33885]
   
   (12) ShuffleQueryStage
   Output [1]: [id#808L]
   Arguments: 1
   
   (13) AQEShuffleRead
   Input [1]: [id#808L]
   Arguments: local
   
   (14) BroadcastExchange
   Input [1]: [id#808L]
   Arguments: HashedRelationBroadcastMode(List(input[0, bigint, true]),false), 
[plan_id=34079]
   
   (15) BroadcastQueryStage
   Output [1]: [id#808L]
   Arguments: 5
   
   (16) Scan ExistingRDD [codegen id : 3]
   Output [2]: [a#4L, b#5L]
   Arguments: [a#4L, b#5L], MapPartitionsRDD[9] at applySchemaToPythonRDD at 
NativeMethodAccessorImpl.java:0, ExistingRDD, UnknownPartitioning(0)
   
   (17) Filter [codegen id : 3]
   Input [2]: [a#4L, b#5L]
   Condition : (isnotnull(a#4L) AND isnotnull(b#5L))
   
   (18) Exchange
   Input [2]: [a#4L, b#5L]
   Arguments: hashpartitioning(a#4L, 2), ENSURE_REQUIREMENTS, [plan_id=33892]
   
   (19) ShuffleQueryStage
   Output [2]: [a#4L, b#5L]
   Arguments: 2
   
   (20) AQEShuffleRead
   Input [2]: [a#4L, b#5L]
   Arguments: coalesced
   
   (21) BroadcastHashJoin [codegen id : 6]
   Left keys [1]: [id#808L]
   Right keys [1]: [a#4L]
   Join type: Inner
   Join condition: None
   
   (22) Project [codegen id : 6]
   Output [2]: [id#808L, b#5L]
   Input [3]: [id#808L, a#4L, b#5L]
   
   (23) ReusedExchange [Reuses operator id: 14]
   Output [1]: [id#812L]
   
   (24) BroadcastQueryStage
   Output [1]: [id#812L]
   Arguments: 7
   
   (25) BroadcastHashJoin [codegen id : 6]
   Left keys [1]: [b#5L]
   Right keys [1]: [id#812L]
   Join type: Inner
   Join condition: None
   
   (26) Project [codegen id : 6]
   Output [1]: [id#808L AS anchor#806L]
   Input [3]: [id#808L, b#5L, id#812L]
   
   (27) Sort [codegen id : 6]
   Input [1]: [anchor#806L]
   Arguments: [anchor#806L ASC NULLS FIRST], false, 0
   
   (28) SortMergeJoin [codegen id : 7]
   Left keys [1]: [id#0L]
   Right keys [1]: [anchor#806L]
   Join type: LeftOuter
   Join condition: None
   
   (29) Project [codegen id : 7]
   Output [1]: [id#0L AS x#807L]
   Input [2]: [id#0L, anchor#806L]
   
   (30) LocalLimit [codegen id : 7]
   Input [1]: [x#807L]
   Arguments: 8
   
   (31) Exchange
   Input [1]: [x#807L]
   Arguments: SinglePartition, ENSURE_REQUIREMENTS, [plan_id=34256]
   
   (32) ShuffleQueryStage
   Output [1]: [x#807L]
   Arguments: 8
   
   (33) GlobalLimit [codegen id : 8]
   Input [1]: [x#807L]
   Arguments: 8, 2
   
   (34) CollectLimit
   Input [1]: [x#807L]
   Arguments: 4, 1
   
   (35) ResultQueryStage
   Output [1]: [x#807L]
   Arguments: 9
   
   (36) Exchange
   Input [1]: [id#0L]
   Arguments: hashpartitioning(id#0L, 2), ENSURE_REQUIREMENTS, [plan_id=33807]
   
   (37) Sort
   Input [1]: [id#0L]
   Arguments: [id#0L ASC NULLS FIRST], false, 0
   
   (38) Exchange
   Input [1]: [id#808L]
   Arguments: hashpartitioning(id#808L, 2), ENSURE_REQUIREMENTS, [plan_id=33791]
   
   (39) Sort
   Input [1]: [id#808L]
   Arguments: [id#808L ASC NULLS FIRST], false, 0
   
   (40) Exchange
   Input [2]: [a#4L, b#5L]
   Arguments: hashpartitioning(a#4L, 2), ENSURE_REQUIREMENTS, [plan_id=33792]
   
   (41) Sort
   Input [2]: [a#4L, b#5L]
   Arguments: [a#4L ASC NULLS FIRST], false, 0
   
   (42) SortMergeJoin
   Left keys [1]: [id#808L]
   Right keys [1]: [a#4L]
   Join type: Inner
   Join condition: None
   
   (43) Project
   Output [2]: [id#808L, b#5L]
   Input [3]: [id#808L, a#4L, b#5L]
   
   (44) Exchange
   Input [2]: [id#808L, b#5L]
   Arguments: hashpartitioning(b#5L, 2), ENSURE_REQUIREMENTS, [plan_id=33799]
   
   (45) Sort
   Input [2]: [id#808L, b#5L]
   Arguments: [b#5L ASC NULLS FIRST], false, 0
   
   (46) Scan ExistingRDD
   Output [4]: [id#812L, name#813, age#814L, g#815]
   Arguments: [id#812L, name#813, age#814L, g#815], MapPartitionsRDD[4] at 
applySchemaToPythonRDD at NativeMethodAccessorImpl.java:0, ExistingRDD, 
UnknownPartitioning(0)
   
   (47) Filter
   Input [4]: [id#812L, name#813, age#814L, g#815]
   Condition : isnotnull(id#812L)
   
   (48) Project
   Output [1]: [id#812L]
   Input [4]: [id#812L, name#813, age#814L, g#815]
   
   (49) Exchange
   Input [1]: [id#812L]
   Arguments: hashpartitioning(id#812L, 2), ENSURE_REQUIREMENTS, [plan_id=33800]
   
   (50) Sort
   Input [1]: [id#812L]
   Arguments: [id#812L ASC NULLS FIRST], false, 0
   
   (51) SortMergeJoin
   Left keys [1]: [b#5L]
   Right keys [1]: [id#812L]
   Join type: Inner
   Join condition: None
   
   (52) Project
   Output [1]: [id#808L AS anchor#806L]
   Input [3]: [id#808L, b#5L, id#812L]
   
   (53) Exchange
   Input [1]: [anchor#806L]
   Arguments: hashpartitioning(anchor#806L, 2), ENSURE_REQUIREMENTS, 
[plan_id=33808]
   
   (54) Sort
   Input [1]: [anchor#806L]
   Arguments: [anchor#806L ASC NULLS FIRST], false, 0
   
   (55) SortMergeJoin
   Left keys [1]: [id#0L]
   Right keys [1]: [anchor#806L]
   Join type: LeftOuter
   Join condition: None
   
   (56) Project
   Output [1]: [id#0L AS x#807L]
   Input [2]: [id#0L, anchor#806L]
   
   (57) LocalLimit
   Input [1]: [x#807L]
   Arguments: 8
   
   (58) Exchange
   Input [1]: [x#807L]
   Arguments: SinglePartition, ENSURE_REQUIREMENTS, [plan_id=33815]
   
   (59) GlobalLimit
   Input [1]: [x#807L]
   Arguments: 8, 2
   
   (60) CollectLimit
   Input [1]: [x#807L]
   Arguments: 4, 1
   
   (61) AdaptiveSparkPlan
   Output [1]: [x#807L]
   Arguments: isFinalPlan=true
   
   
   
   ===== physical plan, spark.sql.adaptive.enabled=false (after a run) =====
   == Physical Plan ==
   CollectLimit (30)
   +- * GlobalLimit (29)
      +- Exchange (28)
         +- * LocalLimit (27)
            +- * Project (26)
               +- * SortMergeJoin LeftOuter (25)
                  :- * Sort (5)
                  :  +- Exchange (4)
                  :     +- * LocalLimit (3)
                  :        +- * Project (2)
                  :           +- * Scan ExistingRDD (1)
                  +- * Sort (24)
                     +- Exchange (23)
                        +- * Project (22)
                           +- * SortMergeJoin Inner (21)
                              :- * Sort (18)
                              :  +- Exchange (17)
                              :     +- * Project (16)
                              :        +- * SortMergeJoin Inner (15)
                              :           :- * Sort (10)
                              :           :  +- Exchange (9)
                              :           :     +- * Project (8)
                              :           :        +- * Filter (7)
                              :           :           +- * Scan ExistingRDD (6)
                              :           +- * Sort (14)
                              :              +- Exchange (13)
                              :                 +- * Filter (12)
                              :                    +- * Scan ExistingRDD (11)
                              +- * Sort (20)
                                 +- ReusedExchange (19)
   
   
   (1) Scan ExistingRDD [codegen id : 1]
   Output [4]: [id#0L, name#1, age#2L, g#3]
   Arguments: [id#0L, name#1, age#2L, g#3], MapPartitionsRDD[4] at 
applySchemaToPythonRDD at NativeMethodAccessorImpl.java:0, ExistingRDD, 
UnknownPartitioning(0)
   
   (2) Project [codegen id : 1]
   Output [1]: [id#0L]
   Input [4]: [id#0L, name#1, age#2L, g#3]
   
   (3) LocalLimit [codegen id : 1]
   Input [1]: [id#0L]
   Arguments: 8
   
   (4) Exchange
   Input [1]: [id#0L]
   Arguments: hashpartitioning(id#0L, 2), ENSURE_REQUIREMENTS, [plan_id=34403]
   
   (5) Sort [codegen id : 2]
   Input [1]: [id#0L]
   Arguments: [id#0L ASC NULLS FIRST], false, 0
   
   (6) Scan ExistingRDD [codegen id : 3]
   Output [4]: [id#818L, name#819, age#820L, g#821]
   Arguments: [id#818L, name#819, age#820L, g#821], MapPartitionsRDD[4] at 
applySchemaToPythonRDD at NativeMethodAccessorImpl.java:0, ExistingRDD, 
UnknownPartitioning(0)
   
   (7) Filter [codegen id : 3]
   Input [4]: [id#818L, name#819, age#820L, g#821]
   Condition : isnotnull(id#818L)
   
   (8) Project [codegen id : 3]
   Output [1]: [id#818L]
   Input [4]: [id#818L, name#819, age#820L, g#821]
   
   (9) Exchange
   Input [1]: [id#818L]
   Arguments: hashpartitioning(id#818L, 2), ENSURE_REQUIREMENTS, [plan_id=34409]
   
   (10) Sort [codegen id : 4]
   Input [1]: [id#818L]
   Arguments: [id#818L ASC NULLS FIRST], false, 0
   
   (11) Scan ExistingRDD [codegen id : 5]
   Output [2]: [a#4L, b#5L]
   Arguments: [a#4L, b#5L], MapPartitionsRDD[9] at applySchemaToPythonRDD at 
NativeMethodAccessorImpl.java:0, ExistingRDD, UnknownPartitioning(0)
   
   (12) Filter [codegen id : 5]
   Input [2]: [a#4L, b#5L]
   Condition : (isnotnull(a#4L) AND isnotnull(b#5L))
   
   (13) Exchange
   Input [2]: [a#4L, b#5L]
   Arguments: hashpartitioning(a#4L, 2), ENSURE_REQUIREMENTS, [plan_id=34415]
   
   (14) Sort [codegen id : 6]
   Input [2]: [a#4L, b#5L]
   Arguments: [a#4L ASC NULLS FIRST], false, 0
   
   (15) SortMergeJoin [codegen id : 7]
   Left keys [1]: [id#818L]
   Right keys [1]: [a#4L]
   Join type: Inner
   Join condition: None
   
   (16) Project [codegen id : 7]
   Output [2]: [id#818L, b#5L]
   Input [3]: [id#818L, a#4L, b#5L]
   
   (17) Exchange
   Input [2]: [id#818L, b#5L]
   Arguments: hashpartitioning(b#5L, 2), ENSURE_REQUIREMENTS, [plan_id=34423]
   
   (18) Sort [codegen id : 8]
   Input [2]: [id#818L, b#5L]
   Arguments: [b#5L ASC NULLS FIRST], false, 0
   
   (19) ReusedExchange [Reuses operator id: 9]
   Output [1]: [id#822L]
   
   (20) Sort [codegen id : 10]
   Input [1]: [id#822L]
   Arguments: [id#822L ASC NULLS FIRST], false, 0
   
   (21) SortMergeJoin [codegen id : 11]
   Left keys [1]: [b#5L]
   Right keys [1]: [id#822L]
   Join type: Inner
   Join condition: None
   
   (22) Project [codegen id : 11]
   Output [1]: [id#818L AS anchor#816L]
   Input [3]: [id#818L, b#5L, id#822L]
   
   (23) Exchange
   Input [1]: [anchor#816L]
   Arguments: hashpartitioning(anchor#816L, 2), ENSURE_REQUIREMENTS, 
[plan_id=34509]
   
   (24) Sort [codegen id : 12]
   Input [1]: [anchor#816L]
   Arguments: [anchor#816L ASC NULLS FIRST], false, 0
   
   (25) SortMergeJoin [codegen id : 13]
   Left keys [1]: [id#0L]
   Right keys [1]: [anchor#816L]
   Join type: LeftOuter
   Join condition: None
   
   (26) Project [codegen id : 13]
   Output [1]: [id#0L AS x#817L]
   Input [2]: [id#0L, anchor#816L]
   
   (27) LocalLimit [codegen id : 13]
   Input [1]: [x#817L]
   Arguments: 8
   
   (28) Exchange
   Input [1]: [x#817L]
   Arguments: SinglePartition, ENSURE_REQUIREMENTS, [plan_id=34535]
   
   (29) GlobalLimit [codegen id : 14]
   Input [1]: [x#817L]
   Arguments: 8, 2
   
   (30) CollectLimit
   Input [1]: [x#817L]
   Arguments: 4, 1
   ```
   
   </details>
   


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