DB
Drivers & Protocol

Experimental wire protocol

The current NYXDB raw TCP and WebSocket envelope, operations, result statuses, v1/v2 negotiation, streaming termination, and binary block ingest.

NYXDB uses the same request/response envelope over two transports:

  • raw TCP carries frames directly on the byte stream;
  • WebSocket carries each complete frame verbatim in an RFC 6455 binary message on the same server port.

Wire v1, wire v2, WebSocket transport, and block ingest are experimental. They are executable and tested, but no long-term compatibility guarantee is declared. Prefer the repository clients; custom clients must pin a server revision and maintain protocol conformance tests. The native listener has no TLS or public Origin-enforcement contract. Put untrusted WebSocket access behind a reviewed edge and start NYXDB with --require-auth; otherwise a client can skip auth and remain an implicit full-SQL operator.

Authoritative references: transport envelope, wire v2 ADR, capability handshake, and block codec.

Transport detection

The server listens for raw TCP and WebSocket connections on one IPv4 endpoint. An HTTP Upgrade handshake selects WebSocket; otherwise the connection is parsed as raw frames. WebSocket clients must use binary messages. Text messages are not the SQL protocol.

The native server defaults to 127.0.0.1:7510; the container defaults to 0.0.0.0:7777. Configure clients explicitly because those defaults differ.

Outer frame

All multi-byte structural lengths use network byte order (big-endian).

Request  := op:u8 request_id:16 payload_len:u32 payload[payload_len]
Response := request_id:16 status:u8 payload_len:u32 payload[payload_len]

The response repeats the 16-byte request ID. Treat it as opaque correlation bytes, not as the engine's numeric query_id. Generate a fresh nonzero value for every request that may be outstanding, and reject a response that cannot be correlated to an in-flight operation. The only documented exception is the all-zero request ID used by an unsolicited end-user auth revoked control frame. Raw TCP readers must keep reading until the full declared frame is available; one recv is not one frame.

Non-block operations are capped at an 8 MiB request payload. Binary ingest has separate limits described below. The server enforces finite connection handshake, idle, partial-frame, and response-write deadlines.

Operations

OperationValueRequest payloadResponse behavior
Query0x02SQL UTF-8One v1 TSV or negotiated v2 result.
Stream0x03STREAM SELECT SQL UTF-8Initial snapshot, then deltas until termination.
Query + metadata0x04SQL UTF-8#meta + TSV, or a negotiated schema-2 typed metadata envelope.
Stream + metadata0x05Streaming SQL UTF-8Stream plus timing frames.
Parameterized query0x06sql (NUL parameter)*Prepared execution with metadata and plan-cache status.
Parameterized stream0x07sql (NUL parameter)*Bound values apply to the subscription lifetime.
Ping0x08EmptyImmediate pong response; bypasses query execution and telemetry.
Auth0x09End user: provider NUL token; operator: NUL system NUL user NUL passwordAuthenticates this connection; success returns authenticated\n.
Cancel request0x0AExactly 16 raw bytes containing a live one-shot request IDRequests cooperative cancellation and returns a two-column TSV acknowledgement.
Endpoint execute0x0Bendpoint_or_view (NUL parameter)*Executes a published one-shot endpoint or an authorized invocable logical view.
Endpoint subscribe0x0Cendpoint_or_view (NUL parameter)*Subscribes to a published streaming endpoint or an authorized invocable logical view; snapshot/delta bodies follow the negotiated stream mode.
Ingest block0x0DBinary columnar blockingested\t<rows>\n on success.
Ingest compressed block0x0EExpanded length + LZ4 blockSame acknowledgement after bounded decode.
Capability0x0FVersion/capability helloChooses the connection's wire version and result mode.

Unknown operations return an error status. Parameter text is positional: the first NUL-delimited value binds $1, the second $2, and so on. The v1 parameter form cannot represent SQL NULL as a distinct wire value.

Authentication and application endpoint operations

OP_AUTH (0x09) is connection-scoped. Its application-user form is:

provider_name "\0" token

An empty provider name is accepted only when exactly one provider is registered. A lookup or streaming provider converts the opaque token into server-trusted claims. Clients send only the endpoint's declared positional parameters; the server appends declared $auth.* values from that claims row. A client cannot override a claim with an extra parameter.

The operator form is deliberately distinct:

"\0system\0" user "\0" password

When --require-auth is enabled, an unauthenticated connection may negotiate capabilities at most once, ping, and use either operator or end-user OP_AUTH; other operations are gated. Negotiate before auth when v2 is required. Without the flag, the default remains fully open. End-user auth does not close that operator path.

Authenticate once on a fresh connection. A failed OP_AUTH does not clear or replace a principal already attached to that socket. Close the transport and open a new one for every token, provider, or operator-user change.

After successful end-user auth, the connection retains the one-time connection-setup capability exception and is otherwise restricted to ping, auth, endpoint execute, endpoint subscribe, and request-ID cancellation for a one-shot. Raw SQL and block ingest return operation not permitted. Endpoint invocation sends:

endpoint_or_view ( "\0" parameter )*

Endpoint execute/subscribe request parameters use the v1 NUL-delimited text contract even when the connection negotiated wire v2. A one-shot OP_ENDPOINT_EXEC response is plain TSV with no metadata envelope. A successful OP_ENDPOINT_SUB may instead carry negotiated v2 typed snapshot/delta frames (and optional LZ4), but it does not emit metadata/query-ID status frames because the endpoint subscription uses the non-metadata stream operation. Decode by operation, status, and negotiated mode—not connection state alone.

OP_ENDPOINT_EXEC is nevertheless a cancellable one-shot. Its request ID can be targeted by OP_CANCEL_REQUEST while the request is waiting for admission or executing. OP_ENDPOINT_SUB remains a stream lifecycle and is cancelled by closing that subscription's owning transport, not through the request-cancel operation.

A published endpoint is a pre-authorized query contract. A logical view is default-deny and requires an application read grant for execution or subscribe for a live subscription. Other grant targets and verbs are not consumable through the current end-user wire. Application errors are sanitized to stable bodies such as auth failed, auth revoked, not authorized, invalid parameter, endpoint failed, and limit exceeded: <dimension>.

An endpoint's USING provider is a definition-time claim-schema check, not runtime provider affinity. Any authenticated provider with compatible claims can invoke it. Endpoint names also resolve before view names; use a relation UUID to target a colliding logical view unambiguously.

The application-grant check applies only to end-user view invocation. A named non-admin operator invoking a claimless endpoint or logical view with OP_ENDPOINT_EXEC or OP_ENDPOINT_SUB is checked against the canonical query's scanned base relations in the separate operator-grant catalog. Execution requires read; subscription requires subscribe. Admin and default-open implicit operators keep their bypass, and all operator-class calls remain exempt from application endpoint quotas.

A streaming auth gate may revoke an idle connection by sending status 0x01, body auth revoked, and a 16-byte all-zero request ID before closing the transport. Revocation on an active subscription is terminal. There is no durable resume cursor: reconnect, authenticate again, resubscribe, and replace the local replica with the new snapshot.

Changing a live-gate claims row affects future invocations only; an existing subscription keeps frozen bindings and identity. Replacing or dropping an endpoint likewise does not terminate an existing subscription, and dropping it removes governor/metering state. Operators must drain those subscriptions before security, query, provider, or limit changes that must invalidate old behavior.

See Browser and platform endpoint clients for framing code, the complete sanitized error table, and reconnect policy.

Response statuses

StatusValueMeaning
OK0x00Operation succeeded; payload format depends on the operation and negotiated mode.
Error0x01Operation failed; the payload contains the current server diagnostic contract.
Snapshot0x02Full starting result for a stream.
Delta0x03Positional changes to the client's maintained result.
Metadata0x04Server timing metadata for metadata-enabled streams.

An error status is not a transport retry signal. For a stream, an error is terminal for that subscription.

Wire v1 text results

Without capability negotiation, the connection stays in v1 mode. A one-shot result is UTF-8 TSV:

market\tamount
BTC-USD\t4120
ETH-USD\t2880

The first row contains column labels. Metadata-enabled operations add a line such as #meta\telapsed_us=<n>\tserver=<id>[\tcache=hit|miss]\tquery_id=<n>.

V1 is intentionally simple but cannot carry full parametric type information. A client commonly sees text rather than a decimal scale, temporal timezone, or vector dimension.

Streaming snapshots and deltas

After the initial snapshot, v1 delta payloads contain positional operations:

RecordMeaning
I\t<row>\t<v1>\t<v2>…Insert a row at a position.
V\t<row>\t<v1>\t<v2>…Replace a row.
U\t<row>\t<column>\t<value>Update one cell.
R\t<row>Remove a row.
M\t<from>\t<to>Move a row.

Apply operations in payload order. If an operation is malformed, out of bounds, or incompatible with the current snapshot, fail closed and rebuild from a new subscription instead of guessing state.

A stream terminates on client cancellation, EOF, transport timeout, server shutdown, source drop, or an error frame. Cross-connection resume is not a current contract. A new subscription begins with a new snapshot that replaces the abandoned client replica.

Wire v2 negotiation

Negotiation happens at most once per connection through operation 0x0F.

ClientHello := magic:0x4E43 schema:u8 flags:u16 count:u8 versions[count]
ServerReply := magic:0x4E43 schema:u8 flags:u16 chosen_version:u8

The handshake schema byte is currently 1 or 2; that schema version is separate from the selected wire version. Both schemas can advertise wire versions 1 and 2.

FlagClient helloServer acknowledgementAvailability
0x0001Request binary resultsBinary results engagedSchema 1 and 2
0x0002Accept raw-LZ4 result framesServer may emit LZ4 framesSchema 1 and 2; requires 0x0001
0x0004Accept typed result metadata envelopesServer may emit TypedResultEnvelopeSchema 2 only; requires 0x0001

The server chooses the highest advertised wire version it supports, with v1 as its fallback floor, and acknowledges only flags present in that exact hello. A client must bind the reply to the attempted handshake schema and advertised versions. A client that can decode v1 should include it in the advertised list. The server/protocol supports 0x0002, but the current in-tree CLI, console, and JDBC one-shot paths advertise binary plus typed metadata (0x0001 | 0x0004) and do not enable LZ4. Their stream paths do not negotiate these capabilities.

An older server may explicitly reject the capability operation as unsupported. After a schema-2 rejection, close that connection and retry schema 1 on a fresh connection; after a schema-1 rejection, use another fresh connection for plain v1. Negotiate before authentication when v2 is required, then authenticate each fresh socket before a gated operation. Never reuse the rejected socket. A malformed hello or malformed successful reply is fatal and never silently upgrades or downgrades the connection. Old clients that never send a hello remain on v1.

Wire v2 typed frames

V2 keeps the outer request envelope and replaces eligible result/stream bodies with a self-describing columnar frame. Its schema preserves:

  • column names, type IDs, nullability, and low-cardinality flags;
  • decimal precision and scale;
  • datetime64 precision and timezone;
  • fixed-string width and vector(N) dimension;
  • recursive child fields; and
  • Arrow-style validity bitmaps, offsets, dictionaries, and fixed-width value buffers.

Structural integers remain big-endian and fixed-width values are little-endian. Frame version 2 is a loud incompatibility guard. A client must validate magic, version, flags, schema depth, lengths, offsets, and row counts before allocating or exposing values.

Writes and result shapes that are not emitted as a v2 frame may still use a text acknowledgement on a negotiated connection. Decode based on the actual validated payload, not on an assumption that negotiation makes every response binary.

Schema-2 metadata envelope and fallback

When schema 2 acknowledges flags 0x0001 and 0x0004, successful OP_QUERY_META and OP_QUERY_PARAMS row results carry one NR TypedResultEnvelope:

magic:0x4E52 version:1 flags:0
elapsed_us:u64be query_id:u64be cache_state:u8
server_len:u16be server:utf8
frame_len:u32be frame:ColumnarFrame

The outer envelope is never compressed; only its nested ColumnarFrame may use the negotiated LZ4 wrapper. query_id is nonzero and is the engine identity published in system.queries and later system.query_log. Parameterized results additionally distinguish cache hit from miss; ordinary metadata queries use the not-applicable state.

OP_QUERY deliberately remains a bare NY columnar frame or TSV fallback even on the same schema-2 connection. One-shot endpoint execution also retains plain TSV; endpoint subscription can use the negotiated stream frame mode but has no metadata envelope. Classify the payload according to the operation and negotiated capabilities rather than magic-byte guessing across unrelated operations.

If either metadata-bearing operation completes with no rows or a shape that the typed encoder cannot represent, the server renders #meta plus escaped TSV from the already captured rows. It does not execute the SQL again. Clients must retain the same elapsed time, numeric query ID, server identity, and parameter-cache state from that fallback carrier. Bare OP_QUERY fallback is plain TSV without metadata. This single-execution rule is especially important for statements with writes or read-side effects.

Query identity and one-shot cancellation

NYXDB exposes two intentionally different identities:

IdentityShapePurpose
Request ID16 opaque bytes in the outer envelopeCorrelate frames and target a live one-shot through OP_CANCEL_REQUEST
Engine query IDNonzero unsigned 64-bit integer in metadataJoin system.queries/system.query_log and issue SQL CANCEL QUERY <id>

Metadata-enabled v1 responses add the engine identity without changing the TSV schema:

#meta\telapsed_us=<n>\tserver=<id>[\tcache=hit|miss]\tquery_id=<n>

To cancel a one-shot before its terminal result, open a separate connection that satisfies the normal auth gate and send:

request op      := 0x0A
request payload := target_request_id:bytes[16]

success or miss payload (TSV):
query_id\tcancel_requested
<matched_query_id>\t1

query_id\tcancel_requested
0\t0

The cancel request has its own independent request ID. The target payload must be exactly 16 bytes and nonzero. A unique pending or executing OP_QUERY, OP_QUERY_META, OP_QUERY_PARAMS, or OP_ENDPOINT_EXEC target returns 1; a missing, stale, zero, or non-unique target returns 0 and changes nothing. Registration happens before bounded admission, and cancel dispatch bypasses query admission, so a saturated queue cannot hide the target or block the control request.

The server processes ordinary requests sequentially on one connection. Sending cancel behind the blocked statement on that same socket cannot overtake it; use a separate control connection. The acknowledgement means cancellation was requested, not that the original terminal frame has already arrived. Keep the original request registered until its terminal response, and close its transport under a bounded policy if a successful acknowledgement is not followed by one.

Streams, endpoint subscriptions, auth, capability negotiation, ping, and asynchronous ingest are not 0x0A targets. Close the stream's owning transport or use its documented query lifecycle instead.

Disconnect-driven cancellation

For production one-shots, raw TCP FIN/reset and WebSocket Close are cooperative cancellation signals. Query operators and parallel storage reads observe peer liveness through the same bounded checkpoints used for deadlines and explicit cancellation. The stable operator diagnostic is NYXDB_EXEC_CANCELLED: client disconnected, and completion is recorded in system.query_log.

While a WebSocket one-shot is executing, the liveness path services RFC 6455 Ping/Pong and Close control frames and retains bounded pipelined application frames for normal dispatch. This is distinct from NYXDB OP_PING (0x08), which remains the application-level health operation and bypasses query admission and telemetry.

Binary block ingest

Block ingest bypasses SQL value parsing. A block is column-major and self-describing:

Block := magic:0x4E59 version:1 table:string ncols:u32 nrows:u32 columns[ncols]
Column := name:string type_id:u16 flags:u8 [null_mask] values

Structural lengths are big-endian; fixed-width values use the engine's little-endian value encoding. V1 block ingest supports the documented scalar set and rejects unsupported nested or unknown types:

  • fixed-width: Bool, signed and unsigned integers from 8 through 256 bits, Float32, Float64, Date, Date32, DateTime, DateTime64, and decimal storage widths from 32 through 256 bits;
  • variable-width: String, FixedString, Bytes, UUID, IPv4, and IPv6;
  • nullable columns: flag bit 0 adds one byte per row to the null mask; a null row contributes no value bytes; and
  • unsupported: nested types and unlisted scalar type IDs. Reject them before sending rather than attempting text coercion inside a block.

Every column must describe exactly nrows cells. Fixed-width cells must match the type's wire width; variable-width cells carry a big-endian length followed by raw bytes.

Clients must chunk loads:

  • compressed or uncompressed wire payload: at most 64 MiB;
  • expanded compressed block: at most 512 MiB.

LZ4 ingest prepends the expected expanded length. Both the compressed and expanded limits are validated before the block reaches storage.

Failure and retry contract

  • A framing violation, impossible length, request-ID mismatch, malformed negotiation, or invalid typed frame is connection-fatal.
  • An SQL error frame is operation-scoped; surface it without reconnect loops.
  • A transport close before a DDL/DML response is ambiguous. Reconcile state or use application-level idempotency before repeating the statement.
  • Retry connection establishment with bounded exponential backoff, jitter, and an overall deadline.
  • Resubscribing after a stream failure creates a new snapshot boundary; it does not resume the previous cursor.

Use Drivers & protocol for the application-level decision table and Capabilities & status for release maturity.

On this page