7 min read·Olamilekan

Rewriting the PostgreSQL wire protocol in Rust

PostgreSQL wire protocol v3 is the interface between every PostgreSQL client and server in the world. psql, Prisma, Django, SQLAlchemy, JDBC — they all speak this protocol. For Trubase DB to hold the compatibility bar, it has to be implemented from scratch in pure Rust. Here's what that involves.

The protocol has two modes: Simple Query and Extended Query. Simple Query sends a SQL string and gets back rows. Extended Query is a pipeline: Parse → Bind → Describe → Execute → Sync. Most ORMs and connection pools use Extended Query because it supports prepared statements and binary parameter binding.

Every protocol message starts with a 1-byte message type followed by a 4-byte length. The client sends StartupMessage (with protocol version 3.0 and key-value parameters like 'user' and 'database'), the server responds with AuthenticationOk (or challenges), then sends ParameterStatus messages, BackendKeyData, and finally ReadyForQuery.

Every message type has to be exact: Query ('Q'), Parse ('P'), Bind ('B'), Describe ('D'), Execute ('E'), Sync ('S'), Close ('C'), Terminate ('X'). On the server side: ParseComplete ('1'), BindComplete ('2'), RowDescription ('T'), DataRow ('D'), CommandComplete ('C'), ReadyForQuery ('Z'), ErrorResponse ('E').

The key architectural insight: in Tokio, the entire wire protocol loop — read bytes from TCP socket → decode message → process → encode response → write bytes — compiles to a single async state machine. The tokio::io::AsyncRead and AsyncWrite traits handle non-blocking I/O automatically. When waiting for bytes from the network, the task yields to the Tokio scheduler.

Error handling follows PostgreSQL's protocol exactly. ErrorResponse messages contain SQLSTATE codes, severity levels, message text, detail, hint, and position. Every ORM and client library parses these fields. Getting even one wrong means tools break silently.

Proving wire protocol compatibility means running every major PostgreSQL client library in CI and verifying they work unchanged: psql, libpq, node-postgres, psycopg, JDBC, Prisma, Drizzle, SQLAlchemy, Django. Each exercises different protocol paths — psql uses SimpleQuery, Prisma uses ExtendedQuery with prepared statements, JDBC uses binary parameters. The tools themselves are the test suite.