[#585] Add support for DATE, TIME and INTERVAL arithmetic

This commit is contained in:
Lukas Eder 2012-03-24 11:52:53 +00:00
parent f917e72193
commit bba467bcee
9 changed files with 415 additions and 24 deletions

View File

@ -44,6 +44,8 @@ import static junit.framework.Assert.fail;
import static org.jooq.SQLDialect.SQLITE;
import static org.jooq.impl.Factory.cast;
import static org.jooq.impl.Factory.castNull;
import static org.jooq.impl.Factory.dateDiff;
import static org.jooq.impl.Factory.timestampDiff;
import static org.jooq.impl.Factory.val;
import static org.jooq.tools.unsigned.Unsigned.ubyte;
import static org.jooq.tools.unsigned.Unsigned.uint;
@ -1191,6 +1193,8 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
public void testDateTime() throws Exception {
// [#1009] SQL DATE doesn't have a time zone. SQL TIMESTAMP does
long tsShift = -3600000;
Record record =
create().select(
val(new Date(0)).as("d"),
@ -1202,7 +1206,7 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
// ... (except for SQLite)
if (getDialect() != SQLITE)
assertEquals(new Date(-3600000), record.getValue("d"));
assertEquals(new Date(tsShift), record.getValue("d"));
assertEquals(new Time(0), record.getValue("t"));
assertEquals(new Timestamp(0), record.getValue("ts"));
@ -1211,8 +1215,8 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
// TODO: Add tests for reading date / time / interval types into pojos
// [#566] INTERVAL arithmetic
// --------------------------
// [#566] INTERVAL arithmetic: multiplication
// ------------------------------------------
record =
create().select(
val(new YearToMonth(1)).div(2).as("y1"),
@ -1231,5 +1235,82 @@ extends BaseTest<A, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, I, IPK, T658, T725
assertEquals(new DayToSecond(0, 12), record.getValue("d1"));
assertEquals(new DayToSecond(2), record.getValue("d2"));
assertEquals(new DayToSecond(1), record.getValue("d3"));
// [#566] INTERVAL arithmetic: addition
// ------------------------------------
record =
create().select(
val(new Date(0)).add(1).as("d1"),
val(new Date(0)).sub(1).as("d2"),
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 DayToSecond(2)).as("d5"),
val(new Date(0)).sub(new DayToSecond(2)).as("d6"),
val(new Timestamp(0)).add(1).as("ts1"),
val(new Timestamp(0)).sub(1).as("ts2"),
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 DayToSecond(2)).as("ts5"),
val(new Timestamp(0)).sub(new DayToSecond(2)).as("ts6")
).fetchOne();
Calendar cal;
cal = cal();
cal.add(Calendar.DATE, 1);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d1"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts1"));
cal = cal();
cal.add(Calendar.DATE, -1);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d2"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts2"));
cal = cal();
cal.add(Calendar.MONTH, 18);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d3"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts3"));
cal = cal();
cal.add(Calendar.MONTH, -18);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d4"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts4"));
cal = cal();
cal.add(Calendar.DATE, 2);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d5"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts5"));
cal = cal();
cal.add(Calendar.DATE, -2);
assertEquals(new Date(cal.getTimeInMillis()), record.getValue("d6"));
assertEquals(new Timestamp(cal.getTimeInMillis() - tsShift), record.getValue("ts6"));
// [#566] INTERVAL arithmetic: difference
// --------------------------------------
record =
create().select(
dateDiff(new Date(0), new Date(24 * 60 * 60 * 1000L)).as("d1"),
dateDiff(new Date(24 * 60 * 60 * 1000L), new Date(0)).as("d2"),
//TODO [#566] Make this work!
//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(24 * 60 * 60 * 1000L)).as("ts1"),
timestampDiff(new Timestamp(24 * 60 * 60 * 1000L), new Timestamp(0)).as("ts2")
).fetchOne();
assertEquals(-1, record.getValue("d1"));
assertEquals(1, record.getValue("d2"));
//assertEquals(new DayToSecond(0, 1).neg(), record.getValue("t1"));
//assertEquals(new DayToSecond(0, 1), record.getValue("t2"));
assertEquals(new DayToSecond(1).neg(), record.getValue("ts1"));
assertEquals(new DayToSecond(1), record.getValue("ts2"));
}
private Calendar cal() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(-3600000);
return cal;
}
}

View File

@ -54,6 +54,7 @@ import java.util.Collection;
import java.util.Map;
import org.jooq.impl.Factory;
import org.jooq.types.Interval;
/**
* A field used in tables and conditions
@ -291,60 +292,161 @@ public interface Field<T> extends NamedTypeProviderQueryPart<T>, AliasProvider<F
/**
* An arithmetic expression adding this to value.
* <p>
* <ul>
* <li>If this is a numeric field, then the value is added arithmetically</li>
* <li>If this is a date time field, then [value] days are added to this date</li>
* </ul>
*
* @see #add(Field)
*/
@Support
Field<T> add(Number value);
/**
* An arithmetic expression adding this to value
* An arithmetic expression adding an interval to this.
*
* @see #add(Field)
*/
@Support
Field<T> add(Field<? extends Number> value);
Field<T> add(Interval<?> value);
/**
* An arithmetic expression to add value to this.
* <p>
* The behaviour of this operation is as follows:
* <table border="1">
* <tr>
* <th>Operand 1</th>
* <th>Operand 2</th>
* <th>Result Type</th>
* </tr>
* <tr>
* <td>Numeric</td>
* <td>Numeric</td>
* <td>Numeric</td>
* </tr>
* <tr>
* <td>Date / Time</td>
* <td>Numeric</td>
* <td>Date / Time</td>
* </tr>
* <tr>
* <td>Date / Time</td>
* <td>Interval</td>
* <td>Date / Time</td>
* </tr>
* <tr>
* <td>Interval</td>
* <td>Interval</td>
* <td>Interval</td>
* </tr>
* </table>
*/
@Support
Field<T> add(Field<?> value);
/**
* An arithmetic expression subtracting value from this.
* <p>
* <ul>
* <li>If this is a numeric field, then the value is subtracted
* arithmetically</li>
* <li>If this is a date time field, then [value] days are subtracted to
* this date</li>
* </ul>
*
* @see #sub(Field)
*/
@Support
Field<T> sub(Number value);
/**
* An arithmetic expression subtracting value from this
* An arithmetic expression subtracting an interval from this.
*
* @see #sub(Field)
*/
@Support
Field<T> sub(Field<? extends Number> value);
Field<T> sub(Interval<?> value);
/**
* An arithmetic expression subtracting value from this.
* <p>
* <table border="1">
* <tr>
* <th>Operand 1</th>
* <th>Operand 2</th>
* <th>Result Type</th>
* </tr>
* <tr>
* <td>Numeric</td>
* <td>Numeric</td>
* <td>Numeric</td>
* </tr>
* <tr>
* <td>Date / Time</td>
* <td>Numeric</td>
* <td>Date / Time</td>
* </tr>
* <tr>
* <td>Date / Time</td>
* <td>Interval</td>
* <td>Date / Time</td>
* </tr>
* <tr>
* <td>Interval</td>
* <td>Interval</td>
* <td>Interval</td>
* </tr>
* </table>
* <p>
* In order to subtract one date time field from another, use any of these
* methods:
* <ul>
* <li> {@link Factory#dateDiff(Field, Field)}</li>
* <li> {@link Factory#timeDiff(Field, Field)}</li>
* <li> {@link Factory#timestampDiff(Field, Field)}</li>
* </ul>
*/
@Support
Field<T> sub(Field<?> value);
/**
* An arithmetic expression multiplying this with value
* <p>
* <ul>
* <li>If this is a numeric field, then the result is a number of the same
* type as this field.</li>
* <li>If this is an <code>INTERVAL</code> field, then the result is also an
* <code>INTERVAL</code> field (see {@link Interval})</li>
* </ul>
*/
@Support
Field<T> mul(Number value);
/**
* An arithmetic expression multiplying this with value
* <p>
* <ul>
* <li>If this is a numeric field, then the result is a number of the same
* type as this field.</li>
* <li>If this is an <code>INTERVAL</code> field, then the result is also an
* <code>INTERVAL</code> field (see {@link Interval})</li>
* </ul>
*/
@Support
Field<T> mul(Field<? extends Number> value);
/**
* An arithmetic expression dividing this by value
* <p>
* <ul>
* <li>If this is a numeric field, then the result is a number of the same
* type as this field.</li>
* <li>If this is an <code>INTERVAL</code> field, then the result is also an
* <code>INTERVAL</code> field (see {@link Interval})</li>
* </ul>
*/
@Support
Field<T> div(Number value);
/**
* An arithmetic expression dividing this by value
* <p>
* <ul>
* <li>If this is a numeric field, then the result is a number of the same
* type as this field.</li>
* <li>If this is an <code>INTERVAL</code> field, then the result is also an
* <code>INTERVAL</code> field (see {@link Interval})</li>
* </ul>
*/
@Support
Field<T> div(Field<? extends Number> value);

View File

@ -71,6 +71,7 @@ import org.jooq.SortOrder;
import org.jooq.WindowIgnoreNullsStep;
import org.jooq.WindowPartitionByStep;
import org.jooq.tools.Convert;
import org.jooq.types.Interval;
abstract class AbstractField<T> extends AbstractNamedTypeProviderQueryPart<T> implements Field<T> {
@ -225,12 +226,17 @@ abstract class AbstractField<T> extends AbstractNamedTypeProviderQueryPart<T> im
}
}
@Override
public final Field<T> add(Interval<?> value) {
return add(val(value));
}
/**
* This default implementation is known to be overridden by
* {@link Expression} to generate neater expressions
*/
@Override
public Field<T> add(Field<? extends Number> value) {
public Field<T> add(Field<?> value) {
return new Expression<T>(ADD, this, nullSafe(value));
}
@ -249,7 +255,12 @@ abstract class AbstractField<T> extends AbstractNamedTypeProviderQueryPart<T> im
}
@Override
public final Field<T> sub(Field<? extends Number> value) {
public final Field<T> sub(Interval<?> value) {
return sub(val(value));
}
@Override
public final Field<T> sub(Field<?> value) {
return new Expression<T>(SUBTRACT, this, nullSafe(value));
}

View File

@ -136,7 +136,7 @@ public abstract class CustomField<T> extends AbstractField<T> {
}
@Override
public final Field<T> add(Field<? extends Number> value) {
public final Field<T> add(Field<?> value) {
return super.add(value);
}

View File

@ -92,7 +92,7 @@ class Expression<T> extends AbstractFunction<T> {
}
@Override
public final Field<T> add(Field<? extends Number> value) {
public final Field<T> add(Field<?> value) {
if (operator == ExpressionOperator.ADD) {
rhs.add(value);
return this;

View File

@ -131,6 +131,7 @@ import org.jooq.exception.DataAccessException;
import org.jooq.exception.InvalidResultException;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.tools.JooqLogger;
import org.jooq.types.DayToSecond;
/**
* A factory providing implementations to the org.jooq interfaces
@ -4450,7 +4451,7 @@ public class Factory implements FactoryOperations {
}
// -------------------------------------------------------------------------
// XXX Pseudo fields and date time functions
// XXX date time functions
// -------------------------------------------------------------------------
/**
@ -4483,6 +4484,165 @@ public class Factory implements FactoryOperations {
return new CurrentTimestamp();
}
/**
* Get the date difference in number of days
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<Integer> dateDiff(Date date1, Date date2) {
return dateDiff(val(date1), val(date2));
}
/**
* Get the date difference in number of days
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<Integer> dateDiff(Field<Date> date1, Date date2) {
return dateDiff(nullSafe(date1), val(date2));
}
/**
* Get the date difference in number of days
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<Integer> dateDiff(Date date1, Field<Date> date2) {
return dateDiff(val(date1), nullSafe(date2));
}
/**
* Get the date difference in number of days
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<Integer> dateDiff(Field<Date> date1, Field<Date> date2) {
// TODO [#585] This cast shouldn't be necessary
return nullSafe(date1).sub(nullSafe(date2)).cast(Integer.class);
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timestampDiff(Timestamp timestamp1, Timestamp timestamp2) {
return timestampDiff(val(timestamp1), val(timestamp2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timestampDiff(Field<Timestamp> timestamp1, Timestamp timestamp2) {
return timestampDiff(nullSafe(timestamp1), val(timestamp2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timestampDiff(Timestamp timestamp1, Field<Timestamp> timestamp2) {
return timestampDiff(val(timestamp1), nullSafe(timestamp2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timestampDiff(Field<Timestamp> timestamp1, Field<Timestamp> timestamp2) {
// TODO [#585] This cast shouldn't be necessary
return nullSafe(timestamp1).sub(nullSafe(timestamp2)).cast(DayToSecond.class);
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timeDiff(Time time1, Time time2) {
return timeDiff(val(time1), val(time2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timeDiff(Field<Time> time1, Time time2) {
return timeDiff(nullSafe(time1), val(time2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timeDiff(Time time1, Field<Time> time2) {
return timeDiff(val(time1), nullSafe(time2));
}
/**
* Get the timestamp difference as a <code>INTERVAL DAY TO SECOND</code>
* type
* <p>
* This translates into any dialect
*
* @see Field#sub(Field)
*/
@Support
public static Field<DayToSecond> timeDiff(Field<Time> time1, Field<Time> time2) {
// TODO [#585] This cast shouldn't be necessary
return nullSafe(time1).sub(nullSafe(time2)).cast(DayToSecond.class);
}
// -------------------------------------------------------------------------
// XXX other functions
// -------------------------------------------------------------------------
/**
* Get the current_user() function
* <p>

View File

@ -133,6 +133,20 @@ public final class DayToSecond implements Interval<DayToSecond> {
return null;
}
// -------------------------------------------------------------------------
// XXX Inteval API
// -------------------------------------------------------------------------
@Override
public final DayToSecond neg() {
return new DayToSecond(days, hours, minutes, seconds, nano, !negative);
}
@Override
public final DayToSecond abs() {
return new DayToSecond(days, hours, minutes, seconds, nano, false);
}
// -------------------------------------------------------------------------
// XXX Comparable and Object API
// -------------------------------------------------------------------------

View File

@ -101,4 +101,13 @@ import java.io.Serializable;
*/
public interface Interval<T extends Interval<T>> extends Serializable, Comparable<T> {
/**
* Negate the interval (change its sign)
*/
T neg();
/**
* Get the absolute value of the interval (set its sign to positive)
*/
T abs();
}

View File

@ -101,6 +101,20 @@ public final class YearToMonth implements Interval<YearToMonth> {
return null;
}
// -------------------------------------------------------------------------
// XXX Inteval API
// -------------------------------------------------------------------------
@Override
public final YearToMonth neg() {
return new YearToMonth(years, months, !negative);
}
@Override
public final YearToMonth abs() {
return new YearToMonth(years, months, false);
}
// -------------------------------------------------------------------------
// XXX Comparable and Object API
// -------------------------------------------------------------------------