[FLINK-40491][core] Extend BinaryVariant with TIME, TIMESTAMP_NS, TIMESTAMP_LTZ_NS primitives - #29050
[FLINK-40491][core] Extend BinaryVariant with TIME, TIMESTAMP_NS, TIMESTAMP_LTZ_NS primitives#29050manner wants to merge 6 commits into
Conversation
…ESTAMP_LTZ_NS primitives
| case DATE: | ||
| return readLong(value, pos + 1, 4); | ||
| case INT8: | ||
| case TIME: |
There was a problem hiding this comment.
is it TIME with micro/nanos or not?
There was a problem hiding this comment.
| case TIMESTAMP_LTZ_NS: | ||
| case TIMESTAMP_NS: |
There was a problem hiding this comment.
why do we think 8 bytes is enough here?
There was a problem hiding this comment.
The Variant specification states 8 bytes for nanosecond timestamps as well. The downside for using the same 8 bytes for the higher precision values is the smaller range of timestamps that can be used (+/- 292 years around unix epoch)

https://parquet.apache.org/docs/file-format/types/variantencoding/
There was a problem hiding this comment.
ok, thanks for clarification
I tend to think we need to explicitly mention such limitation in docs
There was a problem hiding this comment.
and by the way what will happen with timestamp_NS -300 years?
will it fail (user friendly message?)
or produce some wrong result?
There was a problem hiding this comment.
You're right, this should be properly documented. I think documentation will mostly happen in this ticket:
https://issues.apache.org/jira/browse/FLINK-40494
When converting goes wrong it will now throw a VariantTypeException with a helpful error message:
https://github.com/apache/flink/pull/29050/changes#diff-2a1f60d1bf5ca4585c1c2d08c51866702d17d70306ee16ce096b238377b82770R137-R148
There's a test for this here:
https://github.com/apache/flink/pull/29050/changes#diff-662023392a0949caa3a0814536613caf4fb2a17c1e6f3d7217e829bee0d50390R142-R154
| // Get a long value from variant value `value[pos...]`. | ||
| // It is only legal to call it if `getType` returns one of `Type.LONG/DATE/TIMESTAMP/ | ||
| // TIMESTAMP_LTZ`. If the type is `DATE`, the return value is guaranteed to fit into an int and | ||
| // represents the number of days from the Unix epoch. | ||
| // It is only legal to call it if `getType` returns one of `Type.LONG/DATE/TIME/TIMESTAMP/ | ||
| // TIMESTAMP_LTZ/TIMESTAMP_NS/TIMESTAMP_LTZ_NS`. | ||
| // If the type is `DATE`, the return value is | ||
| // guaranteed to fit into an int and represents the number of days from the Unix epoch. | ||
| // If the type is `TIME`, the return value represents the number of microseconds since | ||
| // midnight. | ||
| // If the type is `TIMESTAMP/TIMESTAMP_LTZ`, the return value represents the number of | ||
| // microseconds from the Unix epoch. | ||
| // If the type is `TIMESTAMP_NS/TIMESTAMP_LTZ_NS`, the return value represents the number of | ||
| // nanoseconds from the Unix epoch. |
There was a problem hiding this comment.
nit: Should we change this into proper JavaDocs instead of line comments?
| Instant instant = Instant.EPOCH; | ||
| LocalDateTime localDateTime = LocalDateTime.of(2000, 1, 1, 0, 0); | ||
| LocalDate localDate = LocalDate.of(2000, 1, 1); | ||
| LocalTime localTime = LocalTime.of(13, 45, 30, 123456000); |
There was a problem hiding this comment.
Let's keep the nano precision with non-zero values for the time here to be sure that the truncation works as expected
| LocalTime localTime = LocalTime.of(13, 45, 30, 123456000); | |
| LocalTime localTime = LocalTime.of(13, 45, 30, 123456789); |
What is the purpose of the change
The variant binary encoding spec (see Variant Encoding) defines primitive type codes 17-20 for
TIME,TIMESTAMP_LTZ_NS,TIMESTAMP_NS, andUUIDthat are currently missing in Flink.This PR adds support for codes 17-19 (
TIME,TIMESTAMP_LTZ_NS,TIMESTAMP_NS) toBinaryVariant, so that aLocalTimeand a nanosecond-precisionInstant/LocalDateTimecan be represented in aVariantwithout lossy truncation to microseconds.UUID(code 20) is intentionally out of scope here and will be added in a separate ticket.Brief change log
BinaryVariantUtilconstants for primitive codes 17-19, extendedgetType()/valueSize()/getLong()to handle them, and added aTIME_FORMATTERfor JSON renderingVariant.Type.TIME/TIMESTAMP_NS/TIMESTAMP_LTZ_NSand the correspondinggetTime()/getDateTimeNanos()/getInstantNanos()accessors to theVariantinterfaceVariantBuilder.of(LocalTime); madeof(Instant)/of(LocalDateTime)precision-aware so a value with no sub-microsecond component keeps using the existing compact micros encoding, and only switches to the new nanosecond encoding when the value actually needs itappendTime/appendTimestampNanos/appendTimestampLtzNanosinBinaryVariantInternalBuilder, and the matching read/get()/toJson()support inBinaryVariantVerifying this change
This change added tests and can be verified as follows:
get()dispatch tests for the new types (BinaryVariantTest#testScalarVariant)Instant/LocalDateTimepick the existingTIMESTAMP_LTZ/TIMESTAMPencoding for microsecond-aligned values and the newTIMESTAMP_LTZ_NS/TIMESTAMP_NSencoding otherwise, including that the mismatched accessor throwsVariantTypeException(BinaryVariantTest#testNanosecondPrecisionVariant)LocalTimesilently truncates below microsecond precision, sinceTIMEhas no nanosecond-precision counterpart in the variant spec (BinaryVariantTest#testTimeSubMicrosecondTruncation)TIME/TIMESTAMP_NS/TIMESTAMP_LTZ_NS(BinaryVariantTest#testToJsonScalar)Does this pull request potentially affect one of the following parts:
@Public(Evolving): yes (VariantandVariantBuilderare@PublicEvolving; this adds new enum constants and new interface methods)Documentation
Was generative AI tooling used to co-author this PR?
Generated-by: Claude Sonnet 5 (Claude Code)