Skip to content

Commit 5974aa1

Browse files
authored
feat: supported Date32, Datetime64, Timestamp64, Interval64 (#116)
2 parents c0011aa + 38d119d commit 5974aa1

28 files changed

+1503
-440
lines changed

jdbc/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@
7777
<environmentVariables>
7878
<TESTCONTAINERS_REUSE_ENABLE>true</TESTCONTAINERS_REUSE_ENABLE>
7979
<YDB_DOCKER_IMAGE>ydbplatform/local-ydb:trunk</YDB_DOCKER_IMAGE>
80-
<YDB_DOCKER_FEATURE_FLAGS>enable_parameterized_decimal</YDB_DOCKER_FEATURE_FLAGS>
8180
</environmentVariables>
8281
<systemPropertyVariables>
8382
<java.util.logging.config.file>src/test/resources/logging.properties</java.util.logging.config.file>

jdbc/src/main/java/tech/ydb/jdbc/common/ColumnInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public ColumnInfo(String name, TypeDescription type) {
2727
this.isOptional = type.isOptional();
2828
this.ydbType = type.ydbType();
2929

30-
this.isTimestamp = ydbType == PrimitiveType.Timestamp;
30+
this.isTimestamp = ydbType == PrimitiveType.Timestamp || ydbType == PrimitiveType.Timestamp64;
3131
this.isNumber = ydbType == PrimitiveType.Int8 || ydbType == PrimitiveType.Uint8
3232
|| ydbType == PrimitiveType.Int16 || ydbType == PrimitiveType.Uint16
3333
|| ydbType == PrimitiveType.Int32 || ydbType == PrimitiveType.Uint32

jdbc/src/main/java/tech/ydb/jdbc/common/MappingGetters.java

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ private static ValueToString valueToString(PrimitiveType id) {
167167
return value -> String.valueOf(value.getTimestamp());
168168
case Interval:
169169
return value -> String.valueOf(value.getInterval());
170+
case Date32:
171+
return value -> String.valueOf(value.getDate32());
172+
case Datetime64:
173+
return value -> String.valueOf(value.getDatetime64());
174+
case Timestamp64:
175+
return value -> String.valueOf(value.getTimestamp64());
176+
case Interval64:
177+
return value -> String.valueOf(value.getInterval64());
170178
case TzDate:
171179
return value -> String.valueOf(value.getTzDate());
172180
case TzDatetime:
@@ -375,6 +383,8 @@ private static ValueToInt valueToInt(PrimitiveType id) {
375383
return value -> checkIntValue(id, value.getUint64());
376384
case Date:
377385
return value -> checkIntValue(id, value.getDate().toEpochDay());
386+
case Date32:
387+
return value -> checkIntValue(id, value.getDate32().toEpochDay());
378388
default:
379389
return castToIntNotSupported(id.name());
380390
}
@@ -402,17 +412,25 @@ private static ValueToLong valueToLong(PrimitiveType id) {
402412
return PrimitiveReader::getUint64;
403413
case Date:
404414
return value -> value.getDate().toEpochDay();
415+
case Date32:
416+
return value -> value.getDate32().toEpochDay();
405417
case Datetime:
406418
return value -> value.getDatetime().toEpochSecond(ZoneOffset.UTC);
419+
case Datetime64:
420+
return value -> value.getDatetime64().toEpochSecond(ZoneOffset.UTC);
407421
case Timestamp:
408422
return value -> value.getTimestamp().toEpochMilli();
423+
case Timestamp64:
424+
return value -> value.getTimestamp64().toEpochMilli();
409425
case TzDate:
410426
case TzDatetime:
411427
case TzTimestamp:
412428
ValueToInstant delegate = valueToInstant(id);
413429
return value -> delegate.fromValue(value).toEpochMilli();
414430
case Interval:
415431
return value -> TimeUnit.NANOSECONDS.toMicros(value.getInterval().toNanos());
432+
case Interval64:
433+
return value -> TimeUnit.NANOSECONDS.toMicros(value.getInterval64().toNanos());
416434
default:
417435
return castToLongNotSupported(id.name());
418436
}
@@ -514,6 +532,12 @@ private static ValueToInstant valueToInstant(PrimitiveType id) {
514532
return ValueReader::getTimestamp;
515533
case TzTimestamp:
516534
return v -> v.getTzTimestamp().toInstant();
535+
case Date32:
536+
return v -> Instant.ofEpochSecond(v.getDate32().toEpochDay() * 24 * 60 * 60);
537+
case Datetime64:
538+
return v -> Instant.ofEpochSecond(v.getDatetime64().toEpochSecond(ZoneOffset.UTC));
539+
case Timestamp64:
540+
return ValueReader::getTimestamp64;
517541
default:
518542
return castToInstantNotSupported(id.name());
519543
}
@@ -627,12 +651,16 @@ private static SqlType buildPrimitiveType(int sqlType, PrimitiveType id) {
627651
case Double:
628652
return new SqlType(sqlType, Double.class);
629653
case Date:
654+
case Date32:
630655
return new SqlType(sqlType, LocalDate.class);
631656
case Datetime:
657+
case Datetime64:
632658
return new SqlType(sqlType, LocalDateTime.class);
633659
case Timestamp:
660+
case Timestamp64:
634661
return new SqlType(sqlType, Instant.class);
635662
case Interval:
663+
case Interval64:
636664
return new SqlType(sqlType, Duration.class);
637665
case TzDate:
638666
case TzDatetime:
@@ -704,6 +732,14 @@ private static ValueToObject valueToObject(PrimitiveType id) {
704732
return PrimitiveReader::getTzDatetime;
705733
case TzTimestamp:
706734
return PrimitiveReader::getTzTimestamp;
735+
case Date32:
736+
return PrimitiveReader::getDate32;
737+
case Datetime64:
738+
return PrimitiveReader::getDatetime64;
739+
case Timestamp64:
740+
return PrimitiveReader::getTimestamp64;
741+
case Interval64:
742+
return PrimitiveReader::getInterval64;
707743
default:
708744
// DyNumber
709745
return value -> {
@@ -741,7 +777,7 @@ public <T> T fromValue(ValueReader reader, Class<T> clazz) throws SQLException {
741777
}
742778
}
743779

744-
780+
@SuppressWarnings("MethodLength")
745781
private static ValueToClass valueToClass(PrimitiveType id) {
746782
ValueToClassBuilder builder = new ValueToClassBuilder(id);
747783
switch (id) {
@@ -866,6 +902,49 @@ private static ValueToClass valueToClass(PrimitiveType id) {
866902
return builder
867903
.register(Duration.class, ValueReader::getInterval)
868904
.build();
905+
case Date32:
906+
return builder
907+
.register(long.class, v -> v.getDate32().toEpochDay())
908+
.register(Long.class, v -> v.getDate32().toEpochDay())
909+
.register(LocalDate.class, ValueReader::getDate32)
910+
.register(LocalDateTime.class, v -> v.getDate32().atStartOfDay())
911+
.register(java.sql.Date.class, v -> java.sql.Date.valueOf(v.getDate32()))
912+
.register(java.sql.Timestamp.class,
913+
v -> java.sql.Timestamp.valueOf(v.getDate32().atStartOfDay()))
914+
.register(Instant.class, v -> v.getDate32().atStartOfDay(ZoneId.systemDefault()).toInstant())
915+
.register(java.util.Date.class, v -> java.util.Date.from(
916+
v.getDate32().atStartOfDay(ZoneId.systemDefault()).toInstant()))
917+
.build();
918+
case Datetime64:
919+
return builder
920+
.register(long.class, v -> v.getDatetime64().toEpochSecond(ZoneOffset.UTC))
921+
.register(Long.class, v -> v.getDatetime64().toEpochSecond(ZoneOffset.UTC))
922+
.register(LocalDate.class, v -> v.getDatetime64().toLocalDate())
923+
.register(LocalDateTime.class, ValueReader::getDatetime64)
924+
.register(java.sql.Date.class, v -> java.sql.Date.valueOf(v.getDatetime64().toLocalDate()))
925+
.register(java.sql.Timestamp.class, v -> java.sql.Timestamp.valueOf(v.getDatetime64()))
926+
.register(Instant.class, v -> v.getDatetime64().atZone(ZoneId.systemDefault()).toInstant())
927+
.register(java.util.Date.class, v -> java.util.Date.from(
928+
v.getDatetime64().atZone(ZoneId.systemDefault()).toInstant()))
929+
.build();
930+
case Timestamp64:
931+
return builder
932+
.register(long.class, v -> v.getTimestamp64().toEpochMilli())
933+
.register(Long.class, v -> v.getTimestamp64().toEpochMilli())
934+
.register(LocalDate.class, v -> v.getTimestamp64().atZone(ZoneId.systemDefault()).toLocalDate())
935+
.register(LocalDateTime.class, v -> v.getTimestamp64()
936+
.atZone(ZoneId.systemDefault()).toLocalDateTime())
937+
.register(java.sql.Date.class, v -> java.sql.Date
938+
.valueOf(v.getTimestamp64().atZone(ZoneId.systemDefault()).toLocalDate()))
939+
.register(java.sql.Timestamp.class, v -> java.sql.Timestamp
940+
.valueOf(v.getTimestamp64().atZone(ZoneId.systemDefault()).toLocalDateTime()))
941+
.register(Instant.class, ValueReader::getTimestamp64)
942+
.register(java.util.Date.class, v -> java.util.Date.from(v.getTimestamp64()))
943+
.build();
944+
case Interval64:
945+
return builder
946+
.register(Duration.class, ValueReader::getInterval64)
947+
.build();
869948
case TzDate:
870949
return builder
871950
.register(ZonedDateTime.class, ValueReader::getTzDate)

jdbc/src/main/java/tech/ydb/jdbc/common/MappingSetters.java

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ private static Setters buildToValueImpl(Type type) {
9797
return x -> PrimitiveValue.newTzDatetime(castAsZonedDateTime(id, x));
9898
case TzTimestamp:
9999
return x -> PrimitiveValue.newTzTimestamp(castAsZonedDateTime(id, x));
100+
case Date32:
101+
return x -> castToDate32(id, x);
102+
case Datetime64:
103+
return x -> castToDateTime64(id, x);
104+
case Timestamp64:
105+
return x -> castToTimestamp64(id, x);
106+
case Interval64:
107+
return x -> castToInterval64(id, x);
100108
default:
101109
return x -> {
102110
throw castNotSupported(id, x);
@@ -502,6 +510,108 @@ private static PrimitiveValue castToTimestamp(PrimitiveType type, Object x) thro
502510
throw castNotSupported(type, x);
503511
}
504512

513+
private static PrimitiveValue castToInterval64(PrimitiveType type, Object x) throws SQLException {
514+
if (x instanceof Duration) {
515+
return PrimitiveValue.newInterval64((Duration) x);
516+
} else if (x instanceof Long) {
517+
return PrimitiveValue.newInterval64((Long) x);
518+
} else if (x instanceof String) {
519+
Duration parsed;
520+
try {
521+
parsed = Duration.parse((String) x);
522+
} catch (DateTimeParseException e) {
523+
throw castNotSupported(type, x, e);
524+
}
525+
return PrimitiveValue.newInterval64(parsed);
526+
}
527+
throw castNotSupported(type, x);
528+
}
529+
530+
private static PrimitiveValue castToDate32(PrimitiveType type, Object x) throws SQLException {
531+
if (x instanceof Instant) {
532+
return PrimitiveValue.newDate32(((Instant) x).atZone(ZoneId.systemDefault()).toLocalDate());
533+
} else if (x instanceof LocalDateTime) {
534+
return PrimitiveValue.newDate32(((LocalDateTime) x).toLocalDate());
535+
} else if (x instanceof LocalDate) {
536+
return PrimitiveValue.newDate32((LocalDate) x);
537+
} else if (x instanceof Integer) {
538+
return PrimitiveValue.newDate32(LocalDate.ofEpochDay((Integer) x));
539+
} else if (x instanceof Long) {
540+
return PrimitiveValue.newDate32(LocalDate.ofEpochDay((Long) x));
541+
} else if (x instanceof Timestamp) {
542+
// Normalize date - use system timezone to detect correct date
543+
Instant instant = Instant.ofEpochMilli(((Timestamp) x).getTime());
544+
LocalDate ld = instant.atZone(ZoneId.systemDefault()).toLocalDate();
545+
return PrimitiveValue.newDate32(ld);
546+
} else if (x instanceof Date) {
547+
// Normalize date - use system timezone to detect correct date
548+
Instant instant = Instant.ofEpochMilli(((Date) x).getTime());
549+
LocalDate ld = instant.atZone(ZoneId.systemDefault()).toLocalDate();
550+
return PrimitiveValue.newDate32(ld);
551+
} else if (x instanceof String) {
552+
try {
553+
return PrimitiveValue.newDate32(LocalDate.parse((String) x));
554+
} catch (DateTimeParseException e) {
555+
throw castNotSupported(type, x, e);
556+
}
557+
}
558+
throw castNotSupported(type, x);
559+
}
560+
561+
private static PrimitiveValue castToDateTime64(PrimitiveType type, Object x) throws SQLException {
562+
if (x instanceof Instant) {
563+
return PrimitiveValue.newDatetime64(((Instant) x).atZone(ZoneId.systemDefault()).toLocalDateTime());
564+
} else if (x instanceof LocalDateTime) {
565+
return PrimitiveValue.newDatetime64(((LocalDateTime) x));
566+
} else if (x instanceof LocalDate) {
567+
return PrimitiveValue.newDatetime64(((LocalDate) x).atStartOfDay());
568+
} else if (x instanceof Long) {
569+
return PrimitiveValue.newDatetime64(LocalDateTime.ofEpochSecond((Long) x, 0, ZoneOffset.UTC));
570+
} else if (x instanceof Timestamp) {
571+
// Normalize date - use system timezone to detect correct date
572+
Instant instant = Instant.ofEpochMilli(((Timestamp) x).getTime());
573+
LocalDateTime ldt = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
574+
return PrimitiveValue.newDatetime64(ldt);
575+
} else if (x instanceof Date) {
576+
// Normalize date - use system timezone to detect correct date
577+
Instant instant = Instant.ofEpochMilli(((Date) x).getTime());
578+
LocalDate ld = instant.atZone(ZoneId.systemDefault()).toLocalDate();
579+
return PrimitiveValue.newDatetime64(ld.atStartOfDay());
580+
} else if (x instanceof String) {
581+
try {
582+
return PrimitiveValue.newDatetime64(LocalDateTime.parse((String) x));
583+
} catch (DateTimeParseException e) {
584+
throw castNotSupported(type, x, e);
585+
}
586+
}
587+
throw castNotSupported(type, x);
588+
}
589+
590+
private static PrimitiveValue castToTimestamp64(PrimitiveType type, Object x) throws SQLException {
591+
if (x instanceof Instant) {
592+
return PrimitiveValue.newTimestamp64((Instant) x);
593+
} else if (x instanceof Long) {
594+
return PrimitiveValue.newTimestamp64(Instant.ofEpochMilli((Long) x));
595+
} else if (x instanceof LocalDate) {
596+
return PrimitiveValue.newTimestamp64(((LocalDate) x).atStartOfDay().toInstant(ZoneOffset.UTC));
597+
} else if (x instanceof LocalDateTime) {
598+
long epochSeconds = ((LocalDateTime) x).toEpochSecond(ZoneOffset.UTC);
599+
return PrimitiveValue.newTimestamp64(Instant.ofEpochSecond(epochSeconds));
600+
} else if (x instanceof Timestamp) {
601+
return PrimitiveValue.newTimestamp64(((Timestamp) x).toInstant());
602+
} else if (x instanceof Date) {
603+
Instant instant = ((Date) x).toLocalDate().atStartOfDay().toInstant(ZoneOffset.UTC);
604+
return PrimitiveValue.newTimestamp64(instant);
605+
} else if (x instanceof String) {
606+
try {
607+
return PrimitiveValue.newTimestamp64(Instant.parse((String) x));
608+
} catch (DateTimeParseException e) {
609+
throw castNotSupported(type, x, e);
610+
}
611+
}
612+
throw castNotSupported(type, x);
613+
}
614+
505615
private static DecimalValue validateValue(DecimalType type, DecimalValue value, Object x) throws SQLException {
506616
if (value.isNan()) {
507617
throw new SQLException(String.format(YdbConst.UNABLE_TO_CAST_TO_DECIMAL, type, toString(x), "NaN"));

0 commit comments

Comments
 (0)