{"record":{"id":"e729d42140197b4c","repo":"apache/shardingsphere","slug":"batch-already-opened-for-statement-handle-d","errorCode":null,"errorMessage":"Batch already opened for statement handle %d","messagePattern":"Batch already opened for statement handle (.+?)","errorType":"exception","errorClass":"BatchAlreadyOpenedException","httpStatus":null,"severity":"error","filePath":"proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java","lineNumber":80,"sourceCode":"    // private static final int BLOB_STREAM = 3;\n    \n    private static final int WIDE_CLUMPLET_LENGTH_SIZE = 4;\n    \n    private static final int INTEGER_VALUE_LENGTH = 4;\n    \n    private final FirebirdBatchCreateCommandPacket packet;\n    \n    private final ConnectionSession connectionSession;\n    \n    @Override\n    public Collection<DatabasePacket> execute() throws SQLException {\n        int connectionId = connectionSession.getConnectionId();\n        int statementId = packet.getStatementHandle();\n        if (null == connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(statementId)) {\n            throw new InvalidStatementHandleException(statementId);\n        }\n        if (null != FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementId)) {\n            throw new BatchAlreadyOpenedException(statementId);\n        }\n        ByteBuf batchBlr = packet.getBatchBlr();\n        int blrLength = batchBlr.readableBytes();\n        FirebirdParseBatchBlr messageFormat = FirebirdParseBatchBlr.parse(batchBlr, blrLength);\n        if (messageFormat.getFields().isEmpty()) {\n            throw new BatchParametersRequiredException(statementId);\n        }\n        if (packet.getBatchMessageLength() != messageFormat.getMessageLength()) {\n            throw new InvalidBatchMessageFormatException(\n                    String.format(\"invalid message length: computed %d from BLR but client sent %d\", messageFormat.getMessageLength(), packet.getBatchMessageLength()));\n        }\n        ByteBuf batchParametersBuffer = packet.getBatchParametersBuffer();\n        BatchParameters batchParameters = BatchParameters.parse(batchParametersBuffer);\n        FirebirdBatchRegistry.getInstance().registerBatchStatement(connectionId, statementId,\n                new FirebirdBatchStatement(statementId, messageFormat.getFields(), batchParameters.getBufferSize(), batchParameters.isRecordCounts(), batchParameters.isMultiError()));\n        return Collections.singleton(new FirebirdGenericResponsePacket().setHandle(statementId));\n    }\n    ","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java#L62-L98","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Send CLOSE BATCH (or FREE STATEMENT DROP/UNPREPARE, which also unregisters the batch) before creating a new batch on the same statement handle.","Make retries idempotent: on 'batch already opened', reuse or close the existing batch instead of blindly re-creating it.","Audit driver code for error paths that skip batch teardown after a failed EXECUTE BATCH."],"exampleFix":"// before\nsendCreateBatch(h); // second time -> BatchAlreadyOpenedException\n\n// after\nif (batchOpen(h)) { sendCloseBatch(h); }\nsendCreateBatch(h);","handlingStrategy":"validation","validationCode":"if (openBatches.contains(stmtHandle)) { sendCloseBatch(stmtHandle); openBatches.remove(stmtHandle); }\nsendCreateBatch(stmtHandle);\nopenBatches.add(stmtHandle);","typeGuard":null,"tryCatchPattern":"catch (SQLException e) {\n    if (e.getMessage().contains(\"Batch already opened\")) { sendCloseBatch(stmtHandle); sendCreateBatch(stmtHandle); }\n    else throw e;\n}","preventionTips":["Maintain client-side set of open batch handles.","Close batches in finally blocks after execute."],"tags":["firebird","batch","statement-handle","resource-cleanup"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}