diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index eaba5412b6..a4fa5bb0a2 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -6703,26 +6703,22 @@ public class DefaultBinding implements Binding { @Override final YearToSecond get0(BindingGetResultSetContext ctx) throws SQLException { - if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) { - Object object = ctx.resultSet().getObject(ctx.index()); - return object == null ? null : PostgresUtils.toYearToSecond(object); - } - else { - String string = ctx.resultSet().getString(ctx.index()); + String string = ctx.resultSet().getString(ctx.index()); + + if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) + return string == null ? null : PostgresUtils.toYearToSecond(string); + else return string == null ? null : YearToSecond.valueOf(string); - } } @Override final YearToSecond get0(BindingGetStatementContext ctx) throws SQLException { - if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) { - Object object = ctx.statement().getObject(ctx.index()); - return object == null ? null : PostgresUtils.toYearToSecond(object); - } - else { - String string = ctx.statement().getString(ctx.index()); + String string = ctx.statement().getString(ctx.index()); + + if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) + return string == null ? null : PostgresUtils.toYearToSecond(string); + else return string == null ? null : YearToSecond.valueOf(string); - } } @Override diff --git a/jOOQ/src/main/java/org/jooq/util/postgres/PGInterval.java b/jOOQ/src/main/java/org/jooq/util/postgres/PGInterval.java index fdfb9bb42b..ed42e0ef30 100644 --- a/jOOQ/src/main/java/org/jooq/util/postgres/PGInterval.java +++ b/jOOQ/src/main/java/org/jooq/util/postgres/PGInterval.java @@ -2,9 +2,11 @@ * Copyright (c) 2004, PostgreSQL Global Development Group * See the LICENSE file in the project root for more information. */ + package org.jooq.util.postgres; - +import java.io.Serializable; +import java.sql.SQLException; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Calendar; @@ -41,7 +43,7 @@ public class PGInterval extends PGobject { /** * Initialize a interval with a given interval string representation. * - * @param value String representated interval (e.g. '3 years 2 mons') + * @param value String represented interval (e.g. '3 years 2 mons') * @see PGobject#setValue(String) */ public PGInterval(String value) { @@ -49,13 +51,13 @@ public class PGInterval extends PGobject { setValue(value); } - private int lookAhead(String value, int position, String find) { + private static int lookAhead(String value, int position, String find) { char [] tokens = find.toCharArray(); int found = -1; - for ( int i = 0; i < tokens.length; i++ ) { + for (int i = 0; i < tokens.length; i++) { found = value.indexOf(tokens[i], position); - if ( found > 0 ) { + if (found > 0) { return found; } } @@ -70,14 +72,14 @@ public class PGInterval extends PGobject { int hasTime = value.indexOf('T'); if ( hasTime > 0 ) { /* skip over the P */ - dateValue = value.substring(1,hasTime); + dateValue = value.substring(1, hasTime); timeValue = value.substring(hasTime + 1); } else { /* skip over the P */ dateValue = value.substring(1); } - for ( int i = 0; i < dateValue.length(); i++ ) { + for (int i = 0; i < dateValue.length(); i++) { int lookAhead = lookAhead(dateValue, i, "YMD"); if (lookAhead > 0) { number = Integer.parseInt(dateValue.substring(i, lookAhead)); @@ -128,7 +130,7 @@ public class PGInterval extends PGobject { * Sets a interval string represented value to this instance. This method only recognize the * format, that Postgres returns - not all input formats are supported (e.g. '1 yr 2 m 3 s'). * - * @param value String representated interval (e.g. '3 years 2 mons') + * @param value String represented interval (e.g. '3 years 2 mons') */ @Override public void setValue(String value) { @@ -138,13 +140,13 @@ public class PGInterval extends PGobject { isNull = true; return; } - final boolean PostgresFormat = !value.startsWith("@"); + final boolean postgresFormat = !value.startsWith("@"); if (value.startsWith("P")) { parseISO8601Format(value); return; } // Just a simple '0' - if (!PostgresFormat && value.length() == 3 && value.charAt(2) == '0') { + if (!postgresFormat && value.length() == 3 && value.charAt(2) == '0') { setValue(0, 0, 0, 0, 0, 0.0); return; } @@ -159,6 +161,7 @@ public class PGInterval extends PGobject { String valueToken = null; value = value.replace('+', ' ').replace('@', ' '); + value = value.toLowerCase(Locale.ROOT); final StringTokenizer st = new StringTokenizer(value); for (int i = 1; st.hasMoreTokens(); i++) { String token = st.nextToken(); @@ -172,7 +175,7 @@ public class PGInterval extends PGobject { // This handles hours, minutes, seconds and microseconds for // ISO intervals - int offset = (token.charAt(0) == '-') ? 1 : 0; + int offset = token.charAt(0) == '-' ? 1 : 0; hours = nullSafeIntGet(token.substring(offset + 0, endHours)); minutes = nullSafeIntGet(token.substring(endHours + 1, endHours + 3)); @@ -212,7 +215,7 @@ public class PGInterval extends PGobject { } } - if (!PostgresFormat && value.endsWith("ago")) { + if (!postgresFormat && value.endsWith("ago")) { // Inverse the leading sign setValue(-years, -months, -days, -hours, -minutes, -seconds); } else { @@ -249,19 +252,36 @@ public class PGInterval extends PGobject { if (isNull) { return null; } - DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.US); - df.applyPattern("0.0#####"); - return String.format( - Locale.ROOT, - "%d years %d mons %d days %d hours %d mins %s secs", - years, - months, - days, - hours, - minutes, - df.format(getSeconds()) - ); + // [jOOQ/jOOQ#19369] Alternative implementation to avoid https://github.com/pgjdbc/pgjdbc/issues/3865 + StringBuilder sb = new StringBuilder(); + sb.append(years).append(" years ") + .append(months).append(" mons ") + .append(days).append(" days ") + .append(hours).append(" hours ") + .append(minutes).append(" mins "); + + if (wholeSeconds < 0 || microSeconds < 0) + sb.append('-'); + + sb.append(Math.abs(wholeSeconds)).append('.'); + + if (microSeconds != 0) { + String s = "" + Math.abs(microSeconds); + + for (int i = s.length(); i < 6; i++) + sb.append('0'); + + sb.append(s); + + while (sb.charAt(sb.length() - 1) == '0') + sb.deleteCharAt(sb.length() - 1); + } + else + sb.append('0'); + + sb.append(" secs"); + return sb.toString(); } /** @@ -397,7 +417,7 @@ public class PGInterval extends PGobject { return; } - final int milliseconds = (microSeconds + ((microSeconds < 0) ? -500 : 500)) / 1000 + wholeSeconds * 1000; + final int milliseconds = (microSeconds + (microSeconds < 0 ? -500 : 500)) / 1000 + wholeSeconds * 1000; cal.add(Calendar.MILLISECOND, milliseconds); cal.add(Calendar.MINUTE, getMinutes()); @@ -468,7 +488,7 @@ public class PGInterval extends PGobject { * @throws NumberFormatException if the string contains invalid chars */ private static int nullSafeIntGet(String value) throws NumberFormatException { - return (value == null) ? 0 : Integer.parseInt(value); + return value == null ? 0 : Integer.parseInt(value); } /** @@ -479,7 +499,7 @@ public class PGInterval extends PGobject { * @throws NumberFormatException if the string contains invalid chars */ private static double nullSafeDoubleGet(String value) throws NumberFormatException { - return (value == null) ? 0 : Double.parseDouble(value); + return value == null ? 0 : Double.parseDouble(value); } /** diff --git a/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java b/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java index 488fa0fa20..8273a5c88e 100644 --- a/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java +++ b/jOOQ/src/main/java/org/jooq/util/postgres/PostgresUtils.java @@ -261,10 +261,14 @@ public class PostgresUtils { * Convert a Postgres interval to a jOOQ DAY TO SECOND interval */ public static DayToSecond toDayToSecond(PGInterval pgInterval) { - boolean negative = pgInterval.toString().contains("-"); + boolean negative = + pgInterval.getDays() < 0 + || pgInterval.getHours() < 0 + || pgInterval.getMinutes() < 0 + || pgInterval.getSeconds() < 0; if (negative) - pgInterval.scale(-1); + pgInterval = negative(pgInterval); Double seconds = pgInterval.getSeconds(); DayToSecond result = new DayToSecond( @@ -281,6 +285,17 @@ public class PostgresUtils { return result; } + private static PGInterval negative(PGInterval pgInterval) { + return new PGInterval( + -1 * pgInterval.getYears(), + -1 * pgInterval.getMonths(), + -1 * pgInterval.getDays(), + -1 * pgInterval.getHours(), + -1 * pgInterval.getMinutes(), + -1 * pgInterval.getSeconds() + ); + } + /** * Convert a Postgres interval to a jOOQ YEAR TO MONTH interval */ @@ -297,10 +312,12 @@ public class PostgresUtils { * Convert a Postgres interval to a jOOQ YEAR TO MONTH interval */ public static YearToMonth toYearToMonth(PGInterval pgInterval) { - boolean negative = pgInterval.toString().contains("-"); + boolean negative = + pgInterval.getYears() < 0 + || pgInterval.getMonths() < 0; if (negative) - pgInterval.scale(-1); + pgInterval = negative(pgInterval); YearToMonth result = new YearToMonth(pgInterval.getYears(), pgInterval.getMonths()); @@ -314,7 +331,16 @@ public class PostgresUtils { * Convert a Postgres interval to a jOOQ YEAR TO SECOND interval */ public static YearToSecond toYearToSecond(Object pgInterval) { - return new YearToSecond(toYearToMonth(pgInterval), toDayToSecond(pgInterval)); + PGInterval i; + + if (pgInterval == null) + return null; + else if (pgInterval instanceof PGInterval) + return new YearToSecond(toYearToMonth(pgInterval), toDayToSecond(pgInterval)); + + // [#19369] Avoid calling possibly toString() twice, unnecessarily + else + return new YearToSecond(toYearToMonth(i = new PGInterval(pgInterval.toString())), toDayToSecond(i)); } /**