Skip to content

Commit a90b2da

Browse files
zhuwenzhuangxiedeyantu
authored andcommitted
[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
1 parent 69a7a06 commit a90b2da

14 files changed

Lines changed: 448 additions & 18 deletions

File tree

core/src/main/java/org/apache/calcite/config/CalciteSystemProperty.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ public final class CalciteSystemProperty<T> {
138138
public static final CalciteSystemProperty<Boolean> TOPDOWN_OPT =
139139
booleanProperty("calcite.planner.topdown.opt", false);
140140

141+
142+
/** Whether to disable generate rel data type digest string.
143+
*
144+
* <p> Disable generate rel data type digest string for every type can
145+
* reduce composite type's digest memory and digest relative operation's latency. */
146+
public static final CalciteSystemProperty<Boolean> DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING =
147+
booleanProperty("calcite.disable.generate.rel.data.type.digest.string", false);
148+
141149
/**
142150
* Whether to run integration tests.
143151
*/

core/src/main/java/org/apache/calcite/jdbc/JavaRecordType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,16 @@ public JavaRecordType(List<RelDataTypeField> fields, Class clazz) {
5151
@Override public int hashCode() {
5252
return Objects.hash(fieldList, clazz);
5353
}
54+
55+
@Override public boolean deepEquals(@Nullable Object obj) {
56+
return this == obj
57+
|| obj instanceof JavaRecordType
58+
&& Objects.equals(fieldList, ((JavaRecordType) obj).fieldList)
59+
&& clazz == ((JavaRecordType) obj).clazz
60+
&& this.isNullable() == ((JavaRecordType) obj).isNullable();
61+
}
62+
63+
@Override public int deepHashCode() {
64+
return Objects.hash(fieldList, this.isNullable(), clazz);
65+
}
5466
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.rel;
18+
19+
/**
20+
* Interface for objects that have a digest string.
21+
*/
22+
public interface HasDigestString {
23+
String getDigestString();
24+
}

core/src/main/java/org/apache/calcite/rel/type/RelDataType.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,4 +322,26 @@ default boolean equalsSansFieldNamesAndNullability(@Nullable RelDataType that) {
322322
default boolean isMeasure() {
323323
return getSqlTypeName() == SqlTypeName.MEASURE;
324324
}
325+
326+
/**
327+
* Returns the digest of this type.
328+
*
329+
* @return digest of this type
330+
*/
331+
RelDataTypeDigest getDigest();
332+
333+
/**
334+
* Deep equality check for RelDataType digest.
335+
*
336+
* @return Whether the 2 RelDataTypes are equivalent or have the same digest.
337+
* @see #deepHashCode()
338+
*/
339+
boolean deepEquals(@Nullable Object obj);
340+
341+
/**
342+
* Compute deep hash code for RelDataType digest.
343+
*
344+
* @see #deepEquals(Object)
345+
*/
346+
int deepHashCode();
325347
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.calcite.rel.type;
18+
19+
import org.apache.calcite.rel.HasDigestString;
20+
21+
/**
22+
* Digest of a RelDataType.
23+
*/
24+
public interface RelDataTypeDigest extends HasDigestString {
25+
RelDataType getType();
26+
}

core/src/main/java/org/apache/calcite/rel/type/RelDataTypeImpl.java

Lines changed: 120 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.calcite.rel.type;
1818

19+
import org.apache.calcite.config.CalciteSystemProperty;
1920
import org.apache.calcite.sql.SqlCollation;
2021
import org.apache.calcite.sql.SqlIdentifier;
2122
import org.apache.calcite.sql.SqlIntervalQualifier;
@@ -46,8 +47,8 @@
4647
* RelDataTypeImpl is an abstract base for implementations of
4748
* {@link RelDataType}.
4849
*
49-
* <p>Identity is based upon the {@link #digest} field, which each derived class
50-
* should set during construction.
50+
* <p>Identity is based upon the {@link #digest} or {@link #innerDigest} field,
51+
* which each derived class should set {@link #digest} or {@link #innerDigest} during construction.
5152
*/
5253
public abstract class RelDataTypeImpl
5354
implements RelDataType, RelDataTypeFamily {
@@ -60,7 +61,14 @@ public abstract class RelDataTypeImpl
6061
//~ Instance fields --------------------------------------------------------
6162

6263
protected final @Nullable List<RelDataTypeField> fieldList;
63-
protected @Nullable String digest;
64+
65+
/**
66+
* Use {@link #innerDigest} instead.
67+
*
68+
* @deprecated See {@link CalciteSystemProperty#DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING}.
69+
*/
70+
protected @Deprecated @Nullable String digest;
71+
protected @Nullable RelDataTypeDigest innerDigest;
6472

6573
//~ Constructors -----------------------------------------------------------
6674

@@ -232,18 +240,50 @@ private static void getFieldRecurse(List<Slot> slots, RelDataType type,
232240
return fieldList != null;
233241
}
234242

243+
/**
244+
* Gets the {@link RelDataTypeDigest} of this type.
245+
* If a user has set the legacy string {@code digest} and {@code innerDigest} has not
246+
* been initialized yet, this method computes and initializes it.
247+
*/
248+
@Override public RelDataTypeDigest getDigest() {
249+
if (digest != null && innerDigest == null) {
250+
innerDigest = new InnerRelDataTypeDigest();
251+
}
252+
return requireNonNull(innerDigest, "innerDigest");
253+
}
254+
235255
@Override public boolean equals(@Nullable Object obj) {
236-
return this == obj
237-
|| obj instanceof RelDataTypeImpl
238-
&& Objects.equals(this.digest, ((RelDataTypeImpl) obj).digest);
256+
if (obj == this) {
257+
return true;
258+
}
259+
if (obj instanceof RelDataTypeImpl) {
260+
final RelDataTypeImpl that = (RelDataTypeImpl) obj;
261+
return this.getDigest().equals(that.getDigest());
262+
}
263+
return false;
239264
}
240265

241266
@Override public int hashCode() {
242-
return Objects.hashCode(digest);
267+
return getDigest().hashCode();
268+
}
269+
270+
@Override public boolean deepEquals(@Nullable Object obj) {
271+
if (this == obj) {
272+
return true;
273+
}
274+
if (obj == null || this.getClass() != obj.getClass()) {
275+
return false;
276+
}
277+
return Objects.equals(this.getDigest().getDigestString(),
278+
((RelDataTypeImpl) obj).getDigest().getDigestString());
279+
}
280+
281+
@Override public int deepHashCode() {
282+
return Objects.hashCode(this.getDigest().getDigestString());
243283
}
244284

245285
@Override public String getFullTypeString() {
246-
return requireNonNull(digest, "digest");
286+
return requireNonNull(this.getDigest().getDigestString(), "digest");
247287
}
248288

249289
@Override public boolean isNullable() {
@@ -309,23 +349,85 @@ protected abstract void generateTypeString(
309349
boolean withDetail);
310350

311351
/**
312-
* Computes the digest field. This should be called in every non-abstract
313-
* subclass constructor once the type is fully defined.
352+
* Init the lazy digest computing field {@link #innerDigest}.
353+
* This should be called in every non-abstract subclass
354+
* constructor once the type is fully defined.
314355
*/
315356
@SuppressWarnings("method.invocation.invalid")
316357
protected void computeDigest(@UnknownInitialization RelDataTypeImpl this) {
317-
StringBuilder sb = new StringBuilder();
318-
generateTypeString(sb, true);
319-
if (!isNullable()) {
320-
sb.append(NON_NULLABLE_SUFFIX);
358+
digest = null;
359+
innerDigest = new InnerRelDataTypeDigest();
360+
if (!CalciteSystemProperty.DISABLE_GENERATE_REL_DATA_TYPE_DIGEST_STRING.value()) {
361+
digest = this.getDigest().getDigestString();
321362
}
322-
digest = sb.toString();
323363
}
324364

325365
@Override public String toString() {
326-
StringBuilder sb = new StringBuilder();
327-
generateTypeString(sb, false);
328-
return sb.toString();
366+
return getDigest().toString();
367+
}
368+
369+
/** Implementation of {@link RelDataTypeDigest}. */
370+
private class InnerRelDataTypeDigest implements RelDataTypeDigest {
371+
/** Cached hash code. */
372+
private int hash = 0;
373+
/** Cached type string. */
374+
private @Nullable String digestWithDetail = null; // NOTE: shorter detail will be better
375+
private @Nullable String digestWithoutDetail = null;
376+
377+
@Override public RelDataType getType() {
378+
return RelDataTypeImpl.this;
379+
}
380+
381+
@Override public boolean equals(@Nullable Object o) {
382+
if (this == o) {
383+
return true;
384+
}
385+
if (o == null || getClass() != o.getClass()) {
386+
return false;
387+
}
388+
final RelDataTypeImpl.InnerRelDataTypeDigest otherDigest =
389+
(RelDataTypeImpl.InnerRelDataTypeDigest) o;
390+
if (digest != null) {
391+
return digest.equals(otherDigest.getDigestString());
392+
}
393+
return deepEquals(otherDigest.getType());
394+
}
395+
396+
@Override public int hashCode() {
397+
if (digest != null) {
398+
return Objects.hashCode(digest);
399+
}
400+
if (hash == 0) {
401+
hash = deepHashCode();
402+
}
403+
return hash;
404+
}
405+
406+
@Override public String getDigestString() {
407+
// return user defined digest by set legacy digest string field.
408+
if (digest != null) {
409+
return digest;
410+
}
411+
412+
if (digestWithDetail == null) {
413+
StringBuilder sb = new StringBuilder();
414+
generateTypeString(sb, true);
415+
if (!isNullable()) {
416+
sb.append(NON_NULLABLE_SUFFIX);
417+
}
418+
digestWithDetail = sb.toString();
419+
}
420+
return digestWithDetail;
421+
}
422+
423+
@Override public String toString() {
424+
if (digestWithoutDetail == null || digest != null) {
425+
StringBuilder sb = new StringBuilder();
426+
RelDataTypeImpl.this.generateTypeString(sb, false);
427+
digestWithoutDetail = sb.toString();
428+
}
429+
return digestWithoutDetail;
430+
}
329431
}
330432

331433
@Override public RelDataTypePrecedenceList getPrecedenceList() {

core/src/main/java/org/apache/calcite/rel/type/RelRecordType.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Objects;
3031

3132
import static java.util.Objects.requireNonNull;
3233

@@ -146,6 +147,39 @@ public RelRecordType(List<RelDataTypeField> fields) {
146147
sb.append(")");
147148
}
148149

150+
@Override public boolean deepEquals(@Nullable Object obj) {
151+
if (this == obj) {
152+
return true;
153+
}
154+
if (obj == null || this.getClass() != obj.getClass()) {
155+
return false;
156+
}
157+
158+
RelRecordType that = (RelRecordType) obj;
159+
if (kind != that.kind || nullable != that.nullable) {
160+
return false;
161+
}
162+
163+
if (fieldList == null || that.fieldList == null) {
164+
return fieldList == null && that.fieldList == null;
165+
}
166+
167+
if (fieldList.size() != that.fieldList.size()) {
168+
return false;
169+
}
170+
171+
for (int i = 0; i < fieldList.size(); i++) {
172+
if (!fieldList.get(i).equals(that.fieldList.get(i))) {
173+
return false;
174+
}
175+
}
176+
return true;
177+
}
178+
179+
@Override public int deepHashCode() {
180+
return Objects.hash(kind.ordinal(), nullable, fieldList);
181+
}
182+
149183
/**
150184
* Per {@link Serializable} API, provides a replacement object to be written
151185
* during serialization.

core/src/main/java/org/apache/calcite/rel/type/SingleColumnAliasRelDataType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,16 @@ public SingleColumnAliasRelDataType(RelDataType original, RelDataType alias) {
136136
@Override public boolean isDynamicStruct() {
137137
return original.isDynamicStruct();
138138
}
139+
140+
@Override public RelDataTypeDigest getDigest() {
141+
return original.getDigest();
142+
}
143+
144+
@Override public boolean deepEquals(@Nullable Object obj) {
145+
return original.deepEquals(obj);
146+
}
147+
148+
@Override public int deepHashCode() {
149+
return original.deepHashCode();
150+
}
139151
}

core/src/main/java/org/apache/calcite/sql/type/ArraySqlType.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
import org.apache.calcite.rel.type.RelDataTypeFamily;
2121
import org.apache.calcite.rel.type.RelDataTypePrecedenceList;
2222

23+
import org.checkerframework.checker.nullness.qual.Nullable;
24+
25+
import java.util.Objects;
26+
2327
import static org.apache.calcite.sql.type.NonNullableAccessors.getComponentTypeOrThrow;
2428

2529
import static java.util.Objects.requireNonNull;
@@ -56,6 +60,21 @@ public ArraySqlType(RelDataType elementType, boolean isNullable) {
5660
sb.append(" ARRAY");
5761
}
5862

63+
@Override public boolean deepEquals(@Nullable Object obj) {
64+
if (this == obj) {
65+
return true;
66+
}
67+
if (obj == null || this.getClass() != obj.getClass()) {
68+
return false;
69+
}
70+
ArraySqlType that = (ArraySqlType) obj;
71+
return this.isNullable() == that.isNullable() && elementType.equals(that.elementType);
72+
}
73+
74+
@Override public int deepHashCode() {
75+
return Objects.hash(SqlTypeName.ARRAY.ordinal(), isNullable, elementType.hashCode());
76+
}
77+
5978
// implement RelDataType
6079
@Override public RelDataType getComponentType() {
6180
return elementType;

0 commit comments

Comments
 (0)