Error Handling
All errors thrown by QueryMode are QueryModeError instances with a structured code field. Low-level errors (ENOENT, parse failures, OOM) are wrapped automatically with context.
Error codes
Section titled “Error codes”| Code | When | Example message |
|---|---|---|
TABLE_NOT_FOUND | File doesn’t exist, R2 key missing | ”Table not found: events.lance” |
COLUMN_NOT_FOUND | Column name not in schema | ”Column not found: foo” |
INVALID_FORMAT | File can’t be parsed, decompression fails, bad binary format | ”Invalid table format: data.xyz. Supported formats: .lance, .parquet, .csv, …” |
SCHEMA_MISMATCH | Column type doesn’t match operation, unsupported dtype | ”Column not found in events: age” |
INVALID_FILTER | Bad filter op or value type | ”Invalid filter: unknown op ‘regex‘“ |
INVALID_AGGREGATE | Bad aggregate function or missing column | ”Invalid aggregate: sum requires numeric column” |
MEMORY_EXCEEDED | Operator exceeds memory budget | ”Memory budget exceeded querying events” |
NETWORK_TIMEOUT | R2 or RPC call timed out | ”Network timeout on events: R2 read timed out” |
QUERY_TIMEOUT | Total query time exceeded | ”Query timeout on events” |
QUERY_FAILED | Catch-all for unclassified errors | ”Query failed on events: …” |
Catching errors
Section titled “Catching errors”import { QueryModeError } from "querymode"
try { await qm.table("missing.lance").collect()} catch (err) { if (err instanceof QueryModeError) { switch (err.code) { case "TABLE_NOT_FOUND": console.log("Table doesn't exist:", err.message) break case "MEMORY_EXCEEDED": console.log("Try adding filters or reducing projections") break default: console.log(`${err.code}: ${err.message}`) } // Original error is preserved if (err.cause) console.log("Caused by:", err.cause) }}Error wrapping
Section titled “Error wrapping”QueryModeError.from() wraps any error with context:
try { await riskyOperation()} catch (err) { throw QueryModeError.from(err, { table: "events", operation: "scan" }) // Automatically classifies: ENOENT → TABLE_NOT_FOUND, // "footer" in message → INVALID_FORMAT, "OOM" → MEMORY_EXCEEDED, etc.}Already-wrapped QueryModeError instances pass through unchanged.
HTTP status codes
Section titled “HTTP status codes”When using the HTTP API (/query, /write, etc.), error codes map to HTTP status codes:
| Error code | HTTP status | Meaning |
|---|---|---|
TABLE_NOT_FOUND | 404 | Table doesn’t exist in R2 |
COLUMN_NOT_FOUND | 404 | Column not in schema |
INVALID_FILTER | 400 | Malformed filter op or value |
INVALID_FORMAT | 400 | File can’t be parsed |
INVALID_AGGREGATE | 400 | Bad aggregate function |
SCHEMA_MISMATCH | 400 | Column type doesn’t match operation |
MEMORY_EXCEEDED | 503 | Operator exceeded memory budget |
QUERY_TIMEOUT | 504 | Total query time exceeded |
NETWORK_TIMEOUT | 504 | R2 or RPC call timed out |
QUERY_FAILED | 500 | Unclassified error |
| (SyntaxError) | 400 | Malformed JSON request body |
| (CAS conflict) | 409 | Concurrent write conflict |
The response body always includes { error: string } and, for QueryModeError, { code: string }:
{ "error": "Column not found in events: foo", "code": "SCHEMA_MISMATCH" }PostgreSQL SQLSTATE codes
Section titled “PostgreSQL SQLSTATE codes”When connecting via the pg-wire protocol, errors include standard SQLSTATE codes (e.g., 42P01 for table not found, 57014 for timeout). See pg-wire error codes for the full mapping.
DataFrame validation errors
Section titled “DataFrame validation errors”The DataFrame API validates arguments eagerly and throws QueryModeError with QUERY_FAILED:
qm.table("events").limit(-1)// QueryModeError: QUERY_FAILED — "limit() must be non-negative"
qm.table("events").after(100)// QueryModeError: QUERY_FAILED — "after() requires sort()"
await qm.table("events").append(rows)// QueryModeError: QUERY_FAILED — "append() requires an executor with write support"// (when using a read-only executor)WASM out-of-memory errors throw MEMORY_EXCEEDED:
// QueryModeError: MEMORY_EXCEEDED — "WASM OOM allocating zstd output"SQL errors
Section titled “SQL errors”SQL syntax errors throw SqlParseError or SqlLexerError (not QueryModeError) with a position field indicating where the error occurred:
import { SqlParseError, SqlLexerError } from "querymode"
try { await qm.sql("SELECT FROM").collect()} catch (err) { if (err instanceof SqlParseError) { console.log(err.message) // "Expected column or expression at position 7" console.log(err.position) // 7 } if (err instanceof SqlLexerError) { console.log(err.message) // "Unexpected character at position 12" console.log(err.position) // 12 }}