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

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


The following commit(s) were added to refs/heads/main by this push:
     new a90b2da1ce [CALCITE-7393] Support RelDataTypeDigest
a90b2da1ce is described below

commit a90b2da1ce2016e57f42e6b6c12def41f30696de
Author: wenzhuang.zwz <[email protected]>
AuthorDate: Thu Jan 22 18:40:11 2026 +0800

    [CALCITE-7393] Support RelDataTypeDigest
    
    Use structured innerDigest instead of string digest for composite/UDT types
    to reduce memory and improve hashCode/equals latency. Controlled by
    `calcite.disable.generate.rel.data.type.digest.string` (default: false).
    
    Legacy string digest is still used in hashCode/equals if explicitly set,
    ensuring backward compatibility.
    
    TestCase: TypeDigestBenchmark
---
 .../calcite/config/CalciteSystemProperty.java      |   8 ++
 .../org/apache/calcite/jdbc/JavaRecordType.java    |  12 ++
 .../org/apache/calcite/rel/HasDigestString.java    |  24 ++++
 .../org/apache/calcite/rel/type/RelDataType.java   |  22 ++++
 .../apache/calcite/rel/type/RelDataTypeDigest.java |  26 ++++
 .../apache/calcite/rel/type/RelDataTypeImpl.java   | 138 ++++++++++++++++++---
 .../org/apache/calcite/rel/type/RelRecordType.java |  34 +++++
 .../rel/type/SingleColumnAliasRelDataType.java     |  12 ++
 .../org/apache/calcite/sql/type/ArraySqlType.java  |  19 +++
 .../org/apache/calcite/sql/type/MapSqlType.java    |  21 ++++
 .../apache/calcite/sql/type/MultisetSqlType.java   |  19 +++
 .../org/apache/calcite/rex/RexBuilderTest.java     |   1 +
 site/_docs/history.md                              |   4 +
 .../calcite/benchmarks/TypeDigestBenchmark.java    | 126 +++++++++++++++++++
 14 files changed, 448 insertions(+), 18 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java 
b/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
index e5ea8c2ea2..70db4505d6 100644
--- a/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
+++ b/core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java
@@ -138,6 +138,14 @@ public final class CalciteSystemProperty<T> {
   public static final CalciteSystemProperty<Boolean> TOPDOWN_OPT =
       booleanProperty("calcite.planner.topdown.opt", false);
 
+
+  /** Whether to disable generate rel data type digest string.
+   *
+   * <p> Disable generate rel data type digest string for every type can
+   * reduce composite type's digest memory and digest relative operation's 
latency. */
+  public static final CalciteSystemProperty<Boolean> 
DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING =
+      booleanProperty("calcite.disable.generate.rel.data.type.digest.string", 
false);
+
   /**
    * Whether to run integration tests.
    */
diff --git a/core/src/main/java/org/apache/calcite/jdbc/JavaRecordType.java 
b/core/src/main/java/org/apache/calcite/jdbc/JavaRecordType.java
index 72242d3934..2183c2ac28 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaRecordType.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaRecordType.java
@@ -51,4 +51,16 @@ public JavaRecordType(List<RelDataTypeField> fields, Class 
clazz) {
   @Override public int hashCode() {
     return Objects.hash(fieldList, clazz);
   }
+
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    return this == obj
+        || obj instanceof JavaRecordType
+        && Objects.equals(fieldList, ((JavaRecordType) obj).fieldList)
+        && clazz == ((JavaRecordType) obj).clazz
+        && this.isNullable() == ((JavaRecordType) obj).isNullable();
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hash(fieldList, this.isNullable(), clazz);
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/rel/HasDigestString.java 
b/core/src/main/java/org/apache/calcite/rel/HasDigestString.java
new file mode 100644
index 0000000000..5058c38769
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/HasDigestString.java
@@ -0,0 +1,24 @@
+/*
+ * 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.rel;
+
+/**
+ * Interface for objects that have a digest string.
+ */
+public interface HasDigestString {
+  String getDigestString();
+}
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
index 4188d04698..071a1152a1 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataType.java
@@ -322,4 +322,26 @@ default boolean 
equalsSansFieldNamesAndNullability(@Nullable RelDataType that) {
   default boolean isMeasure() {
     return getSqlTypeName() == SqlTypeName.MEASURE;
   }
+
+  /**
+   * Returns the digest of this type.
+   *
+   * @return digest of this type
+   */
+  RelDataTypeDigest getDigest();
+
+  /**
+   * Deep equality check for RelDataType digest.
+   *
+   * @return Whether the 2 RelDataTypes are equivalent or have the same digest.
+   * @see #deepHashCode()
+   */
+  boolean deepEquals(@Nullable Object obj);
+
+  /**
+   * Compute deep hash code for RelDataType digest.
+   *
+   * @see #deepEquals(Object)
+   */
+  int deepHashCode();
 }
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeDigest.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeDigest.java
new file mode 100644
index 0000000000..ac805aae22
--- /dev/null
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeDigest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.rel.type;
+
+import org.apache.calcite.rel.HasDigestString;
+
+/**
+ * Digest of a RelDataType.
+ */
+public interface RelDataTypeDigest extends HasDigestString {
+  RelDataType getType();
+}
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
index 67ab341016..d896ece7e9 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java
@@ -16,6 +16,7 @@
  */
 package org.apache.calcite.rel.type;
 
+import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.sql.SqlCollation;
 import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlIntervalQualifier;
@@ -46,8 +47,8 @@
  * RelDataTypeImpl is an abstract base for implementations of
  * {@link RelDataType}.
  *
- * <p>Identity is based upon the {@link #digest} field, which each derived 
class
- * should set during construction.
+ * <p>Identity is based upon the {@link #digest} or {@link #innerDigest} field,
+ * which each derived class should set {@link #digest} or {@link #innerDigest} 
during construction.
  */
 public abstract class RelDataTypeImpl
     implements RelDataType, RelDataTypeFamily {
@@ -60,7 +61,14 @@ public abstract class RelDataTypeImpl
   //~ Instance fields --------------------------------------------------------
 
   protected final @Nullable List<RelDataTypeField> fieldList;
-  protected @Nullable String digest;
+
+  /**
+   * Use {@link #innerDigest} instead.
+   *
+   * @deprecated See {@link 
CalciteSystemProperty#DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING}.
+   */
+  protected @Deprecated @Nullable String digest;
+  protected @Nullable RelDataTypeDigest innerDigest;
 
   //~ Constructors -----------------------------------------------------------
 
@@ -232,18 +240,50 @@ private static void getFieldRecurse(List<Slot> slots, 
RelDataType type,
     return fieldList != null;
   }
 
+  /**
+   * Gets the {@link RelDataTypeDigest} of this type.
+   * If a user has set the legacy string {@code digest} and {@code 
innerDigest} has not
+   * been initialized yet, this method computes and initializes it.
+   */
+  @Override public RelDataTypeDigest getDigest() {
+    if (digest != null && innerDigest == null) {
+      innerDigest = new InnerRelDataTypeDigest();
+    }
+    return requireNonNull(innerDigest, "innerDigest");
+  }
+
   @Override public boolean equals(@Nullable Object obj) {
-    return this == obj
-        || obj instanceof RelDataTypeImpl
-        && Objects.equals(this.digest, ((RelDataTypeImpl) obj).digest);
+    if (obj == this) {
+      return true;
+    }
+    if (obj instanceof RelDataTypeImpl) {
+      final RelDataTypeImpl that = (RelDataTypeImpl) obj;
+      return this.getDigest().equals(that.getDigest());
+    }
+    return false;
   }
 
   @Override public int hashCode() {
-    return Objects.hashCode(digest);
+    return getDigest().hashCode();
+  }
+
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || this.getClass() != obj.getClass()) {
+      return false;
+    }
+    return Objects.equals(this.getDigest().getDigestString(),
+        ((RelDataTypeImpl) obj).getDigest().getDigestString());
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hashCode(this.getDigest().getDigestString());
   }
 
   @Override public String getFullTypeString() {
-    return requireNonNull(digest, "digest");
+    return requireNonNull(this.getDigest().getDigestString(), "digest");
   }
 
   @Override public boolean isNullable() {
@@ -309,23 +349,85 @@ protected abstract void generateTypeString(
       boolean withDetail);
 
   /**
-   * Computes the digest field. This should be called in every non-abstract
-   * subclass constructor once the type is fully defined.
+   * Init the lazy digest computing field {@link #innerDigest}.
+   * This should be called in every non-abstract subclass
+   * constructor once the type is fully defined.
    */
   @SuppressWarnings("method.invocation.invalid")
   protected void computeDigest(@UnknownInitialization RelDataTypeImpl this) {
-    StringBuilder sb = new StringBuilder();
-    generateTypeString(sb, true);
-    if (!isNullable()) {
-      sb.append(NON_NULLABLE_SUFFIX);
+    digest = null;
+    innerDigest = new InnerRelDataTypeDigest();
+    if 
(!CalciteSystemProperty.DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING.value()) {
+      digest = this.getDigest().getDigestString();
     }
-    digest = sb.toString();
   }
 
   @Override public String toString() {
-    StringBuilder sb = new StringBuilder();
-    generateTypeString(sb, false);
-    return sb.toString();
+    return getDigest().toString();
+  }
+
+  /** Implementation of {@link RelDataTypeDigest}. */
+  private class InnerRelDataTypeDigest implements RelDataTypeDigest {
+    /** Cached hash code. */
+    private int hash = 0;
+    /** Cached type string. */
+    private @Nullable String digestWithDetail = null;  // NOTE: shorter detail 
will be better
+    private @Nullable String digestWithoutDetail = null;
+
+    @Override public RelDataType getType() {
+      return RelDataTypeImpl.this;
+    }
+
+    @Override public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      final RelDataTypeImpl.InnerRelDataTypeDigest otherDigest =
+          (RelDataTypeImpl.InnerRelDataTypeDigest) o;
+      if (digest != null) {
+        return digest.equals(otherDigest.getDigestString());
+      }
+      return deepEquals(otherDigest.getType());
+    }
+
+    @Override public int hashCode() {
+      if (digest != null) {
+        return Objects.hashCode(digest);
+      }
+      if (hash == 0) {
+        hash = deepHashCode();
+      }
+      return hash;
+    }
+
+    @Override public String getDigestString() {
+      // return user defined digest by set legacy digest string field.
+      if (digest != null) {
+        return digest;
+      }
+
+      if (digestWithDetail == null) {
+        StringBuilder sb = new StringBuilder();
+        generateTypeString(sb, true);
+        if (!isNullable()) {
+          sb.append(NON_NULLABLE_SUFFIX);
+        }
+        digestWithDetail = sb.toString();
+      }
+      return digestWithDetail;
+    }
+
+    @Override public String toString() {
+      if (digestWithoutDetail == null || digest != null) {
+        StringBuilder sb = new StringBuilder();
+        RelDataTypeImpl.this.generateTypeString(sb, false);
+        digestWithoutDetail = sb.toString();
+      }
+      return digestWithoutDetail;
+    }
   }
 
   @Override public RelDataTypePrecedenceList getPrecedenceList() {
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java 
b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
index 4f253bf225..a63b107ca0 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java
@@ -27,6 +27,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 import static java.util.Objects.requireNonNull;
 
@@ -146,6 +147,39 @@ public RelRecordType(List<RelDataTypeField> fields) {
     sb.append(")");
   }
 
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || this.getClass() != obj.getClass()) {
+      return false;
+    }
+
+    RelRecordType that = (RelRecordType) obj;
+    if (kind != that.kind || nullable != that.nullable) {
+      return false;
+    }
+
+    if (fieldList == null || that.fieldList == null) {
+      return fieldList == null && that.fieldList == null;
+    }
+
+    if (fieldList.size() != that.fieldList.size()) {
+      return false;
+    }
+
+    for (int i = 0; i < fieldList.size(); i++) {
+      if (!fieldList.get(i).equals(that.fieldList.get(i))) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hash(kind.ordinal(), nullable, fieldList);
+  }
+
   /**
    * Per {@link Serializable} API, provides a replacement object to be written
    * during serialization.
diff --git 
a/core/src/main/java/org/apache/calcite/rel/type/SingleColumnAliasRelDataType.java
 
b/core/src/main/java/org/apache/calcite/rel/type/SingleColumnAliasRelDataType.java
index b09196b577..28e0406f67 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/type/SingleColumnAliasRelDataType.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/type/SingleColumnAliasRelDataType.java
@@ -136,4 +136,16 @@ public SingleColumnAliasRelDataType(RelDataType original, 
RelDataType alias) {
   @Override public boolean isDynamicStruct() {
     return original.isDynamicStruct();
   }
+
+  @Override public RelDataTypeDigest getDigest() {
+    return original.getDigest();
+  }
+
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    return original.deepEquals(obj);
+  }
+
+  @Override public int deepHashCode() {
+    return original.deepHashCode();
+  }
 }
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ArraySqlType.java 
b/core/src/main/java/org/apache/calcite/sql/type/ArraySqlType.java
index 1124939c7b..7d1d1afc47 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ArraySqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ArraySqlType.java
@@ -20,6 +20,10 @@
 import org.apache.calcite.rel.type.RelDataTypeFamily;
 import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
 
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+
 import static 
org.apache.calcite.sql.type.NonNullableAccessors.getComponentTypeOrThrow;
 
 import static java.util.Objects.requireNonNull;
@@ -56,6 +60,21 @@ public ArraySqlType(RelDataType elementType, boolean 
isNullable) {
     sb.append(" ARRAY");
   }
 
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || this.getClass() != obj.getClass()) {
+      return false;
+    }
+    ArraySqlType that = (ArraySqlType) obj;
+    return this.isNullable() == that.isNullable() && 
elementType.equals(that.elementType);
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hash(SqlTypeName.ARRAY.ordinal(), isNullable, 
elementType.hashCode());
+  }
+
   // implement RelDataType
   @Override public RelDataType getComponentType() {
     return elementType;
diff --git a/core/src/main/java/org/apache/calcite/sql/type/MapSqlType.java 
b/core/src/main/java/org/apache/calcite/sql/type/MapSqlType.java
index 960fec7ac4..9ad2160575 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/MapSqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/MapSqlType.java
@@ -19,6 +19,10 @@
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFamily;
 
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+
 import static java.util.Objects.requireNonNull;
 
 /**
@@ -69,6 +73,23 @@ public MapSqlType(
         .append(") MAP");
   }
 
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || this.getClass() != obj.getClass()) {
+      return false;
+    }
+    MapSqlType that = (MapSqlType) obj;
+    return this.isNullable() == that.isNullable() && 
keyType.equals(that.keyType)
+        && valueType.equals(that.valueType);
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hash(SqlTypeName.MAP.ordinal(), this.isNullable, 
keyType.hashCode(),
+        valueType.hashCode());
+  }
+
   // implement RelDataType
   @Override public RelDataTypeFamily getFamily() {
     return this;
diff --git 
a/core/src/main/java/org/apache/calcite/sql/type/MultisetSqlType.java 
b/core/src/main/java/org/apache/calcite/sql/type/MultisetSqlType.java
index b12c836d89..cbea4062c2 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/MultisetSqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/MultisetSqlType.java
@@ -20,6 +20,10 @@
 import org.apache.calcite.rel.type.RelDataTypeFamily;
 import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
 
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.Objects;
+
 import static 
org.apache.calcite.sql.type.NonNullableAccessors.getComponentTypeOrThrow;
 
 import static java.util.Objects.requireNonNull;
@@ -56,6 +60,21 @@ public MultisetSqlType(RelDataType elementType, boolean 
isNullable) {
     sb.append(" MULTISET");
   }
 
+  @Override public boolean deepEquals(@Nullable Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (obj == null || this.getClass() != obj.getClass()) {
+      return false;
+    }
+    MultisetSqlType that = (MultisetSqlType) obj;
+    return this.isNullable() == that.isNullable() && 
elementType.equals(that.elementType);
+  }
+
+  @Override public int deepHashCode() {
+    return Objects.hash(SqlTypeName.MULTISET.ordinal(), this.isNullable, 
elementType.hashCode());
+  }
+
   // implement RelDataType
   @Override public RelDataType getComponentType() {
     return elementType;
diff --git a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java 
b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
index 7653d5370c..e23679eaf5 100644
--- a/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/rex/RexBuilderTest.java
@@ -1477,6 +1477,7 @@ private void checkBigDecimalLiteral(RexBuilder builder, 
String val) {
 
   /** Emulate a user defined type. */
   private static class UDT extends RelDataTypeImpl {
+    @SuppressWarnings("deprecation")
     UDT() {
       this.digest = "(udt)NOT NULL";
     }
diff --git a/site/_docs/history.md b/site/_docs/history.md
index 89d91bd266..dba2c98c34 100644
--- a/site/_docs/history.md
+++ b/site/_docs/history.md
@@ -49,6 +49,10 @@ ## <a 
href="https://github.com/apache/calcite/releases/tag/calcite-1.42.0";>1.42.
 #### Breaking Changes
 {: #breaking-1-42-0}
 
+* [<a 
href="https://issues.apache.org/jira/browse/CALCITE-7393";>CALCITE-7393</a>]
+`RelDataTypeImpl.digest` is deprecated. We recommend using 
`RelDataTypeImpl.innerDigest` instead.
+See system property 
`CalciteSystemProperty.DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING`.
+
 * [<a 
href="https://issues.apache.org/jira/browse/CALCITE-7301";>CALCITE-7301</a>]
 Prior to this change, most `SqlNode`s in the `org.apache.calcite.sql.ddl` 
package could not be unparsed
 when created with `SqlOperator#createCall`. To fix this, those `SqlNode`s now 
implement their own `SqlOperator`.
diff --git 
a/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/TypeDigestBenchmark.java
 
b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/TypeDigestBenchmark.java
new file mode 100644
index 0000000000..a02a6e836a
--- /dev/null
+++ 
b/ubenchmark/src/jmh/java/org/apache/calcite/benchmarks/TypeDigestBenchmark.java
@@ -0,0 +1,126 @@
+/*
+ * 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.benchmarks;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.sql.SqlCollation;
+import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.tools.Frameworks;
+import org.apache.calcite.tools.RelBuilder;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Benchmark for {@link RelDataType} digest generation and comparison.
+ */
+@Fork(value = 1, jvmArgsPrepend = 
"-Dcalcite.disable.generate.type.digest.string=true")
+@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
+@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Thread)
+@Threads(1)
+public class TypeDigestBenchmark {
+
+  @Param({"1", "50", "500", "5000", "50000"})
+  int topN;
+
+  RelDataType type;
+  RelDataType type2;
+
+  @Setup(Level.Trial)
+  public void setup() {
+    type = createType(topN);
+    type2 = createType(topN);
+  }
+
+  private RelDataType createType(int n) {
+    RelBuilder builder =
+        RelBuilder.create(Frameworks.newConfigBuilder()
+            .defaultSchema(Frameworks.createRootSchema(true))
+            .build());
+    final RelDataTypeFactory typeFactory = builder.getTypeFactory();
+
+    RelDataType varchar =
+        typeFactory.createTypeWithCharsetAndCollation(typeFactory
+                .createSqlType(SqlTypeName.VARCHAR, 100),
+            StandardCharsets.UTF_8, SqlCollation.IMPLICIT);
+
+    RelDataType leafObj = typeFactory.builder().add("k", varchar).add("v", 
varchar)
+        .add("attrs", typeFactory.createMapType(varchar, varchar))
+        .add("tags", typeFactory.createArrayType(varchar, -1)).build();
+
+    final RelDataTypeFactory.Builder root = typeFactory.builder();
+    for (int i = 0; i < n; i++) {
+      int depth = 1 + (i % 8);
+      RelDataType t = leafObj;
+
+      for (int d = 0; d < depth; d++) {
+        RelDataType arrObj = typeFactory.createArrayType(t, -1);
+        RelDataType mapObj = typeFactory.createMapType(varchar, t);
+
+        t =
+            typeFactory.builder().add("lvl" + d, t).add("arr" + d, 
arrObj).add("map" + d, mapObj)
+                .add("s" + d, varchar).build();
+      }
+
+      if ((i % 11) == 0) {
+        root.add("f" + i, typeFactory.createArrayType(t, -1));
+      } else if ((i % 11) == 1) {
+        root.add("f" + i, typeFactory.createMapType(varchar, t));
+      } else {
+        root.add("f" + i, t);
+      }
+    }
+
+    return root.build();
+  }
+
+  @Benchmark
+  public boolean testEquals() {
+    return type.hashCode() == type2.hashCode() && type.equals(type2);
+  }
+
+  public static void main(String[] args) throws RunnerException {
+    Options opt = new OptionsBuilder()
+        .include(TypeDigestBenchmark.class.getSimpleName())
+        .detectJvmArgs()
+        .build();
+
+    new Runner(opt).run();
+  }
+}

Reply via email to