Skip to content

Commit

Permalink
[CALCITE-4872] Add UNKNOWN value to enum SqlTypeName, distinct from t…
Browse files Browse the repository at this point in the history
…he NULL type

Before this change, the UNKNOWN type would become the NULL type
when switching nullability.

Close apache#2595
  • Loading branch information
wnob authored and liyafan82 committed Mar 4, 2022
1 parent 466fb42 commit 67ba007
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,12 @@ private SqlNode callToSql(@Nullable RexProgram program, RexCall call0,
SqlNode fieldOperand = field(ordinal);
return SqlStdOperatorTable.CURSOR.createCall(SqlParserPos.ZERO, fieldOperand);
}
if (ignoreCast) {
// Ideally the UNKNOWN type would never exist in a fully-formed, validated rel node, but
// it can be useful in certain situations where determining the type of an expression is
// infeasible, such as inserting arbitrary user-provided SQL snippets into an otherwise
// manually-constructed (as opposed to parsed) rel node.
// In such a context, assume that casting anything to UNKNOWN is a no-op.
if (ignoreCast || call.getType().getSqlTypeName() == SqlTypeName.UNKNOWN) {
assert nodeList.size() == 1;
return nodeList.get(0);
} else {
Expand Down
29 changes: 17 additions & 12 deletions core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public class BasicSqlType extends AbstractSqlType {

private final int precision;
private final int scale;
private final RelDataTypeSystem typeSystem;
protected final RelDataTypeSystem typeSystem;
private final @Nullable SqlCollation collation;
private final @Nullable SerializableCharset wrappedCharset;

Expand All @@ -54,19 +54,14 @@ public class BasicSqlType extends AbstractSqlType {
* @param typeName Type name
*/
public BasicSqlType(RelDataTypeSystem typeSystem, SqlTypeName typeName) {
this(typeSystem, typeName, false, PRECISION_NOT_SPECIFIED,
SCALE_NOT_SPECIFIED, null, null);
checkPrecScale(typeName, false, false);
this(typeSystem, typeName, false);
}

/** Throws if {@code typeName} does not allow the given combination of
* precision and scale. */
protected static void checkPrecScale(SqlTypeName typeName,
boolean precisionSpecified, boolean scaleSpecified) {
if (!typeName.allowsPrecScale(precisionSpecified, scaleSpecified)) {
throw new AssertionError("typeName.allowsPrecScale("
+ precisionSpecified + ", " + scaleSpecified + "): " + typeName);
}
protected BasicSqlType(RelDataTypeSystem typeSystem, SqlTypeName typeName,
boolean nullable) {
this(typeSystem, typeName, nullable, PRECISION_NOT_SPECIFIED,
SCALE_NOT_SPECIFIED, null, null);
checkPrecScale(typeName, false, false);
}

/**
Expand Down Expand Up @@ -115,6 +110,16 @@ private BasicSqlType(
computeDigest();
}

/** Throws if {@code typeName} does not allow the given combination of
* precision and scale. */
protected static void checkPrecScale(SqlTypeName typeName,
boolean precisionSpecified, boolean scaleSpecified) {
if (!typeName.allowsPrecScale(precisionSpecified, scaleSpecified)) {
throw new AssertionError("typeName.allowsPrecScale("
+ precisionSpecified + ", " + scaleSpecified + "): " + typeName);
}
}

//~ Methods ----------------------------------------------------------------

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,20 @@ private RelDataType copyMapType(RelDataType type, boolean nullable) {

/** The unknown type. Similar to the NULL type, but is only equal to
* itself. */
private static class UnknownSqlType extends BasicSqlType {
static class UnknownSqlType extends BasicSqlType {
UnknownSqlType(RelDataTypeFactory typeFactory) {
super(typeFactory.getTypeSystem(), SqlTypeName.NULL);
this(typeFactory.getTypeSystem(), false);
}

private UnknownSqlType(RelDataTypeSystem typeSystem, boolean nullable) {
super(typeSystem, SqlTypeName.UNKNOWN, nullable);
}

@Override BasicSqlType createWithNullability(boolean nullable) {
if (nullable == this.isNullable) {
return this;
}
return new UnknownSqlType(this.typeSystem, nullable);
}

@Override protected void generateTypeString(StringBuilder sb,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ default boolean canApplyFrom(SqlTypeName to, SqlTypeName from) {
Objects.requireNonNull(to, "to");
Objects.requireNonNull(from, "from");

if (to == SqlTypeName.NULL) {
if (to == SqlTypeName.NULL || to == SqlTypeName.UNKNOWN) {
return false;
} else if (from == SqlTypeName.NULL) {
} else if (from == SqlTypeName.NULL || from == SqlTypeName.UNKNOWN) {
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ public enum SqlTypeName {
VARBINARY(PrecScale.NO_NO | PrecScale.YES_NO, false, Types.VARBINARY,
SqlTypeFamily.BINARY),
NULL(PrecScale.NO_NO, true, Types.NULL, SqlTypeFamily.NULL),
UNKNOWN(PrecScale.NO_NO, true, Types.NULL, SqlTypeFamily.NULL),
ANY(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, true,
Types.JAVA_OBJECT, SqlTypeFamily.ANY),
SYMBOL(PrecScale.NO_NO, true, Types.OTHER, null),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ private SqlTypeName toVar(RelDataType type) {
return SqlTypeName.ANY;
case NULL:
return SqlTypeName.NULL;
case UNKNOWN:
return SqlTypeName.UNKNOWN;
default:
throw Util.unexpected(sqlTypeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,9 @@ public static boolean canCastFrom(

final SqlTypeName fromTypeName = fromType.getSqlTypeName();
final SqlTypeName toTypeName = toType.getSqlTypeName();
if (toTypeName == SqlTypeName.UNKNOWN) {
return true;
}
if (toType.isStruct() || fromType.isStruct()) {
if (toTypeName == SqlTypeName.DISTINCT) {
if (fromTypeName == SqlTypeName.DISTINCT) {
Expand Down Expand Up @@ -1033,7 +1036,7 @@ public static SqlDataTypeSpec convertTypeToSpec(RelDataType type,
assert typeName != null;

final SqlTypeNameSpec typeNameSpec;
if (isAtomic(type) || isNull(type)) {
if (isAtomic(type) || isNull(type) || type.getSqlTypeName() == SqlTypeName.UNKNOWN) {
int precision = typeName.allowsPrec() ? type.getPrecision() : -1;
// fix up the precision.
if (maxPrecision > 0 && precision > maxPrecision) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rel.type.RelDataTypeFieldImpl;
import org.apache.calcite.rel.type.RelRecordType;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl.UnknownSqlType;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
Expand All @@ -33,6 +34,7 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.Is.isA;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -286,4 +288,18 @@ private void checkCreateSqlTypeWithPrecision(
assertThat(tsWithPrecision3 == tsWithPrecision8, is(true));
}

/** Test that the {code UNKNOWN} type does not does not change class when nullified. */
@Test void testUnknownCreateWithNullabilityTypeConsistency() {
SqlTypeFixture f = new SqlTypeFixture();

RelDataType unknownType = f.typeFactory.createUnknownType();
assertThat(unknownType, isA(UnknownSqlType.class));
assertThat(unknownType.getSqlTypeName(), is(SqlTypeName.UNKNOWN));
assertFalse(unknownType.isNullable());

RelDataType nullableRelDataType = f.typeFactory.createTypeWithNullability(unknownType, true);
assertThat(nullableRelDataType, isA(UnknownSqlType.class));
assertThat(nullableRelDataType.getSqlTypeName(), is(SqlTypeName.UNKNOWN));
assertTrue(nullableRelDataType.isNullable());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class SqlTypeFixture {
typeFactory.createSqlType(SqlTypeName.VARCHAR), true);
final RelDataType sqlNull = typeFactory.createTypeWithNullability(
typeFactory.createSqlType(SqlTypeName.NULL), false);
final RelDataType sqlUnknown = typeFactory.createTypeWithNullability(
typeFactory.createSqlType(SqlTypeName.UNKNOWN), false);
final RelDataType sqlAny = typeFactory.createTypeWithNullability(
typeFactory.createSqlType(SqlTypeName.ANY), false);
final RelDataType sqlFloat = typeFactory.createTypeWithNullability(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import static org.apache.calcite.sql.type.SqlTypeUtil.areSameFamily;
Expand Down Expand Up @@ -155,6 +156,10 @@ class SqlTypeUtilTest {
(SqlBasicTypeNameSpec) convertTypeToSpec(f.sqlNull).getTypeNameSpec();
assertThat(nullSpec.getTypeName().getSimple(), is("NULL"));

SqlBasicTypeNameSpec unknownSpec =
(SqlBasicTypeNameSpec) convertTypeToSpec(f.sqlUnknown).getTypeNameSpec();
assertThat(unknownSpec.getTypeName().getSimple(), is("UNKNOWN"));

SqlBasicTypeNameSpec basicSpec =
(SqlBasicTypeNameSpec) convertTypeToSpec(f.sqlBigInt).getTypeNameSpec();
assertThat(basicSpec.getTypeName().getSimple(), is("BIGINT"));
Expand Down Expand Up @@ -226,4 +231,36 @@ private void compareTypesIgnoringNullability(
compareTypesIgnoringNullability("identical types should return true.",
bigIntType, bigIntType1, true);
}

@Test void testCanAlwaysCastToUnknownFromBasic() {
RelDataType unknownType = f.typeFactory.createUnknownType();
RelDataType nullableUnknownType = f.typeFactory.createTypeWithNullability(unknownType, true);

for (SqlTypeName fromTypeName : SqlTypeName.values()) {
BasicSqlType fromType;
try {
// This only works for basic types. Ignore the rest.
fromType = (BasicSqlType) f.typeFactory.createSqlType(fromTypeName);
} catch (AssertionError e) {
continue;
}
BasicSqlType nullableFromType = fromType.createWithNullability(!fromType.isNullable);

assertCanCast(unknownType, fromType);
assertCanCast(unknownType, nullableFromType);
assertCanCast(nullableUnknownType, fromType);
assertCanCast(nullableUnknownType, nullableFromType);
}
}

private static void assertCanCast(RelDataType toType, RelDataType fromType) {
assertThat(
String.format(Locale.ROOT,
"Expected to be able to cast from %s to %s without coercion.", fromType, toType),
SqlTypeUtil.canCastFrom(toType, fromType, /* coerce= */ false), is(true));
assertThat(
String.format(Locale.ROOT,
"Expected to be able to cast from %s to %s with coercion.", fromType, toType),
SqlTypeUtil.canCastFrom(toType, fromType, /* coerce= */ true), is(true));
}
}
3 changes: 2 additions & 1 deletion site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1150,7 +1150,8 @@ Note:

| Type | Description | Example literals
|:-------- |:---------------------------|:---------------
| ANY | A value of an unknown type |
| ANY | The union of all types |
| UNKNOWN | A value of an unknown type; used as a placeholder |
| ROW | Row with 1 or more columns | Example: Row(f0 int null, f1 varchar)
| MAP | Collection of keys mapped to values |
| MULTISET | Unordered collection that may contain duplicates | Example: int multiset
Expand Down

0 comments on commit 67ba007

Please sign in to comment.