{"record":{"id":"fe3b3aa17797e651","repo":"apache/shardingsphere","slug":"invalid-batch-handle-statement-handle-d","errorCode":null,"errorMessage":"Invalid batch handle: statement handle %d","messagePattern":"Invalid batch handle: statement handle (.+?)","errorType":"exception","errorClass":"InvalidBatchHandleException","httpStatus":null,"severity":"error","filePath":"database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java","lineNumber":96,"sourceCode":"     */\n    public static int getLength(final FirebirdPacketPayload payload, final List<FirebirdBatchColumnDescriptor> columnDescriptors) {\n        int startReaderIndex = payload.getByteBuf().readerIndex();\n        if (payload.getByteBuf().readableBytes() < FIXED_BATCH_MSG_HEADER_LENGTH) {\n            return -1;\n        }\n        payload.skipReserved(8);\n        return parseBatchMessages(payload, startReaderIndex, payload.readInt4Unsigned(), columnDescriptors);\n    }\n    \n    private static int getLength(final FirebirdPacketPayload payload, final int connectionId, final int startReaderIndex, final int availableBytes) {\n        if (availableBytes < FIXED_BATCH_MSG_HEADER_LENGTH) {\n            return -1;\n        }\n        payload.skipReserved(4);\n        int statementHandle = payload.readInt4();\n        FirebirdBatchStatement batchStatement = FirebirdBatchRegistry.getInstance().getBatchStatement(connectionId, statementHandle);\n        if (null == batchStatement) {\n            throw new InvalidBatchHandleException(statementHandle);\n        }\n        return parseBatchMessages(payload, startReaderIndex, payload.readInt4Unsigned(), batchStatement.getColumnDescriptors());\n    }\n    \n    private static int parseBatchMessages(final FirebirdPacketPayload payload, final int startReaderIndex, final long messageCount,\n                                          final List<FirebirdBatchColumnDescriptor> columnDescriptors) {\n        for (long each = 0; each < messageCount; each++) {\n            int messageStartIndex = payload.getByteBuf().readerIndex();\n            try {\n                parseSingleMessage(payload, columnDescriptors);\n                payload.skipPadding(payload.getByteBuf().readerIndex() - messageStartIndex);\n                // CHECKSTYLE:OFF\n            } catch (final IndexOutOfBoundsException ex) {\n                // CHECKSTYLE:ON\n                payload.getByteBuf().readerIndex(messageStartIndex);\n                return -1;\n            }\n        }","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/apache/shardingsphere/blob/e952770a215630a3659c75d64369168cd3e26b82/database/protocol/dialect/firebird/src/main/java/org/apache/shardingsphere/database/protocol/firebird/packet/command/query/batch/FirebirdBatchMessageCommandPacket.java#L78-L114","documentation":"IllegalStateException thrown by Portal.describe() in the PostgreSQL extended-protocol path when a Describe ('P') message arrives before the portal has a bound response header. A portal only gets its QueryResponseHeader or UpdateResponseHeader during Bind; describing before that leaves the header null, and the switch on instanceof falls through to the exception naming the portal.","triggerScenarios":"Sending PostgreSQL extended-protocol message sequence Describe(portal) before Bind for that portal, e.g. Parse; Describe; Sync; or re-describing a portal after it was closed and re-created by Parse without a fresh Bind.","commonSituations":"Hand-rolled extended-protocol clients or ORMs issuing wrong message ordering; drivers with a bug in pipelining that reorder Describe before Bind; retry logic that replays Parse+Describe but skips Bind; edge behavior after server-side errors that reset portal state.","solutions":["Fix the client's message order: always Bind the portal (Bind → Execute/Describe) before issuing Describe for it.","If describing a statement's parameter/row formats is the goal, Describe the prepared statement ('S') instead of the portal ('P') before Bind.","Update the driver/ORM version if the wrong ordering comes from its extended-protocol implementation.","Add a protocol trace (e.g. psqlodbc log, pgjdbc logger, or Wireshark on Postgres protocol) to confirm Describe precedes Bind, then correct the sequencing."],"exampleFix":"-- before (extended protocol, wrong order)\nParse(stmt='', sql)\nDescribe(portal='')   -- no Bind yet -> IllegalStateException\n\n-- after\nParse(stmt='', sql)\nBind(stmt='', portal='')\nDescribe(portal='')","handlingStrategy":"validation","validationCode":"// Custom extended-protocol client: enforce Bind-before-Describe per portal\nif (!boundPortals.contains(portalName)) {\n    throw new IllegalStateException(\"Portal '\" + portalName + \"' must be bound before Describe\");\n}\nsendDescribe('P', portalName);","typeGuard":null,"tryCatchPattern":"// IllegalStateException arrives as a proxy error response; recover by rebinding\ntry {\n    portal.describe();\n} catch (IllegalStateException e) {\n    if (e.getMessage().contains(\"before bind\")) {\n        bindPortal(portalName, statementName, params); // re-Bind, then retry Describe\n        return portal.describe();\n    }\n    throw e;\n}","preventionTips":["Model the extended-protocol state machine explicitly: Parse → Bind → (Describe|Execute) → Sync.","Track bound portals client-side; clear tracking when portals are closed.","Describe 'S' (statement) when you need metadata before binding."],"tags":["postgresql","extended-protocol","portal","bind","state-machine"],"backgroundTag":null,"analyzedSha":"e952770a215630a3659c75d64369168cd3e26b82","analyzedAt":"2026-08-14T13:54:53.392Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}