diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index f1e4fcdb3..ae1811a95 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -515,6 +515,9 @@ jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_ case LogicalTypeId::TIME: constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_t)); break; + case LogicalTypeId::TIME_NS: + constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_ns_t)); + break; case LogicalTypeId::TIME_TZ: constlen_data = env->NewDirectByteBuffer(FlatVector::GetData(vec), row_count * sizeof(dtime_tz_t)); break; diff --git a/src/main/java/org/duckdb/DuckDBColumnType.java b/src/main/java/org/duckdb/DuckDBColumnType.java index ada7c0d8f..d46df3fd7 100644 --- a/src/main/java/org/duckdb/DuckDBColumnType.java +++ b/src/main/java/org/duckdb/DuckDBColumnType.java @@ -18,6 +18,7 @@ public enum DuckDBColumnType { VARCHAR, BLOB, TIME, + TIME_NS, DATE, TIMESTAMP, TIMESTAMP_MS, diff --git a/src/main/java/org/duckdb/DuckDBResultSet.java b/src/main/java/org/duckdb/DuckDBResultSet.java index 0d14712dc..6df384326 100644 --- a/src/main/java/org/duckdb/DuckDBResultSet.java +++ b/src/main/java/org/duckdb/DuckDBResultSet.java @@ -27,10 +27,7 @@ import java.sql.Struct; import java.sql.Time; import java.sql.Timestamp; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; -import java.time.OffsetTime; +import java.time.*; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -411,6 +408,10 @@ public Time getTime(int columnIndex) throws SQLException { return getTime(columnIndex, null); } + public LocalTime getLocalTime(int columnIndex) throws SQLException { + return currentChunk[columnIndex - 1].getLocalTime(chunkIdx - 1); + } + public Timestamp getTimestamp(int columnIndex) throws SQLException { if (checkAndNull(columnIndex)) { return null; @@ -589,6 +590,10 @@ public Time getTime(String columnLabel) throws SQLException { return getTime(findColumn(columnLabel)); } + public LocalTime getLocalTime(String columnLabel) throws SQLException { + return getLocalTime(findColumn(columnLabel)); + } + public Timestamp getTimestamp(String columnLabel) throws SQLException { return getTimestamp(findColumn(columnLabel)); } @@ -1332,11 +1337,20 @@ public T getObject(int columnIndex, Class type) throws SQLException { throw new SQLException("Can't convert value to Date, Java type: " + type + ", SQL type: " + sqlType); } } else if (type == Time.class) { - if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) { + if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_NS || + sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) { return type.cast(getTime(columnIndex)); } else { throw new SQLException("Can't convert value to Time, Java type: " + type + ", SQL type: " + sqlType); } + } else if (type == LocalTime.class) { + if (sqlType == DuckDBColumnType.TIME || sqlType == DuckDBColumnType.TIME_NS || + sqlType == DuckDBColumnType.TIME_WITH_TIME_ZONE) { + return type.cast(getLocalTime(columnIndex)); + } else { + throw new SQLException("Can't convert value to LocalTime, Java type: " + type + + ", SQL type: " + sqlType); + } } else if (type == Timestamp.class) { if (isTimestamp(sqlType)) { return type.cast(getTimestamp(columnIndex)); diff --git a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java index 2461dfa83..92a544e35 100644 --- a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java +++ b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java @@ -149,6 +149,7 @@ public static int type_to_int(DuckDBColumnType type) { case VARCHAR: return Types.VARCHAR; case TIME: + case TIME_NS: return Types.TIME; case DATE: return Types.DATE; @@ -212,6 +213,7 @@ protected static String type_to_javaString(DuckDBColumnType type) { case DECIMAL: return BigDecimal.class.getName(); case TIME: + case TIME_NS: return LocalTime.class.getName(); case TIME_WITH_TIME_ZONE: return OffsetTime.class.getName(); @@ -341,6 +343,7 @@ public int getPrecision(int column) throws SQLException { case DOUBLE: return 17; case TIME: + case TIME_NS: return 15; case DATE: return 13; diff --git a/src/main/java/org/duckdb/DuckDBVector.java b/src/main/java/org/duckdb/DuckDBVector.java index 7e4b923b5..e522452ea 100644 --- a/src/main/java/org/duckdb/DuckDBVector.java +++ b/src/main/java/org/duckdb/DuckDBVector.java @@ -93,6 +93,7 @@ Object getObject(int idx) throws SQLException { case DECIMAL: return getBigDecimal(idx); case TIME: + case TIME_NS: return getLocalTime(idx); case TIME_WITH_TIME_ZONE: return getOffsetTime(idx); @@ -136,6 +137,10 @@ LocalTime getLocalTime(int idx) throws SQLException { long nanoseconds = TimeUnit.MICROSECONDS.toNanos(microseconds); return LocalTime.ofNanoOfDay(nanoseconds); } + case TIME_NS: { + long nanoseconds = getLongFromConstlen(idx); + return LocalTime.ofNanoOfDay(nanoseconds); + } case TIMESTAMP: case TIMESTAMP_MS: case TIMESTAMP_NS: @@ -360,7 +365,8 @@ OffsetTime getOffsetTime(int idx) throws SQLException { } switch (duckdb_type) { - case TIME: { + case TIME: + case TIME_NS: { LocalTime lt = getLocalTime(idx); return lt.atOffset(ZoneOffset.UTC); } @@ -395,7 +401,8 @@ Time getTime(int idx, Calendar cal) throws SQLException { } switch (duckdb_type) { - case TIME: { + case TIME: + case TIME_NS: { LocalTime lt = getLocalTime(idx); return Time.valueOf(lt); } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index aa5830f03..a6b8bb6c7 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -1501,6 +1501,7 @@ private static OffsetDateTime localDateTimeToOffset(LocalDateTime ldt) { "179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368", null)); correct_answer_map.put("time", asList(LocalTime.of(0, 0), LocalTime.parse("23:59:59.999999"), null)); + correct_answer_map.put("time_ns", asList(LocalTime.of(0, 0), LocalTime.parse("23:59:59.999999"), null)); correct_answer_map.put("float", asList(-3.4028234663852886e+38f, 3.4028234663852886e+38f, null)); correct_answer_map.put("double", asList(-1.7976931348623157e+308d, 1.7976931348623157e+308d, null)); 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) { asList(asList(int_array, null, int_array), asList(int_list, int_array, int_list), null)); correct_answer_map.put("fixed_nested_varchar_array", asList(asList(varchar_array, null, varchar_array), asList(def, varchar_array, def), null)); - correct_answer_map.put("fixed_struct_array", asList(asList(abnull, ducks, abnull), asList(ducks, abnull, ducks), null)); - correct_answer_map.put("struct_of_fixed_array", asList(mapOf("a", int_array, "b", varchar_array), mapOf("a", int_list, "b", def), null)); - correct_answer_map.put("fixed_array_of_int_list", asList(asList(emptyList(), numbers, emptyList()), asList(numbers, emptyList(), numbers), null)); - correct_answer_map.put("list_of_fixed_int_array", asList(asList(int_array, int_list, int_array), asList(int_list, int_array, int_list), null)); TimeZone.setDefault(defaultTimeZone); @@ -1578,9 +1575,11 @@ public static void test_all_types() throws Exception { try { Logger logger = Logger.getAnonymousLogger(); String sql = - "select * EXCLUDE(time, time_tz)" + "select * EXCLUDE(time, time_ns, time_tz)" + "\n , CASE WHEN time = '24:00:00'::TIME THEN '23:59:59.999999'::TIME ELSE time END AS time" + + "\n , CASE WHEN time_ns = '24:00:00'::TIME_NS THEN '23:59:59.999999'::TIME_NS ELSE time_ns END AS time_ns" + + "\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" + "\nfrom test_all_types()"; diff --git a/src/test/java/org/duckdb/TestTimestamp.java b/src/test/java/org/duckdb/TestTimestamp.java index a433ab0b7..3238472e5 100644 --- a/src/test/java/org/duckdb/TestTimestamp.java +++ b/src/test/java/org/duckdb/TestTimestamp.java @@ -544,6 +544,27 @@ public static void test_time_tz() throws Exception { } } + public static void test_time_ns() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement s = conn.createStatement()) { + s.executeUpdate("create table t (i time_ns)"); + try (ResultSet rs = conn.getMetaData().getColumns(null, "%", "t", "i");) { + rs.next(); + + assertEquals(rs.getString("TYPE_NAME"), "TIME_NS"); + assertEquals(rs.getInt("DATA_TYPE"), Types.OTHER); + } + + s.execute("INSERT INTO t VALUES ('01:01:00'), ('01:02:03.456789012')"); + try (ResultSet rs = s.executeQuery("SELECT * FROM t")) { + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalTime.class), LocalTime.of(1, 1)); + assertTrue(rs.next()); + assertEquals(rs.getObject(1, LocalTime.class), LocalTime.of(1, 2, 3, 456789012)); + assertFalse(rs.next()); + } + } + } + public static void test_bug532_timestamp() throws Exception { try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) {