This is an automated email from the ASF dual-hosted git repository.

uros-b pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new b724fd87f4f2 [SPARK-58420][SQL] Normalize map values in distinct 
aggregates
b724fd87f4f2 is described below

commit b724fd87f4f2d2e0d34a665f84b90edf5da16ef0
Author: Vladimir Golubev <[email protected]>
AuthorDate: Fri Aug 7 09:04:42 2026 +0000

    [SPARK-58420][SQL] Normalize map values in distinct aggregates
    
    ### What changes were proposed in this pull request?
    
    Normalize map-typed arguments of distinct aggregate expressions with 
`MapSort`. Complex distinct
    arguments are projected to attributes before normalization, and the 
existing grouping aliases are
    reused when applicable. The rule retains its plan-wide fast path and has a 
default-on internal
    killswitch.
    
    ### Why are the changes needed?
    
    Map equality is independent of entry order, but distinct aggregation 
currently compares the
    physical map representation. Equivalent maps with different insertion 
orders are therefore treated
    as different values.
    
    `InsertMapSortInGroupingExpressions` was introduced by
    
[SPARK-47430](https://github.com/apache/spark/commit/d57164a5cab92e8821563e776c19a424721adce3)
    to normalize `MapType` grouping keys. This PR extends the same 
normalization to distinct aggregate
    arguments.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes. `COUNT(DISTINCT map_column)` now treats maps containing the same 
entries in different orders as
    the same value. For example:
    
    ```sql
    SELECT count(DISTINCT m)
    FROM VALUES
      (map('a', 1, 'b', 2)),
      (map('b', 2, 'a', 1)),
      (map('a', 3)) AS t(m);
    ```
    
    Before this change, the query returns `3`. After this change, it returns 
`2`. Distinct aggregates
    that return their argument, such as `COLLECT_LIST(DISTINCT m)`, now return 
maps whose entries are
    sorted by key.
    
    Normalization is scoped to each use of the map. In a query containing both
    `MAX(map_values(m)[0])` and `COUNT(DISTINCT m)`, the non-distinct aggregate 
sees the original map
    while the distinct aggregate sees the sorted map. Existing grouping-key 
behavior is unchanged: a
    normalized map grouping key is also the grouped value exposed to the select 
list.
    
    Setting `spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled` to 
`false` disables the
    distinct-specific normalization and restores the previous behavior; 
grouping-key normalization is
    unchanged.
    
    ### How was this patch tested?
    
    Added a dedicated SQL golden test covering `COUNT(DISTINCT map)`, returned
    `COLLECT_LIST(DISTINCT map)` values, multiple and multi-argument distinct 
aggregates, grouped
    aggregation, aggregate filters, `ORDER BY`, `HAVING`, nested maps, null and 
empty maps, grouping
    sets, and representative cases with the killswitch disabled.
    
    ```bash
    build/sbt 'sql/testOnly org.apache.spark.sql.SQLQueryTestSuite -- -z 
distinct-map-aggregates.sql'
    ```
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: OpenAI Codex (GPT-5)
    
    Closes #57629 from vladimirg-db/fix-count-distinct-map.
    
    Authored-by: Vladimir Golubev <[email protected]>
    Signed-off-by: Uros Bojanic <[email protected]>
    (cherry picked from commit f649423aa3574ec62af637392c713a3bc3daed9b)
    Signed-off-by: Uros Bojanic <[email protected]>
---
 docs/sql-migration-guide.md                        |   1 +
 .../optimizer/InsertMapSortExpression.scala        | 140 ++++++--
 .../spark/sql/catalyst/optimizer/Optimizer.scala   |   8 +-
 .../org/apache/spark/sql/internal/SQLConf.scala    |  11 +
 .../optimizer/InsertMapSortInAggregateSuite.scala  | 117 +++++++
 .../distinct-map-aggregates.sql.out                | 363 +++++++++++++++++++++
 .../sql-tests/inputs/distinct-map-aggregates.sql   | 102 ++++++
 .../results/distinct-map-aggregates.sql.out        | 265 +++++++++++++++
 8 files changed, 977 insertions(+), 30 deletions(-)

diff --git a/docs/sql-migration-guide.md b/docs/sql-migration-guide.md
index b267a5d6fa45..efe999bb036c 100644
--- a/docs/sql-migration-guide.md
+++ b/docs/sql-migration-guide.md
@@ -28,6 +28,7 @@ license: |
 
 ## Upgrading from Spark SQL 4.2 to 4.3
 
+- Since Spark 4.3, map-typed arguments to distinct aggregates are normalized 
by key order. As a result, `COUNT(DISTINCT m)` treats maps with the same 
entries in different orders as the same value, and aggregates that return 
distinct input values, such as `COLLECT_LIST(DISTINCT m)`, return maps sorted 
by key.
 - Since Spark 4.3, [ASOF JOIN](sql-ref-syntax-qry-select-asof-join.html) is 
available as an opt-in SQL feature gated by `spark.sql.join.asofJoin.enabled` 
(default `false`). When disabled, `ASOF JOIN` fails at parse time with 
`UNSUPPORTED_FEATURE.ASOF_JOIN`.
 - Since Spark 4.3, zero-length files are skipped during Parquet schema 
inference instead of failing with a `FAILED_READ_FILE.CANNOT_READ_FILE_FOOTER` 
error.
 - Since Spark 4.3, metrics produced by `Dataset.observe` include values from 
only the last successful task attempts instead of aggregating values from all 
attempts. To restore the previous behavior, set 
`spark.sql.legacy.observeMetricsAggregateAllAttempts` to `true`.
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortExpression.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortExpression.scala
index 9e613c54a49b..e2043e06186f 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortExpression.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortExpression.scala
@@ -17,33 +17,49 @@
 
 package org.apache.spark.sql.catalyst.optimizer
 
+import scala.collection.immutable.VectorMap
 import scala.collection.mutable
 
-import org.apache.spark.sql.catalyst.expressions.{Alias, ArrayTransform, 
CreateNamedStruct, Expression, GetStructField, If, IsNull, LambdaFunction, 
Literal, MapFromArrays, MapKeys, MapSort, MapValues, NamedExpression, 
NamedLambdaVariable}
+import org.apache.spark.sql.catalyst.expressions.{Alias, ArrayTransform, 
Attribute, CreateNamedStruct, Expression, GetStructField, If, IsNull, 
LambdaFunction, Literal, MapFromArrays, MapKeys, MapSort, MapValues, 
NamedExpression, NamedLambdaVariable}
+import 
org.apache.spark.sql.catalyst.expressions.aggregate.{AggregateExpression, 
AggregateFunction}
 import org.apache.spark.sql.catalyst.plans.logical.{Aggregate, LogicalPlan, 
Project, RepartitionByExpression}
 import org.apache.spark.sql.catalyst.rules.Rule
 import org.apache.spark.sql.catalyst.trees.TreePattern.{AGGREGATE, 
REPARTITION_OPERATION}
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types.{ArrayType, MapType, StructType}
 import org.apache.spark.util.ArrayImplicits.SparkArrayOps
 
 /**
- * Adds [[MapSort]] to [[Aggregate]] expressions containing map columns,
- * as the key/value pairs need to be in the correct order before grouping:
+ * Adds [[MapSort]] to grouping expressions and distinct aggregate arguments 
that contain maps,
+ * ensuring key/value pairs have a consistent order before aggregation:
  *
  * SELECT map_column, COUNT(*) FROM TABLE GROUP BY map_column =>
  * SELECT _groupingmapsort as map_column, COUNT(*) FROM (
  *   SELECT map_sort(map_column) as _groupingmapsort FROM TABLE
  * ) GROUP BY _groupingmapsort
+ *
+ * SELECT COUNT(DISTINCT map_column) FROM TABLE =>
+ * SELECT COUNT(DISTINCT _distinctmapsort) FROM (
+ *   SELECT map_sort(map_column) as _distinctmapsort FROM TABLE
+ * )
+ *
+ * Distinct arguments are normalized here instead of in 
[[RewriteDistinctAggregates]] because a
+ * single distinct group without a filter bypasses that rule and is handled by 
the physical planner.
  */
-object InsertMapSortInGroupingExpressions extends Rule[LogicalPlan] {
+object InsertMapSortInAggregate extends Rule[LogicalPlan] {
   import InsertMapSortExpression._
 
   override def apply(plan: LogicalPlan): LogicalPlan = {
     if (!plan.containsPattern(AGGREGATE)) {
       return plan
     }
+    val normalizeDistinctAggregates =
+      conf.getConf(SQLConf.INSERT_MAP_SORT_IN_DISTINCT_AGGREGATES_ENABLED)
     val shouldRewrite = plan.exists {
-      case agg: Aggregate if 
agg.groupingExpressions.exists(mapTypeExistsRecursively) => true
+      case agg: Aggregate =>
+        agg.groupingExpressions.exists(mapTypeExistsRecursively) ||
+          (normalizeDistinctAggregates &&
+            
distinctAggregateChildren(agg.aggregateExpressions).exists(mapTypeExistsRecursively))
       case _ => false
     }
     if (!shouldRewrite) {
@@ -52,32 +68,104 @@ object InsertMapSortInGroupingExpressions extends 
Rule[LogicalPlan] {
 
     plan transformUpWithNewOutput {
       case agg @ Aggregate(groupingExprs, aggregateExpressions, child, hint) =>
-        val exprToMapSort = new mutable.HashMap[Expression, NamedExpression]
-        val newGroupingKeys = groupingExprs.map { expr =>
-          val inserted = insertMapSortRecursively(expr)
-          if (expr.ne(inserted)) {
-            exprToMapSort.getOrElseUpdate(
-              expr.canonicalized,
-              Alias(inserted, "_groupingmapsort")()
-            ).toAttribute
+        val distinctExpressions = if (normalizeDistinctAggregates) {
+          distinctAggregateChildren(aggregateExpressions)
+        } else {
+          Seq.empty
+        }
+        val expressionsToNormalize = groupingExprs ++ distinctExpressions
+        if (!expressionsToNormalize.exists(mapTypeExistsRecursively)) {
+          agg -> Nil
+        } else {
+          val groupingMapSortAliases = insertMapSortInExpressions(
+            expressions = groupingExprs,
+            aliasName = "_groupingmapsort")
+          val distinctInputAliases = 
createDistinctInputAliases(distinctExpressions)
+          val distinctMapSortAliases = insertMapSortInExpressions(
+            expressions = distinctExpressions,
+            aliasName = "_distinctmapsort",
+            reusableAliases = groupingMapSortAliases,
+            inputAliases = distinctInputAliases)
+          val newGroupingKeys = groupingExprs.map { expr =>
+            
groupingMapSortAliases.get(expr.canonicalized).map(_.toAttribute).getOrElse(expr)
+          }
+          val newAggregateExprs = aggregateExpressions.map {
+            case named if groupingMapSortAliases.contains(named.canonicalized) 
=>
+              // If we replace the top-level named expr, then should add back 
the original name
+              
groupingMapSortAliases(named.canonicalized).toAttribute.withName(named.name)
+            case other =>
+              // This must be top-down so distinct arguments are normalized 
before their children.
+              // Continuing downward also substitutes grouping aliases in 
other arguments.
+              other.transformDown {
+                case ae: AggregateExpression if ae.isDistinct =>
+                  ae.copy(aggregateFunction = 
ae.aggregateFunction.withNewChildren(
+                    ae.aggregateFunction.children.map { child =>
+                      distinctMapSortAliases.get(child.canonicalized)
+                        .map(_.toAttribute).getOrElse(child)
+                    }).asInstanceOf[AggregateFunction])
+                case e =>
+                  
groupingMapSortAliases.get(e.canonicalized).map(_.toAttribute).getOrElse(e)
+              }.asInstanceOf[NamedExpression]
+          }
+          val distinctInput = if (distinctInputAliases.nonEmpty) {
+            // Project complex inputs once because recursive normalization can 
reference them
+            // repeatedly. The operator optimization batch later removes these 
temporary projection
+            // layers with CollapseProject and ColumnPruning.
+            Project(child.output ++ distinctInputAliases.values, child)
           } else {
-            expr
+            child
           }
+          val newChild = Project(
+            child.output ++ (groupingMapSortAliases ++ 
distinctMapSortAliases).values,
+            distinctInput)
+          val newAgg = Aggregate(newGroupingKeys, newAggregateExprs, newChild, 
hint)
+          newAgg -> agg.output.zip(newAgg.output)
         }
-        val newAggregateExprs = aggregateExpressions.map {
-          case named if exprToMapSort.contains(named.canonicalized) =>
-            // If we replace the top-level named expr, then should add back 
the original name
-            exprToMapSort(named.canonicalized).toAttribute.withName(named.name)
-          case other =>
-            other.transformUp {
-              case e => 
exprToMapSort.get(e.canonicalized).map(_.toAttribute).getOrElse(e)
-            }.asInstanceOf[NamedExpression]
-        }
-        val newChild = Project(child.output ++ exprToMapSort.values, child)
-        val newAgg = Aggregate(newGroupingKeys, newAggregateExprs, newChild, 
hint)
-        newAgg -> agg.output.zip(newAgg.output)
     }
   }
+
+  private def distinctAggregateChildren(
+      aggregateExpressions: Seq[NamedExpression]): Seq[Expression] = {
+    aggregateExpressions
+      .flatMap(_.collect {
+        case ae: AggregateExpression if ae.isDistinct => ae
+      })
+      .flatMap(_.aggregateFunction.children)
+  }
+
+  private def createDistinctInputAliases(
+      expressions: Seq[Expression]): VectorMap[Expression, NamedExpression] = {
+    val aliases = new mutable.LinkedHashMap[Expression, NamedExpression]
+    expressions.foreach {
+      case _: Attribute =>
+      case expr if mapTypeExistsRecursively(expr) =>
+        aliases.getOrElseUpdate(
+          expr.canonicalized,
+          Alias(expr, "_distinctaggregateexpression")())
+      case _ =>
+    }
+    VectorMap.from(aliases)
+  }
+
+  private def insertMapSortInExpressions(
+      expressions: Seq[Expression],
+      aliasName: String,
+      reusableAliases: Map[Expression, NamedExpression] = Map.empty,
+      inputAliases: Map[Expression, NamedExpression] = Map.empty)
+    : VectorMap[Expression, NamedExpression] = {
+    val aliases = new mutable.LinkedHashMap[Expression, NamedExpression]
+    expressions.foreach { expr =>
+      val canonicalized = expr.canonicalized
+      val input = 
inputAliases.get(canonicalized).map(_.toAttribute).getOrElse(expr)
+      val inserted = insertMapSortRecursively(input)
+      if (input.ne(inserted)) {
+        aliases.getOrElseUpdate(
+          canonicalized,
+          reusableAliases.getOrElse(canonicalized, Alias(inserted, 
aliasName)()))
+      }
+    }
+    VectorMap.from(aliases)
+  }
 }
 
 /**
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
index 25edd59412e9..b71ee3485de4 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala
@@ -355,10 +355,10 @@ abstract class Optimizer(catalogManager: CatalogManager)
       NormalizeFloatingNumbers,
       RewriteNonCorrelatedExists,
       PullOutGroupingExpressions,
-      // Put `InsertMapSortInGroupingExpressions` after 
`PullOutGroupingExpressions`,
-      // so the grouping keys can only be attribute and literal which makes
-      // `InsertMapSortInGroupingExpressions` easy to insert `MapSort`.
-      InsertMapSortInGroupingExpressions,
+      // Put `InsertMapSortInAggregate` after `PullOutGroupingExpressions`,
+      // so grouping keys are attributes or literals. The rule also projects 
complex distinct
+      // aggregate arguments before inserting `MapSort`.
+      InsertMapSortInAggregate,
       InsertMapSortInRepartitionExpressions,
       ComputeCurrentTime,
       ReplaceCurrentLike(catalogManager),
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
index 16175a1a8c18..4f1643d4725b 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/internal/SQLConf.scala
@@ -3160,6 +3160,17 @@ object SQLConf {
     .booleanConf
     .createWithDefault(true)
 
+  val INSERT_MAP_SORT_IN_DISTINCT_AGGREGATES_ENABLED =
+    buildConf("spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled")
+      .internal()
+      .doc("When true, map-typed arguments of distinct aggregates are 
normalized with MapSort. " +
+        "When false, MapSort is not added solely for distinct aggregate 
arguments; arguments " +
+        "that are also grouping expressions remain normalized.")
+      .version("4.3.0")
+      .withBindingPolicy(ConfigBindingPolicy.NOT_APPLICABLE)
+      .booleanConf
+      .createWithDefault(true)
+
   val OPTIMIZE_EXPAND_RATIO =
     buildConf("spark.sql.optimizer.optimizeExpandRatio")
       .internal()
diff --git 
a/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortInAggregateSuite.scala
 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortInAggregateSuite.scala
new file mode 100644
index 000000000000..fa2340318cbc
--- /dev/null
+++ 
b/sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/optimizer/InsertMapSortInAggregateSuite.scala
@@ -0,0 +1,117 @@
+/*
+ * 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.catalyst.optimizer
+
+import org.apache.spark.sql.catalyst.dsl.expressions._
+import org.apache.spark.sql.catalyst.expressions.{Alias, MapSort}
+import org.apache.spark.sql.catalyst.expressions.aggregate.AggregateExpression
+import org.apache.spark.sql.catalyst.plans.PlanTest
+import org.apache.spark.sql.catalyst.plans.logical.{
+  Aggregate, LocalRelation, LogicalPlan, Project, Union}
+import org.apache.spark.sql.internal.SQLConf
+import org.apache.spark.sql.types.{IntegerType, StringType}
+
+class InsertMapSortInAggregateSuite extends PlanTest {
+  private val input = LocalRelation(Symbol("m").map(StringType, IntegerType))
+  private val mapAttribute = input.output.head
+
+  private def aliasesNamed(plan: LogicalPlan, name: String): Seq[Alias] = {
+    plan.flatMap { node =>
+      node.expressions.flatMap(_.collect {
+        case alias @ Alias(_, aliasName) if aliasName == name => alias
+      })
+    }
+  }
+
+  test("reuse map sort when a grouping key is also a distinct argument") {
+    val plan = Aggregate(
+      Seq(mapAttribute),
+      Seq(mapAttribute, countDistinct(mapAttribute).as("count")),
+      input)
+    val rewritten = InsertMapSortInAggregate(plan)
+    val groupingAliases = aliasesNamed(rewritten, "_groupingmapsort")
+
+    assert(groupingAliases.size == 1)
+    assert(groupingAliases.head.child.isInstanceOf[MapSort])
+    rewritten match {
+      case Aggregate(Seq(groupingExpression), aggregateExpressions, _: 
Project, _) =>
+        
assert(groupingExpression.semanticEquals(groupingAliases.head.toAttribute))
+        val distinctChildren = aggregateExpressions.flatMap(_.collect {
+          case expression: AggregateExpression if expression.isDistinct =>
+            expression.aggregateFunction.children
+        }).flatten
+        assert(distinctChildren.size == 1)
+        
assert(distinctChildren.head.semanticEquals(groupingAliases.head.toAttribute))
+      case other =>
+        fail(s"Unexpected plan:\n$other")
+    }
+  }
+
+  test("project complex distinct arguments only when needed") {
+    val attributePlan = Aggregate(
+      Nil,
+      Seq(countDistinct(mapAttribute).as("count")),
+      input)
+    val complexPlan = Aggregate(
+      Nil,
+      Seq(countDistinct(namedStruct("m", mapAttribute)).as("count")),
+      input)
+
+    val rewrittenAttributePlan = InsertMapSortInAggregate(attributePlan)
+    assert(rewrittenAttributePlan.collect { case _: Project => 1 }.size == 1)
+    assert(aliasesNamed(rewrittenAttributePlan, 
"_distinctaggregateexpression").isEmpty)
+    assert(aliasesNamed(rewrittenAttributePlan, "_distinctmapsort").size == 1)
+
+    val rewrittenComplexPlan = InsertMapSortInAggregate(complexPlan)
+    assert(rewrittenComplexPlan.collect { case _: Project => 1 }.size == 2)
+    assert(aliasesNamed(rewrittenComplexPlan, 
"_distinctaggregateexpression").size == 1)
+    assert(aliasesNamed(rewrittenComplexPlan, "_distinctmapsort").size == 1)
+  }
+
+  test("skip distinct argument normalization when disabled") {
+    val plan = Aggregate(
+      Nil,
+      Seq(countDistinct(mapAttribute).as("count")),
+      input)
+
+    withSQLConf(SQLConf.INSERT_MAP_SORT_IN_DISTINCT_AGGREGATES_ENABLED.key -> 
"false") {
+      comparePlans(InsertMapSortInAggregate(plan), plan)
+    }
+  }
+
+  test("leave map-free aggregates untouched when another aggregate needs 
rewriting") {
+    val scalarInput = LocalRelation(Symbol("i").int)
+    val scalarAggregate = Aggregate(
+      Nil,
+      Seq(count(scalarInput.output.head).as("count")),
+      scalarInput)
+    val mapAggregate = Aggregate(
+      Nil,
+      Seq(countDistinct(mapAttribute).as("count")),
+      input)
+
+    InsertMapSortInAggregate(Union(Seq(scalarAggregate, mapAggregate))) match {
+      case Union(Seq(rewrittenScalarAggregate, rewrittenMapAggregate), _, _) =>
+        comparePlans(rewrittenScalarAggregate, scalarAggregate)
+        assert(rewrittenScalarAggregate.collect { case _: Project => 1 
}.isEmpty)
+        assert(rewrittenMapAggregate.collect { case _: Project => 1 }.size == 
1)
+      case other =>
+        fail(s"Unexpected plan:\n$other")
+    }
+  }
+}
diff --git 
a/sql/core/src/test/resources/sql-tests/analyzer-results/distinct-map-aggregates.sql.out
 
b/sql/core/src/test/resources/sql-tests/analyzer-results/distinct-map-aggregates.sql.out
new file mode 100644
index 000000000000..50f9153b7e3f
--- /dev/null
+++ 
b/sql/core/src/test/resources/sql-tests/analyzer-results/distinct-map-aggregates.sql.out
@@ -0,0 +1,363 @@
+-- Automatically generated by SQLQueryTestSuite
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW distinct_map_data AS SELECT * FROM VALUES
+  (2, map('a', 1, 'b', 2), 1, true),
+  (2, map('b', 2, 'a', 1), 1, true),
+  (1, map('a', 1, 'b', 2), 1, true),
+  (1, map('a', 3), 2, false)
+AS distinct_map_data(g, m, id, should_keep)
+-- !query analysis
+CreateViewCommand `distinct_map_data`, SELECT * FROM VALUES
+  (2, map('a', 1, 'b', 2), 1, true),
+  (2, map('b', 2, 'a', 1), 1, true),
+  (1, map('a', 1, 'b', 2), 1, true),
+  (1, map('a', 3), 2, false)
+AS distinct_map_data(g, m, id, should_keep), false, true, LocalTempView, 
UNSUPPORTED, true
+   +- Project [g#x, m#x, id#x, should_keep#x]
+      +- SubqueryAlias distinct_map_data
+         +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct m#x) AS count(DISTINCT m)#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT SIZE(COLLECT_LIST(DISTINCT m)) FROM distinct_map_data
+-- !query analysis
+Aggregate [size(collect_list(distinct m#x, 0, 0, true), false) AS 
size(collect_list(DISTINCT m))#x]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a')
+-- !query analysis
+Project [map_entries(m)#x]
++- Sort [element_at(m#x, a, None, true) ASC NULLS FIRST], true
+   +- Project [map_entries(m#x) AS map_entries(m)#x, m#x]
+      +- SubqueryAlias collected_maps
+         +- Project [m#x]
+            +- Generate explode(_gen_input_0#x), false, [m#x]
+               +- Aggregate [collect_list(distinct m#x, 0, 0, true) AS 
_gen_input_0#x]
+                  +- SubqueryAlias distinct_map_data
+                     +- View (`distinct_map_data`, [g#x, m#x, id#x, 
should_keep#x])
+                        +- Project [cast(g#x as int) AS g#x, cast(m#x as 
map<string,int>) AS m#x, cast(id#x as int) AS id#x, cast(should_keep#x as 
boolean) AS should_keep#x]
+                           +- Project [g#x, m#x, id#x, should_keep#x]
+                              +- SubqueryAlias distinct_map_data
+                                 +- LocalRelation [g#x, m#x, id#x, 
should_keep#x]
+
+
+-- !query
+SELECT map_entries(FIRST(DISTINCT m)), map_entries(LAST(DISTINCT m)), 
COUNT(DISTINCT m)
+FROM VALUES (map('b', 2, 'a', 1)) AS single_map_data(m)
+-- !query analysis
+Aggregate [map_entries(first(distinct m#x, false)) AS 
map_entries(first(DISTINCT m))#x, map_entries(last(distinct m#x, false)) AS 
map_entries(last(DISTINCT m))#x, count(distinct m#x) AS count(DISTINCT m)#xL]
++- SubqueryAlias single_map_data
+   +- LocalRelation [m#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT m, id) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct m#x, id#x) AS count(DISTINCT m, id)#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT m), COUNT(DISTINCT id) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct m#x) AS count(DISTINCT m)#xL, count(distinct id#x) 
AS count(DISTINCT id)#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT g, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY g
+ORDER BY g
+-- !query analysis
+Sort [g#x ASC NULLS FIRST], true
++- Aggregate [g#x], [g#x, count(distinct m#x) AS count(DISTINCT m)#xL]
+   +- SubqueryAlias distinct_map_data
+      +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+         +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+            +- Project [g#x, m#x, id#x, should_keep#x]
+               +- SubqueryAlias distinct_map_data
+                  +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT m, COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a')
+-- !query analysis
+Sort [element_at(m#x, a, None, true) ASC NULLS FIRST], true
++- Aggregate [m#x], [m#x, count(distinct m#x) AS count(DISTINCT m)#xL, 
collect_list(distinct m#x, 0, 0, true) AS collect_list(DISTINCT m)#x]
+   +- SubqueryAlias distinct_map_data
+      +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+         +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+            +- Project [g#x, m#x, id#x, should_keep#x]
+               +- SubqueryAlias distinct_map_data
+                  +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FILTER (WHERE should_keep) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct m#x) FILTER (WHERE should_keep#x) AS count(DISTINCT 
m) FILTER (WHERE should_keep)#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT MAX(map_values(m)[0])
+FROM distinct_map_data
+WHERE id = 1
+-- !query analysis
+Aggregate [max(map_values(m#x)[0]) AS max(map_values(m)[0])#x]
++- Filter (id#x = 1)
+   +- SubqueryAlias distinct_map_data
+      +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+         +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+            +- Project [g#x, m#x, id#x, should_keep#x]
+               +- SubqueryAlias distinct_map_data
+                  +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT MAX(map_values(m)[0]), COUNT(DISTINCT m)
+FROM distinct_map_data
+WHERE id = 1
+-- !query analysis
+Aggregate [max(map_values(m#x)[0]) AS max(map_values(m)[0])#x, count(distinct 
m#x) AS count(DISTINCT m)#xL]
++- Filter (id#x = 1)
+   +- SubqueryAlias distinct_map_data
+      +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+         +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+            +- Project [g#x, m#x, id#x, should_keep#x]
+               +- SubqueryAlias distinct_map_data
+                  +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+ORDER BY COUNT(DISTINCT m), g
+-- !query analysis
+Project [g#x]
++- Sort [count(DISTINCT m)#xL ASC NULLS FIRST, g#x ASC NULLS FIRST], true
+   +- Aggregate [g#x], [g#x, count(distinct m#x) AS count(DISTINCT m)#xL]
+      +- SubqueryAlias distinct_map_data
+         +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+            +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) 
AS m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS 
should_keep#x]
+               +- Project [g#x, m#x, id#x, should_keep#x]
+                  +- SubqueryAlias distinct_map_data
+                     +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+HAVING COUNT(DISTINCT m) = 1
+ORDER BY g
+-- !query analysis
+Sort [g#x ASC NULLS FIRST], true
++- Project [g#x]
+   +- Filter (count(DISTINCT m)#xL = cast(1 as bigint))
+      +- Aggregate [g#x], [g#x, count(distinct m#x) AS count(DISTINCT m)#xL]
+         +- SubqueryAlias distinct_map_data
+            +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+               +- Project [cast(g#x as int) AS g#x, cast(m#x as 
map<string,int>) AS m#x, cast(id#x as int) AS id#x, cast(should_keep#x as 
boolean) AS should_keep#x]
+                  +- Project [g#x, m#x, id#x, should_keep#x]
+                     +- SubqueryAlias distinct_map_data
+                        +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct named_struct(m, m#x)) AS count(DISTINCT 
named_struct(m, m))#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT array(m)) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct array(m#x)) AS count(DISTINCT array(m))#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT map('m', m)) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct map(m, m#x)) AS count(DISTINCT map(m, m))#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM VALUES
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(NULL AS MAP<STRING, INT>))
+AS null_and_empty_map_data(m)
+-- !query analysis
+Aggregate [count(distinct m#x) AS count(DISTINCT m)#xL, collect_list(distinct 
m#x, 0, 0, true) AS collect_list(DISTINCT m)#x]
++- SubqueryAlias null_and_empty_map_data
+   +- LocalRelation [m#x]
+
+
+-- !query
+SELECT g, GROUPING(g), COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY GROUPING SETS ((g), ())
+ORDER BY GROUPING(g), g
+-- !query analysis
+Sort [grouping(g)#x ASC NULLS FIRST, g#x ASC NULLS FIRST], true
++- Aggregate [g#x, spark_grouping_id#xL], [g#x, 
cast((shiftright(spark_grouping_id#xL, 0) & 1) as tinyint) AS grouping(g)#x, 
count(distinct m#x) AS count(DISTINCT m)#xL]
+   +- Expand [[g#x, m#x, id#x, should_keep#x, g#x, 0], [g#x, m#x, id#x, 
should_keep#x, null, 1]], [g#x, m#x, id#x, should_keep#x, g#x, 
spark_grouping_id#xL]
+      +- Project [g#x, m#x, id#x, should_keep#x, g#x AS g#x]
+         +- SubqueryAlias distinct_map_data
+            +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+               +- Project [cast(g#x as int) AS g#x, cast(m#x as 
map<string,int>) AS m#x, cast(id#x as int) AS id#x, cast(should_keep#x as 
boolean) AS should_keep#x]
+                  +- Project [g#x, m#x, id#x, should_keep#x]
+                     +- SubqueryAlias distinct_map_data
+                        +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m, 'n', n))
+FROM VALUES
+  (map('a', 1, 'b', 2), map('x', 1, 'y', 2)),
+  (map('b', 2, 'a', 1), map('y', 2, 'x', 1))
+AS grouped_distinct_map_data(m, n)
+GROUP BY m
+-- !query analysis
+Aggregate [m#x], [count(distinct named_struct(m, m#x, n, n#x)) AS 
count(DISTINCT named_struct(m, m, n, n))#xL]
++- SubqueryAlias grouped_distinct_map_data
+   +- LocalRelation [m#x, n#x]
+
+
+-- !query
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=false
+-- !query analysis
+SetCommand 
(spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled,Some(false))
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct m#x) AS count(DISTINCT m)#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a'), map_entries(m)[0].key
+-- !query analysis
+Project [map_entries(m)#x]
++- Sort [element_at(m#x, a, None, true) ASC NULLS FIRST, 
map_entries(m#x)[0].key ASC NULLS FIRST], true
+   +- Project [map_entries(m#x) AS map_entries(m)#x, m#x]
+      +- SubqueryAlias collected_maps
+         +- Project [m#x]
+            +- Generate explode(_gen_input_0#x), false, [m#x]
+               +- Aggregate [collect_list(distinct m#x, 0, 0, true) AS 
_gen_input_0#x]
+                  +- SubqueryAlias distinct_map_data
+                     +- View (`distinct_map_data`, [g#x, m#x, id#x, 
should_keep#x])
+                        +- Project [cast(g#x as int) AS g#x, cast(m#x as 
map<string,int>) AS m#x, cast(id#x as int) AS id#x, cast(should_keep#x as 
boolean) AS should_keep#x]
+                           +- Project [g#x, m#x, id#x, should_keep#x]
+                              +- SubqueryAlias distinct_map_data
+                                 +- LocalRelation [g#x, m#x, id#x, 
should_keep#x]
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data
+-- !query analysis
+Aggregate [count(distinct named_struct(m, m#x)) AS count(DISTINCT 
named_struct(m, m))#xL]
++- SubqueryAlias distinct_map_data
+   +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+      +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+         +- Project [g#x, m#x, id#x, should_keep#x]
+            +- SubqueryAlias distinct_map_data
+               +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SELECT m, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a')
+-- !query analysis
+Sort [element_at(m#x, a, None, true) ASC NULLS FIRST], true
++- Aggregate [m#x], [m#x, count(distinct m#x) AS count(DISTINCT m)#xL]
+   +- SubqueryAlias distinct_map_data
+      +- View (`distinct_map_data`, [g#x, m#x, id#x, should_keep#x])
+         +- Project [cast(g#x as int) AS g#x, cast(m#x as map<string,int>) AS 
m#x, cast(id#x as int) AS id#x, cast(should_keep#x as boolean) AS should_keep#x]
+            +- Project [g#x, m#x, id#x, should_keep#x]
+               +- SubqueryAlias distinct_map_data
+                  +- LocalRelation [g#x, m#x, id#x, should_keep#x]
+
+
+-- !query
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=true
+-- !query analysis
+SetCommand 
(spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled,Some(true))
diff --git 
a/sql/core/src/test/resources/sql-tests/inputs/distinct-map-aggregates.sql 
b/sql/core/src/test/resources/sql-tests/inputs/distinct-map-aggregates.sql
new file mode 100644
index 000000000000..523ed0caee89
--- /dev/null
+++ b/sql/core/src/test/resources/sql-tests/inputs/distinct-map-aggregates.sql
@@ -0,0 +1,102 @@
+-- Test DISTINCT aggregates with MapType arguments.
+
+CREATE OR REPLACE TEMPORARY VIEW distinct_map_data AS SELECT * FROM VALUES
+  (2, map('a', 1, 'b', 2), 1, true),
+  (2, map('b', 2, 'a', 1), 1, true),
+  (1, map('a', 1, 'b', 2), 1, true),
+  (1, map('a', 3), 2, false)
+AS distinct_map_data(g, m, id, should_keep);
+
+SELECT COUNT(DISTINCT m) FROM distinct_map_data;
+
+SELECT SIZE(COLLECT_LIST(DISTINCT m)) FROM distinct_map_data;
+
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a');
+
+SELECT map_entries(FIRST(DISTINCT m)), map_entries(LAST(DISTINCT m)), 
COUNT(DISTINCT m)
+FROM VALUES (map('b', 2, 'a', 1)) AS single_map_data(m);
+
+SELECT COUNT(DISTINCT m, id) FROM distinct_map_data;
+
+SELECT COUNT(DISTINCT m), COUNT(DISTINCT id) FROM distinct_map_data;
+
+SELECT g, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY g
+ORDER BY g;
+
+SELECT m, COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a');
+
+SELECT COUNT(DISTINCT m) FILTER (WHERE should_keep) FROM distinct_map_data;
+
+SELECT MAX(map_values(m)[0])
+FROM distinct_map_data
+WHERE id = 1;
+
+SELECT MAX(map_values(m)[0]), COUNT(DISTINCT m)
+FROM distinct_map_data
+WHERE id = 1;
+
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+ORDER BY COUNT(DISTINCT m), g;
+
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+HAVING COUNT(DISTINCT m) = 1
+ORDER BY g;
+
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data;
+
+SELECT COUNT(DISTINCT array(m)) FROM distinct_map_data;
+
+SELECT COUNT(DISTINCT map('m', m)) FROM distinct_map_data;
+
+SELECT COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM VALUES
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(NULL AS MAP<STRING, INT>))
+AS null_and_empty_map_data(m);
+
+SELECT g, GROUPING(g), COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY GROUPING SETS ((g), ())
+ORDER BY GROUPING(g), g;
+
+SELECT COUNT(DISTINCT named_struct('m', m, 'n', n))
+FROM VALUES
+  (map('a', 1, 'b', 2), map('x', 1, 'y', 2)),
+  (map('b', 2, 'a', 1), map('y', 2, 'x', 1))
+AS grouped_distinct_map_data(m, n)
+GROUP BY m;
+
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=false;
+
+SELECT COUNT(DISTINCT m) FROM distinct_map_data;
+
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a'), map_entries(m)[0].key;
+
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data;
+
+SELECT m, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a');
+
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=true;
diff --git 
a/sql/core/src/test/resources/sql-tests/results/distinct-map-aggregates.sql.out 
b/sql/core/src/test/resources/sql-tests/results/distinct-map-aggregates.sql.out
new file mode 100644
index 000000000000..57f53db7e3f5
--- /dev/null
+++ 
b/sql/core/src/test/resources/sql-tests/results/distinct-map-aggregates.sql.out
@@ -0,0 +1,265 @@
+-- Automatically generated by SQLQueryTestSuite
+-- !query
+CREATE OR REPLACE TEMPORARY VIEW distinct_map_data AS SELECT * FROM VALUES
+  (2, map('a', 1, 'b', 2), 1, true),
+  (2, map('b', 2, 'a', 1), 1, true),
+  (1, map('a', 1, 'b', 2), 1, true),
+  (1, map('a', 3), 2, false)
+AS distinct_map_data(g, m, id, should_keep)
+-- !query schema
+struct<>
+-- !query output
+
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT m):bigint>
+-- !query output
+2
+
+
+-- !query
+SELECT SIZE(COLLECT_LIST(DISTINCT m)) FROM distinct_map_data
+-- !query schema
+struct<size(collect_list(DISTINCT m)):int>
+-- !query output
+2
+
+
+-- !query
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a')
+-- !query schema
+struct<map_entries(m):array<struct<key:string,value:int>>>
+-- !query output
+[{"key":"a","value":1},{"key":"b","value":2}]
+[{"key":"a","value":3}]
+
+
+-- !query
+SELECT map_entries(FIRST(DISTINCT m)), map_entries(LAST(DISTINCT m)), 
COUNT(DISTINCT m)
+FROM VALUES (map('b', 2, 'a', 1)) AS single_map_data(m)
+-- !query schema
+struct<map_entries(first(DISTINCT 
m)):array<struct<key:string,value:int>>,map_entries(last(DISTINCT 
m)):array<struct<key:string,value:int>>,count(DISTINCT m):bigint>
+-- !query output
+[{"key":"a","value":1},{"key":"b","value":2}]  
[{"key":"a","value":1},{"key":"b","value":2}]   1
+
+
+-- !query
+SELECT COUNT(DISTINCT m, id) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT m, id):bigint>
+-- !query output
+2
+
+
+-- !query
+SELECT COUNT(DISTINCT m), COUNT(DISTINCT id) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT m):bigint,count(DISTINCT id):bigint>
+-- !query output
+2      2
+
+
+-- !query
+SELECT g, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY g
+ORDER BY g
+-- !query schema
+struct<g:int,count(DISTINCT m):bigint>
+-- !query output
+1      2
+2      1
+
+
+-- !query
+SELECT m, COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a')
+-- !query schema
+struct<m:map<string,int>,count(DISTINCT m):bigint,collect_list(DISTINCT 
m):array<map<string,int>>>
+-- !query output
+{"a":1,"b":2}  1       [{"a":1,"b":2}]
+{"a":3}        1       [{"a":3}]
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FILTER (WHERE should_keep) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT m) FILTER (WHERE should_keep):bigint>
+-- !query output
+1
+
+
+-- !query
+SELECT MAX(map_values(m)[0])
+FROM distinct_map_data
+WHERE id = 1
+-- !query schema
+struct<max(map_values(m)[0]):int>
+-- !query output
+2
+
+
+-- !query
+SELECT MAX(map_values(m)[0]), COUNT(DISTINCT m)
+FROM distinct_map_data
+WHERE id = 1
+-- !query schema
+struct<max(map_values(m)[0]):int,count(DISTINCT m):bigint>
+-- !query output
+2      1
+
+
+-- !query
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+ORDER BY COUNT(DISTINCT m), g
+-- !query schema
+struct<g:int>
+-- !query output
+2
+1
+
+
+-- !query
+SELECT g
+FROM distinct_map_data
+GROUP BY g
+HAVING COUNT(DISTINCT m) = 1
+ORDER BY g
+-- !query schema
+struct<g:int>
+-- !query output
+2
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT named_struct(m, m)):bigint>
+-- !query output
+2
+
+
+-- !query
+SELECT COUNT(DISTINCT array(m)) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT array(m)):bigint>
+-- !query output
+2
+
+
+-- !query
+SELECT COUNT(DISTINCT map('m', m)) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT map(m, m)):bigint>
+-- !query output
+2
+
+
+-- !query
+SELECT COUNT(DISTINCT m), COLLECT_LIST(DISTINCT m)
+FROM VALUES
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(map() AS MAP<STRING, INT>)),
+  (CAST(NULL AS MAP<STRING, INT>))
+AS null_and_empty_map_data(m)
+-- !query schema
+struct<count(DISTINCT m):bigint,collect_list(DISTINCT 
m):array<map<string,int>>>
+-- !query output
+1      [{}]
+
+
+-- !query
+SELECT g, GROUPING(g), COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY GROUPING SETS ((g), ())
+ORDER BY GROUPING(g), g
+-- !query schema
+struct<g:int,grouping(g):tinyint,count(DISTINCT m):bigint>
+-- !query output
+1      0       2
+2      0       1
+NULL   1       2
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m, 'n', n))
+FROM VALUES
+  (map('a', 1, 'b', 2), map('x', 1, 'y', 2)),
+  (map('b', 2, 'a', 1), map('y', 2, 'x', 1))
+AS grouped_distinct_map_data(m, n)
+GROUP BY m
+-- !query schema
+struct<count(DISTINCT named_struct(m, m, n, n)):bigint>
+-- !query output
+1
+
+
+-- !query
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=false
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled  false
+
+
+-- !query
+SELECT COUNT(DISTINCT m) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT m):bigint>
+-- !query output
+3
+
+
+-- !query
+SELECT map_entries(m)
+FROM (
+  SELECT EXPLODE(COLLECT_LIST(DISTINCT m)) AS m
+  FROM distinct_map_data
+) AS collected_maps
+ORDER BY element_at(m, 'a'), map_entries(m)[0].key
+-- !query schema
+struct<map_entries(m):array<struct<key:string,value:int>>>
+-- !query output
+[{"key":"a","value":1},{"key":"b","value":2}]
+[{"key":"b","value":2},{"key":"a","value":1}]
+[{"key":"a","value":3}]
+
+
+-- !query
+SELECT COUNT(DISTINCT named_struct('m', m)) FROM distinct_map_data
+-- !query schema
+struct<count(DISTINCT named_struct(m, m)):bigint>
+-- !query output
+3
+
+
+-- !query
+SELECT m, COUNT(DISTINCT m)
+FROM distinct_map_data
+GROUP BY m
+ORDER BY element_at(m, 'a')
+-- !query schema
+struct<m:map<string,int>,count(DISTINCT m):bigint>
+-- !query output
+{"a":1,"b":2}  1
+{"a":3}        1
+
+
+-- !query
+SET spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled=true
+-- !query schema
+struct<key:string,value:string>
+-- !query output
+spark.sql.optimizer.insertMapSortInDistinctAggregates.enabled  true


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to