提交 6ebf22cb authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add support for java.time.Instant on Java 8

Instant values are mapped to TIMESTAMP data type.
上级 ec7148b2
...@@ -3826,6 +3826,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS ...@@ -3826,6 +3826,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
} else if (LocalDateTimeUtils.isLocalDateTime(type)) { } else if (LocalDateTimeUtils.isLocalDateTime(type)) {
return type.cast(LocalDateTimeUtils.valueToLocalDateTime( return type.cast(LocalDateTimeUtils.valueToLocalDateTime(
(ValueTimestamp) value)); (ValueTimestamp) value));
} else if (LocalDateTimeUtils.isInstant(type)) {
return type.cast(LocalDateTimeUtils.valueToInstant(value));
} else if (LocalDateTimeUtils.isOffsetDateTime(type) && } else if (LocalDateTimeUtils.isOffsetDateTime(type) &&
value instanceof ValueTimestampTimeZone) { value instanceof ValueTimestampTimeZone) {
return type.cast(LocalDateTimeUtils.valueToOffsetDateTime( return type.cast(LocalDateTimeUtils.valueToOffsetDateTime(
......
...@@ -9,6 +9,7 @@ package org.h2.util; ...@@ -9,6 +9,7 @@ package org.h2.util;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -28,11 +29,15 @@ import org.h2.value.ValueTimestampTimeZone; ...@@ -28,11 +29,15 @@ import org.h2.value.ValueTimestampTimeZone;
* Java 7 as well.</p> * Java 7 as well.</p>
* *
* <p>Custom conversion methods between H2 internal values and JSR-310 classes * <p>Custom conversion methods between H2 internal values and JSR-310 classes
* are used without intermediate conversions to java.sql classes. Direct * are used in most cases without intermediate conversions to java.sql classes.
* conversion is simpler, faster, and it does not inherit limitations and * Direct conversion is simpler, faster, and it does not inherit limitations
* issues from java.sql classes and conversion methods provided by JDK.</p> * and issues from java.sql classes and conversion methods provided by JDK.</p>
* *
* <p>Once the driver requires Java 8 all the reflection can be removed.</p> * <p>The only one exclusion is a conversion between {@link Timestamp} and
* Instant.</p>
*
* <p>Once the driver requires Java 8 and Android API 26 all the reflection
* can be removed.</p>
*/ */
public class LocalDateTimeUtils { public class LocalDateTimeUtils {
...@@ -42,6 +47,8 @@ public class LocalDateTimeUtils { ...@@ -42,6 +47,8 @@ public class LocalDateTimeUtils {
private static final Class<?> LOCAL_TIME; private static final Class<?> LOCAL_TIME;
// Class<java.time.LocalDateTime> // Class<java.time.LocalDateTime>
private static final Class<?> LOCAL_DATE_TIME; private static final Class<?> LOCAL_DATE_TIME;
// Class<java.time.Instant>
private static final Class<?> INSTANT;
// Class<java.time.OffsetDateTime> // Class<java.time.OffsetDateTime>
private static final Class<?> OFFSET_DATE_TIME; private static final Class<?> OFFSET_DATE_TIME;
// Class<java.time.ZoneOffset> // Class<java.time.ZoneOffset>
...@@ -66,6 +73,11 @@ public class LocalDateTimeUtils { ...@@ -66,6 +73,11 @@ public class LocalDateTimeUtils {
// java.time.LocalDate#atStartOfDay() // java.time.LocalDate#atStartOfDay()
private static final Method LOCAL_DATE_AT_START_OF_DAY; private static final Method LOCAL_DATE_AT_START_OF_DAY;
// java.sql.Timestamp.from(java.time.Instant)
private static final Method TIMESTAMP_FROM;
// java.sql.Timestamp.toInstant()
private static final Method TIMESTAMP_TO_INSTANT;
// java.time.LocalTime#parse(CharSequence) // java.time.LocalTime#parse(CharSequence)
private static final Method LOCAL_TIME_PARSE; private static final Method LOCAL_TIME_PARSE;
...@@ -99,11 +111,12 @@ public class LocalDateTimeUtils { ...@@ -99,11 +111,12 @@ public class LocalDateTimeUtils {
LOCAL_DATE = tryGetClass("java.time.LocalDate"); LOCAL_DATE = tryGetClass("java.time.LocalDate");
LOCAL_TIME = tryGetClass("java.time.LocalTime"); LOCAL_TIME = tryGetClass("java.time.LocalTime");
LOCAL_DATE_TIME = tryGetClass("java.time.LocalDateTime"); LOCAL_DATE_TIME = tryGetClass("java.time.LocalDateTime");
INSTANT = tryGetClass("java.time.Instant");
OFFSET_DATE_TIME = tryGetClass("java.time.OffsetDateTime"); OFFSET_DATE_TIME = tryGetClass("java.time.OffsetDateTime");
ZONE_OFFSET = tryGetClass("java.time.ZoneOffset"); ZONE_OFFSET = tryGetClass("java.time.ZoneOffset");
IS_JAVA8_DATE_API_PRESENT = LOCAL_DATE != null && LOCAL_TIME != null && IS_JAVA8_DATE_API_PRESENT = LOCAL_DATE != null && LOCAL_TIME != null &&
LOCAL_DATE_TIME != null && OFFSET_DATE_TIME != null && LOCAL_DATE_TIME != null && INSTANT != null &&
ZONE_OFFSET != null; OFFSET_DATE_TIME != null && ZONE_OFFSET != null;
if (IS_JAVA8_DATE_API_PRESENT) { if (IS_JAVA8_DATE_API_PRESENT) {
LOCAL_TIME_OF_NANO = getMethod(LOCAL_TIME, "ofNanoOfDay", long.class); LOCAL_TIME_OF_NANO = getMethod(LOCAL_TIME, "ofNanoOfDay", long.class);
...@@ -119,6 +132,9 @@ public class LocalDateTimeUtils { ...@@ -119,6 +132,9 @@ public class LocalDateTimeUtils {
LOCAL_DATE_GET_DAY_OF_MONTH = getMethod(LOCAL_DATE, "getDayOfMonth"); LOCAL_DATE_GET_DAY_OF_MONTH = getMethod(LOCAL_DATE, "getDayOfMonth");
LOCAL_DATE_AT_START_OF_DAY = getMethod(LOCAL_DATE, "atStartOfDay"); LOCAL_DATE_AT_START_OF_DAY = getMethod(LOCAL_DATE, "atStartOfDay");
TIMESTAMP_FROM = getMethod(Timestamp.class, "from", INSTANT);
TIMESTAMP_TO_INSTANT = getMethod(Timestamp.class, "toInstant");
LOCAL_TIME_PARSE = getMethod(LOCAL_TIME, "parse", CharSequence.class); LOCAL_TIME_PARSE = getMethod(LOCAL_TIME, "parse", CharSequence.class);
LOCAL_DATE_TIME_PLUS_NANOS = getMethod(LOCAL_DATE_TIME, "plusNanos", long.class); LOCAL_DATE_TIME_PLUS_NANOS = getMethod(LOCAL_DATE_TIME, "plusNanos", long.class);
...@@ -144,6 +160,8 @@ public class LocalDateTimeUtils { ...@@ -144,6 +160,8 @@ public class LocalDateTimeUtils {
LOCAL_DATE_GET_MONTH_VALUE = null; LOCAL_DATE_GET_MONTH_VALUE = null;
LOCAL_DATE_GET_DAY_OF_MONTH = null; LOCAL_DATE_GET_DAY_OF_MONTH = null;
LOCAL_DATE_AT_START_OF_DAY = null; LOCAL_DATE_AT_START_OF_DAY = null;
TIMESTAMP_FROM = null;
TIMESTAMP_TO_INSTANT = null;
LOCAL_TIME_PARSE = null; LOCAL_TIME_PARSE = null;
LOCAL_DATE_TIME_PLUS_NANOS = null; LOCAL_DATE_TIME_PLUS_NANOS = null;
LOCAL_DATE_TIME_TO_LOCAL_DATE = null; LOCAL_DATE_TIME_TO_LOCAL_DATE = null;
...@@ -202,6 +220,15 @@ public class LocalDateTimeUtils { ...@@ -202,6 +220,15 @@ public class LocalDateTimeUtils {
return LOCAL_DATE_TIME; return LOCAL_DATE_TIME;
} }
/**
* Returns the class java.time.Instant.
*
* @return the class java.time.Instant, null on Java 7
*/
public static Class<?> getInstantClass() {
return INSTANT;
}
/** /**
* Returns the class java.time.OffsetDateTime. * Returns the class java.time.OffsetDateTime.
* *
...@@ -322,6 +349,18 @@ public class LocalDateTimeUtils { ...@@ -322,6 +349,18 @@ public class LocalDateTimeUtils {
return LOCAL_DATE_TIME == clazz; return LOCAL_DATE_TIME == clazz;
} }
/**
* Checks if the given class is Instant.
*
* <p>This method can be called from Java 7.</p>
*
* @param clazz the class to check
* @return if the class is Instant
*/
public static boolean isInstant(Class<?> clazz) {
return INSTANT == clazz;
}
/** /**
* Checks if the given class is OffsetDateTime. * Checks if the given class is OffsetDateTime.
* *
...@@ -380,7 +419,6 @@ public class LocalDateTimeUtils { ...@@ -380,7 +419,6 @@ public class LocalDateTimeUtils {
* @return the LocalDateTime * @return the LocalDateTime
*/ */
public static Object valueToLocalDateTime(ValueTimestamp value) { public static Object valueToLocalDateTime(ValueTimestamp value) {
long dateValue = value.getDateValue(); long dateValue = value.getDateValue();
long timeNanos = value.getTimeNanos(); long timeNanos = value.getTimeNanos();
try { try {
...@@ -394,6 +432,24 @@ public class LocalDateTimeUtils { ...@@ -394,6 +432,24 @@ public class LocalDateTimeUtils {
} }
} }
/**
* Converts a value to a Instant.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the Instant
*/
public static Object valueToInstant(Value value) {
try {
return TIMESTAMP_TO_INSTANT.invoke(value.getTimestamp());
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "timestamp conversion failed");
}
}
/** /**
* Converts a value to a OffsetDateTime. * Converts a value to a OffsetDateTime.
* *
...@@ -479,6 +535,22 @@ public class LocalDateTimeUtils { ...@@ -479,6 +535,22 @@ public class LocalDateTimeUtils {
} }
} }
/**
* Converts a Instant to a Value.
*
* @param instant the Instant to convert, not {@code null}
* @return the value
*/
public static Value instantToValue(Object instant) {
try {
return ValueTimestamp.get((Timestamp) TIMESTAMP_FROM.invoke(null, instant));
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "instant conversion failed");
}
}
/** /**
* Converts a OffsetDateTime to a Value. * Converts a OffsetDateTime to a Value.
* *
......
...@@ -1122,6 +1122,8 @@ public class DataType { ...@@ -1122,6 +1122,8 @@ public class DataType {
return LocalDateTimeUtils.localTimeToTimeValue(x); return LocalDateTimeUtils.localTimeToTimeValue(x);
} else if (LocalDateTimeUtils.isLocalDateTime(x.getClass())) { } else if (LocalDateTimeUtils.isLocalDateTime(x.getClass())) {
return LocalDateTimeUtils.localDateTimeToValue(x); return LocalDateTimeUtils.localDateTimeToValue(x);
} else if (LocalDateTimeUtils.isInstant(x.getClass())) {
return LocalDateTimeUtils.instantToValue(x);
} else if (LocalDateTimeUtils.isOffsetDateTime(x.getClass())) { } else if (LocalDateTimeUtils.isOffsetDateTime(x.getClass())) {
return LocalDateTimeUtils.offsetDateTimeToValue(x); return LocalDateTimeUtils.offsetDateTimeToValue(x);
} else if (x instanceof TimestampWithTimeZone) { } else if (x instanceof TimestampWithTimeZone) {
......
...@@ -9,6 +9,7 @@ import java.io.ByteArrayInputStream; ...@@ -9,6 +9,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
import java.net.URL; import java.net.URL;
...@@ -76,6 +77,7 @@ public class TestPreparedStatement extends TestBase { ...@@ -76,6 +77,7 @@ public class TestPreparedStatement extends TestBase {
testTime8(conn); testTime8(conn);
testDateTime8(conn); testDateTime8(conn);
testOffsetDateTime8(conn); testOffsetDateTime8(conn);
testInstant8(conn);
testArray(conn); testArray(conn);
testUUIDGeneratedKeys(conn); testUUIDGeneratedKeys(conn);
testSetObject(conn); testSetObject(conn);
...@@ -702,6 +704,34 @@ public class TestPreparedStatement extends TestBase { ...@@ -702,6 +704,34 @@ public class TestPreparedStatement extends TestBase {
rs.close(); rs.close();
} }
private void testInstant8(Connection conn) throws Exception {
if (!LocalDateTimeUtils.isJava8DateApiPresent()) {
return;
}
Method timestampToInstant = Timestamp.class.getMethod("toInstant"),
now = LocalDateTimeUtils.getInstantClass().getMethod("now");
PreparedStatement prep = conn.prepareStatement("SELECT ?");
Object instant1 = now.invoke(null);
prep.setObject(1, instant1);
ResultSet rs = prep.executeQuery();
rs.next();
Object instant2 = rs.getObject(1, LocalDateTimeUtils.getInstantClass());
assertEquals(instant1, instant2);
Timestamp ts = rs.getTimestamp(1);
assertEquals(instant1, timestampToInstant.invoke(ts));
assertFalse(rs.next());
rs.close();
prep.setTimestamp(1, ts);
rs = prep.executeQuery();
rs.next();
instant2 = rs.getObject(1, LocalDateTimeUtils.getInstantClass());
assertEquals(instant1, instant2);
assertFalse(rs.next());
rs.close();
}
private void testPreparedSubquery(Connection conn) throws SQLException { private void testPreparedSubquery(Connection conn) throws SQLException {
Statement s = conn.createStatement(); Statement s = conn.createStatement();
s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)"); s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论