You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@impala.apache.org by bh...@apache.org on 2017/09/15 04:33:41 UTC

[4/4] incubator-impala git commit: IMPALA-3437: DECIMAL_V2: avoid implicit decimal->double conversion

IMPALA-3437: DECIMAL_V2: avoid implicit decimal->double conversion

This changes the behaviour of applying an arithmetic operator to
constant DECIMAL and non-DECIMAL arguments. In DECIMAL_V1, this
caused an implicit conversion to floating point, which caused
users a lot of confusion in some cases. In DECIMAL_V2 the typing
rules are simplified: constant decimals are treated the same as any
other decimals.

Testing:
Added some expression tests for different arithmetic operators
and binary predicates (the two Expr subclasses that call
convertNumericLiteralsFromDecimal()).

Extended analyzer tests to test DECIMAL_V2 behaviour. Added many
additional test for various combinations of literals and non-literals to
get better coverage of existing and new behaviour.

Ran core tests.

Change-Id: Ie419a75784eec2294947103e6e1465dfadfc29da
Reviewed-on: http://gerrit.cloudera.org:8080/7916
Reviewed-by: Tim Armstrong <ta...@cloudera.com>
Tested-by: Impala Public Jenkins


Project: http://git-wip-us.apache.org/repos/asf/incubator-impala/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-impala/commit/0a54cb5e
Tree: http://git-wip-us.apache.org/repos/asf/incubator-impala/tree/0a54cb5e
Diff: http://git-wip-us.apache.org/repos/asf/incubator-impala/diff/0a54cb5e

Branch: refs/heads/master
Commit: 0a54cb5ec0f5d1646e5fa3d211dd2b765242816e
Parents: 032dee2
Author: Tim Armstrong <ta...@cloudera.com>
Authored: Wed Aug 30 16:41:56 2017 -0700
Committer: Impala Public Jenkins <im...@gerrit.cloudera.org>
Committed: Fri Sep 15 02:30:29 2017 +0000

----------------------------------------------------------------------
 be/src/exprs/expr-test.cc                       |  41 +++
 .../java/org/apache/impala/analysis/Expr.java   |  17 +-
 .../impala/analysis/AnalyzeExprsTest.java       | 254 +++++++++++++++----
 3 files changed, 256 insertions(+), 56 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/0a54cb5e/be/src/exprs/expr-test.cc
----------------------------------------------------------------------
diff --git a/be/src/exprs/expr-test.cc b/be/src/exprs/expr-test.cc
index 3899a7a..33d77e0 100644
--- a/be/src/exprs/expr-test.cc
+++ b/be/src/exprs/expr-test.cc
@@ -2261,6 +2261,47 @@ TEST_F(ExprTest, DecimalArithmeticExprs) {
   }
 }
 
+// Tests for expressions that mix decimal and non-decimal arguments with DECIMAL_V2=false.
+TEST_F(ExprTest, DecimalV1MixedArithmeticExprs) {
+  executor_->PushExecOption("DECIMAL_V2=false");
+  // IMPALA-3437: decimal constants are implicitly converted to double.
+  TestValue("10.0 + 3", TYPE_DOUBLE, 13.0);
+  TestValue("10 + 3.0", TYPE_DOUBLE, 13.0);
+  TestValue("10.0 - 3", TYPE_DOUBLE, 7.0);
+  TestValue("10.0 * 3", TYPE_DOUBLE, 30.0);
+  TestValue("10.0 / 3", TYPE_DOUBLE, 10.0 / 3);
+  // Conversion to DOUBLE loses some precision.
+  TestValue("0.999999999999999999999999999999 = 1", TYPE_BOOLEAN, true);
+  TestValue("0.999999999999999999999999999999 != 1", TYPE_BOOLEAN, false);
+  TestValue("0.999999999999999999999999999999 < 1", TYPE_BOOLEAN, false);
+  TestValue("0.999999999999999999999999999999 >= 1", TYPE_BOOLEAN, true);
+  TestValue("0.999999999999999999999999999999 > 1", TYPE_BOOLEAN, false);
+  executor_->PopExecOption();
+}
+
+// Tests the same expressions as above with DECIMAL_V2=true.
+TEST_F(ExprTest, DecimalV2MixedArithmeticExprs) {
+  executor_->PushExecOption("DECIMAL_V2=true");
+  // IMPALA-3437: decimal constants remain decimal.
+  TestDecimalValue(
+      "10.0 + 3", Decimal4Value(130), ColumnType::CreateDecimalType(5, 1));
+  TestDecimalValue(
+      "10 + 3.0", Decimal4Value(130), ColumnType::CreateDecimalType(5, 1));
+  TestDecimalValue(
+      "10.0 - 3", Decimal4Value(70), ColumnType::CreateDecimalType(5, 1));
+  TestDecimalValue(
+      "10.0 * 3", Decimal4Value(300), ColumnType::CreateDecimalType(6, 1));
+  TestDecimalValue(
+      "10.0 / 3", Decimal4Value(3333333), ColumnType::CreateDecimalType(8, 6));
+  // Comparisons between DECIMAL values are precise.
+  TestValue("0.999999999999999999999999999999 = 1", TYPE_BOOLEAN, false);
+  TestValue("0.999999999999999999999999999999 != 1", TYPE_BOOLEAN, true);
+  TestValue("0.999999999999999999999999999999 < 1", TYPE_BOOLEAN, true);
+  TestValue("0.999999999999999999999999999999 >= 1", TYPE_BOOLEAN, false);
+  TestValue("0.999999999999999999999999999999 > 1", TYPE_BOOLEAN, false);
+  executor_->PopExecOption();
+}
+
 // There are two tests of ranges, the second of which requires a cast
 // of the second operand to a higher-resolution type.
 TEST_F(ExprTest, BinaryPredicates) {

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/0a54cb5e/fe/src/main/java/org/apache/impala/analysis/Expr.java
----------------------------------------------------------------------
diff --git a/fe/src/main/java/org/apache/impala/analysis/Expr.java b/fe/src/main/java/org/apache/impala/analysis/Expr.java
index 2b31b61..1eba24f 100644
--- a/fe/src/main/java/org/apache/impala/analysis/Expr.java
+++ b/fe/src/main/java/org/apache/impala/analysis/Expr.java
@@ -510,8 +510,12 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
   }
 
   /**
-   * Converts numeric literal in the expr tree rooted at this expr to return floating
-   * point types instead of decimals, if possible.
+   * DECIMAL_V1:
+   * ----------
+   * This function applies a heuristic that casts literal child exprs of this expr from
+   * decimal to floating point in certain circumstances to reduce processing cost. In
+   * earlier versions of Impala's decimal support, it was much slower than floating point
+   * arithmetic. The original rationale for the automatic casting follows.
    *
    * Decimal has a higher processing cost than floating point and we should not pay
    * the cost if the user does not require the accuracy. For example:
@@ -524,14 +528,19 @@ abstract public class Expr extends TreeNode<Expr> implements ParseNode, Cloneabl
    *
    * Another way to think about it is that DecimalLiterals are analyzed as returning
    * decimals (of the narrowest precision/scale) and we later convert them to a floating
-   * point type when it is consistent with the user's intent.
+   * point type according to a heuristic that attempts to guess what the user intended.
    *
-   * TODO: another option is to do constant folding in the FE and then apply this rule.
+   * DECIMAL_V2:
+   * ----------
+   * This function does nothing. All decimal numeric literals are interpreted as decimals
+   * and the normal expression typing rules apply.
    */
   protected void convertNumericLiteralsFromDecimal(Analyzer analyzer)
       throws AnalysisException {
     Preconditions.checkState(this instanceof ArithmeticExpr ||
         this instanceof BinaryPredicate);
+    // This heuristic conversion is not part of DECIMAL_V2.
+    if (analyzer.getQueryOptions().isDecimal_v2()) return;
     if (children_.size() == 1) return; // Do not attempt to convert for unary ops
     Preconditions.checkState(children_.size() == 2);
     Type t0 = getChild(0).getType();

http://git-wip-us.apache.org/repos/asf/incubator-impala/blob/0a54cb5e/fe/src/test/java/org/apache/impala/analysis/AnalyzeExprsTest.java
----------------------------------------------------------------------
diff --git a/fe/src/test/java/org/apache/impala/analysis/AnalyzeExprsTest.java b/fe/src/test/java/org/apache/impala/analysis/AnalyzeExprsTest.java
index 5d843bf..18c7263 100644
--- a/fe/src/test/java/org/apache/impala/analysis/AnalyzeExprsTest.java
+++ b/fe/src/test/java/org/apache/impala/analysis/AnalyzeExprsTest.java
@@ -104,18 +104,64 @@ public class AnalyzeExprsTest extends AnalyzerTest {
     AnalysisError(String.format("select %s1", Double.toString(Double.MIN_VALUE)),
       "Numeric literal '4.9E-3241' underflows minimum resolution of doubles.");
 
+    // Test edge cases near the upper limits of decimal precision - 38 digits.
+    // Integer literal.
+    checkDecimalReturnType("select 12345678901234567890123456789012345678",
+        ScalarType.createDecimalType(38, 0));
+    // Decimal point at front.
     testNumericLiteral("0.99999999999999999999999999999999999999",
         ScalarType.createDecimalType(38,38));
+    // Decimal point at back.
     testNumericLiteral("99999999999999999999999999999999999999.",
         ScalarType.createDecimalType(38,0));
+    // Negative values.
     testNumericLiteral("-0.99999999999999999999999999999999999999",
         ScalarType.createDecimalType(38,38));
     testNumericLiteral("-99999999999999999999999999999999999999.",
         ScalarType.createDecimalType(38,0));
+    // Decimal point in middle.
     testNumericLiteral("999999999999999999999.99999999999999999",
         ScalarType.createDecimalType(38,17));
     testNumericLiteral("-999999999999999999.99999999999999999999",
         ScalarType.createDecimalType(38,20));
+
+    // Test literals that do not fit into a decimal and are treated as DOUBLE.
+    // Edge cases that have 39 digits.
+    testNumericLiteral("123456789012345678901234567890123456789", Type.DOUBLE);
+    testNumericLiteral("123456789012345678901234567890123456789.", Type.DOUBLE);
+    testNumericLiteral("1234567890123.45678901234567890123456789", Type.DOUBLE);
+    testNumericLiteral(".123456789012345678901234567890123456789", Type.DOUBLE);
+
+    // Really huge literals are always interpreted as DOUBLE.
+    testNumericLiteral("12345678901234567890123456789012345678123455555555555555555555"
+        + "55559999999999999999999999999999999999999999999999999999999", Type.DOUBLE);
+    testNumericLiteral("1234567890123456789012345678901234567812.345555555555555555555"
+        + "555559999999999999999999999999999999999999999999999999999999", Type.DOUBLE);
+    testNumericLiteral(".1234567890123456789012345678901234567812345555555555555555555"
+        + "555559999999999999999999999999999999999999999999999999999999", Type.DOUBLE);
+  }
+
+  /**
+   * Test scientific notation typing. Values in scientific notation are expanded to
+   * decimal or integer literals and the same typing rules then apply.
+   */
+  @Test
+  public void TestScientificNumericLiterals() {
+    checkDecimalReturnType("select 1e9", Type.INT);
+    checkDecimalReturnType("select 1.123456789e9", Type.INT);
+    checkDecimalReturnType("select 1.1234567891e9",
+        ScalarType.createDecimalType(11, 1));
+    checkDecimalReturnType("select 1.1234567891e6",
+        ScalarType.createDecimalType(11, 4));
+    checkDecimalReturnType("select 9e9", Type.BIGINT);
+    checkDecimalReturnType("select 1e-2", ScalarType.createDecimalType(2, 2));
+    checkDecimalReturnType("select 1.123e-2", ScalarType.createDecimalType(5, 5));
+
+    // Scientific notation - edge cases between decimal and double.
+    checkDecimalReturnType("select 1e37", ScalarType.createDecimalType(38, 0));
+    checkDecimalReturnType("select 1.23456e37", ScalarType.createDecimalType(38, 0));
+    checkDecimalReturnType("select 1e38", Type.DOUBLE);
+    checkDecimalReturnType("select 1.23456e38", Type.DOUBLE);
   }
 
   /**
@@ -303,7 +349,8 @@ public class AnalyzeExprsTest extends AnalyzerTest {
         "Decimal precision must be > 0: 0");
 
     // IMPALA-2264: decimal is implicitly cast to lower-precision integer in edge cases.
-    checkReturnType("select CAST(999 AS DECIMAL(3,0))", ScalarType.createDecimalType(3,0));
+    checkDecimalReturnType(
+        "select CAST(999 AS DECIMAL(3,0))", ScalarType.createDecimalType(3,0));
     AnalysisError("insert into functional.alltypesinsert (tinyint_col, year, month) " +
         "values(CAST(999 AS DECIMAL(3,0)), 1, 1)",
         "Possible loss of precision for target table 'functional.alltypesinsert'.\n" +
@@ -1279,80 +1326,183 @@ public class AnalyzeExprsTest extends AnalyzerTest {
     }
   }
 
-  private void checkReturnType(String stmt, Type resultType) {
-    SelectStmt select = (SelectStmt) AnalyzesOk(stmt);
+  /**
+   * Get the result type of a select statement with a single select list element.
+   */
+  Type getReturnType(String stmt, Analyzer analyzer) {
+    SelectStmt select = (SelectStmt) AnalyzesOk(stmt, analyzer, null);
     List<Expr> selectListExprs = select.getResultExprs();
     assertNotNull(selectListExprs);
     assertEquals(selectListExprs.size(), 1);
     // check the first expr in select list
     Expr expr = selectListExprs.get(0);
-    assertEquals("Expected: " + resultType + " != " + expr.getType(),
-        resultType, expr.getType());
+    return expr.getType();
+  }
+
+  private void checkReturnType(String stmt, Type resultType, Analyzer analyzer) {
+    Type exprType = getReturnType(stmt, analyzer);
+    assertEquals("Expected: " + resultType + " != " + exprType, resultType, exprType);
+  }
+
+  private void checkReturnType(String stmt, Type resultType) {
+    checkReturnType(stmt, resultType, createAnalyzer(Catalog.DEFAULT_DB));
+  }
+
+  /**
+   * Test expressions involving decimal types that return different numeric types
+   * depending on the DECIMAL_V2 setting.
+   */
+  private void checkDecimalReturnType(String stmt, Type decimalV1ResultType,
+      Type decimalV2ResultType) {
+    Analyzer analyzer = createAnalyzer(Catalog.DEFAULT_DB);
+    analyzer.getQueryOptions().setDecimal_v2(false);
+    checkReturnType(stmt, decimalV1ResultType, analyzer);
+    analyzer = createAnalyzer(Catalog.DEFAULT_DB);
+    analyzer.getQueryOptions().setDecimal_v2(true);
+    checkReturnType(stmt, decimalV2ResultType, analyzer);
+  }
+
+  /**
+   * Test expressions that return the same type with either DECIMAL_V2 setting.
+   */
+  private void checkDecimalReturnType(String stmt, Type resultType) {
+    checkDecimalReturnType(stmt, resultType, resultType);
   }
 
   @Test
   public void TestNumericLiteralTypeResolution() throws AnalysisException {
     checkReturnType("select 1", Type.TINYINT);
-    checkReturnType("select 1.1", ScalarType.createDecimalType(2,1));
-    checkReturnType("select 01.1", ScalarType.createDecimalType(2,1));
-    checkReturnType("select 1 + 1.1", Type.DOUBLE);
-    checkReturnType("select 0.23 + 1.1", ScalarType.createDecimalType(4,2));
+    checkDecimalReturnType("select 1.1", ScalarType.createDecimalType(2,1));
+    checkDecimalReturnType("select 01.1", ScalarType.createDecimalType(2,1));
+    checkDecimalReturnType("select 1 + 1.1",
+        Type.DOUBLE, ScalarType.createDecimalType(5, 1));
+    checkDecimalReturnType("select 0.23 + 1.1", ScalarType.createDecimalType(4,2));
 
     checkReturnType("select float_col + float_col from functional.alltypestiny",
         Type.DOUBLE);
     checkReturnType("select int_col + int_col from functional.alltypestiny",
         Type.BIGINT);
 
-    // floating point + numeric literal = floating point
-    checkReturnType("select float_col + 1.1 from functional.alltypestiny",
+    // All arithmetic operators except multiplication involving floating point follow the
+    // same rules for deciding whether the output is a decimal or floating point.
+    //
+    // DECIMAL_V1: floating point + any numeric literal = floating point
+    // DECIMAL_V2: floating point + any expr of type decimal = decimal
+    checkDecimalReturnType("select float_col + 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select float_col - 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select float_col / 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 8));
+    checkDecimalReturnType("select float_col % 1.1 from functional.alltypestiny",
+        Type.FLOAT, ScalarType.createDecimalType(10, 9));
+    // BOTH: decimal + decimal literal = decimal
+    checkDecimalReturnType("select d1 + 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(11, 1));
+    checkDecimalReturnType("select d1 - 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(11, 1));
+    checkDecimalReturnType("select d1 * 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(11, 1));
+    checkDecimalReturnType("select d1 / 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(14, 4), ScalarType.createDecimalType(16, 6));
+    checkDecimalReturnType("select d1 % 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(2, 1));
+    // BOTH: decimal + int literal = decimal
+    checkDecimalReturnType("select d1 + 2 from functional.decimal_tbl",
+        ScalarType.createDecimalType(10, 0));
+    checkDecimalReturnType("select d1 - 2 from functional.decimal_tbl",
+        ScalarType.createDecimalType(10, 0));
+    checkDecimalReturnType("select d1 * 2 from functional.decimal_tbl",
+        ScalarType.createDecimalType(12, 0));
+    checkDecimalReturnType("select d1 / 2 from functional.decimal_tbl",
+        ScalarType.createDecimalType(13, 4), ScalarType.createDecimalType(15, 6));
+    checkDecimalReturnType("select d1 % 2 from functional.decimal_tbl",
+        ScalarType.createDecimalType(3, 0));
+    // DECIMAL_V1: int + numeric literal = floating point
+    // DECIMAL_V2: int + decimal expr = decimal
+    checkDecimalReturnType("select int_col + 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(12, 1));
+    checkDecimalReturnType("select int_col - 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(12, 1));
+    checkDecimalReturnType("select int_col * 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(12, 1));
+    checkDecimalReturnType("select int_col / 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(17, 6));
+    checkDecimalReturnType("select int_col % 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(2, 1));
+
+    // Multiplying a floating point type with any other type always results in a double.
+    checkDecimalReturnType("select float_col * d1 from functional.alltypestiny " +
+         "join functional.decimal_tbl", Type.DOUBLE);
+    checkDecimalReturnType("select d1 * float_col from functional.alltypestiny " +
+         "join functional.decimal_tbl", Type.DOUBLE);
+    checkDecimalReturnType("select double_col * d1 from functional.alltypestiny " +
+         "join functional.decimal_tbl", Type.DOUBLE);
+    checkDecimalReturnType("select float_col * 10 from functional.alltypestiny",
         Type.DOUBLE);
-    // decimal + numeric literal = decimal
-    checkReturnType("select d1 + 1.1 from functional.decimal_tbl",
-        ScalarType.createDecimalType(11,1));
-    // int + numeric literal = floating point
-    checkReturnType("select int_col + 1.1 from functional.alltypestiny",
+    checkDecimalReturnType("select 10 * float_col from functional.alltypestiny",
         Type.DOUBLE);
-
-    // IMPALA-3439: Non-trivial constant expression with decimal + double = double.
-    // Tests that only the decimal literals in the constant decimal expr are cast
-    // to double. The second argument of round() must be an integer.
-    checkReturnType("select round(1.2345, 2) * pow(10, 10)", Type.DOUBLE);
-
-    // Explicitly casting the literal to a decimal will override the behavior
-    checkReturnType("select int_col + cast(1.1 as decimal(2,1)) from "
-        + " functional.alltypestiny", ScalarType.createDecimalType(12,1));
-    checkReturnType("select float_col + cast(1.1 as decimal(2,1)) from "
-        + " functional.alltypestiny", ScalarType.createDecimalType(38,9));
-    checkReturnType("select float_col + cast(1.1*1.2+1.3 as decimal(2,1)) from "
-        + " functional.alltypestiny", ScalarType.createDecimalType(38,9));
-
-    // The location and complexity of the expr should not matter.
-    checkReturnType("select 1.0 + float_col + 1.1 from functional.alltypestiny",
+    checkDecimalReturnType("select double_col * 10 from functional.alltypestiny",
         Type.DOUBLE);
-    checkReturnType("select 1.0 + 2.0 + float_col from functional.alltypestiny",
+    checkDecimalReturnType("select float_col * 10.0 from functional.alltypestiny",
         Type.DOUBLE);
-    checkReturnType("select 1.0 + 2.0 + pi() * float_col from functional.alltypestiny",
+    checkDecimalReturnType("select 10.0 * float_col from functional.alltypestiny",
         Type.DOUBLE);
-    checkReturnType("select 1.0 + d1 + 1.1 from functional.decimal_tbl",
-        ScalarType.createDecimalType(12,1));
-    checkReturnType("select 1.0 + 2.0 + d1 from functional.decimal_tbl",
-        ScalarType.createDecimalType(11,1));
-    checkReturnType("select 1.0 + 2.0 + pi()*d1 from functional.decimal_tbl",
+    checkDecimalReturnType("select double_col * 10.0 from functional.alltypestiny",
         Type.DOUBLE);
 
-    // Test with multiple cols
-    checkReturnType("select double_col + 1.23 + float_col + 1.0 " +
-        " from functional.alltypestiny", Type.DOUBLE);
-    checkReturnType("select double_col + 1.23 + float_col + 1.0 + int_col " +
-        " + bigint_col from functional.alltypestiny", Type.DOUBLE);
-    checkReturnType("select d1 + 1.23 + d2 + 1.0 " +
-        " from functional.decimal_tbl", ScalarType.createDecimalType(14,2));
-
-    // Test with slot of both decimal and non-decimal
-    checkReturnType("select t1.int_col + t2.c1 from functional.alltypestiny t1 " +
-        " cross join functional.decimal_tiny t2", ScalarType.createDecimalType(15,4));
-    checkReturnType("select 1.1 + t1.int_col + t2.c1 from functional.alltypestiny t1 " +
-        " cross join functional.decimal_tiny t2", ScalarType.createDecimalType(38,17));
+    // IMPALA-3439: constant expression involving a function with mixed decimal and
+    // integer inputs. Regression tests to make sure that only the decimal literals
+    // in the constant decimal expr are cast to double. The second argument of round()
+    // must be an integer.
+    checkDecimalReturnType("select round(1.2345, 2) * pow(10, 10)", Type.DOUBLE);
+    checkDecimalReturnType("select round(1.2345, 2) + pow(10, 10)",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 17));
+
+    // Explicitly casting the literal to a decimal or float changes the type of the
+    // literal. This is independent of the DECIMAL_V2 setting.
+    checkDecimalReturnType("select int_col + cast(1.1 as decimal(2,1)) from "
+        + " functional.alltypestiny", ScalarType.createDecimalType(12, 1));
+    checkDecimalReturnType("select float_col + cast(1.1 as decimal(2,1)) from "
+        + " functional.alltypestiny", ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select float_col + cast(1.1*1.2+1.3 as decimal(2,1)) from "
+        + " functional.alltypestiny", ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select float_col + cast(1.1 as float) from "
+        + " functional.alltypestiny", Type.DOUBLE);
+    checkDecimalReturnType("select float_col + cast(1.1 as float) from "
+        + " functional.alltypestiny", Type.DOUBLE);
+    checkDecimalReturnType("select float_col + cast(1.1 as double) from "
+        + " functional.alltypestiny", Type.DOUBLE);
+
+    // Test behavior of compound expressions with a single slot ref and many literals.
+    checkDecimalReturnType("select 1.0 + float_col + 1.1 from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select 1.0 + 2.0 + float_col from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 9));
+    checkDecimalReturnType("select 1.0 + 2.0 + pi() * float_col from functional.alltypestiny",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 17));
+    checkDecimalReturnType("select 1.0 + d1 + 1.1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(12, 1));
+    checkDecimalReturnType("select 1.0 + 2.0 + d1 from functional.decimal_tbl",
+        ScalarType.createDecimalType(11, 1));
+    checkDecimalReturnType("select 1.0 + 2.0 + pi() * d1 from functional.decimal_tbl",
+        Type.DOUBLE, ScalarType.createDecimalType(38, 17));
+
+    // Test behavior of compound expressions with multiple slot refs and literals.
+    checkDecimalReturnType("select double_col + 1.23 + float_col + 1.0 " +
+        " from functional.alltypestiny", Type.DOUBLE, ScalarType.createDecimalType(38, 17));
+    checkDecimalReturnType("select double_col + 1.23 + float_col + 1.0 + int_col " +
+        " + bigint_col from functional.alltypestiny", Type.DOUBLE,
+        ScalarType.createDecimalType(38, 17));
+    checkDecimalReturnType("select d1 + 1.23 + d2 + 1.0 " +
+        " from functional.decimal_tbl", ScalarType.createDecimalType(14, 2));
+
+    // Test with slot refs of both decimal and non-decimal
+    checkDecimalReturnType("select t1.int_col + t2.c1 from functional.alltypestiny t1 " +
+        " cross join functional.decimal_tiny t2", ScalarType.createDecimalType(15, 4));
+    checkDecimalReturnType("select 1.1 + t1.int_col + t2.c1 from functional.alltypestiny t1 " +
+        " cross join functional.decimal_tiny t2", ScalarType.createDecimalType(38, 17),
+        ScalarType.createDecimalType(16, 4));
   }
 
   /**