From 0332314c7f516b1ece1effecec5a3f8e2c5d85a7 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Thu, 13 Aug 2015 16:57:06 +0200 Subject: [PATCH] [#4338] Add support for java.time - Enhanced DefaultBinding for PreparedStatement, ResultSet --- jOOQ/src/main/java/org/jooq/DataType.java | 10 ++ .../java/org/jooq/impl/DefaultBinding.java | 107 ++++++++++++++++++ .../java/org/jooq/impl/DefaultDataType.java | 6 +- .../main/java/org/jooq/impl/SQLDataType.java | 36 ++++++ 4 files changed, 158 insertions(+), 1 deletion(-) diff --git a/jOOQ/src/main/java/org/jooq/DataType.java b/jOOQ/src/main/java/org/jooq/DataType.java index 94bb87da65..6f1e94499c 100644 --- a/jOOQ/src/main/java/org/jooq/DataType.java +++ b/jOOQ/src/main/java/org/jooq/DataType.java @@ -345,6 +345,11 @@ public interface DataType extends Serializable { *
  • {@link SQLDataType#DATE}
  • *
  • {@link SQLDataType#TIME}
  • *
  • {@link SQLDataType#TIMESTAMP}
  • + *
  • {@link SQLDataType#LOCALDATE}
  • + *
  • {@link SQLDataType#LOCALTIME}
  • + *
  • {@link SQLDataType#LOCALDATETIME}
  • + *
  • {@link SQLDataType#OFFSETTIME}
  • + *
  • {@link SQLDataType#OFFSETDATETIME}
  • * */ boolean isDateTime(); @@ -357,6 +362,11 @@ public interface DataType extends Serializable { *
  • {@link SQLDataType#DATE}
  • *
  • {@link SQLDataType#TIME}
  • *
  • {@link SQLDataType#TIMESTAMP}
  • + *
  • {@link SQLDataType#LOCALDATE}
  • + *
  • {@link SQLDataType#LOCALTIME}
  • + *
  • {@link SQLDataType#LOCALDATETIME}
  • + *
  • {@link SQLDataType#OFFSETTIME}
  • + *
  • {@link SQLDataType#OFFSETDATETIME}
  • *
  • {@link YearToMonth}
  • *
  • {@link DayToSecond}
  • * diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 21573056e6..31cadedae5 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -89,9 +89,17 @@ import java.sql.Timestamp; import java.sql.Types; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.List; +import java.util.TimeZone; import java.util.UUID; // ... @@ -318,6 +326,15 @@ public class DefaultBinding implements Binding { } } + /* [java-8] */ + if (type == OffsetTime.class || type == OffsetDateTime.class) { + switch (ctx.family()) { + case POSTGRES: + return true; + } + } + /* [/java-8] */ + return false; } @@ -368,6 +385,7 @@ public class DefaultBinding implements Binding { // [#1029] Postgres generally doesn't need the casting. Only in the // above case where the type is OTHER // [#1125] Also with temporal data types, casting is needed some times + // [#4338] ... specifically when using JSR-310 types // [#1130] TODO type can be null for ARRAY types, etc. else if (asList(POSTGRES).contains(family) && (sqlDataType == null || !sqlDataType.isTemporal())) { toSQL(ctx, converted); @@ -985,6 +1003,24 @@ public class DefaultBinding implements Binding { } } + /* [java-8] */ + else if (actualType == LocalDate.class) { + ctx.statement().setDate(ctx.index(), Date.valueOf((LocalDate) value)); + } + else if (actualType == LocalTime.class) { + ctx.statement().setTime(ctx.index(), Time.valueOf((LocalTime) value)); + } + else if (actualType == LocalDateTime.class) { + ctx.statement().setTimestamp(ctx.index(), Timestamp.valueOf((LocalDateTime) value)); + } + else if (actualType == OffsetTime.class) { + ctx.statement().setString(ctx.index(), value.toString()); + } + else if (actualType == OffsetDateTime.class) { + ctx.statement().setString(ctx.index(), value.toString()); + } + /* [/java-8] */ + // [#566] Interval data types are best bound as Strings else if (actualType == YearToMonth.class) { if (dialect.family() == POSTGRES) { @@ -1095,6 +1131,19 @@ public class DefaultBinding implements Binding { } } + private final ZoneOffset offset() { + return OffsetTime.now().getOffset(); + } + + private final Calendar calendar(Object value) { + if (value instanceof OffsetTime) + return Calendar.getInstance(TimeZone.getTimeZone(((OffsetTime) value).getOffset())); + if (value instanceof OffsetDateTime) + return Calendar.getInstance(TimeZone.getTimeZone(((OffsetDateTime) value).getOffset())); + + throw new IllegalArgumentException("Cannot extract calendar from " + value); + } + @Override public void set(BindingSetSQLOutputContext ctx) throws SQLException { T value = converter.to(ctx.value()); @@ -1285,9 +1334,28 @@ public class DefaultBinding implements Binding { else if (type == Integer.class) { result = (T) wasNull(ctx.resultSet(), Integer.valueOf(ctx.resultSet().getInt(ctx.index()))); } + /* [java-8] */ + else if (type == LocalDate.class) { + result = (T) localDate(getDate(ctx.configuration().dialect(), ctx.resultSet(), ctx.index())); + } + else if (type == LocalTime.class) { + result = (T) localTime(getTime(ctx.configuration().dialect(), ctx.resultSet(), ctx.index())); + } + else if (type == LocalDateTime.class) { + result = (T) localDateTime(getTimestamp(ctx.configuration().dialect(), ctx.resultSet(), ctx.index())); + } + /* [/java-8] */ else if (type == Long.class) { result = (T) wasNull(ctx.resultSet(), Long.valueOf(ctx.resultSet().getLong(ctx.index()))); } + /* [java-8] */ + else if (type == OffsetTime.class) { + result = (T) offsetTime(ctx.resultSet().getString(ctx.index())); + } + else if (type == OffsetDateTime.class) { + result = (T) offsetDateTime(ctx.resultSet().getString(ctx.index())); + } + /* [/java-8] */ else if (type == Short.class) { result = (T) wasNull(ctx.resultSet(), Short.valueOf(ctx.resultSet().getShort(ctx.index()))); } @@ -1408,6 +1476,45 @@ public class DefaultBinding implements Binding { ctx.value(converter.from(result)); } + private final LocalDate localDate(Date date) { + return date == null ? null : date.toLocalDate(); + } + + private final LocalTime localTime(Time time) { + return time == null ? null : time.toLocalTime(); + } + + private final LocalDateTime localDateTime(Timestamp timestamp) { + return timestamp == null ? null : timestamp.toLocalDateTime(); + } + + private final OffsetTime offsetTime(String string) { + if (string == null) + return null; + + // [#4338] PostgreSQL is more lenient regarding the offset format + if (string.lastIndexOf('+') == string.length() - 3) + string = string + ":00"; + + return OffsetTime.parse(string); + } + + private final OffsetDateTime offsetDateTime(String string) { + if (string == null) + return null; + + // [#4338] PostgreSQL is more lenient regarding the offset format + if (string.lastIndexOf('+') == string.length() - 3) + string = string + ":00"; + + // [#4338] SQL supports the alternative ISO 8601 date format, where a + // whitespace character separates date and time. java.time does not + if (string.charAt(10) == ' ') + string = string.substring(0, 10) + "T" + string.substring(11); + + return OffsetDateTime.parse(string); + } + @SuppressWarnings("unchecked") @Override public void get(BindingGetStatementContext ctx) throws SQLException { diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java b/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java index 5231af3d65..9d835dedc8 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultDataType.java @@ -774,7 +774,11 @@ public class DefaultDataType implements DataType { @Override public final boolean isDateTime() { - return java.util.Date.class.isAssignableFrom(type); + return java.util.Date.class.isAssignableFrom(type) + /* [java-8] */ + || java.time.temporal.Temporal.class.isAssignableFrom(type) + /* [/java-8] */ + ; } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java index 25675eeaa9..cf1b76e5ab 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java @@ -47,6 +47,11 @@ import java.sql.ResultSet; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; import java.util.UUID; import org.jooq.DataType; @@ -267,6 +272,37 @@ public final class SQLDataType { */ public static final DataType INTERVALDAYTOSECOND = new DefaultDataType(null, DayToSecond.class, "interval day to second"); + /* [java-8] */ + // ------------------------------------------------------------------------- + // JSR310 types + // ------------------------------------------------------------------------- + + /** + * The {@link Types#DATE} type. + */ + public static final DataType LOCALDATE = new DefaultDataType(null, LocalDate.class, "date"); + + /** + * The {@link Types#TIME} type. + */ + public static final DataType LOCALTIME = new DefaultDataType(null, LocalTime.class, "time"); + + /** + * The {@link Types#TIMESTAMP} type. + */ + public static final DataType LOCALDATETIME = new DefaultDataType(null, LocalDateTime.class, "timestamp"); + + /** + * The {@link Types#TIME_WITH_TIMEZONE} type. + */ + public static final DataType OFFSETTIME = new DefaultDataType(null, OffsetTime.class, "time with time zone"); + + /** + * The {@link Types#TIMESTAMP_WITH_TIMEZONE} type. + */ + public static final DataType OFFSETDATETIME = new DefaultDataType(null, OffsetDateTime.class, "timestamp with time zone"); + /* [/java-8] */ + // ------------------------------------------------------------------------- // Binary types // -------------------------------------------------------------------------