Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,40 @@ Versioning](https://semver.org/spec/v2.0.0.html).
by PostgreSQL 18+ and the minor-only form used by older servers) and adopts
the negotiated version for the rest of the connection.

### Fixed
### Changed

- Breaking: `QueryParser::parse_sql` now returns
`PgWireResult<Option<Self::Statement>>`, and `StoredStatement::parse`
correspondingly returns `Option<StoredStatement<S>>`. `None` denotes an
empty query: it is stored as an empty statement and executes to
`EmptyQueryResponse`. This allows a query parser to report its own notion
of empty query; syntactically empty queries (semicolons and whitespace
only) are still never passed to the parser.
- Breaking: `PortalStore` now represents empty statements and portals.
`get_statement` returns `Option<Entry<StoredStatement<S>>>` and
`get_portal` returns `Option<Entry<Portal<S>>>`: the new `Entry::Empty`
variant marks a name under which an empty prepared statement or portal is
stored, alongside the new `put_empty_statement`/`put_empty_portal`
methods. Like every `put_*`, storing an empty entry replaces whatever was
previously stored under that name, and `rm_*`/`clear_portals` remove
empty entries along with regular ones. `StoredStatement` and `Portal`
themselves are unchanged — the impact is limited to `PortalStore`
implementors and code calling `get_statement`/`get_portal` directly
(`Entry::value` helps with the migration).

### Fixed

- Extended query protocol: empty queries (a query string without any
statement, such as `""` or `";;"`) are now handled like PostgreSQL instead
of being dispatched to the query parser: `Parse` succeeds without calling
`QueryParser` and stores an empty statement, `Describe` answers
`ParameterDescription` (no parameters) + `NoData`, `Bind` succeeds
(rejecting bound parameters with `08P01`) and stores an empty portal, and
`Execute` returns `EmptyQueryResponse` without reaching `do_query`. An
empty `Parse` replaces any statement previously stored under the same
name, and `Close`/`Sync` drop empty statements and the unnamed empty
portal like real ones. Behavior verified message-for-message against
PostgreSQL 18.
- Client API: backend messages are now decoded with the rules of the protocol
version the client actually advertised, instead of always 3.2. Previously a
4-byte protocol 3.0 cancel key was decoded as `SecretKey::Bytes` instead of
Expand Down
4 changes: 2 additions & 2 deletions examples/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use pgwire::api::portal::Portal;
use pgwire::api::query::SimpleQueryHandler;
use pgwire::api::results::{DataRowEncoder, FieldFormat, FieldInfo, QueryResponse, Response, Tag};
use pgwire::api::stmt::StoredStatement;
use pgwire::api::store::{MemPortalStore, PortalStore};
use pgwire::api::store::{Entry, MemPortalStore, PortalStore};
use pgwire::api::{ClientInfo, ClientPortalStore, PgWireServerHandlers, Type};
use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
use pgwire::messages::response::NoticeResponse;
Expand Down Expand Up @@ -210,7 +210,7 @@ async fn handle_fetch(
) -> PgWireResult<Vec<Response>> {
println!("FETCH {} FROM {}", count, cursor_name);

let Some(portal) = portal_store.get_portal(cursor_name) else {
let Some(Entry::Value(portal)) = portal_store.get_portal(cursor_name) else {
return Err(PgWireError::UserError(Box::new(ErrorInfo::new(
"ERROR".to_owned(),
"34000".to_owned(),
Expand Down
Loading
Loading