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}. */
