Skip to content

Commit 3677b8b

Browse files
committed
#880 Fix ResultSet move incorrectly closes input clob
1 parent 8091069 commit 3677b8b

File tree

4 files changed

+81
-19
lines changed

4 files changed

+81
-19
lines changed

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

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

268268
@Override
269-
public FirebirdBlob detach() throws SQLException {
270-
try (LockCloseable ignored = withLock()) {
269+
public FBBlob detach() throws SQLException {
270+
try (var ignored = withLock()) {
271271
checkClosed();
272-
return new FBBlob(gdsHelper, blobId, blobListener, config);
272+
var blobCopy = new FBBlob(gdsHelper, blobId, blobListener, config);
273+
if (cachedInlineBlob != null) {
274+
blobCopy.cachedInlineBlob = cachedInlineBlob.copy();
275+
}
276+
return blobCopy;
273277
}
274278
}
275279

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
SPDX-FileCopyrightText: Copyright 2002-2004 Roman Rokytskyy
33
SPDX-FileCopyrightText: Copyright 2003 Blas Rodriguez Somoza
44
SPDX-FileCopyrightText: Copyright 2007 Gabriel Reid
5-
SPDX-FileCopyrightText: Copyright 2014-2024 Mark Rotteveel
5+
SPDX-FileCopyrightText: Copyright 2014-2025 Mark Rotteveel
66
SPDX-License-Identifier: LGPL-2.1-or-later
77
*/
88
package org.firebirdsql.jdbc.field;
@@ -34,15 +34,20 @@ final class FBCachedLongVarCharField extends FBLongVarCharField {
3434
}
3535

3636
@Override
37-
public Blob getBlob() throws SQLException {
38-
if (isNull()) return null;
39-
return new FBCachedBlob(getFieldData());
37+
public Blob getBlob() {
38+
return getBlobInternal();
4039
}
4140

42-
@SuppressWarnings("DataFlowIssue")
4341
@Override
44-
public Clob getClob() throws SQLException {
45-
if (isNull()) return null;
46-
return new FBCachedClob((FBCachedBlob) getBlob(), blobConfig);
42+
protected FBCachedBlob getBlobInternal() {
43+
final byte[] fieldData = getFieldData();
44+
return fieldData != null ? new FBCachedBlob(fieldData) : null;
4745
}
46+
47+
@Override
48+
public Clob getClob() {
49+
final FBCachedBlob blob = getBlobInternal();
50+
return blob != null ? new FBCachedClob(blob, blobConfig) : null;
51+
}
52+
4853
}

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
SPDX-FileCopyrightText: Copyright 2003 Blas Rodriguez Somoza
44
SPDX-FileCopyrightText: Copyright 2003 Ryan Baldwin
55
SPDX-FileCopyrightText: Copyright 2007 Gabriel Reid
6-
SPDX-FileCopyrightText: Copyright 2011-2024 Mark Rotteveel
6+
SPDX-FileCopyrightText: Copyright 2011-2025 Mark Rotteveel
77
SPDX-FileCopyrightText: Copyright 2020 Vasiliy Yashkov
88
SPDX-License-Identifier: LGPL-2.1-or-later
99
*/
1010
package org.firebirdsql.jdbc.field;
1111

1212
import org.firebirdsql.gds.impl.GDSHelper;
13+
import org.firebirdsql.gds.ng.FbTransaction;
1314
import org.firebirdsql.gds.ng.fields.FieldDescriptor;
15+
import org.firebirdsql.gds.ng.listeners.TransactionListener;
1416
import org.firebirdsql.jaybird.props.PropertyConstants;
1517
import org.firebirdsql.jdbc.FBBlob;
1618
import org.firebirdsql.jdbc.FBClob;
@@ -85,28 +87,34 @@ public void close() throws SQLException {
8587

8688
@Override
8789
public Blob getBlob() throws SQLException {
90+
final FirebirdBlob blob = getBlobInternal();
91+
return blob != null ? registerWithTransaction(blob.detach()) : null;
92+
}
93+
94+
protected FirebirdBlob getBlobInternal() {
8895
if (blob != null) return blob;
89-
byte[] bytes = getFieldData();
96+
final byte[] bytes = getFieldData();
9097
if (bytes == null) return null;
9198

9299
return blob = new FBBlob(gdsHelper, getDatatypeCoder().decodeLong(bytes), blobListener, blobConfig);
93100
}
94101

95102
@Override
96103
public Clob getClob() throws SQLException {
97-
FBBlob blob = (FBBlob) getBlob();
98-
return blob != null ? new FBClob(blob) : null;
104+
final FBBlob blob = (FBBlob) getBlobInternal();
105+
if (blob == null) return null;
106+
return new FBClob(registerWithTransaction(blob.detach()));
99107
}
100108

101109
@Override
102110
public InputStream getBinaryStream() throws SQLException {
103-
Blob blob = getBlob();
111+
final Blob blob = getBlobInternal();
104112
return blob != null ? blob.getBinaryStream() : null;
105113
}
106114

107115
@Override
108116
public byte[] getBytes() throws SQLException {
109-
final FirebirdBlob blob = (FirebirdBlob) getBlob();
117+
final FirebirdBlob blob = getBlobInternal();
110118
return blob != null ? blob.getBytes() : null;
111119
}
112120

@@ -279,4 +287,15 @@ private void copyBytes(byte @NonNull [] bytes, int length) throws SQLException {
279287
blobExplicitNull = false;
280288
}
281289

290+
@NullMarked
291+
private <T extends FirebirdBlob> T registerWithTransaction(T blob) {
292+
if (blob instanceof TransactionListener transactionListener) {
293+
FbTransaction currentTransaction = gdsHelper.getCurrentTransaction();
294+
if (currentTransaction != null) {
295+
currentTransaction.addWeakTransactionListener(transactionListener);
296+
}
297+
}
298+
return blob;
299+
}
300+
282301
}

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
SPDX-FileCopyrightText: Copyright 2003 Ryan Baldwin
55
SPDX-FileCopyrightText: Copyright 2005-2007 Gabriel Reid
66
SPDX-FileCopyrightText: Copyright 2005 Steven Jardine
7-
SPDX-FileCopyrightText: Copyright 2011-2024 Mark Rotteveel
7+
SPDX-FileCopyrightText: Copyright 2011-2025 Mark Rotteveel
88
SPDX-FileCopyrightText: Copyright 2019 Vasiliy Yashkov
99
SPDX-License-Identifier: LGPL-2.1-or-later
1010
*/
@@ -22,6 +22,7 @@
2222
import org.junit.jupiter.params.provider.MethodSource;
2323
import org.junit.jupiter.params.provider.ValueSource;
2424

25+
import java.io.BufferedReader;
2526
import java.io.ByteArrayInputStream;
2627
import java.io.StringReader;
2728
import java.nio.charset.StandardCharsets;
@@ -519,7 +520,7 @@ void testUpdatableResultSet(String scrollableCursorPropertyValue) throws Excepti
519520
counter++;
520521
}
521522

522-
assertEquals(counter - 1, recordCount, "Should process " + recordCount + " rows");
523+
assertEquals(recordCount, counter - 1, "Should process " + recordCount + " rows");
523524

524525
// check the insertRow() feature
525526
int newId = recordCount + 1;
@@ -1437,6 +1438,7 @@ void testRequiringMultipleFetches() throws Exception {
14371438
*/
14381439
@ParameterizedTest
14391440
@MethodSource
1441+
@SuppressWarnings("MagicConstant")
14401442
void testIsBeforeFirst_isAfterLast_emptyResultSet_bug807(int resultSetType, int resultSetConcurrency,
14411443
String scrollableCursorPropertyValue) throws SQLException {
14421444
try (var connection = createConnection(scrollableCursorPropertyValue)) {
@@ -1485,6 +1487,7 @@ void testNextAfterLastRowShouldNotCloseResultSet(boolean autoCommit) throws Exce
14851487

14861488
@ParameterizedTest
14871489
@MethodSource
1490+
@SuppressWarnings("MagicConstant")
14881491
void wasNull_onInsertRow(int resultSetType, String scrollableCursorPropertyValue) throws Exception {
14891492
try (var connection = createConnection(scrollableCursorPropertyValue)) {
14901493
executeCreateTable(connection, CREATE_TABLE_STATEMENT);
@@ -1793,6 +1796,37 @@ void updateObject_String_InputStream_type_scaleOrLength() throws SQLException {
17931796
}
17941797
}
17951798

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

0 commit comments

Comments
 (0)