Separate the protocol layers
Flyology.Postgres defines shared authentication and TLS policy. Its children divide the implementation into transport, framing, protocol, client, server, and replication layers.
- Transport
- Moves exact byte sequences and preserves socket or connection ownership.
- Framing and protocol
- Validate lengths and decode owned typed messages.
- Client
- Enforces frontend operation order and returns bounded events.
- Server
- Owns startup, authentication, cancellation routing, and connection-task lifecycle.
- Replication
- Adds replication commands, COPY BOTH envelopes, logical messages, and application-supplied persistence interfaces.
Keep transport ownership explicit
Flyology.Postgres.Transports defines the abstract byte transport. A client Session borrows this transport, so the transport must outlive the session.
Transports.Sockets adapts a Flyology socket without taking ownership. Transports.TLS_Sockets preserves the same borrowing boundary while the upgraded TLS connection owns the socket.
Validate wire data before session policy
Flyology.Postgres.Protocol owns frontend and backend message values. A Message retains its tag and complete payload. Typed decoding preserves the original message for custom state machines and unknown future tags.
The protocol layer bounds a frame to 16 MiB. It validates lengths, terminators, format counts, and recognized values before returning typed data. An allocation-free SPARK core handles the narrow cursor and length calculations below the owned Ada values.
Enforce client operation order
Flyology.Postgres.Client combines protocol messages with a session state machine. The Operation_State distinguishes startup, ready, simple-query, extended-query, COPY, recovery, and closed states.
Simple and extended receive operations return one owned event per call. The library does not collect a complete result set or COPY stream. Local ordering checks reject invalid operations before the client writes them.
After an extended-query error, the application calls Synchronize to send Sync. The application then receives events through ReadyForQuery before it reuses the session.
Enter_Pipeline_Mode lets several Sync-terminated batches stay outstanding at the same time. The session counts the Sync messages it has written, and each ReadyForQuery retires one count. Pending_Synchronizations reports the remainder.
The count separates the batch being read from the batch being written. While the count is positive, an arriving response belongs to an earlier batch, so the session leaves the ordering state of the open batch alone.
A pipelined failure reaches the application as an ordinary error event. The failed batch's own Sync already ends it, so the session stays usable for the batches behind it and does not require recovery.
Pipeline mode rejects a simple query, which carries no Sync boundary of its own. It also rejects a COPY response, because COPY holds the connection until it completes and its own state machine tracks a single Sync. That rejection is terminal: the session closes, and the application must discard its transport.
Cancellation uses backend credentials on a separate connection. It does not interrupt the active transport directly.
Give each accepted connection one session task
Flyology.Postgres.Server is generic over application context, authentication, verifier lookup, and command handling. It delegates connection ownership and task lifecycle to Flyology's structured server.
Flyology.Postgres.Server_Sessions supplies bounded response operations to the application handler. The handler retains responsibility for SQL meaning, data, transactions, prepared-state storage, and result metadata.
Each connection uses a native or lightweight Ada task according to the generic configuration. Lightweight scheduling remains cooperative.
If a CPU-bound handler prevents peer progress, it must suspend, use a fairness checkpoint, or move blocking work to a native-task boundary.
Keep replication durability in application stores
Flyology.Postgres.Replication defines startup commands, LSN conversion, physical stream envelopes, feedback, and keepalives. Replication.Logical adds typed pgoutput messages for protocol versions 1 through 4.
Replication.Persistence defines interfaces for slots, retained WAL, timeline history, and prepared-consumer recovery. The application supplies the storage technology and establishes its durability guarantees.
Replication.Managed_Primary composes these stores with an application-owned logical-change source. The bounded Persistence.Memory implementation is for tests and ephemeral single-owner servers. It is not a durable production backend.
Generate one SQL API for each PostgreSQL major
The nested SQL crates generate independent Ada 2022 parser APIs for PostgreSQL 14 through 18. Applications normally select one version crate. The all-version crate remains a compatibility umbrella.
The owned APIs are SQL.AST.V14, V15, V16, V17, and V18. Each package returns an owned recursive graph.
The generated SQL.AST.V18.Visitors package illustrates the typed traversal API. Each supported major has an equivalent visitor package.
The shallow alternatives are SQL.Views.V14, V15, V16, V17, and V18. These packages expose arena views for allocation-sensitive consumers.
Both representations derive their shape from the pinned PostgreSQL grammar and schema inputs.
Test each external boundary
The integration suite tests the client against real PostgreSQL and the server against real psql. Replication tests cover PostgreSQL 14 through 18 in client, primary, standby, and logical-consumer roles.
The SQL suite compares each owned AST with its shallow arena baseline. It then compares the native Ada arena with a version-pinned C and protobuf oracle. Production parser archives must contain no oracle symbols.
These tests support the documented interoperability baseline. They do not qualify the experimental library for production use or make its application-supplied storage durable.