From 2919b890cea45d22a1d86913e55589214a1fabac Mon Sep 17 00:00:00 2001 From: Raghav Aggarwal Date: Fri, 3 Jul 2026 22:12:52 +0530 Subject: [PATCH] HIVE-29695: ClassCastException in MapJoin when Parquet String is mapped to Date --- .../ql/io/parquet/convert/ETypeConverter.java | 14 ++++ .../parquet/convert/TestETypeConverter.java | 30 +++++++ .../parquet_string_to_date_mapjoin.q | 25 ++++++ .../llap/parquet_string_to_date_mapjoin.q.out | 81 +++++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 ql/src/test/queries/clientpositive/parquet_string_to_date_mapjoin.q create mode 100644 ql/src/test/results/clientpositive/llap/parquet_string_to_date_mapjoin.q.out diff --git a/ql/src/java/org/apache/hadoop/hive/ql/io/parquet/convert/ETypeConverter.java b/ql/src/java/org/apache/hadoop/hive/ql/io/parquet/convert/ETypeConverter.java index c1bbd35ddeaa..8660d373d766 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/io/parquet/convert/ETypeConverter.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/io/parquet/convert/ETypeConverter.java @@ -16,6 +16,7 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import java.time.ZoneId; import java.time.ZoneOffset; import java.util.ArrayList; @@ -24,6 +25,7 @@ import java.util.TimeZone; import com.google.common.base.MoreObjects; +import org.apache.hadoop.hive.common.type.Date; import org.apache.hadoop.hive.common.type.HiveDecimal; import org.apache.hadoop.hive.common.type.Timestamp; import org.apache.hadoop.hive.conf.HiveConf; @@ -525,6 +527,18 @@ protected HiveVarcharWritable convert(Binary binary) { return new HiveVarcharWritable(binary.getBytes(), ((VarcharTypeInfo) hiveTypeInfo).getLength()); } }; + case DATE: + return new BinaryConverter(type, parent, index) { + @Override + protected DateWritableV2 convert(Binary binary) { + String s = new String(binary.getBytes(), StandardCharsets.UTF_8); + try { + return new DateWritableV2(Date.valueOf(s)); + } catch (IllegalArgumentException e) { + return null; + } + } + }; } } // STRING type diff --git a/ql/src/test/org/apache/hadoop/hive/ql/io/parquet/convert/TestETypeConverter.java b/ql/src/test/org/apache/hadoop/hive/ql/io/parquet/convert/TestETypeConverter.java index 3173d2db9007..ff0b31a3982c 100644 --- a/ql/src/test/org/apache/hadoop/hive/ql/io/parquet/convert/TestETypeConverter.java +++ b/ql/src/test/org/apache/hadoop/hive/ql/io/parquet/convert/TestETypeConverter.java @@ -21,6 +21,7 @@ import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.getPrimitiveTypeInfo; import static org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory.stringTypeInfo; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.nio.ByteBuffer; @@ -33,6 +34,7 @@ import org.apache.hadoop.hive.ql.io.parquet.convert.ETypeConverter.BinaryConverter; import org.apache.hadoop.hive.ql.io.parquet.timestamp.NanoTime; import org.apache.hadoop.hive.ql.io.parquet.timestamp.NanoTimeUtils; +import org.apache.hadoop.hive.serde2.io.DateWritableV2; import org.apache.hadoop.hive.serde2.io.HiveCharWritable; import org.apache.hadoop.hive.serde2.io.HiveDecimalWritable; import org.apache.hadoop.hive.serde2.io.HiveVarcharWritable; @@ -341,6 +343,34 @@ public void testGetTextConverterNoHiveTypeInfo() { assertEquals(value, textWritable.toString()); } + @Test + public void testGetTextConverterForDate() { + PrimitiveType primitiveType = + Types.optional(PrimitiveTypeName.BINARY) + .as(LogicalTypeAnnotation.stringType()) + .named("value"); + String value = "2026-01-01"; + DateWritableV2 dateWritable = + (DateWritableV2) + getWritableFromBinaryConverter( + TypeInfoFactory.dateTypeInfo, primitiveType, Binary.fromString(value)); + assertEquals(org.apache.hadoop.hive.common.type.Date.valueOf(value), dateWritable.get()); + } + + @Test + public void testGetTextConverterForDateInvalid() { + PrimitiveType primitiveType = + Types.optional(PrimitiveTypeName.BINARY) + .as(LogicalTypeAnnotation.stringType()) + .named("value"); + String value = "invalid_date"; + DateWritableV2 dateWritable = + (DateWritableV2) + getWritableFromBinaryConverter( + TypeInfoFactory.dateTypeInfo, primitiveType, Binary.fromString(value)); + assertNull(dateWritable); + } + @Test public void testGetIntConverterForTinyInt() throws Exception { PrimitiveType primitiveType = Types.optional(PrimitiveTypeName.INT32) diff --git a/ql/src/test/queries/clientpositive/parquet_string_to_date_mapjoin.q b/ql/src/test/queries/clientpositive/parquet_string_to_date_mapjoin.q new file mode 100644 index 000000000000..6a6c8fe3f321 --- /dev/null +++ b/ql/src/test/queries/clientpositive/parquet_string_to_date_mapjoin.q @@ -0,0 +1,25 @@ +CREATE TABLE repro_parquet_string ( + id INT, + col1 STRING +) STORED AS PARQUET; + +INSERT INTO repro_parquet_string VALUES (1, '2026-01-01'); + +CREATE EXTERNAL TABLE repro_parquet_date ( + id INT, + col1 DATE +) STORED AS PARQUET +LOCATION '${hiveconf:hive.metastore.warehouse.dir}/repro_parquet_string'; + +CREATE TABLE small_table ( + id INT, + date_col DATE +); +INSERT INTO small_table VALUES (1, '2026-01-01'); + +SET hive.auto.convert.join=true; +SET hive.vectorized.execution.enabled=false; + +SELECT /*+ MAPJOIN(small_table) */ a.col1 +FROM repro_parquet_date a +JOIN small_table b ON a.id = b.id; diff --git a/ql/src/test/results/clientpositive/llap/parquet_string_to_date_mapjoin.q.out b/ql/src/test/results/clientpositive/llap/parquet_string_to_date_mapjoin.q.out new file mode 100644 index 000000000000..5e260d1f7f86 --- /dev/null +++ b/ql/src/test/results/clientpositive/llap/parquet_string_to_date_mapjoin.q.out @@ -0,0 +1,81 @@ +PREHOOK: query: CREATE TABLE repro_parquet_string ( + id INT, + col1 STRING +) STORED AS PARQUET +PREHOOK: type: CREATETABLE +PREHOOK: Output: database:default +PREHOOK: Output: default@repro_parquet_string +POSTHOOK: query: CREATE TABLE repro_parquet_string ( + id INT, + col1 STRING +) STORED AS PARQUET +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: database:default +POSTHOOK: Output: default@repro_parquet_string +PREHOOK: query: INSERT INTO repro_parquet_string VALUES (1, '2026-01-01') +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: default@repro_parquet_string +POSTHOOK: query: INSERT INTO repro_parquet_string VALUES (1, '2026-01-01') +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: default@repro_parquet_string +POSTHOOK: Lineage: repro_parquet_string.col1 SCRIPT [] +POSTHOOK: Lineage: repro_parquet_string.id SCRIPT [] +PREHOOK: query: CREATE EXTERNAL TABLE repro_parquet_date ( + id INT, + col1 DATE +) STORED AS PARQUET +#### A masked pattern was here #### +PREHOOK: type: CREATETABLE +#### A masked pattern was here #### +PREHOOK: Output: database:default +PREHOOK: Output: default@repro_parquet_date +POSTHOOK: query: CREATE EXTERNAL TABLE repro_parquet_date ( + id INT, + col1 DATE +) STORED AS PARQUET +#### A masked pattern was here #### +POSTHOOK: type: CREATETABLE +#### A masked pattern was here #### +POSTHOOK: Output: database:default +POSTHOOK: Output: default@repro_parquet_date +PREHOOK: query: CREATE TABLE small_table ( + id INT, + date_col DATE +) +PREHOOK: type: CREATETABLE +PREHOOK: Output: database:default +PREHOOK: Output: default@small_table +POSTHOOK: query: CREATE TABLE small_table ( + id INT, + date_col DATE +) +POSTHOOK: type: CREATETABLE +POSTHOOK: Output: database:default +POSTHOOK: Output: default@small_table +PREHOOK: query: INSERT INTO small_table VALUES (1, '2026-01-01') +PREHOOK: type: QUERY +PREHOOK: Input: _dummy_database@_dummy_table +PREHOOK: Output: default@small_table +POSTHOOK: query: INSERT INTO small_table VALUES (1, '2026-01-01') +POSTHOOK: type: QUERY +POSTHOOK: Input: _dummy_database@_dummy_table +POSTHOOK: Output: default@small_table +POSTHOOK: Lineage: small_table.date_col SCRIPT [] +POSTHOOK: Lineage: small_table.id SCRIPT [] +PREHOOK: query: SELECT /*+ MAPJOIN(small_table) */ a.col1 +FROM repro_parquet_date a +JOIN small_table b ON a.id = b.id +PREHOOK: type: QUERY +PREHOOK: Input: default@repro_parquet_date +PREHOOK: Input: default@small_table +#### A masked pattern was here #### +POSTHOOK: query: SELECT /*+ MAPJOIN(small_table) */ a.col1 +FROM repro_parquet_date a +JOIN small_table b ON a.id = b.id +POSTHOOK: type: QUERY +POSTHOOK: Input: default@repro_parquet_date +POSTHOOK: Input: default@small_table +#### A masked pattern was here #### +2026-01-01