Skip to content

Dev - #67

Merged
erayhanoglu merged 7 commits into
mainfrom
dev
Sep 9, 2026
Merged

Dev#67
erayhanoglu merged 7 commits into
mainfrom
dev

Conversation

@erayhanoglu

Copy link
Copy Markdown
Member

No description provided.

erayhanoglu and others added 7 commits September 9, 2026 20:13
…tion

parseNegotiateProtocolVersion() read the server's count of unrecognized
startup options but then always read exactly one option-name string
regardless of that count - a count of 0 (version mismatch only, no
unrecognized options) or 2+ would desync the buffer offset and corrupt
every message parsed after it. It now reads exactly `count` strings into
NegotiateProtocolVersionMessage.unrecognizedOptions.

The message was also parsed but never dispatched anywhere, so a real
negotiation reply from the server was silently discarded. PgSocket now
stores it and emits a 'debug' event; IntlConnection and Connection each
expose it as a new readonly protocolNegotiation getter, mirroring the
existing processID/secretKey/sessionParameters delegation chain -
undefined means the server recognized everything this client's startup
packet asked for.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds Connection.callFunction(functionId, args, options?), calling a
function by OID directly instead of through SQL - superseded in current
PostgreSQL by SELECT func(...) over Simple/Extended Query, which is why
no other current client bothers with it, but it was one of the last
gaps in this driver's own wire-protocol coverage. Arguments and the
result are raw wire-format bytes rather than typed JS values, matching
the low-level nature of the protocol itself.

Also fixes a real parsing bug found while wiring this up:
parseFunctionCallResponse read `len - 4` bytes as the result directly,
but the result value has its own length prefix ahead of it (-1 for SQL
NULL, no bytes following in that case) - the old code folded that prefix
into the returned buffer as leading garbage and never supported NULL at
all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds DatabaseConnectionParams.longCancelKey (default false): requests
protocol 3.2 instead of 3.0, which lets the server hand out a
cancellation key up to 256 bytes instead of always exactly 4 - the short
key is brute-forceable by an attacker who wants to cancel another
session's query. Off by default, and never a connection-breaking choice
when on: an older server just replies with NegotiateProtocolVersion
naming the highest minor version it supports, and the session proceeds
there exactly as if longCancelKey had never been set.

BackendKeyData's secretKey is now a Buffer instead of a fixed-width
number - it has no length prefix of its own, so parseBackendKeyData
reads whatever bytes remain in the message (4 of them before 3.2, up to
256 with it). getCancelRequestMessage relays that Buffer back unchanged
rather than assuming 4 bytes. This is a breaking change to the public
secretKey getter on PgSocket/IntlConnection/Connection.

Verified against the real PostgreSQL 18 test server: longCancelKey:true
hands back a key longer than 4 bytes with no protocol negotiation
needed, and cancel() still genuinely interrupts a running query with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds details about new features like long cancellation keys, legacy function call protocol, and graceful protocol renegotiation. Updates version numbers in the feature table for PostgreJS 3.1.0.
… in CI

apt-get update fails outright if any configured repo's Release/Packages
files disagree, even one we never use - the runner image's preinstalled
Google Chrome apt source hit exactly that (a hash sum mismatch on
Google's end) and took the postgresql.org repo update down with it,
failing the job before PostgreSQL could even be installed. Removing the
unused repo first isolates the failure to repos we actually depend on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Protocol 3.2 (and its longer cancel key) only exists on PG 18+; older
servers gracefully negotiate back down to 3.0 and hand back the legacy
4-byte key by design, which was failing the assertion on CI's PG 12/16
jobs.
@erayhanoglu
erayhanoglu merged commit c8f1b82 into main Sep 9, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant