[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
final YearToSecond get0(BindingGetResultSetContext<U> 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<U> 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

View File

@ -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);
}
/**

View File

@ -261,10 +261,14 @@ public class PostgresUtils {
* Convert a Postgres interval to a jOOQ <code>DAY TO SECOND</code> 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 <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
*/
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 <code>YEAR TO SECOND</code> 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));
}
/**