Skip to content

Commit 0a88ea4

Browse files
MrEasykimjand
authored andcommitted
fix: EOFException on PreparedStatement#toString with unset bytea parameter since 42.7.4
Previously, PgPreparedStatement#toString() could consume InputStream parameters, so it caused exceptions when executing the query later. Fixes #3365 Co-authored-by: Kim Johan Andersson <31474178+kimjand@users.noreply.github.com>
1 parent 2de9b94 commit 0a88ea4

File tree

15 files changed

+411
-23
lines changed

15 files changed

+411
-23
lines changed

pgjdbc/src/main/java/org/postgresql/core/NativeQuery.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package org.postgresql.core;
88

9+
import org.postgresql.core.v3.SqlSerializationContext;
10+
911
import org.checkerframework.checker.nullness.qual.Nullable;
1012

1113
/**
@@ -40,22 +42,35 @@ public NativeQuery(String nativeSql, int @Nullable [] bindPositions, boolean mul
4042
}
4143

4244
/**
43-
* Stringize this query to a human-readable form, substituting particular parameter values for
45+
* Returns string representation of the query, substituting particular parameter values for
4446
* parameter placeholders.
4547
*
4648
* @param parameters a ParameterList returned by this Query's {@link Query#createParameterList}
4749
* method, or {@code null} to leave the parameter placeholders unsubstituted.
4850
* @return a human-readable representation of this query
4951
*/
5052
public String toString(@Nullable ParameterList parameters) {
53+
return toString(parameters, SqlSerializationContext.of(true, true));
54+
}
55+
56+
/**
57+
* Returns string representation of the query, substituting particular parameter values for
58+
* parameter placeholders.
59+
*
60+
* @param parameters a ParameterList returned by this Query's {@link Query#createParameterList}
61+
* method, or {@code null} to leave the parameter placeholders unsubstituted.
62+
* @param context specifies configuration for converting the parameters to string
63+
* @return a human-readable representation of this query
64+
*/
65+
public String toString(@Nullable ParameterList parameters, SqlSerializationContext context) {
5166
if (bindPositions.length == 0) {
5267
return nativeSql;
5368
}
5469

5570
int queryLength = nativeSql.length();
5671
String[] params = new String[bindPositions.length];
5772
for (int i = 1; i <= bindPositions.length; i++) {
58-
String param = parameters == null ? "?" : parameters.toString(i, true);
73+
String param = parameters == null ? "?" : parameters.toString(i, context);
5974
params[i - 1] = param;
6075
queryLength += param.length() - bindName(i).length();
6176
}

pgjdbc/src/main/java/org/postgresql/core/ParameterList.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package org.postgresql.core;
88

9+
import org.postgresql.core.v3.SqlSerializationContext;
910
import org.postgresql.util.ByteStreamWriter;
1011

1112
import org.checkerframework.checker.index.qual.NonNegative;
@@ -189,14 +190,26 @@ void setBytea(@Positive int index, byte[] data,
189190

190191
/**
191192
* Return a human-readable representation of a particular parameter in this ParameterList. If the
192-
* parameter is not bound, returns "?".
193+
* parameter is not bound or is of type bytea sourced from an InputStream, returns "?".
194+
* This method will NOT consume InputStreams, instead "?" will be returned.
193195
*
194196
* @param index the 1-based parameter index to bind.
195197
* @param standardConformingStrings true if \ is not an escape character in strings literals
196198
* @return a string representation of the parameter.
197199
*/
198200
String toString(@Positive int index, boolean standardConformingStrings);
199201

202+
/**
203+
* Return the string literal representation of a particular parameter in this ParameterList. If the
204+
* parameter is not bound, returns "?".
205+
* This method will consume all InputStreams to produce the result.
206+
*
207+
* @param index the 1-based parameter index to bind.
208+
* @param context specifies configuration for converting the parameters to string
209+
* @return a string representation of the parameter.
210+
*/
211+
String toString(@Positive int index, SqlSerializationContext context);
212+
200213
/**
201214
* Use this operation to append more parameters to the current list.
202215
* @param list of parameters to append with.

pgjdbc/src/main/java/org/postgresql/core/Query.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
package org.postgresql.core;
88

9+
import org.postgresql.core.v3.SqlSerializationContext;
10+
911
import org.checkerframework.checker.nullness.qual.Nullable;
1012

1113
import java.util.Map;
@@ -32,15 +34,30 @@ public interface Query {
3234
ParameterList createParameterList();
3335

3436
/**
35-
* Stringize this query to a human-readable form, substituting particular parameter values for
37+
* Returns string representation of the query, substituting particular parameter values for
3638
* parameter placeholders.
3739
*
40+
* <p>Note: the method replaces the values on a best-effort basis as it might omit the replacements
41+
* for parameters that can't be processed several times. For instance, {@link java.io.InputStream}
42+
* can be processed only once.
43+
*
3844
* @param parameters a ParameterList returned by this Query's {@link #createParameterList} method,
39-
* or <code>null</code> to leave the parameter placeholders unsubstituted.
40-
* @return a human-readable representation of this query
45+
* or {@code null} to leave the parameter placeholders unsubstituted.
46+
* @return string representation of this query
4147
*/
4248
String toString(@Nullable ParameterList parameters);
4349

50+
/**
51+
* Returns string representation of the query, substituting particular parameter values for
52+
* parameter placeholders.
53+
*
54+
* @param parameters a ParameterList returned by this Query's {@link #createParameterList} method,
55+
* or {@code null} to leave the parameter placeholders unsubstituted.
56+
* @param context specifies configuration for converting the parameters to string
57+
* @return string representation of this query
58+
*/
59+
String toString(@Nullable ParameterList parameters, SqlSerializationContext context);
60+
4461
/**
4562
* Returns SQL in native for database format.
4663
* @return SQL in native for database format

pgjdbc/src/main/java/org/postgresql/core/v3/BatchedQuery.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ public String getNativeSql() {
8282
if (sql != null) {
8383
return sql;
8484
}
85-
sql = buildNativeSql(null);
85+
sql = buildNativeSql(null, DefaultSqlSerializationContext.STDSTR_IDEMPOTENT);
8686
return sql;
8787
}
8888

89-
private String buildNativeSql(@Nullable ParameterList params) {
89+
private String buildNativeSql(@Nullable ParameterList params, SqlSerializationContext context) {
9090
String sql = null;
9191
// dynamically build sql with parameters for batches
9292
String nativeSql = super.getNativeSql();
@@ -158,7 +158,7 @@ private String buildNativeSql(@Nullable ParameterList params) {
158158
if (params == null) {
159159
NativeQuery.appendBindName(s, pos++);
160160
} else {
161-
s.append(params.toString(pos++, true));
161+
s.append(params.toString(pos++, context));
162162
}
163163
s.append(nativeSql, chunkStart[j], chunkEnd[j]);
164164
}
@@ -174,11 +174,11 @@ private String buildNativeSql(@Nullable ParameterList params) {
174174
}
175175

176176
@Override
177-
public String toString(@Nullable ParameterList params) {
177+
public String toString(@Nullable ParameterList params, SqlSerializationContext context) {
178178
if (getBatchSize() < 2) {
179-
return super.toString(params);
179+
return super.toString(params, context);
180180
}
181-
return buildNativeSql(params);
181+
return buildNativeSql(params, context);
182182
}
183183

184184
}

pgjdbc/src/main/java/org/postgresql/core/v3/CompositeParameterList.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,14 @@ public void setNull(@Positive int index, int oid) throws SQLException {
144144

145145
@Override
146146
public String toString(@Positive int index, boolean standardConformingStrings) {
147+
return toString(index, SqlSerializationContext.of(standardConformingStrings, true));
148+
}
149+
150+
@Override
151+
public String toString(@Positive int index, SqlSerializationContext context) {
147152
try {
148153
int sub = findSubParam(index);
149-
return subparams[sub].toString(index - offsets[sub], standardConformingStrings);
154+
return subparams[sub].toString(index - offsets[sub], context);
150155
} catch (SQLException e) {
151156
throw new IllegalStateException(e.getMessage());
152157
}

pgjdbc/src/main/java/org/postgresql/core/v3/CompositeQuery.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,18 @@ public ParameterList createParameterList() {
3838

3939
@Override
4040
public String toString(@Nullable ParameterList parameters) {
41-
StringBuilder sbuf = new StringBuilder(subqueries[0].toString());
41+
return toString(parameters, DefaultSqlSerializationContext.STDSTR_IDEMPOTENT);
42+
}
43+
44+
@Override
45+
public String toString(@Nullable ParameterList parameters, SqlSerializationContext context) {
46+
SimpleParameterList[] subparams =
47+
parameters == null ? null : ((V3ParameterList) parameters).getSubparams();
48+
StringBuilder sbuf = new StringBuilder(
49+
subqueries[0].toString(subparams == null ? null : subparams[0], context));
4250
for (int i = 1; i < subqueries.length; i++) {
4351
sbuf.append(';');
44-
sbuf.append(subqueries[i]);
52+
sbuf.append(subqueries[i].toString(subparams == null ? null : subparams[i], context));
4553
}
4654
return sbuf.toString();
4755
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (c) 2025, PostgreSQL Global Development Group
3+
* See the LICENSE file in the project root for more information.
4+
*/
5+
6+
package org.postgresql.core.v3;
7+
8+
/**
9+
* Provides a default implementation for {@link SqlSerializationContext}.
10+
*/
11+
enum DefaultSqlSerializationContext implements SqlSerializationContext {
12+
/**
13+
* Render SQL in a repeatable way (avoid consuming {@link java.io.InputStream} sources),
14+
* use standard_conforming_strings=yes string literals.
15+
* This option is useful for {@code toString()} implementations as it does induce side effects.
16+
*/
17+
STDSTR_IDEMPOTENT(true, true),
18+
19+
/**
20+
* Render SQL with replacing all the parameters, including {@link java.io.InputStream} sources.
21+
* Use standard_conforming_strings=yes for string literals.
22+
* This option is useful for rendering an executable SQL.
23+
*/
24+
STDSTR_NONIDEMPOTENT(true, false),
25+
26+
// Auxiliary options as standard_conforming_strings=on since PostgreSQL 9.1
27+
28+
/**
29+
* Render SQL in a repeatable way (avoid consuming {@link java.io.InputStream} sources),
30+
* use standard_conforming_strings=no string literals.
31+
* The entry is for completeness only as standard_conforming_strings=no should probably be avoided.
32+
*/
33+
NONSTDSTR_IDEMPOTENT(false, true),
34+
35+
/**
36+
* Render SQL with replacing all the parameters, including {@link java.io.InputStream} sources.
37+
* Use standard_conforming_strings=no for string literals.
38+
* The entry is for completeness only as standard_conforming_strings=no should probably be avoided.
39+
*/
40+
NONSTDSTR_NONIDEMPOTENT(false, false),
41+
;
42+
43+
private final boolean standardConformingStrings;
44+
private final boolean idempotent;
45+
46+
DefaultSqlSerializationContext(boolean standardConformingStrings, boolean idempotent) {
47+
this.standardConformingStrings = standardConformingStrings;
48+
this.idempotent = idempotent;
49+
}
50+
51+
@Override
52+
public boolean getStandardConformingStrings() {
53+
return standardConformingStrings;
54+
}
55+
56+
@Override
57+
public boolean getIdempotent() {
58+
return idempotent;
59+
}
60+
}

pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,7 +1672,7 @@ private void sendBind(SimpleQuery query, SimpleParameterList params, @Nullable P
16721672
StringBuilder sbuf = new StringBuilder(" FE=> Bind(stmt=" + statementName + ",portal=" + portal);
16731673
for (int i = 1; i <= params.getParameterCount(); i++) {
16741674
sbuf.append(",$").append(i).append("=<")
1675-
.append(params.toString(i, true))
1675+
.append(params.toString(i, getStandardConformingStrings()))
16761676
.append(">,type=").append(Oid.toString(params.getTypeOID(i)));
16771677
}
16781678
sbuf.append(")");
@@ -2050,7 +2050,9 @@ private void sendOneQuery(SimpleQuery query, SimpleParameterList params, int max
20502050
}
20512051

20522052
private void sendSimpleQuery(SimpleQuery query, SimpleParameterList params) throws IOException {
2053-
String nativeSql = query.toString(params);
2053+
String nativeSql = query.toString(
2054+
params,
2055+
SqlSerializationContext.of(getStandardConformingStrings(), false));
20542056

20552057
LOGGER.log(Level.FINEST, " FE=> SimpleQuery(query=\"{0}\")", nativeSql);
20562058
Encoding encoding = pgStream.getEncoding();

pgjdbc/src/main/java/org/postgresql/core/v3/SimpleParameterList.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,12 @@ private static <E extends Throwable> RuntimeException sneakyThrow(Throwable e) t
238238
}
239239

240240
@Override
241-
@SuppressWarnings("type.arguments.not.inferred")
242241
public String toString(@Positive int index, boolean standardConformingStrings) {
242+
return toString(index, SqlSerializationContext.of(standardConformingStrings, true));
243+
}
244+
245+
@Override
246+
public String toString(@Positive int index, SqlSerializationContext context) {
243247
--index;
244248
Object paramValue = paramValues[index];
245249
if (paramValue == null) {
@@ -251,7 +255,7 @@ public String toString(@Positive int index, boolean standardConformingStrings) {
251255
String type;
252256
if (paramTypes[index] == Oid.BYTEA) {
253257
try {
254-
return PGbytea.toPGLiteral(paramValue);
258+
return PGbytea.toPGLiteral(paramValue, context);
255259
} catch (Throwable e) {
256260
Throwable cause = e;
257261
if (!(cause instanceof IOException)) {
@@ -393,7 +397,7 @@ public String toString(@Positive int index, boolean standardConformingStrings) {
393397
type = null;
394398
}
395399
}
396-
return quoteAndCast(textValue, type, standardConformingStrings);
400+
return quoteAndCast(textValue, type, context.getStandardConformingStrings());
397401
}
398402

399403
@Override

pgjdbc/src/main/java/org/postgresql/core/v3/SimpleQuery.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ public ParameterList createParameterList() {
5555

5656
@Override
5757
public String toString(@Nullable ParameterList parameters) {
58-
return nativeQuery.toString(parameters);
58+
return toString(parameters, DefaultSqlSerializationContext.STDSTR_IDEMPOTENT);
59+
}
60+
61+
@Override
62+
public String toString(@Nullable ParameterList parameters, SqlSerializationContext context) {
63+
return nativeQuery.toString(parameters, context);
5964
}
6065

6166
@Override

0 commit comments

Comments
 (0)