matrixorigin / matrixorigin/matrixone
[Bug]: COM_STMT_CLOSE on an absent statement sends a response and desynchronizes the next command
- Dominant language
- Go
- Stars
- 1.9k
- Forks
- 311
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 768
Description
### Is there an existing issue for the same bug?
- [x] I searched open and closed issues and fix PRs.
Related but not duplicates:
- #25978 / #26259 cover text-protocol `DEALLOCATE PREPARE` for an unknown name. This report is about binary-protocol `COM_STMT_CLOSE`, whose response contract is different.
- #25744 covers truncated `COM_STMT_CLOSE` / `COM_STMT_RESET` packets.
- #25423 covers normal binary Prepare execution and is already fixed.
### Branch Name
main
### Commit ID
`71031d0e9e45920821d71ce03f519181d346e669`
### Other Environment Information
- MatrixOne was built from the commit above.
- MySQL control: 8.0.46.
- JDBC clients: MySQL Connector/J 8.0.33 and 26.7.0.
- The deterministic protocol probe was repeated 3 times per endpoint on fresh connections.
### Actual Behavior
MatrixOne sends an error response for `COM_STMT_CLOSE` when the statement id is absent. The classic MySQL protocol defines `COM_STMT_CLOSE` as a no-response command, including the absent-id path.
This leaves an unexpected packet in the response stream because clients do not read a response for this command. A following command may consume that packet instead of its own response.
Deterministic two-command probe on a fresh connection:
1. Send `COM_STMT_CLOSE` with statement id `0`.
2. Send `COM_PING` without reading between the two commands.
3. Read the first response packet.
Results, 3/3:
```text
MatrixOne: first packet = ERR, code 20400
MySQL 8.0.46: first packet = OK for COM_PING
```
Every fresh MatrixOne connection still prepares and executes `SELECT ?` successfully, so the effect is isolated to the connection whose packet stream was shifted.
JDBC impact observed with Connector/J 26.7.0:
- A server-side `prepareStatement(...)` fails, for example because the SQL references an absent object.
- Connector/J cleans up its half-constructed server statement with `COM_STMT_CLOSE` and does not expect a response.
- A following ordinary or prepared statement can intermittently receive `HY000 / 20400`, reporting that internal prepared statement `__mo_stmt_id_0` does not exist.
- Connector/J 8.0.33 did not take the same cleanup path in this matrix; MySQL remained synchronized with both versions.
### Expected Behavior
`COM_STMT_CLOSE` must not send a response packet. An absent statement id should be handled silently on this binary command, while text SQL `DEALLOCATE PREPARE unknown_name` may continue returning MySQL error 1243.
MySQL's protocol source documents:
> `COM_STMT_CLOSE` deallocates a prepared statement. No response packet is sent back to the client. Return: None.
The MySQL server's absent-id branch for `COM_STMT_CLOSE` also goes to `silent_error`.
Primary references:
- https://github.com/mysql/mysql-server/blob/trunk/sql/protocol_classic.cc (`page_protocol_com_stmt_close`)
- https://github.com/mysql/mysql-server/blob/trunk/sql/sql_prepare.cc (`mysql_stmt_precheck`, `COM_STMT_CLOSE`)
### Root Cause
In `pkg/frontend/mysql_cmd_executor.go`, the `COM_STMT_CLOSE` branch calls `Session.GetPrepareStmt`. When the id is absent it returns a `GeneralErrorResponse`. The frontend then writes that error packet even though this protocol command must not produce any response.
The existing unit expectation for a missing binary close also treats an error response as correct, so the protocol mismatch is currently encoded in the test.
### Recovery and Consistency Checks
- Reproduced on the latest official `main` commit above.
- MatrixOne result was identical in 3/3 deterministic runs.
- MySQL control was identical in 3/3 runs and did not emit a close response.
- A fresh connection remained usable after every run.
- The probe performs no table writes, so there is no partial-data outcome.
Contributor guide
Assessment
This issue has not been assessed yet.