From 9935f99bbe8d626fd0f80f2165da9c4796cb4ee9 Mon Sep 17 00:00:00 2001 From: Andrey Pechkurov Date: Mon, 18 Sep 2023 14:44:14 +0300 Subject: [PATCH] feat(nodejs): support BigInt type for timestamp values --- docs/Sender.html | 14 ++++++--- docs/index.html | 2 +- docs/index.js.html | 2 +- docs/module-@questdb_nodejs-client.html | 38 +---------------------- docs/src_sender.js.html | 21 +++++++------ examples/basic.js | 41 ++++++++++--------------- notes.md | 4 --- src/sender.js | 23 +++++++------- src/validation.js | 20 ++++++------ test/sender.test.js | 32 ++++++++++++++++--- 10 files changed, 91 insertions(+), 106 deletions(-) diff --git a/docs/Sender.html b/docs/Sender.html index 674288a..e91d0a5 100644 --- a/docs/Sender.html +++ b/docs/Sender.html @@ -281,6 +281,9 @@
Parameters:
string +| + +bigint @@ -290,7 +293,7 @@
Parameters:
- A string represents the designated timestamp in nanoseconds. + A string or BigInt that represents the designated timestamp in epoch nanoseconds. @@ -420,7 +423,7 @@

atNowSource:
@@ -2266,6 +2269,9 @@
Parameters:
number +| + +bigint @@ -2275,7 +2281,7 @@
Parameters:
- Column value, accepts only number objects. + Epoch timestamp in microseconds, accepts only numbers or BigInts. @@ -2390,7 +2396,7 @@

Home

Modules

  • diff --git a/docs/index.html b/docs/index.html index c53c555..bfb95e8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -248,7 +248,7 @@

    Home

    Modules

    • diff --git a/docs/index.js.html b/docs/index.js.html index 4a8e884..ffc31a1 100644 --- a/docs/index.js.html +++ b/docs/index.js.html @@ -53,7 +53,7 @@

      Home

      Modules

      • diff --git a/docs/module-@questdb_nodejs-client.html b/docs/module-@questdb_nodejs-client.html index c2dd799..e63fa98 100644 --- a/docs/module-@questdb_nodejs-client.html +++ b/docs/module-@questdb_nodejs-client.html @@ -28,10 +28,6 @@

        Module: @questdb/nodejs-client

        - - - -
        @@ -42,20 +38,6 @@

        Module: @questdb/nodejs-client

        - - - - - - - - - - - - - -
        @@ -98,24 +80,6 @@

        Module: @questdb/nodejs-client

        - - - - - - - - - - - - - - - - - - @@ -156,7 +120,7 @@

        Home

        Modules

        • diff --git a/docs/src_sender.js.html b/docs/src_sender.js.html index 67e5219..881c7e5 100644 --- a/docs/src_sender.js.html +++ b/docs/src_sender.js.html @@ -365,12 +365,12 @@

          Source: src/sender.js

          * Write a timestamp column with its value into the buffer of the sender. * * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number objects. + * @param {number | bigint} value - Epoch timestamp in microseconds, accepts only numbers or BigInts. * @return {Sender} Returns with a reference to this sender. */ timestampColumn(name, value) { - if (!Number.isInteger(value)) { - throw new Error(`Value must be an integer, received ${value}`); + if (typeof value !== "bigint" || !Number.isInteger(value)) { + throw new Error(`Value must be an integer or BigInt, received ${value}`); } writeColumn(this, name, value, () => { const valueStr = value.toString(); @@ -384,19 +384,20 @@

          Source: src/sender.js

          /** * Closing the row after writing the designated timestamp into the buffer of the sender. * - * @param {string} timestamp - A string represents the designated timestamp in nanoseconds. + * @param {string | bigint} timestamp - A string or BigInt that represents the designated timestamp in epoch nanoseconds. */ at(timestamp) { if (!this.hasSymbols && !this.hasColumns) { throw new Error("The row must have a symbol or column set before it is closed"); } - if (typeof timestamp !== "string") { - throw new Error(`The designated timestamp must be of type string, received ${typeof timestamp}`); + if (typeof timestamp !== "string" || typeof timestamp !== "bigint") { + throw new Error(`The designated timestamp must be of type string or BigInt, received ${typeof timestamp}`); } validateDesignatedTimestamp(timestamp); - checkCapacity(this, [], 2 + timestamp.length); + const timestampStr = timestamp.toString(); + checkCapacity(this, [], 2 + timestampStr.length); write(this, ' '); - write(this, timestamp); + write(this, timestampStr); write(this, '\n'); startNewRow(this); } @@ -529,7 +530,7 @@

          Source: src/sender.js

          } exports.Sender = Sender; -exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE +exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE;
        @@ -546,7 +547,7 @@

        Home

        Modules

        • diff --git a/examples/basic.js b/examples/basic.js index 7d05921..9290b8d 100644 --- a/examples/basic.js +++ b/examples/basic.js @@ -1,7 +1,7 @@ const { Sender } = require("@questdb/nodejs-client"); async function run() { - // create a sender with a 4k buffer + // create a sender with a 4KB buffer const sender = new Sender({ bufferSize: 4096 }); // connect to QuestDB @@ -9,39 +9,32 @@ async function run() { await sender.connect({ port: 9009, host: "localhost" }); // add rows to the buffer of the sender + let bday = Date.parse("1856-07-10"); sender - .table("prices") - .symbol("instrument", "EURUSD") - .floatColumn("bid", 1.0195) - .floatColumn("ask", 1.0221) - .atNow(); - + .table("inventors") + .symbol("born", "Austrian Empire") + .timestampColumn("birthday", BigInt(bday) * 1000n) // epoch in micros (BigInt) + .intColumn("id", 0) + .stringColumn("name", "Nicola Tesla") + .at(BigInt(Date.now()) * 1000_000n); // epoch in nanos (BigInt) + bday = Date.parse("1847-02-11"); sender - .table("prices") - .symbol("instrument", "GBPUSD") - .floatColumn("bid", 1.2076) - .floatColumn("ask", 1.2082) + .table("inventors") + .symbol("born", "USA") + .timestampColumn("birthday", BigInt(bday) * 1000n) + .intColumn("id", 1) + .stringColumn("name", "Thomas Alva Edison") .atNow(); // flush the buffer of the sender, sending the data to QuestDB // the buffer is cleared after the data is sent and the sender is ready to accept new data await sender.flush(); - // add rows to the buffer again and send it to the server - sender - .table("prices") - .symbol("instrument", "EURUSD") - .floatColumn("bid", 1.0197) - .floatColumn("ask", 1.0224) - .atNow(); - - await sender.flush(); - - // close the connection after all rows ingested + // close the connection after all rows were sent await sender.close(); return 0; } run() - .then((value) => console.log(value)) - .catch((err) => console.log(err)); + .then(console.log) + .catch(console.error); diff --git a/notes.md b/notes.md index a0ba2a9..d2f840e 100644 --- a/notes.md +++ b/notes.md @@ -1,6 +1,2 @@ ### Certs used in tests generated by running: > ./scripts/generateCerts.sh . questdbPwd123 - -### TODO: -- Would be nice to accept alternative logger implementations to be used instead of console -- If the buffer had to be extended, shrink it back to original size on a subsequent flush() call? diff --git a/src/sender.js b/src/sender.js index 3b25479..da67a77 100644 --- a/src/sender.js +++ b/src/sender.js @@ -329,7 +329,7 @@ class Sender { checkCapacity(this, [valueStr], 1 + valueStr.length); write(this, valueStr); write(this, 'i'); - }, "number"); + }); return this; } @@ -337,38 +337,39 @@ class Sender { * Write a timestamp column with its value into the buffer of the sender. * * @param {string} name - Column name. - * @param {number} value - Column value, accepts only number objects. + * @param {number | bigint} value - Epoch timestamp in microseconds, accepts only numbers or BigInts. * @return {Sender} Returns with a reference to this sender. */ timestampColumn(name, value) { - if (!Number.isInteger(value)) { - throw new Error(`Value must be an integer, received ${value}`); + if (typeof value !== "bigint" && !Number.isInteger(value)) { + throw new Error(`Value must be an integer or BigInt, received ${value}`); } writeColumn(this, name, value, () => { const valueStr = value.toString(); checkCapacity(this, [valueStr], 1 + valueStr.length); write(this, valueStr); write(this, 't'); - }, "number"); + }); return this; } /** * Closing the row after writing the designated timestamp into the buffer of the sender. * - * @param {string} timestamp - A string represents the designated timestamp in nanoseconds. + * @param {string | bigint} timestamp - A string or BigInt that represents the designated timestamp in epoch nanoseconds. */ at(timestamp) { if (!this.hasSymbols && !this.hasColumns) { throw new Error("The row must have a symbol or column set before it is closed"); } - if (typeof timestamp !== "string") { - throw new Error(`The designated timestamp must be of type string, received ${typeof timestamp}`); + if (typeof timestamp !== "string" && typeof timestamp !== "bigint") { + throw new Error(`The designated timestamp must be of type string or BigInt, received ${typeof timestamp}`); } validateDesignatedTimestamp(timestamp); - checkCapacity(this, [], 2 + timestamp.length); + const timestampStr = timestamp.toString(); + checkCapacity(this, [], 2 + timestampStr.length); write(this, ' '); - write(this, timestamp); + write(this, timestampStr); write(this, '\n'); startNewRow(this); } @@ -441,7 +442,7 @@ function writeColumn(sender, name, value, writeValue, valueType) { if (typeof name !== "string") { throw new Error(`Column name must be a string, received ${typeof name}`); } - if (typeof value !== valueType) { + if (valueType != null && typeof value !== valueType) { throw new Error(`Column value must be of type ${valueType}, received ${typeof value}`); } if (!sender.hasTable) { diff --git a/src/validation.js b/src/validation.js index 29b84a9..981944e 100644 --- a/src/validation.js +++ b/src/validation.js @@ -123,17 +123,19 @@ function validateColumnName(name) { * Validates a designated timestamp. The value must contain only digits.
          * Throws an error if the value is invalid. * - * @param {string} timestamp - The table name to validate. + * @param {string | bigint} timestamp - The table name to validate. */ function validateDesignatedTimestamp(timestamp) { - const len = timestamp.length; - if (len === 0) { - throw new Error("Empty string is not allowed as designated timestamp"); - } - for (let i = 0; i < len; i++) { - let ch = timestamp[i]; - if (ch < '0' || ch > '9') { - throw new Error(`Invalid character in designated timestamp: ${ch}`); + if (typeof timestamp === "string") { + const len = timestamp.length; + if (len === 0) { + throw new Error("Empty string is not allowed as designated timestamp"); + } + for (let i = 0; i < len; i++) { + let ch = timestamp[i]; + if (ch < '0' || ch > '9') { + throw new Error(`Invalid character in designated timestamp: ${ch}`); + } } } } diff --git a/test/sender.test.js b/test/sender.test.js index 93b6f64..48af0f7 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -255,7 +255,7 @@ describe('Client interop test suite', function () { }); describe('Sender message builder test suite (anything not covered in client interop test suite)', function () { - it('supports timestamp fields', function () { + it('supports timestamp field as number', function () { const sender = new Sender({bufferSize: 1024}); sender.table("tableName") .booleanColumn("boolCol", true) @@ -266,7 +266,18 @@ describe('Sender message builder test suite (anything not covered in client inte ); }); - it('supports setting designated timestamp from client', function () { + it('supports timestamp field as BigInt', function () { + const sender = new Sender({bufferSize: 1024}); + sender.table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000n) + .atNow(); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t\n" + ); + }); + + it('supports setting designated timestamp as string from client', function () { const sender = new Sender({bufferSize: 1024}); sender.table("tableName") .booleanColumn("boolCol", true) @@ -277,6 +288,17 @@ describe('Sender message builder test suite (anything not covered in client inte ); }); + it('supports setting designated timestamp as BigInt from client', function () { + const sender = new Sender({bufferSize: 1024}); + sender.table("tableName") + .booleanColumn("boolCol", true) + .timestampColumn("timestampCol", 1658484765000000) + .at(1658484769000000123n); + expect(sender.toBufferView().toString()).toBe( + "tableName boolCol=t,timestampCol=1658484765000000t 1658484769000000123\n" + ); + }); + it('throws exception if table name is not a string', function () { const sender = new Sender({bufferSize: 1024}); expect( @@ -403,16 +425,16 @@ describe('Sender message builder test suite (anything not covered in client inte expect( () => sender.table("tableName") .timestampColumn("intField", 123.222) - ).toThrow("Value must be an integer, received 123.222"); + ).toThrow("Value must be an integer or BigInt, received 123.222"); }); - it('throws exception if designated timestamp is not a string', function () { + it('throws exception if designated timestamp is not a string or bigint', function () { const sender = new Sender({bufferSize: 1024}); expect( () => sender.table("tableName") .symbol("name", "value") .at(23232322323) - ).toThrow("The designated timestamp must be of type string, received number"); + ).toThrow("The designated timestamp must be of type string or BigInt, received number"); }); it('throws exception if designated timestamp is an empty string', function () {