GitHub user nevzheng edited a comment on the discussion: [DISCUSS] Iceberg V3 
type support in Gravitino

# Proposal: Native `variant` type support in Gravitino

Status: draft / notes

## Summary

Promote Iceberg V3 `variant` from the `ExternalType("variant")` stopgap 
(#11928) to a
first-class `VariantType` leaf. It stays opaque and just gains a real identity 
in the
type model. Small change: at the schema level a variant is a parameterless 
leaf, the
same shape as the existing `UUIDType`.

## What a variant is

A column type for arbitrary, JSON-like semi-structured data whose shape varies 
row to
row. At the schema level it is the bare, parameterless word `variant`: all 
structure
lives in the values, not the type. A metadata catalog sees one opaque leaf.

## Current state

Gravitino only records that a column is a variant; it never reads values. The 
IRC path
already handled it (#10994); the native converter (#11927) threw 
`UnsupportedOperationException:
Unsupported type: variant`. After #11928 it maps to 
`ExternalType.of("variant")`, so the
column loads. That is already a relabeled blob, but as a string, not a type: 
nothing can
branch on it or map it deliberately per connector.

## The change

Serde here is of the schema, not values: a `Type` object round-trips to JSON to 
cross the
REST API and the entity store. A variant leaf serializes to the bare token 
`"variant"`
(like `uuid`), so this is four small edits:

- `Type.Name.VARIANT` - new enum tag.
- `Types.VariantType` - singleton leaf extending `PrimitiveType` (a `UUIDType` 
clone).
- `JsonUtils` - add `VariantType.get()` to the primitive `TYPES` map (read) and 
a
  `case VARIANT` in `writeDataType` (write).
- `FromIcebergType.variant()` - return `Types.VariantType.get()` instead of 
`ExternalType`.

Out of scope: introspection (no sub-fields, no `variant_get`-style accessors). 
Those are
query-engine operations on row data, not catalog metadata.

## Decisions and trade-offs

- **Extend `PrimitiveType`** (not `ComplexType`, not raw `Type`): a 
parameterless childless
  leaf fits the primitive serde path with no bespoke code. Risk: "primitive" 
reads oddly
  for semi-structured data, but the base class classifies by structure and 
`uuid`/`binary`
  set the precedent.
- **Iceberg-first**: other connectors keep mapping variant to `ExternalType`. 
Adding
  `Name.VARIANT` touches every `Type.Name` switch (Hive, Glue, Paimon, Lance, 
Trino,
  Flink, JDBC, Spark), but only Iceberg emits one today. Risk: a new constant 
can reach a
  switch `default` elsewhere; audit each and convert connectors individually 
later.
- **Why not stay external**: `external(serial)` is unqueryable via Trino 
(#10957) and
  `catalogString()` has been written verbatim into DDL SQL unvalidated 
(#11805). A modeled
  identity avoids passing an opaque string through those paths.

## PoC
https://github.com/apache/gravitino/pull/11932


## References

- #11927 (open, bug) - native metadata API fails to load V3 variant columns.
- #11928 (open, PR) - the `ExternalType` stopgap this builds on.
- #10994 (closed) - V3 variant in the Iceberg REST server JDBC backend (the IRC 
path).
- #3500 (closed) - `ExternalType` for unsolved catalog type (the escape hatch's 
origin).
- #10957 (open) - `external(serial)` unqueryable via Trino.
- #11805 (open) - `ExternalType.catalogString()` written verbatim into DDL SQL.


GitHub link: 
https://github.com/apache/gravitino/discussions/11929#discussioncomment-17567285

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]

Reply via email to