Skip to content

Commit 853e894

Browse files
committed
#881 backport to Jaybird 5: #880 Fix ResultSet move incorrectly closes input clob
1 parent 6cbff69 commit 853e894

File tree

5 files changed

+85
-21
lines changed

5 files changed

+85
-21
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ For known issues, consult <<known-issues>>.
3434

3535
The following has been changed or fixed since Jaybird 5.0.8:
3636

37-
* ...
37+
* Fixed: `ResultSet` move incorrectly closes input `Clob` (https://github.com/FirebirdSQL/jaybird/issues/881[#881])
3838
3939
[#jaybird-5-0-8-changelog]
4040
=== Jaybird 5.0.8

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,14 @@ public boolean isSegmented() throws SQLException {
327327
}
328328

329329
@Override
330-
public FirebirdBlob detach() throws SQLException {
330+
public FBBlob detach() throws SQLException {
331331
try (LockCloseable ignored = withLock()) {
332332
checkClosed();
333-
return new FBBlob(gdsHelper, blobId, blobListener, config);
333+
FBBlob blobCopy = new FBBlob(gdsHelper, blobId, blobListener, config);
334+
if (cachedInlineBlob != null) {
335+
blobCopy.cachedInlineBlob = cachedInlineBlob.copy();
336+
}
337+
return blobCopy;
334338
}
335339
}
336340

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,21 @@ final class FBCachedLongVarCharField extends FBLongVarCharField {
4141
super(fieldDescriptor, dataProvider, requiredType, gdsHelper);
4242
}
4343

44-
public Blob getBlob() throws SQLException {
45-
if (isNull()) return null;
46-
return new FBCachedBlob(getFieldData());
44+
@Override
45+
public Blob getBlob() {
46+
return getBlobInternal();
4747
}
48-
49-
public Clob getClob() throws SQLException {
50-
if (isNull()) return null;
51-
return new FBCachedClob((FBCachedBlob)getBlob(), blobConfig);
48+
49+
@Override
50+
protected FBCachedBlob getBlobInternal() {
51+
final byte[] fieldData = getFieldData();
52+
return fieldData != null ? new FBCachedBlob(fieldData) : null;
53+
}
54+
55+
@Override
56+
public Clob getClob() {
57+
final FBCachedBlob blob = getBlobInternal();
58+
return blob != null ? new FBCachedClob(blob, blobConfig) : null;
5259
}
60+
5361
}

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
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;
2628
import org.firebirdsql.jdbc.FBObjectListener;
29+
import org.firebirdsql.jdbc.FirebirdBlob;
2730

2831
import java.io.ByteArrayOutputStream;
2932
import java.io.IOException;
@@ -96,30 +99,35 @@ public void close() throws SQLException {
9699

97100
@Override
98101
public Blob getBlob() throws SQLException {
102+
final FirebirdBlob blob = getBlobInternal();
103+
return blob != null ? registerWithTransaction(blob.detach()) : null;
104+
}
105+
106+
protected FirebirdBlob getBlobInternal() {
99107
if (blob != null) return blob;
100-
if (isNull()) return null;
108+
final byte[] bytes = getFieldData();
109+
if (bytes == null) return null;
101110

102-
blob = new FBBlob(gdsHelper, getDatatypeCoder().decodeLong(getFieldData()), blobListener, blobConfig);
103-
return blob;
111+
return blob = new FBBlob(gdsHelper, getDatatypeCoder().decodeLong(bytes), blobListener, blobConfig);
104112
}
105113

106114
@Override
107115
public Clob getClob() throws SQLException {
108-
FBBlob blob = (FBBlob) getBlob();
109-
if (blob == null) return null;
110-
return new FBClob(blob);
116+
final FBBlob blob = (FBBlob) getBlobInternal();
117+
if (blob == null) return null;
118+
return new FBClob(registerWithTransaction(blob.detach()));
111119
}
112120

113121
@Override
114122
public InputStream getBinaryStream() throws SQLException {
115-
Blob blob = getBlob();
123+
final Blob blob = getBlobInternal();
116124
if (blob == null) return null;
117125
return blob.getBinaryStream();
118126
}
119127

120128
@Override
121129
public byte[] getBytes() throws SQLException {
122-
final Blob blob = getBlob();
130+
final Blob blob = getBlobInternal();
123131
if (blob == null) return null;
124132

125133
try (final InputStream in = blob.getBinaryStream()) {
@@ -319,4 +327,14 @@ private void copyBytes(byte[] bytes, int length) throws SQLException {
319327
blobExplicitNull = false;
320328
}
321329

330+
private <T extends FirebirdBlob> T registerWithTransaction(T blob) {
331+
if (blob instanceof TransactionListener) {
332+
FbTransaction currentTransaction = gdsHelper.getCurrentTransaction();
333+
if (currentTransaction != null) {
334+
currentTransaction.addWeakTransactionListener((TransactionListener) blob);
335+
}
336+
}
337+
return blob;
338+
}
339+
322340
}

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.junit.jupiter.params.provider.Arguments;
3131
import org.junit.jupiter.params.provider.MethodSource;
3232

33+
import java.io.BufferedReader;
3334
import java.io.ByteArrayInputStream;
3435
import java.io.StringReader;
3536
import java.nio.charset.StandardCharsets;
@@ -590,7 +591,7 @@ void testUpdatableResultSet(String scrollableCursorPropertyValue) throws Excepti
590591
counter++;
591592
}
592593

593-
assertEquals(counter - 1, recordCount, "Should process " + recordCount + " rows");
594+
assertEquals(recordCount, counter - 1, "Should process " + recordCount + " rows");
594595

595596
// check the insertRow() feature
596597
int newId = recordCount + 1;
@@ -625,9 +626,9 @@ void testUpdatableResultSet(String scrollableCursorPropertyValue) throws Excepti
625626
if (SCROLLABLE_CURSOR_SERVER.equals(scrollableCursorPropertyValue)
626627
&& isPureJavaType().matches(GDS_TYPE)
627628
&& getDefaultSupportInfo().supportsScrollableCursors()) {
628-
assertEquals(counter - 1, recordCount + 1);
629+
assertEquals(recordCount + 1, counter - 1);
629630
} else {
630-
assertEquals(counter - 1, recordCount);
631+
assertEquals(recordCount, counter - 1);
631632
}
632633
}
633634

@@ -1445,6 +1446,7 @@ void testIsAfterLast_bug689() 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 (Connection connection = createConnection(scrollableCursorPropertyValue)) {
@@ -1477,6 +1479,7 @@ static Stream<Arguments> testIsBeforeFirst_isAfterLast_emptyResultSet_bug807() {
14771479

14781480
@ParameterizedTest
14791481
@MethodSource
1482+
@SuppressWarnings("MagicConstant")
14801483
void wasNull_onInsertRow(int resultSetType, String scrollableCursorPropertyValue) throws Exception {
14811484
try (Connection connection = createConnection(scrollableCursorPropertyValue)) {
14821485
executeCreateTable(connection, CREATE_TABLE_STATEMENT);
@@ -1574,6 +1577,37 @@ && canSupportServerSideScrollable()
15741577
}
15751578
}
15761579

1580+
@Test
1581+
void clobRemainsOpenAfterNext() throws Exception {
1582+
try (Connection connection = getConnectionViaDriverManager()) {
1583+
executeCreateTable(connection, CREATE_TABLE_STATEMENT);
1584+
connection.setAutoCommit(false);
1585+
createTestData(2, i -> "clob-value-" + i, connection, "blob_str");
1586+
connection.commit();
1587+
1588+
Clob secondClob;
1589+
1590+
try (Statement stmt = connection.createStatement()) {
1591+
ResultSet rs = stmt.executeQuery("select id, blob_str from test_table order by id");
1592+
assertNextRow(rs);
1593+
Clob firstClob = rs.getClob(2);
1594+
assertNextRow(rs);
1595+
try (BufferedReader firstReader = new BufferedReader(
1596+
assertDoesNotThrow(() -> firstClob.getCharacterStream(),
1597+
"should be able to get character stream from clob after next"))) {
1598+
assertEquals("clob-value-1", firstReader.readLine());
1599+
}
1600+
secondClob = rs.getClob(2);
1601+
assertNoNextRow(rs);
1602+
}
1603+
try (BufferedReader secondReader = new BufferedReader(
1604+
assertDoesNotThrow(() -> secondClob.getCharacterStream(),
1605+
"should be able to get character stream from clob after statement close"))) {
1606+
assertEquals("clob-value-2", secondReader.readLine());
1607+
}
1608+
}
1609+
}
1610+
15771611
private static boolean canSupportServerSideScrollable() {
15781612
return "PURE_JAVA".equalsIgnoreCase(GDS_TYPE)
15791613
&& getDefaultSupportInfo().supportsScrollableCursors();

0 commit comments

Comments
 (0)