Skip to content

Commit 57593d4

Browse files
committed
#880 Fix ResultSet move incorrectly closes input clob
1 parent af57b48 commit 57593d4

File tree

5 files changed

+80
-17
lines changed

5 files changed

+80
-17
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ For known issues, consult <<known-issues>>.
3535

3636
=== Jaybird 6.0.3
3737

38-
The following was fixed or changed since Jaybird 6.0.3:
38+
The following was fixed or changed since Jaybird 6.0.2:
3939

4040
* Fixed: statement close could cause a hang of the connection (https://github.com/FirebirdSQL/jaybird/issues/876[#876])
4141
+
4242
A large amount (>= 64) of statement closes (or cleanup of leaked statements) without interleaving other server operations could cause a connection to Firebird 4.0 or higher to hang.
4343
This could also hang the cleaner thread used for closing leaked statements.
44+
* Fixed: `ResultSet` move incorrectly closes input `Clob` (https://github.com/FirebirdSQL/jaybird/issues/880[#880])
4445

4546
=== Jaybird 6.0.2
4647

src/main/org/firebirdsql/jdbc/FBBlob.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,14 @@ public boolean isSegmented() throws SQLException {
271271
}
272272

273273
@Override
274-
public FirebirdBlob detach() throws SQLException {
275-
try (LockCloseable ignored = withLock()) {
274+
public FBBlob detach() throws SQLException {
275+
try (var ignored = withLock()) {
276276
checkClosed();
277-
return new FBBlob(gdsHelper, blobId, blobListener, config);
277+
final var blobCopy = new FBBlob(gdsHelper, blobId, blobListener, config);
278+
if (cachedInlineBlob != null) {
279+
blobCopy.cachedInlineBlob = cachedInlineBlob.copy();
280+
}
281+
return blobCopy;
278282
}
279283
}
280284

src/main/org/firebirdsql/jdbc/field/FBCachedLongVarCharField.java

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,20 @@ final class FBCachedLongVarCharField extends FBLongVarCharField {
4545
}
4646

4747
@Override
48-
public Blob getBlob() throws SQLException {
49-
if (isNull()) return null;
50-
return new FBCachedBlob(getFieldData());
48+
public Blob getBlob() {
49+
return getBlobInternal();
5150
}
5251

53-
@SuppressWarnings("DataFlowIssue")
5452
@Override
55-
public Clob getClob() throws SQLException {
56-
if (isNull()) return null;
57-
return new FBCachedClob((FBCachedBlob) getBlob(), blobConfig);
53+
protected FBCachedBlob getBlobInternal() {
54+
final byte[] fieldData = getFieldData();
55+
return fieldData != null ? new FBCachedBlob(fieldData) : null;
5856
}
57+
58+
@Override
59+
public Clob getClob() {
60+
final FBCachedBlob blob = getBlobInternal();
61+
return blob != null ? new FBCachedClob(blob, blobConfig) : null;
62+
}
63+
5964
}

src/main/org/firebirdsql/jdbc/field/FBLongVarCharField.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
package org.firebirdsql.jdbc.field;
2020

2121
import org.firebirdsql.gds.impl.GDSHelper;
22+
import org.firebirdsql.gds.ng.FbTransaction;
2223
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
24+
import org.firebirdsql.gds.ng.listeners.TransactionListener;
2325
import org.firebirdsql.jaybird.props.PropertyConstants;
2426
import org.firebirdsql.jdbc.FBBlob;
2527
import org.firebirdsql.jdbc.FBClob;
@@ -94,28 +96,34 @@ public void close() throws SQLException {
9496

9597
@Override
9698
public Blob getBlob() throws SQLException {
99+
final FirebirdBlob blob = getBlobInternal();
100+
return blob != null ? registerWithTransaction(blob.detach()) : null;
101+
}
102+
103+
protected FirebirdBlob getBlobInternal() {
97104
if (blob != null) return blob;
98-
byte[] bytes = getFieldData();
105+
final byte[] bytes = getFieldData();
99106
if (bytes == null) return null;
100107

101108
return blob = new FBBlob(gdsHelper, getDatatypeCoder().decodeLong(bytes), blobListener, blobConfig);
102109
}
103110

104111
@Override
105112
public Clob getClob() throws SQLException {
106-
FBBlob blob = (FBBlob) getBlob();
107-
return blob != null ? new FBClob(blob) : null;
113+
final FBBlob blob = (FBBlob) getBlobInternal();
114+
if (blob == null) return null;
115+
return new FBClob(registerWithTransaction(blob.detach()));
108116
}
109117

110118
@Override
111119
public InputStream getBinaryStream() throws SQLException {
112-
Blob blob = getBlob();
120+
final Blob blob = getBlobInternal();
113121
return blob != null ? blob.getBinaryStream() : null;
114122
}
115123

116124
@Override
117125
public byte[] getBytes() throws SQLException {
118-
final FirebirdBlob blob = (FirebirdBlob) getBlob();
126+
final FirebirdBlob blob = getBlobInternal();
119127
return blob != null ? blob.getBytes() : null;
120128
}
121129

@@ -288,4 +296,15 @@ private void copyBytes(byte @NonNull [] bytes, int length) throws SQLException {
288296
blobExplicitNull = false;
289297
}
290298

299+
@NullMarked
300+
private <T extends FirebirdBlob> T registerWithTransaction(T blob) {
301+
if (blob instanceof TransactionListener transactionListener) {
302+
FbTransaction currentTransaction = gdsHelper.getCurrentTransaction();
303+
if (currentTransaction != null) {
304+
currentTransaction.addWeakTransactionListener(transactionListener);
305+
}
306+
}
307+
return blob;
308+
}
309+
291310
}

src/test/org/firebirdsql/jdbc/FBResultSetTest.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.junit.jupiter.params.provider.MethodSource;
3131
import org.junit.jupiter.params.provider.ValueSource;
3232

33+
import java.io.BufferedReader;
3334
import java.io.ByteArrayInputStream;
3435
import java.io.StringReader;
3536
import java.nio.charset.StandardCharsets;
@@ -527,7 +528,7 @@ void testUpdatableResultSet(String scrollableCursorPropertyValue) throws Excepti
527528
counter++;
528529
}
529530

530-
assertEquals(counter - 1, recordCount, "Should process " + recordCount + " rows");
531+
assertEquals(recordCount, counter - 1, "Should process " + recordCount + " rows");
531532

532533
// check the insertRow() feature
533534
int newId = recordCount + 1;
@@ -1445,6 +1446,7 @@ void testRequiringMultipleFetches() throws Exception {
14451446
*/
14461447
@ParameterizedTest
14471448
@MethodSource
1449+
@SuppressWarnings("MagicConstant")
14481450
void testIsBeforeFirst_isAfterLast_emptyResultSet_bug807(int resultSetType, int resultSetConcurrency,
14491451
String scrollableCursorPropertyValue) throws SQLException {
14501452
try (var connection = createConnection(scrollableCursorPropertyValue)) {
@@ -1493,6 +1495,7 @@ void testNextAfterLastRowShouldNotCloseResultSet(boolean autoCommit) throws Exce
14931495

14941496
@ParameterizedTest
14951497
@MethodSource
1498+
@SuppressWarnings("MagicConstant")
14961499
void wasNull_onInsertRow(int resultSetType, String scrollableCursorPropertyValue) throws Exception {
14971500
try (var connection = createConnection(scrollableCursorPropertyValue)) {
14981501
executeCreateTable(connection, CREATE_TABLE_STATEMENT);
@@ -1801,6 +1804,37 @@ void updateObject_String_InputStream_type_scaleOrLength() throws SQLException {
18011804
}
18021805
}
18031806

1807+
@Test
1808+
void clobRemainsOpenAfterNext() throws Exception {
1809+
try (var connection = getConnectionViaDriverManager()) {
1810+
executeCreateTable(connection, CREATE_TABLE_STATEMENT);
1811+
connection.setAutoCommit(false);
1812+
createTestData(2, i -> "clob-value-" + i, connection, "blob_str");
1813+
connection.commit();
1814+
1815+
Clob secondClob;
1816+
1817+
try (var stmt = connection.createStatement()) {
1818+
var rs = stmt.executeQuery("select id, blob_str from test_table order by id");
1819+
assertNextRow(rs);
1820+
Clob firstClob = rs.getClob(2);
1821+
assertNextRow(rs);
1822+
try (BufferedReader firstReader = new BufferedReader(
1823+
assertDoesNotThrow(() -> firstClob.getCharacterStream(),
1824+
"should be able to get character stream from clob after next"))) {
1825+
assertEquals("clob-value-1", firstReader.readLine());
1826+
}
1827+
secondClob = rs.getClob(2);
1828+
assertNoNextRow(rs);
1829+
}
1830+
try (BufferedReader secondReader = new BufferedReader(
1831+
assertDoesNotThrow(() -> secondClob.getCharacterStream(),
1832+
"should be able to get character stream from clob after statement close"))) {
1833+
assertEquals("clob-value-2", secondReader.readLine());
1834+
}
1835+
}
1836+
}
1837+
18041838
static Stream<String> scrollableCursorPropertyValues() {
18051839
// We are unconditionally emitting SERVER, to check if the value behaves appropriately on versions that do
18061840
// not support server-side scrollable cursors

0 commit comments

Comments
 (0)