diff --git a/src/buffer/base.ts b/src/buffer/base.ts index 3fcf6de..6a54e99 100644 --- a/src/buffer/base.ts +++ b/src/buffer/base.ts @@ -251,9 +251,9 @@ abstract class SenderBufferBase implements SenderBuffer { * @return {SenderBuffer} Returns with a reference to this buffer. * @throws Error if the value is not an integer */ - intColumn(name: string, value: number): SenderBuffer { - if (!Number.isInteger(value)) { - throw new Error(`Value must be an integer, received ${value}`); + intColumn(name: string, value: number | bigint): SenderBuffer { + if (typeof value !== "bigint" && !Number.isInteger(value)) { + throw new Error(`Value must be an integer or BigInt, received ${value}`); } this.writeColumn(name, value, () => { const valueStr = value.toString(); diff --git a/src/buffer/index.ts b/src/buffer/index.ts index 0b61c94..d0558ed 100644 --- a/src/buffer/index.ts +++ b/src/buffer/index.ts @@ -137,11 +137,12 @@ interface SenderBuffer { * Writes a 64-bit signed integer into the buffer. * Use it to insert into LONG, INT, SHORT and BYTE columns. * @param name - Column name. - * @param value - Column value, accepts only number values. + * @param value - Column value, accepts integer or `BigInt` values. Use a + * `BigInt` for LONG values outside the safe integer range of `number`. * @returns Returns with a reference to this buffer. - * @throws Error if the value is not an integer + * @throws Error if the value is not an integer or a `BigInt` */ - intColumn(name: string, value: number): SenderBuffer; + intColumn(name: string, value: number | bigint): SenderBuffer; /** * Writes a timestamp column and its value into the buffer. diff --git a/src/sender.ts b/src/sender.ts index 4a63a3b..1af3224 100644 --- a/src/sender.ts +++ b/src/sender.ts @@ -299,11 +299,14 @@ class Sender { * Use it to insert into LONG, INT, SHORT and BYTE columns. * * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number values. + * @param {number | bigint} value - Column value, accepts integer or `BigInt` + * values. QuestDB's LONG is a 64-bit signed integer, which is wider than the + * safe integer range of `number`, so use a `BigInt` beyond + * `Number.MAX_SAFE_INTEGER` to avoid losing precision. * @return {Sender} Returns with a reference to this sender. - * @throws Error if the value is not an integer + * @throws Error if the value is not an integer or a `BigInt` */ - intColumn(name: string, value: number): Sender { + intColumn(name: string, value: number | bigint): Sender { this.buffer.intColumn(name, value); return this; } diff --git a/test/sender.buffer.test.ts b/test/sender.buffer.test.ts index 9b0cc96..8b02d95 100644 --- a/test/sender.buffer.test.ts +++ b/test/sender.buffer.test.ts @@ -986,7 +986,44 @@ describe("Sender message builder test suite (anything not covered in client inte }); expect(() => sender.table("tableName").intColumn("intField", 123.222), - ).toThrow("Value must be an integer, received 123.222"); + ).toThrow("Value must be an integer or BigInt, received 123.222"); + await sender.close(); + }); + + it("supports BigInt values in integer fields", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "1", + host: "host", + init_buf_size: 1024, + }); + await sender + .table("tableName") + .intColumn("small", 42n) + .intColumn("negative", -42n) + .at(1658484769000000, "us"); + expect(bufferContent(sender)).toBe( + "tableName small=42i,negative=-42i 1658484769000000000\n", + ); + await sender.close(); + }); + + it("keeps full LONG precision above Number.MAX_SAFE_INTEGER", async function () { + const sender = new Sender({ + protocol: "tcp", + protocol_version: "1", + host: "host", + init_buf_size: 1024, + }); + // 2^63-1, the largest QuestDB LONG. As a `number` this rounds to + // 9223372036854775808, which is out of range for the column. + await sender + .table("tableName") + .intColumn("maxLong", 9223372036854775807n) + .at(1658484769000000, "us"); + expect(bufferContent(sender)).toBe( + "tableName maxLong=9223372036854775807i 1658484769000000000\n", + ); await sender.close(); });