test(postgrest): add int8/bigint precision e2e over PostgREST#2461
test(postgrest): add int8/bigint precision e2e over PostgREST#2461Maliik-B wants to merge 3 commits into
Conversation
Adds test/bigint.test.ts and a bigint_precision fixture demonstrating int8 behavior around Number.MAX_SAFE_INTEGER on read (with and without a ::text cast) and on write. Refs: supabase/postgres-meta#1083
avallete
left a comment
There was a problem hiding this comment.
Hey @Maliik-B,
Thanks for putting together the runtime demonstration that's exactly the evidence we needed to settle the type direction.
I think the cleanest shape is to make bigint_as accept the full set of representations rather than a single scalar choice:
number · bigint · string · number | bigint · number | string · …
That way users pick the tradeoff that fits their app, and we lean on defaults that preserve today's behavior:
- Read (SELECT) defaults to
number. PostgREST emitsint8as a JSON number, so precision is already lost by the timeJSON.parseruns — typing it otherwise would misrepresent what comes over the wire. Users who need the exact value cast to::textand get a string back (and the type inference properly infer that the query result for a casted column will match the cast as demonstrated by your test). - Write (INSERT/UPDATE) —
number | bigintis the value I'd recommend.bigintis the lossless channel (postgrest-js serializes it to a JSON string), andnumberkeeps the ergonomic path for the common small-value case. It's pure input widening, so it's backward compatible.
The one thing to document loudly: any value that includes string trades away compile-time validation an arbitrary non-numeric string passes the type check and only fails at runtime in PostgREST. Totally fine as a deliberate opt-in, as long as users know what they're choosing.
What do you think?
@supabase/auth-js
@supabase/functions-js
@supabase/postgrest-js
@supabase/realtime-js
@supabase/storage-js
@supabase/supabase-js
commit: |
|
This shape works, and it matches what the test shows. Let me confirm I have the model right before I reshape the generator:
Two things to pin down so I build the right thing:
Once you confirm those, I will update #1083 to apply bigint_as to the Insert/Update types only, leave Row as number, and document the string tradeoff. |
|
|
Sounds good, that all works for me. And thanks for the prompt replies.
I will push the type-override demo to #2461 first so the DX is concrete, then reshape the bigint_as option in postgres-meta#1083 to emit the |-split union on Insert/Update only (Row left as number), defaulting to number. I will link both back here. |
Per the read/write model worked out on supabase/supabase-js#2461: the Row stays number (an un-cast int8 is a lossy JSON number, and ::text already infers string on the casted column), and only Insert/Update widen to the bigint_as union. The option is now a pipe-split string accepting number and bigint; string is dropped, since an arbitrary non-numeric value would pass the type check and fail only at runtime in PostgREST.
|
The type-override demo is up in this PR (test/bigint.test-d.ts), and postgres-meta#1083 is reshaped to match. The demo overrides bigint_precision's Insert/Update to number | bigint and checks the DX directly:
postgres-meta#1083 now applies bigint_as per direction to match:
Generator change plus tests (default number, number|bigint, and bigint): supabase/postgres-meta#1083. Ready for another look whenever you have a moment, and happy to adjust the scope or the accepted set from here. |
What
Adds an end-to-end test (
test/bigint.test.ts) and a fixture table (bigint_precision) showing howint8/bigintvalues behave over PostgREST aroundNumber.MAX_SAFE_INTEGER(2^53 - 1), on read (with and without a::textcast) and on write.Why
This came out of the review on supabase/postgres-meta#1083 (a
bigint_astypegen option). @avallete asked to demonstrate at runtime that the type adjustment matches PostgREST behavior, including on update, rather than asserting it at the type level. This test does that against the postgrest-js stack.What it shows
Against a seeded value of
9223372036854775807(2^63 - 1):9223372036854776000. The exact value is already lost, because PostgREST emitsint8as a JSON number andJSON.parserounds it to the nearest double.select('big_value::text')) returns the exact string"9223372036854775807". PostgREST returns a JSON string, which survivesJSON.parse."9007199254740993"and reading back with a cast returns"9007199254740993". The precision loss is a client-side concern, not a PostgREST one, so a string (or aBigInt, which postgrest-js already serializes to a string) goes in intact.Number("9007199254740993")is already9007199254740992in JS, and that is what gets stored.The string-write case carries a
@ts-expect-error: the generatedbig_valueisnumber, so the lossless string form is a type error today. That is the gap the postgres-metabigint_as=stringoption closes, by generating theInsert/Updatecolumn asstring.Notes
test/types.generated.tsgains thebigint_precisiontable. I added that entry in place to keep the diff focused. A fullcodegen:postgrestrun also surfaces unrelated pre-existing drift in that file (a couple of functions added/removed in the schema since it was last generated), which I left out of this change. Happy to do a full regen instead if you would prefer.src/change.Thanks @avallete for the review direction on postgres-meta#1083.