From 3c242dd05d28ada3f8065b74a33fc480136f84ea Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 27 Mar 2012 16:25:22 +0000 Subject: [PATCH] [#566] Add support for INTERVAL data types - initial implementation for the Postgres dialect --- .../generatedclasses/test/tables/TDates.java | 6 +- .../org/jooq/impl/DefaultBindContext.java | 17 ++- .../java/org/jooq/impl/FieldTypeHelper.java | 42 ++++-- .../main/java/org/jooq/impl/SQLDataType.java | 4 +- .../main/java/org/jooq/types/DayToSecond.java | 53 +++++++- .../main/java/org/jooq/types/YearToMonth.java | 16 +++ .../org/jooq/util/oracle/OracleDataType.java | 110 ++++++++-------- .../util/postgres/PGIntervalConverter.java | 88 +++++++++++++ .../jooq/util/postgres/PostgresDataType.java | 122 +++++++++--------- 9 files changed, 322 insertions(+), 136 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/util/postgres/PGIntervalConverter.java diff --git a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/TDates.java b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/TDates.java index c6cca22177..0300356d65 100644 --- a/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/TDates.java +++ b/jOOQ-test/src/org/jooq/test/oracle/generatedclasses/test/tables/TDates.java @@ -8,7 +8,7 @@ package org.jooq.test.oracle.generatedclasses.test.tables; */ public class TDates extends org.jooq.impl.UpdatableTableImpl { - private static final long serialVersionUID = 2000943673; + private static final long serialVersionUID = -745318463; /** * The singleton instance of TEST.T_DATES @@ -63,12 +63,12 @@ public class TDates extends org.jooq.impl.UpdatableTableImpl I_Y = createField("I_Y", org.jooq.impl.SQLDataType.INTERVAL_YEAR_TO_MONTH, this); + public final org.jooq.TableField I_Y = createField("I_Y", org.jooq.impl.SQLDataType.INTERVALYEARTOMONTH, this); /** * An uncommented item */ - public final org.jooq.TableField I_D = createField("I_D", org.jooq.impl.SQLDataType.INTERVAL_DAY_TO_SECOND, this); + public final org.jooq.TableField I_D = createField("I_D", org.jooq.impl.SQLDataType.INTERVALDAYTOSECOND, this); /** * No further instances allowed diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java index d2faf4a6e5..df718ce11c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java @@ -40,6 +40,7 @@ import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.SQLDialect.SQLITE; import static org.jooq.SQLDialect.SQLSERVER; import static org.jooq.SQLDialect.SYBASE; +import static org.jooq.util.postgres.PGIntervalConverter.toPGInterval; import java.math.BigDecimal; import java.math.BigInteger; @@ -221,13 +222,23 @@ class DefaultBindContext extends AbstractBindContext { else if (type == Timestamp.class) { stmt.setTimestamp(nextIndex(), (Timestamp) value); } - + // [#566] Interval data types are best bound as Strings else if (type == YearToMonth.class) { - stmt.setString(nextIndex(), value.toString()); + if (dialect == POSTGRES) { + stmt.setObject(nextIndex(), toPGInterval((YearToMonth) value)); + } + else { + stmt.setString(nextIndex(), value.toString()); + } } else if (type == DayToSecond.class) { - stmt.setString(nextIndex(), value.toString()); + if (dialect == POSTGRES) { + stmt.setObject(nextIndex(), toPGInterval((DayToSecond) value)); + } + else { + stmt.setString(nextIndex(), value.toString()); + } } else if (UNumber.class.isAssignableFrom(type)) { stmt.setString(nextIndex(), value.toString()); diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java index 0b266fd900..63cb9fce8c 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java @@ -36,6 +36,7 @@ package org.jooq.impl; +import static org.jooq.SQLDialect.POSTGRES; import static org.jooq.impl.Factory.getNewFactory; import java.math.BigDecimal; @@ -87,6 +88,7 @@ import org.jooq.util.hsqldb.HSQLDBDataType; import org.jooq.util.ingres.IngresDataType; import org.jooq.util.mysql.MySQLDataType; import org.jooq.util.oracle.OracleDataType; +import org.jooq.util.postgres.PGIntervalConverter; import org.jooq.util.postgres.PGobjectParser; import org.jooq.util.postgres.PostgresDataType; import org.jooq.util.sqlite.SQLiteDataType; @@ -380,12 +382,24 @@ public final class FieldTypeHelper { return (T) getTimestamp(ctx.getDialect(), rs, index); } else if (type == YearToMonth.class) { - String string = rs.getString(index); - return (T) (string == null ? null : YearToMonth.valueOf(string)); + if (ctx.getDialect() == POSTGRES) { + Object object = rs.getObject(index); + return (T) (object == null ? null : PGIntervalConverter.toYearToMonth(object)); + } + else { + String string = rs.getString(index); + return (T) (string == null ? null : YearToMonth.valueOf(string)); + } } else if (type == DayToSecond.class) { - String string = rs.getString(index); - return (T) (string == null ? null : DayToSecond.valueOf(string)); + if (ctx.getDialect() == POSTGRES) { + Object object = rs.getObject(index); + return (T) (object == null ? null : PGIntervalConverter.toDayToSecond(object)); + } + else { + String string = rs.getString(index); + return (T) (string == null ? null : DayToSecond.valueOf(string)); + } } else if (type == UByte.class) { String string = rs.getString(index); @@ -653,12 +667,24 @@ public final class FieldTypeHelper { return (T) stmt.getTimestamp(index); } else if (type == YearToMonth.class) { - String string = stmt.getString(index); - return (T) (string == null ? null : YearToMonth.valueOf(string)); + if (ctx.getDialect() == POSTGRES) { + Object object = stmt.getObject(index); + return (T) (object == null ? null : PGIntervalConverter.toYearToMonth(object)); + } + else { + String string = stmt.getString(index); + return (T) (string == null ? null : YearToMonth.valueOf(string)); + } } else if (type == DayToSecond.class) { - String string = stmt.getString(index); - return (T) (string == null ? null : DayToSecond.valueOf(string)); + if (ctx.getDialect() == POSTGRES) { + Object object = stmt.getObject(index); + return (T) (object == null ? null : PGIntervalConverter.toDayToSecond(object)); + } + else { + String string = stmt.getString(index); + return (T) (string == null ? null : DayToSecond.valueOf(string)); + } } else if (type == UByte.class) { String string = stmt.getString(index); diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java index a9bc299ddb..51cc593b80 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLDataType.java @@ -250,12 +250,12 @@ public final class SQLDataType extends AbstractDataType { /** * The SQL standard INTERVAL YEAR TO MONTH data type */ - public static final SQLDataType INTERVAL_YEAR_TO_MONTH = new SQLDataType(YearToMonth.class, "interval_year_to_month"); + public static final SQLDataType INTERVALYEARTOMONTH = new SQLDataType(YearToMonth.class, "interval year to month"); /** * The SQL standard INTERVAL DAY TO SECOND data type */ - public static final SQLDataType INTERVAL_DAY_TO_SECOND = new SQLDataType(DayToSecond.class, "interval_day_to_second"); + public static final SQLDataType INTERVALDAYTOSECOND = new SQLDataType(DayToSecond.class, "interval day to second"); // ------------------------------------------------------------------------- // Binary types diff --git a/jOOQ/src/main/java/org/jooq/types/DayToSecond.java b/jOOQ/src/main/java/org/jooq/types/DayToSecond.java index 58be33f5b7..184f2f6ae5 100644 --- a/jOOQ/src/main/java/org/jooq/types/DayToSecond.java +++ b/jOOQ/src/main/java/org/jooq/types/DayToSecond.java @@ -38,6 +38,7 @@ package org.jooq.types; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.jooq.tools.Convert; import org.jooq.tools.StringUtils; /** @@ -53,7 +54,7 @@ public final class DayToSecond implements Interval { * Generated UID */ private static final long serialVersionUID = -3853596481984643811L; - private static final Pattern PATTERN = Pattern.compile("(\\+|-)?(\\d+) (\\d+):(\\d+):(\\d+)\\.(\\d+)"); + private static final Pattern PATTERN = Pattern.compile("(\\+|-)?(?:(\\d+) )?(\\d+):(\\d+):(\\d+)(?:\\.(\\d+))?"); private final boolean negative; private final int days; @@ -98,6 +99,26 @@ public final class DayToSecond implements Interval { } private DayToSecond(int days, int hours, int minutes, int seconds, int nano, boolean negative) { + + // Perform normalisation. Specifically, Postgres may return intervals + // such as 24:00:00, 25:13:15, etc... + if (nano >= 1000000000) { + seconds += (nano / 1000000000); + nano %= 1000000000; + } + if (seconds >= 60) { + minutes += (seconds / 60); + seconds %= 60; + } + if (minutes >= 60) { + hours += (minutes / 60); + minutes %= 60; + } + if (hours >= 24) { + days += (hours / 24); + hours %= 24; + } + this.negative = negative; this.days = days; this.hours = hours; @@ -120,11 +141,11 @@ public final class DayToSecond implements Interval { if (matcher.find()) { boolean negative = "-".equals(matcher.group(1)); - int days = Integer.parseInt(matcher.group(2)); - int hours = Integer.parseInt(matcher.group(3)); - int minutes = Integer.parseInt(matcher.group(4)); - int seconds = Integer.parseInt(matcher.group(5)); - int nano = Integer.parseInt(StringUtils.rightPad(matcher.group(6), 9, "0")); + int days = Convert.convert(matcher.group(2), int.class); + int hours = Convert.convert(matcher.group(3), int.class); + int minutes = Convert.convert(matcher.group(4), int.class); + int seconds = Convert.convert(matcher.group(5), int.class); + int nano = Convert.convert(StringUtils.rightPad(matcher.group(6), 9, "0"), int.class); return new DayToSecond(days, hours, minutes, seconds, nano, negative); } @@ -147,6 +168,26 @@ public final class DayToSecond implements Interval { return new DayToSecond(days, hours, minutes, seconds, nano, false); } + public final int getDays() { + return days; + } + + public final int getHours() { + return hours; + } + + public final int getMinutes() { + return minutes; + } + + public final int getSeconds() { + return seconds; + } + + public final int getNano() { + return nano; + } + // ------------------------------------------------------------------------- // XXX Comparable and Object API // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/types/YearToMonth.java b/jOOQ/src/main/java/org/jooq/types/YearToMonth.java index 29ade2329e..5f87558e29 100644 --- a/jOOQ/src/main/java/org/jooq/types/YearToMonth.java +++ b/jOOQ/src/main/java/org/jooq/types/YearToMonth.java @@ -72,6 +72,14 @@ public final class YearToMonth implements Interval { } private YearToMonth(int years, int months, boolean negative) { + + // Perform normalisation. Specifically, Postgres may return intervals + // such as 0-13 + if (months >= 12) { + years += (months / 12); + months %= 12; + } + this.negative = negative; this.years = years; this.months = months; @@ -115,6 +123,14 @@ public final class YearToMonth implements Interval { return new YearToMonth(years, months, false); } + public final int getYears() { + return years; + } + + public final int getMonths() { + return months; + } + // ------------------------------------------------------------------------- // XXX Comparable and Object API // ------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/util/oracle/OracleDataType.java b/jOOQ/src/main/java/org/jooq/util/oracle/OracleDataType.java index 4224453854..5ff729f07c 100644 --- a/jOOQ/src/main/java/org/jooq/util/oracle/OracleDataType.java +++ b/jOOQ/src/main/java/org/jooq/util/oracle/OracleDataType.java @@ -62,84 +62,84 @@ public class OracleDataType extends AbstractDataType { /** * Generated UID */ - private static final long serialVersionUID = -5677365115109672781L; + private static final long serialVersionUID = -5677365115109672781L; // ------------------------------------------------------------------------- // Default SQL data types and synonyms thereof // ------------------------------------------------------------------------- - public static final OracleDataType NUMBER = new OracleDataType(SQLDataType.NUMERIC, "number", true); - public static final OracleDataType NUMERIC = new OracleDataType(SQLDataType.NUMERIC, "numeric", true); - public static final OracleDataType DECIMAL = new OracleDataType(SQLDataType.DECIMAL, "decimal", true); - public static final OracleDataType DEC = new OracleDataType(SQLDataType.DECIMAL, "dec", true); - public static final OracleDataType VARCHAR2 = new OracleDataType(SQLDataType.VARCHAR, "varchar2", "varchar2(4000)"); - public static final OracleDataType VARCHAR = new OracleDataType(SQLDataType.VARCHAR, "varchar", "varchar2(4000)"); - public static final OracleDataType CHAR = new OracleDataType(SQLDataType.CHAR, "char", "varchar2(4000)"); - public static final OracleDataType CLOB = new OracleDataType(SQLDataType.CLOB, "clob"); - public static final OracleDataType NVARCHAR2 = new OracleDataType(SQLDataType.NVARCHAR, "nvarchar2", "varchar2(4000)"); - public static final OracleDataType NVARCHAR = new OracleDataType(SQLDataType.NVARCHAR, "nvarchar", "varchar2(4000)"); - public static final OracleDataType NCHAR = new OracleDataType(SQLDataType.NCHAR, "nchar", "varchar2(4000)"); - public static final OracleDataType NCLOB = new OracleDataType(SQLDataType.NCLOB, "nclob"); - public static final OracleDataType DATE = new OracleDataType(SQLDataType.DATE, "date"); - public static final OracleDataType TIMESTAMP = new OracleDataType(SQLDataType.TIMESTAMP, "timestamp"); - public static final OracleDataType BLOB = new OracleDataType(SQLDataType.BLOB, "blob"); - public static final OracleDataType INTERVALYEARTOMONTH = new OracleDataType(SQLDataType.INTERVAL_YEAR_TO_MONTH, "interval year to month"); - public static final OracleDataType INTERVALDAYTOSECOND = new OracleDataType(SQLDataType.INTERVAL_DAY_TO_SECOND, "interval day to second"); + public static final OracleDataType NUMBER = new OracleDataType(SQLDataType.NUMERIC, "number", true); + public static final OracleDataType NUMERIC = new OracleDataType(SQLDataType.NUMERIC, "numeric", true); + public static final OracleDataType DECIMAL = new OracleDataType(SQLDataType.DECIMAL, "decimal", true); + public static final OracleDataType DEC = new OracleDataType(SQLDataType.DECIMAL, "dec", true); + public static final OracleDataType VARCHAR2 = new OracleDataType(SQLDataType.VARCHAR, "varchar2", "varchar2(4000)"); + public static final OracleDataType VARCHAR = new OracleDataType(SQLDataType.VARCHAR, "varchar", "varchar2(4000)"); + public static final OracleDataType CHAR = new OracleDataType(SQLDataType.CHAR, "char", "varchar2(4000)"); + public static final OracleDataType CLOB = new OracleDataType(SQLDataType.CLOB, "clob"); + public static final OracleDataType NVARCHAR2 = new OracleDataType(SQLDataType.NVARCHAR, "nvarchar2", "varchar2(4000)"); + public static final OracleDataType NVARCHAR = new OracleDataType(SQLDataType.NVARCHAR, "nvarchar", "varchar2(4000)"); + public static final OracleDataType NCHAR = new OracleDataType(SQLDataType.NCHAR, "nchar", "varchar2(4000)"); + public static final OracleDataType NCLOB = new OracleDataType(SQLDataType.NCLOB, "nclob"); + public static final OracleDataType DATE = new OracleDataType(SQLDataType.DATE, "date"); + public static final OracleDataType TIMESTAMP = new OracleDataType(SQLDataType.TIMESTAMP, "timestamp"); + public static final OracleDataType BLOB = new OracleDataType(SQLDataType.BLOB, "blob"); + public static final OracleDataType INTERVALYEARTOMONTH = new OracleDataType(SQLDataType.INTERVALYEARTOMONTH, "interval year to month"); + public static final OracleDataType INTERVALDAYTOSECOND = new OracleDataType(SQLDataType.INTERVALDAYTOSECOND, "interval day to second"); // ------------------------------------------------------------------------- // Compatibility types for supported SQLDataTypes // ------------------------------------------------------------------------- - protected static final OracleDataType __BINARY = new OracleDataType(SQLDataType.BINARY, "blob"); - protected static final OracleDataType __BIGINT = new OracleDataType(SQLDataType.BIGINT, "number", "number(19)"); - protected static final OracleDataType __BIT = new OracleDataType(SQLDataType.BIT, "number", "number(1)"); - protected static final OracleDataType __BOOLEAN = new OracleDataType(SQLDataType.BOOLEAN, "number", "number(1)"); - protected static final OracleDataType __DOUBLE = new OracleDataType(SQLDataType.DOUBLE, "number"); - protected static final OracleDataType __FLOAT = new OracleDataType(SQLDataType.FLOAT, "number"); - protected static final OracleDataType __INTEGER = new OracleDataType(SQLDataType.INTEGER, "number", "number(10)"); - protected static final OracleDataType __LONGVARBINARY = new OracleDataType(SQLDataType.LONGVARBINARY, "blob"); - protected static final OracleDataType __LONGVARCHAR = new OracleDataType(SQLDataType.LONGVARCHAR, "varchar2", "varchar2(4000)"); - protected static final OracleDataType __LONGNVARCHAR = new OracleDataType(SQLDataType.LONGNVARCHAR, "varchar2", "varchar2(4000)"); - protected static final OracleDataType __REAL = new OracleDataType(SQLDataType.REAL, "number"); - protected static final OracleDataType __SMALLINT = new OracleDataType(SQLDataType.SMALLINT, "number", "number(5)"); - protected static final OracleDataType