Skip to content

Postgres Wire Protocol

QueryMode includes a PostgreSQL wire protocol server. Any tool that connects to Postgres — psql, DBeaver, Metabase, Grafana, TablePlus, DataGrip — can query your data without code changes.

Terminal window
# Start the pg-wire server (reads files from current directory)
npx tsx src/pg-wire/server.ts
# Connect with psql
psql -h localhost -p 5433 -U querymode

Then query as normal SQL:

SELECT * FROM './data/events.parquet' WHERE region = 'us' LIMIT 10;
SELECT region, COUNT(*), AVG(amount) FROM './data/orders.lance' GROUP BY region;
Environment variable Default Description
PG_PORT 5433 Listen port
PG_HOST 127.0.0.1 Bind address
Terminal window
PG_PORT=5432 PG_HOST=0.0.0.0 npx tsx src/pg-wire/server.ts

The pg-wire server uses QueryMode’s SQL parser, so all SQL syntax is supported:

  • SELECT, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
  • JOIN (inner, left, right, full, cross)
  • Window functions (ROW_NUMBER, RANK, LAG, LEAD, rolling aggregates)
  • CTEs (WITH ... AS)
  • Set operations (UNION, INTERSECT, EXCEPT)
  • Vector search (WHERE embedding NEAR [...] TOPK 10)
  • All 14 filter operators, CASE, CAST, arithmetic, || concatenation
  • -- line comments and /* */ block comments

These are accepted and acknowledged for BI tool compatibility but have no effect:

  • SET client_encoding TO 'UTF8'
  • RESET ALL
  • DISCARD ALL
  • SHOW <parameter> (returns "on" for all parameters)
  • BEGIN, START TRANSACTION, COMMIT, END, ROLLBACK, ABORT (auto-commit mode)
  • DEALLOCATE, CLOSE (no-op since prepared statements are not stored)

The PgConnectionHandler class can be embedded in any Node.js TCP server or WebSocket bridge:

import { QueryMode } from "querymode/local"
import { PgConnectionHandler } from "querymode"
import * as net from "node:net"
const qm = QueryMode.local()
net.createServer((socket) => {
const handler = new PgConnectionHandler({
executor: qm.getExecutor(),
send: (data) => socket.write(data),
})
socket.on("data", async (chunk) => {
await handler.onData(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength))
})
}).listen(5433)

The handler manages the full connection lifecycle:

  1. Startup — SSL negotiation (rejected), authentication (always ok), server parameters
  2. Query — SQL parsing, compilation, execution via the QueryMode pipeline
  3. ResponseRowDescription + DataRow messages with proper type OIDs
  4. Error — SQL errors return ErrorResponse with proper SQLSTATE codes and the connection stays open

Errors include standard PostgreSQL SQLSTATE codes so BI tools can classify them:

QueryMode error SQLSTATE Meaning
TABLE_NOT_FOUND 42P01 Undefined table
COLUMN_NOT_FOUND 42703 Undefined column
INVALID_FILTER, INVALID_AGGREGATE 42601 Syntax error
SCHEMA_MISMATCH 42804 Datatype mismatch
INVALID_FORMAT 08P01 Protocol violation
QUERY_TIMEOUT, NETWORK_TIMEOUT 57014 Query canceled
MEMORY_EXCEEDED 53200 Out of memory
QUERY_FAILED XX000 Internal error
Other 42000 Generic syntax error (default)

QueryMode types map to PostgreSQL type OIDs:

QueryMode type Postgres type OID
int8, int16, uint8, uint16 INT2 21
int32, uint32 INT4 23
int64, uint64 INT8 20
float16, float32 FLOAT4 700
float64 FLOAT8 701
utf8, string TEXT 25
bool BOOL 16
binary BYTEA 17
  • Simple Query protocol — extended query protocol messages (Parse, Bind, Describe, Execute) are acknowledged but not executed. Sync is handled correctly. Most BI tools fall back to Simple Query automatically.
  • No authentication — all connections are accepted. Run behind a firewall or SSH tunnel for production use.
  • No SSL/TLS — SSL requests are rejected; clients retry in plaintext. Use an SSH tunnel or reverse proxy for encrypted connections.
  • Local mode only — the built-in server uses LocalExecutor. For edge mode, build a custom server that passes a RemoteExecutor to PgConnectionHandler.