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
66 changes: 64 additions & 2 deletions .release-notes/next-release.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ be pg_query_failed(query: SimpleQuery,
After:

```pony
be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
match query
Expand All @@ -91,7 +91,7 @@ You can now create server-side named prepared statements with `Session.prepare()
session.prepare("find_user", "SELECT * FROM users WHERE id = $1", receiver)

// In the PrepareReceiver callback:
be pg_statement_prepared(name: String) =>
be pg_statement_prepared(session: Session, name: String) =>
// Execute with different parameters
session.execute(
NamedPreparedQuery("find_user",
Expand Down Expand Up @@ -141,3 +141,65 @@ let session = Session(

If the server accepts SSL, the connection is encrypted before authentication begins. If the server refuses, `pg_session_connection_failed` fires.

## Change ResultReceiver and PrepareReceiver callbacks to take Session as first parameter

All `ResultReceiver` and `PrepareReceiver` callbacks now take `Session` as their first parameter, matching the convention used by `SessionStatusNotify`. This enables receivers to execute follow-up queries directly from callbacks without storing a session reference (see "Enable follow-up queries from ResultReceiver and PrepareReceiver callbacks" below).

Before:

```pony
be pg_query_result(result: Result) =>
// ...

be pg_query_failed(query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
// ...

be pg_statement_prepared(name: String) =>
// ...

be pg_prepare_failed(name: String,
failure: (ErrorResponseMessage | ClientQueryError))
=>
// ...
```

After:

```pony
be pg_query_result(session: Session, result: Result) =>
// ...

be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
// ...

be pg_statement_prepared(session: Session, name: String) =>
// ...

be pg_prepare_failed(session: Session, name: String,
failure: (ErrorResponseMessage | ClientQueryError))
=>
// ...
```

## Enable follow-up queries from ResultReceiver and PrepareReceiver callbacks

`ResultReceiver` and `PrepareReceiver` callbacks now receive the `Session`, so receivers can execute follow-up queries, close the session, or chain operations directly from callbacks without needing to store a session reference at construction time.

```pony
actor MyReceiver is ResultReceiver
// no need to store session — it's passed to every callback

be pg_query_result(session: Session, result: Result) =>
// execute a follow-up query using the session from the callback
session.execute(SimpleQuery("SELECT 1"), this)

be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
session.close()
```

2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ All notable changes to this project will be documented in this file. This projec
- Add parameterized queries via extended query protocol ([PR #70](https://github.com/ponylang/postgres/pull/70))
- Add named prepared statement support ([PR #78](https://github.com/ponylang/postgres/pull/78))
- Add SSL/TLS negotiation support ([PR #79](https://github.com/ponylang/postgres/pull/79))
- Enable follow-up queries from ResultReceiver and PrepareReceiver callbacks ([PR #84](https://github.com/ponylang/postgres/pull/84))

### Changed

- Change ResultReceiver and Result to use Query union type instead of SimpleQuery ([PR #70](https://github.com/ponylang/postgres/pull/70))
- Change ResultReceiver and PrepareReceiver callbacks to take Session as first parameter ([PR #84](https://github.com/ponylang/postgres/pull/84))
- Fix typo in SesssionNeverOpened ([PR #59](https://github.com/ponylang/postgres/pull/59))

## [0.2.2] - 2025-07-16
Expand Down
5 changes: 2 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ Only one operation is in-flight at a time. The queue serializes execution. `quer
- `Rows` / `Row` / `Field` — result data. `Field.value` is `FieldDataTypes` union
- `FieldDataTypes` = `(Bool | F32 | F64 | I16 | I32 | I64 | None | String)`
- `SessionStatusNotify` interface (tag) — lifecycle callbacks (connected, connection_failed, authenticated, authentication_failed, shutdown)
- `ResultReceiver` interface (tag) — `pg_query_result(Result)`, `pg_query_failed(Query, (ErrorResponseMessage | ClientQueryError))`
- `PrepareReceiver` interface (tag) — `pg_statement_prepared(name)`, `pg_prepare_failed(name, (ErrorResponseMessage | ClientQueryError))`
- `ResultReceiver` interface (tag) — `pg_query_result(Session, Result)`, `pg_query_failed(Session, Query, (ErrorResponseMessage | ClientQueryError))`
- `PrepareReceiver` interface (tag) — `pg_statement_prepared(Session, name)`, `pg_prepare_failed(Session, name, (ErrorResponseMessage | ClientQueryError))`
- `ClientQueryError` trait — `SessionNeverOpened`, `SessionClosed`, `SessionNotAuthenticated`, `DataError`
- `SSLMode` — union type `(SSLDisabled | SSLRequired)`. `SSLDisabled` is the default (plaintext). `SSLRequired` wraps an `SSLContext val` for TLS negotiation.
- `ErrorResponseMessage` — full PostgreSQL error with all standard fields
Expand Down Expand Up @@ -147,7 +147,6 @@ Test helpers: `_ConnectionTestConfiguration` reads env vars with defaults. Sever

- `rows.pony:43` — TODO: need tests for Rows/Row/Field (requires implementing `eq`)
- `_test_response_parser.pony:6` — TODO: chain-of-messages tests to verify correct buffer advancement across message sequences
- `result_receiver.pony:1` — TODO: consider passing session to result callbacks so receivers without a session tag can execute follow-up queries

## Roadmap

Expand Down
4 changes: 2 additions & 2 deletions examples/crud/crud-example.pony
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
=>
_out.print("Failed to authenticate.")

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
_phase = _phase + 1

match _phase
Expand Down Expand Up @@ -135,7 +135,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
_out.print(r.command() + " " + r.impacted().string() + " rows")
end

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
match failure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ actor Client is (SessionStatusNotify & ResultReceiver & PrepareReceiver)
=>
_out.print("Failed to authenticate.")

be pg_statement_prepared(name: String) =>
be pg_statement_prepared(session: Session, name: String) =>
_out.print("Statement '" + name + "' prepared.")
// Execute the same prepared statement with different parameters.
_session.execute(
Expand All @@ -56,13 +56,13 @@ actor Client is (SessionStatusNotify & ResultReceiver & PrepareReceiver)
recover val [as (String | None): "Hi"; "World"] end),
this)

be pg_prepare_failed(name: String,
be pg_prepare_failed(session: Session, name: String,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_out.print("Failed to prepare statement '" + name + "'.")
close()

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
match result
| let r: ResultSet =>
_out.print("ResultSet (" + r.rows().size().string() + " rows):")
Expand Down Expand Up @@ -93,7 +93,7 @@ actor Client is (SessionStatusNotify & ResultReceiver & PrepareReceiver)
close()
end

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_out.print("Query failed.")
Expand Down
4 changes: 2 additions & 2 deletions examples/prepared-query/prepared-query-example.pony
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
=>
_out.print("Failed to authenticate.")

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
match result
| let r: ResultSet =>
_out.print("ResultSet (" + r.rows().size().string() + " rows):")
Expand All @@ -73,7 +73,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
end
close()

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_out.print("Query failed.")
Expand Down
4 changes: 2 additions & 2 deletions examples/query/query-example.pony
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
=>
_out.print("Failed to authenticate.")

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
match result
| let r: ResultSet =>
_out.print("ResultSet (" + r.rows().size().string() + " rows):")
Expand All @@ -68,7 +68,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
end
close()

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_out.print("Query failed.")
Expand Down
4 changes: 2 additions & 2 deletions examples/ssl-query/ssl-query-example.pony
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
=>
_out.print("Failed to authenticate.")

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
match result
| let r: ResultSet =>
_out.print("ResultSet (" + r.rows().size().string() + " rows):")
Expand All @@ -93,7 +93,7 @@ actor Client is (SessionStatusNotify & ResultReceiver)
end
close()

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_out.print("Query failed.")
Expand Down
12 changes: 6 additions & 6 deletions postgres/_test.pony
Original file line number Diff line number Diff line change
Expand Up @@ -380,11 +380,11 @@ actor \nodoc\ _DoesntAnswerClient is (SessionStatusNotify & ResultReceiver)
_h.fail("Unable to authenticate.")
_h.complete(false)

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
_h.fail("Unexpectedly got a result for a query.")
_h.complete(false)

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
if _in_flight_queries.contains(query) then
Expand Down Expand Up @@ -527,7 +527,7 @@ actor \nodoc\ _ZeroRowSelectTestClient is (SessionStatusNotify & ResultReceiver)
_h.fail("Unable to authenticate.")
_h.complete(false)

be pg_query_result(result: Result) =>
be pg_query_result(session: Session, result: Result) =>
if result.query() isnt _query then
_h.fail("Query in result isn't the expected query.")
_close_and_complete(false)
Expand All @@ -554,7 +554,7 @@ actor \nodoc\ _ZeroRowSelectTestClient is (SessionStatusNotify & ResultReceiver)

_close_and_complete(true)

be pg_query_failed(query: Query,
be pg_query_failed(session: Session, query: Query,
failure: (ErrorResponseMessage | ClientQueryError))
=>
_h.fail("Unexpected query failure.")
Expand Down Expand Up @@ -692,11 +692,11 @@ actor \nodoc\ _PrepareShutdownTestClient is
_h.fail("Unable to authenticate.")
_h.complete(false)

be pg_statement_prepared(name: String) =>
be pg_statement_prepared(session: Session, name: String) =>
_h.fail("Unexpectedly got a prepared statement.")
_h.complete(false)

be pg_prepare_failed(name: String,
be pg_prepare_failed(session: Session, name: String,
failure: (ErrorResponseMessage | ClientQueryError))
=>
match failure
Expand Down
Loading