feat: accept BigInt values in intColumn() - #63
PedroHenrique0713 wants to merge 1 commit into
Conversation
56da9c5 to
a9aecfb
Compare
Review — level 3Reviewed Verdict: approve with comments. The change is correct, minimal, well-tested at runtime, and matches the signature agreed in #58. Three non-blocking items below, all cheap. ModerateM1 — Stale TSDoc contradicts the widened signature it sits on
The PR updated the two sibling doc blocks ( The natural containment argument — " Fix: copy the updated M2 — New
|
| Change | Test | Failure link | Disposition |
|---|---|---|---|
| bigint accepted, ±, v1 wire bytes | "supports BigInt values in integer fields" | exact ILP line; fails in revert tree with Value must be an integer, received 42 |
covered |
| 2^63-1 without precision loss | "keeps full LONG precision above Number.MAX_SAFE_INTEGER" | exact ILP line; fails in revert tree | covered |
| changed error message | "throws exception if a float is passed as integer field" (updated) | toThrow string; fails in revert tree |
covered |
widened Sender.intColumn type |
none | narrowing to number keeps every gate green |
moderate gap (M3) |
Both new-test ILP literals were recomputed by hand from writeColumn + SenderBufferV1.writeTimestamp (timestampToNanos(1658484769000000n, "us") → 1658484769000000000, no t suffix for a designated timestamp) and match character for character. bufferContent observes the same [0, endOfLastRow) range the transport sends, and would throw on null rather than pass vacuously if the row were not closed.
Notes
- Checked and found clean: capacity math (
checkCapacity([valueStr], 1)reserves exactlybyteLength(valueStr) + 1, andBigInt.prototype.toString()is ASCII-only with no exponent form, soBuffer.writetruncation is unreachable); buffer growth for multi-thousand-digit bigints; interface widening against externalimplements SenderBuffer(method parameters are bivariant, so no break); protocol v1/v2/v3 parity, verified by execution rather than inferred; no code anywhere matches on the old error string. - The changed error message affects every rejected value, not only bigints, but nothing in
src/,test/,examples/, orREADME.mdmatches on it, and the new wording aligns withtimestampColumn. README and examples never mentionintColumn, so no doc updates are needed there. eslint,prettier --checkandtsc --noEmitare clean at head; 174/174 tests pass.- Minor description nit: the "one pre-existing undici FormData error" from
tsc --noEmitdid not reproduce here — tsc is clean at both base and head. Likely an@types/nodedifference; it alleges nothing about the branch, so it is not blocking.
Heads-up: this branch now conflicts with
|
|
Hi @PedroHenrique0713, the change looks good, many thanks for the contribution! However, I think M1 and M2 issues (stale docs and missing range check) should be addressed before merge. Also, a rather big change has just been merged, and there are conflicts on the PR. |
QuestDB's LONG is a 64-bit signed integer, which is wider than the safe integer range of JavaScript's number. Passing a LONG above Number.MAX_SAFE_INTEGER silently lost precision before it ever reached the buffer. intColumn() now takes number | bigint, keeping number for backwards compatibility, and validates the same way timestampColumn() already does for its number | bigint parameter. value.toString() already renders a BigInt without the trailing n, so the ILP line is unchanged in shape. A BigInt carries no width of its own, so the guard also rejects values outside the int64 range: without it, 2^63 would reach the wire as 9223372036854775808i and be rejected by the server instead of here. The QWP path stages the same LONG column, so it takes the same values through checkedInt64(), which longColumn() already uses. The error message for a non-integer changes to mention BigInt, matching the wording timestampColumn() uses; the existing test for it is updated.
a9aecfb to
712fb74
Compare
|
Rebased onto M1 — M2 — the guard now rejects a BigInt outside int64: M3 — no longer reproducible on One thing the rebase forced a decision on: Verified locally: |
Closes #58.
Picking this up since it has been open since February and @saibotsivad, who offered to send a PR, has not had the chance. Happy to step aside if they would rather land it themselves.
Implements exactly the signature @glasstiger asked for in the issue thread:
numberis kept for backwards compatibility.Why
QuestDB's
LONGis a 64-bit signed integer. JavaScript'snumberis a double, so anything aboveNumber.MAX_SAFE_INTEGER(9007199254740991) is already rounded by the time it reachesintColumn()— the loss happens at the call site and nothing downstream can detect it. There was no way to write a full-rangeLONGfrom this client.What changed
src/buffer/base.ts—intColumn()takesnumber | bigint, validated the same waytimestampColumn()already validates its ownnumber | bigintparameter:typeof value !== "bigint" && !Number.isInteger(value).src/buffer/index.tsandsrc/sender.ts— signatures and JSDoc.The wire format is unchanged:
value.toString()renders a BigInt without the trailingn, so the ILP line is identical for a value expressible both ways.Tests
Two new cases in
test/sender.buffer.test.ts:9223372036854775807n(2^63-1, the largestLONG) survives the round trip. As anumberit rounds to 9223372036854775808, which is out of range for the columnOne existing test is updated: the non-integer error message now mentions BigInt, matching the wording
timestampColumn()uses.vitestpasses locally,eslintandprettier --checkare clean.tsc --noEmitreports one pre-existing error about conflictingundiciFormData types, unchanged by this branch (same count on a clean checkout).