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

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


The following commit(s) were added to refs/heads/master by this push:
     new cccc6efe729 HIVE-29770: Iceberg: Return VARIANT values as JSON over 
the HS2 Thrift protocol (#6644)
cccc6efe729 is described below

commit cccc6efe72932f6d80b54f9fa6087b50d1bfcc5c
Author: Denys Kuzmenko <[email protected]>
AuthorDate: Thu Oct 15 13:31:55 2026 +0200

    HIVE-29770: Iceberg: Return VARIANT values as JSON over the HS2 Thrift 
protocol (#6644)
---
 .../queries/positive/variant_type_projection.q     |  66 ++++++
 .../results/positive/variant_type_projection.q.out | 235 +++++++++++++++++++++
 .../java/org/apache/hive/jdbc/TestJdbcDriver2.java |  16 ++
 .../java/org/apache/hive/jdbc/HiveConnection.java  |   2 +
 jdbc/src/java/org/apache/hive/jdbc/JdbcColumn.java |   4 +
 .../hadoop/hive/serde2/DelimitedJSONSerDe.java     |   2 +-
 .../org/apache/hadoop/hive/serde2/SerDeUtils.java  |  87 ++++++--
 .../org/apache/hadoop/hive/serde2/thrift/Type.java | 123 ++++-------
 .../apache/hadoop/hive/serde2/TestSerDeUtils.java  | 102 +++++++++
 service-rpc/if/TCLIService.thrift                  |   8 +-
 .../gen/thrift/gen-cpp/TCLIService_constants.cpp   |   2 +
 .../src/gen/thrift/gen-cpp/TCLIService_types.cpp   |  16 +-
 .../src/gen/thrift/gen-cpp/TCLIService_types.h     |   6 +-
 .../service/rpc/thrift/TCLIServiceConstants.java   |   2 +
 .../hive/service/rpc/thrift/TProtocolVersion.java  |   5 +-
 .../apache/hive/service/rpc/thrift/TTypeId.java    |   5 +-
 service-rpc/src/gen/thrift/gen-php/Constant.php    |   2 +
 .../src/gen/thrift/gen-php/TProtocolVersion.php    |   3 +
 service-rpc/src/gen/thrift/gen-php/TTypeId.php     |   3 +
 .../src/gen/thrift/gen-py/TCLIService/constants.py |   2 +
 .../src/gen/thrift/gen-py/TCLIService/ttypes.py    |   6 +
 .../gen/thrift/gen-rb/t_c_l_i_service_constants.rb |   2 +
 .../src/gen/thrift/gen-rb/t_c_l_i_service_types.rb |  10 +-
 .../org/apache/hive/service/cli/ColumnValue.java   |   6 +-
 24 files changed, 590 insertions(+), 125 deletions(-)

diff --git 
a/iceberg/iceberg-handler/src/test/queries/positive/variant_type_projection.q 
b/iceberg/iceberg-handler/src/test/queries/positive/variant_type_projection.q
new file mode 100644
index 00000000000..2076693c5fa
--- /dev/null
+++ 
b/iceberg/iceberg-handler/src/test/queries/positive/variant_type_projection.q
@@ -0,0 +1,66 @@
+-- SORT_QUERY_RESULTS
+set hive.explain.user=false;
+
+drop table if exists variant_proj;
+
+CREATE EXTERNAL TABLE variant_proj (
+  id INT,
+  data VARIANT
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+);
+
+INSERT INTO variant_proj VALUES
+(1, parse_json('{"name": "John", "age": 30}')),
+(2, parse_json('[1, 2, 3]')),
+(3, parse_json('"plain string"')),
+(4, parse_json('42'));
+
+INSERT INTO variant_proj (id) SELECT 5;
+
+-- raw projection: variants are returned as their logical JSON
+EXPLAIN SELECT id, data FROM variant_proj;
+
+SELECT id, data FROM variant_proj;
+
+SELECT * FROM variant_proj;
+
+-- raw projection across a shuffle
+SELECT id, data FROM variant_proj ORDER BY id DESC;
+
+-- variant nested inside a struct is decoded within the folded output
+-- (array<variant>/map<..,variant> reads fail in Iceberg column projection; 
tracked separately)
+drop table if exists variant_nested;
+
+CREATE EXTERNAL TABLE variant_nested (
+  id INT,
+  s STRUCT<label:STRING, v:VARIANT>
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+);
+
+INSERT INTO variant_nested SELECT
+  1,
+  named_struct('label', 'x', 'v', parse_json('{"k": 1}'));
+
+SELECT * FROM variant_nested;
+
+SELECT id, to_json(s.v) FROM variant_nested;
+
+-- a plain struct with the variant shape keeps the generic struct rendering
+drop table if exists bin_struct;
+
+CREATE EXTERNAL TABLE bin_struct (
+  id INT,
+  s STRUCT<metadata:BINARY, value:BINARY>
+) STORED BY ICEBERG;
+
+INSERT INTO bin_struct SELECT 1, named_struct('metadata', cast('x' as binary), 
'value', cast('y' as binary));
+
+SELECT * FROM bin_struct;
+
+drop table variant_proj;
+drop table variant_nested;
+drop table bin_struct;
diff --git 
a/iceberg/iceberg-handler/src/test/results/positive/variant_type_projection.q.out
 
b/iceberg/iceberg-handler/src/test/results/positive/variant_type_projection.q.out
new file mode 100644
index 00000000000..e4165c150de
--- /dev/null
+++ 
b/iceberg/iceberg-handler/src/test/results/positive/variant_type_projection.q.out
@@ -0,0 +1,235 @@
+PREHOOK: query: drop table if exists variant_proj
+PREHOOK: type: DROPTABLE
+PREHOOK: Output: database:default
+POSTHOOK: query: drop table if exists variant_proj
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Output: database:default
+PREHOOK: query: CREATE EXTERNAL TABLE variant_proj (
+  id INT,
+  data VARIANT
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+)
+PREHOOK: type: CREATETABLE
+PREHOOK: Output: database:default
+PREHOOK: Output: default@variant_proj
+POSTHOOK: query: CREATE EXTERNAL TABLE variant_proj (
+  id INT,
+  data VARIANT
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+)
+POSTHOOK: type: CREATETABLE
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@variant_proj
+PREHOOK: query: INSERT INTO variant_proj VALUES
+(1, parse_json('{"name": "John", "age": 30}')),
+(2, parse_json('[1, 2, 3]')),
+(3, parse_json('"plain string"')),
+(4, parse_json('42'))
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: default@variant_proj
+POSTHOOK: query: INSERT INTO variant_proj VALUES
+(1, parse_json('{"name": "John", "age": 30}')),
+(2, parse_json('[1, 2, 3]')),
+(3, parse_json('"plain string"')),
+(4, parse_json('42'))
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: default@variant_proj
+PREHOOK: query: INSERT INTO variant_proj (id) SELECT 5
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: default@variant_proj
+POSTHOOK: query: INSERT INTO variant_proj (id) SELECT 5
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: default@variant_proj
+PREHOOK: query: EXPLAIN SELECT id, data FROM variant_proj
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_proj
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: EXPLAIN SELECT id, data FROM variant_proj
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_proj
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+STAGE DEPENDENCIES:
+  Stage-0 is a root stage
+
+STAGE PLANS:
+  Stage: Stage-0
+    Fetch Operator
+      limit: -1
+      Processor Tree:
+        TableScan
+          alias: variant_proj
+          Select Operator
+            expressions: id (type: int), data (type: 
struct<metadata:binary,value:binary>)
+            outputColumnNames: _col0, _col1
+            ListSink
+
+PREHOOK: query: SELECT id, data FROM variant_proj
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_proj
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT id, data FROM variant_proj
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_proj
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"age":30,"name":"John"}
+2      [1,2,3]
+3      "plain string"
+4      42
+5      NULL
+PREHOOK: query: SELECT * FROM variant_proj
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_proj
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT * FROM variant_proj
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_proj
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"age":30,"name":"John"}
+2      [1,2,3]
+3      "plain string"
+4      42
+5      NULL
+PREHOOK: query: SELECT id, data FROM variant_proj ORDER BY id DESC
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_proj
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT id, data FROM variant_proj ORDER BY id DESC
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_proj
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"age":30,"name":"John"}
+2      [1,2,3]
+3      "plain string"
+4      42
+5      NULL
+PREHOOK: query: drop table if exists variant_nested
+PREHOOK: type: DROPTABLE
+PREHOOK: Output: database:default
+POSTHOOK: query: drop table if exists variant_nested
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Output: database:default
+PREHOOK: query: CREATE EXTERNAL TABLE variant_nested (
+  id INT,
+  s STRUCT<label:STRING, v:VARIANT>
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+)
+PREHOOK: type: CREATETABLE
+PREHOOK: Output: database:default
+PREHOOK: Output: default@variant_nested
+POSTHOOK: query: CREATE EXTERNAL TABLE variant_nested (
+  id INT,
+  s STRUCT<label:STRING, v:VARIANT>
+) STORED BY ICEBERG
+TBLPROPERTIES (
+  'format-version'='3'
+)
+POSTHOOK: type: CREATETABLE
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@variant_nested
+PREHOOK: query: INSERT INTO variant_nested SELECT
+  1,
+  named_struct('label', 'x', 'v', parse_json('{"k": 1}'))
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: default@variant_nested
+POSTHOOK: query: INSERT INTO variant_nested SELECT
+  1,
+  named_struct('label', 'x', 'v', parse_json('{"k": 1}'))
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: default@variant_nested
+PREHOOK: query: SELECT * FROM variant_nested
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_nested
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT * FROM variant_nested
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_nested
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"label":"x","v":{"k":1}}
+PREHOOK: query: SELECT id, to_json(s.v) FROM variant_nested
+PREHOOK: type: QUERY
+PREHOOK: Input: default@variant_nested
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT id, to_json(s.v) FROM variant_nested
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@variant_nested
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"k":1}
+PREHOOK: query: drop table if exists bin_struct
+PREHOOK: type: DROPTABLE
+PREHOOK: Output: database:default
+POSTHOOK: query: drop table if exists bin_struct
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Output: database:default
+PREHOOK: query: CREATE EXTERNAL TABLE bin_struct (
+  id INT,
+  s STRUCT<metadata:BINARY, value:BINARY>
+) STORED BY ICEBERG
+PREHOOK: type: CREATETABLE
+PREHOOK: Output: database:default
+PREHOOK: Output: default@bin_struct
+POSTHOOK: query: CREATE EXTERNAL TABLE bin_struct (
+  id INT,
+  s STRUCT<metadata:BINARY, value:BINARY>
+) STORED BY ICEBERG
+POSTHOOK: type: CREATETABLE
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@bin_struct
+PREHOOK: query: INSERT INTO bin_struct SELECT 1, named_struct('metadata', 
cast('x' as binary), 'value', cast('y' as binary))
+PREHOOK: type: QUERY
+PREHOOK: Input: _dummy_database@_dummy_table
+PREHOOK: Output: default@bin_struct
+POSTHOOK: query: INSERT INTO bin_struct SELECT 1, named_struct('metadata', 
cast('x' as binary), 'value', cast('y' as binary))
+POSTHOOK: type: QUERY
+POSTHOOK: Input: _dummy_database@_dummy_table
+POSTHOOK: Output: default@bin_struct
+PREHOOK: query: SELECT * FROM bin_struct
+PREHOOK: type: QUERY
+PREHOOK: Input: default@bin_struct
+PREHOOK: Output: hdfs://### HDFS PATH ###
+POSTHOOK: query: SELECT * FROM bin_struct
+POSTHOOK: type: QUERY
+POSTHOOK: Input: default@bin_struct
+POSTHOOK: Output: hdfs://### HDFS PATH ###
+1      {"metadata":x,"value":y}
+PREHOOK: query: drop table variant_proj
+PREHOOK: type: DROPTABLE
+PREHOOK: Input: default@variant_proj
+PREHOOK: Output: database:default
+PREHOOK: Output: default@variant_proj
+POSTHOOK: query: drop table variant_proj
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Input: default@variant_proj
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@variant_proj
+PREHOOK: query: drop table variant_nested
+PREHOOK: type: DROPTABLE
+PREHOOK: Input: default@variant_nested
+PREHOOK: Output: database:default
+PREHOOK: Output: default@variant_nested
+POSTHOOK: query: drop table variant_nested
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Input: default@variant_nested
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@variant_nested
+PREHOOK: query: drop table bin_struct
+PREHOOK: type: DROPTABLE
+PREHOOK: Input: default@bin_struct
+PREHOOK: Output: database:default
+PREHOOK: Output: default@bin_struct
+POSTHOOK: query: drop table bin_struct
+POSTHOOK: type: DROPTABLE
+POSTHOOK: Input: default@bin_struct
+POSTHOOK: Output: database:default
+POSTHOOK: Output: default@bin_struct
diff --git 
a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestJdbcDriver2.java 
b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestJdbcDriver2.java
index 5dd0d473074..b2d01009650 100644
--- a/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestJdbcDriver2.java
+++ b/itests/hive-unit/src/test/java/org/apache/hive/jdbc/TestJdbcDriver2.java
@@ -1123,6 +1123,22 @@ public void testIntervalTypes() throws Exception {
     stmt.close();
   }
 
+  @Test
+  public void testVariantType() throws Exception {
+    try (Statement stmt = con.createStatement()) {
+      stmt.execute("drop table if exists variant_table");
+      stmt.execute("create table variant_table (v variant) stored by iceberg 
tblproperties ('format-version'='3')");
+      stmt.execute("insert into variant_table values 
(parse_json('{\"name\":\"John\",\"age\":30}'))");
+
+      try (ResultSet res = stmt.executeQuery("select v from variant_table")) {
+        assertTrue(res.next());
+        assertEquals("{\"age\":30,\"name\":\"John\"}", res.getString(1));
+        assertFalse(res.next());
+      }
+      stmt.execute("drop table variant_table");
+    }
+  }
+
   private void doTestSelectAll(String tableName, int maxRows, int fetchSize) 
throws Exception {
     boolean isPartitionTable = tableName.equals(partitionedTableName);
 
diff --git a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java 
b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
index 888b5ca5c8c..5cba71ca4c9 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/HiveConnection.java
@@ -389,6 +389,8 @@ protected HiveConnection(String uri, Properties info,
     supportedProtocols.add(TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V8);
     supportedProtocols.add(TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V9);
     supportedProtocols.add(TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V10);
+    supportedProtocols.add(TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V11);
+    supportedProtocols.add(TProtocolVersion.HIVE_CLI_SERVICE_PROTOCOL_V12);
 
     if (isBrowserAuthMode()) {
       try {
diff --git a/jdbc/src/java/org/apache/hive/jdbc/JdbcColumn.java 
b/jdbc/src/java/org/apache/hive/jdbc/JdbcColumn.java
index 9d83e0aaa34..c35d020adcc 100644
--- a/jdbc/src/java/org/apache/hive/jdbc/JdbcColumn.java
+++ b/jdbc/src/java/org/apache/hive/jdbc/JdbcColumn.java
@@ -165,6 +165,8 @@ static Type typeStringToHiveType(String type) throws 
SQLException {
       return Type.STRUCT_TYPE;
     case "uniontype":
       return Type.UNION_TYPE;
+    case serdeConstants.VARIANT_TYPE_NAME:
+      return Type.VARIANT_TYPE;
     case "void":
       /* fall through */
     case "null":
@@ -235,6 +237,8 @@ static String getColumnTypeName(String type) throws 
SQLException {
         return serdeConstants.LIST_TYPE_NAME;
       case "struct":
         return serdeConstants.STRUCT_TYPE_NAME;
+      case serdeConstants.VARIANT_TYPE_NAME:
+        return serdeConstants.VARIANT_TYPE_NAME;
       default:
         throw new SQLException("Unrecognized column type: " + type);
     }
diff --git 
a/serde/src/java/org/apache/hadoop/hive/serde2/DelimitedJSONSerDe.java 
b/serde/src/java/org/apache/hadoop/hive/serde2/DelimitedJSONSerDe.java
index ba9b8f36c19..219e29c00c2 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/DelimitedJSONSerDe.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/DelimitedJSONSerDe.java
@@ -59,7 +59,7 @@ protected void serializeField(ByteStream.Output out, Object 
obj, ObjectInspector
     if (!objInspector.getCategory().equals(Category.PRIMITIVE) || 
(objInspector.getTypeName().equalsIgnoreCase(serdeConstants.BINARY_TYPE_NAME))) 
{
       //do this for all complex types and binary
       try {
-        serialize(out, SerDeUtils.getJSONString(obj, objInspector, 
serdeParams.getNullSequence().toString()),
+        serialize(out, SerDeUtils.getJSONString(obj, objInspector, 
serdeParams.getNullSequence().toString(), true),
             PrimitiveObjectInspectorFactory.javaStringObjectInspector, 
serdeParams.getSeparators(),
             1, serdeParams.getNullSequence(), serdeParams.isEscaped(), 
serdeParams.getEscapeChar(),
             serdeParams.getNeedsEscape());
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/SerDeUtils.java 
b/serde/src/java/org/apache/hadoop/hive/serde2/SerDeUtils.java
index 36ba1536039..6ee8c261f8e 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/SerDeUtils.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/SerDeUtils.java
@@ -20,6 +20,7 @@
 package org.apache.hadoop.hive.serde2;
 
 import java.nio.charset.Charset;
+import java.time.ZoneOffset;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
@@ -32,6 +33,7 @@
 import org.apache.hadoop.hive.serde2.objectinspector.StructField;
 import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
 import org.apache.hadoop.hive.serde2.objectinspector.UnionObjectInspector;
+import org.apache.hadoop.hive.serde2.objectinspector.VariantObjectInspector;
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.BinaryObjectInspector;
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.BooleanObjectInspector;
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.ByteObjectInspector;
@@ -49,6 +51,7 @@
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.TimestampObjectInspector;
 import 
org.apache.hadoop.hive.serde2.objectinspector.primitive.TimestampLocalTZObjectInspector;
+import org.apache.hadoop.hive.serde2.variant.Variant;
 import org.apache.hadoop.io.BytesWritable;
 import org.apache.hadoop.io.Text;
 import org.slf4j.Logger;
@@ -73,6 +76,8 @@ public final class SerDeUtils {
 
   // lower case null is used within json objects
   private static final String JSON_NULL = "null";
+  // the physical struct<metadata:binary,value:binary> shape identifying 
variant values at runtime
+  private static final String VARIANT_TYPE_NAME = 
VariantObjectInspector.get().getTypeName();
   public static final String LIST_SINK_OUTPUT_FORMATTER = 
"list.sink.output.formatter";
   public static final String LIST_SINK_OUTPUT_PROTOCOL = 
"list.sink.output.protocol";
   public static final Logger LOG = 
LoggerFactory.getLogger(SerDeUtils.class.getName());
@@ -181,7 +186,7 @@ public static Object toThriftPayload(Object val, 
ObjectInspector valOI, int vers
     }
     // for now, expose non-primitive as a string
     // TODO: expose non-primitive as a structured object while maintaining 
JDBC compliance
-    return SerDeUtils.getJSONString(val, valOI);
+    return SerDeUtils.getJSONString(val, valOI, JSON_NULL, true);
   }
 
   public static String getJSONString(Object o, ObjectInspector oi) {
@@ -198,13 +203,23 @@ public static String getJSONString(Object o, 
ObjectInspector oi) {
    * @return
    */
   public static String getJSONString(Object o, ObjectInspector oi, String 
nullStr) {
+    return getJSONString(o, oi, nullStr, false);
+  }
+
+  /**
+   * Use this for client-facing output: variant values, recognized by their 
physical
+   * {@code struct<metadata:binary,value:binary>} shape, are rendered as their 
logical JSON
+   * value instead of the raw metadata/value bytes.
+   */
+  public static String getJSONString(Object o, ObjectInspector oi, String 
nullStr, boolean decodeVariant) {
     StringBuilder sb = new StringBuilder();
-    buildJSONString(sb, o, oi, nullStr);
+    buildJSONString(sb, o, oi, nullStr, decodeVariant);
     return sb.toString();
   }
 
 
-  static void buildJSONString(StringBuilder sb, Object o, ObjectInspector oi, 
String nullStr) {
+  static void buildJSONString(StringBuilder sb, Object o, ObjectInspector oi, 
String nullStr,
+      boolean decodeVariant) {
     switch (oi.getCategory()) {
     case PRIMITIVE: {
       PrimitiveObjectInspector poi = (PrimitiveObjectInspector) oi;
@@ -322,7 +337,7 @@ static void buildJSONString(StringBuilder sb, Object o, 
ObjectInspector oi, Stri
           if (i > 0) {
             sb.append(COMMA);
           }
-          buildJSONString(sb, olist.get(i), listElementObjectInspector, 
JSON_NULL);
+          buildJSONString(sb, olist.get(i), listElementObjectInspector, 
JSON_NULL, decodeVariant);
         }
         sb.append(RBRACKET);
       }
@@ -346,9 +361,9 @@ static void buildJSONString(StringBuilder sb, Object o, 
ObjectInspector oi, Stri
             sb.append(COMMA);
           }
           Map.Entry<?, ?> e = (Map.Entry<?, ?>) entry;
-          buildJSONString(sb, e.getKey(), mapKeyObjectInspector, JSON_NULL);
+          buildJSONString(sb, e.getKey(), mapKeyObjectInspector, JSON_NULL, 
decodeVariant);
           sb.append(COLON);
-          buildJSONString(sb, e.getValue(), mapValueObjectInspector, 
JSON_NULL);
+          buildJSONString(sb, e.getValue(), mapValueObjectInspector, 
JSON_NULL, decodeVariant);
         }
         sb.append(RBRACE);
       }
@@ -356,24 +371,27 @@ static void buildJSONString(StringBuilder sb, Object o, 
ObjectInspector oi, Stri
     }
     case STRUCT: {
       StructObjectInspector soi = (StructObjectInspector) oi;
-      List<? extends StructField> structFields = soi.getAllStructFieldRefs();
       if (o == null) {
         sb.append(nullStr);
-      } else {
-        sb.append(LBRACE);
-        for (int i = 0; i < structFields.size(); i++) {
-          if (i > 0) {
-            sb.append(COMMA);
-          }
-          sb.append(QUOTE);
-          sb.append(structFields.get(i).getFieldName());
-          sb.append(QUOTE);
-          sb.append(COLON);
-          buildJSONString(sb, soi.getStructFieldData(o, structFields.get(i)),
-              structFields.get(i).getFieldObjectInspector(), JSON_NULL);
+        break;
+      }
+      if (decodeVariant && appendVariantJson(sb, o, soi)) {
+        break;
+      }
+      List<? extends StructField> structFields = soi.getAllStructFieldRefs();
+      sb.append(LBRACE);
+      for (int i = 0; i < structFields.size(); i++) {
+        if (i > 0) {
+          sb.append(COMMA);
         }
-        sb.append(RBRACE);
+        sb.append(QUOTE);
+        sb.append(structFields.get(i).getFieldName());
+        sb.append(QUOTE);
+        sb.append(COLON);
+        buildJSONString(sb, soi.getStructFieldData(o, structFields.get(i)),
+            structFields.get(i).getFieldObjectInspector(), JSON_NULL, 
decodeVariant);
       }
+      sb.append(RBRACE);
       break;
     }
     case UNION: {
@@ -385,7 +403,7 @@ static void buildJSONString(StringBuilder sb, Object o, 
ObjectInspector oi, Stri
         sb.append(uoi.getTag(o));
         sb.append(COLON);
         buildJSONString(sb, uoi.getField(o),
-              uoi.getObjectInspectors().get(uoi.getTag(o)), JSON_NULL);
+              uoi.getObjectInspectors().get(uoi.getTag(o)), JSON_NULL, 
decodeVariant);
         sb.append(RBRACE);
       }
       break;
@@ -395,6 +413,33 @@ static void buildJSONString(StringBuilder sb, Object o, 
ObjectInspector oi, Stri
     }
   }
 
+  /**
+   * Renders a variant value as its logical JSON and returns true. Variant 
values are recognized
+   * by their physical {@code struct<metadata:binary,value:binary>} shape, 
independent of the
+   * ObjectInspector flavor; returns false without output when the struct has 
a different shape
+   * or the bytes are not valid variant encoding, so the caller falls back to 
the generic struct
+   * rendering.
+   */
+  private static boolean appendVariantJson(StringBuilder sb, Object o, 
StructObjectInspector soi) {
+    if (!VARIANT_TYPE_NAME.equals(soi.getTypeName())) {
+      return false;
+    }
+    List<? extends StructField> fields = soi.getAllStructFieldRefs();
+    byte[] metadata = ((BinaryObjectInspector) 
fields.get(0).getFieldObjectInspector())
+        .getPrimitiveJavaObject(soi.getStructFieldData(o, fields.get(0)));
+    byte[] value = ((BinaryObjectInspector) 
fields.get(1).getFieldObjectInspector())
+        .getPrimitiveJavaObject(soi.getStructFieldData(o, fields.get(1)));
+    if (metadata == null || value == null) {
+      return false;
+    }
+    try {
+      sb.append(new Variant(value, metadata).toJson(ZoneOffset.UTC));
+      return true;
+    } catch (RuntimeException e) {
+      return false;
+    }
+  }
+
   /**
    * return false though element is null if nullsafe flag is true for that
    */
diff --git a/serde/src/java/org/apache/hadoop/hive/serde2/thrift/Type.java 
b/serde/src/java/org/apache/hadoop/hive/serde2/thrift/Type.java
index c4e51733b20..5522edf0e4e 100644
--- a/serde/src/java/org/apache/hadoop/hive/serde2/thrift/Type.java
+++ b/serde/src/java/org/apache/hadoop/hive/serde2/thrift/Type.java
@@ -24,6 +24,7 @@
 
 import org.apache.hadoop.hive.common.type.HiveDecimal;
 import org.apache.hadoop.hive.serde.serdeConstants;
+import 
org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector.PrimitiveCategory;
 import org.apache.hadoop.hive.serde2.typeinfo.PrimitiveTypeInfo;
 import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
 import org.apache.hive.service.rpc.thrift.TTypeId;
@@ -107,6 +108,10 @@ public enum Type {
       java.sql.Types.OTHER,
       TTypeId.UNION_TYPE,
       true, false),
+  VARIANT_TYPE(serdeConstants.VARIANT_TYPE_NAME.toUpperCase(),
+      java.sql.Types.OTHER,
+      TTypeId.VARIANT_TYPE,
+      true, false),
   USER_DEFINED_TYPE("USER_DEFINED",
       java.sql.Types.OTHER,
       TTypeId.USER_DEFINED_TYPE,
@@ -187,92 +192,44 @@ public static Type getType(String name) {
    * @return
    */
   public static Type getType(TypeInfo typeInfo) {
-    switch (typeInfo.getCategory()) {
-    case PRIMITIVE: {
-      PrimitiveTypeInfo pTypeInfo = (PrimitiveTypeInfo) typeInfo;
-      switch (pTypeInfo.getPrimitiveCategory()) {
-      case VOID: {
-        return Type.NULL_TYPE;
-      }
-      case BOOLEAN: {
-        return Type.BOOLEAN_TYPE;
-      }
+    return switch (typeInfo.getCategory()) {
+      case PRIMITIVE -> getType(((PrimitiveTypeInfo) 
typeInfo).getPrimitiveCategory());
       // Double check if this is the right mapping
-      case BYTE: {
-        return Type.BINARY_TYPE;
-      }
+      case LIST -> Type.STRING_TYPE;
+      case MAP -> Type.MAP_TYPE;
+      case STRUCT -> Type.STRUCT_TYPE;
+      case UNION -> Type.UNION_TYPE;
+      case VARIANT -> Type.VARIANT_TYPE;
+      case UNKNOWN -> Type.UNKNOWN_TYPE;
+    };
+  }
+
+  private static Type getType(PrimitiveCategory category) {
+    return switch (category) {
+      case VOID -> Type.NULL_TYPE;
+      case BOOLEAN -> Type.BOOLEAN_TYPE;
       // Double check if this is the right mapping
-      case SHORT: {
-        return Type.SMALLINT_TYPE;
-      }
-      case INT: {
-        return Type.INT_TYPE;
-      }
+      case BYTE -> Type.BINARY_TYPE;
       // Double check if this is the right mapping
-      case LONG: {
-        return Type.BIGINT_TYPE;
-      }
-      case FLOAT: {
-        return Type.FLOAT_TYPE;
-      }
-      case DOUBLE: {
-        return Type.DOUBLE_TYPE;
-      }
-      case STRING: {
-        return Type.STRING_TYPE;
-      }
-      case CHAR: {
-        return Type.CHAR_TYPE;
-      }
-      case VARCHAR: {
-        return Type.VARCHAR_TYPE;
-      }
-      case BINARY: {
-        return Type.BINARY_TYPE;
-      }
-      case DATE: {
-        return Type.DATE_TYPE;
-      }
-      case TIMESTAMP: {
-        return Type.TIMESTAMP_TYPE;
-      }
-      case TIMESTAMPLOCALTZ: {
-        return Type.TIMESTAMPLOCALTZ_TYPE;
-      }
-      case INTERVAL_YEAR_MONTH: {
-        return Type.INTERVAL_YEAR_MONTH_TYPE;
-      }
-      case INTERVAL_DAY_TIME: {
-        return Type.INTERVAL_DAY_TIME_TYPE;
-      }
-      case DECIMAL: {
-        return Type.DECIMAL_TYPE;
-      }
-      default: {
-        throw new RuntimeException("Unrecognized type: " + 
pTypeInfo.getPrimitiveCategory());
-      }
-      }
-    }
-    // Double check if this is the right mapping
-    case LIST: {
-      return Type.STRING_TYPE;
-    }
-    case MAP: {
-      return Type.MAP_TYPE;
-    }
-    case STRUCT: {
-      return Type.STRUCT_TYPE;
-    }
-    case UNION: {
-      return Type.UNION_TYPE;
-    }
-    case UNKNOWN: {
-      return Type.UNKNOWN_TYPE;
-    }
-    default: {
-      throw new RuntimeException("Unrecognized type: " + 
typeInfo.getCategory());
-    }
-    }
+      case SHORT -> Type.SMALLINT_TYPE;
+      case INT -> Type.INT_TYPE;
+      // Double check if this is the right mapping
+      case LONG -> Type.BIGINT_TYPE;
+      case FLOAT -> Type.FLOAT_TYPE;
+      case DOUBLE -> Type.DOUBLE_TYPE;
+      case STRING -> Type.STRING_TYPE;
+      case CHAR -> Type.CHAR_TYPE;
+      case VARCHAR -> Type.VARCHAR_TYPE;
+      case BINARY -> Type.BINARY_TYPE;
+      case DATE -> Type.DATE_TYPE;
+      case TIMESTAMP -> Type.TIMESTAMP_TYPE;
+      case TIMESTAMPLOCALTZ -> Type.TIMESTAMPLOCALTZ_TYPE;
+      case INTERVAL_YEAR_MONTH -> Type.INTERVAL_YEAR_MONTH_TYPE;
+      case INTERVAL_DAY_TIME -> Type.INTERVAL_DAY_TIME_TYPE;
+      case DECIMAL -> Type.DECIMAL_TYPE;
+      default ->
+          throw new RuntimeException("Unrecognized type: " + category);
+    };
   }
 
   /**
diff --git a/serde/src/test/org/apache/hadoop/hive/serde2/TestSerDeUtils.java 
b/serde/src/test/org/apache/hadoop/hive/serde2/TestSerDeUtils.java
new file mode 100644
index 00000000000..f0f1c070a87
--- /dev/null
+++ b/serde/src/test/org/apache/hadoop/hive/serde2/TestSerDeUtils.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.hadoop.hive.serde2;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
+import org.apache.hadoop.hive.serde2.thrift.Type;
+import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoUtils;
+import org.apache.hadoop.hive.serde2.variant.Variant;
+import org.apache.hadoop.hive.serde2.variant.VariantBuilder;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TestSerDeUtils {
+
+  private static ObjectInspector oi(String typeString) {
+    return TypeInfoUtils.getStandardJavaObjectInspectorFromTypeInfo(
+        TypeInfoUtils.getTypeInfoFromTypeString(typeString));
+  }
+
+  private static List<Object> variant(String json) throws Exception {
+    Variant v = VariantBuilder.parseJson(json, false);
+    return List.of(v.getMetadata(), v.getValue());
+  }
+
+  @Test
+  public void testGetJSONStringDecodesVariant() throws Exception {
+    String json = 
SerDeUtils.getJSONString(variant("{\"name\":\"John\",\"age\":30}"),
+        oi("struct<metadata:binary,value:binary>"), "null", true);
+    assertEquals("{\"age\":30,\"name\":\"John\"}", json);
+  }
+
+  @Test
+  public void testGetJSONStringDecodesNestedVariant() throws Exception {
+    Object row = Arrays.asList("x", variant("{\"k\":1}"));
+    String json = SerDeUtils.getJSONString(row, 
oi("struct<label:string,v:variant>"), "null", true);
+    assertEquals("{\"label\":\"x\",\"v\":{\"k\":1}}", json);
+  }
+
+  @Test
+  public void testGetJSONStringDecodesVariantInListAndMap() throws Exception {
+    Object list = Arrays.asList(variant("1"), null);
+    assertEquals("[1,null]", SerDeUtils.getJSONString(list, 
oi("array<variant>"), "null", true));
+
+    Object map = Map.of("k", variant("{\"x\":true}"));
+    assertEquals("{\"k\":{\"x\":true}}", SerDeUtils.getJSONString(map, 
oi("map<string,variant>"), "null", true));
+  }
+
+  @Test
+  public void testNonVariantBytesKeepGenericRendering() {
+    Object row = Arrays.asList(new byte[] {2}, new byte[] {65});
+    String json = SerDeUtils.getJSONString(row, 
oi("struct<metadata:binary,value:binary>"), "null", true);
+    assertEquals("{\"metadata\":\u0002,\"value\":A}", json);
+  }
+
+  @Test
+  public void testNullVariantFieldsKeepGenericRendering() {
+    Object row = Arrays.asList(null, null);
+    String json = SerDeUtils.getJSONString(row, 
oi("struct<metadata:binary,value:binary>"), "null", true);
+    assertEquals("{\"metadata\":null,\"value\":null}", json);
+  }
+
+  @Test
+  public void testVariantIsNotDecodedByDefault() throws Exception {
+    String json = SerDeUtils.getJSONString(variant("{\"k\":1}"), 
oi("struct<metadata:binary,value:binary>"));
+    assertTrue(json, json.startsWith("{\"metadata\":"));
+  }
+
+  @Test
+  public void testToThriftPayloadDecodesVariant() throws Exception {
+    Object payload = SerDeUtils.toThriftPayload(variant("{\"k\":1}"), 
oi("struct<metadata:binary,value:binary>"), 10);
+    assertEquals("{\"k\":1}", payload);
+  }
+
+  @Test
+  public void testThriftTypeForVariant() {
+    assertEquals(Type.VARIANT_TYPE, Type.getType("variant"));
+    assertEquals(Type.VARIANT_TYPE, 
Type.getType(TypeInfoUtils.getTypeInfoFromTypeString("variant")));
+  }
+}
diff --git a/service-rpc/if/TCLIService.thrift 
b/service-rpc/if/TCLIService.thrift
index cbca32b6e06..8f9720de58a 100644
--- a/service-rpc/if/TCLIService.thrift
+++ b/service-rpc/if/TCLIService.thrift
@@ -69,6 +69,9 @@ enum TProtocolVersion {
 
   // V11 adds timestamp with local time zone type
   HIVE_CLI_SERVICE_PROTOCOL_V11
+
+  // V12 adds variant type
+  HIVE_CLI_SERVICE_PROTOCOL_V12
 }
 
 enum TTypeId {
@@ -95,7 +98,8 @@ enum TTypeId {
   INTERVAL_YEAR_MONTH_TYPE,
   INTERVAL_DAY_TIME_TYPE,
   TIMESTAMPLOCALTZ_TYPE,
-  UNKNOWN_TYPE
+  UNKNOWN_TYPE,
+  VARIANT_TYPE
 }
 
 const set<TTypeId> PRIMITIVE_TYPES = [
@@ -126,6 +130,7 @@ const set<TTypeId> COMPLEX_TYPES = [
   TTypeId.STRUCT_TYPE
   TTypeId.UNION_TYPE
   TTypeId.USER_DEFINED_TYPE
+  TTypeId.VARIANT_TYPE
 ]
 
 const set<TTypeId> COLLECTION_TYPES = [
@@ -157,6 +162,7 @@ const map<TTypeId,string> TYPE_NAMES = {
   TTypeId.INTERVAL_DAY_TIME_TYPE: "INTERVAL_DAY_TIME"
   TTypeId.TIMESTAMPLOCALTZ_TYPE: "TIMESTAMP WITH LOCAL TIME ZONE"
   TTypeId.UNKNOWN_TYPE: "UNKNOWN"
+  TTypeId.VARIANT_TYPE: "VARIANT"
 }
 
 // Thrift does not support recursively defined types or forward declarations,
diff --git a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_constants.cpp 
b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_constants.cpp
index ae0e3039f30..583be0f6dfd 100644
--- a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_constants.cpp
+++ b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_constants.cpp
@@ -36,6 +36,7 @@ TCLIServiceConstants::TCLIServiceConstants() {
   COMPLEX_TYPES.insert((TTypeId::type)12);
   COMPLEX_TYPES.insert((TTypeId::type)13);
   COMPLEX_TYPES.insert((TTypeId::type)14);
+  COMPLEX_TYPES.insert((TTypeId::type)24);
 
   COLLECTION_TYPES.insert((TTypeId::type)10);
   COLLECTION_TYPES.insert((TTypeId::type)11);
@@ -63,6 +64,7 @@ TCLIServiceConstants::TCLIServiceConstants() {
   TYPE_NAMES.insert(std::make_pair((TTypeId::type)13, "UNIONTYPE"));
   TYPE_NAMES.insert(std::make_pair((TTypeId::type)23, "UNKNOWN"));
   TYPE_NAMES.insert(std::make_pair((TTypeId::type)18, "VARCHAR"));
+  TYPE_NAMES.insert(std::make_pair((TTypeId::type)24, "VARIANT"));
 
   CHARACTER_MAXIMUM_LENGTH = "characterMaximumLength";
 
diff --git a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.cpp 
b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.cpp
index d7611ca49ff..4d18c8bf715 100644
--- a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.cpp
+++ b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.cpp
@@ -24,7 +24,8 @@ int _kTProtocolVersionValues[] = {
   TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V8,
   TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V9,
   TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V10,
-  TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V11
+  TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V11,
+  TProtocolVersion::HIVE_CLI_SERVICE_PROTOCOL_V12
 };
 const char* _kTProtocolVersionNames[] = {
   "HIVE_CLI_SERVICE_PROTOCOL_V1",
@@ -37,9 +38,10 @@ const char* _kTProtocolVersionNames[] = {
   "HIVE_CLI_SERVICE_PROTOCOL_V8",
   "HIVE_CLI_SERVICE_PROTOCOL_V9",
   "HIVE_CLI_SERVICE_PROTOCOL_V10",
-  "HIVE_CLI_SERVICE_PROTOCOL_V11"
+  "HIVE_CLI_SERVICE_PROTOCOL_V11",
+  "HIVE_CLI_SERVICE_PROTOCOL_V12"
 };
-const std::map<int, const char*> 
_TProtocolVersion_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(11, 
_kTProtocolVersionValues, _kTProtocolVersionNames), 
::apache::thrift::TEnumIterator(-1, nullptr, nullptr));
+const std::map<int, const char*> 
_TProtocolVersion_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(12, 
_kTProtocolVersionValues, _kTProtocolVersionNames), 
::apache::thrift::TEnumIterator(-1, nullptr, nullptr));
 
 std::ostream& operator<<(std::ostream& out, const TProtocolVersion::type& val) 
{
   std::map<int, const char*>::const_iterator it = 
_TProtocolVersion_VALUES_TO_NAMES.find(val);
@@ -84,7 +86,8 @@ int _kTTypeIdValues[] = {
   TTypeId::INTERVAL_YEAR_MONTH_TYPE,
   TTypeId::INTERVAL_DAY_TIME_TYPE,
   TTypeId::TIMESTAMPLOCALTZ_TYPE,
-  TTypeId::UNKNOWN_TYPE
+  TTypeId::UNKNOWN_TYPE,
+  TTypeId::VARIANT_TYPE
 };
 const char* _kTTypeIdNames[] = {
   "BOOLEAN_TYPE",
@@ -110,9 +113,10 @@ const char* _kTTypeIdNames[] = {
   "INTERVAL_YEAR_MONTH_TYPE",
   "INTERVAL_DAY_TIME_TYPE",
   "TIMESTAMPLOCALTZ_TYPE",
-  "UNKNOWN_TYPE"
+  "UNKNOWN_TYPE",
+  "VARIANT_TYPE"
 };
-const std::map<int, const char*> 
_TTypeId_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(24, _kTTypeIdValues, 
_kTTypeIdNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr));
+const std::map<int, const char*> 
_TTypeId_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(25, _kTTypeIdValues, 
_kTTypeIdNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr));
 
 std::ostream& operator<<(std::ostream& out, const TTypeId::type& val) {
   std::map<int, const char*>::const_iterator it = 
_TTypeId_VALUES_TO_NAMES.find(val);
diff --git a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.h 
b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.h
index c1b6fa3d0ef..cfe0e8d6b18 100644
--- a/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.h
+++ b/service-rpc/src/gen/thrift/gen-cpp/TCLIService_types.h
@@ -33,7 +33,8 @@ struct TProtocolVersion {
     HIVE_CLI_SERVICE_PROTOCOL_V8 = 7,
     HIVE_CLI_SERVICE_PROTOCOL_V9 = 8,
     HIVE_CLI_SERVICE_PROTOCOL_V10 = 9,
-    HIVE_CLI_SERVICE_PROTOCOL_V11 = 10
+    HIVE_CLI_SERVICE_PROTOCOL_V11 = 10,
+    HIVE_CLI_SERVICE_PROTOCOL_V12 = 11
   };
 };
 
@@ -68,7 +69,8 @@ struct TTypeId {
     INTERVAL_YEAR_MONTH_TYPE = 20,
     INTERVAL_DAY_TIME_TYPE = 21,
     TIMESTAMPLOCALTZ_TYPE = 22,
-    UNKNOWN_TYPE = 23
+    UNKNOWN_TYPE = 23,
+    VARIANT_TYPE = 24
   };
 };
 
diff --git 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TCLIServiceConstants.java
 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TCLIServiceConstants.java
index e525f2cdae3..8fa4b327fb0 100644
--- 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TCLIServiceConstants.java
+++ 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TCLIServiceConstants.java
@@ -39,6 +39,7 @@
     COMPLEX_TYPES.add(org.apache.hive.service.rpc.thrift.TTypeId.STRUCT_TYPE);
     COMPLEX_TYPES.add(org.apache.hive.service.rpc.thrift.TTypeId.UNION_TYPE);
     
COMPLEX_TYPES.add(org.apache.hive.service.rpc.thrift.TTypeId.USER_DEFINED_TYPE);
+    COMPLEX_TYPES.add(org.apache.hive.service.rpc.thrift.TTypeId.VARIANT_TYPE);
   }
 
   public static final java.util.Set<TTypeId> COLLECTION_TYPES = 
java.util.EnumSet.noneOf(TTypeId.class);
@@ -72,6 +73,7 @@
     TYPE_NAMES.put(org.apache.hive.service.rpc.thrift.TTypeId.UNION_TYPE, 
"UNIONTYPE");
     TYPE_NAMES.put(org.apache.hive.service.rpc.thrift.TTypeId.UNKNOWN_TYPE, 
"UNKNOWN");
     TYPE_NAMES.put(org.apache.hive.service.rpc.thrift.TTypeId.VARCHAR_TYPE, 
"VARCHAR");
+    TYPE_NAMES.put(org.apache.hive.service.rpc.thrift.TTypeId.VARIANT_TYPE, 
"VARIANT");
   }
 
   public static final java.lang.String CHARACTER_MAXIMUM_LENGTH = 
"characterMaximumLength";
diff --git 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TProtocolVersion.java
 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TProtocolVersion.java
index eb7742dd93d..3b067049572 100644
--- 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TProtocolVersion.java
+++ 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TProtocolVersion.java
@@ -19,7 +19,8 @@ public enum TProtocolVersion implements 
org.apache.thrift.TEnum {
   HIVE_CLI_SERVICE_PROTOCOL_V8(7),
   HIVE_CLI_SERVICE_PROTOCOL_V9(8),
   HIVE_CLI_SERVICE_PROTOCOL_V10(9),
-  HIVE_CLI_SERVICE_PROTOCOL_V11(10);
+  HIVE_CLI_SERVICE_PROTOCOL_V11(10),
+  HIVE_CLI_SERVICE_PROTOCOL_V12(11);
 
   private final int value;
 
@@ -63,6 +64,8 @@ public static TProtocolVersion findByValue(int value) {
         return HIVE_CLI_SERVICE_PROTOCOL_V10;
       case 10:
         return HIVE_CLI_SERVICE_PROTOCOL_V11;
+      case 11:
+        return HIVE_CLI_SERVICE_PROTOCOL_V12;
       default:
         return null;
     }
diff --git 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TTypeId.java
 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TTypeId.java
index 57108255c1c..0b566dc7945 100644
--- 
a/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TTypeId.java
+++ 
b/service-rpc/src/gen/thrift/gen-javabean/org/apache/hive/service/rpc/thrift/TTypeId.java
@@ -32,7 +32,8 @@ public enum TTypeId implements org.apache.thrift.TEnum {
   INTERVAL_YEAR_MONTH_TYPE(20),
   INTERVAL_DAY_TIME_TYPE(21),
   TIMESTAMPLOCALTZ_TYPE(22),
-  UNKNOWN_TYPE(23);
+  UNKNOWN_TYPE(23),
+  VARIANT_TYPE(24);
 
   private final int value;
 
@@ -102,6 +103,8 @@ public static TTypeId findByValue(int value) {
         return TIMESTAMPLOCALTZ_TYPE;
       case 23:
         return UNKNOWN_TYPE;
+      case 24:
+        return VARIANT_TYPE;
       default:
         return null;
     }
diff --git a/service-rpc/src/gen/thrift/gen-php/Constant.php 
b/service-rpc/src/gen/thrift/gen-php/Constant.php
index 59b6c40636c..5f45ad5ba61 100644
--- a/service-rpc/src/gen/thrift/gen-php/Constant.php
+++ b/service-rpc/src/gen/thrift/gen-php/Constant.php
@@ -57,6 +57,7 @@ final class Constant extends \Thrift\Type\TConstant
                         12 => true,
                         13 => true,
                         14 => true,
+                        24 => true,
         );
     }
 
@@ -94,6 +95,7 @@ final class Constant extends \Thrift\Type\TConstant
                         13 => "UNIONTYPE",
                         23 => "UNKNOWN",
                         18 => "VARCHAR",
+                        24 => "VARIANT",
         );
     }
 
diff --git a/service-rpc/src/gen/thrift/gen-php/TProtocolVersion.php 
b/service-rpc/src/gen/thrift/gen-php/TProtocolVersion.php
index b27fb349010..cc4a8e06c46 100644
--- a/service-rpc/src/gen/thrift/gen-php/TProtocolVersion.php
+++ b/service-rpc/src/gen/thrift/gen-php/TProtocolVersion.php
@@ -38,6 +38,8 @@ final class TProtocolVersion
 
     const HIVE_CLI_SERVICE_PROTOCOL_V11 = 10;
 
+    const HIVE_CLI_SERVICE_PROTOCOL_V12 = 11;
+
     static public $__names = array(
         0 => 'HIVE_CLI_SERVICE_PROTOCOL_V1',
         1 => 'HIVE_CLI_SERVICE_PROTOCOL_V2',
@@ -50,6 +52,7 @@ final class TProtocolVersion
         8 => 'HIVE_CLI_SERVICE_PROTOCOL_V9',
         9 => 'HIVE_CLI_SERVICE_PROTOCOL_V10',
         10 => 'HIVE_CLI_SERVICE_PROTOCOL_V11',
+        11 => 'HIVE_CLI_SERVICE_PROTOCOL_V12',
     );
 }
 
diff --git a/service-rpc/src/gen/thrift/gen-php/TTypeId.php 
b/service-rpc/src/gen/thrift/gen-php/TTypeId.php
index ac799c4a173..aa4a6b1a7c2 100644
--- a/service-rpc/src/gen/thrift/gen-php/TTypeId.php
+++ b/service-rpc/src/gen/thrift/gen-php/TTypeId.php
@@ -64,6 +64,8 @@ final class TTypeId
 
     const UNKNOWN_TYPE = 23;
 
+    const VARIANT_TYPE = 24;
+
     static public $__names = array(
         0 => 'BOOLEAN_TYPE',
         1 => 'TINYINT_TYPE',
@@ -89,6 +91,7 @@ final class TTypeId
         21 => 'INTERVAL_DAY_TIME_TYPE',
         22 => 'TIMESTAMPLOCALTZ_TYPE',
         23 => 'UNKNOWN_TYPE',
+        24 => 'VARIANT_TYPE',
     );
 }
 
diff --git a/service-rpc/src/gen/thrift/gen-py/TCLIService/constants.py 
b/service-rpc/src/gen/thrift/gen-py/TCLIService/constants.py
index 769dc57c9b8..61c8107da3e 100644
--- a/service-rpc/src/gen/thrift/gen-py/TCLIService/constants.py
+++ b/service-rpc/src/gen/thrift/gen-py/TCLIService/constants.py
@@ -39,6 +39,7 @@
     12,
     13,
     14,
+    24,
 ))
 COLLECTION_TYPES = set((
     10,
@@ -68,6 +69,7 @@
     13: "UNIONTYPE",
     23: "UNKNOWN",
     18: "VARCHAR",
+    24: "VARIANT",
 }
 CHARACTER_MAXIMUM_LENGTH = "characterMaximumLength"
 PRECISION = "precision"
diff --git a/service-rpc/src/gen/thrift/gen-py/TCLIService/ttypes.py 
b/service-rpc/src/gen/thrift/gen-py/TCLIService/ttypes.py
index 9bb79b5e264..0c52270e3db 100644
--- a/service-rpc/src/gen/thrift/gen-py/TCLIService/ttypes.py
+++ b/service-rpc/src/gen/thrift/gen-py/TCLIService/ttypes.py
@@ -28,6 +28,7 @@ class TProtocolVersion(object):
     HIVE_CLI_SERVICE_PROTOCOL_V9 = 8
     HIVE_CLI_SERVICE_PROTOCOL_V10 = 9
     HIVE_CLI_SERVICE_PROTOCOL_V11 = 10
+    HIVE_CLI_SERVICE_PROTOCOL_V12 = 11
 
     _VALUES_TO_NAMES = {
         0: "HIVE_CLI_SERVICE_PROTOCOL_V1",
@@ -41,6 +42,7 @@ class TProtocolVersion(object):
         8: "HIVE_CLI_SERVICE_PROTOCOL_V9",
         9: "HIVE_CLI_SERVICE_PROTOCOL_V10",
         10: "HIVE_CLI_SERVICE_PROTOCOL_V11",
+        11: "HIVE_CLI_SERVICE_PROTOCOL_V12",
     }
 
     _NAMES_TO_VALUES = {
@@ -55,6 +57,7 @@ class TProtocolVersion(object):
         "HIVE_CLI_SERVICE_PROTOCOL_V9": 8,
         "HIVE_CLI_SERVICE_PROTOCOL_V10": 9,
         "HIVE_CLI_SERVICE_PROTOCOL_V11": 10,
+        "HIVE_CLI_SERVICE_PROTOCOL_V12": 11,
     }
 
 
@@ -83,6 +86,7 @@ class TTypeId(object):
     INTERVAL_DAY_TIME_TYPE = 21
     TIMESTAMPLOCALTZ_TYPE = 22
     UNKNOWN_TYPE = 23
+    VARIANT_TYPE = 24
 
     _VALUES_TO_NAMES = {
         0: "BOOLEAN_TYPE",
@@ -109,6 +113,7 @@ class TTypeId(object):
         21: "INTERVAL_DAY_TIME_TYPE",
         22: "TIMESTAMPLOCALTZ_TYPE",
         23: "UNKNOWN_TYPE",
+        24: "VARIANT_TYPE",
     }
 
     _NAMES_TO_VALUES = {
@@ -136,6 +141,7 @@ class TTypeId(object):
         "INTERVAL_DAY_TIME_TYPE": 21,
         "TIMESTAMPLOCALTZ_TYPE": 22,
         "UNKNOWN_TYPE": 23,
+        "VARIANT_TYPE": 24,
     }
 
 
diff --git a/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_constants.rb 
b/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_constants.rb
index 8aca726814b..c1771bd2af1 100644
--- a/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_constants.rb
+++ b/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_constants.rb
@@ -35,6 +35,7 @@ COMPLEX_TYPES = Set.new([
     12,
     13,
     14,
+    24,
 ])
 
 COLLECTION_TYPES = Set.new([
@@ -66,6 +67,7 @@ TYPE_NAMES = {
     13 => %q"UNIONTYPE",
     23 => %q"UNKNOWN",
     18 => %q"VARCHAR",
+    24 => %q"VARIANT",
 }
 
 CHARACTER_MAXIMUM_LENGTH = %q"characterMaximumLength"
diff --git a/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_types.rb 
b/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_types.rb
index 6a4e3fbd971..8e6eb56ad50 100644
--- a/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_types.rb
+++ b/service-rpc/src/gen/thrift/gen-rb/t_c_l_i_service_types.rb
@@ -18,8 +18,9 @@ module TProtocolVersion
   HIVE_CLI_SERVICE_PROTOCOL_V9 = 8
   HIVE_CLI_SERVICE_PROTOCOL_V10 = 9
   HIVE_CLI_SERVICE_PROTOCOL_V11 = 10
-  VALUE_MAP = {0 => "HIVE_CLI_SERVICE_PROTOCOL_V1", 1 => 
"HIVE_CLI_SERVICE_PROTOCOL_V2", 2 => "HIVE_CLI_SERVICE_PROTOCOL_V3", 3 => 
"HIVE_CLI_SERVICE_PROTOCOL_V4", 4 => "HIVE_CLI_SERVICE_PROTOCOL_V5", 5 => 
"HIVE_CLI_SERVICE_PROTOCOL_V6", 6 => "HIVE_CLI_SERVICE_PROTOCOL_V7", 7 => 
"HIVE_CLI_SERVICE_PROTOCOL_V8", 8 => "HIVE_CLI_SERVICE_PROTOCOL_V9", 9 => 
"HIVE_CLI_SERVICE_PROTOCOL_V10", 10 => "HIVE_CLI_SERVICE_PROTOCOL_V11"}
-  VALID_VALUES = Set.new([HIVE_CLI_SERVICE_PROTOCOL_V1, 
HIVE_CLI_SERVICE_PROTOCOL_V2, HIVE_CLI_SERVICE_PROTOCOL_V3, 
HIVE_CLI_SERVICE_PROTOCOL_V4, HIVE_CLI_SERVICE_PROTOCOL_V5, 
HIVE_CLI_SERVICE_PROTOCOL_V6, HIVE_CLI_SERVICE_PROTOCOL_V7, 
HIVE_CLI_SERVICE_PROTOCOL_V8, HIVE_CLI_SERVICE_PROTOCOL_V9, 
HIVE_CLI_SERVICE_PROTOCOL_V10, HIVE_CLI_SERVICE_PROTOCOL_V11]).freeze
+  HIVE_CLI_SERVICE_PROTOCOL_V12 = 11
+  VALUE_MAP = {0 => "HIVE_CLI_SERVICE_PROTOCOL_V1", 1 => 
"HIVE_CLI_SERVICE_PROTOCOL_V2", 2 => "HIVE_CLI_SERVICE_PROTOCOL_V3", 3 => 
"HIVE_CLI_SERVICE_PROTOCOL_V4", 4 => "HIVE_CLI_SERVICE_PROTOCOL_V5", 5 => 
"HIVE_CLI_SERVICE_PROTOCOL_V6", 6 => "HIVE_CLI_SERVICE_PROTOCOL_V7", 7 => 
"HIVE_CLI_SERVICE_PROTOCOL_V8", 8 => "HIVE_CLI_SERVICE_PROTOCOL_V9", 9 => 
"HIVE_CLI_SERVICE_PROTOCOL_V10", 10 => "HIVE_CLI_SERVICE_PROTOCOL_V11", 11 => 
"HIVE_CLI_SERVICE_PROTOCOL_V12"}
+  VALID_VALUES = Set.new([HIVE_CLI_SERVICE_PROTOCOL_V1, 
HIVE_CLI_SERVICE_PROTOCOL_V2, HIVE_CLI_SERVICE_PROTOCOL_V3, 
HIVE_CLI_SERVICE_PROTOCOL_V4, HIVE_CLI_SERVICE_PROTOCOL_V5, 
HIVE_CLI_SERVICE_PROTOCOL_V6, HIVE_CLI_SERVICE_PROTOCOL_V7, 
HIVE_CLI_SERVICE_PROTOCOL_V8, HIVE_CLI_SERVICE_PROTOCOL_V9, 
HIVE_CLI_SERVICE_PROTOCOL_V10, HIVE_CLI_SERVICE_PROTOCOL_V11, 
HIVE_CLI_SERVICE_PROTOCOL_V12]).freeze
 end
 
 module TTypeId
@@ -47,8 +48,9 @@ module TTypeId
   INTERVAL_DAY_TIME_TYPE = 21
   TIMESTAMPLOCALTZ_TYPE = 22
   UNKNOWN_TYPE = 23
-  VALUE_MAP = {0 => "BOOLEAN_TYPE", 1 => "TINYINT_TYPE", 2 => "SMALLINT_TYPE", 
3 => "INT_TYPE", 4 => "BIGINT_TYPE", 5 => "FLOAT_TYPE", 6 => "DOUBLE_TYPE", 7 
=> "STRING_TYPE", 8 => "TIMESTAMP_TYPE", 9 => "BINARY_TYPE", 10 => 
"ARRAY_TYPE", 11 => "MAP_TYPE", 12 => "STRUCT_TYPE", 13 => "UNION_TYPE", 14 => 
"USER_DEFINED_TYPE", 15 => "DECIMAL_TYPE", 16 => "NULL_TYPE", 17 => 
"DATE_TYPE", 18 => "VARCHAR_TYPE", 19 => "CHAR_TYPE", 20 => 
"INTERVAL_YEAR_MONTH_TYPE", 21 => "INTERVAL_DAY_TIME_TYPE", 2 [...]
-  VALID_VALUES = Set.new([BOOLEAN_TYPE, TINYINT_TYPE, SMALLINT_TYPE, INT_TYPE, 
BIGINT_TYPE, FLOAT_TYPE, DOUBLE_TYPE, STRING_TYPE, TIMESTAMP_TYPE, BINARY_TYPE, 
ARRAY_TYPE, MAP_TYPE, STRUCT_TYPE, UNION_TYPE, USER_DEFINED_TYPE, DECIMAL_TYPE, 
NULL_TYPE, DATE_TYPE, VARCHAR_TYPE, CHAR_TYPE, INTERVAL_YEAR_MONTH_TYPE, 
INTERVAL_DAY_TIME_TYPE, TIMESTAMPLOCALTZ_TYPE, UNKNOWN_TYPE]).freeze
+  VARIANT_TYPE = 24
+  VALUE_MAP = {0 => "BOOLEAN_TYPE", 1 => "TINYINT_TYPE", 2 => "SMALLINT_TYPE", 
3 => "INT_TYPE", 4 => "BIGINT_TYPE", 5 => "FLOAT_TYPE", 6 => "DOUBLE_TYPE", 7 
=> "STRING_TYPE", 8 => "TIMESTAMP_TYPE", 9 => "BINARY_TYPE", 10 => 
"ARRAY_TYPE", 11 => "MAP_TYPE", 12 => "STRUCT_TYPE", 13 => "UNION_TYPE", 14 => 
"USER_DEFINED_TYPE", 15 => "DECIMAL_TYPE", 16 => "NULL_TYPE", 17 => 
"DATE_TYPE", 18 => "VARCHAR_TYPE", 19 => "CHAR_TYPE", 20 => 
"INTERVAL_YEAR_MONTH_TYPE", 21 => "INTERVAL_DAY_TIME_TYPE", 2 [...]
+  VALID_VALUES = Set.new([BOOLEAN_TYPE, TINYINT_TYPE, SMALLINT_TYPE, INT_TYPE, 
BIGINT_TYPE, FLOAT_TYPE, DOUBLE_TYPE, STRING_TYPE, TIMESTAMP_TYPE, BINARY_TYPE, 
ARRAY_TYPE, MAP_TYPE, STRUCT_TYPE, UNION_TYPE, USER_DEFINED_TYPE, DECIMAL_TYPE, 
NULL_TYPE, DATE_TYPE, VARCHAR_TYPE, CHAR_TYPE, INTERVAL_YEAR_MONTH_TYPE, 
INTERVAL_DAY_TIME_TYPE, TIMESTAMPLOCALTZ_TYPE, UNKNOWN_TYPE, 
VARIANT_TYPE]).freeze
 end
 
 module TStatusCode
diff --git a/service/src/java/org/apache/hive/service/cli/ColumnValue.java 
b/service/src/java/org/apache/hive/service/cli/ColumnValue.java
index 77cb4bda748..b6412ab4211 100644
--- a/service/src/java/org/apache/hive/service/cli/ColumnValue.java
+++ b/service/src/java/org/apache/hive/service/cli/ColumnValue.java
@@ -212,11 +212,7 @@ public static TColumnValue toTColumnValue(TypeDescriptor 
typeDescriptor, Object
       return stringValue((HiveDecimal)value, typeDescriptor);
     case BINARY_TYPE:
       return stringValue((String)value);
-    case ARRAY_TYPE:
-    case MAP_TYPE:
-    case STRUCT_TYPE:
-    case UNION_TYPE:
-    case USER_DEFINED_TYPE:
+    case ARRAY_TYPE, MAP_TYPE, STRUCT_TYPE, UNION_TYPE, VARIANT_TYPE, 
USER_DEFINED_TYPE:
       return stringValue((String)value);
     case NULL_TYPE:
       return stringValue((String)value);

Reply via email to