public static TypeInfo getCommonClassForComparison(TypeInfo a, TypeInfo b) {
    // If same return one of them
    if (a.equals(b)) {
      return a;
    }


    if (a.getCategory() != Category.PRIMITIVE || b.getCategory() != Category.PRIMITIVE) {
      // It is not primitive; check if it is a struct and we can infer a common class
      if (a.getCategory() == Category.STRUCT && b.getCategory() == Category.STRUCT) {
        return getCommonClassForStruct((StructTypeInfo)a, (StructTypeInfo)b,
            (type1, type2) -> getCommonClassForComparison(type1, type2));
      }
      return null;
    }

    PrimitiveCategory pcA = ((PrimitiveTypeInfo)a).getPrimitiveCategory();
    PrimitiveCategory pcB = ((PrimitiveTypeInfo)b).getPrimitiveCategory();

    if (pcA == pcB) {
      // Same primitive category but different qualifiers.
      // Rely on getTypeInfoForPrimitiveCategory() to sort out the type params.
      return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, pcA);
    }

    if (pcA == PrimitiveCategory.VOID) {
      // Handle NULL, we return the type of pcB
      return b;
    }
    if (pcB == PrimitiveCategory.VOID) {
      // Handle NULL, we return the type of pcA
      return a;
    }

    PrimitiveGrouping pgA = PrimitiveObjectInspectorUtils.getPrimitiveGrouping(pcA);
    PrimitiveGrouping pgB = PrimitiveObjectInspectorUtils.getPrimitiveGrouping(pcB);

    if (pgA == pgB) {
      // grouping is same, but category is not.
      if (pgA == PrimitiveGrouping.DATE_GROUP) {
        Integer ai = TypeInfoUtils.dateTypes.get(pcA);
        Integer bi = TypeInfoUtils.dateTypes.get(pcB);
        return (ai > bi) ? a : b;
      }
    }
    // handle string types properly
    if (pgA == PrimitiveGrouping.STRING_GROUP && pgB == PrimitiveGrouping.STRING_GROUP) {
      // Compare as strings. Char comparison semantics may be different if/when implemented.
      return getTypeInfoForPrimitiveCategory(
          (PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b,PrimitiveCategory.STRING);
    }

    // timestamp/date is higher precedence than String_GROUP
    if (pgA == PrimitiveGrouping.STRING_GROUP && pgB == PrimitiveGrouping.DATE_GROUP) {
      return b;
    }
    // date/timestamp is higher precedence than String_GROUP
    if (pgB == PrimitiveGrouping.STRING_GROUP && pgA == PrimitiveGrouping.DATE_GROUP) {
      return a;
    }
    // Another special case, because timestamp is not implicitly convertible to numeric types.
    if ((pgA == PrimitiveGrouping.NUMERIC_GROUP || pgB == PrimitiveGrouping.NUMERIC_GROUP)
        && (pcA == PrimitiveCategory.TIMESTAMP || pcB == PrimitiveCategory.TIMESTAMP)) {
      return TypeInfoFactory.doubleTypeInfo;
    }

    for (PrimitiveCategory t : TypeInfoUtils.numericTypeList) {
      if (TypeInfoUtils.implicitConvertible(pcA, t)
          && TypeInfoUtils.implicitConvertible(pcB, t)) {
        return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, t);
      }
    }

    return null;
  }
Find a common class that objects of both TypeInfo a and TypeInfo b can
convert to. This is used for comparing objects of type a and type b.

When we are comparing string and double, we will always convert both of
them to double and then compare.

@return null if no common class could be found.
ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java