[#566] Add support for INTERVAL data types - some fixes for Postgres

[#585] Add support for DATE, TIME and INTERVAL arithmetic - some fixes for Postgres
This commit is contained in:
Lukas Eder 2012-04-07 13:26:32 +00:00
parent 3b95d040ba
commit 053d107f6e
5 changed files with 106 additions and 42 deletions

View File

@ -1250,15 +1250,22 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
else {
record =
create().select(
val(new Date(0)).as("d"),
val(new Time(0)).as("t"),
val(new Timestamp(0)).as("ts"),
val(new YearToMonth(1, 1)).as("iyplus"),
val(new YearToMonth(0)).as("iy"),
val(new DayToSecond(0)).as("id")
val(new YearToMonth(1, 1).neg()).as("iyminus"),
val(new DayToSecond(1, 1, 1, 1)).as("idplus"),
val(new DayToSecond(0)).as("id"),
val(new DayToSecond(1, 1, 1, 1).neg()).as("idminus")
).fetchOne();
assertEquals(new YearToMonth(1, 1), record.getValue("iyplus"));
assertEquals(new YearToMonth(0), record.getValue("iy"));
assertEquals(new YearToMonth(1, 1).neg(), record.getValue("iyminus"));
assertEquals(new DayToSecond(1, 1, 1, 1), record.getValue("idplus"));
assertEquals(new DayToSecond(0), record.getValue("id"));
assertEquals(new DayToSecond(1, 1, 1, 1).neg(), record.getValue("idminus"));
// TODO: Add tests for reading date / time / interval types into pojos
// [#566] INTERVAL arithmetic: multiplication
@ -1294,21 +1301,31 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
// ------------------------------------
Record record =
create().select(
// Extra care needs to be taken with Postgres negative DAY TO SECOND
// intervals. Postgres allows for having several signs in intervals
val(new Date(0)).add(1).as("d1"),
val(new Date(0)).sub(1).as("d2"),
val(new Date(0)).add(-1).as("d2a"),
val(new Date(0)).sub(1).as("d2b"),
val(new Date(0)).add(new YearToMonth(1, 6)).as("d3"),
val(new Date(0)).sub(new YearToMonth(1, 6)).as("d4"),
val(new Date(0)).add(new YearToMonth(1, 6).neg()).as("d4a"),
val(new Date(0)).sub(new YearToMonth(1, 6)).as("d4b"),
val(new Date(0)).add(new DayToSecond(2)).as("d5"),
val(new Date(0)).sub(new DayToSecond(2)).as("d6"),
val(new Date(0)).add(new DayToSecond(2).neg()).as("d6a"),
val(new Date(0)).sub(new DayToSecond(2)).as("d6b"),
val(new Timestamp(0)).add(1).as("ts1"),
val(new Timestamp(0)).sub(1).as("ts2"),
val(new Timestamp(0)).add(-1).as("ts2a"),
val(new Timestamp(0)).sub(1).as("ts2b"),
val(new Timestamp(0)).add(new YearToMonth(1, 6)).as("ts3"),
val(new Timestamp(0)).sub(new YearToMonth(1, 6)).as("ts4"),
val(new Timestamp(0)).add(new YearToMonth(1, 6).neg()).as("ts4a"),
val(new Timestamp(0)).sub(new YearToMonth(1, 6)).as("ts4b"),
val(new Timestamp(0)).add(new DayToSecond(2)).as("ts5"),
val(new Timestamp(0)).sub(new DayToSecond(2)).as("ts6"),
val(new Timestamp(0)).add(new DayToSecond(2).neg()).as("ts6a"),
val(new Timestamp(0)).sub(new DayToSecond(2)).as("ts6b"),
val(new Timestamp(0)).add(new DayToSecond(2, 6)).as("ts7"),
val(new Timestamp(0)).sub(new DayToSecond(2, 6)).as("ts8"),
val(new Timestamp(0)).add(new DayToSecond(2, 6).neg()).as("ts8a"),
val(new Timestamp(0)).sub(new DayToSecond(2, 6)).as("ts8b"),
// Dummy field for simpler testing
literal("'dummy'")
@ -1323,8 +1340,10 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
cal = cal();
cal.add(Calendar.DATE, -1);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d2"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts2"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d2a"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d2b"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts2a"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts2b"));
cal = cal();
cal.add(Calendar.MONTH, 18);
@ -1333,8 +1352,10 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
cal = cal();
cal.add(Calendar.MONTH, -18);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d4"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts4"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d4a"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d4b"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts4a"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts4b"));
cal = cal();
cal.add(Calendar.DATE, 2);
@ -1343,8 +1364,10 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
cal = cal();
cal.add(Calendar.DATE, -2);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d6"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts6"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d6a"));
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d6b"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts6a"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts6b"));
cal = cal();
cal.add(Calendar.DATE, 2);
@ -1354,7 +1377,8 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
cal = cal();
cal.add(Calendar.DATE, -2);
cal.add(Calendar.HOUR, -6);
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts8"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts8a"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts8b"));
// [#566] INTERVAL arithmetic: difference
// --------------------------------------
@ -1366,7 +1390,10 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
//timeDiff(new Time(0), new Time(60 * 60 * 1000L)).as("t1"),
//timeDiff(new Time(60 * 60 * 1000L), new Time(0)).as("t2"),
timestampDiff(new Timestamp(0), new Timestamp(30 * 60 * 60 * 1000L)).as("ts1"),
timestampDiff(new Timestamp(30 * 60 * 60 * 1000L), new Timestamp(0)).as("ts2")
timestampDiff(new Timestamp(30 * 60 * 60 * 1000L), new Timestamp(0)).as("ts2"),
// Dummy field for simpler testing
literal("'dummy'")
).fetchOne();
assertEquals(-1, record.getValue("d1"));

View File

@ -91,11 +91,10 @@ class DateDiff extends AbstractFunction<Integer> {
case CUBRID:
case ORACLE:
case POSTGRES:
// TODO [#585] This cast shouldn't be necessary
return date1.sub(date2).cast(Integer.class);
return field("{0} - {1}", getDataType(), date1, date2);
}
return null;
// Default implementation for equals() and hashCode()
return date1.sub(date2).cast(Integer.class);
}
}

View File

@ -66,7 +66,6 @@ import static org.jooq.impl.Factory.function;
import static org.jooq.impl.Factory.two;
import static org.jooq.impl.Factory.val;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;
@ -428,14 +427,15 @@ class Expression<T> extends AbstractFunction<T> {
}
case POSTGRES: {
// Postgres can add / subtract days using +/- operators only to DATE
DataType<T> type = lhs.getDataType();
if (type.getType() == Date.class) {
return new DefaultExpression();
// This seems to be the most reliable way to avoid issues
// with incompatible data types and timezones
// ? + CAST (? || ' days' as interval)
if (operator == ADD) {
return lhs.add(rhsAsNumber().concat(" day").cast(DayToSecond.class));
}
else {
return new Expression<Date>(operator, lhs.cast(Date.class), rhsAsNumber()).cast(type);
return lhs.sub(rhsAsNumber().concat(" day").cast(DayToSecond.class));
}
}

View File

@ -114,11 +114,10 @@ class TimestampDiff extends AbstractFunction<DayToSecond> {
case ORACLE:
case POSTGRES:
// TODO [#585] This cast shouldn't be necessary
return timestamp1.sub(timestamp2).cast(DayToSecond.class);
return field("{0} - {1}", getDataType(), timestamp1, timestamp2);
}
return null;
// Default implementation for equals() and hashCode()
return timestamp1.sub(timestamp2).cast(DayToSecond.class);
}
}

View File

@ -47,42 +47,81 @@ import org.jooq.types.YearToMonth;
* Postgres returns an undisclosed internal type for intervals. This converter
* takes care of converting the internal type to jOOQ's interval data types
* {@link DayToSecond} and {@link YearToMonth}
* <p>
* Note, that Postgres uses some non-standard ways of describing negative
* intervals. Negative intervals have a sign before every date part!
*
* @author Lukas Eder
*/
public class PGIntervalConverter {
/**
* Convert a jOOQ <code>DAY TO SECOND</code> interval to a Postgres representation
*/
public static Object toPGInterval(DayToSecond interval) {
return on("org.postgresql.util.PGInterval").create(0, 0,
interval.getDays(),
interval.getHours(),
interval.getMinutes(),
interval.getSeconds() +
interval.getNano() / 1000000000.0).get();
interval.getSign() * interval.getDays(),
interval.getSign() * interval.getHours(),
interval.getSign() * interval.getMinutes(),
interval.getSign() * interval.getSeconds() +
interval.getSign() * interval.getNano() / 1000000000.0).get();
}
/**
* Convert a jOOQ <code>YEAR TO MONTH</code> interval to a Postgres representation
*/
public static Object toPGInterval(YearToMonth interval) {
return on("org.postgresql.util.PGInterval").create(
interval.getYears(),
interval.getMonths(),
interval.getSign() * interval.getYears(),
interval.getSign() * interval.getMonths(),
0, 0, 0, 0.0).get();
}
/**
* Convert a Postgres interval to a jOOQ <code>DAY TO SECOND</code> interval
*/
public static DayToSecond toDayToSecond(Object pgInterval) {
boolean negative = pgInterval.toString().contains("-");
Reflect i = on(pgInterval);
if (negative) {
i.call("scale", -1);
}
Double seconds = i.call("getSeconds").<Double>get();
return new DayToSecond(
DayToSecond result = new DayToSecond(
i.call("getDays").<Integer>get(),
i.call("getHours").<Integer>get(),
i.call("getMinutes").<Integer>get(),
seconds.intValue(),
(int) (1000000000 * (seconds - seconds.intValue())));
if (negative) {
result = result.neg();
}
return result;
}
/**
* Convert a Postgres interval to a jOOQ <code>YEAR TO MONTH</code> interval
*/
public static YearToMonth toYearToMonth(Object pgInterval) {
boolean negative = pgInterval.toString().contains("-");
Reflect i = on(pgInterval);
return new YearToMonth(
if (negative) {
i.call("scale", -1);
}
YearToMonth result = new YearToMonth(
i.call("getYears").<Integer>get(),
i.call("getMonths").<Integer>get());
if (negative) {
result = result.neg();
}
return result;
}
}