src/sydra/compat/wire/server.zig
Purpose
Provides a simple PostgreSQL wire-protocol (pgwire) listener suitable for basic compatibility testing with clients like psql.
The server:
- accepts a TCP connection
- performs a minimal startup handshake
- supports a small subset of frontend messages
- translates SQL → sydraQL and executes it via the regular query pipeline
- writes results as
RowDescription+DataRowmessages
See also
- wire protocol (message framing + startup/response writers)
- wire session (handshake + session config)
- wire re-exports
- SQL → sydraQL translator
- sydraQL execution entrypoint
- Reference: PostgreSQL Compatibility
Public API
pub const ServerConfig
address: []const u8 = "127.0.0.1"port: u16 = 6432session: session_mod.SessionConfig = .{}engine: *engine_mod.Engine
pub fn run(alloc, config) !void
- Listens on
address:portwithreuse_address = true. - Runs an accept loop; each connection is handled synchronously via
handleConnection.
pub fn handleConnection(alloc, connection, session_config, engine) !void
- Wraps the socket in buffered reader/writer states.
- Calls
session_mod.performHandshake. - On success enters
messageLoop.
Frontend message support
messageLoop reads:
type_byte: u8message_length: u32be(includes the 4-byte length field)payload: message_length - 4bytes
It enforces:
message_length >= 4payload_len <= 16 MiB(max_message_size)
Handled message types:
'X'– Terminate: close the connection.'Q'– Simple Query: handled byhandleSimpleQuery(SQL→sydraQL→execute).'P'– Parse (extended protocol):handleParseMessagevalidates framing but returns0A000on success (“not implemented yet”).'S'– Responds withReadyForQuery('I')(acts as a simple sync/flush).- Anything else:
ErrorResponse("0A000", "message type not implemented")ReadyForQuery('I')
messageLoop dispatch (excerpt)
switch (type_byte) {
'X' => return,
'Q' => {
try handleSimpleQuery(alloc, writer, payload_storage, engine);
},
'P' => {
try handleParseMessage(alloc, writer, payload_storage);
},
'S' => {
try protocol.writeReadyForQuery(writer, 'I');
},
else => {
try protocol.writeErrorResponse(writer, "ERROR", "0A000", "message type not implemented");
try protocol.writeReadyForQuery(writer, 'I');
},
}
Simple Query execution
fn handleSimpleQuery(alloc, writer, payload, engine) !void
Behavior:
- Trims a trailing NUL byte from
payload(C-string style). - Trims whitespace.
- If empty:
- writes
EmptyQueryResponsethenReadyForQuery('I')
- writes
- Otherwise:
- calls
translator.translate(alloc, sql)(see SQL → sydraQL translator)- on OOM:
ErrorResponse(FATAL, 53100, "out of memory during translation")
- on OOM:
- on translation success:
- calls
handleSydraqlQuery(…, sydraql)
- calls
- on translation failure:
- writes
ErrorResponse(ERROR, failure.sqlstate, failure.message or "translation failed")
- writes
- calls
- always ends with
ReadyForQuery('I')
SQL → sydraQL translation (excerpt)
const translation = translator.translate(alloc, trimmed) catch |err| switch (err) {
error.OutOfMemory => {
try protocol.writeErrorResponse(writer, "FATAL", "53100", "out of memory during translation");
try protocol.writeReadyForQuery(writer, 'I');
return;
},
};
switch (translation) {
.success => |success| {
defer alloc.free(success.sydraql);
try handleSydraqlQuery(alloc, writer, engine, success.sydraql);
try protocol.writeReadyForQuery(writer, 'I');
return;
},
.failure => |failure| {
const msg = if (failure.message.len == 0) "translation failed" else failure.message;
try protocol.writeErrorResponse(writer, "ERROR", failure.sqlstate, msg);
try protocol.writeReadyForQuery(writer, 'I');
},
}
Extended protocol parse (partial)
fn handleParseMessage(alloc, writer, payload) !void
Parses enough of the frontend Parse message to report a deterministic response:
- Reads:
statement_nameas NUL-terminated stringqueryas NUL-terminated stringparameter_count(u16be)- validates presence of
parameter_count * 4bytes for parameter type OIDs
- Translates
queryviatranslator.translate. - If translation succeeds, responds:
ErrorResponse(ERROR, 0A000, "extended protocol not implemented yet")
- If translation fails, responds with the translator’s SQLSTATE/message.
- Always ends with
ReadyForQuery('I').
No prepared statement state is stored, and no subsequent Bind/Execute messages are handled.
SydraQL execution + result encoding
fn handleSydraqlQuery(alloc, writer, engine, sydraql) !void
Execution:
- Calls
query_exec.executeto create anExecutionCursor. - Streams:
RowDescription(even for zero columns; thencolumns.lenis0)DataRowfor each row returned bycursor.next()
Diagnostics:
- Collects operator stats via
cursor.collectOperatorStats. - Computes:
rows_emitted(from stream count)rows_scanned(sum of operatorrows_outwhere operator name is"scan", case-insensitive)stream_msfrom wall timeplan_msfromcursor.stats.{parse,validate,optimize,physical,pipeline}_us
- Emits
NoticeResponsemessages:schema=[{name:\"...\",type:\"...\",nullable:true}, ...](for non-empty schemas)trace_id=...(whencursor.stats.trace_idis present)operator=... rows_out=... elapsed_ms=...for each operator stat
- Completes with:
CommandCompletetagSELECT rows=… scanned=… stream_ms=… plan_ms=… [trace_id=…]ReadyForQuery('I')
fn writeRowDescription(writer, columns) !void
Writes pgwire RowDescription ('T') using:
- the column name (
plan.ColumnInfo.name) - placeholder table/attribute identifiers (
0) - a single “default type” mapping for every column (
query_functions.pgTypeInfo(Type.init(.value, true)); see src/sydra/query/functions.zig)
fn writeDataRow(writer, values, row_buffer, value_buffer) !void
Writes pgwire DataRow ('D') in text format for every value:
- Each value is preceded by a 4-byte
i32belength. - Null values use length
-1.
fn formatValue(value, buf) !?[]const u8
Text formatting rules:
null→null(caller encodes-1)boolean→"t"or"f"integer→ decimal stringfloat→ decimal string via{d}string→ byte slice as-is
Other internal helpers
trimNullTerminatortrims a trailing0byte from query payloads.readU32reads big-endian lengths.readCStringparses NUL-terminated strings from a buffer.parseAddressparses IPv4 or IPv6.anyWriteradapts astd.Io.Writerintostd.Io.AnyWriter.formatSelectTagformats theCommandCompletetag (with optionaltrace_id).