Skip to content

fix(drivers): preserve BIGINT precision past JS Number.MAX_SAFE_INTEGER#220

Merged
debba merged 2 commits into
TabularisDB:mainfrom
NewtTheWolf:fix/bigint-precision-issue-210
May 19, 2026
Merged

fix(drivers): preserve BIGINT precision past JS Number.MAX_SAFE_INTEGER#220
debba merged 2 commits into
TabularisDB:mainfrom
NewtTheWolf:fix/bigint-precision-issue-210

Conversation

@NewtTheWolf
Copy link
Copy Markdown
Contributor

@NewtTheWolf NewtTheWolf commented May 18, 2026

Summary

  • Closes [Bug]: When using values above 2^53, the accuracy of the interface display is lost #210. BIGINT values above 2^53 - 1 (e.g. snowflake ids like 844197938335842304) lost their last digits in the UI because the read path emitted them as JSON numbers and the frontend's JSON.parse rounds to the nearest IEEE 754 double.
  • New drivers::common::safe_int helper emits i64/u64 as a JSON string only when the value falls outside JS's safe integer range; smaller values stay native numbers, so the wire format for the 99% case is byte-identical to before.
  • Wired through MySQL (u64/i64 scalar fallback), Postgres (INT8, XID8, MONEY), and SQLite (INTEGER).
  • Mirror change on the write-back side: a JSON string that parses cleanly as an i64 outside the safe range is bound as a native integer, so editing or deleting by a large bigint PK still matches. Postgres wraps the bind in CAST AS bigint to satisfy tokio-postgres' strict type checking; MySQL and SQLite rely on their implicit numeric coercion.
  • 17 new unit tests covering boundary cases and the snowflake roundtrip. Full driver test suite: 297 passed.
  • Demo seed bigint_demo added to MySQL and Postgres init scripts so the boundary cases are reproducible without ad-hoc SQL (mirrors the text_demo / json_demo pattern).

Behaviour notes

  • Sort and filter are unaffected because both run server-side. handleSort in Editor.tsx builds an ORDER BY clause and the filter toolbar builds a WHERE clause; the comparison happens in the database against the native BIGINT column, never in JS. The grid has no client-side global/quick filter that could trip over a mixed number/string column.
  • Cells that now arrive as strings are still rendered identically (the renderer already accepts either shape), and the row editor sends them back as strings, which the write-back path translates back to i64 before binding.

Test plan

  • cargo test --lib drivers → 297 passed
  • cargo build --lib clean, no new clippy warnings on touched files
  • MySQL manual test (bigint_demo table):
    • Read: snowflake 844197938335842304 displays exactly (was …842300)
    • Read: i64::MAX 9223372036854775807 displays exactly
    • Read: u64 18446744073709551614 (BIGINT UNSIGNED above i64::MAX) displays exactly
    • Boundary: 42 and 9007199254740991 stay JSON numbers (sortable numerically)
    • Write UPDATE of non-PK column on the snowflake row matches exactly one row
    • INSERT new row with id = 7777777777777777777 succeeds
  • Postgres manual test (bigint_demo table):
    • All read cases pass for BIGINT, XID8 and MONEY columns
    • UPDATE note on snowflake PK row → exactly one row updated
    • UPDATE amount on snowflake row → bigint outside safe range round-trips through SET
    • INSERT new row with id = 7777777777777777777 succeeds

Snowflake-style ids and other BIGINT values above 2^53 - 1 lost their
last digits in the UI because the read path serialised them as JSON
numbers, which JSON.parse on the frontend rounds to the nearest IEEE 754
double.

Add a common helper that emits i64/u64 values as JSON strings only when
they fall outside JS's safe range, and wire it through every place a
driver hands a 64-bit integer to the renderer: MySQL's scalar fallback
(both signed and unsigned), Postgres' INT8 / XID8 / MONEY paths, and
SQLite's INTEGER path. Small values stay native numbers so sorting,
filtering and editing keep working untouched.

Mirror the change on the write-back side: a JSON string that parses as
an i64 outside the safe range is bound as a native integer, so editing
or deleting by a large bigint primary key still matches the row. The
Postgres PK predicate wraps the bind in CAST AS bigint to satisfy
tokio-postgres' strict type checking; MySQL and SQLite rely on their
implicit numeric coercion.

Closes TabularisDB#210
…esting

Mirrors the text_demo / json_demo pattern with seven rows that exercise
the JS-safe-integer boundary from both sides: small values that stay
JSON numbers, the 2^53 / 2^53 - 1 boundary, the snowflake id from the
original report, i64::MAX, a u64 value above i64::MAX, and a negative
snowflake. Postgres also covers xid8 and money since both are backed by
64-bit integers.
@debba
Copy link
Copy Markdown
Collaborator

debba commented May 19, 2026

Looks good and thanks for demo seed :)

@debba debba merged commit eb1721f into TabularisDB:main May 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: When using values above 2^53, the accuracy of the interface display is lost

2 participants