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

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


The following commit(s) were added to refs/heads/master by this push:
     new a551d4b  Revert "[CALCITE-4056] Remove Digest from RelNode and RexCall"
a551d4b is described below

commit a551d4bfa3b97c42e74fe4e30ca66ba493719c96
Author: yuzhao.cyz <yuzhao....@gmail.com>
AuthorDate: Thu Jun 18 14:39:38 2020 +0800

    Revert "[CALCITE-4056] Remove Digest from RelNode and RexCall"
    
    This reverts commit b00c1fd6
---
 .../adapter/enumerable/EnumerableAggregate.java    |   8 -
 .../adapter/enumerable/EnumerableFilter.java       |   8 -
 .../adapter/enumerable/EnumerableHashJoin.java     |   8 -
 .../adapter/enumerable/EnumerableMergeJoin.java    |   8 -
 .../enumerable/EnumerableNestedLoopJoin.java       |   8 -
 .../adapter/enumerable/EnumerableProject.java      |   8 -
 .../enumerable/EnumerableSortedAggregate.java      |   8 -
 .../main/java/org/apache/calcite/plan/Digest.java  | 256 +++++++++++++++++++++
 .../java/org/apache/calcite/plan/RelDigest.java    |  52 -----
 .../java/org/apache/calcite/plan/RelOptNode.java   |  15 +-
 .../java/org/apache/calcite/plan/RelOptUtil.java   |   1 -
 .../org/apache/calcite/plan/hep/HepPlanner.java    |  12 +-
 .../org/apache/calcite/plan/hep/HepRelVertex.java  |  15 +-
 .../org/apache/calcite/plan/volcano/RelSubset.java |  36 +--
 .../calcite/plan/volcano/VolcanoPlanner.java       |  55 ++---
 .../org/apache/calcite/rel/AbstractRelNode.java    | 135 ++---------
 .../main/java/org/apache/calcite/rel/RelNode.java  |   7 +-
 .../java/org/apache/calcite/rel/SingleRel.java     |   1 -
 .../org/apache/calcite/rel/core/Aggregate.java     |  21 --
 .../java/org/apache/calcite/rel/core/Filter.java   |  19 --
 .../java/org/apache/calcite/rel/core/Join.java     |  22 --
 .../java/org/apache/calcite/rel/core/Project.java  |  21 --
 .../java/org/apache/calcite/rel/core/Values.java   |  18 --
 .../java/org/apache/calcite/rel/core/Window.java   |   5 +-
 .../calcite/rel/logical/LogicalAggregate.java      |   8 -
 .../apache/calcite/rel/logical/LogicalFilter.java  |  12 -
 .../apache/calcite/rel/logical/LogicalJoin.java    |  13 --
 .../apache/calcite/rel/logical/LogicalProject.java |   8 -
 .../rel/metadata/JaninoRelMetadataProvider.java    |   7 +-
 .../org/apache/calcite/rel/rules/MultiJoin.java    |   1 -
 .../main/java/org/apache/calcite/rex/RexCall.java  |  18 +-
 .../org/apache/calcite/rex/RexDynamicParam.java    |   6 +-
 .../java/org/apache/calcite/rex/RexLiteral.java    |   8 +-
 .../main/java/org/apache/calcite/rex/RexNode.java  |   5 -
 .../main/java/org/apache/calcite/rex/RexOver.java  |   6 +-
 .../java/org/apache/calcite/rex/RexSubQuery.java   |   8 +-
 .../org/apache/calcite/test/HepPlannerTest.java    |   2 +-
 37 files changed, 354 insertions(+), 495 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
index 9967e2a..a0dee72 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableAggregate.java
@@ -105,14 +105,6 @@ public class EnumerableAggregate extends Aggregate 
implements EnumerableRel {
     }
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
     final JavaTypeFactory typeFactory = implementor.getTypeFactory();
     final BlockBuilder builder = new BlockBuilder();
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableFilter.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableFilter.java
index 8d26c7e..5af195c 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableFilter.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableFilter.java
@@ -71,14 +71,6 @@ public class EnumerableFilter
     return new EnumerableFilter(getCluster(), traitSet, input, condition);
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
     // EnumerableCalc is always better
     throw new UnsupportedOperationException();
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
index 41d5d50..918919e 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableHashJoin.java
@@ -166,14 +166,6 @@ public class EnumerableHashJoin extends Join implements 
EnumerableRel {
     }
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   @Override public Result implement(EnumerableRelImplementor implementor, 
Prefer pref) {
     switch (joinType) {
     case SEMI:
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
index d9e04c4..f9f4d1a 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableMergeJoin.java
@@ -130,14 +130,6 @@ public class EnumerableMergeJoin extends Join implements 
EnumerableRel {
         CorrelationId.setOf(variablesStopped), joinType);
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
     // Required collation keys can be subset or superset of merge join keys.
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
index 0bd8aef..1561d8f 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableNestedLoopJoin.java
@@ -121,14 +121,6 @@ public class EnumerableNestedLoopJoin extends Join 
implements EnumerableRel {
     return cost;
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
     // EnumerableNestedLoopJoin traits passdown shall only pass through 
collation to
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
index 5bc8a8b..7c62e2f 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableProject.java
@@ -84,14 +84,6 @@ public class EnumerableProject extends Project implements 
EnumerableRel {
         projects, rowType);
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   public Result implement(EnumerableRelImplementor implementor, Prefer pref) {
     // EnumerableCalcRel is always better
     throw new UnsupportedOperationException();
diff --git 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableSortedAggregate.java
 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableSortedAggregate.java
index f341921..00ca78a 100644
--- 
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableSortedAggregate.java
+++ 
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableSortedAggregate.java
@@ -56,14 +56,6 @@ public class EnumerableSortedAggregate extends Aggregate 
implements EnumerableRe
         groupSet, groupSets, aggCalls);
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
-
   @Override public Pair<RelTraitSet, List<RelTraitSet>> passThroughTraits(
       final RelTraitSet required) {
     if (!isSimple(this)) {
diff --git a/core/src/main/java/org/apache/calcite/plan/Digest.java 
b/core/src/main/java/org/apache/calcite/plan/Digest.java
new file mode 100644
index 0000000..494cb14
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/plan/Digest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.plan;
+
+import org.apache.calcite.rel.RelNode;
+import org.apache.calcite.rel.hint.Hintable;
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.util.Pair;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+
+/**
+ * A short description of relational expression's type, inputs, and
+ * other properties. The digest uniquely identifies the node; another node
+ * is equivalent if and only if it has the same value.
+ *
+ * <p>Row type is part of the digest for the rare occasion that similar
+ * expressions have different types, e.g. variants of
+ * {@code Project(child=rel#1, a=null)} where a is a null INTEGER or a
+ * null VARCHAR(10). Row type is represented as fieldTypes only, so {@code 
RelNode}
+ * that differ with field names only are treated equal.
+ * For instance, {@code Project(input=rel#1,empid=$0)} and {@code 
Project(input=rel#1,deptno=$0)}
+ * are equal.
+ *
+ * <p>Computed by {@code org.apache.calcite.rel.AbstractRelNode#computeDigest},
+ * assigned by {@link org.apache.calcite.rel.AbstractRelNode#onRegister},
+ * returned by {@link org.apache.calcite.rel.AbstractRelNode#getDigest()}.
+ */
+public class Digest implements Comparable<Digest> {
+
+  //~ Instance fields --------------------------------------------------------
+
+  private final int hashCode;
+  private final List<Pair<String, Object>> items;
+  private final RelNode rel;
+
+  // Used for debugging, computed lazily.
+  private String digest = null;
+
+  //~ Constructors -----------------------------------------------------------
+
+  /**
+   * Creates a digest with given rel and properties.
+   *
+   * @param rel   The rel
+   * @param items The properties, e.g. the inputs, the type, the traits and so 
on
+   */
+  private Digest(RelNode rel, List<Pair<String, Object>> items) {
+    this.rel = rel;
+    this.items = normalizeContents(items);
+    this.hashCode = computeIdentity(rel, this.items);
+  }
+
+  /**
+   * Creates a digest with given rel, the digest is computed as simple,
+   * see {@link #simpleRelDigest(RelNode)}.
+   */
+  private Digest(RelNode rel) {
+    this(rel, simpleRelDigest(rel));
+  }
+
+  /** Creates a digest with given rel and string format digest. */
+  private Digest(RelNode rel, String digest) {
+    this.rel = rel;
+    this.items = Collections.emptyList();
+    this.digest = digest;
+    this.hashCode = this.digest.hashCode();
+  }
+
+  /** Returns the identity of this digest which is used to speedup hashCode 
and equals. */
+  private static int computeIdentity(RelNode rel, List<Pair<String, Object>> 
contents) {
+    return Objects.hash(collect(rel, contents, false));
+  }
+
+  /**
+   * Collects the items used for {@link #hashCode} and {@link #equals}.
+   *
+   * <p>Generally, the items used for hashCode and equals should be the same. 
The exception
+   * is the row type of the relational expression: the row type is needed 
because during
+   * planning, new equivalent rels may be produced with changed fields 
nullability
+   * (i.e. most of them comes from the rex simplify or constant reduction).
+   * This expects to be rare case, so the hashcode is computed without row type
+   * but when it conflicts, we compare with the row type involved(sans field 
names).
+   *
+   * @param rel      The rel to compute digest
+   * @param contents The rel properties should be considered in digest
+   * @param withType Whether to involve the row type
+   */
+  private static Object[] collect(
+      RelNode rel,
+      List<Pair<String, Object>> contents,
+      boolean withType) {
+    List<Object> hashCodeItems = new ArrayList<>();
+    // The type name.
+    hashCodeItems.add(rel.getRelTypeName());
+    // The traits.
+    hashCodeItems.addAll(rel.getTraitSet());
+    // The hints.
+    if (rel instanceof Hintable) {
+      hashCodeItems.addAll(((Hintable) rel).getHints());
+    }
+    if (withType) {
+      // The row type sans field names.
+      RelDataType relType = rel.getRowType();
+      if (relType.isStruct()) {
+        hashCodeItems.addAll(Pair.right(relType.getFieldList()));
+      } else {
+        // Make a decision here because
+        // some downstream projects have custom rel type which has no explicit 
fields.
+        hashCodeItems.add(relType);
+      }
+    }
+    // The rel node contents(e.g. the inputs or exprs).
+    hashCodeItems.addAll(contents);
+    return hashCodeItems.toArray();
+  }
+
+  /** Normalizes the rel node properties, currently, just to replace the
+   * {@link RelNode} with a simple string format digest. **/
+  private static List<Pair<String, Object>> normalizeContents(
+      List<Pair<String, Object>> items) {
+    List<Pair<String, Object>> normalized = new ArrayList<>();
+    for (Pair<String, Object> item : items) {
+      if (item.right instanceof RelNode) {
+        RelNode input = (RelNode) item.right;
+        normalized.add(Pair.of(item.left, simpleRelDigest(input)));
+      } else {
+        normalized.add(item);
+      }
+    }
+    return normalized;
+  }
+
+  /**
+   * Returns a simple string format digest.
+   *
+   * <p>Currently, returns composition of class name and id.
+   *
+   * @param rel The rel
+   */
+  private static String simpleRelDigest(RelNode rel) {
+    return rel.getRelTypeName() + '#' + rel.getId();
+  }
+
+  @Override public String toString() {
+    if (null != digest) {
+      return digest;
+    }
+    StringBuilder sb = new StringBuilder();
+    sb.append(rel.getRelTypeName());
+
+    for (RelTrait trait : rel.getTraitSet()) {
+      sb.append('.');
+      sb.append(trait.toString());
+    }
+
+    sb.append('(');
+    int j = 0;
+    for (Pair<String, Object> item : items) {
+      if (j++ > 0) {
+        sb.append(',');
+      }
+      sb.append(item.left);
+      sb.append('=');
+      sb.append(item.right);
+    }
+    sb.append(')');
+    digest = sb.toString();
+    return digest;
+  }
+
+  @Override public int compareTo(@Nonnull Digest other) {
+    return this.equals(other) ? 0 : this.rel.getId() - other.rel.getId();
+  }
+
+  @Override public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    Digest that = (Digest) o;
+    return hashCode == that.hashCode && deepEquals(that);
+  }
+
+  /**
+   * The method is used to resolve hash conflict, in current 6000+ tests, 
there are about 8
+   * tests with conflict, so we do not cache the hash code items in order to
+   * reduce mem consumption.
+   */
+  private boolean deepEquals(Digest other) {
+    Object[] thisItems = collect(this.rel, this.items, true);
+    Object[] thatItems = collect(other.rel, other.items, true);
+    if (thisItems.length != thatItems.length) {
+      return false;
+    }
+    for (int i = 0; i < thisItems.length; i++) {
+      if (!Objects.equals(thisItems[i], thatItems[i])) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override public int hashCode() {
+    return hashCode;
+  }
+
+  /**
+   * Creates a digest with given rel and properties.
+   */
+  public static Digest create(RelNode rel, List<Pair<String, Object>> 
contents) {
+    return new Digest(rel, contents);
+  }
+
+  /**
+   * Creates a digest with given rel.
+   */
+  public static Digest create(RelNode rel) {
+    return new Digest(rel);
+  }
+
+  /**
+   * Creates a digest with given rel and string format digest
+   */
+  public static Digest create(RelNode rel, String digest) {
+    return new Digest(rel, digest);
+  }
+
+  /**
+   * Instantiates a digest with solid string format digest, this digest should 
only
+   * be used as a initial.
+   */
+  public static Digest initial(RelNode rel) {
+    return new Digest(rel, simpleRelDigest(rel));
+  }
+}
diff --git a/core/src/main/java/org/apache/calcite/plan/RelDigest.java 
b/core/src/main/java/org/apache/calcite/plan/RelDigest.java
deleted file mode 100644
index d2716b1..0000000
--- a/core/src/main/java/org/apache/calcite/plan/RelDigest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.plan;
-
-import org.apache.calcite.rel.AbstractRelNode;
-import org.apache.calcite.rel.RelNode;
-import org.apache.calcite.rel.RelWriter;
-
-import org.apiguardian.api.API;
-
-/**
- * The digest is the exact representation of the corresponding {@code RelNode},
- * at anytime, anywhere. The only difference is that digest is compared using
- * {@code #equals} and {@code #hashCode}, which are prohibited for RelNode,
- * for legacy reasons.
- *
- * <p>It is highly recommended to override {@link AbstractRelNode#digestHash}
- * and {@link AbstractRelNode#digestEquals(Object)}, instead of relying on
- * {@link AbstractRelNode#explainTerms(RelWriter)}, which is used as the
- * default source for equivalent comparison, for backward compatibility.</p>
- *
- * <p>INTERNAL USE ONLY.</p>
- *
- * @see AbstractRelNode#digestHash()
- * @see AbstractRelNode#digestEquals(Object)
- */
-@API(since = "1.24", status = API.Status.INTERNAL)
-public interface RelDigest {
-  /**
-   * Reset state, possibly cache of hash code.
-   */
-  void clear();
-
-  /**
-   * Returns the relnode that this digest is associated with.
-   */
-  RelNode getRel();
-}
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptNode.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptNode.java
index 208a87c..8484f54 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptNode.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptNode.java
@@ -18,8 +18,6 @@ package org.apache.calcite.plan;
 
 import org.apache.calcite.rel.type.RelDataType;
 
-import org.apiguardian.api.API;
-
 import java.util.List;
 
 /**
@@ -47,20 +45,9 @@ public interface RelOptNode {
    * <p>If you want a descriptive string which contains the identity, call
    * {@link Object#toString()}, which always returns "rel#{id}:{digest}".
    *
-   * @return Digest string of this {@code RelNode}
-   */
-  default String getDigest() {
-    return getRelDigest().toString();
-  }
-
-  /**
-   * Digest of the {@code RelNode}, for planner internal use only.
-   *
    * @return Digest of this {@code RelNode}
-   * @see #getDigest()
    */
-  @API(since = "1.24", status = API.Status.INTERNAL)
-  RelDigest getRelDigest();
+  Digest getDigest();
 
   /**
    * Retrieves this RelNode's traits. Note that although the RelTraitSet
diff --git a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java 
b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
index c3a8e4e..1083047 100644
--- a/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
+++ b/core/src/main/java/org/apache/calcite/plan/RelOptUtil.java
@@ -2068,7 +2068,6 @@ public abstract class RelOptUtil {
 
   }
 
-  @Deprecated // to be removed before 1.25
   public static StringBuilder appendRelDescription(
       StringBuilder sb, RelNode rel) {
     sb.append("rel#").append(rel.getId())
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
index 97cf205..36bd186 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepPlanner.java
@@ -21,7 +21,7 @@ import org.apache.calcite.linq4j.function.Functions;
 import org.apache.calcite.plan.AbstractRelOptPlanner;
 import org.apache.calcite.plan.CommonRelSubExprRule;
 import org.apache.calcite.plan.Context;
-import org.apache.calcite.plan.RelDigest;
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptCostFactory;
 import org.apache.calcite.plan.RelOptCostImpl;
@@ -85,7 +85,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
    * {@link RelDataType} is represented with its field types as {@code 
List<RelDataType>}.
    * This enables to treat as equal projects that differ in expression names 
only.
    */
-  private final Map<RelDigest, HepRelVertex> mapDigestToVertex =
+  private final Map<Digest, HepRelVertex> mapDigestToVertex =
       new HashMap<>();
 
   private int nTransformations;
@@ -811,7 +811,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
     // try to find equivalent rel only if DAG is allowed
     if (!noDag) {
       // Now, check if an equivalent vertex already exists in graph.
-      HepRelVertex equivVertex = mapDigestToVertex.get(rel.getRelDigest());
+      HepRelVertex equivVertex = mapDigestToVertex.get(rel.getDigest());
       if (equivVertex != null) {
         // Use existing vertex.
         return equivVertex;
@@ -900,7 +900,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
       // reachable from here.
       notifyDiscard(vertex.getCurrentRel());
     }
-    RelDigest oldKey = vertex.getCurrentRel().getRelDigest();
+    Digest oldKey = vertex.getCurrentRel().getDigest();
     if (mapDigestToVertex.get(oldKey) == vertex) {
       mapDigestToVertex.remove(oldKey);
     }
@@ -911,7 +911,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
     // otherwise the digest will be removed wrongly in the mapDigestToVertex
     //  when collectGC
     // so it must update the digest that map to vertex
-    mapDigestToVertex.put(rel.getRelDigest(), vertex);
+    mapDigestToVertex.put(rel.getDigest(), vertex);
     if (rel != vertex.getCurrentRel()) {
       vertex.replaceRel(rel);
     }
@@ -977,7 +977,7 @@ public class HepPlanner extends AbstractRelOptPlanner {
     graphSizeLastGC = graph.vertexSet().size();
 
     // Clean up digest map too.
-    Iterator<Map.Entry<RelDigest, HepRelVertex>> digestIter =
+    Iterator<Map.Entry<Digest, HepRelVertex>> digestIter =
         mapDigestToVertex.entrySet().iterator();
     while (digestIter.hasNext()) {
       HepRelVertex vertex = digestIter.next().getValue();
diff --git a/core/src/main/java/org/apache/calcite/plan/hep/HepRelVertex.java 
b/core/src/main/java/org/apache/calcite/plan/hep/HepRelVertex.java
index 5d52e42..92394b1 100644
--- a/core/src/main/java/org/apache/calcite/plan/hep/HepRelVertex.java
+++ b/core/src/main/java/org/apache/calcite/plan/hep/HepRelVertex.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.plan.hep;
 
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
 import org.apache.calcite.plan.RelTraitSet;
@@ -75,18 +76,8 @@ public class HepRelVertex extends AbstractRelNode {
     return currentRel.getRowType();
   }
 
-  @Override public String toString() {
-    return "rel#" + id + ':' + "HepRelVertex(" + currentRel + ')';
-  }
-
-  @Override public boolean digestEquals(Object obj) {
-    return this == obj
-        || (obj instanceof HepRelVertex
-            && this.currentRel == ((HepRelVertex) obj).currentRel);
-  }
-
-  @Override public int digestHash() {
-    return this.currentRel.getId();
+  @Override protected Digest computeDigest() {
+    return Digest.create(this, getRelTypeName() + '#' + 
getCurrentRel().getId());
   }
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java
index 32486ca..e821496 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/RelSubset.java
@@ -17,7 +17,7 @@
 package org.apache.calcite.plan.volcano;
 
 import org.apache.calcite.linq4j.Linq4j;
-import org.apache.calcite.plan.RelDigest;
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptListener;
@@ -129,9 +129,9 @@ public class RelSubset extends AbstractRelNode {
       RelTraitSet traits) {
     super(cluster, traits);
     this.set = set;
-    this.digest = new RelDigest0();
     assert traits.allSimple();
     computeBestCost(cluster.getPlanner());
+    recomputeDigest();
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -225,6 +225,16 @@ public class RelSubset extends AbstractRelNode {
     pw.done(input);
   }
 
+  @Override protected Digest computeDigest() {
+    StringBuilder digest = new StringBuilder(getRelTypeName());
+    digest.append('#');
+    digest.append(set.id);
+    for (RelTrait trait : getTraitSet()) {
+      digest.append('.').append(trait);
+    }
+    return Digest.create(this, digest.toString());
+  }
+
   @Override protected RelDataType deriveRowType() {
     return set.rel.getRowType();
   }
@@ -537,28 +547,6 @@ public class RelSubset extends AbstractRelNode {
     }
   }
 
-  private class RelDigest0 implements RelDigest {
-
-    @Override public RelNode getRel() {
-      return RelSubset.this;
-    }
-
-    @Override public void clear() {
-    }
-
-    @Override public boolean equals(final Object o) {
-      return this == o;
-    }
-
-    @Override public int hashCode() {
-      return id;
-    }
-
-    @Override public String toString() {
-      return "RelSubset#" + set.id + '.' + getTraitSet();
-    }
-  }
-
   /**
    * Visitor which walks over a tree of {@link RelSet}s, replacing each node
    * with the cheapest implementation of the expression.
diff --git 
a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java 
b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
index 7227293..4842932 100644
--- a/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
+++ b/core/src/main/java/org/apache/calcite/plan/volcano/VolcanoPlanner.java
@@ -22,7 +22,7 @@ import org.apache.calcite.plan.AbstractRelOptPlanner;
 import org.apache.calcite.plan.Context;
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.ConventionTraitDef;
-import org.apache.calcite.plan.RelDigest;
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptCostFactory;
 import org.apache.calcite.plan.RelOptLattice;
@@ -103,7 +103,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
    * Canonical map from {@link String digest} to the unique
    * {@link RelNode relational expression} with that digest.
    */
-  private final Map<RelDigest, RelNode> mapDigestToRel =
+  private final Map<Digest, RelNode> mapDigestToRel =
       new HashMap<>();
 
   /**
@@ -889,12 +889,11 @@ public class VolcanoPlanner extends AbstractRelOptPlanner 
{
    * @param rel Relational expression
    */
   void rename(RelNode rel) {
-    String oldDigest = null;
-    if (LOGGER.isTraceEnabled()) {
-      oldDigest = rel.getDigest();
-    }
+    final Digest oldDigest = rel.getDigest();
     if (fixUpInputs(rel)) {
-      final RelDigest newDigest = rel.recomputeDigest();
+      final RelNode removed = mapDigestToRel.remove(oldDigest);
+      assert removed == rel;
+      final Digest newDigest = rel.recomputeDigest();
       LOGGER.trace("Rename #{} from '{}' to '{}'", rel.getId(), oldDigest, 
newDigest);
       final RelNode equivRel = mapDigestToRel.put(newDigest, rel);
       if (equivRel != null) {
@@ -960,7 +959,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner {
     // Is there an equivalent relational expression? (This might have
     // just occurred because the relational expression's child was just
     // found to be equivalent to another set.)
-    RelNode equivRel = mapDigestToRel.get(rel.getRelDigest());
+    RelNode equivRel = mapDigestToRel.get(rel.getDigest());
     if (equivRel != null && equivRel != rel) {
       assert equivRel.getClass() == rel.getClass();
       assert equivRel.getTraitSet().equals(rel.getTraitSet());
@@ -1023,33 +1022,25 @@ public class VolcanoPlanner extends 
AbstractRelOptPlanner {
 
   private boolean fixUpInputs(RelNode rel) {
     List<RelNode> inputs = rel.getInputs();
-    List<RelNode> newInputs = new ArrayList<>(inputs.size());
+    int i = -1;
     int changeCount = 0;
     for (RelNode input : inputs) {
-      assert input instanceof RelSubset;
-      final RelSubset subset = (RelSubset) input;
-      RelSubset newSubset = canonize(subset);
-      newInputs.add(newSubset);
-      if (newSubset != subset) {
-        if (subset.set != newSubset.set) {
-          subset.set.parents.remove(rel);
-          newSubset.set.parents.add(rel);
+      ++i;
+      if (input instanceof RelSubset) {
+        final RelSubset subset = (RelSubset) input;
+        RelSubset newSubset = canonize(subset);
+        if (newSubset != subset) {
+          rel.replaceInput(i, newSubset);
+          if (subset.set != newSubset.set) {
+            subset.set.parents.remove(rel);
+            newSubset.set.parents.add(rel);
+          }
+          changeCount++;
         }
-        changeCount++;
       }
     }
-
-    if (changeCount > 0) {
-      RelMdUtil.clearCache(rel);
-      RelNode removed = mapDigestToRel.remove(rel.getRelDigest());
-      assert removed == rel;
-      for (int i = 0; i < inputs.size(); i++) {
-        rel.replaceInput(i, newInputs.get(i));
-      }
-      rel.recomputeDigest();
-      return true;
-    }
-    return false;
+    RelMdUtil.clearCache(rel);
+    return changeCount > 0;
   }
 
   private RelSet merge(RelSet set, RelSet set2) {
@@ -1177,7 +1168,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner 
{
 
     // If it is equivalent to an existing expression, return the set that
     // the equivalent expression belongs to.
-    RelDigest digest = rel.getRelDigest();
+    Digest digest = rel.getDigest();
     RelNode equivExp = mapDigestToRel.get(digest);
     if (equivExp == null) {
       // do nothing
@@ -1207,7 +1198,7 @@ public class VolcanoPlanner extends AbstractRelOptPlanner 
{
           && (set.equivalentSet == null)) {
         LOGGER.trace(
             "Register #{} {} (and merge sets, because it is a conversion)",
-            rel.getId(), rel.getRelDigest());
+            rel.getId(), rel.getDigest());
         merge(set, childSet);
 
         // During the mergers, the child set may have changed, and since
diff --git a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java 
b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
index 1ce29f1..c5ec873 100644
--- a/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
+++ b/core/src/main/java/org/apache/calcite/rel/AbstractRelNode.java
@@ -18,7 +18,7 @@ package org.apache.calcite.rel;
 
 import org.apache.calcite.plan.Convention;
 import org.apache.calcite.plan.ConventionTraitDef;
-import org.apache.calcite.plan.RelDigest;
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -27,7 +27,6 @@ import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.core.CorrelationId;
-import org.apache.calcite.rel.hint.Hintable;
 import org.apache.calcite.rel.metadata.Metadata;
 import org.apache.calcite.rel.metadata.MetadataFactory;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
@@ -44,13 +43,11 @@ import org.apache.calcite.util.trace.CalciteTrace;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 
-import org.apiguardian.api.API;
 import org.slf4j.Logger;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -75,8 +72,7 @@ public abstract class AbstractRelNode implements RelNode {
   /**
    * The digest that uniquely identifies the node.
    */
-  @API(since = "1.24", status = API.Status.INTERNAL)
-  protected RelDigest digest;
+  protected Digest digest;
 
   private final RelOptCluster cluster;
 
@@ -101,7 +97,8 @@ public abstract class AbstractRelNode implements RelNode {
     this.cluster = cluster;
     this.traitSet = traitSet;
     this.id = NEXT_ID.getAndIncrement();
-    this.digest = new RelDigest0();
+    this.digest = Digest.initial(this);
+    LOGGER.trace("new {}", digest);
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -337,8 +334,9 @@ public abstract class AbstractRelNode implements RelNode {
     return r;
   }
 
-  public RelDigest recomputeDigest() {
-    digest.clear();
+  public Digest recomputeDigest() {
+    digest = computeDigest();
+    assert digest != null : "computeDigest() should be non-null";
     return digest;
   }
 
@@ -350,7 +348,9 @@ public abstract class AbstractRelNode implements RelNode {
 
   /** Description, consists of id plus digest */
   public String toString() {
-    return "rel#" + id + ':' + getDigest();
+    StringBuilder sb = new StringBuilder();
+    RelOptUtil.appendRelDescription(sb, this);
+    return sb.toString();
   }
 
   /** Description, consists of id plus digest */
@@ -359,11 +359,7 @@ public abstract class AbstractRelNode implements RelNode {
     return this.toString();
   }
 
-  public final String getDigest() {
-    return digest.toString();
-  }
-
-  public final RelDigest getRelDigest() {
+  public final Digest getDigest() {
     return digest;
   }
 
@@ -372,6 +368,17 @@ public abstract class AbstractRelNode implements RelNode {
   }
 
   /**
+   * Computes the digest. Does not modify this object.
+   *
+   * @return Digest
+   */
+  protected Digest computeDigest() {
+    RelDigestWriter rdw = new RelDigestWriter();
+    explain(rdw);
+    return rdw.digest;
+  }
+
+  /**
    * {@inheritDoc}
    *
    * <p>This method (and {@link #hashCode} is intentionally final. We do not 
want
@@ -393,73 +400,6 @@ public abstract class AbstractRelNode implements RelNode {
     return super.hashCode();
   }
 
-  public boolean digestEquals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    AbstractRelNode that = (AbstractRelNode) obj;
-    return this.getTraitSet() == that.getTraitSet()
-        && this.getDigestItems().equals(that.getDigestItems())
-        && Pair.right(getRowType().getFieldList()).equals(
-        Pair.right(that.getRowType().getFieldList()))
-        && (!(that instanceof Hintable)
-            || ((Hintable) this).getHints().equals(
-                ((Hintable) that).getHints()));
-  }
-
-  public int digestHash() {
-    return Objects.hash(getTraitSet(), getDigestItems(),
-        this instanceof Hintable ? ((Hintable) this).getHints() : null);
-  }
-
-  private List<Pair<String, Object>> getDigestItems() {
-    RelDigestWriter rdw = new RelDigestWriter();
-    explainTerms(rdw);
-    return rdw.values;
-  }
-
-  private class RelDigest0 implements RelDigest {
-    /**
-     * Cache of hash code.
-     */
-    private int hash = 0;
-
-    @Override public RelNode getRel() {
-      return AbstractRelNode.this;
-    }
-
-    @Override public void clear() {
-      hash = 0;
-    }
-
-    @Override public boolean equals(final Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (o == null || getClass() != o.getClass()) {
-        return false;
-      }
-      final RelDigest0 relDigest = (RelDigest0) o;
-      return digestEquals(relDigest.getRel());
-    }
-
-    @Override public int hashCode() {
-      if (hash == 0) {
-        hash = digestHash();
-      }
-      return hash;
-    }
-
-    @Override public String toString() {
-      RelDigestWriter rdw = new RelDigestWriter();
-      explain(rdw);
-      return rdw.digest;
-    }
-  }
-
   /**
    * A writer object used exclusively for computing the digest of a RelNode.
    *
@@ -472,7 +412,7 @@ public abstract class AbstractRelNode implements RelNode {
 
     private final List<Pair<String, Object>> values = new ArrayList<>();
 
-    String digest = null;
+    Digest digest = null;
 
     @Override public void explain(final RelNode rel, final List<Pair<String, 
Object>> valueList) {
       throw new IllegalStateException("Should not be called for computing 
digest");
@@ -483,39 +423,12 @@ public abstract class AbstractRelNode implements RelNode {
     }
 
     @Override public RelWriter item(String term, Object value) {
-      if (value != null && value.getClass().isArray()) {
-        // We can't call hashCode and equals on Array, so
-        // convert it to String to keep the same behaviour.
-        value = "" + value;
-      }
       values.add(Pair.of(term, value));
       return this;
     }
 
     @Override public RelWriter done(RelNode node) {
-      StringBuilder sb = new StringBuilder();
-      sb.append(node.getRelTypeName());
-      sb.append('.');
-      sb.append(node.getTraitSet());
-      sb.append('(');
-      int j = 0;
-      for (Pair<String, Object> value : values) {
-        if (j++ > 0) {
-          sb.append(',');
-        }
-        sb.append(value.left);
-        sb.append('=');
-        if (value.right instanceof RelNode) {
-          RelNode input = (RelNode) value.right;
-          sb.append(input.getRelTypeName());
-          sb.append('#');
-          sb.append(input.getId());
-        } else {
-          sb.append(value.right);
-        }
-      }
-      sb.append(')');
-      digest = sb.toString();
+      digest = Digest.create(node, values);
       return this;
     }
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/RelNode.java 
b/core/src/main/java/org/apache/calcite/rel/RelNode.java
index 3d5a08e..18ff76f 100644
--- a/core/src/main/java/org/apache/calcite/rel/RelNode.java
+++ b/core/src/main/java/org/apache/calcite/rel/RelNode.java
@@ -17,7 +17,7 @@
 package org.apache.calcite.rel;
 
 import org.apache.calcite.plan.Convention;
-import org.apache.calcite.plan.RelDigest;
+import org.apache.calcite.plan.Digest;
 import org.apache.calcite.plan.RelOptCost;
 import org.apache.calcite.plan.RelOptNode;
 import org.apache.calcite.plan.RelOptPlanner;
@@ -33,8 +33,6 @@ import org.apache.calcite.rex.RexShuttle;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.Litmus;
 
-import org.apiguardian.api.API;
-
 import java.util.List;
 import java.util.Set;
 
@@ -311,8 +309,7 @@ public interface RelNode extends RelOptNode, Cloneable {
    *
    * @return Digest of this relational expression
    */
-  @API(since = "1.24", status = API.Status.INTERNAL)
-  RelDigest recomputeDigest();
+  Digest recomputeDigest();
 
   /**
    * Replaces the <code>ordinalInParent</code><sup>th</sup> input. You must
diff --git a/core/src/main/java/org/apache/calcite/rel/SingleRel.java 
b/core/src/main/java/org/apache/calcite/rel/SingleRel.java
index ef06c96..a650543 100644
--- a/core/src/main/java/org/apache/calcite/rel/SingleRel.java
+++ b/core/src/main/java/org/apache/calcite/rel/SingleRel.java
@@ -82,7 +82,6 @@ public abstract class SingleRel extends AbstractRelNode {
       RelNode rel) {
     assert ordinalInParent == 0;
     this.input = rel;
-    recomputeDigest();
   }
 
   protected RelDataType deriveRowType() {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java 
b/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
index ec97781..82de9fc 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Aggregate.java
@@ -332,27 +332,6 @@ public abstract class Aggregate extends SingleRel 
implements Hintable {
     return pw;
   }
 
-  protected boolean digestEquals0(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    Aggregate o = (Aggregate) obj;
-    return getTraitSet() == o.getTraitSet()
-        && getInput().equals(o.getInput())
-        && groupSet.equals(o.groupSet)
-        && groupSets.equals(o.groupSets)
-        && aggCalls.equals(o.aggCalls)
-        && hints.equals(o.hints)
-        && getRowType().equals(o.getRowType());
-  }
-
-  protected int digestHash0() {
-    return Objects.hash(traitSet, input, groupSet, groupSets, aggCalls, hints);
-  }
-
   @Override public double estimateRowCount(RelMetadataQuery mq) {
     // Assume that each sort column has 50% of the value count.
     // Therefore one sort column has .5 * rowCount,
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Filter.java 
b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
index d56daf7..dd13485 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Filter.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Filter.java
@@ -37,7 +37,6 @@ import org.apache.calcite.util.Litmus;
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Relational expression that iterates over its input
@@ -100,24 +99,6 @@ public abstract class Filter extends SingleRel {
     return ImmutableList.of(condition);
   }
 
-  protected boolean digestEquals0(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    Filter o = (Filter) obj;
-    return getTraitSet() == o.getTraitSet()
-        && getInput().equals(o.getInput())
-        && condition.equals(o.condition)
-        && getRowType().equals(o.getRowType());
-  }
-
-  protected int digestHash0() {
-    return Objects.hash(traitSet, input, condition);
-  }
-
   public RelNode accept(RexShuttle shuttle) {
     RexNode condition = shuttle.apply(this.condition);
     if (this.condition == condition) {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Join.java 
b/core/src/main/java/org/apache/calcite/rel/core/Join.java
index 9f14f97..5d615a8 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Join.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Join.java
@@ -239,28 +239,6 @@ public abstract class Join extends BiRel implements 
Hintable {
         getSystemFieldList());
   }
 
-  protected boolean digestEquals0(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    Join o = (Join) obj;
-    return getTraitSet() == o.getTraitSet()
-        && getInputs().equals(o.getInputs())
-        && condition.equals(o.condition)
-        && joinType == o.joinType
-        && hints.equals(o.hints)
-        && variablesSet.equals(o.variablesSet)
-        && getRowType().equals(o.getRowType());
-  }
-
-  protected int digestHash0() {
-    return Objects.hash(traitSet, left, right,
-        condition, joinType, hints, variablesSet);
-  }
-
   /**
    * Returns whether this LogicalJoin has already spawned a
    * {@code SemiJoin} via
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Project.java 
b/core/src/main/java/org/apache/calcite/rel/core/Project.java
index d9abce7..e3c1412 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Project.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Project.java
@@ -48,7 +48,6 @@ import com.google.common.collect.Lists;
 
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -155,26 +154,6 @@ public abstract class Project extends SingleRel implements 
Hintable {
     return exps;
   }
 
-  protected boolean digestEquals0(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    Project o = (Project) obj;
-    return getTraitSet() == o.getTraitSet()
-        && getInput().equals(o.getInput())
-        && exps.equals(o.exps)
-        && hints.equals(o.hints)
-        && Pair.right(getRowType().getFieldList()).equals(
-        Pair.right(o.getRowType().getFieldList()));
-  }
-
-  protected int digestHash0() {
-    return Objects.hash(traitSet, input, exps, hints);
-  }
-
   public RelNode accept(RexShuttle shuttle) {
     List<RexNode> exps = shuttle.apply(this.exps);
     if (this.exps == exps) {
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Values.java 
b/core/src/main/java/org/apache/calcite/rel/core/Values.java
index 6386318..8f25815 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Values.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Values.java
@@ -36,7 +36,6 @@ import org.apache.calcite.util.Pair;
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
-import java.util.Objects;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -152,23 +151,6 @@ public abstract class Values extends AbstractRelNode {
     return true;
   }
 
-  @Override public boolean digestEquals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    if (this.getClass() != obj.getClass()) {
-      return false;
-    }
-    Values o = (Values) obj;
-    return getTraitSet() == o.getTraitSet()
-        && tuples.equals(o.tuples)
-        && getRowType().equals(o.getRowType());
-  }
-
-  @Override public int digestHash() {
-    return Objects.hash(traitSet, tuples);
-  }
-
   @Override protected RelDataType deriveRowType() {
     return rowType;
   }
diff --git a/core/src/main/java/org/apache/calcite/rel/core/Window.java 
b/core/src/main/java/org/apache/calcite/rel/core/Window.java
index dc364ea..620afe6 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -411,10 +411,7 @@ public abstract class Window extends SingleRel {
     }
 
     @Override public int hashCode() {
-      if (hash == 0) {
-        hash = Objects.hash(super.hashCode(), ordinal, distinct, ignoreNulls);
-      }
-      return hash;
+      return Objects.hash(super.hashCode(), ordinal, distinct, ignoreNulls);
     }
 
     @Override public RexCall clone(RelDataType type, List<RexNode> operands) {
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
index 5940e97..115e423 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalAggregate.java
@@ -161,12 +161,4 @@ public final class LogicalAggregate extends Aggregate {
     return new LogicalAggregate(getCluster(), traitSet, hintList, input,
         groupSet, groupSets, aggCalls);
   }
-
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
index 894fd17..8995241 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalFilter.java
@@ -135,16 +135,4 @@ public final class LogicalFilter extends Filter {
     return super.explainTerms(pw)
         .itemIf("variablesSet", variablesSet, !variablesSet.isEmpty());
   }
-
-  @Override public boolean digestEquals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    return digestEquals0(obj)
-        && variablesSet.equals(((LogicalFilter) obj).variablesSet);
-  }
-
-  @Override public int digestHash() {
-    return Objects.hash(digestHash0(), variablesSet);
-  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
index ec34121..4b55785 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalJoin.java
@@ -203,17 +203,4 @@ public final class LogicalJoin extends Join {
     return new LogicalJoin(getCluster(), traitSet, hintList,
         left, right, condition, variablesSet, joinType, semiJoinDone, 
systemFieldList);
   }
-
-  @Override public boolean digestEquals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
-    return digestEquals0(obj)
-        && semiJoinDone == ((LogicalJoin) obj).semiJoinDone
-        && systemFieldList.equals(((LogicalJoin) obj).systemFieldList);
-  }
-
-  @Override public int digestHash() {
-    return Objects.hash(digestHash0(), semiJoinDone, systemFieldList);
-  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
index 22fc72d..2696dba 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalProject.java
@@ -136,12 +136,4 @@ public final class LogicalProject extends Project {
     return new LogicalProject(getCluster(), traitSet, hintList,
         input, getProjects(), rowType);
   }
-
-  @Override public boolean digestEquals(Object obj) {
-    return digestEquals0(obj);
-  }
-
-  @Override public int digestHash() {
-    return digestHash0();
-  }
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
 
b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
index 1ab362b..5e087fc 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/metadata/JaninoRelMetadataProvider.java
@@ -400,8 +400,13 @@ public class JaninoRelMetadataProvider implements 
RelMetadataProvider {
   /** Returns e.g. ", ignoreNulls". */
   private static StringBuilder safeArgList(StringBuilder buff, Method method) {
     for (Ord<Class<?>> t : Ord.zip(method.getParameterTypes())) {
-      if (Primitive.is(t.e) || RexNode.class.isAssignableFrom(t.e)) {
+      if (Primitive.is(t.e)) {
         buff.append(", a").append(t.i);
+      } else if (RexNode.class.isAssignableFrom(t.e)) {
+        // For RexNode, convert to string, because equals does not look deep.
+        //   a1 == null ? "" : a1.toString()
+        buff.append(", a").append(t.i).append(" == null ? \"\" : a")
+            .append(t.i).append(".toString()");
       } else {
         buff.append(", ") .append(NullSentinel.class.getName())
             .append(".mask(a").append(t.i).append(")");
diff --git a/core/src/main/java/org/apache/calcite/rel/rules/MultiJoin.java 
b/core/src/main/java/org/apache/calcite/rel/rules/MultiJoin.java
index ebf669b..1b09be8 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/MultiJoin.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/MultiJoin.java
@@ -113,7 +113,6 @@ public final class MultiJoin extends AbstractRelNode {
 
   @Override public void replaceInput(int ordinalInParent, RelNode p) {
     inputs.set(ordinalInParent, p);
-    recomputeDigest();
   }
 
   @Override public RelNode copy(RelTraitSet traitSet, List<RelNode> inputs) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexCall.java 
b/core/src/main/java/org/apache/calcite/rex/RexCall.java
index 3dfe714..6b8f2ac 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCall.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCall.java
@@ -231,7 +231,17 @@ public class RexCall extends RexNode {
   }
 
   @Override public final @Nonnull String toString() {
-    return computeDigest(digestWithType());
+    if (!needNormalize()) {
+      // Non-normalize describe is requested
+      return computeDigest(digestWithType());
+    }
+    // This data race is intentional
+    String localDigest = digest;
+    if (localDigest == null) {
+      localDigest = computeDigest(digestWithType());
+      digest = Objects.requireNonNull(localDigest);
+    }
+    return localDigest;
   }
 
   private boolean digestWithType() {
@@ -326,10 +336,6 @@ public class RexCall extends RexNode {
   }
 
   @Override public int hashCode() {
-    if (hash == 0) {
-      assert digest == null;
-      hash = Objects.hash(op, operands);
-    }
-    return hash;
+    return Objects.hash(op, operands);
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexDynamicParam.java 
b/core/src/main/java/org/apache/calcite/rex/RexDynamicParam.java
index 6e2b1d7..7a59021 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexDynamicParam.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexDynamicParam.java
@@ -65,14 +65,12 @@ public class RexDynamicParam extends RexVariable {
   @Override public boolean equals(Object obj) {
     return this == obj
         || obj instanceof RexDynamicParam
+        && digest.equals(((RexDynamicParam) obj).digest)
         && type.equals(((RexDynamicParam) obj).type)
         && index == ((RexDynamicParam) obj).index;
   }
 
   @Override public int hashCode() {
-    if (hash == 0) {
-      hash = Objects.hash(type, index);
-    }
-    return hash;
+    return Objects.hash(digest, type, index);
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java 
b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
index 92da77b..5617d86 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexLiteral.java
@@ -1083,19 +1083,13 @@ public class RexLiteral extends RexNode {
   }
 
   public boolean equals(Object obj) {
-    if (this == obj) {
-      return true;
-    }
     return (obj instanceof RexLiteral)
         && equals(((RexLiteral) obj).value, value)
         && equals(((RexLiteral) obj).type, type);
   }
 
   public int hashCode() {
-    if (hash == 0) {
-      hash = Objects.hash(value, type);
-    }
-    return hash;
+    return Objects.hash(value, type);
   }
 
   public static Comparable value(RexNode node) {
diff --git a/core/src/main/java/org/apache/calcite/rex/RexNode.java 
b/core/src/main/java/org/apache/calcite/rex/RexNode.java
index f4dd69c..88332a2 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexNode.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexNode.java
@@ -56,11 +56,6 @@ public abstract class RexNode {
   // Effectively final. Set in each sub-class constructor, and never re-set.
   protected String digest;
 
-  /**
-   * Cache of hash code.
-   */
-  protected int hash = 0;
-
   //~ Methods ----------------------------------------------------------------
 
   public abstract RelDataType getType();
diff --git a/core/src/main/java/org/apache/calcite/rex/RexOver.java 
b/core/src/main/java/org/apache/calcite/rex/RexOver.java
index 6e006c9..e90fa3c 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexOver.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexOver.java
@@ -215,10 +215,6 @@ public class RexOver extends RexCall {
   }
 
   @Override public int hashCode() {
-    if (hash == 0) {
-      hash = Objects.hash(super.hashCode(), window,
-          distinct, ignoreNulls, op.allowsFraming());
-    }
-    return hash;
+    return Objects.hash(super.hashCode(), window, distinct, ignoreNulls, 
op.allowsFraming());
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java 
b/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
index 0dbdbcc..d702387 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSubQuery.java
@@ -42,6 +42,7 @@ public class RexSubQuery extends RexCall {
       ImmutableList<RexNode> operands, RelNode rel) {
     super(type, op, operands);
     this.rel = rel;
+    this.digest = computeDigest(false);
   }
 
   /** Creates an IN sub-query. */
@@ -138,14 +139,11 @@ public class RexSubQuery extends RexCall {
 
   @Override public boolean equals(Object obj) {
     return obj == this
-        || obj instanceof RexSubQuery
+        || obj instanceof RexCall
         && toString().equals(obj.toString());
   }
 
   @Override public int hashCode() {
-    if (hash == 0) {
-      hash = toString().hashCode();
-    }
-    return hash;
+    return toString().hashCode();
   }
 }
diff --git a/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java 
b/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
index 90651fa..a4269c5 100644
--- a/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/test/HepPlannerTest.java
@@ -153,7 +153,7 @@ class HepPlannerTest extends RelOptTestBase {
     assertIncludesExactlyOnce("best.getDescription()",
         best.toString(), "LogicalUnion");
     assertIncludesExactlyOnce("best.getDigest()",
-        best.getDigest(), "LogicalUnion");
+        best.getDigest().toString(), "LogicalUnion");
   }
 
   private void assertIncludesExactlyOnce(String message, String digest, String 
substring) {

Reply via email to