From 4ba77749d72493fc7a822bdf01de534eaedfd379 Mon Sep 17 00:00:00 2001 From: Andrei Pechkurov <37772591+puzpuzpuz@users.noreply.github.com> Date: Mon, 18 Sep 2023 18:06:14 +0300 Subject: [PATCH] feat(nodejs): support BigInt type for timestamp values (#20) * feat(nodejs): support BigInt type for timestamp values * Fix typo * fix doc and version bump --------- Co-authored-by: glasstiger --- docs/Sender.html | 14 ++++++--- docs/index.html | 2 +- docs/index.js.html | 2 +- docs/module-@questdb_nodejs-client.html | 40 ++---------------------- docs/src_sender.js.html | 27 ++++++++-------- examples/basic.js | 41 ++++++++++--------------- notes.md | 4 --- package-lock.json | 4 +-- package.json | 2 +- src/sender.js | 23 +++++++------- src/validation.js | 20 ++++++------ test/sender.test.js | 32 ++++++++++++++++--- types/src/sender.d.ts | 8 ++--- types/src/sender.d.ts.map | 2 +- types/src/validation.d.ts | 4 +-- types/src/validation.d.ts.map | 2 +- 16 files changed, 106 insertions(+), 121 deletions(-) diff --git a/docs/Sender.html b/docs/Sender.html index 674288a..4096142 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..9171fb4 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..5f2c899 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..79000d2 100644 --- a/docs/module-@questdb_nodejs-client.html +++ b/docs/module-@questdb_nodejs-client.html @@ -27,11 +27,7 @@

        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..8454273 100644 --- a/docs/src_sender.js.html +++ b/docs/src_sender.js.html @@ -357,7 +357,7 @@

          Source: src/sender.js

          checkCapacity(this, [valueStr], 1 + valueStr.length); write(this, valueStr); write(this, 'i'); - }, "number"); + }); return this; } @@ -365,38 +365,39 @@

          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(); 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); } @@ -469,7 +470,7 @@

          Source: src/sender.js

          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) { @@ -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/package-lock.json b/package-lock.json index e8470c7..0dc1a93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@questdb/nodejs-client", - "version": "1.0.4", + "version": "1.0.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@questdb/nodejs-client", - "version": "1.0.4", + "version": "1.0.5", "license": "Apache-2.0", "devDependencies": { "eslint": "^8.21.0", diff --git a/package.json b/package.json index f7fa419..cd03e3b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@questdb/nodejs-client", - "version": "1.0.4", + "version": "1.0.5", "description": "QuestDB Node.js Client", "main": "index.js", "types": "types/index.d.ts", 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..aef799a 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 timestamp 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 () { diff --git a/types/src/sender.d.ts b/types/src/sender.d.ts index 40c5579..c7cd5e7 100644 --- a/types/src/sender.d.ts +++ b/types/src/sender.d.ts @@ -150,16 +150,16 @@ export 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: string, value: number): Sender; + timestampColumn(name: string, value: number | bigint): Sender; /** * 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: string): void; + at(timestamp: string | bigint): void; /** * Closing the row without writing designated timestamp into the buffer of the sender.
          * Designated timestamp will be populated by the server on this record. diff --git a/types/src/sender.d.ts.map b/types/src/sender.d.ts.map index d3e667d..bdde905 100644 --- a/types/src/sender.d.ts.map +++ b/types/src/sender.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/sender.js"],"names":[],"mappings":";;;AAWA;;;;;;;;;;;;GAYG;AACH;IAeI;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,sBApBW,MAAM,EAmChB;IAnDD,eAAe,CAAC,YAAI;IACpB,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,qBAAa;IAC7B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,YAAI;IA0CpB;;;;;;OAMG;IACH,mBAFW,MAAM,QAShB;IAED;;;;;OAKG;IACH,SAFY,MAAM,CAMjB;IAED;;;;;;;OAOG;IACH,iBALW,IAAI,cAAc,GAAG,IAAI,iBAAiB,WAC1C,OAAO,GAEN,QAAQ,OAAO,CAAC,CAoD3B;IAED;;;OAGG;IACH,uBAKC;IAED;;;;;OAKG;IACH,SAFY,QAAQ,OAAO,CAAC,CAY3B;IAED;;;;OAIG;IACH,yBAHY,MAAM,CAOjB;IAED;;;;OAIG;IACH,wBAHY,MAAM,CAWjB;IAED;;;;;OAKG;IACH,aAHW,MAAM,GACL,MAAM,CAcjB;IAED;;;;;;OAMG;IACH,aAJW,MAAM,SACN,GAAG,GACF,MAAM,CAkBjB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAUjB;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,SACN,OAAO,GACN,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,kBAJW,MAAM,SACN,MAAM,GACL,MAAM,CASjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;;;OAMG;IACH,sBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;OAIG;IACH,cAFW,MAAM,QAehB;IAED;;;OAGG;IACH,cAOC;CACJ;AA1XD,uCAAiC"} \ No newline at end of file +{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/sender.js"],"names":[],"mappings":";;;AAWA;;;;;;;;;;;;GAYG;AACH;IAeI;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,sBApBW,MAAM,EAmChB;IAnDD,eAAe,CAAC,YAAI;IACpB,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,qBAAa;IAC7B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,YAAI;IA0CpB;;;;;;OAMG;IACH,mBAFW,MAAM,QAShB;IAED;;;;;OAKG;IACH,SAFY,MAAM,CAMjB;IAED;;;;;;;OAOG;IACH,iBALW,IAAI,cAAc,GAAG,IAAI,iBAAiB,WAC1C,OAAO,GAEN,QAAQ,OAAO,CAAC,CAoD3B;IAED;;;OAGG;IACH,uBAKC;IAED;;;;;OAKG;IACH,SAFY,QAAQ,OAAO,CAAC,CAY3B;IAED;;;;OAIG;IACH,yBAHY,MAAM,CAOjB;IAED;;;;OAIG;IACH,wBAHY,MAAM,CAWjB;IAED;;;;;OAKG;IACH,aAHW,MAAM,GACL,MAAM,CAcjB;IAED;;;;;;OAMG;IACH,aAJW,MAAM,SACN,GAAG,GACF,MAAM,CAkBjB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAUjB;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,SACN,OAAO,GACN,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,kBAJW,MAAM,SACN,MAAM,GACL,MAAM,CASjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;;;OAMG;IACH,sBAJW,MAAM,SACN,MAAM,GAAG,MAAM,GACd,MAAM,CAajB;IAED;;;;OAIG;IACH,cAFW,MAAM,GAAG,MAAM,QAgBzB;IAED;;;OAGG;IACH,cAOC;CACJ;AA3XD,uCAAiC"} \ No newline at end of file diff --git a/types/src/validation.d.ts b/types/src/validation.d.ts index c57d8a1..b84b604 100644 --- a/types/src/validation.d.ts +++ b/types/src/validation.d.ts @@ -16,7 +16,7 @@ export function validateColumnName(name: string): void; * 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 timestamp to validate. */ -export function validateDesignatedTimestamp(timestamp: string): void; +export function validateDesignatedTimestamp(timestamp: string | bigint): void; //# sourceMappingURL=validation.d.ts.map \ No newline at end of file diff --git a/types/src/validation.d.ts.map b/types/src/validation.d.ts.map index b40e30e..958bf40 100644 --- a/types/src/validation.d.ts.map +++ b/types/src/validation.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wCAFW,MAAM,QAyDhB;AAED;;;;;GAKG;AACH,yCAFW,MAAM,QAgDhB;AAED;;;;;GAKG;AACH,uDAFW,MAAM,QAahB"} \ No newline at end of file +{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/validation.js"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wCAFW,MAAM,QAyDhB;AAED;;;;;GAKG;AACH,yCAFW,MAAM,QAgDhB;AAED;;;;;GAKG;AACH,uDAFW,MAAM,GAAG,MAAM,QAezB"} \ No newline at end of file