[jOOQ/jOOQ#19369] Bad performance when reading / writing PGInterval

values to pgjdbc
This commit is contained in:
Lukas Eder 2025-11-14 10:31:50 +01:00
parent 2336f3bee3
commit 74b2c5b31f
3 changed files with 88 additions and 46 deletions

View File

@ -6703,26 +6703,22 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
@Override @Override
final YearToSecond get0(BindingGetResultSetContext<U> ctx) throws SQLException { final YearToSecond get0(BindingGetResultSetContext<U> ctx) throws SQLException {
if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) { String string = ctx.resultSet().getString(ctx.index());
Object object = ctx.resultSet().getObject(ctx.index());
return object == null ? null : PostgresUtils.toYearToSecond(object); if (REQUIRE_PG_INTERVAL.contains(ctx.dialect()))
} return string == null ? null : PostgresUtils.toYearToSecond(string);
else { else
String string = ctx.resultSet().getString(ctx.index());
return string == null ? null : YearToSecond.valueOf(string); return string == null ? null : YearToSecond.valueOf(string);
}
} }
@Override @Override
final YearToSecond get0(BindingGetStatementContext<U> ctx) throws SQLException { final YearToSecond get0(BindingGetStatementContext<U> ctx) throws SQLException {
if (REQUIRE_PG_INTERVAL.contains(ctx.dialect())) { String string = ctx.statement().getString(ctx.index());
Object object = ctx.statement().getObject(ctx.index());
return object == null ? null : PostgresUtils.toYearToSecond(object); if (REQUIRE_PG_INTERVAL.contains(ctx.dialect()))
} return string == null ? null : PostgresUtils.toYearToSecond(string);
else { else
String string = ctx.statement().getString(ctx.index());
return string == null ? null : YearToSecond.valueOf(string); return string == null ? null : YearToSecond.valueOf(string);
}
} }
@Override @Override

View File

@ -2,9 +2,11 @@
* Copyright (c) 2004, PostgreSQL Global Development Group * Copyright (c) 2004, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information. * See the LICENSE file in the project root for more information.
*/ */
package org.jooq.util.postgres; package org.jooq.util.postgres;
import java.io.Serializable;
import java.sql.SQLException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Calendar; import java.util.Calendar;
@ -41,7 +43,7 @@ public class PGInterval extends PGobject {
/** /**
* Initialize a interval with a given interval string representation. * 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) * @see PGobject#setValue(String)
*/ */
public PGInterval(String value) { public PGInterval(String value) {
@ -49,13 +51,13 @@ public class PGInterval extends PGobject {
setValue(value); 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(); char [] tokens = find.toCharArray();
int found = -1; 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); found = value.indexOf(tokens[i], position);
if ( found > 0 ) { if (found > 0) {
return found; return found;
} }
} }
@ -70,14 +72,14 @@ public class PGInterval extends PGobject {
int hasTime = value.indexOf('T'); int hasTime = value.indexOf('T');
if ( hasTime > 0 ) { if ( hasTime > 0 ) {
/* skip over the P */ /* skip over the P */
dateValue = value.substring(1,hasTime); dateValue = value.substring(1, hasTime);
timeValue = value.substring(hasTime + 1); timeValue = value.substring(hasTime + 1);
} else { } else {
/* skip over the P */ /* skip over the P */
dateValue = value.substring(1); 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"); int lookAhead = lookAhead(dateValue, i, "YMD");
if (lookAhead > 0) { if (lookAhead > 0) {
number = Integer.parseInt(dateValue.substring(i, lookAhead)); 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 * 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'). * 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 @Override
public void setValue(String value) { public void setValue(String value) {
@ -138,13 +140,13 @@ public class PGInterval extends PGobject {
isNull = true; isNull = true;
return; return;
} }
final boolean PostgresFormat = !value.startsWith("@"); final boolean postgresFormat = !value.startsWith("@");
if (value.startsWith("P")) { if (value.startsWith("P")) {
parseISO8601Format(value); parseISO8601Format(value);
return; return;
} }
// Just a simple '0' // 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); setValue(0, 0, 0, 0, 0, 0.0);
return; return;
} }
@ -159,6 +161,7 @@ public class PGInterval extends PGobject {
String valueToken = null; String valueToken = null;
value = value.replace('+', ' ').replace('@', ' '); value = value.replace('+', ' ').replace('@', ' ');
value = value.toLowerCase(Locale.ROOT);
final StringTokenizer st = new StringTokenizer(value); final StringTokenizer st = new StringTokenizer(value);
for (int i = 1; st.hasMoreTokens(); i++) { for (int i = 1; st.hasMoreTokens(); i++) {
String token = st.nextToken(); String token = st.nextToken();
@ -172,7 +175,7 @@ public class PGInterval extends PGobject {
// This handles hours, minutes, seconds and microseconds for // This handles hours, minutes, seconds and microseconds for
// ISO intervals // ISO intervals
int offset = (token.charAt(0) == '-') ? 1 : 0; int offset = token.charAt(0) == '-' ? 1 : 0;
hours = nullSafeIntGet(token.substring(offset + 0, endHours)); hours = nullSafeIntGet(token.substring(offset + 0, endHours));
minutes = nullSafeIntGet(token.substring(endHours + 1, endHours + 3)); 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 // Inverse the leading sign
setValue(-years, -months, -days, -hours, -minutes, -seconds); setValue(-years, -months, -days, -hours, -minutes, -seconds);
} else { } else {
@ -249,19 +252,36 @@ public class PGInterval extends PGobject {
if (isNull) { if (isNull) {
return null; return null;
} }
DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.US);
df.applyPattern("0.0#####");
return String.format( // [jOOQ/jOOQ#19369] Alternative implementation to avoid https://github.com/pgjdbc/pgjdbc/issues/3865
Locale.ROOT, StringBuilder sb = new StringBuilder();
"%d years %d mons %d days %d hours %d mins %s secs", sb.append(years).append(" years ")
years, .append(months).append(" mons ")
months, .append(days).append(" days ")
days, .append(hours).append(" hours ")
hours, .append(minutes).append(" mins ");
minutes,
df.format(getSeconds()) 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; 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.MILLISECOND, milliseconds);
cal.add(Calendar.MINUTE, getMinutes()); cal.add(Calendar.MINUTE, getMinutes());
@ -468,7 +488,7 @@ public class PGInterval extends PGobject {
* @throws NumberFormatException if the string contains invalid chars * @throws NumberFormatException if the string contains invalid chars
*/ */
private static int nullSafeIntGet(String value) throws NumberFormatException { 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 * @throws NumberFormatException if the string contains invalid chars
*/ */
private static double nullSafeDoubleGet(String value) throws NumberFormatException { private static double nullSafeDoubleGet(String value) throws NumberFormatException {
return (value == null) ? 0 : Double.parseDouble(value); return value == null ? 0 : Double.parseDouble(value);
} }
/** /**

View File

@ -261,10 +261,14 @@ public class PostgresUtils {
* Convert a Postgres interval to a jOOQ <code>DAY TO SECOND</code> interval * Convert a Postgres interval to a jOOQ <code>DAY TO SECOND</code> interval
*/ */
public static DayToSecond toDayToSecond(PGInterval pgInterval) { 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) if (negative)
pgInterval.scale(-1); pgInterval = negative(pgInterval);
Double seconds = pgInterval.getSeconds(); Double seconds = pgInterval.getSeconds();
DayToSecond result = new DayToSecond( DayToSecond result = new DayToSecond(
@ -281,6 +285,17 @@ public class PostgresUtils {
return result; 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 <code>YEAR TO MONTH</code> interval * Convert a Postgres interval to a jOOQ <code>YEAR TO MONTH</code> interval
*/ */
@ -297,10 +312,12 @@ public class PostgresUtils {
* Convert a Postgres interval to a jOOQ <code>YEAR TO MONTH</code> interval * Convert a Postgres interval to a jOOQ <code>YEAR TO MONTH</code> interval
*/ */
public static YearToMonth toYearToMonth(PGInterval pgInterval) { public static YearToMonth toYearToMonth(PGInterval pgInterval) {
boolean negative = pgInterval.toString().contains("-"); boolean negative =
pgInterval.getYears() < 0
|| pgInterval.getMonths() < 0;
if (negative) if (negative)
pgInterval.scale(-1); pgInterval = negative(pgInterval);
YearToMonth result = new YearToMonth(pgInterval.getYears(), pgInterval.getMonths()); YearToMonth result = new YearToMonth(pgInterval.getYears(), pgInterval.getMonths());
@ -314,7 +331,16 @@ public class PostgresUtils {
* Convert a Postgres interval to a jOOQ <code>YEAR TO SECOND</code> interval * Convert a Postgres interval to a jOOQ <code>YEAR TO SECOND</code> interval
*/ */
public static YearToSecond toYearToSecond(Object pgInterval) { 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));
} }
/** /**