bug(cubesql): Bind message still processed after Parse failure causes protocol desync with npgsql/.NET clients
- Dominant language
- Rust
- Stars
- 20.8k
- Forks
- 2.1k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 181
Description
## Bug Report
### Description
When a query fails during the `Parse` stage of the PostgreSQL extended query protocol (e.g., bad syntax, invalid column), the `Bind` message that follows is still processed and sends a `BindComplete` response to the client. Since the client never received a `ParseComplete` (because Parse failed), receiving `BindComplete` causes a protocol desynchronization.
This manifests in npgsql (.NET) clients as:
```
Received backend message BindComplete while expecting ParseCompleteMessage. Please file a bug.
```
### Root Cause
In `rust/cubesql/cubesql/src/sql/postgres/shim.rs`, the extended query message loop correctly tracks errors via `tracked_error` and skips subsequent `Execute`, `Close`, and `Describe` messages when an error is present:
```rust
// Execute handler (line ~320) - correctly skips
protocol::FrontendMessage::Execute(body) => {
if tracked_error.is_some() {
// ... logs, then continues
continue;
}
// ...
}
// Close handler (line ~394) - correctly skips
protocol::FrontendMessage::Close(body) => {
if tracked_error.is_none() {
self.close(body).await
} else {
continue;
}
}
```
However, the **Bind handler (line ~308) does NOT skip execution** when `tracked_error` is set:
```rust
protocol::FrontendMessage::Bind(body) => {
if tracked_error.is_none() {
doing_extended_query_message = true;
}
// Still proceeds to execute bind even when tracked_error is Some!
let span_id = { ... };
self.bind(body, span_id).await // <-- sends BindComplete to client
}
```
It only skips setting `doing_extended_query_message`, but still calls `self.bind()`, which writes `BindComplete` to the wire. The client, expecting `ParseComplete` (or `ErrorResponse`), receives an unexpected `BindComplete` and treats it as a protocol violation.
### Expected Behavior
Per the [PostgreSQL protocol documentation](https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-EXT-QUERY):
> If the Parse step fails, the backend should not process any Bind, Execute, or other extended-query messages until a Sync is received.
The Bind handler should skip processing (same as Execute/Close/Describe) when `tracked_error` is set, so that the error is only reported when `Sync` arrives.
### Suggested Fix
Add `continue` to the Bind handler when a tracked error exists:
```rust
protocol::FrontendMessage::Bind(body) => {
if tracked_error.is_some() {
continue;
}
doing_extended_query_message = true;
let span_id = { ... };
self.bind(body, span_id).await
}
```
### Steps to Reproduce
1. Connect to the Cube SQL API (port 15432) using an npgsql/.NET client
2. Send a query with an invalid column name or bad syntax (e.g., `SELECT nonexistent_column FROM some_cube`)
3. npgsql throws: `Received backend message BindComplete while expecting ParseCompleteMessage`
### Environment
- Cube version: v1.6.29 (confirmed also present on `main` branch as of 2026-05-28)
- Client: npgsql (.NET PostgreSQL driver)
- Connection via SQL API on port 15432
### Additional Context
- The `tracked_error` pattern was introduced to handle the PostgreSQL extended query protocol error flow correctly
- The fix for Execute (`continue` when error tracked) works correctly
- The fix for Close and Describe (`continue` when error tracked) also works correctly
- Only the Bind handler is missing this guard
- This is the same class of protocol violation documented in [CockroachDB issue #103936](https://github.com/cockroachdb/cockroach/issues/103936)
- npgsql strictly enforces message ordering per the PostgreSQL protocol spec
Contributor guide
Assessment
This issue has not been assessed yet.