This is an automated email from the ASF dual-hosted git repository.
zhztheplayer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git
The following commit(s) were added to refs/heads/main by this push:
new 91ae3b22fc [VL] Eager aggregation: Support partial-merge mode (#13284)
91ae3b22fc is described below
commit 91ae3b22fcb1dbff6937ae517fc0d816c23f4501
Author: Hongze Zhang <[email protected]>
AuthorDate: Wed Oct 14 16:34:39 2026 +0100
[VL] Eager aggregation: Support partial-merge mode (#13284)
---
.../execution/StarSchemaJoinAggregateSuite.scala | 11 +-
docs/Configuration.md | 2 +-
.../org/apache/gluten/config/ConfigBuilder.scala | 15 +
.../org/apache/gluten/config/GlutenConfig.scala | 20 +-
.../joinagg/PushAggregateThroughJoin.scala | 11 +-
.../gluten/config/SparkConfigUtilSuite.scala | 32 ++
.../execution/PushAggregateThroughJoinSuite.scala | 348 ++++++++-------------
7 files changed, 200 insertions(+), 239 deletions(-)
diff --git
a/backends-velox/src/test/scala/org/apache/gluten/execution/StarSchemaJoinAggregateSuite.scala
b/backends-velox/src/test/scala/org/apache/gluten/execution/StarSchemaJoinAggregateSuite.scala
index 9e848847ae..373ceedef3 100644
---
a/backends-velox/src/test/scala/org/apache/gluten/execution/StarSchemaJoinAggregateSuite.scala
+++
b/backends-velox/src/test/scala/org/apache/gluten/execution/StarSchemaJoinAggregateSuite.scala
@@ -73,7 +73,14 @@ class StarSchemaJoinAggregateSingleDepthSuite extends
StarSchemaJoinAggregateSui
class StarSchemaJoinAggregatePartialMergeEnabledSuite extends
StarSchemaJoinAggregateSuite {
override protected def sparkConf: SparkConf = {
super.sparkConf
- .set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_ENABLED.key,
"true")
+ .set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE.key,
"all")
+ }
+}
+
+class StarSchemaJoinAggregateTopPartialMergeSuite extends
StarSchemaJoinAggregateSuite {
+ override protected def sparkConf: SparkConf = {
+ super.sparkConf
+ .set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE.key,
"top")
}
}
@@ -102,7 +109,7 @@ class StarSchemaJoinAggregateSuite extends
VeloxTPCHTableSupport with AdaptiveSp
.set(GlutenConfig.COLUMNAR_FORCE_SHUFFLED_HASH_JOIN_ENABLED.key, "true")
.set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_ENABLED.key, "true")
.set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_MAX_DEPTH.key,
s"${Int.MaxValue}")
- .set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_ENABLED.key,
"false")
+ .set(GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE.key,
"none")
.set("spark.sql.adaptive.enabled", "false")
}
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 83e3432343..117d5782b0 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -138,7 +138,7 @@ nav_order: 15
| spark.gluten.sql.orc.charType.scan.fallback.enabled | 🔄
Dynamic | true | Force fallback for orc char type scan.
[...]
| spark.gluten.sql.pushAggregateThroughJoin.enabled | 🔄
Dynamic | false | Enables the push-aggregate-through-join
optimization in Gluten. When enabled, aggregate operators may be pushed below
joins during logical optimization and corresponding physical plans may be
rewritten to execute the aggregation earlier.
[...]
| spark.gluten.sql.pushAggregateThroughJoin.maxDepth | 🔄
Dynamic | 2147483647 | Maximum join traversal depth when applying the
push-aggregate-through-join optimization. A value of 1 allows pushing an
aggregate through a single join; larger values allow the rule to traverse and
push through multiple consecutive joins.
[...]
-| spark.gluten.sql.pushAggregateThroughJoin.partialMerge.enabled | 🔄
Dynamic | false | Enables a PartialMerge aggregate above each
aggregate pushed through a join.
[...]
+| spark.gluten.sql.pushAggregateThroughJoin.partialMerge.mode | 🔄
Dynamic | NONE | Controls where PartialMerge aggregates are
retained during aggregate pushdown: none retains none, top retains one at the
original partial aggregate position, and all retains one at every successful
join push.
[...]
| spark.gluten.sql.removeNativeWriteFilesSortAndProject | 🔄
Dynamic | true | When true, Gluten will remove the vanilla
Spark V1Writes added sort and project for velox backend.
[...]
| spark.gluten.sql.rewrite.dateTimestampComparison | 🔄
Dynamic | true | Rewrite the comparision between date and
timestamp to timestamp comparison.For example `from_unixtime(ts) > date` will
be rewritten to `ts > to_unixtime(date)`
[...]
| spark.gluten.sql.scan.detailedMetrics.enabled | 🔄
Dynamic | true | When true (default), Velox backend scan
operators register all detailed SQL metrics. When false, only essential scan
metrics are registered to reduce driver memory usage. Also enabled
automatically when spark.gluten.sql.debug is true. Does not affect the
ClickHouse backend.
[...]
diff --git
a/gluten-core/src/main/scala/org/apache/gluten/config/ConfigBuilder.scala
b/gluten-core/src/main/scala/org/apache/gluten/config/ConfigBuilder.scala
index 957f1dcab6..c44a9ae5d1 100644
--- a/gluten-core/src/main/scala/org/apache/gluten/config/ConfigBuilder.scala
+++ b/gluten-core/src/main/scala/org/apache/gluten/config/ConfigBuilder.scala
@@ -18,6 +18,7 @@ package org.apache.gluten.config
import org.apache.spark.network.util.{ByteUnit, JavaUtils}
+import java.util.Locale
import java.util.concurrent.TimeUnit
object BackendType extends Enumeration {
@@ -97,6 +98,10 @@ private[gluten] case class ConfigBuilder(key: String) {
new TypedConfigBuilder(this, identity)
}
+ def enumConf(e: Enumeration): TypedConfigBuilder[e.Value] = {
+ new TypedConfigBuilder(this, toEnum(_, e, key))
+ }
+
def timeConf(unit: TimeUnit): TypedConfigBuilder[Long] = {
new TypedConfigBuilder(this, timeFromString(_, unit), timeToString(_,
unit))
}
@@ -122,6 +127,16 @@ private[gluten] case class ConfigBuilder(key: String) {
}
private object ConfigHelpers {
+ def toEnum[E <: Enumeration](s: String, enumClass: E, key: String):
enumClass.Value = {
+ try {
+ enumClass.withName(s.trim.toUpperCase(Locale.ROOT))
+ } catch {
+ case _: NoSuchElementException =>
+ throw new IllegalArgumentException(
+ s"$key should be one of ${enumClass.values.mkString(", ")}, but was
$s")
+ }
+ }
+
def toNumber[T](s: String, converter: String => T, key: String, configType:
String): T = {
try {
converter(s.trim)
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
index 753bc865ab..6699c6eb22 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
@@ -30,6 +30,10 @@ import java.util.Locale
import scala.collection.JavaConverters._
import scala.collection.mutable
+object PartialMergeMode extends Enumeration {
+ val NONE, TOP, ALL = Value
+}
+
trait ShuffleWriterType {
val name: String
val requiresResizingShuffleInput: Boolean
@@ -158,8 +162,8 @@ class GlutenConfig(conf: SQLConf) extends
GlutenCoreConfig(conf) {
def pushAggregateThroughJoinMaxDepth: Int =
getConf(PUSH_AGGREGATE_THROUGH_JOIN_MAX_DEPTH)
- def pushAggregateThroughJoinPartialMergeEnabled: Boolean =
- getConf(PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_ENABLED)
+ def pushAggregateThroughJoinPartialMergeMode: PartialMergeMode.Value =
+ getConf(PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE)
def forceOrcCharTypeScanFallbackEnabled: Boolean =
getConf(VELOX_FORCE_ORC_CHAR_TYPE_SCAN_FALLBACK)
@@ -787,12 +791,14 @@ object GlutenConfig extends ConfigRegistry {
.checkValue(_ >= 1, "must be greater than or equal to 1.")
.createWithDefault(Int.MaxValue)
- val PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_ENABLED =
- buildConf("spark.gluten.sql.pushAggregateThroughJoin.partialMerge.enabled")
+ val PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE =
+ buildConf("spark.gluten.sql.pushAggregateThroughJoin.partialMerge.mode")
.doc(
- "Enables a PartialMerge aggregate above each aggregate pushed through
a join.")
- .booleanConf
- .createWithDefault(false)
+ "Controls where PartialMerge aggregates are retained during aggregate
pushdown: " +
+ "none retains none, top retains one at the original partial
aggregate position, " +
+ "and all retains one at every successful join push.")
+ .enumConf(PartialMergeMode)
+ .createWithDefault(PartialMergeMode.NONE)
val GLUTEN_SOFT_AFFINITY_ENABLED =
buildConf("spark.gluten.soft-affinity.enabled")
diff --git
a/gluten-substrait/src/main/scala/org/apache/gluten/extension/joinagg/PushAggregateThroughJoin.scala
b/gluten-substrait/src/main/scala/org/apache/gluten/extension/joinagg/PushAggregateThroughJoin.scala
index 14d652fddc..4f318f10c8 100644
---
a/gluten-substrait/src/main/scala/org/apache/gluten/extension/joinagg/PushAggregateThroughJoin.scala
+++
b/gluten-substrait/src/main/scala/org/apache/gluten/extension/joinagg/PushAggregateThroughJoin.scala
@@ -16,7 +16,7 @@
*/
package org.apache.gluten.extension.joinagg
-import org.apache.gluten.config.GlutenConfig
+import org.apache.gluten.config.{GlutenConfig, PartialMergeMode}
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.catalyst.expressions.{Alias, Attribute,
AttributeReference, AttributeSet, EqualTo, Expression, NamedExpression,
PredicateHelper}
@@ -113,9 +113,6 @@ case class PushAggregateThroughJoin(spark: SparkSession)
private def maxDepth: Int = GlutenConfig.get.pushAggregateThroughJoinMaxDepth
- private def partialMergeEnabled: Boolean =
- GlutenConfig.get.pushAggregateThroughJoinPartialMergeEnabled
-
private def splitAggregate(agg: Aggregate): Option[(Aggregate, Aggregate)] =
{
// Split is intentionally child-agnostic. It only rewrites:
//
@@ -177,6 +174,7 @@ case class PushAggregateThroughJoin(spark: SparkSession)
private def pushPartialWrapperAggregate(lowerPartialAgg: Aggregate):
LogicalPlan = {
// Push one join edge per iteration. This keeps the rewrite local and lets
`maxDepth` bound
// how far a pushed aggregate is allowed to travel through a multi-join
subtree.
+ val partialMergeMode =
GlutenConfig.get.pushAggregateThroughJoinPartialMergeMode
var current: LogicalPlan = lowerPartialAgg
var changed = true
var pushCount = 0
@@ -184,7 +182,10 @@ case class PushAggregateThroughJoin(spark: SparkSession)
changed = false
current = current.transformUp {
case partialAgg: Aggregate if
isPurePartialWrapperAggregate(partialAgg) =>
- val pushed = if (partialMergeEnabled) {
+ // The first successful push leaves a merge at the original partial
aggregate position.
+ val retainPartialMerge = partialMergeMode == PartialMergeMode.ALL ||
+ (partialMergeMode == PartialMergeMode.TOP && pushCount == 0)
+ val pushed = if (retainPartialMerge) {
val (partialMergeAgg, freshLowerPartialAgg) =
splitPartialAggregateForMerge(partialAgg)
pushOnce(freshLowerPartialAgg).map {
pushedPlan => partialMergeAgg.copy(child = pushedPlan)
diff --git
a/gluten-substrait/src/test/scala/org/apache/gluten/config/SparkConfigUtilSuite.scala
b/gluten-substrait/src/test/scala/org/apache/gluten/config/SparkConfigUtilSuite.scala
index 2bcfcb9e67..e39a98ccd6 100644
---
a/gluten-substrait/src/test/scala/org/apache/gluten/config/SparkConfigUtilSuite.scala
+++
b/gluten-substrait/src/test/scala/org/apache/gluten/config/SparkConfigUtilSuite.scala
@@ -24,6 +24,18 @@ import org.scalatest.funsuite.AnyFunSuiteLike
class SparkConfigUtilSuite extends AnyFunSuiteLike {
+ private object TestMode extends Enumeration {
+ val FIRST, LAST = Value
+ }
+
+ private object TestConfigs extends ConfigRegistry {
+ val mode = buildConf("spark.gluten.test.enum.mode")
+ .enumConf(TestMode)
+ .createWithDefault(TestMode.FIRST)
+
+ override def get: GlutenCoreConfig = GlutenCoreConfig.get
+ }
+
test("SparkConfigUtil.get and set methods") {
val conf = new SparkConf()
conf.set(GlutenConfig.SHUFFLE_WRITER_BUFFER_SIZE, Some(1024 * 1024))
@@ -43,4 +55,24 @@ class SparkConfigUtilSuite extends AnyFunSuiteLike {
assert(SparkConfigUtil.get(conf, GlutenConfig.SHUFFLE_WRITER_BUFFER_SIZE)
=== Some(1024 * 1024))
assert(SparkConfigUtil.get(conf, GlutenConfig.GLUTEN_LOAD_LIB_OS).isEmpty)
}
+
+ test("enum config through SQLConf") {
+ val conf = new SQLConf
+ val glutenConf = new GlutenCoreConfig(conf)
+ val entry = TestConfigs.mode
+
+ assert(glutenConf.getConf(entry) == TestMode.FIRST)
+ TestMode.values.foreach {
+ mode =>
+ conf.setConfString(entry.key,
mode.toString.toLowerCase(java.util.Locale.ROOT))
+ assert(glutenConf.getConf(entry) == mode)
+ }
+ conf.unsetConf(entry.key)
+ assert(glutenConf.getConf(entry) == TestMode.FIRST)
+
+ val error = intercept[IllegalArgumentException] {
+ conf.setConfString(entry.key, "invalid")
+ }
+ assert(error.getMessage.contains(entry.key))
+ }
}
diff --git
a/gluten-substrait/src/test/scala/org/apache/gluten/execution/PushAggregateThroughJoinSuite.scala
b/gluten-substrait/src/test/scala/org/apache/gluten/execution/PushAggregateThroughJoinSuite.scala
index 44e654e790..9f204ba688 100644
---
a/gluten-substrait/src/test/scala/org/apache/gluten/execution/PushAggregateThroughJoinSuite.scala
+++
b/gluten-substrait/src/test/scala/org/apache/gluten/execution/PushAggregateThroughJoinSuite.scala
@@ -23,8 +23,7 @@ import
org.apache.gluten.extension.joinagg.PushAggregateThroughJoin
import org.apache.spark.SparkConf
import org.apache.spark.sql.Row
import org.apache.spark.sql.catalyst.plans.PlanTest
-import org.apache.spark.sql.catalyst.plans.logical.Aggregate
-import org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
+import org.apache.spark.sql.catalyst.plans.logical.{Aggregate, LogicalPlan}
import org.apache.spark.sql.catalyst.rules.Rule
import org.apache.spark.sql.execution.{SparkPlan, SparkStrategy}
import org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec
@@ -39,6 +38,8 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
private case class PushdownCase(inputSql: String)
+ private case class ExpectedAggCounts(none: Int, top: Int, all: Int)
+
override protected def sparkConf: SparkConf = {
// Avoid Janino projection codegen here because Spark 4's
QueryExecutionErrors
// has Arrow-typed methods, which breaks test runs as arrow-vector is
excluded.
@@ -93,89 +94,99 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
testCase: PushdownCase,
maxDepth: Int,
expectedPushCount: Int,
- expectedAggCount: Int,
- partialMergeEnabled: Boolean): Unit = {
- withSQLConf(
- GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_ENABLED.key -> "true",
- GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_MAX_DEPTH.key ->
maxDepth.toString,
- GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_ENABLED.key ->
- partialMergeEnabled.toString
- ) {
- val (withoutRuleRows, withoutRuleLogicalPlan, withoutRulePhysicalPlan) =
- withExtraPlanning(Nil, Nil) {
- val df = spark.sql(testCase.inputSql)
- (
- df.collect().toSeq.sortBy(_.toString()),
- df.queryExecution.optimizedPlan,
- finalExecutedPlan(df.queryExecution.executedPlan)
- )
- }
-
- val (withRuleRows, withRuleLogicalPlan, withRulePhysicalPlan) =
- withExtraPlanning(Seq(joinAggregateRule), Nil) {
- joinAggregateRule.resetSuccessfulPushCount()
- val df = spark.sql(testCase.inputSql)
- val withRuleRows = df.collect().toSeq.sortBy(_.toString())
- val withRulePlan = df.queryExecution.optimizedPlan
- val withRulePhysicalPlan =
finalExecutedPlan(df.queryExecution.executedPlan)
- val aggregateNodeCount = withRulePlan.collect { case _: Aggregate =>
1 }.size
- val nodesWithMissingInput = withRulePlan.collect {
- case p if p.missingInput.nonEmpty => p
+ expectedAggCounts: ExpectedAggCounts): Unit = {
+ val modes = Seq(
+ "none" -> expectedAggCounts.none,
+ "top" -> expectedAggCounts.top,
+ "all" -> expectedAggCounts.all)
+ modes.foreach {
+ case (partialMergeMode, expectedAggCount) =>
+ withClue(s"partialMergeMode=$partialMergeMode, maxDepth=$maxDepth: ") {
+ withSQLConf(
+ GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_ENABLED.key -> "true",
+ GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_MAX_DEPTH.key ->
maxDepth.toString,
+ GlutenConfig.PUSH_AGGREGATE_THROUGH_JOIN_PARTIAL_MERGE_MODE.key ->
+ partialMergeMode
+ ) {
+ val (withoutRuleRows, withoutRuleLogicalPlan,
withoutRulePhysicalPlan) =
+ withExtraPlanning(Nil, Nil) {
+ val df = spark.sql(testCase.inputSql)
+ (
+ df.collect().toSeq.sortBy(_.toString()),
+ df.queryExecution.optimizedPlan,
+ finalExecutedPlan(df.queryExecution.executedPlan)
+ )
+ }
+
+ val (withRuleRows, withRuleLogicalPlan, withRulePhysicalPlan) =
+ withExtraPlanning(Seq(joinAggregateRule), Nil) {
+ joinAggregateRule.resetSuccessfulPushCount()
+ val df = spark.sql(testCase.inputSql)
+ val withRuleRows = df.collect().toSeq.sortBy(_.toString())
+ val withRulePlan = df.queryExecution.optimizedPlan
+ val withRulePhysicalPlan =
finalExecutedPlan(df.queryExecution.executedPlan)
+ val aggregateNodeCount = withRulePlan.collect { case _:
Aggregate => 1 }.size
+ val nodesWithMissingInput = withRulePlan.collect {
+ case p if p.missingInput.nonEmpty => p
+ }
+
+ assert(
+ withRulePlan.resolved,
+ s"Optimized plan unresolved:\n${withRulePlan.treeString}\n" +
+ s"MissingInput=${withRulePlan.missingInput}")
+ assert(
+ nodesWithMissingInput.isEmpty,
+ s"Plan has missing input:\n${nodesWithMissingInput
+ .map(_.treeString)
+ .mkString("\n---\n")}")
+ assert(joinAggregateRule.getSuccessfulPushCount ==
expectedPushCount)
+ assert(aggregateNodeCount == expectedAggCount)
+ (withRuleRows, withRulePlan, withRulePhysicalPlan)
+ }
+
+ val (
+ withRuleAndStrategyRows,
+ withRuleAndStrategyLogicalPlan,
+ withRuleAndStrategyPhysicalPlan) =
+ withExtraPlanning(Seq(joinAggregateRule),
Seq(ImplementJoinAggregate(spark))) {
+ joinAggregateRule.resetSuccessfulPushCount()
+ val df = spark.sql(testCase.inputSql)
+ (
+ df.collect().toSeq.sortBy(_.toString()),
+ df.queryExecution.optimizedPlan,
+ finalExecutedPlan(df.queryExecution.executedPlan)
+ )
+ }
+
+ if (debugMode) {
+ // scalastyle:off println
+ println("=== Optimized Plan Before (without
PushJoinAggregatePreAggregation) ===")
+ println(withoutRuleLogicalPlan.treeString)
+ println("=== Optimized Plan After (with
PushJoinAggregatePreAggregation) ===")
+ println(withRuleLogicalPlan.treeString)
+ println(
+ "=== Optimized Plan After (with
PushJoinAggregatePreAggregation and strategy) ===")
+ println(withRuleAndStrategyLogicalPlan.treeString)
+ println("=== Physical Plan Before (without
PushJoinAggregatePreAggregation) ===")
+ println(withoutRulePhysicalPlan.treeString)
+ println("=== Physical Plan After (with
PushJoinAggregatePreAggregation only) ===")
+ println(withRulePhysicalPlan.treeString)
+ println(
+ "=== Physical Plan After (with PushJoinAggregatePreAggregation
and strategy) ===")
+ println(withRuleAndStrategyPhysicalPlan.treeString)
+ println("=== Result Before (without
PushJoinAggregatePreAggregation) ===")
+ println(withoutRuleRows.mkString("\n"))
+ println("=== Result After (with PushJoinAggregatePreAggregation
only) ===")
+ println(withRuleRows.mkString("\n"))
+ println("=== Result After (with PushJoinAggregatePreAggregation
and strategy) ===")
+ println(withRuleAndStrategyRows.mkString("\n"))
+ // scalastyle:on println
+ }
+
+ assertRowsEqual(withRuleRows, withoutRuleRows)
+ assertRowsEqual(withRuleAndStrategyRows, withoutRuleRows)
}
-
- assert(
- withRulePlan.resolved,
- s"Optimized plan unresolved:\n${withRulePlan.treeString}\n" +
- s"MissingInput=${withRulePlan.missingInput}")
- assert(
- nodesWithMissingInput.isEmpty,
- s"Plan has missing input:\n${nodesWithMissingInput
- .map(_.treeString)
- .mkString("\n---\n")}")
- assert(joinAggregateRule.getSuccessfulPushCount == expectedPushCount)
- assert(aggregateNodeCount == expectedAggCount)
- (withRuleRows, withRulePlan, withRulePhysicalPlan)
- }
-
- val (
- withRuleAndStrategyRows,
- withRuleAndStrategyLogicalPlan,
- withRuleAndStrategyPhysicalPlan) =
- withExtraPlanning(Seq(joinAggregateRule),
Seq(ImplementJoinAggregate(spark))) {
- joinAggregateRule.resetSuccessfulPushCount()
- val df = spark.sql(testCase.inputSql)
- (
- df.collect().toSeq.sortBy(_.toString()),
- df.queryExecution.optimizedPlan,
- finalExecutedPlan(df.queryExecution.executedPlan)
- )
}
-
- if (debugMode) {
- // scalastyle:off println
- println("=== Optimized Plan Before (without
PushJoinAggregatePreAggregation) ===")
- println(withoutRuleLogicalPlan.treeString)
- println("=== Optimized Plan After (with
PushJoinAggregatePreAggregation) ===")
- println(withRuleLogicalPlan.treeString)
- println("=== Optimized Plan After (with
PushJoinAggregatePreAggregation and strategy) ===")
- println(withRuleAndStrategyLogicalPlan.treeString)
- println("=== Physical Plan Before (without
PushJoinAggregatePreAggregation) ===")
- println(withoutRulePhysicalPlan.treeString)
- println("=== Physical Plan After (with PushJoinAggregatePreAggregation
only) ===")
- println(withRulePhysicalPlan.treeString)
- println("=== Physical Plan After (with PushJoinAggregatePreAggregation
and strategy) ===")
- println(withRuleAndStrategyPhysicalPlan.treeString)
- println("=== Result Before (without PushJoinAggregatePreAggregation)
===")
- println(withoutRuleRows.mkString("\n"))
- println("=== Result After (with PushJoinAggregatePreAggregation only)
===")
- println(withRuleRows.mkString("\n"))
- println("=== Result After (with PushJoinAggregatePreAggregation and
strategy) ===")
- println(withRuleAndStrategyRows.mkString("\n"))
- // scalastyle:on println
- }
-
- assertRowsEqual(withRuleRows, withoutRuleRows)
- assertRowsEqual(withRuleAndStrategyRows, withoutRuleRows)
}
}
@@ -224,14 +235,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
}
test("pre-aggregate store_sales for sum") {
@@ -249,14 +253,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate store_sales for avg") {
@@ -274,14 +271,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate store_sales for sum on fact table") {
@@ -299,14 +289,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate store_sales for avg on fact table") {
@@ -324,14 +307,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate store_sales for sum on three-way join") {
@@ -351,14 +327,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
}
test("pre-aggregate store_sales for sum and avg on different fact columns on
three-way join") {
@@ -379,14 +348,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
}
test("pre-aggregate store_sales for sum and avg on same fact column on
three-way join") {
@@ -407,14 +369,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
}
test("pre-aggregate store_sales by i_item_desc") {
@@ -432,14 +387,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate store_sales by substr(i_item_desc, 3), 3 ways") {
@@ -459,14 +407,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
}
test("pre-aggregate store_sales for sum with item filter") {
@@ -483,14 +424,7 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
test("pre-aggregate three-way joins independently under union all") {
@@ -533,38 +467,17 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = 1,
expectedPushCount = 3,
- expectedAggCount = 6,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = 1,
- expectedPushCount = 3,
- expectedAggCount = 9,
- partialMergeEnabled = true)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = 2,
- expectedPushCount = 6,
- expectedAggCount = 6,
- partialMergeEnabled = false)
+ expectedAggCounts = ExpectedAggCounts(none = 6, top = 9, all = 9))
runCaseWithMaxDepth(
pushdownCase,
maxDepth = 2,
expectedPushCount = 6,
- expectedAggCount = 12,
- partialMergeEnabled = true)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 6,
- expectedAggCount = 6,
- partialMergeEnabled = false)
+ expectedAggCounts = ExpectedAggCounts(none = 6, top = 9, all = 12))
runCaseWithMaxDepth(
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 6,
- expectedAggCount = 12,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 6, top = 9, all = 12))
}
test("pre-aggregate store_sales for sum on three-way join with maxDepth=1 /
maxDepth=2") {
@@ -584,38 +497,32 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = 1,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = 1,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
runCaseWithMaxDepth(
pushdownCase,
maxDepth = 2,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = 2,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
runCaseWithMaxDepth(
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 2,
- expectedAggCount = 2,
- partialMergeEnabled = false)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 4))
+ }
+
+ test("partial merge is not retained when no side can be pushed") {
+ val pushdownCase = PushdownCase(
+ inputSql = """
+ |SELECT sum(ss_sales_price), sum(i_category_id)
+ |FROM store_sales
+ |JOIN item ON ss_item_sk = i_item_sk
+ |""".stripMargin
+ )
runCaseWithMaxDepth(
pushdownCase,
maxDepth = Int.MaxValue,
- expectedPushCount = 2,
- expectedAggCount = 4,
- partialMergeEnabled = true)
+ expectedPushCount = 0,
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 2, all = 2))
}
test("pre-aggregate with filter inside inner equi-join") {
@@ -633,13 +540,6 @@ class PushAggregateThroughJoinSuite extends PlanTest with
SharedSparkSession {
pushdownCase,
maxDepth = Int.MaxValue,
expectedPushCount = 1,
- expectedAggCount = 2,
- partialMergeEnabled = false)
- runCaseWithMaxDepth(
- pushdownCase,
- maxDepth = Int.MaxValue,
- expectedPushCount = 1,
- expectedAggCount = 3,
- partialMergeEnabled = true)
+ expectedAggCounts = ExpectedAggCounts(none = 2, top = 3, all = 3))
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]