You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@drill.apache.org by dz...@apache.org on 2022/09/20 04:43:02 UTC

[drill] branch master updated: DRILL-8136: Overhaul implict type casting logic (#2638)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new ab7f9e999d DRILL-8136: Overhaul implict type casting logic (#2638)
ab7f9e999d is described below

commit ab7f9e999d97eff7565055aa190d54bccdd6b6b6
Author: James Turton <91...@users.noreply.github.com>
AuthorDate: Tue Sep 20 06:42:53 2022 +0200

    DRILL-8136: Overhaul implict type casting logic (#2638)
---
 .../src/main/codegen/data/NumericTypes.tdd         |   6 +
 .../templates/NumericFunctionsTemplates.java       |   6 +
 .../exec/expr/ExpressionTreeMaterializer.java      |   3 +-
 .../exec/expr/fn/impl/ByteArrayFunctions.java      | 102 ++++++
 .../drill/exec/expr/fn/impl/ByteSubstring.java     |  81 -----
 .../drill/exec/expr/fn/impl/SchemaFunctions.java   |   6 +-
 .../drill/exec/physical/impl/join/JoinUtils.java   |   5 +-
 .../physical/impl/union/UnionAllRecordBatch.java   |  17 +-
 .../drill/exec/planner/common/DrillRelOptUtil.java |  10 +-
 .../sql/DrillCalciteSqlBetweenOperatorWrapper.java |   5 +-
 .../drill/exec/planner/sql/TypeInferenceUtils.java | 149 +++++----
 .../exec/resolver/DefaultFunctionResolver.java     |  58 ++--
 .../drill/exec/resolver/ExactFunctionResolver.java |   4 +-
 .../exec/resolver/ResolverTypePrecedence.java      | 345 +++++++++++++--------
 .../apache/drill/exec/resolver/TypeCastRules.java  | 182 +++++------
 .../store/parquet/ParquetTableMetadataUtils.java   |   6 +-
 .../java/org/apache/drill/TestImplicitCasting.java | 217 ++++++++++++-
 .../physical/impl/TestReverseImplicitCast.java     |   4 +-
 .../drill/exec/store/json/TestJsonReaderFns.java   |  20 +-
 .../functions/cast/two_way_implicit_cast.json      |   4 +-
 .../org/apache/drill/common/types/TypeProtos.java  |   4 +-
 protocol/src/main/protobuf/Types.proto             |   2 +-
 22 files changed, 769 insertions(+), 467 deletions(-)

diff --git a/exec/java-exec/src/main/codegen/data/NumericTypes.tdd b/exec/java-exec/src/main/codegen/data/NumericTypes.tdd
index 6b28c1a1f0..8cca678df6 100644
--- a/exec/java-exec/src/main/codegen/data/NumericTypes.tdd
+++ b/exec/java-exec/src/main/codegen/data/NumericTypes.tdd
@@ -37,6 +37,12 @@
     {inputType: "NullableUInt4", intype: "numeric"},
     {inputType: "UInt8", intype: "numeric"},
     {inputType: "NullableUInt8", intype: "numeric"},
+    {inputType: "Float4", intype: "numeric"},
+    {inputType: "NullableFloat4", intype: "numeric"},
+    {inputType: "Float8", intype: "numeric"},
+    {inputType: "NullableFloat8", intype: "numeric"},
+    {inputType: "VarDecimal", intype: "numeric"},
+    {inputType: "NullableVarDecimal", intype: "numeric"},
     {inputType: "VarChar", intype: "char"},
     {inputType: "NullableVarChar", intype: "char"}
   ]
diff --git a/exec/java-exec/src/main/codegen/templates/NumericFunctionsTemplates.java b/exec/java-exec/src/main/codegen/templates/NumericFunctionsTemplates.java
index b26e9eee18..5ad467ab0a 100644
--- a/exec/java-exec/src/main/codegen/templates/NumericFunctionsTemplates.java
+++ b/exec/java-exec/src/main/codegen/templates/NumericFunctionsTemplates.java
@@ -55,6 +55,12 @@ import org.apache.drill.exec.expr.holders.UInt4Holder;
 import org.apache.drill.exec.expr.holders.NullableUInt4Holder;
 import org.apache.drill.exec.expr.holders.UInt8Holder;
 import org.apache.drill.exec.expr.holders.NullableUInt8Holder;
+import org.apache.drill.exec.expr.holders.Float4Holder;
+import org.apache.drill.exec.expr.holders.NullableFloat4Holder;
+import org.apache.drill.exec.expr.holders.Float8Holder;
+import org.apache.drill.exec.expr.holders.NullableFloat8Holder;
+import org.apache.drill.exec.expr.holders.VarDecimalHolder;
+import org.apache.drill.exec.expr.holders.NullableVarDecimalHolder;
 import org.apache.drill.exec.expr.holders.VarCharHolder;
 import org.apache.drill.exec.expr.holders.NullableVarCharHolder;
 import org.apache.drill.exec.record.RecordBatch;
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/ExpressionTreeMaterializer.java b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/ExpressionTreeMaterializer.java
index b242feec6d..c5b3443ba2 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/ExpressionTreeMaterializer.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/ExpressionTreeMaterializer.java
@@ -19,7 +19,6 @@ package org.apache.drill.exec.expr;
 
 import java.util.ArrayDeque;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.List;
@@ -721,7 +720,7 @@ public class ExpressionTreeMaterializer {
         // Check if we need a cast
         if (thenType != elseType && !(thenType == MinorType.NULL || elseType == MinorType.NULL)) {
 
-          MinorType leastRestrictive = TypeCastRules.getLeastRestrictiveType((Arrays.asList(thenType, elseType)));
+          MinorType leastRestrictive = TypeCastRules.getLeastRestrictiveType(thenType, elseType);
           if (leastRestrictive != thenType) {
             // Implicitly cast the then expression
             conditions = new IfExpression.IfCondition(newCondition,
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteArrayFunctions.java b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteArrayFunctions.java
new file mode 100644
index 0000000000..0142fc33ed
--- /dev/null
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteArrayFunctions.java
@@ -0,0 +1,102 @@
+/*
+ * 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.drill.exec.expr.fn.impl;
+
+import org.apache.drill.exec.expr.DrillSimpleFunc;
+import org.apache.drill.exec.expr.annotations.FunctionTemplate;
+import org.apache.drill.exec.expr.annotations.Output;
+import org.apache.drill.exec.expr.annotations.Param;
+import org.apache.drill.exec.expr.holders.BigIntHolder;
+import org.apache.drill.exec.expr.holders.VarBinaryHolder;
+
+public class ByteArrayFunctions {
+  private ByteArrayFunctions() {
+  }
+
+  // TODO: implement optional length parameter
+  /**
+   * Evaluate a substring expression for a given value; specifying the start
+   * position, and optionally the end position.
+   *
+   *  - If the start position is negative, start from abs(start) characters from
+   *    the end of the buffer.
+   *
+   *  - If no length is specified, continue to the end of the string.
+   *
+   *  - If the substring expression's length exceeds the value's upward bound, the
+   *    value's length will be used.
+   *
+   *  - If the substring is invalid, return an empty string.
+   */
+  @FunctionTemplate(names = {"bytesubstring", "byte_substr"},
+                    scope = FunctionTemplate.FunctionScope.SIMPLE,
+                    nulls = FunctionTemplate.NullHandling.NULL_IF_NULL,
+                    outputWidthCalculatorType = FunctionTemplate.OutputWidthCalculatorType.CUSTOM_CLONE_DEFAULT)
+  public static class ByteSubstring implements DrillSimpleFunc {
+
+    @Param VarBinaryHolder in;
+    @Param BigIntHolder offset;
+    @Param BigIntHolder length;
+    @Output VarBinaryHolder out;
+
+    @Override
+    public void setup() { }
+
+    @Override
+    public void eval() {
+      out.buffer = in.buffer;
+
+      // handle invalid values; e.g. SUBSTRING(value, 0, x) or SUBSTRING(value, x, 0)
+      if (offset.value == 0 || length.value <= 0) {
+        out.start = 0;
+        out.end = 0;
+      } else {
+        // handle negative and positive offset values
+        if (offset.value < 0) {
+          out.start = in.end + (int)offset.value;
+        } else {
+          out.start = in.start + (int)offset.value - 1;
+        }
+        // calculate end position from length and truncate to upper value bounds
+        if (out.start + length.value > in.end) {
+          out.end = in.end;
+        } else {
+          out.end = out.start + (int)length.value;
+        }
+      }
+    }
+  }
+
+  @FunctionTemplate(
+    name = "length",
+    scope = FunctionTemplate.FunctionScope.SIMPLE,
+    nulls = FunctionTemplate.NullHandling.NULL_IF_NULL
+  )
+  public static class ByteArrLength implements DrillSimpleFunc {
+    @Param  VarBinaryHolder input;
+    @Output BigIntHolder out;
+
+    @Override
+    public void setup() {}
+
+    @Override
+    public void eval() {
+      out.value = input.end - input.start;
+    }
+  }
+}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteSubstring.java b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteSubstring.java
deleted file mode 100644
index e1d02584bf..0000000000
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/ByteSubstring.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.drill.exec.expr.fn.impl;
-
-import org.apache.drill.exec.expr.DrillSimpleFunc;
-import org.apache.drill.exec.expr.annotations.FunctionTemplate;
-import org.apache.drill.exec.expr.annotations.Output;
-import org.apache.drill.exec.expr.annotations.Param;
-import org.apache.drill.exec.expr.holders.BigIntHolder;
-import org.apache.drill.exec.expr.holders.VarBinaryHolder;
-
-// TODO: implement optional length parameter
-
-/**
- * Evaluate a substring expression for a given value; specifying the start
- * position, and optionally the end position.
- *
- *  - If the start position is negative, start from abs(start) characters from
- *    the end of the buffer.
- *
- *  - If no length is specified, continue to the end of the string.
- *
- *  - If the substring expression's length exceeds the value's upward bound, the
- *    value's length will be used.
- *
- *  - If the substring is invalid, return an empty string.
- */
-@FunctionTemplate(names = {"bytesubstring", "byte_substr"},
-                  scope = FunctionTemplate.FunctionScope.SIMPLE,
-                  nulls = FunctionTemplate.NullHandling.NULL_IF_NULL,
-                  outputWidthCalculatorType = FunctionTemplate.OutputWidthCalculatorType.CUSTOM_CLONE_DEFAULT)
-public class ByteSubstring implements DrillSimpleFunc {
-
-  @Param VarBinaryHolder in;
-  @Param BigIntHolder offset;
-  @Param BigIntHolder length;
-  @Output VarBinaryHolder out;
-
-  @Override
-  public void setup() { }
-
-  @Override
-  public void eval() {
-    out.buffer = in.buffer;
-
-    // handle invalid values; e.g. SUBSTRING(value, 0, x) or SUBSTRING(value, x, 0)
-    if (offset.value == 0 || length.value <= 0) {
-      out.start = 0;
-      out.end = 0;
-    } else {
-      // handle negative and positive offset values
-      if (offset.value < 0) {
-        out.start = in.end + (int)offset.value;
-      } else {
-        out.start = in.start + (int)offset.value - 1;
-      }
-      // calculate end position from length and truncate to upper value bounds
-      if (out.start + length.value > in.end) {
-        out.end = in.end;
-      } else {
-        out.end = out.start + (int)length.value;
-      }
-    }
-  }
-
-}
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/SchemaFunctions.java b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/SchemaFunctions.java
index 7f747bb6c3..bfcbd2d183 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/SchemaFunctions.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/expr/fn/impl/SchemaFunctions.java
@@ -72,10 +72,12 @@ public class SchemaFunctions {
         if (materializedField != null && !materializedField.getType().equals(type)) {
           org.apache.drill.common.types.TypeProtos.MinorType leastRestrictiveType =
               org.apache.drill.exec.resolver.TypeCastRules.getLeastRestrictiveType(
-                  java.util.Arrays.asList(materializedField.getType().getMinorType(), type.getMinorType()));
+                  materializedField.getType().getMinorType(), type.getMinorType());
           org.apache.drill.common.types.TypeProtos.DataMode leastRestrictiveMode =
               org.apache.drill.exec.resolver.TypeCastRules.getLeastRestrictiveDataMode(
-                  java.util.Arrays.asList(materializedField.getType().getMode(), type.getMode()));
+                materializedField.getType().getMode(),
+                type.getMode()
+              );
 
           org.apache.drill.exec.record.MaterializedField clone = materializedField.clone();
           clone.replaceType(materializedField.getType().toBuilder()
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/join/JoinUtils.java b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/join/JoinUtils.java
index fc5ca527e3..5cb279d864 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/join/JoinUtils.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/join/JoinUtils.java
@@ -212,10 +212,7 @@ public class JoinUtils {
         }
 
         // We need to add a cast to one of the expressions
-        List<TypeProtos.MinorType> types = new LinkedList<>();
-        types.add(rightType);
-        types.add(leftType);
-        TypeProtos.MinorType result = TypeCastRules.getLeastRestrictiveType(types);
+        TypeProtos.MinorType result = TypeCastRules.getLeastRestrictiveType(leftType, rightType);
         ErrorCollector errorCollector = new ErrorCollectorImpl();
 
         if (result == null) {
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/union/UnionAllRecordBatch.java b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/union/UnionAllRecordBatch.java
index c8b9879d1f..07d916d586 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/union/UnionAllRecordBatch.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/physical/impl/union/UnionAllRecordBatch.java
@@ -60,7 +60,6 @@ import org.apache.drill.exec.vector.FixedWidthVector;
 import org.apache.drill.exec.vector.SchemaChangeCallBack;
 import org.apache.drill.exec.vector.ValueVector;
 import org.apache.drill.shaded.guava.com.google.common.base.Preconditions;
-import org.apache.drill.shaded.guava.com.google.common.collect.Lists;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -302,10 +301,10 @@ public class UnionAllRecordBatch extends AbstractBinaryRecordBatch<UnionAll> {
           builder.setMinorType(leftField.getType().getMinorType());
           builder = Types.calculateTypePrecisionAndScale(leftField.getType(), rightField.getType(), builder);
         } else {
-          List<TypeProtos.MinorType> types = Lists.newLinkedList();
-          types.add(leftField.getType().getMinorType());
-          types.add(rightField.getType().getMinorType());
-          TypeProtos.MinorType outputMinorType = TypeCastRules.getLeastRestrictiveType(types);
+          TypeProtos.MinorType outputMinorType = TypeCastRules.getLeastRestrictiveType(
+            leftField.getType().getMinorType(),
+            rightField.getType().getMinorType()
+          );
           if (outputMinorType == null) {
             throw new DrillRuntimeException("Type mismatch between " + leftField.getType().getMinorType().toString() +
                 " on the left side and " + rightField.getType().getMinorType().toString() +
@@ -315,10 +314,10 @@ public class UnionAllRecordBatch extends AbstractBinaryRecordBatch<UnionAll> {
         }
 
         // The output data mode should be as flexible as the more flexible one from the two input tables
-        List<TypeProtos.DataMode> dataModes = Lists.newLinkedList();
-        dataModes.add(leftField.getType().getMode());
-        dataModes.add(rightField.getType().getMode());
-        builder.setMode(TypeCastRules.getLeastRestrictiveDataMode(dataModes));
+        builder.setMode(TypeCastRules.getLeastRestrictiveDataMode(
+          leftField.getType().getMode(),
+          rightField.getType().getMode()
+        ));
 
         container.addOrGet(MaterializedField.create(leftField.getName(), builder.build()), callBack);
       }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/common/DrillRelOptUtil.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/common/DrillRelOptUtil.java
index bf1fd6f789..134554ce41 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/common/DrillRelOptUtil.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/common/DrillRelOptUtil.java
@@ -56,7 +56,6 @@ import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 import org.apache.drill.common.expression.PathSegment;
 import org.apache.drill.common.expression.SchemaPath;
-import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.common.types.Types;
 import org.apache.drill.exec.planner.logical.DrillRelFactories;
 import org.apache.drill.exec.planner.logical.DrillTable;
@@ -67,7 +66,6 @@ import org.apache.drill.exec.resolver.TypeCastRules;
 import org.apache.drill.exec.util.Utilities;
 import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableList;
 import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableMap;
-import org.apache.drill.shaded.guava.com.google.common.collect.Lists;
 import org.apache.drill.shaded.guava.com.google.common.collect.Sets;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -118,10 +116,10 @@ public abstract class DrillRelOptUtil {
         }
 
         // Check if Drill implicit casting can resolve the incompatibility
-        List<TypeProtos.MinorType> types = Lists.newArrayListWithCapacity(2);
-        types.add(Types.getMinorTypeFromName(type1.getSqlTypeName().getName()));
-        types.add(Types.getMinorTypeFromName(type2.getSqlTypeName().getName()));
-        return TypeCastRules.getLeastRestrictiveType(types) != null;
+        return TypeCastRules.getLeastRestrictiveType(
+          Types.getMinorTypeFromName(type1.getSqlTypeName().getName()),
+          Types.getMinorTypeFromName(type2.getSqlTypeName().getName())
+        ) != null;
       }
     }
     return true;
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DrillCalciteSqlBetweenOperatorWrapper.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DrillCalciteSqlBetweenOperatorWrapper.java
index f112e57908..6252405d54 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DrillCalciteSqlBetweenOperatorWrapper.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/DrillCalciteSqlBetweenOperatorWrapper.java
@@ -67,7 +67,10 @@ public class DrillCalciteSqlBetweenOperatorWrapper extends SqlBetweenOperator im
       types.add(inMinorType);
     }
 
-    final boolean isCompatible = TypeCastRules.getLeastRestrictiveType(types) != null;
+    final boolean isCompatible = TypeCastRules.getLeastRestrictiveType(
+      types.toArray(new TypeProtos.MinorType[0])
+    ) != null;
+
     if (!isCompatible && throwOnFailure) {
       throw callBinding.newValidationSignatureError();
     }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
index ced54a49b8..44e46bde45 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/planner/sql/TypeInferenceUtils.java
@@ -56,6 +56,7 @@ import org.apache.drill.exec.resolver.FunctionResolverFactory;
 import org.apache.drill.exec.resolver.TypeCastRules;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 
 @SuppressWarnings("WeakerAccess")
@@ -447,47 +448,57 @@ public class TypeInferenceUtils {
       }
 
       // Determines SqlTypeName of the result.
-      // For the case when input may be implicitly casted to BIGINT, the type of result is BIGINT.
-      // Else for the case when input may be implicitly casted to FLOAT4, the type of result is DOUBLE.
-      // Else for the case when input may be implicitly casted to VARDECIMAL, the type of result is DECIMAL
-      // with the same scale as input and max allowed numeric precision.
-      // Else for the case when input may be implicitly casted to FLOAT8, the type of result is DOUBLE.
-      // When none of these conditions is satisfied, error is thrown.
-      // This order of checks is caused by the order of types in ResolverTypePrecedence.precedenceMap
+      // When the cheapest cast is is to
+      //  - BIGINT then the result is a BIGINT
+      //  - FLOAT8 then the result is DOUBLE
+      //  - VARDECIMAL then the result is a DECIMAL with the same scale as the input and
+      //    the max allowed numeric precision.
+      // When none of these conditions are satisfied an error is thrown.
       final RelDataType operandType = opBinding.getOperandType(0);
       final TypeProtos.MinorType inputMinorType = getDrillTypeFromCalciteType(operandType);
-      if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.BIGINT))
-          == TypeProtos.MinorType.BIGINT) {
-        return createCalciteTypeWithNullability(
-            factory,
-            SqlTypeName.BIGINT,
-            isNullable);
-      } else if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.FLOAT4))
-          == TypeProtos.MinorType.FLOAT4) {
-        return createCalciteTypeWithNullability(
-            factory,
-            SqlTypeName.DOUBLE,
-            isNullable);
-      } else if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.VARDECIMAL))
-          == TypeProtos.MinorType.VARDECIMAL) {
-        RelDataType sqlType = factory.createSqlType(SqlTypeName.DECIMAL,
-          DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericPrecision(),
-          Math.min(operandType.getScale(),
-            DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericScale()));
-        return factory.createTypeWithNullability(sqlType, isNullable);
-      } else if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.FLOAT8))
-          == TypeProtos.MinorType.FLOAT8) {
-        return createCalciteTypeWithNullability(
-            factory,
-            SqlTypeName.DOUBLE,
-            isNullable);
-      } else {
+
+      Optional<TypeProtos.MinorType> targetType = TypeCastRules.getCheapestCast(
+        inputMinorType,
+        TypeProtos.MinorType.BIGINT,
+        TypeProtos.MinorType.FLOAT8,
+        TypeProtos.MinorType.VARDECIMAL
+      );
+
+      if (!targetType.isPresent()) {
         throw UserException
-            .functionError()
-            .message(String.format("%s does not support operand types (%s)",
-                opBinding.getOperator().getName(),
-                opBinding.getOperandType(0).getSqlTypeName()))
-            .build(logger);
+          .functionError()
+          .message(String.format("%s does not support operand types (%s)",
+            opBinding.getOperator().getName(),
+            opBinding.getOperandType(0).getSqlTypeName()))
+          .build(logger);
+      }
+
+      switch (targetType.get()) {
+        case BIGINT: return createCalciteTypeWithNullability(
+          factory,
+          SqlTypeName.BIGINT,
+          isNullable
+        );
+        case FLOAT8: return createCalciteTypeWithNullability(
+          factory,
+          SqlTypeName.DOUBLE,
+          isNullable
+        );
+        case VARDECIMAL:
+          RelDataType sqlType = factory.createSqlType(
+            SqlTypeName.DECIMAL,
+            DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericPrecision(),
+            Math.min(
+              operandType.getScale(),
+              DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericScale()
+            )
+          );
+          return factory.createTypeWithNullability(sqlType, isNullable);
+        default:
+          throw new IllegalStateException(String.format(
+            "Internal error: a minor type of %s should not occur here.",
+            targetType.get()
+          ));
       }
     }
   }
@@ -853,34 +864,21 @@ public class TypeInferenceUtils {
       }
 
       // Determines SqlTypeName of the result.
-      // For the case when input may be implicitly casted to FLOAT4, the type of result is DOUBLE.
-      // Else for the case when input may be implicitly casted to VARDECIMAL, the type of result is DECIMAL
-      // with scale max(6, input) and max allowed numeric precision.
-      // Else for the case when input may be implicitly casted to FLOAT8, the type of result is DOUBLE.
-      // When none of these conditions is satisfied, error is thrown.
-      // This order of checks is caused by the order of types in ResolverTypePrecedence.precedenceMap
+      // When the cheapest cast is to
+      //  - FLOAT8 then the result is a DOUBLE
+      //  - VARDECIMAL then the result is a DECIMAL with scale max(6, input) and
+      //    the max allowed numeric precision.
+      // When none of these conditions are satisfied an error is thrown.
       final RelDataType operandType = opBinding.getOperandType(0);
       final TypeProtos.MinorType inputMinorType = getDrillTypeFromCalciteType(operandType);
-      if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.FLOAT4))
-          == TypeProtos.MinorType.FLOAT4) {
-        return createCalciteTypeWithNullability(
-            factory,
-            SqlTypeName.DOUBLE,
-            isNullable);
-      } else if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.VARDECIMAL))
-          == TypeProtos.MinorType.VARDECIMAL) {
-        RelDataType sqlType = factory.createSqlType(SqlTypeName.DECIMAL,
-            DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericPrecision(),
-            Math.min(Math.max(6, operandType.getScale()),
-                DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericScale()));
-        return factory.createTypeWithNullability(sqlType, isNullable);
-      } else if (TypeCastRules.getLeastRestrictiveType(Lists.newArrayList(inputMinorType, TypeProtos.MinorType.FLOAT8))
-          == TypeProtos.MinorType.FLOAT8) {
-        return createCalciteTypeWithNullability(
-            factory,
-            SqlTypeName.DOUBLE,
-            isNullable);
-      } else {
+
+      Optional<TypeProtos.MinorType> targetType = TypeCastRules.getCheapestCast(
+        inputMinorType,
+        TypeProtos.MinorType.FLOAT8,
+        TypeProtos.MinorType.VARDECIMAL
+      );
+
+      if (!targetType.isPresent()) {
         throw UserException
             .functionError()
             .message(String.format("%s does not support operand types (%s)",
@@ -888,6 +886,29 @@ public class TypeInferenceUtils {
                 opBinding.getOperandType(0).getSqlTypeName()))
             .build(logger);
       }
+
+      switch (targetType.get()) {
+        case FLOAT8: return createCalciteTypeWithNullability(
+          factory,
+          SqlTypeName.DOUBLE,
+          isNullable
+        );
+        case VARDECIMAL:
+          RelDataType sqlType = factory.createSqlType(
+            SqlTypeName.DECIMAL,
+            DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericPrecision(),
+            Math.min(
+              Math.max(6, operandType.getScale()),
+              DrillRelDataTypeSystem.DRILL_REL_DATATYPE_SYSTEM.getMaxNumericScale()
+            )
+          );
+          return factory.createTypeWithNullability(sqlType, isNullable);
+        default:
+          throw new IllegalStateException(String.format(
+            "Internal error: a minor type of %s should not occur here.",
+            targetType.get()
+          ));
+      }
     }
   }
 
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/DefaultFunctionResolver.java b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/DefaultFunctionResolver.java
index ae3c68b767..66ddd8c99a 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/DefaultFunctionResolver.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/DefaultFunctionResolver.java
@@ -20,11 +20,12 @@ package org.apache.drill.exec.resolver;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.stream.Collectors;
+
+import org.apache.drill.common.exceptions.UserException;
 import org.apache.drill.common.expression.FunctionCall;
 import org.apache.drill.common.expression.LogicalExpression;
 import org.apache.drill.common.types.TypeProtos;
 import org.apache.drill.exec.expr.fn.DrillFuncHolder;
-import org.apache.drill.exec.util.AssertionUtil;
 
 public class DefaultFunctionResolver implements FunctionResolver {
 
@@ -33,52 +34,49 @@ public class DefaultFunctionResolver implements FunctionResolver {
   @Override
   public DrillFuncHolder getBestMatch(List<DrillFuncHolder> methods, FunctionCall call) {
 
-    int bestcost = Integer.MAX_VALUE;
-    int currcost = Integer.MAX_VALUE;
-    DrillFuncHolder bestmatch = null;
+    float bestCost = Float.POSITIVE_INFINITY, currCost = Float.POSITIVE_INFINITY;
+    DrillFuncHolder bestMatch = null;
     final List<DrillFuncHolder> bestMatchAlternatives = new LinkedList<>();
     List<TypeProtos.MajorType> argumentTypes = call.args().stream()
             .map(LogicalExpression::getMajorType)
             .collect(Collectors.toList());
-    for (DrillFuncHolder h : methods) {
-      currcost = TypeCastRules.getCost(argumentTypes, h);
 
-      // if cost is lower than 0, func implementation is not matched, either w/ or w/o implicit casts
-      if (currcost  < 0 ) {
-        continue;
-      }
+    for (DrillFuncHolder h : methods) {
+      currCost = TypeCastRules.getCost(argumentTypes, h);
 
-      if (currcost < bestcost) {
-        bestcost = currcost;
-        bestmatch = h;
+      if (currCost < bestCost) {
+        bestCost = currCost;
+        bestMatch = h;
         bestMatchAlternatives.clear();
-      } else if (currcost == bestcost) {
+      } else if (currCost == bestCost && currCost < Float.POSITIVE_INFINITY) {
         // keep log of different function implementations that have the same best cost
         bestMatchAlternatives.add(h);
       }
     }
 
-    if (bestcost < 0) {
+    if (bestCost == Float.POSITIVE_INFINITY) {
       //did not find a matched func implementation, either w/ or w/o implicit casts
       //TODO: raise exception here?
       return null;
-    } else {
-      if (AssertionUtil.isAssertionsEnabled() && bestMatchAlternatives.size() > 0) {
-        /*
-         * There are other alternatives to the best match function which could have been selected
-         * Log the possible functions and the chose implementation and raise an exception
-         */
-        logger.error("Chosen function impl: " + bestmatch.toString());
-
-        // printing the possible matches
-        logger.error("Printing all the possible functions that could have matched: ");
-        for (DrillFuncHolder holder: bestMatchAlternatives) {
-          logger.error(holder.toString());
-        }
+    }
+    if (bestMatchAlternatives.size() > 0) {
+      logger.info("Multiple functions with best cost found, query processing will be aborted.");
 
-        throw new AssertionError("Multiple functions with best cost found");
+      // printing the possible matches
+      logger.debug("Printing all the possible functions that could have matched: ");
+      for (DrillFuncHolder holder : bestMatchAlternatives) {
+        logger.debug(holder.toString());
       }
-      return bestmatch;
+
+      throw UserException.functionError()
+        .message(
+          "There are %d function definitions with the same casting cost for " +
+          "%s, please write explicit casts disambiguate your function call.",
+          1+bestMatchAlternatives.size(),
+          call
+        )
+        .build(logger);
     }
+    return bestMatch;
   }
 }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ExactFunctionResolver.java b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ExactFunctionResolver.java
index 59c248891a..478aeee18c 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ExactFunctionResolver.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ExactFunctionResolver.java
@@ -36,7 +36,7 @@ public class ExactFunctionResolver implements FunctionResolver {
   @Override
   public DrillFuncHolder getBestMatch(List<DrillFuncHolder> methods, FunctionCall call) {
 
-    int currCost;
+    float currCost;
 
     final List<TypeProtos.MajorType> argumentTypes = Lists.newArrayList();
     for (LogicalExpression expression : call.args()) {
@@ -46,7 +46,7 @@ public class ExactFunctionResolver implements FunctionResolver {
     for (DrillFuncHolder h : methods) {
       currCost = TypeCastRules.getCost(argumentTypes, h);
       // Return if we found a function that has an exact match with the input arguments
-      if (currCost == 0) {
+      if (currCost == 0f) {
         return h;
       }
     }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ResolverTypePrecedence.java b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ResolverTypePrecedence.java
index bf0bb22485..abc181402f 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ResolverTypePrecedence.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/ResolverTypePrecedence.java
@@ -21,148 +21,219 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 
 import org.apache.drill.common.types.TypeProtos.MinorType;
+import org.apache.drill.shaded.guava.com.google.common.graph.ImmutableValueGraph;
+import org.apache.drill.shaded.guava.com.google.common.graph.ValueGraphBuilder;
 
 public class ResolverTypePrecedence {
 
+  // A weighted directed graph that represents the cost of casting between
+  // pairs of data types. The edge weights represent casting preferences and
+  // it is important to note that only some of these preferences can be
+  // understood in terms of factors like computational cost or loss of
+  // precision. The others are derived from the expected behaviour of the query
+  // engine in the face of various data types and queries as expressed by the
+  // test suite.
+  //
+  // Bear in mind that this class only establishes which casts will be tried
+  // automatically and how they're ranked. See
+  // {@link org.apache.drill.exec.resolver.TypeCastRules} for listings of which
+  // casts are possible at all.
+  public static final ImmutableValueGraph<MinorType, Float> CAST_GRAPH = ValueGraphBuilder
+    .directed()
+    .<MinorType, Float>immutable()
+
+    // null type source vertex (null is castable to any type)
+    // prefer to cast NULL to a non-NULL over any non-NULL to another non-NULL
+    .putEdgeValue(MinorType.NULL, MinorType.VARCHAR, 1f)
+    .putEdgeValue(MinorType.NULL, MinorType.BIT, 1.1f)
+    .putEdgeValue(MinorType.NULL, MinorType.INT, 1.2f)
+    .putEdgeValue(MinorType.NULL, MinorType.FLOAT4, 1.3f)
+    .putEdgeValue(MinorType.NULL, MinorType.DECIMAL9, 1.4f)
+    .putEdgeValue(MinorType.NULL, MinorType.DATE, 1.5f)
+    .putEdgeValue(MinorType.NULL, MinorType.INTERVALDAY, 1.6f)
+    .putEdgeValue(MinorType.NULL, MinorType.MONEY, 1.7f)
+    .putEdgeValue(MinorType.NULL, MinorType.LIST, 1.8f)
+    .putEdgeValue(MinorType.NULL, MinorType.DICT, 1.9f)
+
+    // Apart from NULL, casts between differing types have a starting cost
+    // of 10f so that when they're summed, a smaller number of casts gets
+    // preferred over a larger number.
+
+    // bit conversions
+    // prefer to cast VARCHAR to BIT than BIT to numerics
+    .putEdgeValue(MinorType.BIT, MinorType.TINYINT, 100f)
+    .putEdgeValue(MinorType.BIT, MinorType.UINT1, 100f)
+
+    // unsigned int widening
+    .putEdgeValue(MinorType.UINT1, MinorType.UINT2, 10f)
+    .putEdgeValue(MinorType.UINT2, MinorType.UINT4, 10f)
+    .putEdgeValue(MinorType.UINT4, MinorType.UINT8, 10f)
+    .putEdgeValue(MinorType.UINT8, MinorType.VARDECIMAL, 10f)
+    // unsigned int conversions
+    // prefer to cast UINTs to BIGINT over FLOAT4
+    .putEdgeValue(MinorType.UINT4, MinorType.BIGINT, 10f)
+    .putEdgeValue(MinorType.UINT4, MinorType.FLOAT4, 11f)
+    .putEdgeValue(MinorType.UINT8, MinorType.FLOAT4, 12f)
+
+    // int widening
+    .putEdgeValue(MinorType.TINYINT, MinorType.SMALLINT, 10f)
+    .putEdgeValue(MinorType.SMALLINT, MinorType.INT, 10f)
+    .putEdgeValue(MinorType.INT, MinorType.BIGINT, 10f)
+    // int conversions
+    .putEdgeValue(MinorType.BIGINT, MinorType.FLOAT4, 10f)
+    // prefer to cast INTs to FLOATs over VARDECIMALs
+    .putEdgeValue(MinorType.BIGINT, MinorType.VARDECIMAL, 100f)
+
+    // float widening
+    .putEdgeValue(MinorType.FLOAT4, MinorType.FLOAT8, 10f)
+    // float conversion
+    // FLOATs are not currently castable to VARDECIMAL (see TypeCastRules)
+    // but it is not possible to avoid some path between them here since
+    // FLOATs must ultimately be implicitly castable to VARCHAR, and VARCHAR
+    // to VARDECIMAL.
+    // prefer the cast in the opposite direction
+    .putEdgeValue(MinorType.FLOAT8, MinorType.VARDECIMAL, 10_000f)
+
+    // decimal widening
+    .putEdgeValue(MinorType.DECIMAL9, MinorType.DECIMAL18, 10f)
+    .putEdgeValue(MinorType.DECIMAL18, MinorType.DECIMAL28DENSE, 10f)
+    .putEdgeValue(MinorType.DECIMAL28DENSE, MinorType.DECIMAL28SPARSE, 10f)
+    .putEdgeValue(MinorType.DECIMAL28SPARSE, MinorType.DECIMAL38DENSE, 10f)
+    .putEdgeValue(MinorType.DECIMAL38DENSE, MinorType.DECIMAL38SPARSE, 10f)
+    .putEdgeValue(MinorType.DECIMAL38SPARSE, MinorType.VARDECIMAL, 10f)
+    .putEdgeValue(MinorType.MONEY, MinorType.VARDECIMAL, 10f)
+    // decimal conversions
+    // prefer to cast INTs to VARDECIMALs over VARDECIMALs to FLOATs
+    .putEdgeValue(MinorType.VARDECIMAL, MinorType.FLOAT8, 1_000f)
+    .putEdgeValue(MinorType.VARDECIMAL, MinorType.FLOAT4, 1_001f)
+    // prefer the casts in the opposite directions
+    .putEdgeValue(MinorType.VARDECIMAL, MinorType.INT, 1_002f)
+    .putEdgeValue(MinorType.VARDECIMAL, MinorType.VARCHAR, 1_003f)
+
+    // interval widening
+    .putEdgeValue(MinorType.INTERVALDAY, MinorType.INTERVALYEAR, 10f)
+    .putEdgeValue(MinorType.INTERVALYEAR, MinorType.INTERVAL, 10f)
+    // interval conversions
+    // prefer the cast in the opposite direction
+    .putEdgeValue(MinorType.INTERVAL, MinorType.VARCHAR, 100f)
+
+    // dict widening
+    .putEdgeValue(MinorType.DICT, MinorType.MAP, 10f)
+
+    // timestamp widening
+    .putEdgeValue(MinorType.DATE, MinorType.TIMESTAMP, 10f)
+    .putEdgeValue(MinorType.TIMESTAMP, MinorType.TIMESTAMPTZ, 10f)
+    .putEdgeValue(MinorType.TIME, MinorType.TIMETZ, 10f)
+    // timestamp conversions
+    // prefer the casts in the opposite directions
+    .putEdgeValue(MinorType.TIMESTAMP, MinorType.DATE, 100f)
+    .putEdgeValue(MinorType.TIMESTAMP, MinorType.TIME, 101f)
+    .putEdgeValue(MinorType.TIMESTAMPTZ, MinorType.VARCHAR, 1_000f)
+    .putEdgeValue(MinorType.TIMETZ, MinorType.VARCHAR, 1_000f)
+
+    // char and binary widening
+    .putEdgeValue(MinorType.FIXEDCHAR, MinorType.VARCHAR, 10f)
+    .putEdgeValue(MinorType.FIXEDBINARY, MinorType.VARBINARY, 10f)
+    // char and binary conversions
+    .putEdgeValue(MinorType.VARCHAR, MinorType.INT, 10f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.FLOAT8, 20f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.FLOAT4, 21f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.VARDECIMAL, 30f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.TIMESTAMP, 40f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.INTERVALDAY, 50f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.BIT, 60f)
+    .putEdgeValue(MinorType.VARCHAR, MinorType.VARBINARY, 70f)
+    .putEdgeValue(MinorType.VARBINARY, MinorType.VARCHAR, 80f)
+
+    // union type sink vertex
+    .putEdgeValue(MinorType.LIST, MinorType.UNION, 10f)
+    .putEdgeValue(MinorType.MAP, MinorType.UNION, 10f)
+    .putEdgeValue(MinorType.VARBINARY, MinorType.UNION, 10f)
+    .putEdgeValue(MinorType.UNION, MinorType.LATE, 10f)
+
+    .build();
+
+  /**
+   * Searches the implicit casting graph for the path of least total cost using
+   * Dijkstra's algorithm.
+   * @param fromType type to cast from
+   * @param toType type to cast to
+   * @return a positive float path cost or +∞ if no path exists
+   */
+  public static float computeCost(MinorType fromType, MinorType toType) {
+    TreeSet<VertexDatum> remaining = new TreeSet<>();
+    Map<MinorType, VertexDatum> vertexData = new HashMap<>();
+    Set<MinorType> shortestPath = new HashSet<>();
+
+    VertexDatum sourceDatum = new VertexDatum(fromType, 0, null);
+    remaining.add(sourceDatum);
+    vertexData.put(fromType, sourceDatum);
+
+    while (!remaining.isEmpty()) {
+      // Poll the datum with lowest totalDistance in remaining
+      VertexDatum vertexDatum = remaining.pollFirst();
+      MinorType vertex = vertexDatum.vertex;
+      shortestPath.add(vertex);
+
+      if (vertex.equals(toType)) {
+        // Goal found. We only wanted to calculate the path distance so we
+        // don't need to go on to backtrace it.
+        return vertexDatum.totalDistance;
+      }
+
+      Set<MinorType> successors = CAST_GRAPH.successors(vertex);
+      for (MinorType successor : successors) {
+        if (shortestPath.contains(successor)) {
+          continue;
+        }
+
+        float distance = CAST_GRAPH.edgeValue(vertex, successor).orElseThrow(IllegalStateException::new);
+        float totalDistance = vertexDatum.totalDistance + distance;
+
+        VertexDatum successorDatum = vertexData.get(successor);
+        if (successorDatum == null) {
+          successorDatum = new VertexDatum(successor, totalDistance, vertexDatum);
+          vertexData.put(successor, successorDatum);
+          remaining.add(successorDatum);
+        } else if (totalDistance < successorDatum.totalDistance) {
+          successorDatum.totalDistance = totalDistance;
+          successorDatum.predecessor = vertexDatum;
+        }
+      }
+    }
+
+    return Float.POSITIVE_INFINITY;
+  }
 
-  public static final Map<MinorType, Integer> precedenceMap;
-  public static final Map<MinorType, Set<MinorType>> secondaryImplicitCastRules;
-  public static int MAX_IMPLICIT_CAST_COST;
-
-  static {
-    /* The precedenceMap is used to decide whether it's allowed to implicitly "promote"
-     * one type to another type.
-     *
-     * The order that each type is inserted into HASHMAP decides its precedence.
-     * First in ==> lowest precedence.
-     * A type of lower precedence can be implicitly "promoted" to type of higher precedence.
-     * For instance, NULL could be promoted to any other type;
-     * tinyint could be promoted into int; but int could NOT be promoted into tinyint (due to possible precision loss).
-     */
-    int i = 0;
-    precedenceMap = new HashMap<>();
-    precedenceMap.put(MinorType.NULL, i += 2);       // NULL is legal to implicitly be promoted to any other type
-    precedenceMap.put(MinorType.FIXEDBINARY, i += 2); // Fixed-length is promoted to var length
-    precedenceMap.put(MinorType.VARBINARY, i += 2);
-    precedenceMap.put(MinorType.FIXEDCHAR, i += 2);
-    precedenceMap.put(MinorType.VARCHAR, i += 2);
-    precedenceMap.put(MinorType.FIXED16CHAR, i += 2);
-    precedenceMap.put(MinorType.VAR16CHAR, i += 2);
-    precedenceMap.put(MinorType.BIT, i += 2);
-    precedenceMap.put(MinorType.TINYINT, i += 2);   //type with few bytes is promoted to type with more bytes ==> no data loss.
-    precedenceMap.put(MinorType.UINT1, i += 2);     //signed is legal to implicitly be promoted to unsigned.
-    precedenceMap.put(MinorType.SMALLINT, i += 2);
-    precedenceMap.put(MinorType.UINT2, i += 2);
-    precedenceMap.put(MinorType.INT, i += 2);
-    precedenceMap.put(MinorType.UINT4, i += 2);
-    precedenceMap.put(MinorType.BIGINT, i += 2);
-    precedenceMap.put(MinorType.UINT8, i += 2);
-    precedenceMap.put(MinorType.MONEY, i += 2);
-    precedenceMap.put(MinorType.FLOAT4, i += 2);
-    precedenceMap.put(MinorType.DECIMAL9, i += 2);
-    precedenceMap.put(MinorType.DECIMAL18, i += 2);
-    precedenceMap.put(MinorType.DECIMAL28DENSE, i += 2);
-    precedenceMap.put(MinorType.DECIMAL28SPARSE, i += 2);
-    precedenceMap.put(MinorType.DECIMAL38DENSE, i += 2);
-    precedenceMap.put(MinorType.DECIMAL38SPARSE, i += 2);
-    precedenceMap.put(MinorType.VARDECIMAL, i += 2);
-    precedenceMap.put(MinorType.FLOAT8, i += 2);
-    precedenceMap.put(MinorType.DATE, i += 2);
-    precedenceMap.put(MinorType.TIMESTAMP, i += 2);
-    precedenceMap.put(MinorType.TIMETZ, i += 2);
-    precedenceMap.put(MinorType.TIMESTAMPTZ, i += 2);
-    precedenceMap.put(MinorType.TIME, i += 2);
-    precedenceMap.put(MinorType.INTERVALDAY, i+= 2);
-    precedenceMap.put(MinorType.INTERVALYEAR, i+= 2);
-    precedenceMap.put(MinorType.INTERVAL, i+= 2);
-    precedenceMap.put(MinorType.MAP, i += 2);
-    precedenceMap.put(MinorType.DICT, i += 2);
-    precedenceMap.put(MinorType.LIST, i += 2);
-    precedenceMap.put(MinorType.UNION, i += 2);
-
-    MAX_IMPLICIT_CAST_COST = i;
-
-    /* Currently implicit cast follows the precedence rules.
-     * It may be useful to perform an implicit cast in
-     * the opposite direction as specified by the precedence rules.
-     *
-     * For example: As per the precedence rules we can implicitly cast
-     * from VARCHAR ---> BIGINT , but based upon some functions (eg: substr, concat)
-     * it may be useful to implicitly cast from BIGINT ---> VARCHAR.
-     *
-     * To allow for such cases we have a secondary set of rules which will allow the reverse
-     * implicit casts. Currently we only allow the reverse implicit cast to VARCHAR so we don't
-     * need any cost associated with it, if we add more of these that may collide we can add costs.
-     */
-    secondaryImplicitCastRules = new HashMap<>();
-    HashSet<MinorType> rule = new HashSet<>();
-
-    // Following cast functions should exist
-    rule.add(MinorType.TINYINT);
-    rule.add(MinorType.SMALLINT);
-    rule.add(MinorType.INT);
-    rule.add(MinorType.BIGINT);
-    rule.add(MinorType.UINT1);
-    rule.add(MinorType.UINT2);
-    rule.add(MinorType.UINT4);
-    rule.add(MinorType.UINT8);
-    rule.add(MinorType.VARDECIMAL);
-    rule.add(MinorType.MONEY);
-    rule.add(MinorType.FLOAT4);
-    rule.add(MinorType.FLOAT8);
-    rule.add(MinorType.BIT);
-    rule.add(MinorType.FIXEDCHAR);
-    rule.add(MinorType.FIXED16CHAR);
-    rule.add(MinorType.VARCHAR);
-    rule.add(MinorType.DATE);
-    rule.add(MinorType.TIME);
-    rule.add(MinorType.TIMESTAMP);
-    rule.add(MinorType.TIMESTAMPTZ);
-    rule.add(MinorType.INTERVAL);
-    rule.add(MinorType.INTERVALYEAR);
-    rule.add(MinorType.INTERVALDAY);
-    secondaryImplicitCastRules.put(MinorType.VARCHAR, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to VARBINARY
-    rule.add(MinorType.INT);
-    rule.add(MinorType.BIGINT);
-    rule.add(MinorType.FLOAT4);
-    rule.add(MinorType.FLOAT8);
-    rule.add(MinorType.VARCHAR);
-    secondaryImplicitCastRules.put(MinorType.VARBINARY, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to VARDECIMAL
-    rule.add(MinorType.FLOAT8);
-    secondaryImplicitCastRules.put(MinorType.VARDECIMAL, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to DECIMAL9
-    rule.add(MinorType.VARDECIMAL);
-    secondaryImplicitCastRules.put(MinorType.DECIMAL9, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to DECIMAL18
-    rule.add(MinorType.VARDECIMAL);
-    secondaryImplicitCastRules.put(MinorType.DECIMAL18, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to DECIMAL28SPARSE
-    rule.add(MinorType.VARDECIMAL);
-    secondaryImplicitCastRules.put(MinorType.DECIMAL28SPARSE, rule);
-
-    rule = new HashSet<>();
-
-    // Be able to implicitly cast to DECIMAL38SPARSE
-    rule.add(MinorType.VARDECIMAL);
-    secondaryImplicitCastRules.put(MinorType.DECIMAL38SPARSE, rule);
+  /**
+   * A struct to hold working data used by Dijkstra's algorithm and allowing
+   * comparison based on total distance from the source vertex to this vertex.
+   */
+  static class VertexDatum implements Comparable<VertexDatum> {
+    final MinorType vertex;
+    float totalDistance;
+    VertexDatum predecessor;
+
+    public VertexDatum(MinorType vertex, float totalDistance, VertexDatum predecessor) {
+      this.vertex = vertex;
+      this.totalDistance = totalDistance;
+      this.predecessor = predecessor;
+    }
+
+    @Override
+    public int compareTo(VertexDatum other) {
+      int distComparison = Float.compare(this.totalDistance, other.totalDistance);
+      // TreeSet uses this method to determine member equality, not equals(), so we
+      // need to differentiate between vertices with the same totalDistance.
+      return distComparison != 0 ? distComparison : this.vertex.compareTo(other.vertex);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("vertex: %s, totalDistance: %f, predecessor: %s", vertex, totalDistance, predecessor);
+    }
   }
 }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/TypeCastRules.java b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/TypeCastRules.java
index b617572eea..1f82592fba 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/TypeCastRules.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/resolver/TypeCastRules.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 
 import org.apache.drill.common.expression.LogicalExpression;
@@ -700,7 +701,7 @@ public class TypeCastRules {
         (rules.get(to) != null && rules.get(to).contains(from));
   }
 
-  public static DataMode getLeastRestrictiveDataMode(List<DataMode> dataModes) {
+  public static DataMode getLeastRestrictiveDataMode(DataMode... dataModes) {
     boolean hasOptional = false;
     for(DataMode dataMode : dataModes) {
       switch (dataMode) {
@@ -708,6 +709,7 @@ public class TypeCastRules {
           return dataMode;
         case OPTIONAL:
           hasOptional = true;
+        default:
       }
     }
 
@@ -718,20 +720,14 @@ public class TypeCastRules {
     }
   }
 
-  /*
-   * Function checks if casting is allowed from the 'from' -> 'to' minor type. If its allowed
-   * we also check if the precedence map allows such a cast and return true if both cases are satisfied
-   */
-  public static MinorType getLeastRestrictiveType(List<MinorType> types) {
-    assert types.size() >= 2;
-    MinorType result = types.get(0);
+  public static MinorType getLeastRestrictiveType(MinorType... types) {
+    MinorType result = types[0];
     if (result == MinorType.UNION) {
       return result;
     }
-    int resultPrec = ResolverTypePrecedence.precedenceMap.get(result);
 
-    for (int i = 1; i < types.size(); i++) {
-      MinorType next = types.get(i);
+    for (int i = 1; i < types.length; i++) {
+      MinorType next = types[i];
       if (next == MinorType.UNION) {
         return next;
       }
@@ -740,14 +736,13 @@ public class TypeCastRules {
         continue;
       }
 
-      int nextPrec = ResolverTypePrecedence.precedenceMap.get(next);
+      float resultCastCost = ResolverTypePrecedence.computeCost(next, result);
+      float nextCastCost = ResolverTypePrecedence.computeCost(result, next);
 
-      if (isCastable(next, result) && resultPrec >= nextPrec) {
-        // result is the least restrictive between the two args; nothing to do continue
+      if (isCastable(next, result) && resultCastCost <= nextCastCost && resultCastCost < Float.POSITIVE_INFINITY) {
         continue;
-      } else if(isCastable(result, next) && nextPrec >= resultPrec) {
+      } else if (isCastable(result, next) && nextCastCost <= resultCastCost && nextCastCost < Float.POSITIVE_INFINITY) {
         result = next;
-        resultPrec = nextPrec;
       } else {
         return null;
       }
@@ -756,28 +751,51 @@ public class TypeCastRules {
     return result;
   }
 
-  private static final int DATAMODE_CAST_COST = 1;
-  private static final int VARARG_COST = Integer.MAX_VALUE / 2;
+  /**
+   * Finds the type in a given set that has the cheapest cast from a given
+   * starting type.
+   * @param fromType type to cast from.
+   * @param toTypes candidate types to cast to.
+   * @return the type in toTypes that has the cheapest cast or empty if no
+   *         finite cost cast can be found.
+   */
+  public static Optional<MinorType> getCheapestCast(MinorType fromType, MinorType... toTypes) {
+    MinorType cheapest = null;
+    float cheapestCost = Float.POSITIVE_INFINITY;
+
+    for (MinorType toType: toTypes) {
+      float toTypeCost = ResolverTypePrecedence.computeCost(fromType, toType);
+      if (toTypeCost < cheapestCost) {
+        cheapest = toType;
+        cheapestCost = toTypeCost;
+      }
+    }
+
+    return Optional.ofNullable(cheapest);
+  }
+
+  // cost of changing mode from required to optional
+  private static final float DATAMODE_CHANGE_COST = 1f;
+  // cost of casting to a field reader, compare to edge weights in ResolverTypePrecedence
+  private static final float FIELD_READER_COST = 100f;
+  // cost of matching a vararg function, compare to edge weights in ResolverTypePrecedence
+  private static final float VARARG_COST = 100f;
 
   /**
-   * Decide whether it's legal to do implicit cast. -1 : not allowed for
-   * implicit cast > 0: cost associated with implicit cast. ==0: params are
-   * exactly same type of arg. No need of implicit.
+   * Decide whether it's legal to do implicit cast.
+   * @returns
+   *      0: param types equal arg types, no casting needed.
+   * (0,+∞): the cost of implicit casting.
+   *     +∞: implicit casting is not allowed.
    */
-  public static int getCost(List<MajorType> argumentTypes, DrillFuncHolder holder) {
-    int cost = 0;
+  public static float getCost(List<MajorType> argumentTypes, DrillFuncHolder holder) {
+    // Running sum of casting cost.
+    float totalCost = 0;
 
     if (argumentTypes.size() != holder.getParamCount() && !holder.isVarArg()) {
-      return -1;
+      return Float.POSITIVE_INFINITY;
     }
 
-    // Indicates whether we used secondary cast rules
-    boolean secondaryCast = false;
-
-    // number of arguments that could implicitly casts using precedence map or
-    // didn't require casting at all
-    int nCasts = 0;
-
     /*
      * If we are determining function holder for decimal data type, we need to
      * make sure the output type of the function can fit the precision that we
@@ -792,7 +810,7 @@ public class TypeCastRules {
 
       if (DRILL_REL_DATATYPE_SYSTEM.getMaxNumericPrecision() <
           holder.getReturnType(logicalExpressions).getPrecision()) {
-        return -1;
+        return Float.POSITIVE_INFINITY;
       }
     }
 
@@ -804,74 +822,32 @@ public class TypeCastRules {
       //@Param FieldReader will match any type
       if (holder.isFieldReader(i)) {
 //        if (Types.isComplex(call.args.get(i).getMajorType()) ||Types.isRepeated(call.args.get(i).getMajorType()) )
-        // add the max cost when encountered with a field reader considering
+        // add a weighted cost when we encounter a field reader considering
         // that it is the most expensive factor contributing to the cost.
-        cost += ResolverTypePrecedence.MAX_IMPLICIT_CAST_COST;
+        totalCost += FIELD_READER_COST;
         continue;
       }
 
       if (!TypeCastRules.isCastableWithNullHandling(argType, paramType, holder.getNullHandling())) {
-        return -1;
-      }
-
-      Integer paramVal = ResolverTypePrecedence.precedenceMap.get(paramType
-          .getMinorType());
-      Integer argVal = ResolverTypePrecedence.precedenceMap.get(argType
-          .getMinorType());
-
-      if (paramVal == null) {
-        throw new RuntimeException(String.format(
-            "Precedence for type %s is not defined", paramType.getMinorType().name()));
+        // one uncastable argument is enough to kill the party
+        return Float.POSITIVE_INFINITY;
       }
 
-      if (argVal == null) {
-        throw new RuntimeException(String.format(
-            "Precedence for type %s is not defined", argType.getMinorType().name()));
-      }
+      float castCost = ResolverTypePrecedence.computeCost(
+        argType.getMinorType(),
+        paramType.getMinorType()
+      );
 
-      if (paramVal - argVal < 0) {
-
-        /* Precedence rules do not allow to implicit cast, however check
-         * if the secondary rules allow us to cast
-         */
-        Set<MinorType> rules;
-        if ((rules = (ResolverTypePrecedence.secondaryImplicitCastRules.get(paramType.getMinorType()))) != null
-            && rules.contains(argType.getMinorType())) {
-          secondaryCast = true;
-        } else {
-          return -1;
-        }
-      }
-      // Check null vs non-null, using same logic as that in Types.softEqual()
-      // Only when the function uses NULL_IF_NULL, nullable and non-nullable are interchangeable.
-      // Otherwise, the function implementation is not a match.
-      if (argType.getMode() != paramType.getMode()) {
-        // TODO - this does not seem to do what it is intended to
-//        if (!((holder.getNullHandling() == NullHandling.NULL_IF_NULL) &&
-//            (argType.getMode() == DataMode.OPTIONAL ||
-//             argType.getMode() == DataMode.REQUIRED ||
-//             paramType.getMode() == DataMode.OPTIONAL ||
-//             paramType.getMode() == DataMode.REQUIRED )))
-//          return -1;
-        // if the function is designed to take optional with custom null handling, and a required
-        // is being passed, increase the cost to account for a null check
-        // this allows for a non-nullable implementation to be preferred
-        if (holder.getNullHandling() == NullHandling.INTERNAL) {
-          // a function that expects required output, but nullable was provided
-          if (paramType.getMode() == DataMode.REQUIRED && argType.getMode() == DataMode.OPTIONAL) {
-            return -1;
-          } else if (paramType.getMode() == DataMode.OPTIONAL && argType.getMode() == DataMode.REQUIRED) {
-            cost+= DATAMODE_CAST_COST;
-          }
-        }
+      if (castCost == Float.POSITIVE_INFINITY) {
+        // A single uncastable argument is enough to kill the party
+        return Float.POSITIVE_INFINITY;
       }
 
-      int castCost;
+      totalCost += castCost;
+      totalCost += holder.getNullHandling() == NullHandling.INTERNAL && paramType.getMode() != argType.getMode()
+        ? DATAMODE_CHANGE_COST
+        : 0;
 
-      if ((castCost = (paramVal - argVal)) >= 0) {
-        nCasts++;
-        cost += castCost;
-      }
     }
 
     if (holder.isVarArg()) {
@@ -883,30 +859,19 @@ public class TypeCastRules {
             && holder.getParamMajorType(varArgIndex).getMode() != argumentTypes.get(i).getMode()) {
           // prohibit using vararg functions for types with different nullability
           // if function accepts required arguments, but provided optional
-          return -1;
+          return Float.POSITIVE_INFINITY;
         }
       }
-
       // increase cost for var arg functions to prioritize regular ones
-      Integer additionalCost = ResolverTypePrecedence.precedenceMap.get(holder.getParamMajorType(varArgIndex).getMinorType());
-      cost += additionalCost != null ? additionalCost : VARARG_COST;
-      cost += holder.getParamMajorType(varArgIndex).getMode() == DataMode.REQUIRED ? 0 : 1;
+      totalCost += VARARG_COST;
+      totalCost += ResolverTypePrecedence.computeCost(
+        MinorType.NULL,
+        holder.getParamMajorType(varArgIndex).getMinorType()
+      );
+      totalCost += holder.getParamMajorType(varArgIndex).getMode() == DataMode.REQUIRED ? 0 : 1;
     }
 
-    if (secondaryCast) {
-      // We have a secondary cast for one or more of the arguments, determine the cost associated
-      int secondaryCastCost =  Integer.MAX_VALUE - 1;
-
-      // Subtract maximum possible implicit costs from the secondary cast cost
-      secondaryCastCost -= (nCasts * (ResolverTypePrecedence.MAX_IMPLICIT_CAST_COST + DATAMODE_CAST_COST));
-
-      // Add cost of implicitly casting the rest of the arguments that didn't use secondary casting
-      secondaryCastCost += cost;
-
-      return secondaryCastCost;
-    }
-
-    return cost;
+    return totalCost;
   }
 
   /*
@@ -934,5 +899,4 @@ public class TypeCastRules {
         return false;
     }
   }
-
 }
diff --git a/exec/java-exec/src/main/java/org/apache/drill/exec/store/parquet/ParquetTableMetadataUtils.java b/exec/java-exec/src/main/java/org/apache/drill/exec/store/parquet/ParquetTableMetadataUtils.java
index dea7f58443..d1505d1120 100644
--- a/exec/java-exec/src/main/java/org/apache/drill/exec/store/parquet/ParquetTableMetadataUtils.java
+++ b/exec/java-exec/src/main/java/org/apache/drill/exec/store/parquet/ParquetTableMetadataUtils.java
@@ -64,7 +64,6 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -680,7 +679,10 @@ public class ParquetTableMetadataUtils {
     if (majorType == null) {
       columns.put(columnPath, type);
     } else if (!majorType.equals(type)) {
-      TypeProtos.MinorType leastRestrictiveType = TypeCastRules.getLeastRestrictiveType(Arrays.asList(majorType.getMinorType(), type.getMinorType()));
+      TypeProtos.MinorType leastRestrictiveType = TypeCastRules.getLeastRestrictiveType(
+        majorType.getMinorType(),
+        type.getMinorType()
+      );
       if (leastRestrictiveType != majorType.getMinorType()) {
         columns.put(columnPath, type);
       }
diff --git a/exec/java-exec/src/test/java/org/apache/drill/TestImplicitCasting.java b/exec/java-exec/src/test/java/org/apache/drill/TestImplicitCasting.java
index 4994bb20a8..a167ce614d 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/TestImplicitCasting.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/TestImplicitCasting.java
@@ -19,26 +19,223 @@ package org.apache.drill;
 
 import org.apache.drill.categories.SqlTest;
 import org.apache.drill.common.types.TypeProtos;
+import org.apache.drill.exec.physical.rowSet.DirectRowSet;
+import org.apache.drill.exec.physical.rowSet.RowSet;
+import org.apache.drill.exec.record.metadata.SchemaBuilder;
+import org.apache.drill.exec.record.metadata.TupleMetadata;
+import org.apache.drill.exec.resolver.ResolverTypePrecedence;
 import org.apache.drill.exec.resolver.TypeCastRules;
-import org.apache.drill.test.BaseTest;
+import org.apache.drill.test.ClusterFixtureBuilder;
+import org.apache.drill.test.ClusterTest;
+import org.apache.drill.test.rowSet.RowSetUtilities;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import org.joda.time.Period;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
-import org.apache.drill.shaded.guava.com.google.common.collect.Lists;
 import org.junit.experimental.categories.Category;
 
-import java.util.List;
-
+import static org.hamcrest.Matchers.lessThan;
 import static org.junit.Assert.assertEquals;
 
 @Category(SqlTest.class)
-public class TestImplicitCasting extends BaseTest {
+public class TestImplicitCasting extends ClusterTest {
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    ClusterTest.startCluster(new ClusterFixtureBuilder(dirTestWatcher));
+  }
+
   @Test
   public void testTimeStampAndTime() {
-    final List<TypeProtos.MinorType> inputTypes = Lists.newArrayList();
-    inputTypes.add(TypeProtos.MinorType.TIME);
-    inputTypes.add(TypeProtos.MinorType.TIMESTAMP);
-    final TypeProtos.MinorType result = TypeCastRules.getLeastRestrictiveType(inputTypes);
+    final TypeProtos.MinorType result = TypeCastRules.getLeastRestrictiveType(
+      TypeProtos.MinorType.TIME,
+      TypeProtos.MinorType.TIMESTAMP
+    );
+
+    assertEquals(TypeProtos.MinorType.TIME, result);
+  }
+
+  @Test
+  /**
+   * Tests path cost arithmetic computed over the graph in ResolveTypePrecedence.
+   */
+  public void testCastingCosts() {
+    // INT -> BIGINT
+    assertEquals(
+      10f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INT, TypeProtos.MinorType.BIGINT),
+      0f
+    );
+    // INT -> BIGINT -> VARDECIMAL
+    assertEquals(
+      10f+100f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INT, TypeProtos.MinorType.VARDECIMAL),
+      0f
+    );
+    // DECIMAL9 -> DECIMAL18 -> DECIMAL28SPARSE -> DECIMAL28DENSE -> DECIMAL38SPARSE ->
+    // -> DECIMAL38DENSE -> VARDECIMAL
+    assertEquals(
+      10f+10f+10f+10f+10f+10f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.DECIMAL9, TypeProtos.MinorType.VARDECIMAL),
+      0f
+    );
+    // FLOAT4 -> FLOAT8 -> VARDECIMAL -> INT
+    assertEquals(
+      10f+10_000f+1_002f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.FLOAT4, TypeProtos.MinorType.INT),
+      0f
+    );
+    // TIMESTAMP -> DATE
+    assertEquals(
+      100f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.TIMESTAMP, TypeProtos.MinorType.DATE),
+      0f
+    );
+    // No path from MAP to FLOAT8
+    assertEquals(
+      Float.POSITIVE_INFINITY,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.MAP, TypeProtos.MinorType.FLOAT8),
+      0f
+    );
+    // VARCHAR -> INT -> BIGINT
+    assertEquals(
+      10f+10f,
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.BIGINT),
+      0f
+    );
+  }
+
+  @Test
+  public void testCastingPreferences() {
+    // All of the constraints that follow should be satisfied in order for
+    // queries involving implicit casts to behave as expected.
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.BIT),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.BIT, TypeProtos.MinorType.INT))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.UINT1, TypeProtos.MinorType.BIGINT),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.UINT1, TypeProtos.MinorType.FLOAT4))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.UINT1, TypeProtos.MinorType.BIGINT),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.UINT1, TypeProtos.MinorType.FLOAT4))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INT, TypeProtos.MinorType.FLOAT4),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INT, TypeProtos.MinorType.VARDECIMAL))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARDECIMAL, TypeProtos.MinorType.FLOAT8),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.FLOAT8, TypeProtos.MinorType.VARDECIMAL))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INT, TypeProtos.MinorType.VARDECIMAL),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARDECIMAL, TypeProtos.MinorType.FLOAT4))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.INTERVAL),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.INTERVAL, TypeProtos.MinorType.VARCHAR))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.TIME),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.TIME, TypeProtos.MinorType.VARCHAR))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.DATE, TypeProtos.MinorType.TIMESTAMP),
+        lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.TIMESTAMP, TypeProtos.MinorType.DATE))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARDECIMAL, TypeProtos.MinorType.FLOAT8),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARDECIMAL, TypeProtos.MinorType.FLOAT4))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.BIGINT),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.TIMESTAMP))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.FLOAT8),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.FLOAT4))
+    );
+    assertThat(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.FLOAT4) +
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.FLOAT4, TypeProtos.MinorType.FLOAT4),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.VARCHAR, TypeProtos.MinorType.FLOAT8) +
+        ResolverTypePrecedence.computeCost(TypeProtos.MinorType.FLOAT4, TypeProtos.MinorType.FLOAT8))
+    );
+    assertThat(
+      ResolverTypePrecedence.computeCost(TypeProtos.MinorType.NULL, TypeProtos.MinorType.FLOAT4),
+      lessThan(ResolverTypePrecedence.computeCost(TypeProtos.MinorType.FLOAT4, TypeProtos.MinorType.FLOAT8))
+    );
+  }
+
+  @Test
+  public void testSqrtOfString() throws Exception {
+    String sql = "select sqrt('5')";
+
+    DirectRowSet results = queryBuilder().sql(sql).rowSet();
+
+    TupleMetadata expectedSchema = new SchemaBuilder()
+      .add("EXPR$0", TypeProtos.MinorType.FLOAT8)
+      .build();
+
+    RowSet expected = client.rowSetBuilder(expectedSchema)
+      .addRow(2.23606797749979)
+      .build();
+
+    RowSetUtilities.verify(expected, results);
+  }
+
+  @Test
+  public void testDateDiffOnStringDate() throws Exception {
+    String sql = "select date_diff('2022-01-01', date '1970-01-01')";
+
+    DirectRowSet results = queryBuilder().sql(sql).rowSet();
+
+    TupleMetadata expectedSchema = new SchemaBuilder()
+      .add("EXPR$0", TypeProtos.MinorType.INTERVALDAY)
+      .build();
+
+    RowSet expected = client.rowSetBuilder(expectedSchema)
+      .addRow(new Period("P18993D"))
+      .build();
+
+    RowSetUtilities.verify(expected, results);
+  }
+
+  @Test
+  public void testSubstringOfDate() throws Exception {
+    String sql = "select substring(date '2022-09-09', 1, 4)";
+
+    DirectRowSet results = queryBuilder().sql(sql).rowSet();
+
+    TupleMetadata expectedSchema = new SchemaBuilder()
+      .add("EXPR$0", TypeProtos.MinorType.VARCHAR, 65535)
+      .build();
+
+    RowSet expected = client.rowSetBuilder(expectedSchema)
+      .addRow("2022")
+      .build();
+
+    RowSetUtilities.verify(expected, results);
+  }
+
+  @Test
+  public void testBooleanStringEquality() throws Exception {
+    String sql = "select true = 'true', true = 'FalsE'";
+
+    DirectRowSet results = queryBuilder().sql(sql).rowSet();
+
+    TupleMetadata expectedSchema = new SchemaBuilder()
+      .add("EXPR$0", TypeProtos.MinorType.BIT)
+      .add("EXPR$1", TypeProtos.MinorType.BIT)
+      .build();
+
+    RowSet expected = client.rowSetBuilder(expectedSchema)
+      .addRow(true, false)
+      .build();
 
-    assertEquals(result, TypeProtos.MinorType.TIME);
+    RowSetUtilities.verify(expected, results);
   }
 }
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestReverseImplicitCast.java b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestReverseImplicitCast.java
index 740bab079a..6dd52da5ed 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestReverseImplicitCast.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/physical/impl/TestReverseImplicitCast.java
@@ -64,8 +64,8 @@ public class TestReverseImplicitCast extends PopUnitTestBase {
       ValueVector.Accessor varcharAccessor1 = itr.next().getValueVector().getAccessor();
 
       for (int i = 0; i < intAccessor1.getValueCount(); i++) {
-        assertEquals(intAccessor1.getObject(i), 10);
-        assertEquals(varcharAccessor1.getObject(i).toString(), "101");
+        assertEquals(10, intAccessor1.getObject(i));
+        assertEquals("101", varcharAccessor1.getObject(i).toString());
       }
 
       batchLoader.clear();
diff --git a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonReaderFns.java b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonReaderFns.java
index 000dbaac39..38d9e4e4a9 100644
--- a/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonReaderFns.java
+++ b/exec/java-exec/src/test/java/org/apache/drill/exec/store/json/TestJsonReaderFns.java
@@ -201,8 +201,10 @@ public class TestJsonReaderFns extends BaseTestJsonReader {
     runBoth(this::doTestRepeatedContainsFloat4);
   }
 
+  // Since decimal literals are read as FLOAT8s by the JSON readers, I don't
+  // believe that this tests the version of repeated_contains that it aims to.
   private void doTestRepeatedContainsFloat4() throws Exception {
-    final RowSet results = runTest("select repeated_contains(FLOAT4_col, -1000000000000.0) from cp.`parquet/alltypes_repeated.json`");
+    final RowSet results = runTest("select repeated_contains(FLOAT4_col, cast(5.0 as float)) from cp.`parquet/alltypes_repeated.json`");
     final RowSet expected = client.rowSetBuilder(bitCountSchema())
         .addSingleCol(1)
         .addSingleCol(0)
@@ -212,6 +214,22 @@ public class TestJsonReaderFns extends BaseTestJsonReader {
     new RowSetComparison(expected).verifyAndClearAll(results);
   }
 
+  @Test
+  public void testRepeatedContainsFloat8() throws Exception {
+    runBoth(this::doTestRepeatedContainsFloat8);
+  }
+
+  private void doTestRepeatedContainsFloat8() throws Exception {
+    final RowSet results = runTest("select repeated_contains(FLOAT8_col, -10000000000000.0) from cp.`parquet/alltypes_repeated.json`");
+    final RowSet expected = client.rowSetBuilder(bitCountSchema())
+      .addSingleCol(1)
+      .addSingleCol(0)
+      .addSingleCol(0)
+      .addSingleCol(0)
+      .build();
+    new RowSetComparison(expected).verifyAndClearAll(results);
+  }
+
   @Test
   public void testRepeatedContainsVarchar() throws Exception {
     runBoth(this::doTestRepeatedContainsVarchar);
diff --git a/exec/java-exec/src/test/resources/functions/cast/two_way_implicit_cast.json b/exec/java-exec/src/test/resources/functions/cast/two_way_implicit_cast.json
index 008a59d85d..d9cf833d0d 100644
--- a/exec/java-exec/src/test/resources/functions/cast/two_way_implicit_cast.json
+++ b/exec/java-exec/src/test/resources/functions/cast/two_way_implicit_cast.json
@@ -23,8 +23,8 @@
             child: 1,
             pop:"project",
             exprs: [
-            {ref: "str_to_int_cast", expr:"8 + '2'" },
-            {ref: "int_to_str_cast", expr:"substr(10123, 1, 3)" }
+            {ref: "str_to_num_cast", expr:"8 + '2'" },
+            {ref: "num_to_str_cast", expr:"substr(10123, 1, 3)" }
             ]
         },
         {
diff --git a/protocol/src/main/java/org/apache/drill/common/types/TypeProtos.java b/protocol/src/main/java/org/apache/drill/common/types/TypeProtos.java
index fe08ab001f..ecf49fd0c7 100644
--- a/protocol/src/main/java/org/apache/drill/common/types/TypeProtos.java
+++ b/protocol/src/main/java/org/apache/drill/common/types/TypeProtos.java
@@ -118,7 +118,7 @@ public final class TypeProtos {
     DECIMAL38SPARSE(10),
     /**
      * <pre>
-     *  signed decimal with two digit precision
+     *  signed decimal with two digit scale
      * </pre>
      *
      * <code>MONEY = 11;</code>
@@ -423,7 +423,7 @@ public final class TypeProtos {
     public static final int DECIMAL38SPARSE_VALUE = 10;
     /**
      * <pre>
-     *  signed decimal with two digit precision
+     *  signed decimal with two digit scale
      * </pre>
      *
      * <code>MONEY = 11;</code>
diff --git a/protocol/src/main/protobuf/Types.proto b/protocol/src/main/protobuf/Types.proto
index d5cf943201..4ccc192b6b 100644
--- a/protocol/src/main/protobuf/Types.proto
+++ b/protocol/src/main/protobuf/Types.proto
@@ -35,7 +35,7 @@ enum MinorType {
     DECIMAL18 = 8;   //  a decimal supporting precision between 10 and 18
     DECIMAL28SPARSE = 9;   //  a decimal supporting precision between 19 and 28
     DECIMAL38SPARSE = 10;   //  a decimal supporting precision between 29 and 38
-    MONEY = 11;   //  signed decimal with two digit precision
+    MONEY = 11;   //  signed decimal with two digit scale
     DATE = 12;   //  days since 4713bc
     TIME = 13;   //  time in micros before or after 2000/1/1
     TIMETZ = 14;   //  time in micros before or after 2000/1/1 with timezone