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.
Quick start
Section titled “Quick start”# Start the pg-wire server (reads files from current directory)npx tsx src/pg-wire/server.ts
# Connect with psqlpsql -h localhost -p 5433 -U querymodeThen 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;Configuration
Section titled “Configuration”| Environment variable | Default | Description |
|---|---|---|
PG_PORT | 5433 | Listen port |
PG_HOST | 127.0.0.1 | Bind address |
PG_PORT=5432 PG_HOST=0.0.0.0 npx tsx src/pg-wire/server.tsSupported SQL
Section titled “Supported SQL”The pg-wire server uses QueryMode’s SQL parser, so all SQL syntax is supported:
SELECT,WHERE,GROUP BY,HAVING,ORDER BY,LIMIT,OFFSETJOIN(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
Compatibility commands
Section titled “Compatibility commands”These are accepted and acknowledged for BI tool compatibility but have no effect:
SET client_encoding TO 'UTF8'RESET ALLDISCARD ALLSHOW <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)
Programmatic use
Section titled “Programmatic use”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:
- Startup — SSL negotiation (rejected), authentication (always ok), server parameters
- Query — SQL parsing, compilation, execution via the QueryMode pipeline
- Response —
RowDescription+DataRowmessages with proper type OIDs - Error — SQL errors return
ErrorResponsewith proper SQLSTATE codes and the connection stays open
Error codes
Section titled “Error codes”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) |
Type mapping
Section titled “Type mapping”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 |
Limitations
Section titled “Limitations”- Simple Query protocol — extended query protocol messages (
Parse,Bind,Describe,Execute) are acknowledged but not executed.Syncis 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 aRemoteExecutortoPgConnectionHandler.