apache/shardingsphere · error · InvalidStatementHandleException
Invalid statement handle: %d
Error message
Invalid statement handle: %d
What it means
InvalidStatementHandleException thrown by FirebirdBatchCreateCommandExecutor when the client asks to create a batch for a statement handle that is not in the connection's ServerPreparedStatementRegistry. It mirrors Firebird's native 'Invalid statement handle' error: you cannot batch-execute a statement that was never prepared (or was already freed) on this connection.
Source
Thrown at proxy/frontend/dialect/firebird/src/main/java/org/apache/shardingsphere/proxy/frontend/firebird/command/query/batch/FirebirdBatchCreateCommandExecutor.java:77
private static final int TAG_BLOB_POLICY = 4;
// 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()));View on GitHub (pinned to e952770a21)
Solutions
- Always run PREPARE first and use the statement handle returned in the prepare response for the subsequent CREATE BATCH on the same connection.
- Do not send CREATE BATCH after FREE STATEMENT (option DROP/UNPREPARE) for that handle; re-prepare instead.
- Ensure pooled clients do not carry statement handles across physical Firebird proxy connections.
- If writing a raw-protocol client, verify you parse the statement handle from the correct response field (int4 in the generic response data).
Example fix
// before: batch created with a stale/foreign handle stmtHandle = 0; // never prepared sendCreateBatch(stmtHandle); // after: prepare first, then batch prepResp = sendPrepare(sql); sendCreateBatch(prepResp.statementHandle);
Defensive patterns
Strategy: validation
Validate before calling
// Client: only batch handles you actually prepared on this connection
Set<Integer> prepared = new HashSet<>();
prepared.add(sendPrepare(sql).statementHandle);
if (!prepared.contains(stmtHandle)) throw new IllegalStateException("handle not prepared"); Try / catch
catch (SQLException e) {
if (e.getMessage().contains("Invalid statement handle")) { int h = sendPrepare(sql).statementHandle; sendCreateBatch(h); }
else throw e;
} Prevention
- Track prepared statement handles per connection.
- Re-prepare instead of reusing handles after FREE.
When it happens
Trigger: FirebirdBatchCreateCommandExecutor.execute() reads packet.getStatementHandle() and looks up connectionSession.getServerPreparedStatementRegistry().getPreparedStatement(statementId); a null result (never prepared, already dropped/unprepared via FREE STATEMENT, or handle from a different connection) triggers the throw.
Common situations: client sends CREATE BATCH before PREPARE; handle reuse after the statement was freed; statement handle created on another connection in a connection pool that reuses handles across physical sessions; client bug mis-parsing the handle returned by the prepare response.
Related errors
- Batch already opened for statement handle %d
- Invalid batch handle: statement handle %d
- Invalid batch handle: statement handle %d
- Invalid statement handle: %d
- Unknown statement info request type %d
AI-assisted analysis of apache/shardingsphere@e952770a21 (2026-08-14).
Data as JSON: /api/errors/152735bf5712b202.
Report an issue: GitHub.