Skip to content

Add VARIANT_JSON and VARIANT_BINARY client capabilities#29046

Open
dain wants to merge 2 commits intomasterfrom
user/dain/variant-client
Open

Add VARIANT_JSON and VARIANT_BINARY client capabilities#29046
dain wants to merge 2 commits intomasterfrom
user/dain/variant-client

Conversation

@dain
Copy link
Copy Markdown
Member

@dain dain commented Apr 8, 2026

Description

Add VARIANT_JSON client capability, which when missing sends the data as JSONtype.

Add VARIANT_BINARY client capability for JDBC. JDBC can access VARIANT columns using:

  • rs.getObject(column) or rs.getObject(column, io.trino.jdbc.Variant.class) to get an io.trino.jdbc.Variant
  • rs.getObject(column, Object.class) to get the corresponding Java object representation
  • rs.getObject(column, Map.class) or rs.getObject(column, List.class) for top-level object and array values

When using rs.getObject(column, Object.class), VARIANT values map to Java objects as follows:

VARIANT value Java object
null null
boolean Boolean
int8 Byte
int16 Short
int32 Integer
int64 Long
float Float
double Double
decimal BigDecimal
string String
binary byte[]
date LocalDate
time(ntz) LocalTime
timestamp(utc) Instant
timestamp(ntz) LocalDateTime
uuid UUID
array List<Object>
object Map<String, Object>

The io.trino.jdbc.Variant class is a minimal implementation of VARIANT for decoding data. This supports getting the raw underlying byte arrays for the value and metdata is the client wants to interact with the raw data (storing in a separate system directly without reencoding). The SPI Variant class cannot be used because it is compiled with Java 25.

Release notes

(x) Release notes are required, with the following suggested text:

## CLI, JDBC
* Add support for VARIANT type. ({issue}`29046`)

@cla-bot cla-bot bot added the cla-signed label Apr 8, 2026
Copy link
Copy Markdown
Member

@findepi findepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except TestJsonEncodingUtils which i didn't review. Seek review from @wendigo .

Add a test in TestJdbcResultSet too.

verify(!types.isEmpty(), "Columns must not be empty");

boolean supportsParametricDateTime = requireNonNull(session, "session is null")
Session querySession = requireNonNull(session, "session is null");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Session querySession = requireNonNull(session, "session is null");
_ clientCapabilities = session.getClientCapabilities();

@findepi findepi requested review from findinpath and wendigo April 8, 2026 19:44
@dain dain force-pushed the user/dain/variant-client branch from c1da882 to aba2fc7 Compare April 9, 2026 01:35
@github-actions github-actions bot added the jdbc Relates to Trino JDBC driver label Apr 9, 2026
@dain dain changed the title Add VARIANT_JSON client capability with fallback to JSON Add VARIANT_JSON and VARIANT_BINARY client capabilities Apr 9, 2026
@dain dain requested a review from findepi April 9, 2026 01:35
@dain dain force-pushed the user/dain/variant-client branch from aba2fc7 to 1ccf36e Compare April 9, 2026 02:31
@ebyhr
Copy link
Copy Markdown
Member

ebyhr commented Apr 9, 2026

Please don't remove ({issue}`issuenumber`) placeholder from PR description. You should put the PR number when the relevant issue doesn't exist.

@dain
Copy link
Copy Markdown
Member Author

dain commented Apr 9, 2026

Please don't remove ({issue}`issuenumber`) placeholder from PR description. You should put the PR number when the relevant issue doesn't exist.

There is no relevant issue. Someone modified the message to say it fixes a bug, but there is no bug since variant is unreleased.

@dain dain force-pushed the user/dain/variant-client branch from 1ccf36e to 308d8d8 Compare April 9, 2026 04:45
@findinpath
Copy link
Copy Markdown
Contributor

Tested with trino cli 468


trino> select CAST(JSON '{"a":1,"b":true}' AS VARIANT);
      _col0       
------------------
 {"a":1,"b":true} 
(1 row)

Query 20260409_160644_00026_n6pci, FINISHED, 1 node
http://localhost:8080/ui/query.html?20260409_160644_00026_n6pci
Splits: 1 total, 1 done (100.00%)
CPU Time: 0.0s total,     0 rows/s,     0B/s, 0% active
Per Node: 0.0 parallelism,     0 rows/s,     0B/s
Parallelism: 0.0
Peak Memory: 256B
0.12 [0 rows, 0B] [0 rows/s, 0B/s]

trino> select CAST(42 AS VARIANT);
 _col0 
-------
 42    
(1 row)

* Whether client supports the `VARIANT` type encoded as a binary payload on the wire.
* This capability is opt-in, so clients continue to receive the JSON representation by default.
*/
VARIANT_BINARY(false),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be mentioned in docs/src/main/sphinx/develop/client-protocol.md ?

I'm seeing in io.trino.jdbc.TrinoConnection#startQuery that it is being added without any checks.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see Client-Capabilities documented anywhere

import static java.util.Collections.unmodifiableMap;
import static java.util.Objects.requireNonNull;

public final class Variant
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document

return result;
})
.add("variant", EncodedVariant.class, Variant.class, AbstractTrinoResultSet::decodeVariant)
.add("variant", EncodedVariant.class, Object.class, value -> decodeVariant(value).toObject())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rs.getObject(column, Object.class) to get the corresponding Java object representation

i don't think this is how getObject should work.
i would expect rs.getObject(column, Object.class) to return same thing as rs.getObject(column) does.

does JDBC spec define this?

.put("interval day to second", TrinoIntervalDayTime.class)
.put("map", Map.class)
.put("row", Row.class)
.put("variant", Variant.class)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using old JDBC, user gets VARIANT as JSON.
When using new JDBC, if user wants JSON for the VARIANT, what should they do?

}

if (columnInfo.getColumnTypeSignature().getRawType().equals("variant") && value instanceof String) {
return value;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove.
This looks like shadowing DEFAULT_OBJECT_REPRESENTATION entry.

Comment on lines +742 to +744
if (columnType.getRawType().equals("variant") && value instanceof String) {
return value;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove.
This looks like shadowing DEFAULT_OBJECT_REPRESENTATION entry.

Comment on lines +207 to +208
.add("variant", EncodedVariant.class, Map.class, value -> variantToMap(decodeVariant(value)))
.add("variant", EncodedVariant.class, List.class, value -> variantToList(decodeVariant(value)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not convinced about this additional conversions.
They might be useful, but they also prevent as from adding rs.getObject(col, String.class) that would return VARIANT-as-JSON data.

Let's not add them in this PR.
User can always invoke a specific method/cast on the Variant object

Comment on lines +1872 to +1877
if (columnTypeSignature.getRawType().equals("variant") && object instanceof String) {
if (type.isInstance(object)) {
return type.cast(object);
}
throw new SQLException(format("Cannot convert VARIANT JSON text to %s", type));
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove.
This shadows TYPE_CONVERSIONS entry.

throws Exception
{
try (ConnectedStatement connectedStatement = newStatement()) {
checkRepresentation(connectedStatement.getStatement(), "CAST(NULL AS variant)", Types.JAVA_OBJECT, (rs, column) -> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does variant have NULL and NULL-value concepts like JSON?

Comment on lines +588 to +589
Variant variant = rs.getObject(column, Variant.class);
assertThat(rs.getObject(column)).isInstanceOf(Variant.class);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swap

assertThat(rs.getObject(column)).isInstanceOf(Variant.class);
assertThat(variant.valueType()).isEqualTo(Variant.ValueType.DATE);
assertThat(variant.getLocalDate()).isEqualTo(LocalDate.of(2021, 2, 3));
assertThat(rs.getObject(column, Object.class)).isEqualTo(LocalDate.of(2021, 2, 3));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps cool, but let's leave it up to a future PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed jdbc Relates to Trino JDBC driver RELEASE-BLOCKER

Development

Successfully merging this pull request may close these issues.

4 participants