提交 ae038f3c authored 作者: Evgenij Ryazanov's avatar Evgenij Ryazanov

Add support for java.time.Period

上级 8469b131
......@@ -3260,6 +3260,7 @@ Interval data type.
If precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
","
INTERVAL YEAR
"
......@@ -3271,6 +3272,7 @@ Interval data type.
If precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
","
INTERVAL MONTH
"
......@@ -3331,6 +3333,7 @@ Interval data type.
If leading field precision is specified it should be from 1 to 18, 2 is default.
Mapped to ""org.h2.api.Interval"".
""java.time.Period"" is also supported on Java 8 and later versions.
","
INTERVAL YEAR TO MONTH
"
......
......@@ -3949,6 +3949,8 @@ public class JdbcResultSet extends TraceObject implements ResultSet, JdbcResultS
return type.cast(LocalDateTimeUtils.valueToInstant(value));
} else if (type == LocalDateTimeUtils.OFFSET_DATE_TIME) {
return type.cast(LocalDateTimeUtils.valueToOffsetDateTime(value));
} else if (type == LocalDateTimeUtils.PERIOD) {
return type.cast(LocalDateTimeUtils.valueToPeriod(value));
} else if (type == LocalDateTimeUtils.DURATION) {
return type.cast(LocalDateTimeUtils.valueToDuration(value));
} else {
......
......@@ -70,6 +70,11 @@ public class LocalDateTimeUtils {
*/
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}.
*/
......@@ -156,6 +161,26 @@ public class LocalDateTimeUtils {
*/
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}.
*/
......@@ -180,10 +205,11 @@ public class LocalDateTimeUtils {
INSTANT = tryGetClass("java.time.Instant");
OFFSET_DATE_TIME = tryGetClass("java.time.OffsetDateTime");
ZONE_OFFSET = tryGetClass("java.time.ZoneOffset");
PERIOD = tryGetClass("java.time.Period");
DURATION = tryGetClass("java.time.Duration");
IS_JAVA8_DATE_API_PRESENT = LOCAL_DATE != null && LOCAL_TIME != 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) {
LOCAL_TIME_OF_NANO = getMethod(LOCAL_TIME, "ofNanoOfDay", long.class);
......@@ -214,6 +240,11 @@ public class LocalDateTimeUtils {
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_GET_SECONDS = getMethod(DURATION, "getSeconds");
DURATION_GET_NANO = getMethod(DURATION, "getNano");
......@@ -236,6 +267,10 @@ public class LocalDateTimeUtils {
OFFSET_DATE_TIME_GET_OFFSET = null;
OFFSET_DATE_TIME_OF_LOCAL_DATE_TIME_ZONE_OFFSET = 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_GET_SECONDS = null;
DURATION_GET_NANO = null;
......@@ -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.
*
......@@ -552,6 +618,62 @@ public class LocalDateTimeUtils {
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.
*
......
......@@ -1285,6 +1285,8 @@ public class DataType {
} else if (x instanceof Interval) {
Interval i = (Interval) x;
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) {
return LocalDateTimeUtils.durationToValue(x);
} else {
......
......@@ -1405,7 +1405,7 @@ public abstract class Value {
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) {
throw DbException.get(
ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column));
......
......@@ -923,6 +923,23 @@ public class TestPreparedStatement extends TestDb {
return;
}
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;
try {
duration = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class)
......@@ -937,6 +954,28 @@ public class TestPreparedStatement extends TestDb {
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 {
Statement s = conn.createStatement();
s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)");
......
......@@ -1589,11 +1589,23 @@ public class TestResultSet extends TestDb {
}
trace("Test INTERVAL 8");
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.next();
assertEquals("INTERVAL '-3.1' SECOND", rs.getString(1));
Object expected;
try {
expected = LocalDateTimeUtils.DURATION.getMethod("ofSeconds", long.class, long.class)
.invoke(null, -4, 900_000_000);
......@@ -1601,10 +1613,7 @@ public class TestResultSet extends TestDb {
throw new RuntimeException(ex);
}
assertEquals(expected, rs.getObject(1, LocalDateTimeUtils.DURATION));
rs = stat.executeQuery("CALL INTERVAL '1-2' YEAR TO MONTH");
rs.next();
assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, LocalDateTimeUtils.DURATION);
assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, LocalDateTimeUtils.PERIOD);
}
private void testBlob() throws SQLException {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论