apache/shardingsphere · error · BatchAlreadyOpenedException

Batch already opened for statement handle %d

Error message

Batch already opened for statement handle %d

What it means

BatchAlreadyOpenedException thrown by FirebirdBatchCreateCommandExecutor when FirebirdBatchRegistry already holds a batch for the same (connectionId, statementId). Firebird allows only one open batch per statement handle, so a second CREATE BATCH without an intervening FREE must fail.

Source

Thrown at proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java:80

    // private static final int BLOB_STREAM = 3;
    
    private static final int WIDE_CLUMPLET_LENGTH_SIZE = 4;
    
    private static final int INTEGER_VALUE_LENGTH = 4;
    
    private final FirebirdBatchCreateCommandPacket packet;
    
    private final ConnectionSession connectionSession;
    
    @Override
    public Collection<DatabasePacket> execute() throws SQLException {
        int connectionId = connectionSession.getConnectionId();
        int statementId = packet.getStatementHandle();
        if (null == connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(statementId)) {
            throw new InvalidStatementHandleException(statementId);
        }
        if (null != FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementId)) {
            throw new BatchAlreadyOpenedException(statementId);
        }
        ByteBuf batchBlr = packet.getBatchBlr();
        int blrLength = batchBlr.readableBytes();
        FirebirdParseBatchBlr messageFormat = FirebirdParseBatchBlr.parse(batchBlr, blrLength);
        if (messageFormat.getFields().isEmpty()) {
            throw new BatchParametersRequiredException(statementId);
        }
        if (packet.getBatchMessageLength() != messageFormat.getMessageLength()) {
            throw new InvalidBatchMessageFormatException(
                    String.format("invalid message length: computed %d from BLR but client sent %d", messageFormat.getMessageLength(), packet.getBatchMessageLength()));
        }
        ByteBuf batchParametersBuffer = packet.getBatchParametersBuffer();
        BatchParameters batchParameters = BatchParameters.parse(batchParametersBuffer);
        FirebirdBatchRegistry.getInstance().registerBatchStatement(connectionId, statementId,
                new FirebirdBatchStatement(statementId, messageFormat.getFields(), batchParameters.getBufferSize(), batchParameters.isRecordCounts(), batchParameters.isMultiError()));
        return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(statementId));
    }
    

View on GitHub (pinned to e952770a21)

Solutions

  1. Send CLOSE BATCH (or FREE STATEMENT DROP/UNPREPARE, which also unregisters the batch) before creating a new batch on the same statement handle.
  2. Make retries idempotent: on 'batch already opened', reuse or close the existing batch instead of blindly re-creating it.
  3. Audit driver code for error paths that skip batch teardown after a failed EXECUTE BATCH.

Example fix

// before
sendCreateBatch(h); // second time -> BatchAlreadyOpenedException

// after
if (batchOpen(h)) { sendCloseBatch(h); }
sendCreateBatch(h);
Defensive patterns

Strategy: validation

Validate before calling

if (openBatches.contains(stmtHandle)) { sendCloseBatch(stmtHandle); openBatches.remove(stmtHandle); }
sendCreateBatch(stmtHandle);
openBatches.add(stmtHandle);

Try / catch

catch (SQLException e) {
    if (e.getMessage().contains("Batch already opened")) { sendCloseBatch(stmtHandle); sendCreateBatch(stmtHandle); }
    else throw e;
}

Prevention

When it happens

Trigger: execute() checks FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementId) != null before registering; sending CREATE BATCH twice for the same prepared statement handle, or re-creating a batch after messages were already queued but before CLOSE BATCH/FREE STATEMENT, hits this branch.

Common situations: client retry logic that resends CREATE BATCH after a timeout without first freeing the previous batch; driver bug that does not tear down batch state on error; batch loop reusing a prepared statement for a second batch run without cleanup.

Related errors


AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14). Data as JSON: /api/errors/e729d42140197b4c. Report an issue: GitHub.