Skip to content

Commit 7e9a32b

Browse files
committed
Add TIME_NS support
This PR adds support for fetching `TIME_NS` values as instances of `java.time.LocalTime` class, example: ```java LocalTime lt = rs.getObject(1, LocalTime.class); ``` Nanosecond precision is maintained on fetching `TIME_NS` values from DB. Currently it is not possible to preserve nanosecond precision when inserting `TIME_NS` values using query parameters. The support is necessary to fix `test_all_types` failures introduced by duckdb/duckdb#20361.
1 parent 455ebf1 commit 7e9a32b

File tree

7 files changed

+60
-12
lines changed

7 files changed

+60
-12
lines changed

src/jni/duckdb_java.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,9 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_
515515
case LogicalTypeId::TIME:
516516
constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_t));
517517
break;
518+
case LogicalTypeId::TIME_NS:
519+
constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_ns_t));
520+
break;
518521
case LogicalTypeId::TIME_TZ:
519522
constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_tz_t));
520523
break;

src/main/java/org/duckdb/DuckDBColumnType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public enum DuckDBColumnType {
1818
VARCHAR,
1919
BLOB,
2020
TIME,
21+
TIME_NS,
2122
DATE,
2223
TIMESTAMP,
2324
TIMESTAMP_MS,

src/main/java/org/duckdb/DuckDBResultSet.java

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,7 @@
2727
import java.sql.Struct;
2828
import java.sql.Time;
2929
import java.sql.Timestamp;
30-
import java.time.LocalDate;
31-
import java.time.LocalDateTime;
32-
import java.time.OffsetDateTime;
33-
import java.time.OffsetTime;
30+
import java.time.*;
3431
import java.util.*;
3532
import java.util.concurrent.locks.Lock;
3633
import java.util.concurrent.locks.ReentrantLock;
@@ -411,6 +408,10 @@ public Time getTime(int columnIndex) throws SQLException {
411408
return getTime(columnIndex, null);
412409
}
413410

411+
public LocalTime getLocalTime(int columnIndex) throws SQLException {
412+
return currentChunk[columnIndex - 1].getLocalTime(chunkIdx - 1);
413+
}
414+
414415
public Timestamp getTimestamp(int columnIndex) throws SQLException {
415416
if (checkAndNull(columnIndex)) {
416417
return null;
@@ -589,6 +590,10 @@ public Time getTime(String columnLabel) throws SQLException {
589590
return getTime(findColumn(columnLabel));
590591
}
591592

593+
public LocalTime getLocalTime(String columnLabel) throws SQLException {
594+
return getLocalTime(findColumn(columnLabel));
595+
}
596+
592597
public Timestamp getTimestamp(String columnLabel) throws SQLException {
593598
return getTimestamp(findColumn(columnLabel));
594599
}
@@ -1332,11 +1337,20 @@ public <T> T getObject(int columnIndex, Class<T> type) throws SQLException {
13321337
throw new SQLException("Can't convert value to Date, Java type: " + type + ", SQL type: " + sqlType);
13331338
}
13341339
} else if (type == Time.class) {
1335-
if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) {
1340+
if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_NS ||
1341+
sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) {
13361342
return type.cast(getTime(columnIndex));
13371343
} else {
13381344
throw new SQLException("Can't convert value to Time, Java type: " + type + ", SQL type: " + sqlType);
13391345
}
1346+
} else if (type == LocalTime.class) {
1347+
if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_NS ||
1348+
sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) {
1349+
return type.cast(getLocalTime(columnIndex));
1350+
} else {
1351+
throw new SQLException("Can't convert value to LocalTime, Java type: " + type +
1352+
", SQL type: " + sqlType);
1353+
}
13401354
} else if (type == Timestamp.class) {
13411355
if (isTimestamp(sqlType)) {
13421356
return type.cast(getTimestamp(columnIndex));

src/main/java/org/duckdb/DuckDBResultSetMetaData.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ public static int type_to_int(DuckDBColumnType type) {
149149
case VARCHAR:
150150
return Types.VARCHAR;
151151
case TIME:
152+
case TIME_NS:
152153
return Types.TIME;
153154
case DATE:
154155
return Types.DATE;
@@ -212,6 +213,7 @@ protected static String type_to_javaString(DuckDBColumnType type) {
212213
case DECIMAL:
213214
return BigDecimal.class.getName();
214215
case TIME:
216+
case TIME_NS:
215217
return LocalTime.class.getName();
216218
case TIME_WITH_TIME_ZONE:
217219
return OffsetTime.class.getName();
@@ -341,6 +343,7 @@ public int getPrecision(int column) throws SQLException {
341343
case DOUBLE:
342344
return 17;
343345
case TIME:
346+
case TIME_NS:
344347
return 15;
345348
case DATE:
346349
return 13;

src/main/java/org/duckdb/DuckDBVector.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Object getObject(int idx) throws SQLException {
9393
case DECIMAL:
9494
return getBigDecimal(idx);
9595
case TIME:
96+
case TIME_NS:
9697
return getLocalTime(idx);
9798
case TIME_WITH_TIME_ZONE:
9899
return getOffsetTime(idx);
@@ -136,6 +137,10 @@ LocalTime getLocalTime(int idx) throws SQLException {
136137
long nanoseconds = TimeUnit.MICROSECONDS.toNanos(microseconds);
137138
return LocalTime.ofNanoOfDay(nanoseconds);
138139
}
140+
case TIME_NS: {
141+
long nanoseconds = getLongFromConstlen(idx);
142+
return LocalTime.ofNanoOfDay(nanoseconds);
143+
}
139144
case TIMESTAMP:
140145
case TIMESTAMP_MS:
141146
case TIMESTAMP_NS:
@@ -360,7 +365,8 @@ OffsetTime getOffsetTime(int idx) throws SQLException {
360365
}
361366

362367
switch (duckdb_type) {
363-
case TIME: {
368+
case TIME:
369+
case TIME_NS: {
364370
LocalTime lt = getLocalTime(idx);
365371
return lt.atOffset(ZoneOffset.UTC);
366372
}
@@ -395,7 +401,8 @@ Time getTime(int idx, Calendar cal) throws SQLException {
395401
}
396402

397403
switch (duckdb_type) {
398-
case TIME: {
404+
case TIME:
405+
case TIME_NS: {
399406
LocalTime lt = getLocalTime(idx);
400407
return Time.valueOf(lt);
401408
}

src/test/java/org/duckdb/TestDuckDBJDBC.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,7 @@ private static OffsetDateTime localDateTimeToOffset(LocalDateTime ldt) {
15011501
"179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368",
15021502
null));
15031503
correct_answer_map.put("time", asList(LocalTime.of(0, 0), LocalTime.parse("23:59:59.999999"), null));
1504+
correct_answer_map.put("time_ns", asList(LocalTime.of(0, 0), LocalTime.parse("23:59:59.999999"), null));
15041505
correct_answer_map.put("float", asList(-3.4028234663852886e+38f, 3.4028234663852886e+38f, null));
15051506
correct_answer_map.put("double", asList(-1.7976931348623157e+308d, 1.7976931348623157e+308d, null));
15061507
correct_answer_map.put("dec_4_1", asList(new BigDecimal("-999.9"), (new BigDecimal("999.9")), null));
@@ -1556,16 +1557,12 @@ private static OffsetDateTime localDateTimeToOffset(LocalDateTime ldt) {
15561557
asList(asList(int_array, null, int_array), asList(int_list, int_array, int_list), null));
15571558
correct_answer_map.put("fixed_nested_varchar_array", asList(asList(varchar_array, null, varchar_array),
15581559
asList(def, varchar_array, def), null));
1559-
15601560
correct_answer_map.put("fixed_struct_array",
15611561
asList(asList(abnull, ducks, abnull), asList(ducks, abnull, ducks), null));
1562-
15631562
correct_answer_map.put("struct_of_fixed_array",
15641563
asList(mapOf("a", int_array, "b", varchar_array), mapOf("a", int_list, "b", def), null));
1565-
15661564
correct_answer_map.put("fixed_array_of_int_list", asList(asList(emptyList(), numbers, emptyList()),
15671565
asList(numbers, emptyList(), numbers), null));
1568-
15691566
correct_answer_map.put("list_of_fixed_int_array", asList(asList(int_array, int_list, int_array),
15701567
asList(int_list, int_array, int_list), null));
15711568
TimeZone.setDefault(defaultTimeZone);
@@ -1578,9 +1575,11 @@ public static void test_all_types() throws Exception {
15781575
try {
15791576
Logger logger = Logger.getAnonymousLogger();
15801577
String sql =
1581-
"select * EXCLUDE(time, time_tz)"
1578+
"select * EXCLUDE(time, time_ns, time_tz)"
15821579
+ "\n , CASE WHEN time = '24:00:00'::TIME THEN '23:59:59.999999'::TIME ELSE time END AS time"
15831580
+
1581+
"\n , CASE WHEN time_ns = '24:00:00'::TIME_NS THEN '23:59:59.999999'::TIME_NS ELSE time_ns END AS time_ns"
1582+
+
15841583
"\n , CASE WHEN time_tz = '24:00:00-15:59:59'::TIMETZ THEN '23:59:59.999999-15:59:59'::TIMETZ ELSE time_tz END AS time_tz"
15851584
+ "\nfrom test_all_types()";
15861585

src/test/java/org/duckdb/TestTimestamp.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,27 @@ public static void test_time_tz() throws Exception {
544544
}
545545
}
546546

547+
public static void test_time_ns() throws Exception {
548+
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement s = conn.createStatement()) {
549+
s.executeUpdate("create table t (i time_ns)");
550+
try (ResultSet rs = conn.getMetaData().getColumns(null, "%", "t", "i");) {
551+
rs.next();
552+
553+
assertEquals(rs.getString("TYPE_NAME"), "TIME_NS");
554+
assertEquals(rs.getInt("DATA_TYPE"), Types.OTHER);
555+
}
556+
557+
s.execute("INSERT INTO t VALUES ('01:01:00'), ('01:02:03.456789012')");
558+
try (ResultSet rs = s.executeQuery("SELECT * FROM t")) {
559+
assertTrue(rs.next());
560+
assertEquals(rs.getObject(1, LocalTime.class), LocalTime.of(1, 1));
561+
assertTrue(rs.next());
562+
assertEquals(rs.getObject(1, LocalTime.class), LocalTime.of(1, 2, 3, 456789012));
563+
assertFalse(rs.next());
564+
}
565+
}
566+
}
567+
547568
public static void test_bug532_timestamp() throws Exception {
548569
try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {
549570

0 commit comments

Comments
 (0)