diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java index 544f9ca708da..2c21f8b278ad 100644 --- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFactoryImpl.java @@ -30,6 +30,8 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReferenceArray; import static com.google.common.base.Preconditions.checkArgument; @@ -42,6 +44,11 @@ * {@link RelDataTypeFactory} which supports SQL types. */ public class SqlTypeFactoryImpl extends RelDataTypeFactoryImpl { + private final AtomicReferenceArray sqlTypesWithPrecision = + new AtomicReferenceArray<>(SqlTypeName.values().length); + private final AtomicReferenceArray decoratedSqlTypes = + new AtomicReferenceArray<>(SqlTypeName.values().length); + //~ Constructors ----------------------------------------------------------- public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) { @@ -72,12 +79,25 @@ public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) { assertBasic(typeName); assert (precision >= 0) || (precision == RelDataType.PRECISION_NOT_SPECIFIED); + final int typeIndex = typeName.ordinal(); + final @Nullable Charset defaultCharset = + SqlTypeName.CHAR_TYPES.contains(typeName) ? getDefaultCharset() : null; + final @Nullable SqlTypeWithPrecision lastType = + sqlTypesWithPrecision.get(typeIndex); + if (lastType != null + && lastType.precision == precision + && Objects.equals(lastType.defaultCharset, defaultCharset)) { + return lastType.type; + } // Does not check precision when typeName is SqlTypeName#NULL. RelDataType newType = precision == RelDataType.PRECISION_NOT_SPECIFIED ? new BasicSqlType(typeSystem, typeName) : new BasicSqlType(typeSystem, typeName, precision); newType = SqlTypeUtil.addCharsetAndCollation(newType, this); - return canonize(newType); + newType = canonize(newType); + sqlTypesWithPrecision.set(typeIndex, + new SqlTypeWithPrecision(precision, defaultCharset, newType)); + return newType; } @SuppressWarnings("deprecation") // [CALCITE-6598] @@ -168,7 +188,17 @@ public SqlTypeFactoryImpl(RelDataTypeSystem typeSystem) { RelDataType newType; if (type instanceof BasicSqlType) { BasicSqlType sqlType = (BasicSqlType) type; + int index = sqlType.getSqlTypeName().ordinal(); + @Nullable DecoratedSqlType cached = decoratedSqlTypes.get(index); + if (cached != null && cached.source == type + && cached.charset.equals(charset) && cached.collation == collation) { + return cached.result; + } newType = sqlType.createWithCharsetAndCollation(charset, collation); + newType = canonize(newType); + decoratedSqlTypes.set(index, + new DecoratedSqlType(sqlType, charset, collation, newType)); + return newType; } else if (type instanceof JavaType) { JavaType javaType = (JavaType) type; newType = @@ -312,6 +342,36 @@ private static void assertBasic(SqlTypeName typeName) { : "use createSqlIntervalType() instead"; } + /** Most recently created precision for a SQL type that has no scale. */ + private static class SqlTypeWithPrecision { + private final int precision; + private final @Nullable Charset defaultCharset; + private final RelDataType type; + + private SqlTypeWithPrecision(int precision, @Nullable Charset defaultCharset, + RelDataType type) { + this.precision = precision; + this.defaultCharset = defaultCharset; + this.type = type; + } + } + + /** Most recent charset/collation decoration for a SQL type name. */ + private static class DecoratedSqlType { + private final BasicSqlType source; + private final Charset charset; + private final SqlCollation collation; + private final RelDataType result; + + DecoratedSqlType(BasicSqlType source, Charset charset, SqlCollation collation, + RelDataType result) { + this.source = source; + this.charset = charset; + this.collation = collation; + this.result = result; + } + } + @SuppressWarnings("deprecation") // [CALCITE-6598] private @Nullable RelDataType leastRestrictiveSqlType(List types) { RelDataType resultType = null; diff --git a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java index 68a34eb5bac7..93930e8710f8 100644 --- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java +++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeFactoryTest.java @@ -21,12 +21,14 @@ import org.apache.calcite.rel.type.RelDataTypeFieldImpl; import org.apache.calcite.rel.type.RelRecordType; import org.apache.calcite.rel.type.StructKind; +import org.apache.calcite.sql.SqlCollation; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.junit.jupiter.api.Test; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -38,6 +40,7 @@ import static org.hamcrest.Matchers.hasToString; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -46,6 +49,43 @@ */ class SqlTypeFactoryTest { + @Test void testReuseMostRecentSqlTypeWithPrecision() { + SqlTypeFixture f = new SqlTypeFixture(); + RelDataType varchar17 = f.typeFactory.createSqlType(SqlTypeName.VARCHAR, 17); + assertSame(varchar17, + f.typeFactory.createSqlType(SqlTypeName.VARCHAR, 17)); + + RelDataType varchar18 = f.typeFactory.createSqlType(SqlTypeName.VARCHAR, 18); + assertThat(varchar18.getPrecision(), is(18)); + assertThat(f.typeFactory.createSqlType(SqlTypeName.VARCHAR, 17).getPrecision(), is(17)); + + RelDataType char17 = f.typeFactory.createSqlType(SqlTypeName.CHAR, 17); + assertSame(char17, f.typeFactory.createSqlType(SqlTypeName.CHAR, 17)); + } + + @Test void testCachedCharsetAndCollationDecoration() { + SqlTypeFixture f = new SqlTypeFixture(); + RelDataTypeFactory typeFactory = f.typeFactory; + RelDataType source = typeFactory.createSqlType(SqlTypeName.VARCHAR, 17); + RelDataType decorated = + typeFactory.createTypeWithCharsetAndCollation( + source, StandardCharsets.UTF_8, SqlCollation.COERCIBLE); + assertSame( + decorated, + typeFactory.createTypeWithCharsetAndCollation( + source, StandardCharsets.UTF_8, SqlCollation.COERCIBLE)); + + RelDataType differentCharset = + typeFactory.createTypeWithCharsetAndCollation( + source, StandardCharsets.UTF_16, SqlCollation.COERCIBLE); + assertThat(differentCharset.getCharset(), is(StandardCharsets.UTF_16)); + RelDataType otherSource = typeFactory.createSqlType(SqlTypeName.VARCHAR, 18); + RelDataType other = + typeFactory.createTypeWithCharsetAndCollation( + otherSource, StandardCharsets.UTF_8, SqlCollation.COERCIBLE); + assertThat(other.getPrecision(), is(18)); + } + @Test void testLeastRestrictiveWithAny() { SqlTypeFixture f = new SqlTypeFixture(); RelDataType leastRestrictive =