Unverified 提交 37047454 authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov 提交者: GitHub

Merge pull request #1588 from katzyn/datetime

Add support for java.time.Period
...@@ -3260,6 +3260,7 @@ Interval data type. ...@@ -3260,6 +3260,7 @@ Interval data type.
If precision is specified it should be from 1 to 18, 2 is default. If precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"". Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
"," ","
INTERVAL YEAR INTERVAL YEAR
" "
...@@ -3271,6 +3272,7 @@ Interval data type. ...@@ -3271,6 +3272,7 @@ Interval data type.
If precision is specified it should be from 1 to 18, 2 is default. If precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"". Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
"," ","
INTERVAL MONTH INTERVAL MONTH
" "
...@@ -3331,6 +3333,7 @@ Interval data type. ...@@ -3331,6 +3333,7 @@ Interval data type.
If leading field precision is specified it should be from 1 to 18, 2 is default. If leading field precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"". Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
"," ","
INTERVAL YEAR TO MONTH INTERVAL YEAR TO MONTH
" "
......
...@@ -3949,6 +3949,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS ...@@ -3949,6 +3949,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
return type.cast(LocalDateTimeUtils.valueToInstant(value)); return type.cast(LocalDateTimeUtils.valueToInstant(value));
} else if (type == LocalDateTimeUtils.OFFSET_DATE_TIME) { } else if (type == LocalDateTimeUtils.OFFSET_DATE_TIME) {
return type.cast(LocalDateTimeUtils.valueToOffsetDateTime(value)); return type.cast(LocalDateTimeUtils.valueToOffsetDateTime(value));
} else if (type == LocalDateTimeUtils.PERIOD) {
return type.cast(LocalDateTimeUtils.valueToPeriod(value));
} else if (type == LocalDateTimeUtils.DURATION) { } else if (type == LocalDateTimeUtils.DURATION) {
return type.cast(LocalDateTimeUtils.valueToDuration(value)); return type.cast(LocalDateTimeUtils.valueToDuration(value));
} else { } else {
......
...@@ -70,6 +70,11 @@ public class LocalDateTimeUtils { ...@@ -70,6 +70,11 @@ public class LocalDateTimeUtils {
*/ */
private static final Class<?> ZONE_OFFSET; private static final Class<?> ZONE_OFFSET;
/**
* {@code Class<java.time.Period>} or {@code null}.
*/
public static final Class<?> PERIOD;
/** /**
* {@code Class<java.time.Duration>} or {@code null}. * {@code Class<java.time.Duration>} or {@code null}.
*/ */
...@@ -156,6 +161,26 @@ public class LocalDateTimeUtils { ...@@ -156,6 +161,26 @@ public class LocalDateTimeUtils {
*/ */
private static final Method ZONE_OFFSET_GET_TOTAL_SECONDS; private static final Method ZONE_OFFSET_GET_TOTAL_SECONDS;
/**
* {@code java.time.Period#of(int, int, int)} or {@code null}.
*/
private static final Method PERIOD_OF;
/**
* {@code java.time.Period#getYears()} or {@code null}.
*/
private static final Method PERIOD_GET_YEARS;
/**
* {@code java.time.Period#getMonths()} or {@code null}.
*/
private static final Method PERIOD_GET_MONTHS;
/**
* {@code java.time.Period#getDays()} or {@code null}.
*/
private static final Method PERIOD_GET_DAYS;
/** /**
* {@code java.time.Duration#ofSeconds(long, long)} or {@code null}. * {@code java.time.Duration#ofSeconds(long, long)} or {@code null}.
*/ */
...@@ -180,10 +205,11 @@ public class LocalDateTimeUtils { ...@@ -180,10 +205,11 @@ public class LocalDateTimeUtils {
INSTANT = tryGetClass("java.time.Instant"); 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");
PERIOD = tryGetClass("java.time.Period");
DURATION = tryGetClass("java.time.Duration"); DURATION = tryGetClass("java.time.Duration");
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 && INSTANT != null && LOCAL_DATE_TIME != null && INSTANT != null &&
OFFSET_DATE_TIME != null && ZONE_OFFSET != null && DURATION != null; OFFSET_DATE_TIME != null && ZONE_OFFSET != null && PERIOD != null && DURATION != 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);
...@@ -214,6 +240,11 @@ public class LocalDateTimeUtils { ...@@ -214,6 +240,11 @@ public class LocalDateTimeUtils {
ZONE_OFFSET_GET_TOTAL_SECONDS = getMethod(ZONE_OFFSET, "getTotalSeconds"); ZONE_OFFSET_GET_TOTAL_SECONDS = getMethod(ZONE_OFFSET, "getTotalSeconds");
PERIOD_OF = getMethod(PERIOD, "of", int.class, int.class, int.class);
PERIOD_GET_YEARS = getMethod(PERIOD, "getYears");
PERIOD_GET_MONTHS = getMethod(PERIOD, "getMonths");
PERIOD_GET_DAYS = getMethod(PERIOD, "getDays");
DURATION_OF_SECONDS = getMethod(DURATION, "ofSeconds", long.class, long.class); DURATION_OF_SECONDS = getMethod(DURATION, "ofSeconds", long.class, long.class);
DURATION_GET_SECONDS = getMethod(DURATION, "getSeconds"); DURATION_GET_SECONDS = getMethod(DURATION, "getSeconds");
DURATION_GET_NANO = getMethod(DURATION, "getNano"); DURATION_GET_NANO = getMethod(DURATION, "getNano");
...@@ -236,6 +267,10 @@ public class LocalDateTimeUtils { ...@@ -236,6 +267,10 @@ public class LocalDateTimeUtils {
OFFSET_DATE_TIME_GET_OFFSET = null; OFFSET_DATE_TIME_GET_OFFSET = null;
OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET = null; OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET = null;
ZONE_OFFSET_GET_TOTAL_SECONDS = null; ZONE_OFFSET_GET_TOTAL_SECONDS = null;
PERIOD_OF = null;
PERIOD_GET_YEARS = null;
PERIOD_GET_MONTHS = null;
PERIOD_GET_DAYS = null;
DURATION_OF_SECONDS = null; DURATION_OF_SECONDS = null;
DURATION_GET_SECONDS = null; DURATION_GET_SECONDS = null;
DURATION_GET_NANO = null; DURATION_GET_NANO = null;
...@@ -382,6 +417,37 @@ public class LocalDateTimeUtils { ...@@ -382,6 +417,37 @@ public class LocalDateTimeUtils {
} }
} }
/**
* Converts a value to a Period.
*
* <p>This method should only called from Java 8 or later.</p>
*
* @param value the value to convert
* @return the Period
*/
public static Object valueToPeriod(Value value) {
if (!(value instanceof ValueInterval)) {
value = value.convertTo(Value.INTERVAL_YEAR_TO_MONTH);
}
if (!DataType.isYearMonthIntervalType(value.getType())) {
throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (Throwable) null, value.getString());
}
ValueInterval v = (ValueInterval) value;
IntervalQualifier qualifier = v.getQualifier();
boolean negative = v.isNegative();
long leading = v.getLeading();
long remaining = v.getRemaining();
int y = Value.convertToInt(IntervalUtils.yearsFromInterval(qualifier, negative, leading, remaining), null);
int m = Value.convertToInt(IntervalUtils.monthsFromInterval(qualifier, negative, leading, remaining), null);
try {
return PERIOD_OF.invoke(null, y, m, 0);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "timestamp with time zone conversion failed");
}
}
/** /**
* Converts a value to a Duration. * Converts a value to a Duration.
* *
...@@ -552,6 +618,62 @@ public class LocalDateTimeUtils { ...@@ -552,6 +618,62 @@ public class LocalDateTimeUtils {
return LOCAL_DATE_TIME_PLUS_NANOS.invoke(localDateTime, timeNanos); return LOCAL_DATE_TIME_PLUS_NANOS.invoke(localDateTime, timeNanos);
} }
/**
* Converts a Period to a Value.
*
* @param period the Period to convert, not {@code null}
* @return the value
*/
public static ValueInterval periodToValue(Object period) {
try {
int days = (int) PERIOD_GET_DAYS.invoke(period);
if (days != 0) {
throw DbException.getInvalidValueException("Period.days", days);
}
int years = (int) PERIOD_GET_YEARS.invoke(period);
int months = (int) PERIOD_GET_MONTHS.invoke(period);
IntervalQualifier qualifier;
boolean negative = false;
long leading = 0L, remaining = 0L;
if (years == 0) {
if (months == 0L) {
// Use generic qualifier
qualifier = IntervalQualifier.YEAR_TO_MONTH;
} else {
qualifier = IntervalQualifier.MONTH;
leading = months;
if (leading < 0) {
leading = -leading;
negative = true;
}
}
} else {
if (months == 0L) {
qualifier = IntervalQualifier.YEAR;
leading = years;
if (leading < 0) {
leading = -leading;
negative = true;
}
} else {
qualifier = IntervalQualifier.YEAR_TO_MONTH;
leading = years * 12 + months;
if (leading < 0) {
leading = -leading;
negative = true;
}
remaining = leading % 12;
leading /= 12;
}
}
return ValueInterval.from(qualifier, negative, leading, remaining);
} catch (IllegalAccessException e) {
throw DbException.convert(e);
} catch (InvocationTargetException e) {
throw DbException.convertInvocation(e, "interval conversion failed");
}
}
/** /**
* Converts a Duration to a Value. * Converts a Duration to a Value.
* *
......
...@@ -1285,6 +1285,8 @@ public class DataType { ...@@ -1285,6 +1285,8 @@ public class DataType {
} else if (x instanceof Interval) { } else if (x instanceof Interval) {
Interval i = (Interval) x; Interval i = (Interval) x;
return ValueInterval.from(i.getQualifier(), i.isNegative(), i.getLeading(), i.getRemaining()); return ValueInterval.from(i.getQualifier(), i.isNegative(), i.getLeading(), i.getRemaining());
} else if (clazz == LocalDateTimeUtils.PERIOD) {
return LocalDateTimeUtils.periodToValue(x);
} else if (clazz == LocalDateTimeUtils.DURATION) { } else if (clazz == LocalDateTimeUtils.DURATION) {
return LocalDateTimeUtils.durationToValue(x); return LocalDateTimeUtils.durationToValue(x);
} else { } else {
......
...@@ -1405,7 +1405,7 @@ public abstract class Value { ...@@ -1405,7 +1405,7 @@ public abstract class Value {
return (short) x; return (short) x;
} }
private static int convertToInt(long x, Object column) { public static int convertToInt(long x, Object column) {
if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE) { if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE) {
throw DbException.get( throw DbException.get(
ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column)); ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column));
......
...@@ -923,6 +923,23 @@ public class TestPreparedStatement extends TestDb { ...@@ -923,6 +923,23 @@ public class TestPreparedStatement extends TestDb {
return; return;
} }
PreparedStatement prep = conn.prepareStatement("SELECT ?"); PreparedStatement prep = conn.prepareStatement("SELECT ?");
testPeriod8(prep, 1, 2, "INTERVAL '1-2' YEAR TO MONTH");
testPeriod8(prep, -1, -2, "INTERVAL '-1-2' YEAR TO MONTH");
testPeriod8(prep, 1, -8, "INTERVAL '0-4' YEAR TO MONTH", 0, 4);
testPeriod8(prep, -1, 8, "INTERVAL '-0-4' YEAR TO MONTH", 0, -4);
testPeriod8(prep, 0, 0, "INTERVAL '0-0' YEAR TO MONTH");
testPeriod8(prep, 100, 0, "INTERVAL '100' YEAR");
testPeriod8(prep, -100, 0, "INTERVAL '-100' YEAR");
testPeriod8(prep, 0, 100, "INTERVAL '100' MONTH");
testPeriod8(prep, 0, -100, "INTERVAL '-100' MONTH");
Object period;
try {
Method method = LocalDateTimeUtils.PERIOD.getMethod("of", int.class, int.class, int.class);
period = method.invoke(null, 0, 0, 1);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
assertThrows(ErrorCode.INVALID_VALUE_2, prep).setObject(1, period);
Object duration; Object duration;
try { try {
duration = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class) duration = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class)
...@@ -937,6 +954,28 @@ public class TestPreparedStatement extends TestDb { ...@@ -937,6 +954,28 @@ public class TestPreparedStatement extends TestDb {
assertEquals(duration, rs.getObject(1, LocalDateTimeUtils.DURATION)); assertEquals(duration, rs.getObject(1, LocalDateTimeUtils.DURATION));
} }
private void testPeriod8(PreparedStatement prep, int years, int months, String expectedString)
throws SQLException {
testPeriod8(prep, years, months, expectedString, years, months);
}
private void testPeriod8(PreparedStatement prep, int years, int months, String expectedString, int expYears,
int expMonths) throws SQLException {
Object period, expectedPeriod;
try {
Method method = LocalDateTimeUtils.PERIOD.getMethod("of", int.class, int.class, int.class);
period = method.invoke(null, years, months, 0);
expectedPeriod = method.invoke(null, expYears, expMonths, 0);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
prep.setObject(1, period);
ResultSet rs = prep.executeQuery();
rs.next();
assertEquals(expectedString, rs.getString(1));
assertEquals(expectedPeriod, rs.getObject(1, LocalDateTimeUtils.PERIOD));
}
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)");
......
...@@ -1589,11 +1589,23 @@ public class TestResultSet extends TestDb { ...@@ -1589,11 +1589,23 @@ public class TestResultSet extends TestDb {
} }
trace("Test INTERVAL 8"); trace("Test INTERVAL 8");
ResultSet rs; ResultSet rs;
Object expected;
rs = stat.executeQuery("CALL INTERVAL '1-2' YEAR TO MONTH");
rs.next();
assertEquals("INTERVAL '1-2' YEAR TO MONTH", rs.getString(1));
try {
expected = LocalDateTimeUtils.PERIOD.getMethod("of", int.class, int.class, int.class)
.invoke(null, 1, 2, 0);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
assertEquals(expected, rs.getObject(1, LocalDateTimeUtils.PERIOD));
assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, LocalDateTimeUtils.DURATION);
rs = stat.executeQuery("CALL INTERVAL '-3.1' SECOND"); rs = stat.executeQuery("CALL INTERVAL '-3.1' SECOND");
rs.next(); rs.next();
assertEquals("INTERVAL '-3.1' SECOND", rs.getString(1)); assertEquals("INTERVAL '-3.1' SECOND", rs.getString(1));
Object expected;
try { try {
expected = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class) expected = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class)
.invoke(null, -4, 900_000_000); .invoke(null, -4, 900_000_000);
...@@ -1601,10 +1613,7 @@ public class TestResultSet extends TestDb { ...@@ -1601,10 +1613,7 @@ public class TestResultSet extends TestDb {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
assertEquals(expected, rs.getObject(1, LocalDateTimeUtils.DURATION)); assertEquals(expected, rs.getObject(1, LocalDateTimeUtils.DURATION));
assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, LocalDateTimeUtils.PERIOD);
rs = stat.executeQuery("CALL INTERVAL '1-2' YEAR TO MONTH");
rs.next();
assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, LocalDateTimeUtils.DURATION);
} }
private void testBlob() throws SQLException { private void testBlob() throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论