diff --git a/ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java b/ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java index 69a18cd..4da7c09 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/exec/FunctionRegistry.java @@ -702,15 +702,15 @@ public static TypeInfo getCommonClassForUnionAll(TypeInfo a, TypeInfo b) { (PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b,PrimitiveCategory.STRING); } - if (TypeInfoUtils.implicitConvertible(a, b)) { + if (TypeInfoUtils.isCommonTypeOf(a, b)) { return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, pcB); } - if (TypeInfoUtils.implicitConvertible(b, a)) { + if (TypeInfoUtils.isCommonTypeOf(b, a)) { return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, pcA); } for (PrimitiveCategory t : TypeInfoUtils.numericTypeList) { - if (TypeInfoUtils.implicitConvertible(pcA, t) - && TypeInfoUtils.implicitConvertible(pcB, t)) { + if (TypeInfoUtils.isCommonTypeOf(pcA, t) + && TypeInfoUtils.isCommonTypeOf(pcB, t)) { return getTypeInfoForPrimitiveCategory((PrimitiveTypeInfo)a, (PrimitiveTypeInfo)b, t); } } diff --git a/ql/src/test/queries/clientpositive/unionall_typeconversion.q b/ql/src/test/queries/clientpositive/unionall_typeconversion.q new file mode 100644 index 0000000..6b39b96 --- /dev/null +++ b/ql/src/test/queries/clientpositive/unionall_typeconversion.q @@ -0,0 +1,12 @@ +-- SORT_QUERY_RESULTS + +drop table if exists union_all_typeconversion; +create table union_all_typeconversion(col1 date, col2 int, col3 double); +insert into union_all_typeconversion values('2016-01-01', 5, 1.25); + +select * from +(select col1 from union_all_typeconversion union all + select col2 from union_all_typeconversion union all + select col3 from union_all_typeconversion) t; + +drop table union_all_typeconversion; diff --git a/ql/src/test/results/clientpositive/unionall_typeconversion.q.out b/ql/src/test/results/clientpositive/unionall_typeconversion.q.out new file mode 100644 index 0000000..b87ab6c --- /dev/null +++ b/ql/src/test/results/clientpositive/unionall_typeconversion.q.out @@ -0,0 +1,52 @@ +PREHOOK: query: -- SORT_QUERY_RESULTS + +drop table if exists union_all_typeconversion +PREHOOK: type: DROPTABLE +POSTHOOK: query: -- SORT_QUERY_RESULTS + +drop table if exists union_all_typeconversion +POSTHOOK: type: DROPTABLE +PREHOOK: query: create table union_all_typeconversion(col1 date, col2 int, col3 double) +PREHOOK: type: CREATETABLE +PREHOOK: Output: database:default +PREHOOK: Output: default@union_all_typeconversion +POSTHOOK: query: create table union_all_typeconversion(col1 date, col2 int, col3 double) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: database:default +POSTHOOK: Output: default@union_all_typeconversion +PREHOOK: query: insert into union_all_typeconversion values('2016-01-01', 5, 1.25) +PREHOOK: type: QUERY +PREHOOK: Input: default@values__tmp__table__1 +PREHOOK: Output: default@union_all_typeconversion +POSTHOOK: query: insert into union_all_typeconversion values('2016-01-01', 5, 1.25) +POSTHOOK: type: QUERY +POSTHOOK: Input: default@values__tmp__table__1 +POSTHOOK: Output: default@union_all_typeconversion +POSTHOOK: Lineage: union_all_typeconversion.col1 EXPRESSION [(values__tmp__table__1)values__tmp__table__1.FieldSchema(name:tmp_values_col1, type:string, comment:), ] +POSTHOOK: Lineage: union_all_typeconversion.col2 EXPRESSION [(values__tmp__table__1)values__tmp__table__1.FieldSchema(name:tmp_values_col2, type:string, comment:), ] +POSTHOOK: Lineage: union_all_typeconversion.col3 EXPRESSION [(values__tmp__table__1)values__tmp__table__1.FieldSchema(name:tmp_values_col3, type:string, comment:), ] +PREHOOK: query: select * from +(select col1 from union_all_typeconversion union all + select col2 from union_all_typeconversion union all + select col3 from union_all_typeconversion) t +PREHOOK: type: QUERY +PREHOOK: Input: default@union_all_typeconversion +#### A masked pattern was here #### +POSTHOOK: query: select * from +(select col1 from union_all_typeconversion union all + select col2 from union_all_typeconversion union all + select col3 from union_all_typeconversion) t +POSTHOOK: type: QUERY +POSTHOOK: Input: default@union_all_typeconversion +#### A masked pattern was here #### +1.25 +2016-01-01 +5 +PREHOOK: query: drop table union_all_typeconversion +PREHOOK: type: DROPTABLE +PREHOOK: Input: default@union_all_typeconversion +PREHOOK: Output: default@union_all_typeconversion +POSTHOOK: query: drop table union_all_typeconversion +POSTHOOK: type: DROPTABLE +POSTHOOK: Input: default@union_all_typeconversion +POSTHOOK: Output: default@union_all_typeconversion diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/typeinfo/TypeInfoUtils.java b/serde/src/java/org/apache/hadoop/hive/serde2/typeinfo/TypeInfoUtils.java index 8f7b799..a0a00cd 100644 --- a/serde/src/java/org/apache/hadoop/hive/serde2/typeinfo/TypeInfoUtils.java +++ b/serde/src/java/org/apache/hadoop/hive/serde2/typeinfo/TypeInfoUtils.java @@ -874,6 +874,9 @@ public static void registerNumericType(PrimitiveCategory primitiveCategory, int numericTypes.put(primitiveCategory, level); } + /** + * Test if it's implicitly convertible for data comparison. + */ public static boolean implicitConvertible(PrimitiveCategory from, PrimitiveCategory to) { if (from == to) { return true; @@ -937,4 +940,66 @@ public static boolean implicitConvertible(TypeInfo from, TypeInfo to) { } return false; } + + /** + * Test if the category to is the common type of the category from. It's used by + * union all operator to find the common type. + * It's slightly different from implicitConvertible() used in comparison. e.g., + * with string and double, string is the common type of both while double is + * chosen for type comparison. + */ + public static boolean isCommonTypeOf(PrimitiveCategory from, PrimitiveCategory to) { + if (from == to) { + return true; + } + + PrimitiveGrouping fromPg = PrimitiveObjectInspectorUtils.getPrimitiveGrouping(from); + PrimitiveGrouping toPg = PrimitiveObjectInspectorUtils.getPrimitiveGrouping(to); + + // Void can be converted to any type + if (from == PrimitiveCategory.VOID) { + return true; + } + + // Allow implicit String to Date conversion + if (fromPg == PrimitiveGrouping.DATE_GROUP && toPg == PrimitiveGrouping.STRING_GROUP) { + return true; + } + // Allow implicit Numeric to String conversion + if (fromPg == PrimitiveGrouping.NUMERIC_GROUP && toPg == PrimitiveGrouping.STRING_GROUP) { + return true; + } + // Allow implicit String to varchar conversion, and vice versa + if (fromPg == PrimitiveGrouping.STRING_GROUP && toPg == PrimitiveGrouping.STRING_GROUP) { + return true; + } + + // Allow implicit conversion from Byte -> Integer -> Long -> Float -> Double + // Decimal -> String + Integer f = numericTypes.get(from); + Integer t = numericTypes.get(to); + if (f == null || t == null) { + return false; + } + if (f.intValue() > t.intValue()) { + return false; + } + return true; + } + + public static boolean isCommonTypeOf(TypeInfo from, TypeInfo to) { + if (from.equals(to)) { + return true; + } + + // Reimplemented to use PrimitiveCategory rather than TypeInfo, because + // 2 TypeInfos from the same qualified type (varchar, decimal) should still be + // seen as equivalent. + if (from.getCategory() == Category.PRIMITIVE && to.getCategory() == Category.PRIMITIVE) { + return isCommonTypeOf( + ((PrimitiveTypeInfo) from).getPrimitiveCategory(), + ((PrimitiveTypeInfo) to).getPrimitiveCategory()); + } + return false; + } }