Repository: apache/calcite
Pull request #4371: [CALCITE-6887] ReduceExpressionsRule applied to 'IN subquery' should make the values distinct if the subquery is a Values composed of literals
Author: login: xuzifu666
Created: 2025-05-13T11:36:49Z | Merged: 2025-05-22T14:43:08Z

As description in JIRA: https://issues.apache.org/jira/browse/CALCITE-6887


Diff:
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
index e4fb82ca6b94..7231c4e3ad3c 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
@@ -34,10 +34,13 @@
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
- * Rule that applies {@link Aggregate} to a {@link Values} (currently just an
+ * Rule that
+ * 1. applies {@link Aggregate} to a {@link Values} (currently just an
  * empty {@code Value}s).
+ * 2. {@link Aggregate} and {@link Values} to a distinct {@link Values}.
  *
  * <p>This is still useful because {@link PruneEmptyRules#AGGREGATE_INSTANCE}
  * doesn't handle {@code Aggregate}, which is in turn because {@code Aggregate}
@@ -53,6 +56,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because {@link SubQueryRemoveRule}
+ * doesn't remove duplicates for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ *   LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
+ * </code></blockquote>
+ *
+ * <p>With the Rule would deduplicate the values and convert to:
+ *
+ * <blockquote><code>
+ * LogicalValues(tuples=[[{ 1 }, { 3 }]])
+ * </code></blockquote>
+ *
  * @see CoreRules#AGGREGATE_VALUES
  */
 @Value.Enclosing
@@ -72,6 +89,8 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
         .as(Config.class));
   }
 
+  //~ Methods ----------------------------------------------------------------
+
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
@@ -79,33 +98,46 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
-    final List<RexLiteral> literals = new ArrayList<>();
-    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      switch (aggregateCall.getAggregation().getKind()) {
-      case COUNT:
-      case SUM0:
-        literals.add(
-            rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
-        break;
-
-      case MIN:
-      case MAX:
-      case SUM:
-        literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
-        break;
-
-      default:
-        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
-        return;
+    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
+      final List<RexLiteral> literals = new ArrayList<>();
+      for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+        switch (aggregateCall.getAggregation().getKind()) {
+        case COUNT:
+        case SUM0:
+          literals.add(
+              rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
+          break;
+
+        case MIN:
+        case MAX:
+        case SUM:
+          literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
+          break;
+
+        default:
+          // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+          return;
+        }
       }
-    }
 
-    call.transformTo(
-        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
-            .build());
+      call.transformTo(
+          relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+              .build());
+
+      // New plan is absolutely better than old plan.
+      call.getPlanner().prune(aggregate);
+      return;
+    }
 
-    // New plan is absolutely better than old plan.
-    call.getPlanner().prune(aggregate);
+    if (Aggregate.isSimple(aggregate)
+        && aggregate.getAggCallList().isEmpty()
+        && aggregate.getRowType().equals(values.getRowType())) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
+      call.transformTo(relBuilder.build());
+      call.getPlanner().prune(aggregate);
+    }
   }
 
   /** Rule configuration. */
@@ -123,10 +155,8 @@ default Config withOperandFor(Class<? extends Aggregate> aggregateClass,
         Class<? extends Values> valuesClass) {
       return withOperandSupplier(b0 ->
           b0.operand(aggregateClass)
-              .predicate(aggregate -> aggregate.getGroupCount() == 0)
               .oneInput(b1 ->
                   b1.operand(valuesClass)
-                      .predicate(values -> values.getTuples().isEmpty())
                       .noInputs()))
           .as(Config.class);
     }
diff --git a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
index 88dad59f1286..bd87a7afefe8 100644
--- a/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
@@ -354,6 +354,73 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a Values composed of literals</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2,1,1,1,1,1,1,1,null,null,1,1,1,1,1,1,1)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .check();
+  }
+
+  @Test void testReduceInValuesNonOrdered() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"}, 1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggregation() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
+            b.count(b.field(0)).as("V1_COUNT"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithSameRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithDifferentRowTypes() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES)
+        .checkUnchanged();
+  }
+
   /**
    * Test case for <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6746">
    * [CALCITE-6746] Optimization rule ProjectWindowTranspose is unsound</a>. */
diff --git a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
index 2ab97cd1004c..5382b95db14d 100644
--- a/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
+++ b/core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
@@ -19610,6 +19610,84 @@ LogicalProject(DEPTNO=[$0], EXPR$1=[$1], EXPR$2=[CAST(/($2, $3)):INTEGER NOT NUL
     LogicalAggregate(group=[{0, 2}], agg#0=[MIN($1)], agg#1=[MIN($3)])
       LogicalProject(DEPTNO=[$7], SAL=[$5], JOB=[$2], COMM=[$6])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateValuesRuleWithAggregation">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{0, 1}], V1_COUNT=[COUNT()])
+  LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'a' }, { 1, 'b' }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateValuesRuleWithDifferentRowTypes">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{0}])
+  LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'a' }, { 1, 'b' }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testReduceInValuesNonOrdered">
+    <Resource name="sql">
+      <![CDATA[SELECT deptno, sal FROM EMP WHERE deptno IN (1,1,3,1,2)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalFilter(condition=[OR(=($7, 1), =($7, 3), =($7, 2))])
+    LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateValuesRuleWithInRepeatingValues">
+    <Resource name="sql">
+      <![CDATA[SELECT deptno, sal FROM EMP WHERE deptno IN (1,1,3,1,2,1,1,1,1,1,1,1,null,null,1,1,1,1,1,1,1)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalAggregate(group=[{0}])
+        LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { 1 }, { 2 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { null }, { null }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalValues(tuples=[[{ 1 }, { 3 }, { 2 }, { null }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateValuesRuleWithRepeatingValues">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{0}])
+  LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { null }, { null }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[{ 1 }, { 3 }, { null }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testAggregateValuesRuleWithSameRowType">
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalAggregate(group=[{0, 1}])
+  LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'a' }, { 1, 'b' }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'b' }]])
 ]]>
     </Resource>
   </TestCase>


Reviews:
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T11:56:08Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T12:00:01Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T12:08:04Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T12:09:20Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T13:41:36Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T13:52:00Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T13:55:59Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-13T15:34:56Z
user: login: mihaibudiu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T03:15:09Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T07:37:31Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T07:43:14Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T10:21:31Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T10:42:02Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:03:38Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T14:04:35Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-14T15:30:36Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T09:01:24Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T03:12:52Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T03:58:52Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T09:33:51Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:07:39Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:29:24Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:30:03Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:31:17Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:34:03Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:39:46Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:39:59Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:40:25Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:40:44Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T10:40:54Z
user: login: asolimando
body: A few minor improvements, merging the two modes is a separate discussion.
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-05-20T10:53:29Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:16:41Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:16:56Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:17:01Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:17:04Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:17:08Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:17:11Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T12:17:15Z
user: login: xiedeyantu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T13:43:49Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:03:16Z
user: login: xiedeyantu
body: LGTM, please wait and see if @asolimando and @mihaibudiu will make any comments.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T14:14:04Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:16:00Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:01:35Z
user: login: asolimando
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:06:50Z
user: login: asolimando
body: I need to take another pass on the PR but most (all?) my comments seem to have been addressed, I want to check again the other PRs to see if all comments there were addressed as well.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:07:59Z
user: login: xuzifu666
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-21T10:11:54Z
user: login: asolimando
body: LGTM, can you please squash the commits into one @xuzifu666 making sure the commit title matches the usual format and the associated jira ticket title?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-22T13:05:34Z

Review comments:
diff_hunk: @@ -53,6 +56,21 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ * LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 },
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: asolimando
body: Nit: the example could be made simpler/shorter without loss
created_at: 2025-05-13T11:56:08Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: I'd make the test simpler (no need for that many ending `1`s, a couple suffice) and add more tests around edge cases (rule is no-op if no duplicates, multiple duplicate values, nulls, data types, etc.).

Does the approach also handle complex data types in its current implementation? If not, should it support them?
created_at: 2025-05-13T12:00:00Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Thanks for reminder, here with many ending 1s is due to greater than 20 would construcor with IN operator (otherwise would not), and I would add more tests according your suggestion. @asolimando 
created_at: 2025-05-13T12:08:04Z
author_association: MEMBER
diff_hunk: @@ -53,6 +56,21 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ * LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 },
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Your are right, I would made simpler.
created_at: 2025-05-13T12:09:20Z
author_association: MEMBER
diff_hunk: @@ -19610,6 +19610,46 @@ LogicalProject(DEPTNO=[$0], EXPR$1=[$1], EXPR$2=[CAST(/($2, $3)):INTEGER NOT NUL
     LogicalAggregate(group=[{0, 2}], agg#0=[MIN($1)], agg#1=[MIN($3)])
       LogicalProject(DEPTNO=[$7], SAL=[$5], JOB=[$2], COMM=[$6])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testReduceInValues">
+    <Resource name="sql">
+      <![CDATA[SELECT deptno, sal FROM EMP WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalAggregate(group=[{0}])
+        LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalValues(tuples=[[{ 1 }, { 3 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testReduceInValuesWithAggregateValueReduceRule">
+    <Resource name="sql">
path: core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
user: login: xiedeyantu
body: We should remove the `<Resource name="sql">`. It may be necessary to file a Jira record. This XML should not exist when using `relFn`, but there is no error reported.
created_at: 2025-05-13T12:26:38Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: @asolimando According test and analyze，currently maybe cannot support complex data types，such as：
```
@Test void testReduceInValuesWithAggregateValueReduceRuleWithNestedType() {
  final Function<RelBuilder, RelNode> relFn = b -> b
      .values(new String[]{"v"},  new int[]{1, 2, 3})
      .distinct()
      .build();
  relFn(relFn)
      .withRule(CoreRules.AGGREGATE_VALUES)
      .check();
}
```
would error out：
```
cannot convert [I@6233d094 (class [I) to a constant
java.lang.IllegalArgumentException: cannot convert [I@6233d094 (class [I) to a constant
 at org.apache.calcite.tools.RelBuilder.literal(RelBuilder.java:497)
 at org.apache.calcite.tools.RelBuilder.tupleList(RelBuilder.java:3463)
 at org.apache.calcite.tools.RelBuilder.values(RelBuilder.java:3423)
 at org.apache.calcite.test.RelOptRulesTest.lambda$testReduceInValuesWithAggregateValueReduceRuleWithNestedType$8(RelOptRulesTest.java:394)
 at org.apache.calcite.test.RelSupplier$FnRelSupplier.apply(RelSupplier.java:131)
 at org.apache.calcite.test.RelOptFixture.toRel(RelOptFixture.java:309)
 at org.apache.calcite.test.RelOptFixture.checkPlanning(RelOptFixture.java:345)
 at org.apache.calcite.test.RelOptFixture.check(RelOptFixture.java:334)
 at org.apache.calcite.test.RelOptFixture.check(RelOptFixture.java:318)
```

reason is  RelBuilder##literal method can only handle with basic types. 
```
public RexLiteral literal(@Nullable Object value) {
    final RexBuilder rexBuilder = cluster.getRexBuilder();
    if (value == null) {
      final RelDataType type = getTypeFactory().createSqlType(SqlTypeName.NULL);
      return rexBuilder.makeNullLiteral(type);
    } else if (value instanceof Boolean) {
      return rexBuilder.makeLiteral((Boolean) value);
    } else if (value instanceof BigDecimal) {
      return rexBuilder.makeExactLiteral((BigDecimal) value);
    } else if (value instanceof Float || value instanceof Double) {
      return rexBuilder.makeApproxLiteral(
          ((Number) value).doubleValue(), getTypeFactory().createSqlType(SqlTypeName.DOUBLE));
    } else if (value instanceof Number) {
      return rexBuilder.makeExactLiteral(
          BigDecimal.valueOf(((Number) value).longValue()));
    } else if (value instanceof String) {
      return rexBuilder.makeLiteral((String) value);
    } else if (value instanceof Enum) {
      return rexBuilder.makeLiteral(value,
          getTypeFactory().createSqlType(SqlTypeName.SYMBOL));
    } else if (value instanceof DateString) {
      return rexBuilder.makeDateLiteral((DateString) value);
    } else if (value instanceof Date) {
      return rexBuilder.makeLiteral(value,
          getTypeFactory().createSqlType(SqlTypeName.DATE));
    } else {
      throw new IllegalArgumentException("cannot convert " + value
          + " (" + value.getClass() + ") to a constant");
    }
  }
```
so maybe we can just resolve non-complex data types currently and I had added more case to cover more conditions which expected as the rule.
created_at: 2025-05-13T13:41:35Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: mihaibudiu
body: Because these are not literals, they are constant expressions. That's not the same thing.
created_at: 2025-05-13T13:52:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: @mihaibudiu RelBuilder##values would call tupleList method which can only support with Literal
```
  public RelBuilder values(@Nullable String[] fieldNames, @Nullable Object... values) {
    requireNonNull(fieldNames, "fieldNames");
    if (fieldNames.length == 0
        || values.length % fieldNames.length != 0
        || values.length < fieldNames.length) {
      throw new IllegalArgumentException(
          "Value count must be a positive multiple of field count");
    }
    final int rowCount = values.length / fieldNames.length;
    for (Ord<@Nullable String> fieldName : Ord.zip(fieldNames)) {
      if (allNull(values, fieldName.i, fieldNames.length)) {
        throw new IllegalArgumentException("All values of field '" + fieldName.e
            + "' (field index " + fieldName.i + ")"
            + " are null; cannot deduce type");
      }
    }
    final ImmutableList<ImmutableList<RexLiteral>> tupleList =
        **tupleList(fieldNames.length, values);**
    assert tupleList.size() == rowCount;
    final List<String> fieldNameList =
        Util.transformIndexed(Arrays.asList(fieldNames), (name, i) ->
            name != null ? name : SqlUtil.deriveAliasFromOrdinal(i));
    return values(tupleList, fieldNameList);
  }
```

 RelBuilder##tupleList, here only handle with Literal type.
```
  private ImmutableList<ImmutableList<RexLiteral>> tupleList(int columnCount,
      @Nullable Object[] values) {
    final ImmutableList.Builder<ImmutableList<RexLiteral>> listBuilder =
        ImmutableList.builder();
    final List<RexLiteral> valueList = new ArrayList<>();
    for (int i = 0; i < values.length; i++) {
      Object value = values[i];
      valueList.add(literal(value));
      if ((i + 1) % columnCount == 0) {
        listBuilder.add(ImmutableList.copyOf(valueList));
        valueList.clear();
      }
    }
    return listBuilder.build();
  }
```
So IMO here can only handle with Literal.

created_at: 2025-05-13T15:34:56Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: mihaibudiu
body: this distinct uses Java equals(). I assume it is always correct, but I am not sure it will always find all duplicates.
created_at: 2025-05-14T03:13:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: mihaibudiu
body: doesn't the field you group on need to be the same that is aggregated as well?
where is that check?
VALUES can contain ROW objects.
created_at: 2025-05-14T03:14:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Your are right, but IMO maybe can handle with it by this way, due to RelBuilder##values can only handle RexLiteral type, and all type of RexLiteral had implement Compare interface which may remove duplicates correctly. 
created_at: 2025-05-14T07:37:31Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Here would only handle with groupSets.size = 1 condition，I had a tried seems ROW type can not constructor in test case. Can we just handle with Literal type first？Or maybe I need to think more about it. @mihaibudiu
created_at: 2025-05-14T07:43:14Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: Mihai may mean this: 
```
    final Function<RelBuilder, RelNode> relFn = b -> b
        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
        .aggregate(b.groupKey(ImmutableBitSet.of(0)))
        .build();
```
This will generate the following plan, which only does a GROUP BY on the v1 field.
```
LogicalAggregate(group=[{0}])
  LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'a' }, { 1, 'b' }]])
```
Maybe you need to check whether this scenario is supported.
created_at: 2025-05-14T10:21:30Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: This judgment condition may have some problems. Maybe you can change like this.
```
    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
      // old
    } else if (/* your condition */) {
      
    }
```
This may be a bad case:
```
    final String sql = "select count(*), sum(empno) from emp where false group by empno";
```
created_at: 2025-05-14T10:42:02Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Thanks, had modifed. @xiedeyantu 
created_at: 2025-05-14T14:03:38Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Had test for Row type about it and it would distinct result as expected. @mihaibudiu @xiedeyantu 
```
  <TestCase name="testReduceInValuesWithAggregateValueReduceRuleWithRow">
    <Resource name="planBefore">
      <![CDATA[
LogicalAggregate(group=[{0, 1}])
  LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'a' }, { 1, 'b' }, { 1, 'a' }]])
]]>
    </Resource>
    <Resource name="planAfter">
      <![CDATA[
LogicalValues(tuples=[[{ 1, 'a' }, { 2, 'b' }, { 1, 'b' }]])
]]>
    </Resource>
```
created_at: 2025-05-14T14:04:34Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: You should use `aggregate(b.groupKey(ImmutableBitSet.of(0)))` to test. `distinct()` can not expose any problems.
created_at: 2025-05-14T15:30:36Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: @asolimando I also  added the case with non-sorted and less then 20 values in commit: https://github.com/apache/calcite/commit/c4273c449855a8dc8caa48c0b3f49d27a782495e
created_at: 2025-05-19T09:01:24Z
author_association: MEMBER
diff_hunk: @@ -53,6 +55,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ * LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: ```
 * LogicalAggregate(group=[{0}])
 *   LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
```
created_at: 2025-05-20T03:12:52Z
author_association: MEMBER
diff_hunk: @@ -53,6 +55,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ * LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T03:58:52Z
author_association: MEMBER
diff_hunk: @@ -75,10 +91,18 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
-    Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
+    if ((aggregate.getGroupCount() != 0 || !values.getTuples().isEmpty())
+        && aggregate.groupSets.size() == 1) {
+      List<ImmutableList<RexLiteral>> distinctValues =
+          values.getTuples().stream().distinct().collect(Collectors.toList());
+      relBuilder.values(distinctValues, values.getRowType());
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: ```
doesn't the field you group on need to be the same that is aggregated as well?
where is that check?
VALUES can contain ROW objects.
```
@mihaibudiu had added check for rowType. Need to be same with that is aggregated.


created_at: 2025-05-20T09:33:51Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xiedeyantu
body: You can just use `testAggregateValuesRuleWithInRepeatingValues` to show this logic.
created_at: 2025-05-20T10:05:30Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xiedeyantu
body: Same as above.
created_at: 2025-05-20T10:06:25Z
author_association: MEMBER
diff_hunk: @@ -53,6 +56,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: asolimando
body: ```suggestion
 * <p>This is useful because {@link SubQueryRemoveRule}
```
created_at: 2025-05-20T10:29:24Z
author_association: MEMBER
diff_hunk: @@ -53,6 +56,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: asolimando
body: ```suggestion
 * doesn't remove duplicates for IN filter values.
```
created_at: 2025-05-20T10:30:03Z
author_association: MEMBER
diff_hunk: @@ -53,6 +56,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ *   LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
+ * </code></blockquote>
+ *
+ * <p>should be converted to:
+ *
+ * <blockquote><code>
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: asolimando
body: What happened to the aggregation here? Is it now redundant and removed due to the deduplication? If so, please state it explicitly in the comment.

EDIT: from the code I can see that this is what's happening, but comments should not need to read the code for a general understanding of the functionality
created_at: 2025-05-20T10:31:17Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
  @Test void testReduceInValuesNonOrdered() {
``` ? or `testReduceUnsortedInValues()`?
created_at: 2025-05-20T10:34:03Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
        .values(new String[]{"v"}, 1, 1, 3, null, null)
```
created_at: 2025-05-20T10:39:46Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
```
created_at: 2025-05-20T10:39:59Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
  @Test void testAggregateValuesRuleWithAggregation() {
```
created_at: 2025-05-20T10:40:25Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
+            b.count(b.field(0)).as("V1_COUNT"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithSameRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithDifferentRowType() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
  @Test void testAggregateValuesRuleWithDifferentRowTypes() {
```
created_at: 2025-05-20T10:40:43Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
+            b.count(b.field(0)).as("V1_COUNT"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithSameRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithDifferentRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: ```suggestion
        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
```
created_at: 2025-05-20T10:40:53Z
author_association: MEMBER
diff_hunk: @@ -53,6 +56,20 @@
  * Any non-empty {@code GROUP BY} clause will return one row per group key
  * value, and each group will consist of at least one row.
  *
+ * <p>This is useful because such as {@link SubQueryRemoveRule}
+ * doesn't distinct values for IN filter values.
+ *
+ * <blockquote><code>
+ * LogicalAggregate(group=[{0}])
+ *   LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }]])
+ * </code></blockquote>
+ *
+ * <p>should be converted to:
+ *
+ * <blockquote><code>
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: @asolimando Thanks, I had changed it to: 
```
With the Rule would deduplicate the values and convert to:
```
created_at: 2025-05-20T12:16:41Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:16:56Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:17:01Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:17:04Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:17:08Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
+            b.count(b.field(0)).as("V1_COUNT"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithSameRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithDifferentRowType() {
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:17:10Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,72 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testAggregateValuesRuleWithInRepeatingValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testReduceInValuesWithNonOrder() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,2)";
+
+    sql(sql)
+        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithRepeatingValues() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v"},  1, 1, 3, null, null)
+        .distinct()
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithAggCall() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
+            b.count(b.field(0)).as("V1_COUNT"))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .checkUnchanged();
+  }
+
+  @Test void testAggregateValuesRuleWithSameRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
+        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
+        .build();
+    relFn(relFn)
+        .withRule(CoreRules.AGGREGATE_VALUES_REDUCE)
+        .check();
+  }
+
+  @Test void testAggregateValuesRuleWithDifferentRowType() {
+    final Function<RelBuilder, RelNode> relFn = b -> b
+        .values(new String[]{"v1", "v2"},  1, "a", 2, "b", 1, "a", 1, "b")
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T12:17:15Z
author_association: MEMBER
diff_hunk: @@ -72,40 +89,54 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
         .as(Config.class));
   }
 
+  //~ Methods ----------------------------------------------------------------
+
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
     Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
-    final List<RexLiteral> literals = new ArrayList<>();
-    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      switch (aggregateCall.getAggregation().getKind()) {
-      case COUNT:
-      case SUM0:
-        literals.add(
-            rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
-        break;
-
-      case MIN:
-      case MAX:
-      case SUM:
-        literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
-        break;
-
-      default:
-        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
-        return;
+    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
+      final List<RexLiteral> literals = new ArrayList<>();
+      for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+        switch (aggregateCall.getAggregation().getKind()) {
+        case COUNT:
+        case SUM0:
+          literals.add(
+              rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
+          break;
+
+        case MIN:
+        case MAX:
+        case SUM:
+          literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
+          break;
+
+        default:
+          // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+          return;
+        }
       }
-    }
-
-    call.transformTo(
-        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
-            .build());
 
-    // New plan is absolutely better than old plan.
-    call.getPlanner().prune(aggregate);
+      call.transformTo(
+          relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+              .build());
+
+      // New plan is absolutely better than old plan.
+      call.getPlanner().prune(aggregate);
+    } else {
+      if (Aggregate.isSimple(aggregate)
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: Here you can use a separate if, no need for an if else.
created_at: 2025-05-20T13:41:52Z
author_association: MEMBER
diff_hunk: @@ -72,40 +89,54 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
         .as(Config.class));
   }
 
+  //~ Methods ----------------------------------------------------------------
+
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
     Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
-    final List<RexLiteral> literals = new ArrayList<>();
-    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      switch (aggregateCall.getAggregation().getKind()) {
-      case COUNT:
-      case SUM0:
-        literals.add(
-            rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
-        break;
-
-      case MIN:
-      case MAX:
-      case SUM:
-        literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
-        break;
-
-      default:
-        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
-        return;
+    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
+      final List<RexLiteral> literals = new ArrayList<>();
+      for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+        switch (aggregateCall.getAggregation().getKind()) {
+        case COUNT:
+        case SUM0:
+          literals.add(
+              rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
+          break;
+
+        case MIN:
+        case MAX:
+        case SUM:
+          literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
+          break;
+
+        default:
+          // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+          return;
+        }
       }
-    }
-
-    call.transformTo(
-        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
-            .build());
 
-    // New plan is absolutely better than old plan.
-    call.getPlanner().prune(aggregate);
+      call.transformTo(
+          relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+              .build());
+
+      // New plan is absolutely better than old plan.
+      call.getPlanner().prune(aggregate);
+    } else {
+      if (Aggregate.isSimple(aggregate)
+          && aggregate.getAggCallList().isEmpty()
+          && aggregate.getRowType().equals(values.getRowType())) {
+        List<ImmutableList<RexLiteral>> distinctValues =
+            values.getTuples().stream().distinct().collect(Collectors.toList());
+        relBuilder.values(distinctValues, values.getRowType());
+        call.transformTo(relBuilder.build());
+        call.getPlanner().prune(aggregate);
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xiedeyantu
body: Repeated code can be mentioned outside the if else.
created_at: 2025-05-20T13:43:27Z
author_association: MEMBER
diff_hunk: @@ -72,40 +89,54 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
         .as(Config.class));
   }
 
+  //~ Methods ----------------------------------------------------------------
+
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
     Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
-    final List<RexLiteral> literals = new ArrayList<>();
-    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      switch (aggregateCall.getAggregation().getKind()) {
-      case COUNT:
-      case SUM0:
-        literals.add(
-            rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
-        break;
-
-      case MIN:
-      case MAX:
-      case SUM:
-        literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
-        break;
-
-      default:
-        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
-        return;
+    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
+      final List<RexLiteral> literals = new ArrayList<>();
+      for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+        switch (aggregateCall.getAggregation().getKind()) {
+        case COUNT:
+        case SUM0:
+          literals.add(
+              rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
+          break;
+
+        case MIN:
+        case MAX:
+        case SUM:
+          literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
+          break;
+
+        default:
+          // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+          return;
+        }
       }
-    }
-
-    call.transformTo(
-        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
-            .build());
 
-    // New plan is absolutely better than old plan.
-    call.getPlanner().prune(aggregate);
+      call.transformTo(
+          relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+              .build());
+
+      // New plan is absolutely better than old plan.
+      call.getPlanner().prune(aggregate);
+    } else {
+      if (Aggregate.isSimple(aggregate)
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: Done
created_at: 2025-05-20T14:03:16Z
author_association: MEMBER
diff_hunk: @@ -72,40 +89,54 @@ public AggregateValuesRule(RelBuilderFactory relBuilderFactory) {
         .as(Config.class));
   }
 
+  //~ Methods ----------------------------------------------------------------
+
   @Override public void onMatch(RelOptRuleCall call) {
     final Aggregate aggregate = call.rel(0);
     final Values values = call.rel(1);
     Util.discard(values);
     final RelBuilder relBuilder = call.builder();
     final RexBuilder rexBuilder = relBuilder.getRexBuilder();
 
-    final List<RexLiteral> literals = new ArrayList<>();
-    for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
-      switch (aggregateCall.getAggregation().getKind()) {
-      case COUNT:
-      case SUM0:
-        literals.add(
-            rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
-        break;
-
-      case MIN:
-      case MAX:
-      case SUM:
-        literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
-        break;
-
-      default:
-        // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
-        return;
+    if (aggregate.getGroupCount() == 0 && values.getTuples().isEmpty()) {
+      final List<RexLiteral> literals = new ArrayList<>();
+      for (final AggregateCall aggregateCall : aggregate.getAggCallList()) {
+        switch (aggregateCall.getAggregation().getKind()) {
+        case COUNT:
+        case SUM0:
+          literals.add(
+              rexBuilder.makeLiteral(BigDecimal.ZERO, aggregateCall.getType()));
+          break;
+
+        case MIN:
+        case MAX:
+        case SUM:
+          literals.add(rexBuilder.makeNullLiteral(aggregateCall.getType()));
+          break;
+
+        default:
+          // Unknown what this aggregate call should do on empty Values. Bail out to be safe.
+          return;
+        }
       }
-    }
-
-    call.transformTo(
-        relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
-            .build());
 
-    // New plan is absolutely better than old plan.
-    call.getPlanner().prune(aggregate);
+      call.transformTo(
+          relBuilder.values(ImmutableList.of(literals), aggregate.getRowType())
+              .build());
+
+      // New plan is absolutely better than old plan.
+      call.getPlanner().prune(aggregate);
+    } else {
+      if (Aggregate.isSimple(aggregate)
+          && aggregate.getAggCallList().isEmpty()
+          && aggregate.getRowType().equals(values.getRowType())) {
+        List<ImmutableList<RexLiteral>> distinctValues =
+            values.getTuples().stream().distinct().collect(Collectors.toList());
+        relBuilder.values(distinctValues, values.getRowType());
+        call.transformTo(relBuilder.build());
+        call.getPlanner().prune(aggregate);
path: core/src/main/java/org/apache/calcite/rel/rules/AggregateValuesRule.java
user: login: xuzifu666
body: done
created_at: 2025-05-20T14:16:00Z
author_association: MEMBER
diff_hunk: @@ -19610,6 +19610,46 @@ LogicalProject(DEPTNO=[$0], EXPR$1=[$1], EXPR$2=[CAST(/($2, $3)):INTEGER NOT NUL
     LogicalAggregate(group=[{0, 2}], agg#0=[MIN($1)], agg#1=[MIN($3)])
       LogicalProject(DEPTNO=[$7], SAL=[$5], JOB=[$2], COMM=[$6])
         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testReduceInValues">
+    <Resource name="sql">
+      <![CDATA[SELECT deptno, sal FROM EMP WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)]]>
+    </Resource>
+    <Resource name="planBefore">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalAggregate(group=[{0}])
+        LogicalValues(tuples=[[{ 1 }, { 1 }, { 3 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }, { 1 }]])
+]]>
+    </Resource>
+    <Resource name="planAfter">
+      <![CDATA[
+LogicalProject(DEPTNO=[$7], SAL=[$5])
+  LogicalProject(EMPNO=[$0], ENAME=[$1], JOB=[$2], MGR=[$3], HIREDATE=[$4], SAL=[$5], COMM=[$6], DEPTNO=[$7], SLACKER=[$8])
+    LogicalJoin(condition=[=($7, $9)], joinType=[inner])
+      LogicalTableScan(table=[[CATALOG, SALES, EMP]])
+      LogicalValues(tuples=[[{ 1 }, { 3 }]])
+]]>
+    </Resource>
+  </TestCase>
+  <TestCase name="testReduceInValuesWithAggregateValueReduceRule">
+    <Resource name="sql">
path: core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
user: login: asolimando
body: CALCITE-7016 has been filed to track this, let's see where that discussion leads, we can gloss on this for now here
created_at: 2025-05-21T10:01:35Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: asolimando
body: > Because these are not literals, they are constant expressions. That's not the same thing.

Then I guess we should update the title of the Jira ticket to something like "ReduceExpressionsRule applied to 'IN subquery' should make the values distinct if the subquery is a Values composed of literals"?
created_at: 2025-05-21T10:06:50Z
author_association: MEMBER
diff_hunk: @@ -354,6 +354,32 @@ private static boolean skipItem(RexNode expr) {
         .check();
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
+   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
+   * if the subquery is a constant Values</a>. */
+  @Test void testReduceInValues() {
+    final String sql = "SELECT deptno, sal "
+        + "FROM EMP "
+        + "WHERE deptno IN (1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1)";
path: core/src/test/java/org/apache/calcite/test/RelOptRulesTest.java
user: login: xuzifu666
body: Make sense，I would change as your suggestion，very thanks for your review. @asolimando 
created_at: 2025-05-21T10:11:54Z
author_association: MEMBER

PR comments:
user: login: xuzifu666
created_at: 2025-05-13T11:43:06Z
body: @asolimando @xiedeyantu We can continue here, and I had extend AggregateValuesRule from https://github.com/apache/calcite/pull/4369
author_association: MEMBER
user: login: xuzifu666
created_at: 2025-05-20T09:09:17Z
body: Thanks for all comments, had modified as follow:
1. AggregateValuesRule is expanded to 2 modes, handling the default case and deduplication respectively;
2. Deduplication cannot be fully ensured for complex types such as Row, so deduplication is performed on non-complex data types such as Literal first to ensure that deduplication is effective;
3. Related tests are added
please take a look for the changes. @mihaibudiu @asolimando @xiedeyantu 
author_association: MEMBER
user: login: asolimando
created_at: 2025-05-20T10:52:44Z
body: > Thanks for all comments, had modified as follow:
> 
>     1. AggregateValuesRule is expanded to 2 modes, handling the default case and deduplication respectively;
> 
>     2. Deduplication cannot be fully ensured for complex types such as Row, so deduplication is performed on non-complex data types such as Literal first to ensure that deduplication is effective;
> 
>     3. Related tests are added
>        please take a look for the changes. @mihaibudiu @asolimando @xiedeyantu

I won't push back on this, but I was more convinced on having the two modes activated conditionally in the `onMatch` method, like in the previous version.

It's not always easy when to decide what makes two distinct rules or two optional behavior into the same one, as both approaches have pros and cons.

Separate rules put more burden to the end-user and overhead in the planner, to me it's only worth it when there is a clear logical separation on the modes, that users might want to pick selectively (e.g., I want mode `A` but not mode `B`).

Here I feel users would like any of the two optimizations, when their query matches the conditions, or none. I can't think of cases where one want one mode and not the other, so making two rules feels artificial.

What do you guys think?
author_association: MEMBER
user: login: xuzifu666
created_at: 2025-05-20T11:01:04Z
body: > > Thanks for all comments, had modified as follow:
> > ```
> > 1. AggregateValuesRule is expanded to 2 modes, handling the default case and deduplication respectively;
> > 
> > 2. Deduplication cannot be fully ensured for complex types such as Row, so deduplication is performed on non-complex data types such as Literal first to ensure that deduplication is effective;
> > 
> > 3. Related tests are added
> >    please take a look for the changes. @mihaibudiu @asolimando @xiedeyantu
> > ```
> 
> I won't push back on this, but I was more convinced on having the two modes activated conditionally in the `onMatch` method, like in the previous version.
> 
> It's not always easy when to decide what makes two distinct rules or two optional behavior into the same one, as both approaches have pros and cons.
> 
> Separate rules put more burden to the end-user and overhead in the planner, to me it's only worth it when there is a clear logical separation on the modes, that users might want to pick selectively (e.g., I want mode `A` but not mode `B`).
> 
> Here I feel users would like any of the two optimizations, when their query matches the conditions, or none. I can't think of cases where one want one mode and not the other, so making two rules feels artificial.
> 
> What do you guys think?

@asolimando Thanks for your detailed review and I agree with you, I would changed it to previous version with address all comments.
author_association: MEMBER
user: login: xuzifu666
created_at: 2025-05-20T12:27:03Z
body: > A few minor improvements, merging the two modes is a separate discussion.

@asolimando I had addressed all comments, PTAL~
author_association: MEMBER
user: login: xuzifu666
created_at: 2025-05-22T13:44:28Z
body: > LGTM, can you please squash the commits into one @xuzifu666 making sure the commit title matches the usual format and the associated jira ticket title?

Done，had squashed commits and modified title，thanks for your detailed review！ @asolimando 
author_association: MEMBER
user: login: sonarqubecloud[bot]
created_at: 2025-05-22T14:13:35Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=apache_calcite&pullRequest=4371) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=apache_calcite&pullRequest=4371&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=apache_calcite&pullRequest=4371&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=apache_calcite&pullRequest=4371&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [90.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=apache_calcite&pullRequest=4371&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=apache_calcite&pullRequest=4371&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=apache_calcite&pullRequest=4371)


author_association: NONE