Skip to content

Commit 026e2ff

Browse files
committed
fix: bigint 0n short-circuit in SQL AND + NaN-safe sort comparator
- evaluator.ts AND: `left === 0` didn't catch bigint zero (0n) — FALSE AND NULL should return FALSE per SQL three-valued logic, but returned NULL when the FALSE value was 0n - executor.ts multi-column ORDER BY: `av - bv` returns NaN when either operand is NaN, causing undefined sort behavior — now uses < > operators - docs: pg-wire SQLSTATE error code table, error-handling cross-reference
1 parent c25a620 commit 026e2ff

4 files changed

Lines changed: 24 additions & 4 deletions

File tree

docs/src/content/docs/error-handling.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ The response body always includes `{ error: string }` and, for `QueryModeError`,
8686
{ "error": "Column not found in events: foo", "code": "SCHEMA_MISMATCH" }
8787
```
8888

89+
## PostgreSQL SQLSTATE codes
90+
91+
When connecting via the [pg-wire protocol](/querymode/pg-wire/), errors include standard SQLSTATE codes (e.g., `42P01` for table not found, `57014` for timeout). See [pg-wire error codes](/querymode/pg-wire/#error-codes) for the full mapping.
92+
8993
## DataFrame validation errors
9094

9195
The DataFrame API validates arguments eagerly and throws `QueryModeError` with `QUERY_FAILED`:

docs/src/content/docs/pg-wire.mdx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,23 @@ The handler manages the full connection lifecycle:
8282
1. **Startup** — SSL negotiation (rejected), authentication (always ok), server parameters
8383
2. **Query** — SQL parsing, compilation, execution via the QueryMode pipeline
8484
3. **Response**`RowDescription` + `DataRow` messages with proper type OIDs
85-
4. **Error** — SQL errors return `ErrorResponse` and the connection stays open
85+
4. **Error** — SQL errors return `ErrorResponse` with proper SQLSTATE codes and the connection stays open
86+
87+
## Error codes
88+
89+
Errors include standard PostgreSQL SQLSTATE codes so BI tools can classify them:
90+
91+
| QueryMode error | SQLSTATE | Meaning |
92+
|----------------|----------|---------|
93+
| `TABLE_NOT_FOUND` | `42P01` | Undefined table |
94+
| `COLUMN_NOT_FOUND` | `42703` | Undefined column |
95+
| `INVALID_FILTER`, `INVALID_AGGREGATE` | `42601` | Syntax error |
96+
| `SCHEMA_MISMATCH` | `42804` | Datatype mismatch |
97+
| `INVALID_FORMAT` | `08P01` | Protocol violation |
98+
| `QUERY_TIMEOUT`, `NETWORK_TIMEOUT` | `57014` | Query canceled |
99+
| `MEMORY_EXCEEDED` | `53200` | Out of memory |
100+
| `QUERY_FAILED` | `XX000` | Internal error |
101+
| Other | `42000` | Generic syntax error (default) |
86102

87103
## Type mapping
88104

src/sql/evaluator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ function evaluateBinary(op: string, leftExpr: SqlExpr, rightExpr: SqlExpr, row:
9090
if (op === "and") {
9191
const left = evaluateExpr(leftExpr, row);
9292
const right = evaluateExpr(rightExpr, row);
93-
if (left === false || left === 0) return false;
94-
if (right === false || right === 0) return false;
93+
if (left === false || left === 0 || left === 0n) return false;
94+
if (right === false || right === 0 || right === 0n) return false;
9595
if (left === null || right === null) return null;
9696
return isTruthy(left) && isTruthy(right);
9797
}

src/sql/executor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export class SqlWrappingExecutor implements QueryExecutor {
125125
if (av === null || av === undefined) return 1;
126126
if (bv === null || bv === undefined) return -1;
127127
let cmp: number;
128-
if (typeof av === "number" && typeof bv === "number") cmp = av - bv;
128+
if (typeof av === "number" && typeof bv === "number") cmp = av < bv ? -1 : av > bv ? 1 : 0;
129129
else if (typeof av === "bigint" && typeof bv === "bigint") cmp = av < bv ? -1 : av > bv ? 1 : 0;
130130
else if (typeof av === "bigint" && typeof bv === "number") { if (!Number.isFinite(bv)) { cmp = bv === Infinity ? -1 : 1; } else { const bb = BigInt(Math.trunc(bv)); cmp = av < bb ? -1 : av > bb ? 1 : 0; } }
131131
else if (typeof av === "number" && typeof bv === "bigint") { if (!Number.isFinite(av)) { cmp = av === Infinity ? 1 : -1; } else { const ab = BigInt(Math.trunc(av)); cmp = ab < bv ? -1 : ab > bv ? 1 : 0; } }

0 commit comments

Comments
 (0)