peter-toth commented on code in PR #57742:
URL: https://github.com/apache/spark/pull/57742#discussion_r3755075346


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala:
##########
@@ -4156,6 +4156,46 @@ object SQLConf {
     .booleanConf
     .createWithDefault(false)
 
+  val ADAPTIVE_PARTIAL_AGGREGATION_ENABLED =
+    
buildConf("spark.sql.execution.aggregate.adaptivePartialAggregation.enabled")
+      .doc("When true, hash aggregation adaptively bypasses the pre-shuffle 
partial aggregation " +
+        "at runtime when it observes that the partial aggregation is not 
reducing the number of " +
+        "rows enough to be worthwhile. Once bypassed, the remaining input rows 
are passed " +
+        "through as single-row partial aggregation buffers for the final 
aggregation to merge, " +
+        "which avoids the cost of maintaining and spilling a large aggregation 
map with little " +
+        "reduction benefit. This applies only to hash aggregation with 
grouping keys.")
+      .version("4.4.0")
+      .withBindingPolicy(ConfigBindingPolicy.SESSION)
+      .booleanConf
+      .createWithDefault(true)

Review Comment:
   **Finding 16.** Not a blocker, and I want to be straight about how strong 
this is. Low-cardinality, no spill, whole-stage codegen, off -> on, best / avg 
/ stdev from the files committed here:
   
   | results file | off | on |
   | --- | --- | --- |
   | `-jdk25-results.txt` | 269 / 275 / 4 | 284 / 294 / 6 |
   | `-results.txt` (JDK 17) | 270 / 311 / 37 | 293 / 318 / 22 |
   | `-jdk21-results.txt` | 281 / 292 / 7 | 291 / 323 / 52 |
   
   The JDK 17 and 21 runs are too noisy to carry it on their own — the JDK 17 
off arm has a stdev of 37 against an avg delta of 7. The JDK 25 run is the 
clean one: stdevs of 4 and 6, best +15, avg +19, so 3-5 stdev. All three agree 
in sign on best time, and two of the three files report it as `1.0X -> 0.9X`.
   
   What makes me think it isn't just noise is that a mechanism predicts it. Two 
costs are paid whether or not anything bypasses: `needStopCheck` becomes 
`true`, putting `if (shouldStop()) return;` back into every upstream produce 
loop for a plan that never appends mid-build, and `doConsume` gains the per-row 
guards, the counter increment and the check-point compare. About 1.4 ns/row 
over the 16.8M-row input is the right order for a couple of extra branches per 
row.
   
   Two measurements would settle it: re-run the low-cardinality cases with the 
iteration count raised so the stdevs come down, and run one arm with 
`needStopCheck` hard-coded to `false` to say how much of it is the stop check. 
If it's mostly the stop check that's worth knowing, though I don't think it's a 
one-liner to avoid — `shouldStopCheckCode` is emitted by the child from 
`parent.needStopCheck` at codegen time, so it can't easily be gated on a flag 
that only flips at runtime.
   
   Either way, the benchmark comment on this scenario says the two runs "must 
match (no regression)", which these numbers don't support.
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/aggregate/HashAggregateExec.scala:
##########
@@ -535,19 +642,27 @@ case class HashAggregateExec(
     // `addNewFunction` spills this helper into a nested class (as can happen
     // once the outer class passes the code-size threshold), the bare field
     // reference fails with `IllegalAccessError`.
-    val doAggFuncName = ctx.addNewFunction(doAgg,
-      s"""
-         |private void $doAgg(int partitionIndex) throws java.io.IOException {
-         |  ${child.asInstanceOf[CodegenSupport].produce(ctx, this)}
-         |  $finishHashMap
-         |}
-       """.stripMargin)
 
-    // generate code for output
+    // Generate code for output. This must happen before the `doAgg` helper 
below, because with
+    // adaptive partial aggregation enabled, `doConsumeWithKeys` (invoked from 
the child's produce
+    // inside `doAgg`) emits pass-through rows by calling this output function 
directly.
     val keyTerm = ctx.freshName("aggKey")
     val bufferTerm = ctx.freshName("aggBuffer")
-    val outputFunc = generateResultFunction(ctx)
-
+    outputFunc = generateResultFunction(ctx)

Review Comment:
   **Finding 17.** This moved `generateResultFunction` ahead of the `doAgg` 
registration for every keyed hash aggregate, not only the adaptive ones. Only 
the adaptive path needs it, so `doConsumeWithKeys` can call `outputFunc` while 
the child's produce is generated inside `doAgg`.
   
   The reorder is not consequence-free when the feature is off: 
`generateResultFunction` calls `consume(...)`, so the parent's consume code, 
its fresh names and its mutable-state slots are now allocated before the 
child's produce rather than after. `ctx.addNewFunction` decides 
inline-versus-nested-class from the size accumulated so far, so which helpers 
end up in a nested class can change for a large stage — and the comment just 
above at `:639-644` exists because a nested-class placement already caused an 
`IllegalAccessError` on this method. Nothing is known to break, but there is no 
reason for this to reach plans the feature never touches.
   
   ```scala
       if (adaptivePartialAggEnabled) {
         outputFunc = generateResultFunction(ctx)
       }
       // ... adaptiveOutputMapFuncName, then doAggFuncName ...
       if (!adaptivePartialAggEnabled) {
         outputFunc = generateResultFunction(ctx)
       }
   ```
   
   The second assignment only has to land before `adaptiveFinalOutput` is built 
at `:812`. `keyTerm`/`bufferTerm` are read only by the output loops, so they 
can stay where they are.
   



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/aggregate/AdaptivePartialAggregationSuite.scala:
##########
@@ -0,0 +1,1136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution.aggregate
+
+import org.apache.spark.sql.{DataFrame, QueryTest, Row}
+import org.apache.spark.sql.catalyst.expressions.aggregate.Partial
+import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanHelper
+import org.apache.spark.sql.functions._
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.test.SharedSparkSession
+
+/**
+ * Tests for runtime adaptive partial aggregation
+ * (see [[SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED]]). When a partial 
aggregate is not reducing
+ * rows, the operator stops aggregating and streams the remaining rows through 
as single-row partial
+ * buffers for the Final aggregate to merge. It must never change results.
+ *
+ * The suite has two halves:
+ *   1. Correctness: output is identical to the reference (feature-off) run 
across the full matrix
+ *      of codegen on/off, two-level map on/off, and spill/no-spill, over a 
range of aggregate
+ *      shapes, key types, and `Expand`-bearing plans (ROLLUP / CUBE / 
GROUPING SETS /
+ *      multi-distinct).
+ *   2. Triggering: the `numBypassingRows` metric proves the bypass actually 
fires when (and only
+ *      when) it should -- high-cardinality input bypasses, low-cardinality 
input keeps aggregating,
+ *      the feature switch and eligibility rules are honored, and both check 
points work.
+ */
+class AdaptivePartialAggregationSuite extends QueryTest with SharedSparkSession
+  with AdaptiveSparkPlanHelper {
+
+  import testImplicits._
+
+  // A `testFallbackStartsAt` setting ("fastMapCounter, regularMapCounter") 
that makes the regular
+  // map fall back (spill) periodically, exercising the spill-check decision 
path in both the
+  // codegen and interpreted aggregation paths. Kept moderate so 
low-cardinality inputs (which are
+  // never bypassed and therefore really spill) do not open an unbounded 
number of spill readers.
+  private val forceSpillFallback = "4, 16"
+
+  // The upstream `CombineAdjacentAggregation` and `ReplaceHashWithSortAgg` 
rules would change the
+  // plan of these small single-partition queries away from a Partial+Final 
`HashAggregateExec`:
+  // the former merges the two adjacent phases (no shuffle in between) into a 
single `Complete`
+  // aggregate, and the latter converts a hash aggregate to a sort aggregate 
when the input is
+  // already sorted by the grouping key (a `Range` over an ascending `id` 
key). The adaptive
+  // feature lives in the partial hash aggregation, so both rules are disabled 
to keep that
+  // structure in the tests.
+  private val fixedPlanConfs = Seq(
+    SQLConf.COMBINE_ADJACENT_AGGREGATION_ENABLED.key -> "false",
+    SQLConf.REPLACE_HASH_WITH_SORT_AGG_ENABLED.key -> "false")
+
+  /**
+   * Runs `build` with adaptive partial aggregation disabled (the reference) 
and then across the
+   * full configuration matrix with it enabled, asserting every enabled run 
matches the reference.
+   *
+   * `build` takes the number of input partitions, which the matrix varies 
along with everything
+   * else, because the plan shape decides which parts of the feature run at 
all. When the two
+   * aggregates end up in one whole-stage -- no `Exchange` between them -- the 
partial aggregate's
+   * output feeds the Final's `doConsume` directly and never reaches
+   * `BufferedRowIterator.currentRows`, so `shouldStop()` stays false for the 
whole build and
+   * neither `needStopCheck` nor the resumed-build path is exercised. 
Splitting them puts the
+   * streamed rows through the output buffer and runs both.
+   *
+   * More than one input partition is necessary but not sufficient for that 
split: a `Range` keyed
+   * directly on `id` already reports an output partitioning that satisfies 
the Final aggregate's
+   * `ClusteredDistribution`, so `EnsureRequirements` inserts no `Exchange` 
however many partitions
+   * it has. Tests that want the split shape group on a derived key (a cast, 
say) so the input
+   * partitioning no longer satisfies the requirement.
+   *
+   * `expectBypass` ties the correctness guarantee to the triggering 
guarantee: beyond matching the
+   * reference, every cell must either actually stream rows through (when 
true) or keep
+   * aggregating (when false). Without it a test could silently stop 
exercising pass-through if the
+   * input stopped being bypassable, and only this assertion makes that fail 
loudly.
+   */
+  private def checkAdaptiveMatchesReference(
+      build: Int => DataFrame,
+      expectBypass: Boolean = true): Unit = {
+    for {
+      inputPartitions <- Seq(1, 2)
+      wholeStage <- Seq(true, false)
+      twoLevelMap <- Seq(true, false)
+      forceSpill <- Seq(true, false)
+    } {
+      // The reference is built with the same partitioning, so only the 
feature differs.
+      val reference = withSQLConf(
+        (SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key -> "false") +: 
fixedPlanConfs: _*) {
+        build(inputPartitions).collect().toSeq
+      }
+      val spillConf = if (forceSpill) {
+        Seq("spark.sql.TungstenAggregate.testFallbackStartsAt" -> 
forceSpillFallback)
+      } else {
+        Nil
+      }
+      withSQLConf(
+        (Seq(
+          SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key -> "true",
+          SQLConf.WHOLESTAGE_CODEGEN_ENABLED.key -> wholeStage.toString,
+          SQLConf.ENABLE_TWOLEVEL_AGG_MAP.key -> twoLevelMap.toString,
+          // Small `minRows` so the periodic check runs on modest inputs.
+          SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_MIN_ROWS.key -> "8") ++
+          spillConf ++ fixedPlanConfs): _*) {
+        val msg = s"inputPartitions=$inputPartitions wholeStage=$wholeStage " +
+          s"twoLevelMap=$twoLevelMap forceSpill=$forceSpill"
+        withClue(msg) {
+          // Collect once so the metrics are populated, then check whether the 
bypass fired for
+          // this cell. The metric lives on the `Partial`-mode 
`HashAggregateExec`, so that is the
+          // operator the assertion reads.
+          val df = build(inputPartitions)
+          df.collect()
+          val skipped = collect(df.queryExecution.executedPlan) {
+            case agg: HashAggregateExec if 
agg.aggregateExpressions.forall(_.mode == Partial) =>
+              agg.metrics.get("numBypassingRows").map(_.value).getOrElse(0L)
+          }.sum
+          if (expectBypass) {
+            assert(skipped > 0,
+              s"expected rows to bypass partial aggregation, got $skipped 
bypassed rows")
+          } else {
+            assert(skipped == 0,
+              s"expected no rows to bypass partial aggregation, got $skipped 
bypassed rows")
+          }
+          checkAnswer(df, reference)
+        }
+      }
+    }
+  }
+
+  /**
+   * The observable per-run counters we assert on, all read from the partial 
`HashAggregateExec` in
+   * a single execution so the metrics are not double-counted:
+   *   - `skipped`: our self-reported `numBypassingRows` metric.
+   *   - `partialOutputRows`: the partial aggregate's own `numOutputRows`. An 
independent,
+   *     pre-existing counter driven by the normal output path, so it is the 
ground truth for
+   *     whether rows were streamed through -- it equals the distinct key 
count when aggregation is
+   *     effective and climbs toward the input row count once the operator 
bypasses.
+   *   - `spillBytes`: the partial aggregate's `spillSize`. Reliable only when 
no fallback is
+   *     forced: on the interpreted path this is derived from the 
task-cumulative memory-spill
+   *     counter, so a forced fallback (or downstream shuffle-write spill) can 
inflate it.
+   *     Asserted only by the periodic check test, which forces no fallback; 
use
+   *     `tasksFallBacked` otherwise.
+   *   - `tasksFallBacked`: the partial aggregate's `numTasksFallBacked`, 
incremented only when the
+   *     regular map actually falls back into sort-based aggregation. When the 
spill check bypasses
+   *     at the spill boundary the sorter is never created, so this stays 0 -- 
direct, per-operator
+   *     evidence the bypass replaced the sort fallback.
+   */
+  private case class AggCounters(
+      skipped: Long,
+      partialOutputRows: Long,
+      spillBytes: Long,
+      tasksFallBacked: Long)
+
+  // Verifies `df` (an already-collected bypassing run) produces the same 
results as the feature-off
+  // reference. `build` is re-run for the reference so it gets a genuinely 
non-adaptive plan rather
+  // than reusing the bypassing run's cached one.
+  private def checkAgainstReference(df: DataFrame, build: () => DataFrame): 
Unit = {
+    val reference = withSQLConf(
+      SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key -> "false") {
+      build().collect().toSeq
+    }
+    checkAnswer(df, reference)
+  }
+
+  private def runAndReadCounters(build: () => DataFrame): AggCounters = {
+    // The triggering tests assert on metrics, so also verify the bypassing 
run produces the same
+    // results as the feature-off reference.
+    val df = build()
+    df.collect()
+    val partialAggs = collect(df.queryExecution.executedPlan) {
+      case agg: HashAggregateExec if agg.aggregateExpressions.forall(_.mode == 
Partial) => agg
+    }
+    // A partial aggregate is always present for the grouped queries these 
tests use.
+    assert(partialAggs.nonEmpty, "expected a partial HashAggregateExec in the 
plan")
+    val counters = AggCounters(
+      // The metric is only registered on aggregates the feature applies to; 
an aggregate without
+      // it bypassed nothing.
+      skipped = 
partialAggs.map(_.metrics.get("numBypassingRows").map(_.value).getOrElse(0L)).sum,
+      partialOutputRows = 
partialAggs.map(_.metrics("numOutputRows").value).sum,
+      spillBytes = partialAggs.map(_.metrics("spillSize").value).sum,
+      tasksFallBacked = 
partialAggs.map(_.metrics("numTasksFallBacked").value).sum)
+    checkAgainstReference(df, build)
+    counters
+  }
+
+  private def numBypassingRows(build: () => DataFrame): Long = 
runAndReadCounters(build).skipped
+
+  // Returns the bypassed-row count per Partial-mode `HashAggregateExec`, 
keyed by the number of
+  // grouping keys, and verifies the run matches the feature-off reference. A 
`count(DISTINCT ...)`
+  // group-by has two such Partial phases -- the de-duplication partial 
(grouping on key + distinct
+  // columns) and the distinct partial (grouping on the keys only) -- so their 
bypasses can be told
+  // apart by the grouping key count.
+  private def bypassRowsByGroupingKeyCount(build: () => DataFrame): Map[Int, 
Long] = {
+    val df = build()
+    df.collect()
+    val byKeyCount = collect(df.queryExecution.executedPlan) {
+      case agg: HashAggregateExec if agg.aggregateExpressions.forall(_.mode == 
Partial) =>
+        agg.groupingExpressions.length ->
+          agg.metrics.get("numBypassingRows").map(_.value).getOrElse(0L)
+    }.groupBy(_._1).map { case (n, pairs) => n -> pairs.map(_._2).sum }
+    checkAgainstReference(df, build)
+    byKeyCount
+  }
+
+  /**
+   * Runs `body` once per (wholeStage, twoLevelMap) combination with the 
feature enabled and a small
+   * `minRows`, threading a descriptive clue for failure messages.
+   *
+   * The fast (first-level) map is append-only and never spills, so only the 
regular (second-level)
+   * map can reach a spill boundary. With the default fast-map capacity (2^16) 
a small
+   * high-cardinality input would be fully absorbed by the fast map and never 
reach the regular map,
+   * so nothing could ever bypass. To make the triggering tests meaningful 
when the two-level map is
+   * on, we shrink the fast map via the first field of `testFallbackStartsAt` 
so rows fall through
+   * to the regular map. `regularFallback` optionally sets the second field to 
also force the
+   * regular map to spill (for the spill check); when 0 the regular map does 
not spill.
+   */
+  private def forEachCodegenAndMap(
+      minRows: Long = 8,
+      regularFallback: Int = 0,
+      minCompaction: Double = -1.0)(
+      body: String => Unit): Unit = {
+    for {
+      wholeStage <- Seq(true, false)
+      twoLevelMap <- Seq(true, false)
+    } {
+      // Shrink the fast map to 4 keys when it is on so rows reach the regular 
map. The second field
+      // controls regular-map spilling; 0 means "never" (a large sentinel).
+      val fallbackConf = if (twoLevelMap || regularFallback > 0) {
+        val fastCap = if (twoLevelMap) 4 else 1
+        val regular = if (regularFallback > 0) regularFallback else 
Int.MaxValue
+        Seq("spark.sql.TungstenAggregate.testFallbackStartsAt" -> s"$fastCap, 
$regular")
+      } else {
+        Nil
+      }
+      // A negative value means "leave the threshold at its default".
+      val thresholdConf = if (minCompaction >= 0.0) {
+        Seq(SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_MIN_COMPACTION.key -> 
minCompaction.toString)
+      } else {
+        Nil
+      }
+      withSQLConf(
+        (Seq(
+          SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_ENABLED.key -> "true",
+          SQLConf.WHOLESTAGE_CODEGEN_ENABLED.key -> wholeStage.toString,
+          SQLConf.ENABLE_TWOLEVEL_AGG_MAP.key -> twoLevelMap.toString,
+          SQLConf.ADAPTIVE_PARTIAL_AGGREGATION_MIN_ROWS.key -> 
minRows.toString) ++
+          fallbackConf ++ thresholdConf ++ fixedPlanConfs): _*) {
+        body(s"wholeStage=$wholeStage twoLevelMap=$twoLevelMap")
+      }
+    }
+  }
+
+  /////////////////////////////////////////////////////////////////////////////
+  // Part 1: Correctness -- results identical to the feature-off reference.
+  /////////////////////////////////////////////////////////////////////////////
+
+  test("results unchanged for high-cardinality input that bypasses partial 
aggregation") {
+    // Every grouping key is distinct, so partial aggregation reduces nothing 
and should be
+    // bypassed by the periodic check.
+    checkAdaptiveMatchesReference { parts =>
+      spark.range(0, 200, 1, parts)
+        .select($"id".cast("string") as "k", ($"id" * 2) as "v")
+        .groupBy($"k")
+        .agg(sum($"v") as "s", count(lit(1)) as "c", max($"v") as "m")
+    }
+  }
+
+  test("results unchanged for low-cardinality input that keeps partial 
aggregation") {
+    // Few distinct keys, high reduction: partial aggregation is effective and 
should be kept, so
+    // the bypass metric must stay zero in every cell.
+    checkAdaptiveMatchesReference(
+      expectBypass = false,
+      build = { parts =>
+        spark.range(0, 600, 1, parts)
+          .select(($"id" % 5).cast("string") as "k", $"id" as "v")
+          .groupBy($"k")
+          .agg(sum($"v") as "s", count(lit(1)) as "c", min($"v") as "mn", 
max($"v") as "mx")
+      })
+  }
+
+  test("results unchanged for medium-cardinality input near the reduction 
threshold") {
+    // Roughly half the rows are distinct keys; exercises the boundary of the 
ratio checks. The
+    // overall compaction ratio (~2.0) is above the threshold, but the *first* 
periodic check still
+    // sees the leading distinct keys and fires, so the bypass must be 
observable too.
+    checkAdaptiveMatchesReference { parts =>
+      spark.range(0, 1000, 1, parts)
+        .select(($"id" % 500).cast("string") as "k", $"id" as "v")
+        .groupBy($"k")
+        .agg(sum($"v") as "s", count(lit(1)) as "c")
+    }
+  }
+
+  test("results unchanged with multiple grouping keys and string keys") {
+    checkAdaptiveMatchesReference { parts =>
+      spark.range(0, 500, 1, parts)
+        .select(
+          concat(lit("g"), ($"id" % 300).cast("string")) as "k1",
+          ($"id" % 7) as "k2",
+          $"id" as "v")
+        .groupBy($"k1", $"k2")
+        .agg(sum($"v") as "s", count(lit(1)) as "c")
+    }
+  }
+
+  test("results unchanged with nullable grouping keys") {
+    // Nulls are sparse enough (1 in 40) that the keys stay close to unique 
and the input really
+    // does bypass; a denser null key would lift the compaction ratio above 
the threshold and the
+    // test would never engage the feature.
+    checkAdaptiveMatchesReference { parts =>
+      spark.range(0, 400, 1, parts)
+        .select(
+          when($"id" % 40 === 0, lit(null)).otherwise($"id").cast("string") as 
"k",
+          $"id" as "v")
+        .groupBy($"k")
+        .agg(sum($"v") as "s", count(lit(1)) as "c")
+    }
+  }
+
+  test("results unchanged with average (multi-slot buffer) aggregate") {
+    // avg has a two-slot partial buffer (sum, count); pass-through buffers 
must carry all slots.
+    checkAdaptiveMatchesReference { parts =>
+      spark.range(0, 300, 1, parts)
+        .select($"id".cast("string") as "k", ($"id" + 1) as "v")
+        .groupBy($"k")
+        .agg(avg($"v") as "a", sum($"v") as "s")
+    }
+  }
+
+  test("results unchanged with a mix of many aggregate functions and buffer 
types") {
+    // Exercises a wide pass-through buffer spanning several aggregate buffer 
layouts at once:
+    // sum (decimal), avg (double), count, min/max, first/last, and stddev 
(imperative buffer).

Review Comment:
   **Finding 18.** `stddev` is not an imperative-buffer aggregate — 
`CentralMomentAgg` is a `DeclarativeAggregate`, so this test covers a wide 
declarative buffer and nothing else. That is worth a bit more than a comment 
fix, because no test in the suite reaches a real `ImperativeAggregate` through 
pass-through, and that is the one buffer kind where `nextPassThroughOutput`'s 
`copyFrom(initialAggregationBuffer)` has to restore state written by 
`initialize(buffer)` rather than by a projection.
   
   `approx_count_distinct` is the easy one: `HyperLogLogPlusPlus` is an 
`ImperativeAggregate` with a mutable `LongType` buffer, so it plans as a 
`HashAggregateExec` whose `supportCodegen` is false and only ever runs on 
`TungstenAggregationIterator`. Better as its own test than folded in here — one 
imperative function makes `supportCodegen` false for the whole operator, which 
would quietly turn this test's codegen cells into interpreted ones.
   
   ```scala
     test("results unchanged with an imperative aggregate buffer") {
       // `HyperLogLogPlusPlus` is an `ImperativeAggregate`, so 
`supportCodegen` is false and this
       // only runs on `TungstenAggregationIterator` -- the path where a 
pass-through buffer is reset
       // with `copyFrom(initialAggregationBuffer)` and has to carry state 
`initialize` wrote.
       checkAdaptiveMatchesReference { parts =>
         spark.range(0, 400, 1, parts)
           .select($"id".cast("string") as "k", ($"id" % 97) as "v")
           .groupBy($"k")
           .agg(approx_count_distinct($"v") as "acd", sum($"v") as "s")
       }
     }
   ```
   



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