Skip to content

Commit

Permalink
Allow a VARIANT storing a numeric value be cast to another numeric value
Browse files Browse the repository at this point in the history
Signed-off-by: Mihai Budiu <mbudiu@feldera.com>
  • Loading branch information
mihaibudiu committed Sep 11, 2024
1 parent 849f408 commit 78bff8d
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,8 @@ private Expression getConvertExpression(
case VARIANT:
// Converting any type to a VARIANT invokes the Variant constructor
Expression rtti = RuntimeTypeInformation.createExpression(sourceType);
return Expressions.call(BuiltInMethod.VARIANT_CREATE.method, operand, rtti);
Expression roundingMode = Expressions.constant(typeFactory.getTypeSystem().roundingMode());
return Expressions.call(BuiltInMethod.VARIANT_CREATE.method, roundingMode, operand, rtti);
case ANY:
return operand;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@

import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeField;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.AbstractMap;

import static java.util.Objects.requireNonNull;
Expand Down Expand Up @@ -93,6 +96,28 @@ public boolean isScalar() {
return this.typeName.isScalar();
}

/** If this type is a Primitive, return it, otherwise return null. */
public @Nullable Primitive asPrimitive() {
switch (typeName) {
case BOOLEAN:
return Primitive.BOOLEAN;
case TINYINT:
return Primitive.BYTE;
case SMALLINT:
return Primitive.SHORT;
case INTEGER:
return Primitive.INT;
case BIGINT:
return Primitive.LONG;
case REAL:
return Primitive.FLOAT;
case DOUBLE:
return Primitive.DOUBLE;
default:
return null;
}
}

/**
* Creates and returns an expression that creates a runtime type that
* reflects the information in the statically-known type 'type'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,79 @@
*/
package org.apache.calcite.runtime.variant;

import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.runtime.rtti.GenericSqlTypeRtti;
import org.apache.calcite.runtime.rtti.RowSqlTypeRtti;
import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

import static java.util.Objects.requireNonNull;

/** A VARIANT value that contains a non-null value. */
public class VariantNonNull extends VariantSqlValue {
final RoundingMode roundingMode;
/** Actual value - can have any SQL type. */
final Object value;

VariantNonNull(Object value, RuntimeTypeInformation runtimeType) {
VariantNonNull(RoundingMode roundingMode, Object value, RuntimeTypeInformation runtimeType) {
super(runtimeType);
this.roundingMode = roundingMode;
this.value = value;
// sanity check
switch (runtimeType.getTypeName()) {
case BOOLEAN:
assert value instanceof Boolean;
break;
case TINYINT:
assert value instanceof Byte;
break;
case SMALLINT:
assert value instanceof Short;
break;
case INTEGER:
assert value instanceof Integer;
break;
case BIGINT:
assert value instanceof Long;
break;
case DECIMAL:
assert value instanceof BigDecimal;
break;
case REAL:
assert value instanceof Float;
break;
case DOUBLE:
assert value instanceof Double;
break;
case DATE:
case TIME:
case TIME_WITH_LOCAL_TIME_ZONE:
case TIME_TZ:
case TIMESTAMP:
case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
case TIMESTAMP_TZ:
case INTERVAL_LONG:
case INTERVAL_SHORT:
break;
case VARCHAR:
assert value instanceof String;
break;
case VARBINARY:
case NULL:
case MULTISET:
case ARRAY:
case MAP:
case ROW:
case GEOMETRY:
case VARIANT:
break;
}
}

@Override public boolean equals(@Nullable Object o) {
Expand Down Expand Up @@ -63,9 +119,135 @@ public class VariantNonNull extends VariantSqlValue {
if (this.runtimeType.equals(type)) {
return this.value;
} else {
// Convert numeric values
@Nullable Primitive target = type.asPrimitive();
switch (this.runtimeType.getTypeName()) {
case TINYINT: {
byte b = (byte) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(b, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(b);
default:
break;
}
break;
}
case SMALLINT: {
short s = (short) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(s, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(s);
default:
break;
}
break;
}
case INTEGER: {
int i = (int) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(i, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(i);
default:
break;
}
break;
}
case BIGINT: {
long l = (int) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(l, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(l);
default:
break;
}
break;
}
case DECIMAL: {
BigDecimal d = (BigDecimal) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(d, roundingMode);
case DECIMAL:
return d;
default:
break;
}
break;
}
case REAL: {
float f = (float) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(f, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(f);
default:
break;
}
break;
}
case DOUBLE: {
double d = (double) value;
switch (type.getTypeName()) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case REAL:
case DOUBLE:
return requireNonNull(target, "target").numberValue(d, roundingMode);
case DECIMAL:
return BigDecimal.valueOf(d);
default:
break;
}
break;
}
default:
break;
}
return null;
}
} else {
// Derived type: ARRAY, MAP, etc.
if (this.runtimeType.equals(type)) {
return this.value;
}
Expand Down Expand Up @@ -110,7 +292,7 @@ public class VariantNonNull extends VariantSqlValue {
return (VariantValue) result;
}
// Otherwise pack the result in a Variant
return VariantSqlValue.create(result, fieldType);
return VariantSqlValue.create(roundingMode, result, fieldType);
}

// This method is called by the testing code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import org.checkerframework.checker.nullness.qual.Nullable;

import java.math.RoundingMode;

/** A value of VARIANT type that represents a SQL value
* (The VARIANT type also has a null value which is different
* from any other SQL value). */
Expand All @@ -39,17 +41,19 @@ protected VariantSqlValue(RuntimeTypeInformation runtimeType) {
* Create a VariantValue from a specified SQL value and the runtime type information.
*
* @param object SQL runtime value.
* @param roundingMode Rounding mode used for converting numeric values.
* @param type Runtime type information.
* @return The created VariantValue.
*/
// Normally this method should be in the VariantValue class, but the Janino
// compiler used by Calcite compiles to a Java version that does not
// support static methods in interfaces.
// This method is called from BuiltInMethods.VARIANT_CREATE.
public static VariantValue create(@Nullable Object object, RuntimeTypeInformation type) {
public static VariantValue create(
RoundingMode roundingMode, @Nullable Object object, RuntimeTypeInformation type) {
if (object == null) {
return new VariantSqlNull(type);
}
return new VariantNonNull(object, type);
return new VariantNonNull(roundingMode, object, type);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,8 @@ public enum BuiltInMethod {
BIG_DECIMAL_ADD(BigDecimal.class, "add", BigDecimal.class),
BIG_DECIMAL_NEGATE(BigDecimal.class, "negate"),
COMPARE_TO(Comparable.class, "compareTo", Object.class),
VARIANT_CREATE(VariantSqlValue.class, "create", Object.class, RuntimeTypeInformation.class),
VARIANT_CREATE(VariantSqlValue.class, "create", RoundingMode.class,
Object.class, RuntimeTypeInformation.class),
VARIANT_CAST(VariantValue.class, "cast", RuntimeTypeInformation.class),
TYPEOF(VariantValue.class, "getTypeString", VariantValue.class),
VARIANT_ITEM(SqlFunctions.class, "item", VariantValue.class, Object.class),
Expand Down
4 changes: 2 additions & 2 deletions core/src/test/resources/sql/variant.iq
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ C
1
!ok

# However, you have to use the right type, or you get NULL
# Variant converts between numeric types
SELECT CAST(CAST(1 AS VARIANT) AS TINYINT) AS C;
C
null
1
!ok

# Some VARIANT objects when output receive double quotes
Expand Down
5 changes: 3 additions & 2 deletions site/_docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1259,8 +1259,9 @@ Any such value holds at runtime two pieces of information:
Values of `VARIANT` type can be created by casting any other value to a `VARIANT`: e.g.
`SELECT CAST(x AS VARIANT)`. Conversely, values of type `VARIANT` can be cast to any other data type
`SELECT CAST(variant AS INT)`. A cast of a value of type `VARIANT` to target type T
will compare the runtime type with T. If the types are identical, the
original value is returned. Otherwise the `CAST` returns `NULL`.
will compare the runtime type with T. If the types are identical or the types are
numeric and there is a natural conversion between the two types, the
original value is converted to the target type and returned. Otherwise the `CAST` returns `NULL`.

Values of type `ARRAY`, `MAP`, and `ROW` type can be cast to `VARIANT`. `VARIANT` values
also offer the following operations:
Expand Down

0 comments on commit 78bff8d

Please sign in to comment.