Skip to content

BIGINT and BIGINT[] values beyond JS safe-integer range lose precision in the results grid #41

@openidle-dev

Description

@openidle-dev

Problem

Postgres `BIGINT` (and now `BIGINT[]`, per PR #35) values whose magnitude exceeds JavaScript's `Number.MAX_SAFE_INTEGER` (2^53 ≈ 9 × 10^15) are silently rounded to the nearest float64-representable value when they arrive in the frontend results grid.

Example: `-9223372036854775808` (int8 minimum, -2^63) renders as `-9223372036854776000` in the grid — off by 192. `(1::int8 << 60)` renders as `1152921504606846976` exactly only because float64 happens to have an exact representation at this point; values just above it lose precision.

Repro

Against any Postgres connection:

```sql
SELECT
(-9223372036854775808)::int8 AS int8_min,
9223372036854775807::int8 AS int8_max,
ARRAY[(-9223372036854775808)::int8,
9223372036854775807::int8] AS int8_arr;
```

Expected: the grid shows the exact integer values.
Actual: `int8_min` displays as `-9223372036854776000`; `int8_max` as `9223372036854776000`; the array elements show the same rounded values.

Root cause

`src-tauri/patches/tauri-plugin-sql/src/decode/postgres.rs` decodes `INT8` and `INT8[]` via:

```rust
JsonValue::Number(i64.into()) // scalar INT8 (lines 61-67)
serde_json::Number::from(i64) // INT8[] via int_vec_to_json (lines 83-93)
```

`serde_json::Number::from(i64)` produces an exact integer in the JSON wire format. The precision loss happens after the JSON crosses the Tauri IPC boundary into JavaScript: `JSON.parse` converts every JSON number to a JS `Number` (IEEE 754 float64), and integers beyond ±2^53 cannot be represented exactly.

Scope

  • This isn't new — scalar BIGINT has had this behavior since QueryDen first supported Postgres. PR fix(db): support INT2/INT4/INT8 array deserialization (#27) #35 made it more visible by enabling BIGINT[] display (which used to fail with "unsupported datatype" before reaching the IPC layer).
  • Affects `INT8`, `INT8[]`, and likely `NUMERIC` values that fit in i64 but exceed 2^53. `NUMERIC` decoding (currently via `rust_decimal::Decimal` → string?) should be audited as part of the same fix.

Fix shape (sketch — needs design)

Two viable approaches:

Option A: Emit bigint values as JSON strings, render as strings

In the Rust patch, emit `INT8` and `INT8[]` as `JsonValue::String(v.to_string())` (or as tagged objects like `{ "$bigint": "9223372036854775807" }`). The frontend's results grid would need to recognize and pass these through verbatim instead of treating them as numbers.

Pros: Exact precision preserved. Same conceptual model as how Postgres clients like pgAdmin handle it.

Cons:

  • Breaks any frontend code that does arithmetic on bigint columns (saved queries, charts, EXPLAIN ANALYZE viz).
  • AG-Grid cell renderers, sort comparators, and filter inputs need updating to handle string-typed numeric columns.
  • Export-to-CSV path probably needs a tweak.
  • Backward compat: existing query history rows persisted before the fix would still hold rounded values — not a blocker but worth noting.

Option B: Emit as JS `BigInt` via a custom JSON revival step

Replace the default `JSON.parse` of IPC responses with a custom reviver that promotes values flagged as bigint into `BigInt` instances on the JS side. Requires a wire-format change (a sentinel like `{ "$bigint": "..." }`).

Pros: Native JS arithmetic still works (with `BigInt` semantics).

Cons:

  • `BigInt` is not JSON-serializable, so anything that round-trips through `JSON.stringify` (saved queries, exports) needs a serializer wrapper.
  • Mixing `BigInt` and `Number` arithmetic throws `TypeError`. The grid renderers that compare/format cell values would all need to accept both types.

Recommendation

Option A is simpler and aligns with how DBeaver, DataGrip, and pgAdmin treat bigint columns at the display layer. The arithmetic concern is overstated — users who need to compute on bigint columns push that work down to the SQL layer (`SUM`, `AVG`, etc.) in practice. Cell-level JS arithmetic on bigints is rare.

A user-facing setting (`Settings → Results → Render bigint as: [Number | String]`) could keep the existing rounded-number behavior as opt-in for users who rely on it.

Acceptance criteria

  • The repro query above shows exact values in the grid.
  • An export-to-CSV / copy-as-CSV of the result row also preserves exact values.
  • Saved queries with bigint columns reload without further rounding loss.
  • `NUMERIC` is included in the audit and gets matching treatment if needed.
  • A Vitest case in `postgres.rs`'s test module covers a non-2^53-safe i64 input and asserts the decoded JSON matches the chosen wire format (number vs string).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions