This is an automated email from the ASF dual-hosted git repository.
mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new da6a2042f0 [CALCITE-1466] Support for UNSIGNED types of TINYINT,
SMALLINT, INT, BIGINT in type system
da6a2042f0 is described below
commit da6a2042f01c4bfa64253a040149a263d4923b43
Author: Mihai Budiu <[email protected]>
AuthorDate: Fri May 30 20:42:26 2025 -0700
[CALCITE-1466] Support for UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT
in type system
Signed-off-by: Mihai Budiu <[email protected]>
---
bom/build.gradle.kts | 1 +
core/build.gradle.kts | 1 +
core/src/main/codegen/templates/Parser.jj | 37 ++-
.../calcite/adapter/enumerable/EnumUtils.java | 26 +-
.../calcite/adapter/enumerable/RexImpTable.java | 36 ++-
.../adapter/enumerable/RexToLixTranslator.java | 26 ++
.../apache/calcite/jdbc/JavaTypeFactoryImpl.java | 12 +
.../calcite/rel/type/RelDataTypeSystemImpl.java | 4 +
.../java/org/apache/calcite/rex/RexBuilder.java | 31 ++-
.../apache/calcite/runtime/CalciteResource.java | 3 +
.../org/apache/calcite/runtime/SqlFunctions.java | 188 +++++++++++++
.../calcite/runtime/rtti/BasicSqlTypeRtti.java | 8 +
.../runtime/rtti/RuntimeTypeInformation.java | 31 +++
.../calcite/runtime/variant/VariantNonNull.java | 178 ++++++++++++
.../apache/calcite/sql/SqlBasicTypeNameSpec.java | 21 ++
.../apache/calcite/sql/fun/SqlCastFunction.java | 4 +-
.../org/apache/calcite/sql/type/BasicSqlType.java | 12 +-
.../calcite/sql/type/SqlTypeAssignmentRule.java | 58 ++++
.../calcite/sql/type/SqlTypeCoercionRule.java | 20 ++
.../org/apache/calcite/sql/type/SqlTypeFamily.java | 3 +
.../org/apache/calcite/sql/type/SqlTypeName.java | 66 ++++-
.../org/apache/calcite/sql/type/SqlTypeUtil.java | 68 ++++-
.../sql/validate/SqlAbstractConformance.java | 4 +
.../calcite/sql/validate/SqlConformance.java | 5 +
.../calcite/sql/validate/SqlConformanceEnum.java | 4 +
.../sql/validate/SqlDelegatingConformance.java | 4 +
.../apache/calcite/sql2rel/SqlToRelConverter.java | 5 +
.../org/apache/calcite/util/BuiltInMethod.java | 9 +
.../calcite/runtime/CalciteResource.properties | 1 +
.../calcite/jdbc/CalciteRemoteDriverTest.java | 2 +-
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 7 +
.../org/apache/calcite/test/SqlValidatorTest.java | 20 +-
.../java/org/apache/calcite/tools/PlannerTest.java | 2 +-
core/src/test/resources/sql/unsigned.iq | 157 +++++++++++
gradle.properties | 1 +
linq4j/build.gradle.kts | 1 +
.../calcite/linq4j/tree/OptimizeShuttle.java | 11 +-
.../org/apache/calcite/linq4j/tree/Primitive.java | 15 +-
.../apache/calcite/linq4j/tree/UnsignedType.java | 309 +++++++++++++++++++++
.../apache/calcite/linq4j/test/OptimizerTest.java | 4 +-
site/_docs/reference.md | 66 +++--
.../apache/calcite/sql/parser/SqlParserTest.java | 21 ++
.../org/apache/calcite/test/SqlOperatorTest.java | 12 +-
43 files changed, 1411 insertions(+), 83 deletions(-)
diff --git a/bom/build.gradle.kts b/bom/build.gradle.kts
index 770f8f5c9a..d25ba045f3 100644
--- a/bom/build.gradle.kts
+++ b/bom/build.gradle.kts
@@ -150,6 +150,7 @@ fun DependencyConstraintHandlerScope.runtimev(
apiv("org.hsqldb:hsqldb")
apiv("org.incava:java-diff")
apiv("org.jboss:jandex")
+ apiv("org.jooq:joou-java-6", "joou")
apiv("org.jsoup:jsoup")
apiv("org.junit:junit-bom", "junit5")
apiv("org.mockito:mockito-core", "mockito")
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
index ede7dfee7b..06c1aaa450 100644
--- a/core/build.gradle.kts
+++ b/core/build.gradle.kts
@@ -72,6 +72,7 @@
implementation("org.apache.commons:commons-lang3")
implementation("org.apache.commons:commons-math3")
implementation("org.apache.commons:commons-text")
+ implementation("org.jooq:joou-java-6")
implementation("commons-io:commons-io")
implementation("org.codehaus.janino:commons-compiler")
implementation("org.codehaus.janino:janino")
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index ae367d549a..a7ae833e67 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -5890,6 +5890,7 @@ SqlTypeNameSpec SqlTypeName(Span s) :
SqlTypeNameSpec SqlTypeName1(Span s) :
{
final SqlTypeName sqlTypeName;
+ boolean unsigned = false;
}
{
(
@@ -5903,13 +5904,40 @@ SqlTypeNameSpec SqlTypeName1(Span s) :
|
<BOOLEAN> { s.add(this); sqlTypeName = SqlTypeName.BOOLEAN; }
|
- ( <INTEGER> | <INT> ) { s.add(this); sqlTypeName =
SqlTypeName.INTEGER; }
+ ( <INTEGER> | <INT> ) ( <UNSIGNED> { unsigned = true; })? {
+ if (unsigned && !this.conformance.supportsUnsignedTypes()) {
+ throw SqlUtil.newContextException(getPos(),
RESOURCE.unsignedDisabled());
+ }
+ s.add(this); sqlTypeName = unsigned ? SqlTypeName.UINTEGER :
SqlTypeName.INTEGER;
+ }
|
- <TINYINT> { s.add(this); sqlTypeName = SqlTypeName.TINYINT; }
+ <UNSIGNED> {
+ if (!this.conformance.supportsUnsignedTypes()) {
+ throw SqlUtil.newContextException(getPos(),
RESOURCE.unsignedDisabled());
+ }
+ s.add(this); sqlTypeName = SqlTypeName.UINTEGER;
+ }
|
- <SMALLINT> { s.add(this); sqlTypeName = SqlTypeName.SMALLINT; }
+ <TINYINT> ( <UNSIGNED> { unsigned = true; })? {
+ if (unsigned && !this.conformance.supportsUnsignedTypes()) {
+ throw SqlUtil.newContextException(getPos(),
RESOURCE.unsignedDisabled());
+ }
+ s.add(this); sqlTypeName = unsigned ? SqlTypeName.UTINYINT :
SqlTypeName.TINYINT;
+ }
|
- <BIGINT> { s.add(this); sqlTypeName = SqlTypeName.BIGINT; }
+ <SMALLINT> ( <UNSIGNED> { unsigned = true; })? {
+ if (unsigned && !this.conformance.supportsUnsignedTypes()) {
+ throw SqlUtil.newContextException(getPos(),
RESOURCE.unsignedDisabled());
+ }
+ s.add(this); sqlTypeName = unsigned ? SqlTypeName.USMALLINT :
SqlTypeName.SMALLINT;
+ }
+ |
+ <BIGINT> ( <UNSIGNED> { unsigned = true; })? {
+ if (unsigned && !this.conformance.supportsUnsignedTypes()) {
+ throw SqlUtil.newContextException(getPos(),
RESOURCE.unsignedDisabled());
+ }
+ s.add(this); sqlTypeName = unsigned ? SqlTypeName.UBIGINT :
SqlTypeName.BIGINT;
+ }
|
<REAL> { s.add(this); sqlTypeName = SqlTypeName.REAL; }
|
@@ -8718,6 +8746,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < UNPIVOT: "UNPIVOT" >
| < UNNAMED: "UNNAMED" >
| < UNNEST: "UNNEST" >
+| < UNSIGNED: "UNSIGNED" >
| < UPDATE: "UPDATE" > { beforeTableName(); }
| < UPPER: "UPPER" >
| < UPSERT: "UPSERT" >
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
index 671b2d6bb4..93db65e228 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumUtils.java
@@ -44,6 +44,7 @@
import org.apache.calcite.linq4j.tree.Statement;
import org.apache.calcite.linq4j.tree.Types;
import org.apache.calcite.linq4j.tree.UnaryExpression;
+import org.apache.calcite.linq4j.tree.UnsignedType;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.type.RelDataType;
@@ -443,6 +444,7 @@ public static Expression convert(Expression operand, Type
fromType,
final Primitive toBox = Primitive.ofBox(toType);
final Primitive fromBox = Primitive.ofBox(fromType);
final Primitive fromPrimitive = Primitive.of(fromType);
+ final UnsignedType unsignedType = UnsignedType.of(toType);
final boolean fromNumber = fromType instanceof Class
&& Number.class.isAssignableFrom((Class) fromType);
if (fromType == String.class) {
@@ -598,6 +600,26 @@ public static Expression convert(Expression operand, Type
fromType,
SqlFunctions.class,
"toBigDecimal",
operand));
+ } else if (unsignedType != null) {
+ // E.e. toULong
+ String functionName = unsignedType.getConvertFunctionName();
+ if (fromPrimitive != null) {
+ // E.g. from "int" to "ULong".
+ // Generate "UnsignedType.toULong(x)"
+ return Expressions.call(
+ UnsignedType.class,
+ functionName,
+ operand);
+ } else {
+ // Generate "x == null ? null : UnsignedType.toULong(x)"
+ return Expressions.condition(
+ Expressions.equal(operand, RexImpTable.NULL_EXPR),
+ RexImpTable.NULL_EXPR,
+ Expressions.call(
+ UnsignedType.class,
+ functionName,
+ operand));
+ }
} else if (toType == String.class) {
if (fromPrimitive != null) {
switch (fromPrimitive) {
@@ -752,8 +774,8 @@ private static Expression convertAssignableType(
* @throws RuntimeException if no suitable method found
*/
public static MethodCallExpression call(@Nullable Expression
targetExpression,
- Class clazz, String methodName, List<? extends Expression> arguments) {
- Class[] argumentTypes = Types.toClassArray(arguments);
+ Class<?> clazz, String methodName, List<? extends Expression> arguments)
{
+ Class<?>[] argumentTypes = Types.toClassArray(arguments);
try {
Method candidate = clazz.getMethod(methodName, argumentTypes);
return Expressions.call(targetExpression, candidate, arguments);
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index d6940f7cc3..d4c2e2ea89 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -36,6 +36,7 @@
import org.apache.calcite.linq4j.tree.OptimizeShuttle;
import org.apache.calcite.linq4j.tree.ParameterExpression;
import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.UnsignedType;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeFactoryImpl;
@@ -87,6 +88,10 @@
import com.google.common.collect.Iterables;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -1725,9 +1730,31 @@ static class CountWinImplementor extends
StrictWinAggImplementor {
static class SumImplementor extends StrictAggImplementor {
@Override protected void implementNotNullReset(AggContext info,
AggResetContext reset) {
- Expression start = info.returnType() == BigDecimal.class
- ? Expressions.constant(BigDecimal.ZERO)
- : Expressions.constant(0);
+ Expression zero = Expressions.constant(0);
+ Expression start;
+ if (info.returnType() == BigDecimal.class) {
+ start = Expressions.constant(BigDecimal.ZERO);
+ } else if (UnsignedType.of(info.returnType()) != null) {
+ UnsignedType kind = UnsignedType.of(info.returnType());
+ switch (requireNonNull(kind, "kind")) {
+ case UBYTE:
+ start = Expressions.call(UByte.class, "valueOf", zero);
+ break;
+ case USHORT:
+ start = Expressions.call(UShort.class, "valueOf", zero);
+ break;
+ case UINT:
+ start = Expressions.call(UInteger.class, "valueOf", zero);
+ break;
+ case ULONG:
+ start = Expressions.call(ULong.class, "valueOf", zero);
+ break;
+ default:
+ throw new IllegalArgumentException("Unexpected type " +
info.returnType());
+ }
+ } else {
+ start = zero;
+ }
reset.currentBlock().add(
Expressions.statement(
@@ -1738,7 +1765,8 @@ static class SumImplementor extends StrictAggImplementor {
AggAddContext add) {
Expression acc = add.accumulator().get(0);
Expression next;
- if (info.returnType() == BigDecimal.class) {
+ if (info.returnType() == BigDecimal.class
+ || UnsignedType.of(info.returnType()) != null) {
next = Expressions.call(acc, "add", add.arguments().get(0));
} else {
final Expression arg = EnumUtils.convert(add.arguments().get(0),
acc.type);
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
index db532428bd..a251d8c618 100644
---
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
+++
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexToLixTranslator.java
@@ -586,6 +586,32 @@ private Expression getConvertExpression(
}
return defaultExpression.get();
}
+ case UBIGINT:
+ case UINTEGER:
+ case UTINYINT:
+ case USMALLINT: {
+ Method method;
+ switch (targetType.getSqlTypeName()) {
+ case UBIGINT:
+ method = BuiltInMethod.CAST_TO_ULONG.method;
+ break;
+ case UINTEGER:
+ method = BuiltInMethod.CAST_TO_UINTEGER.method;
+ break;
+ case USMALLINT:
+ method = BuiltInMethod.CAST_TO_USHORT.method;
+ break;
+ case UTINYINT:
+ method = BuiltInMethod.CAST_TO_UBYTE.method;
+ break;
+ default:
+ throw new RuntimeException("Unexpected unsigned type " +
targetType.getSqlTypeName());
+ }
+ return Expressions.call(
+ method,
+ operand,
+ Expressions.constant(typeFactory.getTypeSystem().roundingMode()));
+ }
case BIGINT:
case INTEGER:
case TINYINT:
diff --git
a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
index 6092bcafae..3f276b41e6 100644
--- a/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/jdbc/JavaTypeFactoryImpl.java
@@ -38,6 +38,10 @@
import org.apache.calcite.util.Util;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
import org.locationtech.jts.geom.Geometry;
import java.lang.reflect.Field;
@@ -228,6 +232,14 @@ private static Type fieldType(Field field) {
return Object.class;
case NULL:
return Void.class;
+ case UTINYINT:
+ return UByte.class;
+ case USMALLINT:
+ return UShort.class;
+ case UINTEGER:
+ return UInteger.class;
+ case UBIGINT:
+ return ULong.class;
default:
break;
}
diff --git
a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
index a4dc8e1a30..b5eceedcce 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystemImpl.java
@@ -126,12 +126,16 @@ public abstract class RelDataTypeSystemImpl implements
RelDataTypeSystem {
case BOOLEAN:
return 1;
case TINYINT:
+ case UTINYINT:
return 3;
case SMALLINT:
+ case USMALLINT:
return 5;
case INTEGER:
+ case UINTEGER:
return 10;
case BIGINT:
+ case UBIGINT:
return 19;
case REAL:
return 7;
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index 6f258ed267..fd884af9e5 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -874,7 +874,8 @@ boolean canRemoveCastFromLiteral(RelDataType toType,
final SqlTypeName sqlType = toType.getSqlTypeName();
if (sqlType == SqlTypeName.MEASURE
|| sqlType == SqlTypeName.VARIANT
- || sqlType == SqlTypeName.UUID) {
+ || sqlType == SqlTypeName.UUID
+ || SqlTypeName.UNSIGNED_TYPES.contains(sqlType)) {
return false;
}
if (!RexLiteral.valueMatchesType(value, sqlType, false)) {
@@ -941,14 +942,30 @@ boolean canRemoveCastFromLiteral(RelDataType toType,
private RexNode makeCastExactToBoolean(RelDataType toType, RexNode exp) {
return makeCall(toType,
SqlStdOperatorTable.NOT_EQUALS,
- ImmutableList.of(exp, makeZeroLiteral(exp.getType())));
+ ImmutableList.of(exp, makeZeroValue(exp.getType())));
+ }
+
+ /** Some data types do not have literals; this creates an expression that
+ * evaluates to a zero of the specified type.
+ *
+ * @param type A numeric type.
+ * @return An expression that evaluates to 0 of the specified type.
+ */
+ public RexNode makeZeroValue(RelDataType type) {
+ if (SqlTypeUtil.hasLiterals(type)) {
+ return makeZeroLiteral(type);
+ } else {
+ // This is e.g., an unsigned type
+ RelDataType i = typeFactory.createSqlType(SqlTypeName.INTEGER);
+ return makeAbstractCast(type, makeZeroValue(i), false);
+ }
}
private RexNode makeCastBooleanToExact(RelDataType toType, RexNode exp) {
final RexNode casted =
makeCall(SqlStdOperatorTable.CASE, exp,
makeExactLiteral(BigDecimal.ONE, toType),
- makeZeroLiteral(toType));
+ makeZeroValue(toType));
if (!exp.getType().isNullable()) {
return casted;
}
@@ -1942,11 +1959,11 @@ public RexNode makeZeroRexNode(RelDataType type) {
.collect(Collectors.toList());
return makeCall(type, SqlStdOperatorTable.ROW, zeroFields);
default:
- return makeZeroLiteral(type);
+ return makeZeroValue(type);
}
}
- private static Comparable zeroValue(RelDataType type) {
+ private static Comparable<?> zeroValue(RelDataType type) {
switch (type.getSqlTypeName()) {
case CHAR:
return new NlsString(Spaces.of(type.getPrecision()), null, null);
@@ -1960,6 +1977,10 @@ private static Comparable zeroValue(RelDataType type) {
case SMALLINT:
case INTEGER:
case BIGINT:
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
case DECIMAL:
case FLOAT:
case REAL:
diff --git a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
index 45eba8f61d..dea1d15052 100644
--- a/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
+++ b/core/src/main/java/org/apache/calcite/runtime/CalciteResource.java
@@ -66,6 +66,9 @@ public interface CalciteResource {
@BaseMessage("Geo-spatial extensions and the GEOMETRY data type are not
enabled")
ExInst<SqlValidatorException> geometryDisabled();
+ @BaseMessage("Support for UNSIGNED data types is not enabled")
+ ExInst<SqlValidatorException> unsignedDisabled();
+
@BaseMessage("Proj4J EPSG is missing from the classpath; to resolve this
problem, download the EPSG data set and agree to its terms of use")
ExInst<CalciteException> proj4jEpsgIsMissing();
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 04baae8128..7140b091d8 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -35,6 +35,7 @@
import org.apache.calcite.linq4j.function.NonDeterministic;
import org.apache.calcite.linq4j.function.Predicate1;
import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.UnsignedType;
import org.apache.calcite.rel.type.TimeFrame;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.runtime.FlatLists.ComparableList;
@@ -73,6 +74,11 @@
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.PolyNull;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
+import org.joou.Unsigned;
import java.lang.reflect.Field;
import java.math.BigDecimal;
@@ -2497,6 +2503,30 @@ public static int plus(int b0, int b1) {
throw notArithmetic("+", b0, b1);
}
+ public static @PolyNull UByte plus(@PolyNull UByte b0, @PolyNull UByte b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.add(b1);
+ }
+
+ public static @PolyNull UShort plus(@PolyNull UShort b0, @PolyNull UShort
b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.add(b1);
+ }
+
+ public static @PolyNull UInteger plus(@PolyNull UInteger b0, @PolyNull
UInteger b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.add(b1);
+ }
+
+ public static @PolyNull ULong plus(@PolyNull ULong b0, @PolyNull ULong b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.add(b1);
+ }
+
// checked +
static byte intToByte(int value) {
@@ -2531,6 +2561,22 @@ public static long checkedPlus(long b0, long b1) {
return Math.addExact(b0, b1);
}
+ public static UByte checkedPlus(UByte b0, UByte b1) {
+ return b0.add(b1);
+ }
+
+ public static UShort checkedPlus(UShort b0, UShort b1) {
+ return b0.add(b1);
+ }
+
+ public static UInteger checkedPlus(UInteger b0, UInteger b1) {
+ return b0.add(b1);
+ }
+
+ public static ULong checkedPlus(ULong b0, ULong b1) {
+ return b0.add(b1);
+ }
+
// -
/** SQL <code>-</code> operator applied to int values. */
@@ -2569,6 +2615,13 @@ public static int minus(int b0, int b1) {
: (b0.longValue() - b1.longValue());
}
+ /** SQL <code>-</code> operator applied to nullable long and long values. */
+ public static @PolyNull Long minus(@PolyNull Long b0, @PolyNull Long b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.longValue() - b1.longValue();
+ }
+
/** SQL <code>-</code> operator applied to nullable BigDecimal values. */
public static @PolyNull BigDecimal minus(@PolyNull BigDecimal b0,
@PolyNull BigDecimal b1) {
@@ -2589,6 +2642,25 @@ public static int minus(int b0, int b1) {
throw notArithmetic("-", b0, b1);
}
+ public static @PolyNull UByte minus(@PolyNull UByte b0, @PolyNull UByte b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null) : b0.subtract(b1);
+ }
+
+ public static @PolyNull UShort minus(@PolyNull UShort b0, @PolyNull UShort
b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null) : b0.subtract(b1);
+ }
+
+ public static @PolyNull UInteger minus(@PolyNull UInteger b0, @PolyNull
UInteger b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null) : b0.subtract(b1);
+ }
+
+ /** SQL <code>-</code> operator applied to nullable unsigned long and long
values. */
+ public static @PolyNull ULong minus(@PolyNull ULong b0, @PolyNull ULong b1) {
+ return (b0 == null || b1 == null)
+ ? castNonNull(null)
+ : b0.subtract(b1);
+ }
+
// checked -
public static byte checkedMinus(byte b0, byte b1) {
@@ -2623,6 +2695,38 @@ public static long checkedUnaryMinus(long b) {
return Math.subtractExact(0, b);
}
+ public static UByte checkedMinus(UByte b0, UByte b1) {
+ return b0.subtract(b1);
+ }
+
+ public static UShort checkedMinus(UShort b0, UShort b1) {
+ return b0.subtract(b1);
+ }
+
+ public static UInteger checkedMinus(UInteger b0, UInteger b1) {
+ return b0.subtract(b1);
+ }
+
+ public static ULong checkedMinus(ULong b0, ULong b1) {
+ return b0.subtract(b1);
+ }
+
+ public static UByte checkedUnaryMinus(UByte b) {
+ return Unsigned.ubyte(0).subtract(b);
+ }
+
+ public static UShort checkedUnaryMinus(UShort b) {
+ return Unsigned.ushort(0).subtract(b);
+ }
+
+ public static UInteger checkedUnaryMinus(UInteger b) {
+ return Unsigned.uint(0).subtract(b);
+ }
+
+ public static ULong checkedUnaryMinus(ULong b) {
+ return Unsigned.ulong(0).subtract(b);
+ }
+
// /
/** SQL <code>/</code> operator applied to int values. */
@@ -2695,6 +2799,30 @@ public static long divide(long b0, BigDecimal b1) {
.divide(b1, RoundingMode.HALF_DOWN).longValue();
}
+ public static @PolyNull UByte divide(@PolyNull UByte b0,
+ @PolyNull UByte b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UByte.valueOf(b0.intValue() / b1.intValue());
+ }
+
+ public static @PolyNull UShort divide(@PolyNull UShort b0,
+ @PolyNull UShort b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UShort.valueOf(b0.intValue() / b1.intValue());
+ }
+
+ public static @PolyNull UInteger divide(@PolyNull UInteger b0,
+ @PolyNull UInteger b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UInteger.valueOf(b0.longValue() / b1.longValue());
+ }
+
+ public static @PolyNull ULong divide(@PolyNull ULong b0,
+ @PolyNull ULong b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ :
ULong.valueOf(UnsignedType.toBigInteger(b0).divide(UnsignedType.toBigInteger(b1)));
+ }
+
public static byte checkedDivide(byte b0, byte b1) {
return intToByte(b0 / b1);
}
@@ -2723,6 +2851,22 @@ public static long checkedDivide(long b0, long b1) {
}
}
+ public static UByte checkedDivide(UByte b0, UByte b1) {
+ return UByte.valueOf(b0.intValue() / b1.intValue());
+ }
+
+ public static UShort checkedDivide(UShort b0, UShort b1) {
+ return UShort.valueOf(b0.intValue() / b1.intValue());
+ }
+
+ public static UInteger checkedDivide(UInteger b0, UInteger b1) {
+ return UInteger.valueOf(b0.longValue() / b1.longValue());
+ }
+
+ public static ULong checkedDivide(ULong b0, ULong b1) {
+ return
ULong.valueOf(UnsignedType.toBigInteger(b0).divide(UnsignedType.toBigInteger(b1)));
+ }
+
// *
/** SQL <code>*</code> operator applied to int values. */
@@ -2748,6 +2892,33 @@ public static int multiply(int b0, int b1) {
return (b0 == null || b1 == null) ? castNonNull(null) : (b0 * b1);
}
+ public static @PolyNull UByte multiply(@PolyNull UByte b0,
+ @PolyNull UByte b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UByte.valueOf(b0.longValue() * b1.longValue());
+ }
+
+ public static @PolyNull UShort multiply(@PolyNull UShort b0,
+ @PolyNull UShort b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UShort.valueOf(b0.intValue() * b1.intValue());
+ }
+
+ public static @PolyNull UInteger multiply(@PolyNull UInteger b0,
+ @PolyNull UInteger b1) {
+ return (b0 == null || b1 == null) ? castNonNull(null)
+ : UInteger.valueOf(b0.longValue() * b1.longValue());
+ }
+
+ public static @PolyNull ULong multiply(@PolyNull ULong b0,
+ @PolyNull ULong b1) {
+ if (b0 == null || b1 == null) {
+ return castNonNull(null);
+ }
+ BigInteger result =
UnsignedType.toBigInteger(b0).multiply(UnsignedType.toBigInteger(b1));
+ return ULong.valueOf(result);
+ }
+
/** SQL <code>*</code> operator applied to nullable long and int values. */
public static @PolyNull Long multiply(@PolyNull Long b0, @PolyNull Integer
b1) {
return (b0 == null || b1 == null)
@@ -2801,6 +2972,23 @@ public static long checkedMultiply(long b0, long b1) {
return Math.multiplyExact(b0, b1);
}
+ public static UByte checkedMultiply(UByte b0, UByte b1) {
+ return UByte.valueOf(b0.intValue() * b1.intValue());
+ }
+
+ public static UShort checkedMultiply(UShort b0, UShort b1) {
+ return UShort.valueOf(b0.intValue() * b1.intValue());
+ }
+
+ public static UInteger checkedMultiply(UInteger b0, UInteger b1) {
+ return
+ UInteger.valueOf(b0.longValue() * b1.longValue());
+ }
+
+ public static ULong checkedMultiply(ULong b0, ULong b1) {
+ return
ULong.valueOf(UnsignedType.toBigInteger(b0).multiply(UnsignedType.toBigInteger(b1)));
+ }
+
/** SQL <code>SAFE_ADD</code> function applied to long values. */
public static @Nullable Long safeAdd(long b0, long b1) {
try {
diff --git
a/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
b/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
index cb2281ac68..00aa444a7b 100644
--- a/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
+++ b/core/src/main/java/org/apache/calcite/runtime/rtti/BasicSqlTypeRtti.java
@@ -55,6 +55,14 @@ public BasicSqlTypeRtti(RuntimeSqlTypeName typeName) {
return "INTEGER";
case BIGINT:
return "BIGINT";
+ case UTINYINT:
+ return "TINYINT UNSIGNED";
+ case USMALLINT:
+ return "SMALLINT UNSIGNED";
+ case UINTEGER:
+ return "INTEGER UNSIGNED";
+ case UBIGINT:
+ return "BIGINT UNSIGNED";
case DECIMAL:
return "DECIMAL";
case REAL:
diff --git
a/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
b/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
index 61410dc520..53e7c69301 100644
---
a/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
+++
b/core/src/main/java/org/apache/calcite/runtime/rtti/RuntimeTypeInformation.java
@@ -43,6 +43,10 @@ public enum RuntimeSqlTypeName {
SMALLINT(false),
INTEGER(false),
BIGINT(false),
+ UTINYINT(false),
+ USMALLINT(false),
+ UINTEGER(false),
+ UBIGINT(false),
DECIMAL(false),
REAL(false),
// FLOAT is represented as DOUBLE
@@ -81,6 +85,21 @@ public enum RuntimeSqlTypeName {
public boolean isScalar() {
return !this.composite;
}
+
+ @Override public String toString() {
+ switch (this) {
+ case UINTEGER:
+ return "INTEGER UNSIGNED";
+ case UBIGINT:
+ return "BIGINT UNSIGNED";
+ case UTINYINT:
+ return "TINYINT UNSIGNED";
+ case USMALLINT:
+ return "SMALLINT UNSIGNED";
+ default:
+ return this.name();
+ }
+ }
}
final RuntimeSqlTypeName typeName;
@@ -149,6 +168,18 @@ public static Expression createExpression(RelDataType
type) {
case BIGINT:
return Expressions.new_(BasicSqlTypeRtti.class,
Expressions.constant(RuntimeSqlTypeName.BIGINT));
+ case UTINYINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.UTINYINT));
+ case USMALLINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.USMALLINT));
+ case UINTEGER:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.UINTEGER));
+ case UBIGINT:
+ return Expressions.new_(BasicSqlTypeRtti.class,
+ Expressions.constant(RuntimeSqlTypeName.UBIGINT));
case DECIMAL:
return Expressions.new_(BasicSqlTypeRtti.class,
Expressions.constant(RuntimeSqlTypeName.DECIMAL));
diff --git
a/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
index 7a4d5a9d36..fa296ac702 100644
--- a/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
+++ b/core/src/main/java/org/apache/calcite/runtime/variant/VariantNonNull.java
@@ -17,14 +17,20 @@
package org.apache.calcite.runtime.variant;
import org.apache.calcite.linq4j.tree.Primitive;
+import org.apache.calcite.linq4j.tree.UnsignedType;
import org.apache.calcite.runtime.SqlFunctions;
import org.apache.calcite.runtime.rtti.BasicSqlTypeRtti;
import org.apache.calcite.runtime.rtti.RowSqlTypeRtti;
import org.apache.calcite.runtime.rtti.RuntimeTypeInformation;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -64,18 +70,34 @@ public class VariantNonNull extends VariantSqlValue {
assert value instanceof Byte;
this.value = value;
break;
+ case UTINYINT:
+ assert value instanceof UByte;
+ this.value = value;
+ break;
case SMALLINT:
assert value instanceof Short;
this.value = value;
break;
+ case USMALLINT:
+ assert value instanceof UShort;
+ this.value = value;
+ break;
case INTEGER:
assert value instanceof Integer;
this.value = value;
break;
+ case UINTEGER:
+ assert value instanceof UInteger;
+ this.value = value;
+ break;
case BIGINT:
assert value instanceof Long;
this.value = value;
break;
+ case UBIGINT:
+ assert value instanceof ULong;
+ this.value = value;
+ break;
case DECIMAL:
assert value instanceof BigDecimal;
this.value = value;
@@ -200,6 +222,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(b,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(b);
+ case UTINYINT:
+ return UnsignedType.toUByte(b);
+ case USMALLINT:
+ return UnsignedType.toUShort(b);
+ case UINTEGER:
+ return UnsignedType.toUInteger(b);
+ case UBIGINT:
+ return UnsignedType.toULong(b);
default:
break;
}
@@ -217,6 +247,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(s,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(s);
+ case UTINYINT:
+ return UnsignedType.toUByte(s);
+ case USMALLINT:
+ return UnsignedType.toUShort(s);
+ case UINTEGER:
+ return UnsignedType.toUInteger(s);
+ case UBIGINT:
+ return UnsignedType.toULong(s);
default:
break;
}
@@ -234,6 +272,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(i,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(i);
+ case UTINYINT:
+ return UnsignedType.toUByte(i);
+ case USMALLINT:
+ return UnsignedType.toUShort(i);
+ case UINTEGER:
+ return UnsignedType.toUInteger(i);
+ case UBIGINT:
+ return UnsignedType.toULong(i);
default:
break;
}
@@ -251,6 +297,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(l,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(l);
+ case UTINYINT:
+ return UnsignedType.toUByte(l);
+ case USMALLINT:
+ return UnsignedType.toUShort(l);
+ case UINTEGER:
+ return UnsignedType.toUInteger(l);
+ case UBIGINT:
+ return UnsignedType.toULong(l);
default:
break;
}
@@ -268,6 +322,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(d,
roundingMode);
case DECIMAL:
return d;
+ case UTINYINT:
+ return UnsignedType.toUByte(d.longValueExact());
+ case USMALLINT:
+ return UnsignedType.toUShort(d.longValueExact());
+ case UINTEGER:
+ return UnsignedType.toUInteger(d.longValueExact());
+ case UBIGINT:
+ return UnsignedType.toULong(d.longValueExact());
default:
break;
}
@@ -285,6 +347,14 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(f,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(f);
+ case UTINYINT:
+ return UnsignedType.toUByte(f);
+ case USMALLINT:
+ return UnsignedType.toUShort(f);
+ case UINTEGER:
+ return UnsignedType.toUInteger(f);
+ case UBIGINT:
+ return UnsignedType.toULong(f);
default:
break;
}
@@ -302,6 +372,114 @@ public class VariantNonNull extends VariantSqlValue {
return requireNonNull(target, "target").numberValue(d,
roundingMode);
case DECIMAL:
return BigDecimal.valueOf(d);
+ case UTINYINT:
+ return UnsignedType.toUByte(d);
+ case USMALLINT:
+ return UnsignedType.toUShort(d);
+ case UINTEGER:
+ return UnsignedType.toUInteger(d);
+ case UBIGINT:
+ return UnsignedType.toULong(d);
+ default:
+ break;
+ }
+ break;
+ }
+ case UTINYINT: {
+ UByte b = (UByte) 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.intValue());
+ case UTINYINT:
+ return UnsignedType.toUByte(b.intValue());
+ case USMALLINT:
+ return UnsignedType.toUShort(b.intValue());
+ case UINTEGER:
+ return UnsignedType.toUInteger(b.intValue());
+ case UBIGINT:
+ return UnsignedType.toULong(b.intValue());
+ default:
+ break;
+ }
+ break;
+ }
+ case USMALLINT: {
+ UShort s = (UShort) 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.intValue());
+ case UTINYINT:
+ return UnsignedType.toUByte(s.intValue());
+ case USMALLINT:
+ return UnsignedType.toUShort(s.intValue());
+ case UINTEGER:
+ return UnsignedType.toUInteger(s.intValue());
+ case UBIGINT:
+ return UnsignedType.toULong(s.intValue());
+ default:
+ break;
+ }
+ break;
+ }
+ case UINTEGER: {
+ UInteger b = (UInteger) 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.longValue());
+ case UTINYINT:
+ return UnsignedType.toUByte(b.longValue());
+ case USMALLINT:
+ return UnsignedType.toUShort(b.longValue());
+ case UINTEGER:
+ return UnsignedType.toUInteger(b.longValue());
+ case UBIGINT:
+ return UnsignedType.toULong(b.longValue());
+ default:
+ break;
+ }
+ break;
+ }
+ case UBIGINT: {
+ BigInteger i = UnsignedType.toBigInteger((ULong) 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 new BigDecimal(i);
+ case UTINYINT:
+ return UnsignedType.toUByte(i.longValueExact());
+ case USMALLINT:
+ return UnsignedType.toUShort(i.longValueExact());
+ case UINTEGER:
+ return UnsignedType.toUInteger(i.longValueExact());
+ case UBIGINT:
+ return value;
default:
break;
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/SqlBasicTypeNameSpec.java
b/core/src/main/java/org/apache/calcite/sql/SqlBasicTypeNameSpec.java
index fb1513bcf4..7213352e6d 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlBasicTypeNameSpec.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlBasicTypeNameSpec.java
@@ -160,6 +160,27 @@ public int getPrecision() {
// instead of direct unparsing with enum name.
// i.e. TIME_WITH_LOCAL_TIME_ZONE(3)
// would be unparsed as "time(3) with local time zone".
+ if (SqlTypeName.UNSIGNED_TYPES.contains(sqlTypeName)) {
+ switch (sqlTypeName) {
+ case UBIGINT:
+ writer.keyword("BIGINT");
+ break;
+ case UINTEGER:
+ writer.keyword("INTEGER");
+ break;
+ case USMALLINT:
+ writer.keyword("SMALLINT");
+ break;
+ case UTINYINT:
+ writer.keyword("TINYINT");
+ break;
+ default:
+ throw new UnsupportedOperationException("Unexpected unsigned type");
+ }
+ writer.keyword("UNSIGNED");
+ return;
+ }
+
final boolean isWithTimeZone = isWithTimeZoneDef(sqlTypeName);
if (isWithTimeZone) {
writer.keyword(stripTimeZoneDef(sqlTypeName).name());
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
index 93bcf284a9..b25a9b8fdd 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCastFunction.java
@@ -241,8 +241,8 @@ private static RelDataType
createTypeWithNullabilityFromExpr(RelDataTypeFactory
if (!SqlTypeUtil.canCastFrom(returnType, validatedNodeType, mappingRule)) {
if (throwOnFailure) {
throw callBinding.newError(
- RESOURCE.cannotCastValue(validatedNodeType.toString(),
- returnType.toString()));
+ RESOURCE.cannotCastValue(validatedNodeType.getFullTypeString(),
+ returnType.getFullTypeString()));
}
return false;
}
diff --git a/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java
b/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java
index e0642659f3..e8e9332d61 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/BasicSqlType.java
@@ -161,6 +161,10 @@ BasicSqlType createWithCharsetAndCollation(Charset charset,
case SMALLINT:
case INTEGER:
case BIGINT:
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
case DECIMAL:
return 0;
default:
@@ -183,7 +187,13 @@ BasicSqlType createWithCharsetAndCollation(Charset charset,
// Called to make the digest, which equals() compares;
// so equivalent data types must produce identical type strings.
- sb.append(typeName.name());
+ if (SqlTypeName.UNSIGNED_TYPES.contains(typeName)) {
+ // All these type names start with U
+ String name = typeName.name().substring(1);
+ sb.append(name).append(" UNSIGNED");
+ } else {
+ sb.append(typeName.name());
+ }
boolean printPrecision = precision != PRECISION_NOT_SPECIFIED;
boolean printScale = scale != SCALE_NOT_SPECIFIED;
diff --git
a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRule.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRule.java
index eb292c064a..f90db45d6c 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRule.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeAssignmentRule.java
@@ -70,33 +70,73 @@ private SqlTypeAssignmentRule(
// TINYINT is assignable from...
rules.add(SqlTypeName.TINYINT, EnumSet.of(SqlTypeName.TINYINT));
+ // UTINYINT is assignable from...
+ rules.add(SqlTypeName.UTINYINT, EnumSet.of(SqlTypeName.UTINYINT));
+
// SMALLINT is assignable from...
rule.clear();
rule.add(SqlTypeName.TINYINT);
+ rule.add(SqlTypeName.UTINYINT);
rule.add(SqlTypeName.SMALLINT);
rules.add(SqlTypeName.SMALLINT, rule);
+ // USMALLINT is assignable from...
+ rule.clear();
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.TINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rules.add(SqlTypeName.USMALLINT, rule);
+
// INTEGER is assignable from...
rule.clear();
rule.add(SqlTypeName.TINYINT);
rule.add(SqlTypeName.SMALLINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
rule.add(SqlTypeName.INTEGER);
rules.add(SqlTypeName.INTEGER, rule);
+ // UINTEGER is assignable from...
+ rule.clear();
+ rule.add(SqlTypeName.TINYINT);
+ rule.add(SqlTypeName.SMALLINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rules.add(SqlTypeName.UINTEGER, rule);
+
// BIGINT is assignable from...
rule.clear();
rule.add(SqlTypeName.TINYINT);
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
rule.add(SqlTypeName.BIGINT);
rules.add(SqlTypeName.BIGINT, rule);
+ // UBIGINT is assignable from...
+ rule.clear();
+ rule.add(SqlTypeName.TINYINT);
+ rule.add(SqlTypeName.SMALLINT);
+ rule.add(SqlTypeName.INTEGER);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
+ rules.add(SqlTypeName.UBIGINT, rule);
+
// FLOAT (up to 64 bit floating point) is assignable from...
rule.clear();
rule.add(SqlTypeName.TINYINT);
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.DOUBLE);
@@ -108,6 +148,10 @@ private SqlTypeAssignmentRule(
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.DOUBLE);
@@ -120,6 +164,10 @@ private SqlTypeAssignmentRule(
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.REAL);
@@ -132,6 +180,10 @@ private SqlTypeAssignmentRule(
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.REAL);
rule.add(SqlTypeName.DOUBLE);
rule.add(SqlTypeName.DECIMAL);
@@ -213,6 +265,10 @@ private SqlTypeAssignmentRule(
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.DOUBLE);
@@ -220,6 +276,8 @@ private SqlTypeAssignmentRule(
rule.add(SqlTypeName.TIME);
rule.add(SqlTypeName.DATE);
rule.add(SqlTypeName.TIMESTAMP);
+ rule.add(SqlTypeName.UUID);
+ rule.add(SqlTypeName.VARIANT);
rules.add(SqlTypeName.ANY, rule);
INSTANCE = new SqlTypeAssignmentRule(rules.map);
diff --git
a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
index 303d5bd1e0..c3ba413bf3 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeCoercionRule.java
@@ -111,6 +111,10 @@ private SqlTypeCoercionRule(Map<SqlTypeName,
ImmutableSet<SqlTypeName>> map) {
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.REAL);
@@ -126,6 +130,10 @@ private SqlTypeCoercionRule(Map<SqlTypeName,
ImmutableSet<SqlTypeName>> map) {
coerceRules.add(SqlTypeName.SMALLINT, rule);
coerceRules.add(SqlTypeName.INTEGER, rule);
coerceRules.add(SqlTypeName.BIGINT, rule);
+ coerceRules.add(SqlTypeName.UTINYINT, rule);
+ coerceRules.add(SqlTypeName.USMALLINT, rule);
+ coerceRules.add(SqlTypeName.UINTEGER, rule);
+ coerceRules.add(SqlTypeName.UBIGINT, rule);
coerceRules.add(SqlTypeName.FLOAT, rule);
coerceRules.add(SqlTypeName.REAL, rule);
coerceRules.add(SqlTypeName.DECIMAL, rule);
@@ -149,6 +157,10 @@ private SqlTypeCoercionRule(Map<SqlTypeName,
ImmutableSet<SqlTypeName>> map) {
.add(SqlTypeName.SMALLINT)
.add(SqlTypeName.INTEGER)
.add(SqlTypeName.BIGINT)
+ .add(SqlTypeName.UTINYINT)
+ .add(SqlTypeName.USMALLINT)
+ .add(SqlTypeName.UINTEGER)
+ .add(SqlTypeName.UBIGINT)
.add(SqlTypeName.DECIMAL)
.add(SqlTypeName.CHAR)
.add(SqlTypeName.VARCHAR)
@@ -339,6 +351,10 @@ private SqlTypeCoercionRule(Map<SqlTypeName,
ImmutableSet<SqlTypeName>> map) {
rule.add(SqlTypeName.SMALLINT);
rule.add(SqlTypeName.INTEGER);
rule.add(SqlTypeName.BIGINT);
+ rule.add(SqlTypeName.UTINYINT);
+ rule.add(SqlTypeName.USMALLINT);
+ rule.add(SqlTypeName.UINTEGER);
+ rule.add(SqlTypeName.UBIGINT);
rule.add(SqlTypeName.DECIMAL);
rule.add(SqlTypeName.FLOAT);
rule.add(SqlTypeName.REAL);
@@ -355,6 +371,10 @@ private SqlTypeCoercionRule(Map<SqlTypeName,
ImmutableSet<SqlTypeName>> map) {
coerceRules.add(SqlTypeName.SMALLINT, rule);
coerceRules.add(SqlTypeName.INTEGER, rule);
coerceRules.add(SqlTypeName.BIGINT, rule);
+ coerceRules.add(SqlTypeName.UTINYINT, rule);
+ coerceRules.add(SqlTypeName.USMALLINT, rule);
+ coerceRules.add(SqlTypeName.UINTEGER, rule);
+ coerceRules.add(SqlTypeName.UBIGINT, rule);
// Lenient casting allowing ARRAY to be casted from CHAR and VARCHAR.
coerceRules.add(SqlTypeName.ARRAY,
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
index 4dc077603f..0e9b14e35c 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java
@@ -65,6 +65,7 @@ public enum SqlTypeFamily implements RelDataTypeFamily {
STRING,
APPROXIMATE_NUMERIC,
EXACT_NUMERIC,
+ UNSIGNED_NUMERIC,
DECIMAL,
INTEGER,
DATETIME,
@@ -201,6 +202,8 @@ public Collection<SqlTypeName> getTypeNames() {
return SqlTypeName.APPROX_TYPES;
case EXACT_NUMERIC:
return SqlTypeName.EXACT_TYPES;
+ case UNSIGNED_NUMERIC:
+ return SqlTypeName.UNSIGNED_TYPES;
case INTEGER:
return SqlTypeName.INT_TYPES;
case DATETIME:
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index c984acbffe..79eeaa95bd 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -57,6 +57,11 @@ public enum SqlTypeName {
SMALLINT(PrecScale.NO_NO, false, Types.SMALLINT, SqlTypeFamily.NUMERIC),
INTEGER(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
BIGINT(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
+ // Unsigned types use the next-higher Java type
+ UTINYINT(PrecScale.NO_NO, false, Types.SMALLINT, SqlTypeFamily.NUMERIC),
+ USMALLINT(PrecScale.NO_NO, false, Types.INTEGER, SqlTypeFamily.NUMERIC),
+ UINTEGER(PrecScale.NO_NO, false, Types.BIGINT, SqlTypeFamily.NUMERIC),
+ UBIGINT(PrecScale.NO_NO, false, Types.DECIMAL, SqlTypeFamily.NUMERIC),
DECIMAL(PrecScale.NO_NO | PrecScale.YES_NO | PrecScale.YES_YES, false,
Types.DECIMAL, SqlTypeFamily.NUMERIC),
FLOAT(PrecScale.NO_NO, false, Types.FLOAT, SqlTypeFamily.NUMERIC),
@@ -169,7 +174,8 @@ public enum SqlTypeName {
INTERVAL_HOUR_SECOND, INTERVAL_MINUTE, INTERVAL_MINUTE_SECOND,
INTERVAL_SECOND, TIME_WITH_LOCAL_TIME_ZONE, TIME_TZ,
TIMESTAMP_WITH_LOCAL_TIME_ZONE, TIMESTAMP_TZ,
- FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST,
VARIANT);
+ FLOAT, MULTISET, DISTINCT, STRUCTURED, ROW, CURSOR, COLUMN_LIST,
UUID, VARIANT,
+ UTINYINT, USMALLINT, UINTEGER, UBIGINT);
public static final List<SqlTypeName> BOOLEAN_TYPES =
ImmutableList.of(BOOLEAN);
@@ -180,8 +186,11 @@ public enum SqlTypeName {
public static final List<SqlTypeName> INT_TYPES =
ImmutableList.of(TINYINT, SMALLINT, INTEGER, BIGINT);
+ public static final List<SqlTypeName> UNSIGNED_TYPES =
+ ImmutableList.of(UTINYINT, USMALLINT, UINTEGER, UBIGINT);
+
public static final List<SqlTypeName> EXACT_TYPES =
- combine(INT_TYPES, ImmutableList.of(DECIMAL));
+ combine(combine(INT_TYPES, UNSIGNED_TYPES), ImmutableList.of(DECIMAL));
public static final List<SqlTypeName> APPROX_TYPES =
ImmutableList.of(FLOAT, REAL, DOUBLE);
@@ -307,15 +316,6 @@ public enum SqlTypeName {
* @return Type name, or null if not found
*/
public static @Nullable SqlTypeName get(String name) {
- if (false) {
- // The following code works OK, but the spurious exceptions are
- // annoying.
- try {
- return SqlTypeName.valueOf(name);
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
return VALUES_MAP.get(name);
}
@@ -561,6 +561,18 @@ public int getDefaultScale() {
case BIGINT:
return getNumericLimit(2, 64, sign, limit, beyond);
+ case UTINYINT:
+ return getUnsignedLimit(8, sign, limit, beyond);
+
+ case USMALLINT:
+ return getUnsignedLimit(16, sign, limit, beyond);
+
+ case UINTEGER:
+ return getUnsignedLimit(32, sign, limit, beyond);
+
+ case UBIGINT:
+ return getUnsignedLimit(64, sign, limit, beyond);
+
case DECIMAL:
BigDecimal decimal =
getNumericLimit(10, precision, sign, limit, beyond);
@@ -568,8 +580,6 @@ public int getDefaultScale() {
return null;
}
- // Decimal values must fit into 64 bits. So, the maximum value of
- // a DECIMAL(19, 0) is 2^63 - 1, not 10^19 - 1.
switch (limit) {
case OVERFLOW:
final BigDecimal other =
@@ -971,6 +981,31 @@ public enum Limit {
}
}
+ private static @Nullable BigDecimal getUnsignedLimit(
+ int exponent,
+ boolean sign,
+ Limit limit,
+ boolean beyond) {
+ final int radix = 2;
+ switch (limit) {
+ case OVERFLOW:
+ // 2-based schemes run from 0 to 2^N-1 e.g. 0 to 255
+ if (!sign) {
+ return BigDecimal.ZERO;
+ }
+ final BigDecimal bigRadix = BigDecimal.valueOf(radix);
+ BigDecimal decimal = bigRadix.pow(exponent);
+ return decimal.subtract(BigDecimal.ONE);
+ case UNDERFLOW:
+ return beyond ? null
+ : (sign ? BigDecimal.ZERO : BigDecimal.ONE.negate());
+ case ZERO:
+ return BigDecimal.ZERO;
+ default:
+ throw Util.unexpected(limit);
+ }
+ }
+
public SqlLiteral createLiteral(Object o, SqlParserPos pos) {
switch (this) {
case BOOLEAN:
@@ -999,6 +1034,11 @@ public SqlLiteral createLiteral(Object o, SqlParserPos
pos) {
return SqlLiteral.createTimestamp(this, o instanceof Calendar
? TimestampString.fromCalendarFields((Calendar) o)
: (TimestampString) o, 0 /* todo */, pos);
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
+ // no literals for unsigned values yet
default:
throw Util.unexpected(this);
}
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
index 2dab11f33f..ace4b6bbd2 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeUtil.java
@@ -117,6 +117,39 @@ public static boolean
isCharTypeComparable(List<RelDataType> argTypes) {
return true;
}
+ /** True if there are literals with the specified data type.
+ * Some data types do not have literals (e.g., UNSIGNED, ROW).
+ *
+ * @param type Type for literals.
+ */
+ public static boolean hasLiterals(RelDataType type) {
+ switch (type.getSqlTypeName()) {
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
+ case ANY:
+ case SYMBOL:
+ case MULTISET:
+ case ARRAY:
+ case MAP:
+ case DISTINCT:
+ case STRUCTURED:
+ case ROW:
+ case OTHER:
+ case CURSOR:
+ case COLUMN_LIST:
+ case DYNAMIC_STAR:
+ case GEOMETRY:
+ case MEASURE:
+ case FUNCTION:
+ case SARG:
+ return false;
+ default:
+ return true;
+ }
+ }
+
/**
* Returns whether the operands to a call are char type-comparable.
*
@@ -482,6 +515,10 @@ public static boolean isIntType(RelDataType type) {
case SMALLINT:
case INTEGER:
case BIGINT:
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
return true;
default:
return false;
@@ -508,6 +545,10 @@ public static boolean isExactNumeric(RelDataType type) {
case SMALLINT:
case INTEGER:
case BIGINT:
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
case DECIMAL:
return true;
default:
@@ -520,7 +561,8 @@ public static boolean hasScale(RelDataType type) {
return type.getScale() != Integer.MIN_VALUE;
}
- /** Returns the maximum value of an integral type, as a long value. */
+ /** Returns the maximum value of an integral type, as a long value.
+ * DOES NOT WORK FOR UBIGINT. */
public static long maxValue(RelDataType type) {
assert SqlTypeUtil.isIntType(type);
switch (type.getSqlTypeName()) {
@@ -530,6 +572,12 @@ public static long maxValue(RelDataType type) {
return Short.MAX_VALUE;
case INTEGER:
return Integer.MAX_VALUE;
+ case UTINYINT:
+ return 255;
+ case USMALLINT:
+ return 65535;
+ case UINTEGER:
+ return (1L << 32) - 1;
case BIGINT:
return Long.MAX_VALUE;
default:
@@ -680,6 +728,11 @@ public static long getMinValue(RelDataType type) {
return Short.MIN_VALUE;
case INTEGER:
return Integer.MIN_VALUE;
+ case UTINYINT:
+ case USMALLINT:
+ case UINTEGER:
+ case UBIGINT:
+ return 0;
case BIGINT:
case DECIMAL:
return NumberUtil.getMinUnscaled(type.getPrecision()).longValue();
@@ -689,16 +742,23 @@ public static long getMinValue(RelDataType type) {
}
/** Returns the maximum unscaled value of a numeric type.
+ * DOES NOT WORK CORRECTLY FOR U/BIGINT and many DECIMAL types.
*
* @param type a numeric type
*/
public static long getMaxValue(RelDataType type) {
SqlTypeName typeName = type.getSqlTypeName();
switch (typeName) {
+ case UTINYINT:
+ return 255;
case TINYINT:
return Byte.MAX_VALUE;
+ case USMALLINT:
+ return (1 << 16) - 1;
case SMALLINT:
return Short.MAX_VALUE;
+ case UINTEGER:
+ return (1L << 32) - 1;
case INTEGER:
return Integer.MAX_VALUE;
case BIGINT:
@@ -1132,7 +1192,8 @@ public static SqlDataTypeSpec
convertTypeToSpec(RelDataType type,
final SqlTypeNameSpec typeNameSpec;
if (isAtomic(type) || isNull(type)
|| type.getSqlTypeName() == SqlTypeName.UNKNOWN
- || type.getSqlTypeName() == SqlTypeName.GEOMETRY) {
+ || type.getSqlTypeName() == SqlTypeName.GEOMETRY
+ || SqlTypeUtil.isInterval(type)) {
int precision =
typeName.allowsPrec() ? type.getPrecision() :
RelDataType.PRECISION_NOT_SPECIFIED;
// fix up the precision.
@@ -1848,7 +1909,8 @@ public static boolean isAtomic(RelDataType type) {
return SqlTypeUtil.isDatetime(type)
|| SqlTypeUtil.isNumeric(type)
|| SqlTypeUtil.isString(type)
- || SqlTypeUtil.isBoolean(type);
+ || SqlTypeUtil.isBoolean(type)
+ || typeName == SqlTypeName.UUID;
}
/** Returns a DECIMAL type with the maximum precision for the current
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
index 4922d390b3..dc42e0ad91 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/SqlAbstractConformance.java
@@ -160,4 +160,8 @@ public abstract class SqlAbstractConformance implements
SqlConformance {
@Override public boolean checkedArithmetic() {
return SqlConformanceEnum.DEFAULT.checkedArithmetic();
}
+
+ @Override public boolean supportsUnsignedTypes() {
+ return SqlConformanceEnum.DEFAULT.supportsUnsignedTypes();
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
index a0c28cf5e3..4b3e14e17e 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformance.java
@@ -657,4 +657,9 @@ enum SelectAliasLookup {
* they terminate with a fatal error on overflow.
*/
boolean checkedArithmetic();
+
+ /**
+ * True when the unsigned versions of integer types are supported.
+ */
+ boolean supportsUnsignedTypes();
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
index 0fadad1dc9..d5d99b8099 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlConformanceEnum.java
@@ -506,4 +506,8 @@ public enum SqlConformanceEnum implements SqlConformance {
return true;
}
}
+
+ @Override public boolean supportsUnsignedTypes() {
+ return true;
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
index bb752e5e6c..bd6499e098 100644
---
a/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
+++
b/core/src/main/java/org/apache/calcite/sql/validate/SqlDelegatingConformance.java
@@ -165,4 +165,8 @@ protected SqlDelegatingConformance(SqlConformance delegate)
{
@Override public boolean checkedArithmetic() {
return delegate.checkedArithmetic();
}
+
+ @Override public boolean supportsUnsignedTypes() {
+ return delegate.supportsUnsignedTypes();
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index 9033322110..1a0b298ed0 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -4984,6 +4984,7 @@ private void convertValuesImpl(
mapping = null;
}
+ List<RelDataTypeField> fields = targetRowType.getFieldList();
for (SqlNode rowConstructor : values.getOperandList()) {
SqlCall newRowConst = (SqlCall) rowConstructor;
Blackboard tmpBb = createBlackboard(bb.scope, null, false);
@@ -4991,6 +4992,7 @@ private void convertValuesImpl(
RelOptUtil.Logic.TRUE_FALSE_UNKNOWN);
final PairList<RexNode, String> exps = PairList.of();
Ord.forEach(newRowConst.getOperandList(), (operand, i) -> {
+ RelDataType fieldType = fields.get(i).getType();
RexNode def;
if (processDefaults
&& operand.getKind() == SqlKind.DEFAULT
@@ -5004,6 +5006,9 @@ && requireNonNull(mapping, "mapping")[i] != -1) {
} else {
def = tmpBb.convertExpression(operand);
}
+ if (!(def instanceof RexDynamicParam) &&
!def.getType().equals(fieldType)) {
+ def = rexBuilder.makeCast(operand.getParserPosition(), fieldType,
def);
+ }
exps.add(def, SqlValidatorUtil.alias(operand, i));
});
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 3f472e382e..a76617c194 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -52,6 +52,7 @@
import org.apache.calcite.linq4j.tree.FunctionExpression;
import org.apache.calcite.linq4j.tree.Primitive;
import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.linq4j.tree.UnsignedType;
import org.apache.calcite.plan.volcano.VolcanoPlanner;
import org.apache.calcite.rel.metadata.BuiltInMetadata.AllPredicates;
import org.apache.calcite.rel.metadata.BuiltInMetadata.Collation;
@@ -333,6 +334,14 @@ public enum BuiltInMethod {
INTEGER_CAST(Primitive.class, "integerCast", Primitive.class, Object.class),
INTEGER_CAST_ROUNDING_MODE(Primitive.class, "integerCast",
Primitive.class, Object.class, RoundingMode.class),
+ CAST_TO_UBYTE(UnsignedType.class, "toUByte",
+ Number.class, RoundingMode.class),
+ CAST_TO_USHORT(UnsignedType.class, "toUShort",
+ Number.class, RoundingMode.class),
+ CAST_TO_UINTEGER(UnsignedType.class, "toUInteger",
+ Number.class, RoundingMode.class),
+ CAST_TO_ULONG(UnsignedType.class, "toULong",
+ Number.class, RoundingMode.class),
MEMORY_GET0(MemoryFactory.Memory.class, "get"),
MEMORY_GET1(MemoryFactory.Memory.class, "get", int.class),
ENUMERATOR_CURRENT(Enumerator.class, "current"),
diff --git
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 201287134c..055233677a 100644
---
a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++
b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -30,6 +30,7 @@ IdentifierTooLong=Length of identifier ''{0}'' must be less
than or equal to {1,
BadFormat=not in format ''{0}''
BetweenWithoutAnd=BETWEEN operator has no terminating AND
GeometryDisabled=Geo-spatial extensions and the GEOMETRY data type are not
enabled
+UnsignedDisabled=Support for UNSIGNED data types is not enabled
Proj4jEpsgIsMissing=Proj4J EPSG is missing from the classpath; to resolve this
problem, download the EPSG data set and agree to its terms of use
IllegalIntervalLiteral=Illegal INTERVAL literal {0}; at {1}
IllegalMinusDate=Illegal expression. Was expecting "(DATETIME - DATETIME)
INTERVALQUALIFIER"
diff --git
a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
index b58e48804f..4c15a1476a 100644
--- a/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
+++ b/core/src/test/java/org/apache/calcite/jdbc/CalciteRemoteDriverTest.java
@@ -327,7 +327,7 @@ private static ResultSet getTableTypes(Connection
connection) {
CalciteAssert.hr()
.with(CalciteRemoteDriverTest::getRemoteConnection)
.metaData(CalciteRemoteDriverTest::getTypeInfo)
- .returns(CalciteAssert.checkResultCount(is(45)));
+ .returns(CalciteAssert.checkResultCount(is(49)));
}
@Test void testRemoteTableTypes() {
diff --git
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index 5e8acba68c..7872eaa485 100644
---
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -653,6 +653,13 @@ private static String toSql(RelNode root, SqlDialect
dialect,
sql(query).ok(expected);
}
+ @Test void testUnparseUnsigned() {
+ String query = "select CAST(1 AS UNSIGNED)";
+ String expected = "SELECT CAST(1 AS INTEGER UNSIGNED)\n"
+ + "FROM (VALUES (0)) AS \"t\" (\"ZERO\")";
+ sql(query).ok(expected);
+ }
+
@Test void testSelectQueryWithHiveCube() {
String query = "select \"product_class_id\", \"product_id\", count(*) "
+ "from \"product\" group by cube(\"product_class_id\",
\"product_id\")";
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index ccbd447ac7..e7a5699b8e 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -799,7 +799,7 @@ static SqlOperatorTable operatorTableFor(SqlLibrary
library) {
/** Test case for <a
href="https://issues.apache.org/jira/browse/CALCITE-6779">[CALCITE-6779]
* Casts from UUID to DATE should be invalid</a>. */
@Test void testUuidCasts() {
- final String error = "Cast function cannot convert value of type UUID to
type.*";
+ final String error = "Cast function cannot convert value of type UUID NOT
NULL to type.*";
expr("^CAST(UUID '123e4567-e89b-12d3-a456-426655440000' AS
TIME)^").fails(error);
expr("^CAST(UUID '123e4567-e89b-12d3-a456-426655440000' AS
DATE)^").fails(error);
expr("^CAST(UUID '123e4567-e89b-12d3-a456-426655440000' AS
TIMESTAMP)^").fails(error);
@@ -1414,8 +1414,8 @@ void testLikeAndSimilarFails() {
expr("cast(1.0e1 as boolean)")
.columnType("BOOLEAN NOT NULL");
expr("^cast(true as numeric)^")
- .fails("Cast function cannot convert value of type BOOLEAN "
- + "to type DECIMAL\\(19, 0\\)");
+ .fails("Cast function cannot convert value of type BOOLEAN NOT NULL "
+ + "to type DECIMAL\\(19, 0\\) NOT NULL");
// It's a runtime error that 'TRUE' cannot fit into CHAR(3), but at
// validate time this expression is OK.
expr("cast(true as char(3))")
@@ -1509,16 +1509,16 @@ void testLikeAndSimilarFails() {
.fails("Unknown identifier 'BAR'");
wholeExpr("cast(multiset[1] as integer)")
.fails("(?s).*Cast function cannot convert value of type "
- + "INTEGER MULTISET to type INTEGER");
+ + "INTEGER NOT NULL MULTISET NOT NULL to type INTEGER NOT NULL");
wholeExpr("cast(x'ff' as decimal(5,2))")
.fails("(?s).*Cast function cannot convert value of type "
- + "BINARY\\(1\\) to type DECIMAL\\(5, 2\\)");
+ + "BINARY\\(1\\) NOT NULL to type DECIMAL\\(5, 2\\) NOT NULL");
wholeExpr("cast(DATE '1243-12-01' as TIME)")
.fails("(?s).*Cast function cannot convert value of type "
- + "DATE to type TIME.*");
+ + "DATE NOT NULL to type TIME.*");
wholeExpr("cast(TIME '12:34:01' as DATE)")
.fails("(?s).*Cast function cannot convert value of type "
- + "TIME\\(0\\) to type DATE.*");
+ + "TIME\\(0\\) NOT NULL to type DATE.*");
}
@Test void testCastBinaryLiteral() {
@@ -1683,7 +1683,7 @@ void testLikeAndSimilarFails() {
*/
@Test void testDateTimeCast() {
wholeExpr("CAST(1 as DATE)")
- .fails("Cast function cannot convert value of type INTEGER to type
DATE");
+ .fails("Cast function cannot convert value of type INTEGER NOT NULL to
type DATE NOT NULL");
expr("CAST(DATE '2001-12-21' AS VARCHAR(10))").ok();
expr("CAST( '2001-12-21' AS DATE)").ok();
expr("CAST( TIMESTAMP '2001-12-21 10:12:21' AS VARCHAR(20))").ok();
@@ -7866,10 +7866,10 @@ void testGroupExpressionEquivalenceParams() {
wholeExpr("cast(interval '1:1' hour to minute as interval month)")
.fails("Cast function cannot convert value of type "
- + "INTERVAL HOUR TO MINUTE to type INTERVAL MONTH");
+ + "INTERVAL HOUR TO MINUTE NOT NULL to type INTERVAL MONTH NOT
NULL");
wholeExpr("cast(interval '1-1' year to month as interval second)")
.fails("Cast function cannot convert value of type "
- + "INTERVAL YEAR TO MONTH to type INTERVAL SECOND");
+ + "INTERVAL YEAR TO MONTH NOT NULL to type INTERVAL SECOND NOT
NULL");
}
@Test void testMinusDateOperator() {
diff --git a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
index 16ca49f172..765c65d86c 100644
--- a/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
+++ b/core/src/test/java/org/apache/calcite/tools/PlannerTest.java
@@ -732,7 +732,7 @@ private void runDuplicateSortCheck(String sql, String plan)
throws Exception {
String typeString = SqlTests.getTypeString(insertSourceType);
assertThat(typeString,
is("RecordType(INTEGER NOT NULL empid, INTEGER NOT NULL deptno, "
- + "JavaType(class java.lang.String) name, REAL NOT NULL salary, "
+ + "VARCHAR name, REAL NOT NULL salary, "
+ "INTEGER NOT NULL commission) NOT NULL"));
}
diff --git a/core/src/test/resources/sql/unsigned.iq
b/core/src/test/resources/sql/unsigned.iq
new file mode 100644
index 0000000000..4e0074ae5e
--- /dev/null
+++ b/core/src/test/resources/sql/unsigned.iq
@@ -0,0 +1,157 @@
+# unsigned.iq - Tests for unsigned types
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+!use post
+!set outputformat csv
+
+WITH consts(v) AS (VALUES(CAST(1 AS UNSIGNED)), (2), (3))
+SELECT SUM(v) FROM consts;
+EXPR$0
+6
+!ok
+
+SELECT CAST(200 AS INT UNSIGNED) - 100;
+EXPR$0
+100
+!ok
+
+SELECT CAST(1 AS INT UNSIGNED);
+EXPR$0
+1
+!ok
+
+SELECT CAST(-1 AS INT UNSIGNED);
+java.lang.NumberFormatException: Value is out of range : -1
+!error
+
+SELECT CAST(255 AS TINYINT UNSIGNED);
+EXPR$0
+255
+!ok
+
+SELECT CAST(255 AS TINYINT);
+java.lang.ArithmeticException: Value 255 out of range
+!error
+
+SELECT CAST(200 AS INT UNSIGNED) - CAST(100 AS INT UNSIGNED);
+EXPR$0
+100
+!ok
+
+SELECT CAST(100 AS INT UNSIGNED) - CAST(200 AS INT UNSIGNED);
+java.lang.NumberFormatException: Value is out of range : -100
+!error
+
+SELECT CAST(CAST(100 AS INT UNSIGNED) AS INT) - 200;
+EXPR$0
+-100
+!ok
+
+SELECT CAST(100 AS INT UNSIGNED) - 200;
+java.lang.NumberFormatException: Value is out of range : -100
+!error
+
+SELECT CAST(100 AS INT UNSIGNED) + 200;
+EXPR$0
+300
+!ok
+
+SELECT CAST(100 AS INT UNSIGNED) * 10;
+EXPR$0
+1000
+!ok
+
+SELECT CAST(CAST(10 AS TINYINT) AS INT UNSIGNED);
+EXPR$0
+10
+!ok
+
+SELECT CAST('10' AS INT UNSIGNED);
+EXPR$0
+10
+!ok
+
+SELECT CAST(10.2 AS INT UNSIGNED);
+EXPR$0
+10
+!ok
+
+SELECT CAST(10.2e0 AS INT UNSIGNED);
+EXPR$0
+10
+!ok
+
+SELECT CAST(10 AS INT UNSIGNED) < 20;
+EXPR$0
+true
+!ok
+
+SELECT CAST(65536 AS INT UNSIGNED) * 65536;
+java.lang.NumberFormatException: Value is out of range : 4294967296
+!error
+
+SELECT CAST(65536 AS INT UNSIGNED) * 65535;
+EXPR$0
+4294901760
+!ok
+
+SELECT CAST(65536 AS BIGINT UNSIGNED) * 65536;
+EXPR$0
+4294967296
+!ok
+
+SELECT CAST(255 AS TINYINT UNSIGNED) * 255;
+EXPR$0
+65025
+!ok
+
+SELECT TYPEOF(CAST(CAST(255 AS TINYINT UNSIGNED) * 255 AS VARIANT));
+EXPR$0
+INTEGER
+!ok
+
+SELECT TYPEOF(CAST(CAST(255 AS TINYINT UNSIGNED) AS VARIANT));
+EXPR$0
+TINYINT UNSIGNED
+!ok
+
+SELECT CAST(CAST(CAST(255 AS TINYINT UNSIGNED) AS VARIANT) AS INTEGER);
+EXPR$0
+255
+!ok
+
+SELECT CAST(1000 AS INT UNSIGNED) / 30;
+EXPR$0
+33
+!ok
+
+SELECT CAST(CAST(1 AS UNSIGNED) AS BOOLEAN);
+EXPR$0
+true
+!ok
+
+# Check error message to display type properly
+SELECT CAST(CAST(1 AS UNSIGNED) AS TIME);
+Cast function cannot convert value of type INTEGER UNSIGNED NOT NULL to type
TIME(0) NOT NULL
+!error
+
+SELECT CAST(1 AS UNSIGNED) * 1.0;
+EXPR$0
+1
+!ok
+
+# End unsigned.iq
diff --git a/gradle.properties b/gradle.properties
index 992c9e645e..f86f11caae 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -133,6 +133,7 @@ jmh.version=1.12
jna.version=5.14.0
jna-platform.version=5.14.0
joda-time.version=2.8.1
+joou.version=0.9.4
json-path.version=2.9.0
json-smart.version=2.3
jsr305.version=3.0.2
diff --git a/linq4j/build.gradle.kts b/linq4j/build.gradle.kts
index 68770959b5..fd11eb7379 100644
--- a/linq4j/build.gradle.kts
+++ b/linq4j/build.gradle.kts
@@ -20,4 +20,5 @@
implementation("com.google.guava:guava")
implementation("org.apache.calcite.avatica:avatica-core")
+ implementation("org.jooq:joou-java-6")
}
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/OptimizeShuttle.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/OptimizeShuttle.java
index 2f6f1fefdb..b6ea7e5faa 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/OptimizeShuttle.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/OptimizeShuttle.java
@@ -301,9 +301,14 @@ && isKnownNotNull(expression0)) {
if (expression.getType() == unaryExpression.getType()) {
return expression;
}
- if (expression instanceof ConstantExpression) {
- return Expressions.constant(((ConstantExpression) expression).value,
- unaryExpression.getType());
+ // Removing the cast from a constant may be unsafe
+ // if the value cannot be represented by the target type, e.g., (byte)
1000
+ if (expression instanceof ConstantExpression
+ // but it's always safe for Booleans
+ && (unaryExpression.getType().equals(boolean.class)
+ || expression instanceof ConstantUntypedNull)) {
+ return Expressions.constant(
+ ((ConstantExpression) expression).value,
unaryExpression.getType());
}
break;
case Not:
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
index 926edf9210..a35fba35a0 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/Primitive.java
@@ -18,6 +18,10 @@
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.Nullable;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
@@ -572,7 +576,16 @@ static BigDecimal checkOverflow(BigDecimal value, int
precision, int scale,
if (value instanceof Byte
|| value instanceof Short
|| value instanceof Integer
- || value instanceof Long) {
+ || value instanceof Long
+ || value instanceof UByte
+ || value instanceof UShort
+ || value instanceof UInteger) {
+ return value.longValue();
+ }
+ if (value instanceof ULong) {
+ if (value.longValue() < 0) {
+ throw new ArithmeticException("Value " + value + " out of range");
+ }
return value.longValue();
}
if (value instanceof Float
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
new file mode 100644
index 0000000000..c4277dcd08
--- /dev/null
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.linq4j.tree;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.joou.UByte;
+import org.joou.UInteger;
+import org.joou.ULong;
+import org.joou.UShort;
+import org.joou.Unsigned;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+
+/** Support for compiling unsigned types.
+ * This class has a role similar to {@link Primitive} */
+public enum UnsignedType {
+ UBYTE,
+ USHORT,
+ UINT,
+ ULONG;
+
+ public static @Nullable UnsignedType of(Type type) {
+ if (type == UByte.class) {
+ return UBYTE;
+ } else if (type == UShort.class) {
+ return USHORT;
+ } else if (type == UInteger.class) {
+ return UINT;
+ } else if (type == ULong.class) {
+ return ULONG;
+ } else {
+ return null;
+ }
+ }
+
+ public String toJavaTypeName() {
+ switch (this) {
+ case UBYTE:
+ return UByte.class.getSimpleName();
+ case USHORT:
+ return UShort.class.getSimpleName();
+ case UINT:
+ return UInteger.class.getSimpleName();
+ case ULONG:
+ return ULong.class.getSimpleName();
+ default:
+ throw new AssertionError("Unexpected unsigned type " + this);
+ }
+ }
+
+ /** Get the name of the function that casts a value of type Object
+ * to the current type. */
+ public String getConvertFunctionName() {
+ return "to" + this.toJavaTypeName();
+ }
+
+ public static BigInteger toBigInteger(ULong value) {
+ // This function is missing from the ULong class
+ long l = value.longValue();
+ if (l >= 0L) {
+ return BigInteger.valueOf(l);
+ }
+ return BigInteger.valueOf(l & Long.MAX_VALUE).add(ULong.MAX_VALUE_LONG);
+ }
+
+ // Convert a value to an unsigned type
+
+ // The following methods are called in code generated by EnumUtils.convert
+
+ // UByte
+ public static UByte toUByte(byte n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UByte toUByte(short n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UByte toUByte(int n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UByte toUByte(long n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UByte toUByte(float n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UByte toUByte(double n) {
+ return toUByteNonNull(n, RoundingMode.DOWN);
+ }
+
+ private static UByte toUByteNonNull(Number n, RoundingMode mode) {
+ if (n instanceof BigDecimal) {
+ BigDecimal d = ((BigDecimal) n).setScale(0, mode);
+ return Unsigned.ubyte(d.longValueExact());
+ } else if (n instanceof Byte || n instanceof Short || n instanceof Integer
+ || n instanceof Long || n instanceof Float || n instanceof Double
+ || n instanceof UByte || n instanceof UShort
+ || n instanceof UInteger || n instanceof ULong) {
+ // Using long ensures a range check
+ return Unsigned.ubyte(n.longValue());
+ }
+ throw new IllegalArgumentException("Unexpected Number value " +
n.getClass().getSimpleName());
+ }
+
+ public static @Nullable UByte toUByte(@Nullable Number n, RoundingMode mode)
{
+ if (n == null) {
+ return null;
+ }
+ return toUByteNonNull(n, mode);
+ }
+
+ public static @Nullable UByte toUByte(@Nullable String s, RoundingMode
unused) {
+ if (s == null) {
+ return null;
+ }
+ return Unsigned.ubyte(s);
+ }
+
+ // To UShort
+
+ public static UShort toUShort(byte n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UShort toUShort(short n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UShort toUShort(int n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UShort toUShort(long n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UShort toUShort(float n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UShort toUShort(double n) {
+ return toUShortNonNull(n, RoundingMode.DOWN);
+ }
+
+ private static UShort toUShortNonNull(Number n, RoundingMode mode) {
+ if (n instanceof BigDecimal) {
+ BigDecimal d = ((BigDecimal) n).setScale(0, mode);
+ return Unsigned.ushort(d.intValueExact());
+ } else if (n instanceof Byte || n instanceof Short || n instanceof Integer
+ || n instanceof Long || n instanceof Float || n instanceof Double
+ || n instanceof UByte || n instanceof UShort
+ || n instanceof UInteger || n instanceof ULong) {
+ long value = n.longValue();
+ // ushort(long) is missing from the Unsigned class
+ if (value < 0 || value > 65535) {
+ throw new NumberFormatException("Value is out of range : " + n);
+ }
+ return Unsigned.ushort(n.intValue());
+ }
+ throw new IllegalArgumentException("Unexpected Number value " +
n.getClass().getSimpleName());
+ }
+
+ public static @Nullable UShort toUShort(@Nullable Number n, RoundingMode
mode) {
+ if (n == null) {
+ return null;
+ }
+ return toUShortNonNull(n, mode);
+ }
+
+ public static @Nullable UShort toUShort(@Nullable String s, RoundingMode
unused) {
+ if (s == null) {
+ return null;
+ }
+ return Unsigned.ushort(s);
+ }
+
+ // UInteger
+ public static UInteger toUInteger(byte n) {
+ // Casting to long forces a range check
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UInteger toUInteger(short n) {
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UInteger toUInteger(int n) {
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UInteger toUInteger(long n) {
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UInteger toUInteger(float n) {
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static UInteger toUInteger(double n) {
+ return toUIntegerNonNull(n, RoundingMode.DOWN);
+ }
+
+ private static UInteger toUIntegerNonNull(Number n, RoundingMode mode) {
+ if (n instanceof BigDecimal) {
+ BigDecimal d = ((BigDecimal) n).setScale(0, mode);
+ return Unsigned.uint(d.longValueExact());
+ } else if (n instanceof Byte || n instanceof Short || n instanceof Integer
+ || n instanceof Long || n instanceof Float || n instanceof Double
+ || n instanceof UByte || n instanceof UShort
+ || n instanceof UInteger || n instanceof ULong) {
+ // Using long ensures a range check
+ return Unsigned.uint(n.longValue());
+ }
+ throw new IllegalArgumentException("Unexpected Number value " +
n.getClass().getSimpleName());
+ }
+
+ public static @Nullable UInteger toUInteger(@Nullable Number n, RoundingMode
mode) {
+ if (n == null) {
+ return null;
+ }
+ return toUIntegerNonNull(n, mode);
+ }
+
+ public static @Nullable UInteger toUInteger(@Nullable String s, RoundingMode
unused) {
+ if (s == null) {
+ return null;
+ }
+ return Unsigned.uint(s);
+ }
+
+ // ULong
+ public static ULong toULong(byte n) {
+ // Casting to long forces a range check
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static ULong toULong(short n) {
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static ULong toULong(int n) {
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static ULong toULong(long n) {
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static ULong toULong(float n) {
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ public static ULong toULong(double n) {
+ return toULongNonNull(n, RoundingMode.DOWN);
+ }
+
+ private static ULong toULongNonNull(Number n, RoundingMode mode) {
+ if (n instanceof BigDecimal) {
+ BigDecimal d = ((BigDecimal) n).setScale(0, mode);
+ return Unsigned.ulong(d.longValueExact());
+ } else if (n instanceof Byte || n instanceof Short || n instanceof Integer
+ || n instanceof Long || n instanceof Float || n instanceof Double
+ || n instanceof UByte || n instanceof UShort || n instanceof UInteger)
{
+ long value = n.longValue();
+ if (value < 0) {
+ throw new NumberFormatException("Value is out of range : " + value);
+ }
+ return Unsigned.ulong(value);
+ } else if (n instanceof ULong) {
+ return (ULong) n;
+ }
+ throw new IllegalArgumentException("Unexpected Number value " +
n.getClass().getSimpleName());
+ }
+
+ public static @Nullable ULong toULong(@Nullable Number n, RoundingMode mode)
{
+ if (n == null) {
+ return null;
+ }
+ return toULongNonNull(n, mode);
+ }
+
+ public static @Nullable ULong toULong(@Nullable String s, RoundingMode
unused) {
+ if (s == null) {
+ return null;
+ }
+ return Unsigned.ulong(s);
+ }
+}
diff --git
a/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
b/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
index 579b73f5f5..62c89c7327 100644
--- a/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
+++ b/linq4j/src/test/java/org/apache/calcite/linq4j/test/OptimizerTest.java
@@ -739,7 +739,7 @@ class OptimizerTest {
@Test void testCastIntToShort() {
// return (short) 1 --> return (short) 1
assertThat(optimize(Expressions.convert_(ONE, short.class)),
- is("{\n return (short)1;\n}\n"));
+ is("{\n return (short) 1;\n}\n"));
}
@Test void testCastIntToInt() {
@@ -751,7 +751,7 @@ class OptimizerTest {
@Test void testCastIntToLong() {
// return (long) 1 --> return 1L
assertThat(optimize(Expressions.convert_(ONE, long.class)),
- is("{\n return 1L;\n}\n"));
+ is("{\n return (long) 1;\n}\n"));
}
@Test void testNotTrue() {
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 1c55f4dd2b..7e4f0fa807 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1115,6 +1115,7 @@ ## Keywords
UNNAMED,
**UNNEST**,
UNPIVOT,
+**UNSIGNED**,
**UPDATE**,
**UPPER**,
**UPSERT**,
@@ -1193,6 +1194,10 @@ ### Scalar types
| SMALLINT | 2 byte signed integer | Range is -32768 to 32767
| INTEGER, INT | 4 byte signed integer | Range is -2147483648 to 2147483647
| BIGINT | 8 byte signed integer | Range is -9223372036854775808 to
9223372036854775807
+| TINYINT UNSIGNED | 1 byte unsigned integer | Range is 0 to 255
+| SMALLINT UNSIGNED | 2 byte unsigned integer | Range is 0 to 65535
+| INTEGER UNSIGNED, INT UNSIGNED | 4 byte unsigned integer | Range is 0 to
4294967295
+| BIGINT UNSIGNED | 8 byte unsigned integer | Range is 0 to
18446744073709551615
| DECIMAL(p, s) | Fixed point | Example: 123.45 and DECIMAL
'123.45' are identical values, and have type DECIMAL(5, 2)
| NUMERIC(p, s) | Fixed point | A synonym for DECIMAL
| REAL | 4 byte floating point | 6 decimal digits precision;
examples: CAST(1.2 AS REAL), CAST('Infinity' AS REAL)
@@ -1601,9 +1606,9 @@ #### Explicit Type Conversion
| integer
| BINARY [ precision ]
| varbinary [ precision ]
- | TINYINT
- | SMALLINT
- | BIGINT
+ | TINYINT [ UNSIGNED ]
+ | SMALLINT [ UNSIGNED ]
+ | BIGINT [ UNSIGNED ]
| REAL
| double
| FLOAT
@@ -1628,7 +1633,7 @@ #### Explicit Type Conversion
DECIMAL | DEC | NUMERIC
integer:
- INTEGER | INT
+ INTEGER [ UNSIGNED ] | INT [ UNSIGNED ] | UNSIGNED
varbinary:
BINARY VARYING | VARBINARY
@@ -1658,29 +1663,33 @@ #### Implicit Type Conversion
without regard to the context in which it is made. The rules governing
these details follow the table.
-| FROM - TO | NULL | BOOLEAN | TINYINT | SMALLINT | INT | BIGINT |
DECIMAL | FLOAT or REAL | DOUBLE | INTERVAL | DATE | TIME | TIMESTAMP | CHAR or
VARCHAR | BINARY or VARBINARY | GEOMETRY | ARRAY | MAP | MULTISET | ROW | UUID |
-|:--------------------|:-----|:--------|:--------|:---------|:----|:-------|:--------|:--------------|:-------|:---------|:-----|:-----|:----------|:----------------|:--------------------|:---------|:------|:----|:---------|:----|:-----|
-| NULL | i | i | i | i | i | i | i
| i | i | i | i | i | i | i
| i | i | x | x | x | x | i |
-| BOOLEAN | x | i | x | x | x | x | x
| x | x | x | x | x | x | i
| x | x | x | x | x | x | x |
-| TINYINT | x | e | i | i | i | i | i
| i | i | e | x | x | e | i
| x | x | x | x | x | x | x |
-| SMALLINT | x | e | i | i | i | i | i
| i | i | e | x | x | e | i
| x | x | x | x | x | x | x |
-| INT | x | e | i | i | i | i | i
| i | i | e | x | x | e | i
| x | x | x | x | x | x | x |
-| BIGINT | x | e | i | i | i | i | i
| i | i | e | x | x | e | i
| x | x | x | x | x | x | x |
-| DECIMAL | x | e | i | i | i | i | i
| i | i | e | x | x | e | i
| x | x | x | x | x | x | x |
-| FLOAT/REAL | x | e | i | i | i | i | i
| i | i | x | x | x | e | i
| x | x | x | x | x | x | x |
-| DOUBLE | x | e | i | i | i | i | i
| i | i | x | x | x | e | i
| x | x | x | x | x | x | x |
-| INTERVAL | x | x | e | e | e | e | e
| x | x | i | x | x | x | e
| x | x | x | x | x | x | x |
-| DATE | x | x | x | x | x | x | x
| x | x | x | i | x | i | i
| x | x | x | x | x | x | x |
-| TIME | x | x | x | x | x | x | x
| x | x | x | x | i | e | i
| x | x | x | x | x | x | x |
-| TIMESTAMP | x | x | e | e | e | e | e
| e | e | x | i | e | i | i
| x | x | x | x | x | x | x |
-| CHAR or VARCHAR | x | e | i | i | i | i | i
| i | i | i | i | i | i | i
| i | i | i | i | i | i | i |
-| BINARY or VARBINARY | x | x | x | x | x | x | x
| x | x | x | e | e | e | i
| i | x | x | x | x | x | i |
-| GEOMETRY | x | x | x | x | x | x | x
| x | x | x | x | x | x | i
| x | i | x | x | x | x | x |
-| ARRAY | x | x | x | x | x | x | x
| x | x | x | x | x | x | x
| x | x | i | x | x | x | x |
-| MAP | x | x | x | x | x | x | x
| x | x | x | x | x | x | x
| x | x | x | i | x | x | x |
-| MULTISET | x | x | x | x | x | x | x
| x | x | x | x | x | x | x
| x | x | x | x | i | x | x |
-| ROW | x | x | x | x | x | x | x
| x | x | x | x | x | x | x
| x | x | x | x | x | i | x |
-| UUID | x | x | x | x | x | x | x
| x | x | x | x | x | x | x
| x | x | x | x | x | x | i |
+| FROM - TO | NULL | BOOLEAN | TINYINT | SMALLINT | INT | BIGINT |
TINYINT UNSIGNED | SMALLINT UNSIGNED | INT UNSIGNED | BIGINT UNSIGNED | DECIMAL
| FLOAT or REAL | DOUBLE | INTERVAL | DATE | TIME | TIMESTAMP | CHAR or VARCHAR
| BINARY or VARBINARY | GEOMETRY | ARRAY | MAP | MULTISET | ROW | UUID |
+|:--------------------|:-----|:--------|:--------|:---------|:----|:-------|:-----------------|:------------------|:-------------|:----------------|:--------|:--------------|:-------|:---------|:-----|:-----|:----------|:----------------|:--------------------|:---------|:------|:----|:---------|:----|:-----|
+| NULL | i | i | i | i | i | i | i
| i | i | i | i |
i | i | i | i | i | i | i |
i | i | x | x | x | x | i |
+| BOOLEAN | x | i | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | i |
x | x | x | x | x | x | x |
+| TINYINT | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| SMALLINT | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| INT | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| BIGINT | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| TINYINT UNSIGNED | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| SMALLINT UNSIGNED | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| INT UNSIGNED | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| BIGINT UNSIGNED | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| DECIMAL | x | e | i | i | i | i | i
| i | i | i | i |
i | i | e | x | x | e | i |
x | x | x | x | x | x | x |
+| FLOAT/REAL | x | e | i | i | i | i | i
| i | i | i | i |
i | i | x | x | x | e | i |
x | x | x | x | x | x | x |
+| DOUBLE | x | e | i | i | i | i | i
| i | i | i | i |
i | i | x | x | x | e | i |
x | x | x | x | x | x | x |
+| INTERVAL | x | x | e | e | e | e | e
| e | e | e | e |
x | x | i | x | x | x | e |
x | x | x | x | x | x | x |
+| DATE | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | i | x | i | i |
x | x | x | x | x | x | x |
+| TIME | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | i | e | i |
x | x | x | x | x | x | x |
+| TIMESTAMP | x | x | e | e | e | e | e
| e | e | e | e |
e | e | x | i | e | i | i |
x | x | x | x | x | x | x |
+| CHAR or VARCHAR | x | e | i | i | i | i | i
| i | i | i | i |
i | i | i | i | i | i | i |
i | i | i | i | i | i | i |
+| BINARY or VARBINARY | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | e | e | e | i |
i | x | x | x | x | x | i |
+| GEOMETRY | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | i |
x | i | x | x | x | x | x |
+| ARRAY | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | x |
x | x | i | x | x | x | x |
+| MAP | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | x |
x | x | x | i | x | x | x |
+| MULTISET | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | x |
x | x | x | x | i | x | x |
+| ROW | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | x |
x | x | x | x | x | i | x |
+| UUID | x | x | x | x | x | x | x
| x | x | x | x |
x | x | x | x | x | x | x |
x | x | x | x | x | x | i |
i: implicit cast / e: explicit cast / x: not allowed
@@ -1688,6 +1697,9 @@ ##### Conversion Contexts and Strategies
* Set operation (`UNION`, `EXCEPT`, `INTERSECT`): compare every branch
row data type and find the common type of each fields pair;
+* Arithmetic operations combining signed and unsigned values will
+ produce a result with the wider type; if both types have the same width,
+ the result is unsigned;
* Binary arithmetic expression (`+`, `-`, `&`, `^`, `/`, `%`): promote
string operand to data type of the other numeric operand;
* Binary comparison (`=`, `<`, `<=`, `<>`, `>`, `>=`):
diff --git
a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 07dc89179e..5846dac493 100644
--- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -40,6 +40,7 @@
import org.apache.calcite.sql.test.SqlTests;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.util.SqlShuttle;
+import org.apache.calcite.sql.validate.SqlAbstractConformance;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.test.IntervalTest;
@@ -566,6 +567,7 @@ public class SqlParserTest {
"UNIQUE", "92", "99", "2003", "2011", "2014", "c",
"UNKNOWN", "92", "99", "2003", "2011", "2014", "c",
"UNNEST", "99", "2003", "2011", "2014", "c",
+ "UNSIGNED", "c",
"UNTIL", "92", "99", "2003",
"UPDATE", "92", "99", "2003", "2011", "2014", "c",
"UPPER", "92", "2011", "2014", "c",
@@ -1413,6 +1415,25 @@ private void checkLarge(int n) {
+ "FROM `T`");
}
+ @Test void testUnsigned() {
+ sql("SELECT CAST(1 AS UNSIGNED)")
+ .ok("SELECT CAST(1 AS INTEGER UNSIGNED)");
+ sql("SELECT CAST(1 AS INTEGER UNSIGNED)")
+ .same();
+ sql("SELECT CAST(1 AS TINYINT UNSIGNED)")
+ .same();
+ sql("SELECT CAST(1 AS SMALLINT UNSIGNED)")
+ .same();
+ sql("SELECT CAST(1 AS BIGINT UNSIGNED)")
+ .same();
+ sql("SELECT CAST(1 AS ^UNSIGNED^)")
+ .withConformance(new SqlAbstractConformance() {
+ @Override public boolean supportsUnsignedTypes() {
+ return false;
+ }
+ }).fails("Support for UNSIGNED data types is not enabled");
+ }
+
@Test void testRow() {
sql("select t.r.\"EXPR$1\", t.r.\"EXPR$0\" from (select (1,2) r from
sales.depts) t")
.ok("SELECT `T`.`R`.`EXPR$1`, `T`.`R`.`EXPR$0`\n"
diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
index a20ad2d326..4866a567fa 100644
--- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
+++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java
@@ -643,18 +643,22 @@ void testCastBooleanToNumeric(CastType castType,
SqlOperatorFixture f) {
f.setFor(SqlStdOperatorTable.CAST, VmName.EXPAND);
SqlOperatorFixture f0 = f.withConformance(SqlConformanceEnum.DEFAULT);
f0.checkFails("^" + castType.name() + "(true as integer)^",
- "Cast function cannot convert value of type BOOLEAN to type INTEGER",
false);
+ "Cast function cannot convert value of type BOOLEAN NOT NULL to "
+ + "type INTEGER NOT NULL", false);
f0.checkFails("^" + castType.name() + "(true as decimal)^",
- "Cast function cannot convert value of type BOOLEAN to type
DECIMAL\\(19, 0\\)", false);
+ "Cast function cannot convert value of type BOOLEAN NOT NULL to "
+ + "type DECIMAL\\(19, 0\\) NOT NULL", false);
SqlOperatorFixture f1 = f.withConformance(SqlConformanceEnum.BIG_QUERY);
f1.checkString("cast(true as integer)", "1", "INTEGER NOT NULL");
f1.checkString("cast(false as integer)", "0", "INTEGER NOT NULL");
f1.checkString("cast(true as bigint)", "1", "BIGINT NOT NULL");
f1.checkFails("^" + castType.name() + "(true as float)^",
- "Cast function cannot convert value of type BOOLEAN to type FLOAT",
false);
+ "Cast function cannot convert value of type BOOLEAN NOT NULL to "
+ + "type FLOAT NOT NULL", false);
f1.checkFails("^" + castType.name() + "(true as decimal)^",
- "Cast function cannot convert value of type BOOLEAN to type
DECIMAL\\(19, 0\\)", false);
+ "Cast function cannot convert value of type BOOLEAN NOT NULL to "
+ + "type DECIMAL\\(19, 0\\) NOT NULL", false);
}
@ParameterizedTest