Design: LISTEN/NOTIFY Support #103
Replies: 2 comments
-
|
Two notes from review before implementation: 1. Parser should isolate the payload before parsing (will fix) The proposed parser code reads variable-length strings directly from the shared buffer via Will follow the established pattern: extract with 2. After the change, this test has ParameterStatus (skipped), NoticeResponse (skipped), NotificationResponse (now parsed as |
Beta Was this translation helpful? Give feedback.
-
|
Implemented in PR #108 (merged). The two findings from the earlier review comment were addressed in the implementation. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Add support for PostgreSQL's LISTEN/NOTIFY asynchronous notification mechanism. Users subscribe to channels via regular queries (
LISTEN channel) and receive notifications via a new callback onSessionStatusNotify.Background
PostgreSQL allows connections to subscribe to named channels with
LISTEN channel_nameand send notifications withNOTIFY channel_name, 'payload'. Notifications are delivered asynchronously -- they arrive in the message stream between query cycles, specifically just beforeReadyForQuery. A connection can receive notifications it sent to itself.Wire Protocol
NotificationResponse (
'A'):Byte1('A') Int32(length) Int32(pid) String(channel) String(payload)pid: Process ID of the notifying backendchannel: Null-terminated channel namepayload: Null-terminated payload string (can be empty)NotificationResponse is an asynchronous message that can arrive between any other messages, though PostgreSQL delivers pending notifications just before
ReadyForQuery.Current Handling
The parser (
_response_parser.ponyline 214-217) matches NotificationResponse and returns_SkippedMessage-- the payload is discarded without parsing. The router (_response_message_parser.ponyline 56-59) silently ignores_SkippedMessage. A test message builder (_IncomingNotificationResponseTestMessage) already exists in_test_response_parser.ponyand correctly constructs the wire format.Design
New Public Type
notification.pony:New Internal Message Type
Add to
_backend_messages.pony:Add
_NotificationResponseMessageto the_ResponseParserResultunion type.Parser Change
In
_response_parser.pony, replace the skip-and-return-_SkippedMessagecase with actual parsing:Update the
_SkippedMessagedocstring to remove NotificationResponse from the list -- it now only covers ParameterStatus and NoticeResponse.Router Change
In
_response_message_parser.pony, add a match clause before the_SkippedMessagecase:Update the
_SkippedMessagecomment to remove NotificationResponse from the list.New Callback
Add to
SessionStatusNotify:Non-breaking -- default body means existing implementations don't need to change.
State Machine Changes
Add
on_notificationto_SessionStateinterface:Default in
_NotAuthenticatedtrait (notifications cannot arrive before auth):Implementation in
_SessionLoggedIn:Callback Timing
Notifications are delivered inline during message parsing. Since callbacks are
be(behavior sends), they are queued and don't block the parser loop. WhenSessionStatusNotifyandResultReceiverare the same actor, the delivery order matches the wire protocol order. For example, if a notification arrives betweenCommandCompleteandReadyForQuery,pg_query_resultis delivered first, thenpg_notification.Files Changed
postgres/notification.pony--Notificationval classpostgres/_backend_messages.pony-- add_NotificationResponseMessageclasspostgres/_response_parser.pony-- parse NotificationResponse instead of skipping; add to_ResponseParserResultunion; update_SkippedMessagedocstringpostgres/_response_message_parser.pony-- route_NotificationResponseMessageto session state; update_SkippedMessagecommentpostgres/session_status_notify.pony-- addpg_notificationcallbackpostgres/session.pony-- addon_notificationto_SessionStateinterface; default_IllegalState()in_NotAuthenticated; implement in_SessionLoggedInpostgres/_test_response_parser.pony-- update_TestResponseParserNotificationResponseSkippedto verify parsing into_NotificationResponseMessagewith correct fields (rename to_TestResponseParserNotificationResponseMessage); update_TestResponseParserMultipleMessagesSkippedFirstto expect_NotificationResponseMessageinstead of_SkippedMessagefor the notification entrypostgres/_test_notification.pony-- mock server notification testspostgres/_test.pony-- register new test classesCLAUDE.md-- update architecture (async message handling), test organization (new file), public API types (Notification), file layout (new files), supported featuresTests
Parser Unit Tests
In
_test_response_parser.pony:_TestResponseParserNotificationResponseMessage(renamed from_TestResponseParserNotificationResponseSkipped) -- parse NotificationResponse bytes and verify_NotificationResponseMessagehas correctprocess_id,channel, andpayloadfields. Include a second case with empty payload to verifyread_until(0)handles immediately null-terminated strings._TestResponseParserMultipleMessagesSkippedFirst-- update to expect_NotificationResponseMessagefor the notification entry instead of_SkippedMessage. Verify the other async messages (ParameterStatus, NoticeResponse) still return_SkippedMessage.Unit Tests (Mock Servers)
New test file:
postgres/_test_notification.ponyMock port assignments: 7686, 7687
_TestNotificationDelivery(port 7686) -- mock server authenticates, sends ReadyForQuery('I'). Client sends a query. Server responds with CommandComplete + NotificationResponse(pid=42, channel="test_ch", payload="hello") + ReadyForQuery('I'). Client verifiespg_notificationfires with correctNotificationfields (channel, payload, pid)._TestNotificationDuringDataRows(port 7687) -- mock server authenticates, sends ReadyForQuery('I'). Client sends a query. Server responds with RowDescription + DataRow + NotificationResponse(pid=1, channel="ch", payload="mid-query") + DataRow + CommandComplete + ReadyForQuery('I'). Client verifies both the query result (with both data rows) and the notification are delivered.Integration Test
Added to
postgres/_test_query.pony:_TestListenNotify-- executeLISTEN test_channel_XX(with a unique suffix to avoid interference from other tests), then executeNOTIFY test_channel_XX, 'hello'. Verifypg_notificationfires with channel matching and payload "hello". Clean up withUNLISTEN test_channel_XX.Existing Tests
No regressions expected. The parser change converts NotificationResponse from
_SkippedMessageto_NotificationResponseMessage, but_ResponseMessageParsernow routes it toon_notificationinstead of ignoring it. Existing tests don't involve NotificationResponse in their message streams, so behavior is unchanged. The two parser tests that reference NotificationResponse are updated explicitly.Example
New example:
examples/listen-notify/listen-notify-example.ponyDemonstrates:
LISTEN channel_nameNOTIFY channel_name, 'payload'pg_notificationcallbackUNLISTEN channel_nameBuild/Run Commands
Beta Was this translation helpful? Give feedback.
All reactions