Skip to content

Commit 8546f4f

Browse files
committed
#880 Fix batch error "Repeated blob id 0:0 in registerBlob()"
1 parent 57593d4 commit 8546f4f

File tree

3 files changed

+47
-0
lines changed

3 files changed

+47
-0
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The following was fixed or changed since Jaybird 6.0.2:
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.
4444
* Fixed: `ResultSet` move incorrectly closes input `Clob` (https://github.com/FirebirdSQL/jaybird/issues/880[#880])
45+
* Fixed: Batch execution with multiple empty strings resulted in error _"Repeated blob id 0:0 in registerBlob()"_ (https://github.com/FirebirdSQL/jaybird/issues/888[#888])
4546

4647
=== Jaybird 6.0.2
4748

src/main/org/firebirdsql/gds/ng/wire/version16/V16Statement.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141

4242
import java.io.ByteArrayOutputStream;
4343
import java.io.IOException;
44+
import java.nio.ByteBuffer;
4445
import java.sql.SQLException;
4546
import java.util.Collection;
47+
import java.util.HashSet;
4648
import java.util.List;
4749

4850
import static org.firebirdsql.gds.impl.wire.WireProtocolConstants.op_batch_cancel;
@@ -166,10 +168,14 @@ private void registerBlobs(XdrOutputStream xdrOut, RowDescriptor parameterDescri
166168
List<Integer> blobPositions = blobPositions(parameterDescriptor);
167169
if (blobPositions.isEmpty()) return;
168170
FbWireDatabase db = getDatabase();
171+
var blobsRegistered = new HashSet<>((int) (rowValues.size() * blobPositions.size() / 0.75f + 1));
169172
for (RowValue rowValue : rowValues) {
170173
for (int position : blobPositions) {
171174
byte[] fieldData = rowValue.getFieldData(position);
172175
if (fieldData == null) continue;
176+
// Do not register a blob multiple times
177+
if (!blobsRegistered.add(toLong(fieldData))) continue;
178+
173179
xdrOut.writeInt(WireProtocolConstants.op_batch_regblob);
174180
xdrOut.writeInt(getHandle()); // p_batch_statement
175181
// register as itself
@@ -181,6 +187,12 @@ private void registerBlobs(XdrOutputStream xdrOut, RowDescriptor parameterDescri
181187
xdrOut.flush();
182188
}
183189

190+
// This is not necessarily the correct endianness, we just want the id bytes expressed as a long.
191+
private static long toLong(byte[] bytes) {
192+
assert bytes.length == 8 : "expected 8 bytes";
193+
return ByteBuffer.wrap(bytes).getLong();
194+
}
195+
184196
private List<Integer> blobPositions(RowDescriptor parameterDescriptor) {
185197
return parameterDescriptor.getFieldDescriptors().stream()
186198
.filter(f -> f.isFbType(ISCConstants.SQL_BLOB))

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.time.LocalDateTime;
3535
import java.time.LocalTime;
3636
import java.util.Arrays;
37+
import java.util.List;
3738
import java.util.Properties;
3839
import java.util.stream.IntStream;
3940

@@ -42,6 +43,9 @@
4243
import static org.firebirdsql.common.FBTestProperties.getDefaultPropertiesForConnection;
4344
import static org.firebirdsql.common.FBTestProperties.getUrl;
4445
import static org.firebirdsql.common.FbAssumptions.assumeServerBatchSupport;
46+
import static org.firebirdsql.common.assertions.ResultSetAssertions.assertNextRow;
47+
import static org.firebirdsql.common.assertions.ResultSetAssertions.assertNoNextRow;
48+
import static org.firebirdsql.common.assertions.ResultSetAssertions.assertRowEquals;
4549
import static org.firebirdsql.common.matchers.SQLExceptionMatchers.message;
4650
import static org.hamcrest.MatcherAssert.assertThat;
4751
import static org.hamcrest.Matchers.containsString;
@@ -399,6 +403,36 @@ void testPreparedStatementBatch_65Blobs() throws SQLException {
399403
}
400404
}
401405

406+
/**
407+
* Rationale: see <a href="https://github.com/FirebirdSQL/jaybird/issues/888">#888</a>.
408+
*/
409+
@ParameterizedTest(name = "[{index}] useServerBatch = {0}")
410+
@ValueSource(booleans = { true, false })
411+
void testBatchMultipleEmptyStringsInBlob(boolean useServerBatch) throws Exception {
412+
try (var connection = createConnection(useServerBatch);
413+
var stmt = connection.createStatement()) {
414+
stmt.execute(RECREATE_BATCH_UPDATES_TABLE);
415+
connection.setAutoCommit(false);
416+
try (var ps = connection.prepareStatement("INSERT INTO batch_updates(id, clob_value) VALUES (?, ?)")) {
417+
ps.setInt(1, 1);
418+
ps.setString(2, "");
419+
ps.addBatch();
420+
ps.setInt(1, 2);
421+
ps.setString(2, "");
422+
ps.addBatch();
423+
assertDoesNotThrow(ps::executeBatch);
424+
}
425+
426+
try (var rs = stmt.executeQuery("select ID, CLOB_VALUE from BATCH_UPDATES order by ID")) {
427+
assertNextRow(rs);
428+
assertRowEquals(rs, List.of(1, ""));
429+
assertNextRow(rs);
430+
assertRowEquals(rs, List.of(2, ""));
431+
assertNoNextRow(rs);
432+
}
433+
}
434+
}
435+
402436
private static Connection createConnection(boolean useServerBatch) throws SQLException {
403437
Properties props = getDefaultPropertiesForConnection();
404438
props.setProperty(PropertyNames.useServerBatch, String.valueOf(useServerBatch));

0 commit comments

Comments
 (0)