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

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 2c3dc22779ca8df754a79e3939e4feeee5069dab
Author: Julian Hyde <[email protected]>
AuthorDate: Mon Jul 13 10:23:02 2020 -0700

    Refactor SqlValidatorImpl: combine whereScopes, groupByScopes etc. into one 
field, clauseScopes
    
    Add utility class IdPair.
    
    Refactor how AggregatingSelectScope uses GroupAnalyzer while
    resolving types.
    
    Add method SqlNameMatcher.indexOf.
    
    Other minor refactoring.
---
 .../apache/calcite/sql/SqlInternalOperator.java    |  4 +-
 .../apache/calcite/sql/SqlWindowTableFunction.java | 12 ++--
 .../org/apache/calcite/sql/type/InferTypes.java    |  5 +-
 .../java/org/apache/calcite/sql/util/IdPair.java   | 63 ++++++++++++++++++
 .../sql/validate/AggregatingSelectScope.java       | 74 ++++++++++------------
 .../calcite/sql/validate/SqlNameMatcher.java       |  7 ++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 68 ++++++++------------
 .../calcite/sql/validate/SqlValidatorUtil.java     | 13 +---
 .../calcite/sql/validate/SqlValidatorUtilTest.java |  6 ++
 .../apache/calcite/test/SqlToRelConverterTest.java |  2 +-
 .../java/org/apache/calcite/util/UtilTest.java     | 45 +++++++++++++
 11 files changed, 191 insertions(+), 108 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/sql/SqlInternalOperator.java 
b/core/src/main/java/org/apache/calcite/sql/SqlInternalOperator.java
index 5481fbe..e72f937 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlInternalOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlInternalOperator.java
@@ -74,8 +74,8 @@ public class SqlInternalOperator extends SqlSpecialOperator {
 
   //~ Methods ----------------------------------------------------------------
 
-  public SqlSyntax getSyntax() {
-    return SqlSyntax.FUNCTION;
+  @Override public SqlSyntax getSyntax() {
+    return SqlSyntax.INTERNAL;
   }
 
   @Override public RelDataType deriveType(SqlValidator validator,
diff --git 
a/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java 
b/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java
index f17a383..a1670c9 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java
@@ -67,13 +67,13 @@ public class SqlWindowTableFunction extends SqlFunction
   }
 
   protected void validateColumnNames(SqlValidator validator,
-      List<String> fieldNames, List<SqlNode> unvalidatedColumnNames) {
+      List<String> fieldNames, List<SqlNode> columnNames) {
     final SqlNameMatcher matcher = validator.getCatalogReader().nameMatcher();
-    for (SqlNode descOperand : unvalidatedColumnNames) {
-      final String colName = ((SqlIdentifier) descOperand).getSimple();
-      if (matcher.frequency(fieldNames, colName) == 0) {
-        throw SqlUtil.newContextException(descOperand.getParserPosition(),
-            RESOURCE.unknownIdentifier(colName));
+    for (SqlNode columnName : columnNames) {
+      final String name = ((SqlIdentifier) columnName).getSimple();
+      if (matcher.indexOf(fieldNames, name) < 0) {
+        throw SqlUtil.newContextException(columnName.getParserPosition(),
+            RESOURCE.unknownIdentifier(name));
       }
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/sql/type/InferTypes.java 
b/core/src/main/java/org/apache/calcite/sql/type/InferTypes.java
index a869465..5983447 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/InferTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/InferTypes.java
@@ -22,6 +22,7 @@ import org.apache.calcite.sql.SqlNode;
 
 import com.google.common.collect.ImmutableList;
 
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -55,9 +56,7 @@ public abstract class InferTypes {
         // unknown types for incomplete expressions.
         // Maybe we need to distinguish the two kinds of unknown.
         //assert !knownType.equals(unknownType);
-        for (int i = 0; i < operandTypes.length; ++i) {
-          operandTypes[i] = knownType;
-        }
+        Arrays.fill(operandTypes, knownType);
       };
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/sql/util/IdPair.java 
b/core/src/main/java/org/apache/calcite/sql/util/IdPair.java
new file mode 100644
index 0000000..9cd211d
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/sql/util/IdPair.java
@@ -0,0 +1,63 @@
+/*
+ * 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.calcite.sql.util;
+
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** Similar to {@link org.apache.calcite.util.Pair} but identity is based
+ * on identity of values.
+ *
+ * <p>Also, uses {@code hashCode} algorithm of {@link List},
+ * not {@link Map.Entry#hashCode()}.
+ *
+ * @param <L> Left type
+ * @param <R> Right type
+ */
+public class IdPair<L, R> {
+  final L left;
+  final R right;
+
+  /** Creates an IdPair. */
+  public static <L, R> IdPair<L, R> of(L left, R right) {
+    return new IdPair<>(left, right);
+  }
+
+  protected IdPair(L left, R right) {
+    this.left = Objects.requireNonNull(left);
+    this.right = Objects.requireNonNull(right);
+  }
+
+  @Override public String toString() {
+    return left + "=" + right;
+  }
+
+  @Override public boolean equals(Object obj) {
+    return obj == this
+        || obj instanceof IdPair
+        && left == ((IdPair) obj).left
+        && right == ((IdPair) obj).right;
+  }
+
+  @Override public int hashCode() {
+    return (31
+        + System.identityHashCode(left)) * 31
+        + System.identityHashCode(right);
+  }
+}
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
 
b/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
index 415a21f..b61fec5 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/validate/AggregatingSelectScope.java
@@ -32,7 +32,6 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.SortedMap;
@@ -54,19 +53,11 @@ public class AggregatingSelectScope
   private final SqlSelect select;
   private final boolean distinct;
 
-  /** Use while under construction. */
-  private List<SqlNode> temporaryGroupExprList;
+  /** Use while resolving. */
+  private SqlValidatorUtil.GroupAnalyzer groupAnalyzer;
 
   public final Supplier<Resolved> resolved =
-      Suppliers.memoize(() -> {
-        assert temporaryGroupExprList == null;
-        temporaryGroupExprList = new ArrayList<>();
-        try {
-          return resolve();
-        } finally {
-          temporaryGroupExprList = null;
-        }
-      })::get;
+      Suppliers.memoize(this::resolve)::get;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -92,36 +83,37 @@ public class AggregatingSelectScope
   //~ Methods ----------------------------------------------------------------
 
   private Resolved resolve() {
-    final ImmutableList.Builder<ImmutableList<ImmutableBitSet>> builder =
-        ImmutableList.builder();
-    List<SqlNode> extraExprs = ImmutableList.of();
-    Map<Integer, Integer> groupExprProjection = ImmutableMap.of();
-    if (select.getGroup() != null) {
-      final SqlNodeList groupList = select.getGroup();
-      final SqlValidatorUtil.GroupAnalyzer groupAnalyzer =
-          new SqlValidatorUtil.GroupAnalyzer(temporaryGroupExprList);
-      for (SqlNode groupExpr : groupList) {
-        SqlValidatorUtil.analyzeGroupItem(this, groupAnalyzer, builder,
-            groupExpr);
+    assert groupAnalyzer == null : "resolve already in progress";
+    groupAnalyzer = new SqlValidatorUtil.GroupAnalyzer();
+    try {
+      final ImmutableList.Builder<ImmutableList<ImmutableBitSet>> builder =
+          ImmutableList.builder();
+      if (select.getGroup() != null) {
+        final SqlNodeList groupList = select.getGroup();
+        for (SqlNode groupExpr : groupList) {
+          SqlValidatorUtil.analyzeGroupItem(this, groupAnalyzer, builder,
+              groupExpr);
+        }
       }
-      extraExprs = groupAnalyzer.extraExprs;
-      groupExprProjection = groupAnalyzer.groupExprProjection;
-    }
 
-    final SortedMap<ImmutableBitSet, Integer> flatGroupSetCount =
-        Maps.newTreeMap(ImmutableBitSet.COMPARATOR);
-    for (List<ImmutableBitSet> groupSet : Linq4j.product(builder.build())) {
-      final ImmutableBitSet set = ImmutableBitSet.union(groupSet);
-      flatGroupSetCount.put(set, flatGroupSetCount.getOrDefault(set, 0) + 1);
-    }
+      final SortedMap<ImmutableBitSet, Integer> flatGroupSetCount =
+          Maps.newTreeMap(ImmutableBitSet.COMPARATOR);
+      for (List<ImmutableBitSet> groupSet : Linq4j.product(builder.build())) {
+        final ImmutableBitSet set = ImmutableBitSet.union(groupSet);
+        flatGroupSetCount.put(set, flatGroupSetCount.getOrDefault(set, 0) + 1);
+      }
 
-    // For GROUP BY (), we need a singleton grouping set.
-    if (flatGroupSetCount.isEmpty()) {
-      flatGroupSetCount.put(ImmutableBitSet.of(), 1);
-    }
+      // For GROUP BY (), we need a singleton grouping set.
+      if (flatGroupSetCount.isEmpty()) {
+        flatGroupSetCount.put(ImmutableBitSet.of(), 1);
+      }
 
-    return new Resolved(extraExprs, temporaryGroupExprList, 
flatGroupSetCount.keySet(),
-        flatGroupSetCount, groupExprProjection);
+      return new Resolved(groupAnalyzer.extraExprs, groupAnalyzer.groupExprs,
+          flatGroupSetCount.keySet(), flatGroupSetCount,
+          groupAnalyzer.groupExprProjection);
+    } finally {
+      groupAnalyzer = null;
+    }
   }
 
   /**
@@ -149,10 +141,10 @@ public class AggregatingSelectScope
       }
       return Pair.of(ImmutableList.of(), groupExprs.build());
     } else if (select.getGroup() != null) {
-      if (temporaryGroupExprList != null) {
+      if (groupAnalyzer != null) {
         // we are in the middle of resolving
         return Pair.of(ImmutableList.of(),
-            ImmutableList.copyOf(temporaryGroupExprList));
+            ImmutableList.copyOf(groupAnalyzer.groupExprs));
       } else {
         final Resolved resolved = this.resolved.get();
         return Pair.of(resolved.extraExprList, resolved.groupExprList);
@@ -232,7 +224,7 @@ public class AggregatingSelectScope
   /** Information about an aggregating scope that can only be determined
    * after validation has occurred. Therefore it cannot be populated when
    * the scope is created. */
-  public class Resolved {
+  public static class Resolved {
     public final ImmutableList<SqlNode> extraExprList;
     public final ImmutableList<SqlNode> groupExprList;
     public final ImmutableBitSet groupSet;
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
index 8276666..14a905b 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlNameMatcher.java
@@ -19,6 +19,8 @@ package org.apache.calcite.sql.validate;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
 
+import com.google.common.collect.Iterables;
+
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -64,6 +66,11 @@ public interface SqlNameMatcher {
    * <p>Similar to {@link java.util.Collections#frequency}. */
   int frequency(Iterable<String> names, String name);
 
+  /** Returns the index of the first element of a collection that matches. */
+  default int indexOf(Iterable<String> names, String name) {
+    return Iterables.indexOf(names, n -> matches(n, name));
+  }
+
   /** Creates a set that has the same case-sensitivity as this matcher. */
   Set<String> createSet();
 }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index 0919ee2..19e0e21 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -88,6 +88,7 @@ import org.apache.calcite.sql.type.SqlOperandTypeInference;
 import org.apache.calcite.sql.type.SqlTypeCoercionRule;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.util.IdPair;
 import org.apache.calcite.sql.util.SqlBasicVisitor;
 import org.apache.calcite.sql.util.SqlShuttle;
 import org.apache.calcite.sql.util.SqlVisitor;
@@ -182,37 +183,10 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       new IdentityHashMap<>();
 
   /**
-   * Maps a {@link SqlSelect} node to the scope used by its WHERE and HAVING
-   * clauses.
+   * Maps a {@link SqlSelect} and a clause to the scope used by that clause.
    */
-  private final Map<SqlSelect, SqlValidatorScope> whereScopes =
-      new IdentityHashMap<>();
-
-  /**
-   * Maps a {@link SqlSelect} node to the scope used by its GROUP BY clause.
-   */
-  private final Map<SqlSelect, SqlValidatorScope> groupByScopes =
-      new IdentityHashMap<>();
-
-  /**
-   * Maps a {@link SqlSelect} node to the scope used by its SELECT and HAVING
-   * clauses.
-   */
-  private final Map<SqlSelect, SqlValidatorScope> selectScopes =
-      new IdentityHashMap<>();
-
-  /**
-   * Maps a {@link SqlSelect} node to the scope used by its ORDER BY clause.
-   */
-  private final Map<SqlSelect, SqlValidatorScope> orderScopes =
-      new IdentityHashMap<>();
-
-  /**
-   * Maps a {@link SqlSelect} node that is the argument to a CURSOR
-   * constructor to the scope of the result of that select node.
-   */
-  private final Map<SqlSelect, SqlValidatorScope> cursorScopes =
-      new IdentityHashMap<>();
+  private final Map<IdPair<SqlSelect, Clause>, SqlValidatorScope>
+      clauseScopes = new HashMap<>();
 
   /**
    * The name-resolution scope of a LATERAL TABLE clause.
@@ -377,7 +351,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     // that is the argument to the cursor constructor; register it
     // with a scope corresponding to the cursor
     SelectScope cursorScope = new SelectScope(parentScope, null, select);
-    cursorScopes.put(select, cursorScope);
+    clauseScopes.put(IdPair.of(select, Clause.CURSOR), cursorScope);
     final SelectNamespace selectNs = createSelectNamespace(select, select);
     String alias = deriveAlias(select, nextGeneratedId++);
     registerNamespace(cursorScope, alias, selectNs, false);
@@ -1099,15 +1073,15 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
   }
 
   public SqlValidatorScope getCursorScope(SqlSelect select) {
-    return cursorScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.CURSOR));
   }
 
   public SqlValidatorScope getWhereScope(SqlSelect select) {
-    return whereScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.WHERE));
   }
 
   public SqlValidatorScope getSelectScope(SqlSelect select) {
-    return selectScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.SELECT));
   }
 
   public SelectScope getRawSelectScope(SqlSelect select) {
@@ -1120,12 +1094,12 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
 
   public SqlValidatorScope getHavingScope(SqlSelect select) {
     // Yes, it's the same as getSelectScope
-    return selectScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.SELECT));
   }
 
   public SqlValidatorScope getGroupScope(SqlSelect select) {
     // Yes, it's the same as getWhereScope
-    return groupByScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.GROUP_BY));
   }
 
   public SqlValidatorScope getFromScope(SqlSelect select) {
@@ -1133,7 +1107,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
   }
 
   public SqlValidatorScope getOrderScope(SqlSelect select) {
-    return orderScopes.get(select);
+    return clauseScopes.get(IdPair.of(select, Clause.ORDER));
   }
 
   public SqlValidatorScope getMatchRecognizeScope(SqlMatchRecognize node) {
@@ -2541,7 +2515,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       scopes.put(select, selectScope);
 
       // Start by registering the WHERE clause
-      whereScopes.put(select, selectScope);
+      clauseScopes.put(IdPair.of(select, Clause.WHERE), selectScope);
       registerOperandSubQueries(
           selectScope,
           select,
@@ -2575,14 +2549,14 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       if (isAggregate(select)) {
         aggScope =
             new AggregatingSelectScope(selectScope, select, false);
-        selectScopes.put(select, aggScope);
+        clauseScopes.put(IdPair.of(select, Clause.SELECT), aggScope);
       } else {
-        selectScopes.put(select, selectScope);
+        clauseScopes.put(IdPair.of(select, Clause.SELECT), selectScope);
       }
       if (select.getGroup() != null) {
         GroupByScope groupByScope =
             new GroupByScope(selectScope, select.getGroup(), select);
-        groupByScopes.put(select, groupByScope);
+        clauseScopes.put(IdPair.of(select, Clause.GROUP_BY), groupByScope);
         registerSubQueries(groupByScope, select.getGroup());
       }
       registerOperandSubQueries(
@@ -2600,7 +2574,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
         }
         OrderByScope orderScope =
             new OrderByScope(aggScope, orderList, select);
-        orderScopes.put(select, orderScope);
+        clauseScopes.put(IdPair.of(select, Clause.ORDER), orderScope);
         registerSubQueries(orderScope, orderList);
 
         if (!isAggregate(select)) {
@@ -2758,7 +2732,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
       // validation check
       if (mergeCall.getUpdateCall() != null) {
         registerQuery(
-            whereScopes.get(mergeCall.getSourceSelect()),
+            clauseScopes.get(IdPair.of(mergeCall.getSourceSelect(), 
Clause.WHERE)),
             null,
             mergeCall.getUpdateCall(),
             enclosingNode,
@@ -6643,4 +6617,12 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     VALID
   }
 
+  /** Allows {@link #clauseScopes} to have multiple values per SELECT. */
+  private enum Clause {
+    WHERE,
+    GROUP_BY,
+    SELECT,
+    ORDER,
+    CURSOR
+  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index bd24da5..2874a3c 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -1296,19 +1296,8 @@ public class SqlValidatorUtil {
     /** Extra expressions, computed from the input as extra GROUP BY
      * expressions. For example, calls to the {@code TUMBLE} functions. */
     final List<SqlNode> extraExprs = new ArrayList<>();
-    final List<SqlNode> groupExprs;
+    final List<SqlNode> groupExprs = new ArrayList<>();
     final Map<Integer, Integer> groupExprProjection = new HashMap<>();
-    int groupCount;
-
-    GroupAnalyzer(List<SqlNode> groupExprs) {
-      this.groupExprs = groupExprs;
-    }
-
-    SqlNode createGroupExpr() {
-      // TODO: create an expression that could have no other source
-      return SqlLiteral.createCharString("xyz" + groupCount++,
-          SqlParserPos.ZERO);
-    }
   }
 
   /**
diff --git 
a/core/src/test/java/org/apache/calcite/sql/validate/SqlValidatorUtilTest.java 
b/core/src/test/java/org/apache/calcite/sql/validate/SqlValidatorUtilTest.java
index f3d6fb6..86e2dec 100644
--- 
a/core/src/test/java/org/apache/calcite/sql/validate/SqlValidatorUtilTest.java
+++ 
b/core/src/test/java/org/apache/calcite/sql/validate/SqlValidatorUtilTest.java
@@ -148,10 +148,16 @@ class SqlValidatorUtilTest {
         SqlNameMatchers.withCaseSensitive(false);
     assertThat(insensitiveMatcher.frequency(beatles, "ringo"), is(2));
     assertThat(insensitiveMatcher.frequency(beatles, "rinGo"), is(2));
+    assertThat(insensitiveMatcher.indexOf(beatles, "rinGo"), is(2));
+    assertThat(insensitiveMatcher.indexOf(beatles, "stuart"), is(-1));
     final SqlNameMatcher sensitiveMatcher =
         SqlNameMatchers.withCaseSensitive(true);
     assertThat(sensitiveMatcher.frequency(beatles, "ringo"), is(1));
     assertThat(sensitiveMatcher.frequency(beatles, "rinGo"), is(1));
     assertThat(sensitiveMatcher.frequency(beatles, "Ringo"), is(0));
+    assertThat(sensitiveMatcher.indexOf(beatles, "ringo"), is(2));
+    assertThat(sensitiveMatcher.indexOf(beatles, "rinGo"), is(3));
+    assertThat(sensitiveMatcher.indexOf(beatles, "Ringo"), is(-1));
+
   }
 }
diff --git 
a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
index 0a146b8..6eade46 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java
@@ -3768,7 +3768,7 @@ class SqlToRelConverterTest extends SqlToRelTestBase {
   /** Test case for:
    * <a 
href="https://issues.apache.org/jira/browse/CALCITE-3310";>[CALCITE-3310]
    * Approximate and exact aggregate calls are recognized as the same
-   * during sql-to-rel conversion.</a>.
+   * during sql-to-rel conversion</a>.
    */
   @Test void testProjectApproximateAndExactAggregates() {
     final String sql = "SELECT empno, count(distinct ename),\n"
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java 
b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index fd6c1ea..627ad36 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -31,6 +31,7 @@ import org.apache.calcite.runtime.SqlFunctions;
 import org.apache.calcite.runtime.Utilities;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.dialect.CalciteSqlDialect;
+import org.apache.calcite.sql.util.IdPair;
 import org.apache.calcite.sql.util.SqlBuilder;
 import org.apache.calcite.sql.util.SqlString;
 import org.apache.calcite.test.DiffTestCase;
@@ -39,6 +40,7 @@ import org.apache.calcite.testlib.annotations.LocaleEnUs;
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMultiset;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
@@ -105,6 +107,8 @@ import static org.hamcrest.CoreMatchers.startsWith;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -1116,6 +1120,47 @@ class UtilTest {
     assertThat(list2.appendAll(list2), is(Arrays.asList(1, 3, 5, 1, 3, 5)));
   }
 
+  /** Unit test for {@link IdPair}. */
+  @Test void testIdPair() {
+    final IdPair<Integer, Integer> p0OneTwo = IdPair.of(1, 2);
+    final IdPair<Integer, Integer> p1OneTwo = IdPair.of(1, 2);
+    final IdPair<Integer, Integer> p1TwoOne = IdPair.of(2, 1);
+    assertEquals(p0OneTwo, p0OneTwo);
+    assertEquals(p0OneTwo, p1OneTwo);
+    assertEquals(p0OneTwo.hashCode(), p1OneTwo.hashCode());
+    assertNotEquals(p0OneTwo, p1TwoOne);
+
+    final String s0 = "xy";
+
+    // p0s0One and p1s0One are different objects but are equal because their
+    // contents are the same objects
+    final IdPair<String, Integer> p0s0One = IdPair.of(s0, 1);
+    final IdPair<String, Integer> p1s0One = IdPair.of(s0, 1);
+    assertNotSame(p0s0One, p1s0One); // different objects, but are equal
+    assertEquals(p0s0One, p0s0One);
+    assertEquals(p0s0One, p1s0One);
+    assertEquals(p1s0One, p0s0One);
+    assertEquals(p0s0One.hashCode(), p1s0One.hashCode());
+
+    // A copy of "s0" that is equal but not the same object
+    final String s1 = s0.toUpperCase(Locale.ROOT).toLowerCase(Locale.ROOT);
+    assertEquals(s0, s1);
+    assertNotSame(s0, s1);
+
+    // p0s1One is not equal to p0s0One because s1 is not the same object as s0
+    final IdPair<String, Integer> p0s1One = IdPair.of(s1, 1);
+    assertNotEquals(p0s0One, p0s1One);
+    assertEquals(p0s1One.hashCode(), p0s1One.hashCode());
+
+    final Set<IdPair<?, ?>> set =
+        ImmutableSet.of(p0OneTwo, p1OneTwo, p1TwoOne,
+            p0s0One, p1s0One, p0s1One);
+    assertThat(set.size(), is(4));
+    final String[] expected = {"1=2", "2=1", "xy=1", "xy=1"};
+    assertThat(set.stream().map(IdPair::toString).sorted().toArray(),
+        is(expected));
+  }
+
   /**
    * Unit test for {@link IntegerIntervalSet}.
    */

Reply via email to