Skip to content

Commit 078f2e4

Browse files
committed
[CALCITE-7393] Support RelDataTypeDigest
use RelDataTypeDigest to reduce composite type's digest memory and digest relative operation's latency This is a break change, computeDigest will not set the string digest field, and innerDigest will be initialized by default. We recommend users use innerDigest to replace legacy string digest to archive better memory/latency for nested types. For users rely on set String digest for UDT, we keep the old behavior of hashCode/equals.
1 parent 8a286c5 commit 078f2e4

File tree

12 files changed

+355
-25
lines changed

12 files changed

+355
-25
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ public JavaRecordType(List<RelDataTypeField> fields, Class clazz) {
4141
this.clazz = requireNonNull(clazz, "clazz");
4242
}
4343

44-
@Override public boolean equals(@Nullable Object obj) {
44+
@Override public boolean deepEquals(@Nullable Object obj) {
4545
return this == obj
4646
|| obj instanceof JavaRecordType
4747
&& Objects.equals(fieldList, ((JavaRecordType) obj).fieldList)
48-
&& clazz == ((JavaRecordType) obj).clazz;
48+
&& clazz == ((JavaRecordType) obj).clazz
49+
&& this.isNullable() == ((JavaRecordType) obj).isNullable();
4950
}
5051

51-
@Override public int hashCode() {
52-
return Objects.hash(fieldList, clazz);
52+
@Override public int deepHashCode() {
53+
return Objects.hash(fieldList, this.isNullable(), clazz);
5354
}
5455
}
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: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@
4646
* RelDataTypeImpl is an abstract base for implementations of
4747
* {@link RelDataType}.
4848
*
49-
* <p>Identity is based upon the {@link #digest} field, which each derived class
50-
* should set during construction.
49+
* <p>Identity is based upon the {@link #digest} or {@link #innerDigest} field,
50+
* which each derived class should set {@link #digest} or {@link #innerDigest} during construction.
5151
*/
5252
public abstract class RelDataTypeImpl
5353
implements RelDataType, RelDataTypeFamily {
@@ -60,7 +60,8 @@ public abstract class RelDataTypeImpl
6060
//~ Instance fields --------------------------------------------------------
6161

6262
protected final @Nullable List<RelDataTypeField> fieldList;
63-
protected @Nullable String digest;
63+
protected @Deprecated @Nullable String digest;
64+
protected @Nullable RelDataTypeDigest innerDigest;
6465

6566
//~ Constructors -----------------------------------------------------------
6667

@@ -232,18 +233,50 @@ private static void getFieldRecurse(List<Slot> slots, RelDataType type,
232233
return fieldList != null;
233234
}
234235

235-
@Override public boolean equals(@Nullable Object obj) {
236-
return this == obj
237-
|| obj instanceof RelDataTypeImpl
238-
&& Objects.equals(this.digest, ((RelDataTypeImpl) obj).digest);
236+
/**
237+
* Gets the {@link RelDataTypeDigest} of this type.
238+
* If a user has set the legacy string {@code digest} and {@code innerDigest} has not
239+
* been initialized yet, this method computes and initializes it.
240+
*/
241+
@Override public RelDataTypeDigest getDigest() {
242+
if (digest != null && innerDigest == null) {
243+
computeDigest();
244+
}
245+
return requireNonNull(innerDigest, "innerDigest");
246+
}
247+
248+
@Override public final boolean equals(@Nullable Object obj) {
249+
if (obj == this) {
250+
return true;
251+
}
252+
if (obj instanceof RelDataTypeImpl) {
253+
final RelDataTypeImpl that = (RelDataTypeImpl) obj;
254+
return this.getDigest().equals(that.getDigest());
255+
}
256+
return false;
239257
}
240258

241-
@Override public int hashCode() {
242-
return Objects.hashCode(digest);
259+
@Override public final int hashCode() {
260+
return getDigest().hashCode();
261+
}
262+
263+
@Override public boolean deepEquals(@Nullable Object obj) {
264+
if (this == obj) {
265+
return true;
266+
}
267+
if (obj == null || this.getClass() != obj.getClass()) {
268+
return false;
269+
}
270+
return Objects.equals(this.getDigest().getDigestString(),
271+
((RelDataTypeImpl) obj).getDigest().getDigestString());
272+
}
273+
274+
@Override public int deepHashCode() {
275+
return Objects.hashCode(this.getDigest().getDigestString());
243276
}
244277

245278
@Override public String getFullTypeString() {
246-
return requireNonNull(digest, "digest");
279+
return requireNonNull(this.getDigest().getDigestString(), "digest");
247280
}
248281

249282
@Override public boolean isNullable() {
@@ -309,23 +342,81 @@ protected abstract void generateTypeString(
309342
boolean withDetail);
310343

311344
/**
312-
* Computes the digest field. This should be called in every non-abstract
313-
* subclass constructor once the type is fully defined.
345+
* Init the lazy digest computing field {@link #innerDigest}.
346+
* This should be called in every non-abstract subclass
347+
* constructor once the type is fully defined.
314348
*/
315349
@SuppressWarnings("method.invocation.invalid")
316350
protected void computeDigest(@UnknownInitialization RelDataTypeImpl this) {
317-
StringBuilder sb = new StringBuilder();
318-
generateTypeString(sb, true);
319-
if (!isNullable()) {
320-
sb.append(NON_NULLABLE_SUFFIX);
321-
}
322-
digest = sb.toString();
351+
innerDigest = new InnerRelDataTypeDigest();
323352
}
324353

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

331422
@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)