Sans-I/O connection protocol engine: backend bytes in → high-level events + outbound bytes.
SimpleQueryProtocolis the pure state machine that drives one PostgreSQL connection.
It owns the inbound byte buffer, drains complete messages withparse_message()
(carrying partial reads forward), and folds the wire-levelPGMessagestream into a
small set of high-levelProtocolEventobjects. Outbound work goes the other way: the
send_* helpers return frontend bytes (via ._builder) and advance the state, but the
engine never touches a socket — connection / pool own anyio I/O in a later epic. This keeps
the protocol fuzzable in isolation and parallelizable across free-threaded workers.
Two engines live here.SimpleQueryProtocol(E3 core) models the simple-query
protocol: STARTUP/auth → READY, then oneQueryin flight (RowDescription → DataRow* →
CommandComplete → ReadyForQuery) and back to READY.ExtendedQueryProtocolmodels the
extended-query path — Parse/Bind/Describe/Execute/Sync with$Nparameters — plus a
single-ownerPreparedStatementCache(LRU, bounded, clean-miss on plan invalidation)
and server-side cursors (anExecute row limit yields PortalSuspended; resume()
fetches the next batch). Cancellation lands in a later epic. Raw column bytes are passed
through untouched — decoding against the codec plan is the next layer's job; both engines only
pair eachDataRowEvent with its RowDescription.
Desync discipline: any message that is illegal for the current state raises
ProtocolError. parse_message() already raises ProtocolErroron malformed
bytes, so a corrupt object is never handed back; a desynced connection is unrecoverable and
must be discarded by the I/O layer.
data.drivers._pelt._protocol
| Name | Type | Default | Description |
|---|---|---|---|
type
|
|
— | |
qualified_name
|
|
— | |
element_type
|
|
— | |
description
|
|
— | |
source_file
|
|
— | |
line_number
|
|
— | |
is_autodoc
|
|
— | |
autodoc_element
|
|
— | |
_autodoc_template
|
|
— | |
_autodoc_url_path
|
|
— | |
_autodoc_page_type
|
|
— | |
title
|
|
— | |
doc_content_hash
|
|
— |
Symbols on this page
The connection's wire-protocol phase.
The simple-query lifecycle isSTARTUP → AUTHENTICATING → READY ⇄ BUSY. READY
tracks the most recentReadyForQuerytransaction status; …
The backend's transaction state, reported by everyReadyForQuery.
AuthenticationOkwas received — credentials accepted, session setup begins.
The backend requested authentication material the I/O layer must answer.
requestis the originating auth message (cleartext / MD5 / SASL*); the auth layer…
ReadyForQuery— the backend is idle and accepting a new command.
The column layout for the rows that follow within the current query.
One result row, paired with theRowDescriptionit belongs to.
row.values are still raw wire bytes (or Nonefor SQL NULL); decoding against the…
A command in the current query finished.tag is e.g. 'SELECT 2' / 'INSERT 0 1'.
The submitted query string was empty (EmptyQueryResponse).
A serverErrorResponse mapped onto a PostgresError.
The engine surfaces the error rather than raising it so the I/O layer decides whether to…
A server runtime parameter report (e.g.server_version), deliverable any time.
Cancellation key material (PID + secret), captured during session setup.
A non-fatal server notice, deliverable any time.fields are (code, value)pairs.
An asyncNOTIFYpayload (LISTEN/NOTIFY), deliverable any time.
ParseComplete — the Parsewas accepted and the named statement now exists.
BindComplete— parameters were bound and the named portal now exists.
CloseComplete — the backend dropped the named statement or portal a Closetargeted.
The acknowledgement of aClose request (the orphaned pelt_stmt_Nstatements that
…
ParameterDescription — the OIDs the server inferred / accepted for $Nparams.
Surfaced after aDescribeof a statement; the cache stores these so…
NoData — the described statement/portal returns no rows (e.g. an INSERT).
PortalSuspended — an Executerow limit was reached and more rows remain.
The portal is left open; the I/O layer resumes by issuing another …
Map a backendErrorResponse onto a PostgresError, pulling the standard
fields off the message's properties. The SQLSTATE becomes the error's stablePELT_PG_*code;…
A single PostgreSQL connection's sans-I/O protocol state machine.
Per-connection state is single-owner (one engine per connection, never shared between threads), so it is mutable…
A cache entry: a server-side prepared statement and the plan it was prepared against.
param_oids is the explicit OID list sent in the Parse…
A bounded, per-connection LRU cache of prepared statements.
Single-owner, lock-free by design. One cache belongs to one
ExtendedQueryProtocol, which belongs to one connection,…
The extended-query (Parse/Bind/Describe/Execute/Sync) state machine for one connection.
Single-owner and lock-free for the same reason asSimpleQueryProtocol: one engine per connection, never shared.…
ProtocolState
class
The connection's wire-protocol phase.
The simple-query lifecycle isSTARTUP → AUTHENTICATING → READY ⇄ BUSY. READY
tracks the most recentReadyForQuery transaction status; BUSY means a Queryis
in flight and the engine is folding its result stream.CLOSEDis reached after the
backend stops talking (gracefulTerminateis initiated by the I/O layer).
TransactionStatus
class
The backend's transaction state, reported by everyReadyForQuery.
AuthOkEvent
class
AuthenticationOkwas received — credentials accepted, session setup begins.
AuthRequestEvent
class
The backend requested authentication material the I/O layer must answer.
requestis the originating auth message (cleartext / MD5 / SASL*); the auth layer
(epic E4) inspects it and replies with aPasswordMessage.
ReadyEvent
class
ReadyForQuery— the backend is idle and accepting a new command.
RowDescriptionEvent
class
The column layout for the rows that follow within the current query.
DataRowEvent
class
One result row, paired with theRowDescriptionit belongs to.
row.values are still raw wire bytes (or Nonefor SQL NULL); decoding against the
codec plan happens in the next layer.description is never None — a DataRow
with no precedingRowDescription is a desync and raises ProtocolError.
CommandCompleteEvent
class
A command in the current query finished.tag is e.g. 'SELECT 2' / 'INSERT 0 1'.
EmptyQueryEvent
class
The submitted query string was empty (EmptyQueryResponse).
ErrorEvent
class
A serverErrorResponse mapped onto a PostgresError.
The engine surfaces the error rather than raising it so the I/O layer decides whether to
raise (the common case) or log; either way the trailingReadyForQueryresynchronizes
the connection.
ParameterStatusEvent
class
A server runtime parameter report (e.g.server_version), deliverable any time.
BackendKeyDataEvent
class
Cancellation key material (PID + secret), captured during session setup.
NoticeEvent
class
A non-fatal server notice, deliverable any time.fields are (code, value)pairs.
NotificationEvent
class
An asyncNOTIFYpayload (LISTEN/NOTIFY), deliverable any time.
ParseCompleteEvent
class
ParseComplete — the Parsewas accepted and the named statement now exists.
BindCompleteEvent
class
BindComplete— parameters were bound and the named portal now exists.
CloseCompleteEvent
class
CloseComplete — the backend dropped the named statement or portal a Closetargeted.
The acknowledgement of aClose request (the orphaned pelt_stmt_Nstatements that
drain_pending_close() surfaces are closed this way). It carries
no payload and never desyncs: aCloseComplete is always a legal Closeack, so the
engine surfaces it rather than treating it as an illegal message.
ParameterDescriptionEvent
class
ParameterDescription — the OIDs the server inferred / accepted for $Nparams.
Surfaced after aDescribeof a statement; the cache stores these so a later prepare of
the same SQL with the same explicit param OIDs is a clean hit (and a different set is a
clean miss → re-prepare).
NoDataEvent
class
NoData — the described statement/portal returns no rows (e.g. an INSERT).
PortalSuspendedEvent
class
PortalSuspended — an Executerow limit was reached and more rows remain.
The portal is left open; the I/O layer resumes by issuing anotherExecuteon the same
portal (seeresume_execute()). This is the server-side cursor /
prefetch-batching primitive that powersDatabase.stream().
map_error_response
function
def map_error_response(error: ErrorResponse) -> PostgresError
Map a backendErrorResponse onto a PostgresError, pulling the standard
fields off the message's properties. The SQLSTATE becomes the error's stablePELT_PG_*
code; the serverHint rides onto hint.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
error
|
ErrorResponse
|
— |
SimpleQueryProtocol
class
A single PostgreSQL connection's sans-I/O protocol state machine.
Per-connection state is single-owner (one engine per connection, never shared between
threads), so it is mutable and lock-free by construction — the free-threading discipline
for owned state. Feed inbound bytes withreceive_bytes() (any chunking, including one
byte at a time, is fine — partial reads are buffered); drive outbound work with the
send_*helpers, which return frontend bytes and advance the state.
PreparedStatement
class
A cache entry: a server-side prepared statement and the plan it was prepared against.
param_oids is the explicit OID list sent in the Parse(empty when the server was
asked to infer).resolved_param_oidsis what the backend reported back in its
ParameterDescription (None until a Describeresolves it). A later prepare whose
resolved OIDs disagree is a type mismatch — the entry is evicted and re-prepared rather
than reusing a stale plan.
PreparedStatementCache
class
A bounded, per-connection LRU cache of prepared statements.
Single-owner, lock-free by design. One cache belongs to one
ExtendedQueryProtocol, which belongs to one connection, which is never shared
between threads (the free-threading discipline for owned state — cf.SimpleQueryProtocol).
The codec registry is the shared-mutable structure that needs a lock; this cache is not.
Putting a lock here would be cargo-culted overhead on a hot path that is provably single-owner.
Keyed by(sql, tuple(param_oids))so the same SQL prepared with different explicit
parameter types gets distinct plans. Bounded bysize; reaching capacity evicts the
least-recently-used entry (LRU via insertion-orderedOrderedDict,
moved-to-end on every hit).size == 0disables caching entirely — every lookup misses
and nothing is stored.
Plan invalidation is a clean miss, never a stale reuse: aget() against a stored
entry whoseversionis older than the cache's current generation, or whose resolved
parameter OIDs disagree with the caller's expectation, drops the entry and returnsNone
so the caller re-prepares.bump_version() advances the generation (e.g. after a DDL /
schema change the I/O layer observes), invalidating every older entry lazily on next touch.
ExtendedQueryProtocol
class
The extended-query (Parse/Bind/Describe/Execute/Sync) state machine for one connection.
Single-owner and lock-free for the same reason asSimpleQueryProtocol: one engine
per connection, never shared. It shares theProtocolState / TransactionStatus
vocabulary and the side-band / error / ReadyForQuery handling, then layers the extended
message set on top:
- Prepare (
send_parse_describe()):Parse→Describe(statement)→Sync. The backend answersParseComplete→ParameterDescription→RowDescription|NoData→ReadyForQuery. The resolved parameter OIDs are folded back into the cache entry so a future prepare can detect a type mismatch. - Execute (
send_bind_execute()):Bind→Execute(max_rows)→Sync. The backend answersBindComplete→DataRow*→ (CommandComplete|PortalSuspended) →ReadyForQuery. - Resume (
resume_execute()): anotherExecute→Syncagainst an open portal that previouslyPortalSuspendedEvent-suspended — the server-side cursor.
The whole Parse/Bind/Execute pipeline is sent as one batch terminated by a singleSync,
so the backend reports any error once and a single trailingReadyForQueryresynchronizes.
Raw column bytes pass through untouched; decoding against the codec plan is the next layer.
Owns an optionalPreparedStatementCache. The high-level prepare/execute helpers
consult and update it, but the cache can also be driven directly for tests.
View source · /home/runner/work/chirp/chirp/site/../src/chirp/data/drivers/_pelt/_protocol.py:1