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

Reply via email to