cube-js / cube-js/cube

bug(cubesql): Bind message still processed after Parse failure causes protocol desync with npgsql/.NET clients

Open
#10,962 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.