You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by la...@apache.org on 2019/07/16 20:29:26 UTC

[calcite] branch master updated: CALCITE-3187: Make decimal type inference overridable (Praveen Kumar)

This is an automated email from the ASF dual-hosted git repository.

laurent pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/master by this push:
     new f7c0b0a  CALCITE-3187: Make decimal type inference overridable (Praveen Kumar)
f7c0b0a is described below

commit f7c0b0a18ab72338e0b3afcdfd087aab3572fddb
Author: Praveen <pr...@dremio.com>
AuthorDate: Tue Jul 9 11:24:32 2019 +0530

    CALCITE-3187: Make decimal type inference overridable (Praveen Kumar)
    
    Decimal return type inference for addition and modulus is hardcoded.
    
    Add new methods to RelDataTypeSystem to allow implementers to override
    behavior if needed. Deprecate existing methods for multiply and divide
    in RelDataTypeFactory and move them over to RelDataTypeSystem for
    consistency.
    
    Close apache/calcite#1311
---
 .../calcite/rel/type/RelDataTypeFactory.java       |  11 +
 .../calcite/rel/type/RelDataTypeFactoryImpl.java   | 118 ++--------
 .../apache/calcite/rel/type/RelDataTypeSystem.java | 246 +++++++++++++++++++++
 .../calcite/sql/fun/SqlStdOperatorTable.java       |   2 +-
 .../org/apache/calcite/sql/type/ReturnTypes.java   |  71 +++---
 .../calcite/sql/type/RelDataTypeSystemTest.java    | 202 +++++++++++++++++
 6 files changed, 502 insertions(+), 148 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
index c83e6a2..7cc5f25 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactory.java
@@ -270,7 +270,10 @@ public interface RelDataTypeFactory {
    * @param type2 type of the second operand
    * @return the result type for a decimal multiplication, or null if decimal
    * multiplication should not be applied to the operands.
+   * @deprecated Use
+   * {@link RelDataTypeSystem#deriveDecimalMultiplyType(RelDataTypeFactory, RelDataType, RelDataType)}
    */
+  @Deprecated // to be removed before 2.0
   RelDataType createDecimalProduct(
       RelDataType type1,
       RelDataType type2);
@@ -280,7 +283,11 @@ public interface RelDataTypeFactory {
    * arguments to double values.
    *
    * <p>Pre-condition: <code>createDecimalProduct(type1, type2) != null</code>
+   *
+   * @deprecated Use
+   * {@link RelDataTypeSystem#shouldUseDoubleMultiplication(RelDataType, RelDataType)}
    */
+  @Deprecated // to be removed before 2.0
   boolean useDoubleMultiplication(
       RelDataType type1,
       RelDataType type2);
@@ -294,7 +301,11 @@ public interface RelDataTypeFactory {
    * @param type2 type of the second operand
    * @return the result type for a decimal division, or null if decimal
    * division should not be applied to the operands.
+   *
+   * @deprecated Use
+   * {@link RelDataTypeSystem#deriveDecimalDivideType(RelDataTypeFactory, RelDataType, RelDataType)}
    */
+  @Deprecated // to be removed before 2.0
   RelDataType createDecimalQuotient(
       RelDataType type1,
       RelDataType type2);
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
index 4a7d95d..b8af869 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeFactoryImpl.java
@@ -22,7 +22,6 @@ import org.apache.calcite.sql.type.JavaToSqlTypeConversionRules;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
-import org.apache.calcite.util.Glossary;
 import org.apache.calcite.util.Util;
 
 import com.google.common.cache.CacheBuilder;
@@ -457,125 +456,36 @@ public abstract class RelDataTypeFactoryImpl implements RelDataTypeFactory {
   }
 
   /**
-   * {@inheritDoc}
-   *
-   * <p>Implement RelDataTypeFactory with SQL 2003 compliant behavior. Let p1,
-   * s1 be the precision and scale of the first operand Let p2, s2 be the
-   * precision and scale of the second operand Let p, s be the precision and
-   * scale of the result, Then the result type is a decimal with:
-   *
-   * <ul>
-   * <li>p = p1 + p2</li>
-   * <li>s = s1 + s2</li>
-   * </ul>
-   *
-   * <p>p and s are capped at their maximum values
-   *
-   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   * Delegates to
+   * {@link RelDataTypeSystem#deriveDecimalMultiplyType(RelDataTypeFactory, RelDataType, RelDataType)}
+   * to get the return type for the operation.
    */
   public RelDataType createDecimalProduct(
       RelDataType type1,
       RelDataType type2) {
-    if (SqlTypeUtil.isExactNumeric(type1)
-        && SqlTypeUtil.isExactNumeric(type2)) {
-      if (SqlTypeUtil.isDecimal(type1)
-          || SqlTypeUtil.isDecimal(type2)) {
-        int p1 = type1.getPrecision();
-        int p2 = type2.getPrecision();
-        int s1 = type1.getScale();
-        int s2 = type2.getScale();
-
-        int scale = s1 + s2;
-        scale = Math.min(scale, typeSystem.getMaxNumericScale());
-        int precision = p1 + p2;
-        precision =
-            Math.min(
-                precision,
-                typeSystem.getMaxNumericPrecision());
-
-        RelDataType ret;
-        ret =
-            createSqlType(
-                SqlTypeName.DECIMAL,
-                precision,
-                scale);
-
-        return ret;
-      }
-    }
-
-    return null;
+    return typeSystem.deriveDecimalMultiplyType(this, type1, type2);
   }
 
-  // implement RelDataTypeFactory
+  /**
+   * Delegates to
+   * {@link RelDataTypeSystem#shouldUseDoubleMultiplication(RelDataTypeFactory, RelDataType, RelDataType)}
+   * to get if double should be used for multiplication.
+   */
   public boolean useDoubleMultiplication(
       RelDataType type1,
       RelDataType type2) {
-    assert createDecimalProduct(type1, type2) != null;
-    return false;
+    return typeSystem.shouldUseDoubleMultiplication(this, type1, type2);
   }
 
   /**
-   * Rules:
-   *
-   * <ul>
-   * <li>Let p1, s1 be the precision and scale of the first operand
-   * <li>Let p2, s2 be the precision and scale of the second operand
-   * <li>Let p, s be the precision and scale of the result
-   * <li>Let d be the number of whole digits in the result
-   * <li>Then the result type is a decimal with:
-   *   <ul>
-   *   <li>d = p1 - s1 + s2</li>
-   *   <li>s &lt; max(6, s1 + p2 + 1)</li>
-   *   <li>p = d + s</li>
-   *   </ul>
-   * </li>
-   * <li>p and s are capped at their maximum values</li>
-   * </ul>
-   *
-   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   * Delegates to
+   * {@link RelDataTypeSystem#deriveDecimalDivideType(RelDataTypeFactory, RelDataType, RelDataType)}
+   * to get the return type for the operation.
    */
   public RelDataType createDecimalQuotient(
       RelDataType type1,
       RelDataType type2) {
-    if (SqlTypeUtil.isExactNumeric(type1)
-        && SqlTypeUtil.isExactNumeric(type2)) {
-      if (SqlTypeUtil.isDecimal(type1)
-          || SqlTypeUtil.isDecimal(type2)) {
-        int p1 = type1.getPrecision();
-        int p2 = type2.getPrecision();
-        int s1 = type1.getScale();
-        int s2 = type2.getScale();
-
-        final int maxNumericPrecision = typeSystem.getMaxNumericPrecision();
-        int dout =
-            Math.min(
-                p1 - s1 + s2,
-                maxNumericPrecision);
-
-        int scale = Math.max(6, s1 + p2 + 1);
-        scale =
-            Math.min(
-                scale,
-                maxNumericPrecision - dout);
-        scale = Math.min(scale, getTypeSystem().getMaxNumericScale());
-
-        int precision = dout + scale;
-        assert precision <= maxNumericPrecision;
-        assert precision > 0;
-
-        RelDataType ret;
-        ret =
-            createSqlType(
-                SqlTypeName.DECIMAL,
-                precision,
-                scale);
-
-        return ret;
-      }
-    }
-
-    return null;
+    return typeSystem.deriveDecimalDivideType(this, type1, type2);
   }
 
   public Charset getDefaultCharset() {
diff --git a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
index 6103a24..bc9ded5 100644
--- a/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
+++ b/core/src/main/java/org/apache/calcite/rel/type/RelDataTypeSystem.java
@@ -17,6 +17,8 @@
 package org.apache.calcite.rel.type;
 
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.util.Glossary;
 
 /**
  * Type system.
@@ -100,6 +102,250 @@ public interface RelDataTypeSystem {
   /** Whether the least restrictive type of a number of CHAR types of different
    * lengths should be a VARCHAR type. And similarly BINARY to VARBINARY. */
   boolean shouldConvertRaggedUnionTypesToVarying();
+
+  /**
+   * Infers the return type of a decimal addition. Decimal addition involves
+   * at least one decimal operand and requires both operands to have exact
+   * numeric types.
+   *
+   * Default implementation is SQL 2003 compliant. Let p1, s1 be the precision
+   * and scale of the first operand Let p2, s2 be the precision and scale of the
+   * second operand Let p, s be the precision and scale of the result, Then the
+   * result type is a decimal with:
+   *
+   * Rules:
+   *
+   * <ul>
+   * <li>Let p1, s1 be the precision and scale of the first operand</li>
+   * <li>Let p2, s2 be the precision and scale of the second operand</li>
+   * <li>Let p, s be the precision and scale of the result</li>
+   * <li>Let d be the number of whole digits in the result</li>
+   * <li>Then the result type is a decimal with:
+   *   <ul>
+   *   <li>s = max(s1, s2)</li>
+   *   <li>p = max(p1 - s1, p2 - s2) + s + 1</li>
+   *   </ul>
+   * </li>
+   * <li>p and s are capped at their maximum values</li>
+   * </ul>
+   *
+   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   *
+   * @param typeFactory typeFactory used to create output type
+   * @param type1 type of the first operand
+   * @param type2 type of the second operand
+   * @return the result type for a decimal addition.
+   */
+  default RelDataType deriveDecimalPlusType(RelDataTypeFactory typeFactory,
+                                            RelDataType type1, RelDataType type2) {
+    if (SqlTypeUtil.isExactNumeric(type1)
+            && SqlTypeUtil.isExactNumeric(type2)) {
+      if (SqlTypeUtil.isDecimal(type1)
+              || SqlTypeUtil.isDecimal(type2)) {
+        int p1 = type1.getPrecision();
+        int p2 = type2.getPrecision();
+        int s1 = type1.getScale();
+        int s2 = type2.getScale();
+        int scale = Math.max(s1, s2);
+        assert scale <= getMaxNumericScale();
+        int precision = Math.max(p1 - s1, p2 - s2) + scale + 1;
+        precision =
+                Math.min(
+                        precision,
+                        getMaxNumericPrecision());
+        assert precision > 0;
+
+        return typeFactory.createSqlType(
+                SqlTypeName.DECIMAL,
+                precision,
+                scale);
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Returns whether a decimal multiplication should be implemented by casting
+   * arguments to double values.
+   *
+   * <p>Pre-condition: <code>createDecimalProduct(type1, type2) != null</code>
+   */
+  default boolean shouldUseDoubleMultiplication(
+      RelDataTypeFactory typeFactory,
+      RelDataType type1,
+      RelDataType type2) {
+    assert deriveDecimalMultiplyType(typeFactory, type1, type2) != null;
+    return false;
+  }
+
+  /**
+   * Infers the return type of a decimal multiplication. Decimal
+   * multiplication involves at least one decimal operand and requires both
+   * operands to have exact numeric types.
+   *
+   * Default implementation is SQL 2003 compliant.
+   *
+   * Rules:
+   *
+   * <ul>
+   * <li>Let p1, s1 be the precision and scale of the first operand</li>
+   * <li>Let p2, s2 be the precision and scale of the second operand</li>
+   * <li>Let p, s be the precision and scale of the result</li>
+   * <li>Let d be the number of whole digits in the result</li>
+   * <li>Then the result type is a decimal with:
+   *   <ul>
+   *   <li>p = p1 + p2)</li>
+   *   <li>s = s1 + s2</li>
+   *   </ul>
+   * </li>
+   * <li>p and s are capped at their maximum values</li>
+   * </ul>
+   *
+   * <p>p and s are capped at their maximum values
+   *
+   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   *
+   * @param typeFactory typeFactory used to create output type
+   * @param type1 type of the first operand
+   * @param type2 type of the second operand
+   * @return the result type for a decimal multiplication, or null if decimal
+   * multiplication should not be applied to the operands.
+   */
+  default RelDataType deriveDecimalMultiplyType(RelDataTypeFactory typeFactory,
+      RelDataType type1, RelDataType type2) {
+    if (SqlTypeUtil.isExactNumeric(type1)
+            && SqlTypeUtil.isExactNumeric(type2)) {
+      if (SqlTypeUtil.isDecimal(type1)
+              || SqlTypeUtil.isDecimal(type2)) {
+        int p1 = type1.getPrecision();
+        int p2 = type2.getPrecision();
+        int s1 = type1.getScale();
+        int s2 = type2.getScale();
+
+        int scale = s1 + s2;
+        scale = Math.min(scale, getMaxNumericScale());
+        int precision = p1 + p2;
+        precision =
+                Math.min(
+                        precision,
+                        getMaxNumericPrecision());
+
+        RelDataType ret;
+        ret = typeFactory.createSqlType(
+                        SqlTypeName.DECIMAL,
+                        precision,
+                        scale);
+
+        return ret;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Infers the return type of a decimal division. Decimal division involves
+   * at least one decimal operand and requires both operands to have exact
+   * numeric types.
+   *
+   * Default implementation is SQL 2003 compliant.
+   *
+   * Rules:
+   *
+   * <ul>
+   * <li>Let p1, s1 be the precision and scale of the first operand</li>
+   * <li>Let p2, s2 be the precision and scale of the second operand</li>
+   * <li>Let p, s be the precision and scale of the result</li>
+   * <li>Let d be the number of whole digits in the result</li>
+   * <li>Then the result type is a decimal with:
+   *   <ul>
+   *   <li>d = p1 - s1 + s2</li>
+   *   <li>s &lt; max(6, s1 + p2 + 1)</li>
+   *   <li>p = d + s</li>
+   *   </ul>
+   * </li>
+   * <li>p and s are capped at their maximum values</li>
+   * </ul>
+   *
+   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   *
+   * @param typeFactory typeFactory used to create output type
+   * @param type1 type of the first operand
+   * @param type2 type of the second operand
+   * @return the result type for a decimal division, or null if decimal
+   * division should not be applied to the operands.
+   */
+  default RelDataType deriveDecimalDivideType(RelDataTypeFactory typeFactory,
+      RelDataType type1, RelDataType type2) {
+
+    if (SqlTypeUtil.isExactNumeric(type1)
+            && SqlTypeUtil.isExactNumeric(type2)) {
+      if (SqlTypeUtil.isDecimal(type1)
+              || SqlTypeUtil.isDecimal(type2)) {
+        int p1 = type1.getPrecision();
+        int p2 = type2.getPrecision();
+        int s1 = type1.getScale();
+        int s2 = type2.getScale();
+
+        final int maxNumericPrecision = getMaxNumericPrecision();
+        int dout =
+                Math.min(
+                        p1 - s1 + s2,
+                        maxNumericPrecision);
+
+        int scale = Math.max(6, s1 + p2 + 1);
+        scale =
+                Math.min(
+                        scale,
+                        maxNumericPrecision - dout);
+        scale = Math.min(scale, getMaxNumericScale());
+
+        int precision = dout + scale;
+        assert precision <= maxNumericPrecision;
+        assert precision > 0;
+
+        RelDataType ret;
+        ret = typeFactory.
+                createSqlType(
+                        SqlTypeName.DECIMAL,
+                        precision,
+                        scale);
+
+        return ret;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Infers the return type of a decimal modulus operation. Decimal modulus
+   * involves at least one decimal operand and requires both operands to have
+   * exact numeric types.
+   *
+   * Default implementation is SQL 2003 compliant: the declared type of the
+   * result is the declared type of the second operand (expression divisor).
+   *
+   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.27
+   *
+   * @param typeFactory typeFactory used to create output type
+   * @param type1 type of the first operand
+   * @param type2 type of the second operand
+   * @return the result type for a decimal modulus, or null if decimal
+   * modulus should not be applied to the operands.
+   */
+  default RelDataType deriveDecimalModType(RelDataTypeFactory typeFactory,
+      RelDataType type1, RelDataType type2) {
+    if (SqlTypeUtil.isExactNumeric(type1)
+            && SqlTypeUtil.isExactNumeric(type2)) {
+      if (SqlTypeUtil.isDecimal(type1)
+              || SqlTypeUtil.isDecimal(type2)) {
+        return type2;
+      }
+    }
+    return null;
+  }
+
 }
 
 // End RelDataTypeSystem.java
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index dae3b00..d803beb 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1557,7 +1557,7 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable {
       new SqlFunction(
           "MOD",
           SqlKind.MOD,
-          ReturnTypes.ARG1_NULLABLE,
+          ReturnTypes.NULLABLE_MOD,
           null,
           OperandTypes.EXACT_NUMERIC_EXACT_NUMERIC,
           SqlFunctionCategory.NUMERIC);
diff --git a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
index e05623f..aba475f 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/ReturnTypes.java
@@ -449,7 +449,7 @@ public abstract class ReturnTypes {
     RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
     RelDataType type1 = opBinding.getOperandType(0);
     RelDataType type2 = opBinding.getOperandType(1);
-    return typeFactory.createDecimalProduct(type1, type2);
+    return typeFactory.getTypeSystem().deriveDecimalMultiplyType(typeFactory, type1, type2);
   };
   /**
    * Same as {@link #DECIMAL_PRODUCT} but returns with nullability if any of
@@ -479,7 +479,7 @@ public abstract class ReturnTypes {
     RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
     RelDataType type1 = opBinding.getOperandType(0);
     RelDataType type2 = opBinding.getOperandType(1);
-    return typeFactory.createDecimalQuotient(type1, type2);
+    return typeFactory.getTypeSystem().deriveDecimalDivideType(typeFactory, type1, type2);
   };
   /**
    * Same as {@link #DECIMAL_QUOTIENT} but returns with nullability if any of
@@ -501,52 +501,15 @@ public abstract class ReturnTypes {
   /**
    * Type-inference strategy whereby the result type of a call is the decimal
    * sum of two exact numeric operands where at least one of the operands is a
-   * decimal. Let p1, s1 be the precision and scale of the first operand Let
-   * p2, s2 be the precision and scale of the second operand Let p, s be the
-   * precision and scale of the result, Then the result type is a decimal
-   * with:
-   *
-   * <ul>
-   * <li>s = max(s1, s2)</li>
-   * <li>p = max(p1 - s1, p2 - s2) + s + 1</li>
-   * </ul>
-   *
-   * <p>p and s are capped at their maximum values
-   *
-   * @see Glossary#SQL2003 SQL:2003 Part 2 Section 6.26
+   * decimal.
    */
   public static final SqlReturnTypeInference DECIMAL_SUM = opBinding -> {
+    RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
     RelDataType type1 = opBinding.getOperandType(0);
     RelDataType type2 = opBinding.getOperandType(1);
-    if (SqlTypeUtil.isExactNumeric(type1)
-        && SqlTypeUtil.isExactNumeric(type2)) {
-      if (SqlTypeUtil.isDecimal(type1)
-          || SqlTypeUtil.isDecimal(type2)) {
-        int p1 = type1.getPrecision();
-        int p2 = type2.getPrecision();
-        int s1 = type1.getScale();
-        int s2 = type2.getScale();
-
-        final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
-        int scale = Math.max(s1, s2);
-        final RelDataTypeSystem typeSystem = typeFactory.getTypeSystem();
-        assert scale <= typeSystem.getMaxNumericScale();
-        int precision = Math.max(p1 - s1, p2 - s2) + scale + 1;
-        precision =
-            Math.min(
-                precision,
-                typeSystem.getMaxNumericPrecision());
-        assert precision > 0;
-
-        return typeFactory.createSqlType(
-            SqlTypeName.DECIMAL,
-            precision,
-            scale);
-      }
-    }
-
-    return null;
+    return typeFactory.getTypeSystem().deriveDecimalPlusType(typeFactory, type1, type2);
   };
+
   /**
    * Same as {@link #DECIMAL_SUM} but returns with nullability if any
    * of the operands is nullable by using
@@ -563,6 +526,28 @@ public abstract class ReturnTypes {
   public static final SqlReturnTypeInference NULLABLE_SUM =
       new SqlReturnTypeInferenceChain(DECIMAL_SUM_NULLABLE, LEAST_RESTRICTIVE);
 
+  public static final SqlReturnTypeInference DECIMAL_MOD = opBinding -> {
+    RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
+    RelDataType type1 = opBinding.getOperandType(0);
+    RelDataType type2 = opBinding.getOperandType(1);
+    return typeFactory.getTypeSystem().deriveDecimalModType(typeFactory, type1, type2);
+  };
+
+  /**
+   * Type-inference strategy whereby the result type of a call is the decimal
+   * modulus of two exact numeric operands where at least one of the operands is a
+   * decimal.
+   */
+  public static final SqlReturnTypeInference DECIMAL_MOD_NULLABLE =
+          cascade(DECIMAL_MOD, SqlTypeTransforms.TO_NULLABLE);
+  /**
+   * Type-inference strategy whereby the result type of a call is
+   * {@link #DECIMAL_MOD_NULLABLE} with a fallback to {@link #ARG1_NULLABLE}
+   * These rules are used for modulus.
+   */
+  public static final SqlReturnTypeInference NULLABLE_MOD =
+          chain(DECIMAL_MOD_NULLABLE, ARG1_NULLABLE);
+
   /**
    * Type-inference strategy whereby the result type of a call is
    *
diff --git a/core/src/test/java/org/apache/calcite/sql/type/RelDataTypeSystemTest.java b/core/src/test/java/org/apache/calcite/sql/type/RelDataTypeSystemTest.java
new file mode 100644
index 0000000..2f09cc9
--- /dev/null
+++ b/core/src/test/java/org/apache/calcite/sql/type/RelDataTypeSystemTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.sql.type;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeFactory;
+import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Tests return type inference using {@code RelDataTypeSystem}
+ */
+public class RelDataTypeSystemTest {
+
+  private static final SqlTypeFixture TYPE_FIXTURE = new SqlTypeFixture();
+  private static final SqlTypeFactoryImpl TYPE_FACTORY = TYPE_FIXTURE.typeFactory;
+
+  /**
+   * Custom type system class that overrides the default decimal plus type derivation.
+   */
+  private static final class CustomTypeSystem extends RelDataTypeSystemImpl {
+
+    @Override public RelDataType deriveDecimalPlusType(RelDataTypeFactory typeFactory,
+        RelDataType type1, RelDataType type2) {
+
+      if (!SqlTypeUtil.isExactNumeric(type1)
+          && !SqlTypeUtil.isExactNumeric(type2)) {
+        return null;
+      }
+      if (!SqlTypeUtil.isDecimal(type1)
+            || !SqlTypeUtil.isDecimal(type2)) {
+        return null;
+      }
+
+      int resultScale = Math.max(type1.getScale(), type2.getScale());
+      int resultPrecision = resultScale + Math.max(type1.getPrecision() - type1.getScale(),
+              type2.getPrecision() - type2.getScale()) + 1;
+      if (resultPrecision > 38) {
+        int minScale = Math.min(resultScale, 6);
+        int delta = resultPrecision - 38;
+        resultPrecision = 38;
+        resultScale = Math.max(resultScale - delta, minScale);
+      }
+
+      return typeFactory.createSqlType(SqlTypeName.DECIMAL, resultPrecision, resultScale);
+    }
+
+    @Override public RelDataType deriveDecimalMultiplyType(RelDataTypeFactory typeFactory,
+        RelDataType type1, RelDataType type2) {
+
+      if (!SqlTypeUtil.isExactNumeric(type1)
+          && !SqlTypeUtil.isExactNumeric(type2)) {
+        return null;
+      }
+      if (!SqlTypeUtil.isDecimal(type1)
+            || !SqlTypeUtil.isDecimal(type2)) {
+        return null;
+      }
+
+      return typeFactory.createSqlType(SqlTypeName.DECIMAL,
+          type1.getPrecision() * type2.getPrecision(), type1.getScale() * type2.getScale());
+    }
+
+    @Override public RelDataType deriveDecimalDivideType(RelDataTypeFactory typeFactory,
+        RelDataType type1, RelDataType type2) {
+
+      if (!SqlTypeUtil.isExactNumeric(type1)
+          && !SqlTypeUtil.isExactNumeric(type2)) {
+        return null;
+      }
+      if (!SqlTypeUtil.isDecimal(type1)
+            || !SqlTypeUtil.isDecimal(type2)) {
+        return null;
+      }
+
+      return typeFactory.createSqlType(SqlTypeName.DECIMAL,
+          Math.abs(type1.getPrecision() - type2.getPrecision()),
+          Math.abs(type1.getScale() - type2.getScale()));
+    }
+
+    @Override public RelDataType deriveDecimalModType(RelDataTypeFactory typeFactory,
+        RelDataType type1, RelDataType type2) {
+      if (!SqlTypeUtil.isExactNumeric(type1)
+          && !SqlTypeUtil.isExactNumeric(type2)) {
+        return null;
+      }
+      if (!SqlTypeUtil.isDecimal(type1)
+            || !SqlTypeUtil.isDecimal(type2)) {
+        return null;
+      }
+
+      return type1;
+    }
+
+    @Override public int getMaxNumericPrecision() {
+      return 38;
+    }
+  }
+
+  private static final SqlTypeFactoryImpl CUSTOM_FACTORY = new SqlTypeFactoryImpl(new
+          CustomTypeSystem());
+
+  @Test
+  public void testDecimalAdditionReturnTypeInference() {
+    RelDataType operand1 = TYPE_FACTORY.createSqlType(SqlTypeName.DECIMAL, 10, 1);
+    RelDataType operand2 = TYPE_FACTORY.createSqlType(SqlTypeName.DECIMAL, 10, 2);
+
+    RelDataType dataType = SqlStdOperatorTable.MINUS.inferReturnType(TYPE_FACTORY,
+            Lists.newArrayList(operand1, operand2));
+    Assert.assertEquals(12, dataType.getPrecision());
+    Assert.assertEquals(2, dataType.getScale());
+  }
+
+  @Test
+  public void testDecimalModReturnTypeInference() {
+    RelDataType operand1 = TYPE_FACTORY.createSqlType(SqlTypeName.DECIMAL, 10, 1);
+    RelDataType operand2 = TYPE_FACTORY.createSqlType(SqlTypeName.DECIMAL, 19, 2);
+
+    RelDataType dataType = SqlStdOperatorTable.MOD.inferReturnType(TYPE_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(19, dataType.getPrecision());
+    Assert.assertEquals(2, dataType.getScale());
+  }
+
+  @Test
+  public void testDoubleModReturnTypeInference() {
+    RelDataType operand1 = TYPE_FACTORY.createSqlType(SqlTypeName.DOUBLE);
+    RelDataType operand2 = TYPE_FACTORY.createSqlType(SqlTypeName.DOUBLE);
+
+    RelDataType dataType = SqlStdOperatorTable.MOD.inferReturnType(TYPE_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(SqlTypeName.DOUBLE, dataType.getSqlTypeName());
+  }
+
+  @Test
+  public void testCustomDecimalPlusReturnTypeInference() {
+    RelDataType operand1 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 38, 10);
+    RelDataType operand2 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 38, 20);
+
+    RelDataType dataType = SqlStdOperatorTable.PLUS.inferReturnType(CUSTOM_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(SqlTypeName.DECIMAL, dataType.getSqlTypeName());
+    Assert.assertEquals(38, dataType.getPrecision());
+    Assert.assertEquals(9, dataType.getScale());
+  }
+
+  @Test
+  public void testCustomDecimalMultiplyReturnTypeInference() {
+    RelDataType operand1 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 2, 4);
+    RelDataType operand2 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 3, 5);
+
+    RelDataType dataType = SqlStdOperatorTable.MULTIPLY.inferReturnType(CUSTOM_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(SqlTypeName.DECIMAL, dataType.getSqlTypeName());
+    Assert.assertEquals(6, dataType.getPrecision());
+    Assert.assertEquals(20, dataType.getScale());
+  }
+
+  @Test
+  public void testCustomDecimalDivideReturnTypeInference() {
+    RelDataType operand1 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 28, 10);
+    RelDataType operand2 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 38, 20);
+
+    RelDataType dataType = SqlStdOperatorTable.DIVIDE.inferReturnType(CUSTOM_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(SqlTypeName.DECIMAL, dataType.getSqlTypeName());
+    Assert.assertEquals(10, dataType.getPrecision());
+    Assert.assertEquals(10, dataType.getScale());
+  }
+
+  @Test
+  public void testCustomDecimalModReturnTypeInference() {
+    RelDataType operand1 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 28, 10);
+    RelDataType operand2 = CUSTOM_FACTORY.createSqlType(SqlTypeName.DECIMAL, 38, 20);
+
+    RelDataType dataType = SqlStdOperatorTable.MOD.inferReturnType(CUSTOM_FACTORY, Lists
+            .newArrayList(operand1, operand2));
+    Assert.assertEquals(SqlTypeName.DECIMAL, dataType.getSqlTypeName());
+    Assert.assertEquals(28, dataType.getPrecision());
+    Assert.assertEquals(10, dataType.getScale());
+  }
+}
+// End RelDataTypeSystemTest.java