From c3f404b0468dc8b09388a8d6692e645ab7a2bdff Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Tue, 28 Jul 2020 14:56:00 +0200 Subject: [PATCH] [jOOQ/jOOQ#2230] H2 code generation support for interval types This includes: [jOOQ/jOOQ#10451] Use H2 native interval arithmetic instead of DATEADD emulation --- .../org/jooq/meta/h2/H2TableDefinition.java | 15 +++--- .../postgres/PostgresTableDefinition.java | 6 ++- .../java/org/jooq/impl/DefaultBinding.java | 51 +++++++++++-------- .../main/java/org/jooq/impl/Expression.java | 10 +--- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java index 54cf6dcd10..8c577ff230 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java @@ -37,10 +37,12 @@ */ package org.jooq.meta.h2; +import static org.jooq.impl.DSL.any; import static org.jooq.impl.DSL.choose; import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.noCondition; +import static org.jooq.impl.DSL.when; import static org.jooq.impl.DSL.zero; import static org.jooq.meta.h2.information_schema.Tables.COLUMNS; import static org.jooq.tools.StringUtils.defaultString; @@ -89,7 +91,11 @@ public class H2TableDefinition extends AbstractTableDefinition { for (Record record : create().select( COLUMNS.COLUMN_NAME, COLUMNS.ORDINAL_POSITION, - COLUMNS.TYPE_NAME, + + // [#2230] Translate INTERVAL_TYPE to supported types + when(COLUMNS.INTERVAL_TYPE.like(any(inline("%YEAR%"), inline("%MONTH%"))), inline("INTERVAL YEAR TO MONTH")) + .when(COLUMNS.INTERVAL_TYPE.like(any(inline("%DAY%"), inline("%HOUR%"), inline("%MINUTE%"), inline("%SECOND%"))), inline("INTERVAL DAY TO SECOND")) + .else_(COLUMNS.TYPE_NAME).as(COLUMNS.TYPE_NAME), (((H2Database) getDatabase()).is1_4_197() ? COLUMNS.COLUMN_TYPE : COLUMNS.TYPE_NAME).as(COLUMNS.COLUMN_TYPE), choose().when(COLUMNS.NUMERIC_PRECISION.eq(maxP).and(COLUMNS.NUMERIC_SCALE.eq(maxS)), zero()) .otherwise(COLUMNS.CHARACTER_MAXIMUM_LENGTH).as(COLUMNS.CHARACTER_MAXIMUM_LENGTH), @@ -120,15 +126,10 @@ public class H2TableDefinition extends AbstractTableDefinition { // [#7644] H2 puts DATETIME_PRECISION in NUMERIC_SCALE column boolean isTimestamp = record.get(COLUMNS.TYPE_NAME).trim().toLowerCase().startsWith("timestamp"); - // [#10389] The interval subtype is contained in COLUMN_TYPE, not TYPE_NAME - boolean isInterval = record.get(COLUMNS.TYPE_NAME).trim().toLowerCase().equals("interval"); - DataTypeDefinition type = new DefaultDataTypeDefinition( getDatabase(), getSchema(), - isInterval - ? record.get(COLUMNS.COLUMN_TYPE) - : record.get(COLUMNS.TYPE_NAME), + record.get(COLUMNS.TYPE_NAME), record.get(COLUMNS.CHARACTER_MAXIMUM_LENGTH), isTimestamp ? record.get(COLUMNS.NUMERIC_SCALE) diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresTableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresTableDefinition.java index 3fdbf10191..cf8dec303e 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresTableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresTableDefinition.java @@ -38,6 +38,7 @@ package org.jooq.meta.postgres; +import static org.jooq.impl.DSL.any; import static org.jooq.impl.DSL.count; import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.lower; @@ -84,7 +85,10 @@ public class PostgresTableDefinition extends AbstractTableDefinition { List result = new ArrayList<>(); PostgresDatabase database = (PostgresDatabase) getDatabase(); - Field dataType = COLUMNS.DATA_TYPE; + Field dataType = + when(COLUMNS.INTERVAL_TYPE.like(any(inline("%YEAR%"), inline("%MONTH%"))), inline("INTERVAL YEAR TO MONTH")) + .when(COLUMNS.INTERVAL_TYPE.like(any(inline("%DAY%"), inline("%HOUR%"), inline("%MINUTE%"), inline("%SECOND%"))), inline("INTERVAL DAY TO SECOND")) + .else_(COLUMNS.DATA_TYPE); Field precision = nvl(COLUMNS.DATETIME_PRECISION, COLUMNS.NUMERIC_PRECISION); Field serialColumnDefault = inline("nextval('%_seq'::regclass)"); diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 7747107f36..632c05da67 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -160,6 +160,7 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.UUID; +import java.util.regex.Pattern; // ... import org.jooq.Attachable; @@ -226,6 +227,7 @@ public class DefaultBinding implements Binding { */ private static final long serialVersionUID = -198499389344950496L; private static final Set REQUIRE_JDBC_DATE_LITERAL = SQLDialect.supportedBy(MYSQL); + private static final Pattern P_INTERVAL_LITERAL = Pattern.compile("INTERVAL '([^']+)' .*"); // Taken from org.postgresql.PGStatement 9223372036825200000 private static final long PG_DATE_POSITIVE_INFINITY = 9223372036825200000L; @@ -630,6 +632,7 @@ public class DefaultBinding implements Binding { + case H2: case POSTGRES: return true; } @@ -2220,10 +2223,8 @@ public class DefaultBinding implements Binding { Object object = ctx.resultSet().getObject(ctx.index()); return object == null ? null : PostgresUtils.toDayToSecond(object); } - else { - String string = ctx.resultSet().getString(ctx.index()); - return string == null ? null : DayToSecond.valueOf(string); - } + else + return parseDTS(ctx, ctx.resultSet().getString(ctx.index())); } @Override @@ -2232,16 +2233,22 @@ public class DefaultBinding implements Binding { Object object = ctx.statement().getObject(ctx.index()); return object == null ? null : PostgresUtils.toDayToSecond(object); } - else { - String string = ctx.statement().getString(ctx.index()); - return string == null ? null : DayToSecond.valueOf(string); - } + else + return parseDTS(ctx, ctx.statement().getString(ctx.index())); } @Override final DayToSecond get0(BindingGetSQLInputContext ctx) throws SQLException { - String string = ctx.input().readString(); - return string == null ? null : DayToSecond.valueOf(string); + return parseDTS(ctx, ctx.input().readString()); + } + + private final DayToSecond parseDTS(Scope scope, String string) { + if (string == null) + return null; + else if (scope.family() == H2) + return DayToSecond.valueOf(P_INTERVAL_LITERAL.matcher(string).replaceFirst("$1")); + else + return DayToSecond.valueOf(string); } @Override @@ -4700,10 +4707,8 @@ public class DefaultBinding implements Binding { Object object = ctx.resultSet().getObject(ctx.index()); return object == null ? null : PostgresUtils.toYearToMonth(object); } - else { - String string = ctx.resultSet().getString(ctx.index()); - return string == null ? null : YearToMonth.valueOf(string); - } + else + return parseYTM(ctx, ctx.resultSet().getString(ctx.index())); } @Override @@ -4712,16 +4717,22 @@ public class DefaultBinding implements Binding { Object object = ctx.statement().getObject(ctx.index()); return object == null ? null : PostgresUtils.toYearToMonth(object); } - else { - String string = ctx.statement().getString(ctx.index()); - return string == null ? null : YearToMonth.valueOf(string); - } + else + return parseYTM(ctx, ctx.statement().getString(ctx.index())); } @Override final YearToMonth get0(BindingGetSQLInputContext ctx) throws SQLException { - String string = ctx.input().readString(); - return string == null ? null : YearToMonth.valueOf(string); + return parseYTM(ctx, ctx.input().readString()); + } + + private final YearToMonth parseYTM(Scope scope, String string) { + if (string == null) + return null; + else if (scope.family() == H2) + return YearToMonth.valueOf(P_INTERVAL_LITERAL.matcher(string).replaceFirst("$1")); + else + return YearToMonth.valueOf(string); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/Expression.java b/jOOQ/src/main/java/org/jooq/impl/Expression.java index e026438a72..3e03f2b114 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Expression.java +++ b/jOOQ/src/main/java/org/jooq/impl/Expression.java @@ -401,15 +401,6 @@ final class Expression extends AbstractField { break; } - case H2: { - if (rhs.getType() == YearToMonth.class) - ctx.visit(N_DATEADD).sql("('month', ").visit(p(sign * rhsAsYTM().intValue())).sql(", ").visit(lhs).sql(')'); - else - ctx.visit(N_DATEADD).sql("('ms', ").visit(p(sign * (long) rhsAsDTS().getTotalMilli())).sql(", ").visit(lhs).sql(')'); - - break; - } - case SQLITE: { boolean ytm = rhs.getType() == YearToMonth.class; Field interval = p(ytm ? rhsAsYTM().intValue() : rhsAsDTS().getTotalSeconds()); @@ -584,6 +575,7 @@ final class Expression extends AbstractField { + case H2: case POSTGRES: default: ctx.visit(new DefaultExpression<>(lhs, operator, wrap(rhs)));