From ed4779994db8d85d8b9e5dd3971556b2f4bf52cb Mon Sep 17 00:00:00 2001 From: Lukundo Kileha Date: Wed, 28 Jul 2021 23:13:04 +0300 Subject: [PATCH 1/3] Added partial BigInt support Moved from es6 to support bigint Changed from getNumber to getBigInt Avoided blobal redeclaration of function/variables Improved BigInt support, handled bind params for bigInt Logical change on how big int is supported BigInt is not fully supported at WASM level Added config to enable and disable bigInt support Added documentation Changed var to const as per readme standard --- .eslintrc.js | 4 +-- README.md | 23 ++++++++++++++++ src/api.js | 53 ++++++++++++++++++++++++++++++++----- src/exported_functions.json | 1 + src/worker.js | 9 ++++--- 5 files changed, 77 insertions(+), 13 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index f730a261..50f0c718 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { env: { browser: true, - es6: true, + es2020: true, node: true }, extends: [ @@ -24,7 +24,7 @@ module.exports = { "!/.eslintrc.js" ], parserOptions: { - ecmaVersion: 5, + ecmaVersion: 2020, sourceType: "script" }, rules: { diff --git a/README.md b/README.md index 9b0f3857..d97c7c45 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,29 @@ Example: }); ``` +### Enabling BigInt support +If you need ```BigInt``` support, it is partially supported since most browsers now supports it including Safari.Binding ```BigInt``` is still not supported, only getting ```BigInt``` from the database is supported for now. + +```html + +``` +On WebWorker, you can just add ```config``` param before posting a message. With this, you wont have to pass config param on ```get``` function. + +```html + +``` See [examples/GUI/gui.js](examples/GUI/gui.js) for a full working example. diff --git a/src/api.js b/src/api.js index 52bbab58..afb4ba0c 100644 --- a/src/api.js +++ b/src/api.js @@ -116,6 +116,13 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { "number", ["number", "number", "number"] ); + + var sqlite3_bind_int64 = cwrap( + "sqlite3_bind_int64", + "number", + ["number", "number"] + ); + var sqlite3_bind_parameter_index = cwrap( "sqlite3_bind_parameter_index", "number", @@ -358,6 +365,15 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return sqlite3_column_double(this.stmt, pos); }; + Statement.prototype.getBigInt = function getBigInt(pos) { + if (pos == null) { + pos = this.pos; + this.pos += 1; + } + var text = sqlite3_column_text(this.stmt, pos); + return BigInt(text); + }; + Statement.prototype.getString = function getString(pos) { if (pos == null) { pos = this.pos; @@ -390,8 +406,12 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { Print all the rows of the table test to the console var stmt = db.prepare("SELECT * FROM test"); while (stmt.step()) console.log(stmt.get()); + + Enable BigInt support + var stmt = db.prepare("SELECT * FROM test"); + while (stmt.step()) console.log(stmt.get(null, {useBigInt: true})); */ - Statement.prototype["get"] = function get(params) { + Statement.prototype["get"] = function get(params, config = {}) { if (params != null && this["bind"](params)) { this["step"](); } @@ -400,6 +420,11 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { for (var field = 0; field < ref; field += 1) { switch (sqlite3_column_type(this.stmt, field)) { case SQLITE_INTEGER: + var getfunc = config.useBigInt + ? this.getBigInt(field) + : this.getNumber(field); + results1.push(getfunc); + break; case SQLITE_FLOAT: results1.push(this.getNumber(field)); break; @@ -451,8 +476,8 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { console.log(stmt.getAsObject()); // Will print {nbr:5, data: Uint8Array([1,2,3]), null_value:null} */ - Statement.prototype["getAsObject"] = function getAsObject(params) { - var values = this["get"](params); + Statement.prototype["getAsObject"] = function getAsObject(params, config = {}) { + var values = this["get"](params, config); var names = this["getColumnNames"](); var rowObject = {}; for (var i = 0; i < names.length; i += 1) { @@ -547,6 +572,15 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return true; }; + Statement.prototype.bindBigInt = function bindBigInt(num, pos) { + if (pos == null) { + pos = this.pos; + this.pos += 1; + } + this.db.handleError(sqlite3_bind_int64(this.stmt, pos, num)); + return true; + }; + Statement.prototype.bindNull = function bindNull(pos) { if (pos == null) { pos = this.pos; @@ -560,10 +594,15 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { pos = this.pos; this.pos += 1; } + switch (typeof val) { case "string": return this.bindString(val, pos); case "number": + return this.bindNumber(val + 0, pos); + case "bigint": + // BigInt is not fully supported yet at WASM level. + return this.bindString(val.toString(), pos); case "boolean": return this.bindNumber(val + 0, pos); case "object": @@ -899,7 +938,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { (separated by `;`). This limitation does not apply to params as an object. * @return {Database.QueryExecResult[]} The results of each statement */ - Database.prototype["exec"] = function exec(sql, params) { + Database.prototype["exec"] = function exec(sql, params, config = {}) { if (!this.db) { throw "Database closed"; } @@ -937,7 +976,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { }; results.push(curresult); } - curresult["values"].push(stmt["get"]()); + curresult["values"].push(stmt["get"](null, config)); } stmt["free"](); } @@ -971,7 +1010,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { function (row){console.log(row.name + " is a grown-up.")} ); */ - Database.prototype["each"] = function each(sql, params, callback, done) { + Database.prototype["each"] = function each(sql, params, callback, done, config = {}) { var stmt; if (typeof params === "function") { done = callback; @@ -981,7 +1020,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { stmt = this["prepare"](sql, params); try { while (stmt["step"]()) { - callback(stmt["getAsObject"]()); + callback(stmt["getAsObject"](null, config)); } } finally { stmt["free"](); diff --git a/src/exported_functions.json b/src/exported_functions.json index b93b07d2..309161ae 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -13,6 +13,7 @@ "_sqlite3_bind_blob", "_sqlite3_bind_double", "_sqlite3_bind_int", +"_sqlite3_bind_int64", "_sqlite3_bind_parameter_index", "_sqlite3_step", "_sqlite3_column_count", diff --git a/src/worker.js b/src/worker.js index c4f31d83..aed0a82c 100644 --- a/src/worker.js +++ b/src/worker.js @@ -15,6 +15,7 @@ function onModuleReady(SQL) { var buff; var data; var result; data = this["data"]; + var config = data["config"] ? data["config"] : {}; switch (data && data["action"]) { case "open": buff = data["buffer"]; @@ -32,26 +33,26 @@ function onModuleReady(SQL) { } return postMessage({ id: data["id"], - results: db.exec(data["sql"], data["params"]) + results: db.exec(data["sql"], data["params"], config) }); case "each": if (db === null) { createDb(); } - var callback = function callback(row) { + var callbackfunc = function callback(row) { return postMessage({ id: data["id"], row: row, finished: false }); }; - var done = function done() { + var donefunc = function done() { return postMessage({ id: data["id"], finished: true }); }; - return db.each(data["sql"], data["params"], callback, done); + return db.each(data["sql"], data["params"], callbackfunc, donefunc, config); case "export": buff = db["export"](); result = { From d0ea2dbddb33ac4c12d091e0f7151fbde4740274 Mon Sep 17 00:00:00 2001 From: Lukundo Kileha Date: Wed, 11 Aug 2021 16:05:08 +0300 Subject: [PATCH 2/3] Changes as per PR comments --- .eslintrc.js | 4 ++-- README.md | 4 ++++ src/api.js | 31 +++++++++++-------------------- src/exported_functions.json | 1 - src/worker.js | 6 +++--- test/test_big_int.js | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 55 insertions(+), 26 deletions(-) create mode 100644 test/test_big_int.js diff --git a/.eslintrc.js b/.eslintrc.js index 50f0c718..f730a261 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { env: { browser: true, - es2020: true, + es6: true, node: true }, extends: [ @@ -24,7 +24,7 @@ module.exports = { "!/.eslintrc.js" ], parserOptions: { - ecmaVersion: 2020, + ecmaVersion: 5, sourceType: "script" }, rules: { diff --git a/README.md b/README.md index d97c7c45..0f9563aa 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,10 @@ If you need ```BigInt``` support, it is partially supported since most browsers const config = {useBigInt: true}; /*Pass optional config param to the get function*/ while (stmt.step()) console.log(stmt.get(null, config)); + + /*OR*/ + const result = db.exec("SELECT * FROM test", config); + console.log(results[0].values) ``` On WebWorker, you can just add ```config``` param before posting a message. With this, you wont have to pass config param on ```get``` function. diff --git a/src/api.js b/src/api.js index afb4ba0c..b1d08b99 100644 --- a/src/api.js +++ b/src/api.js @@ -117,12 +117,6 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { ["number", "number", "number"] ); - var sqlite3_bind_int64 = cwrap( - "sqlite3_bind_int64", - "number", - ["number", "number"] - ); - var sqlite3_bind_parameter_index = cwrap( "sqlite3_bind_parameter_index", "number", @@ -371,6 +365,10 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { this.pos += 1; } var text = sqlite3_column_text(this.stmt, pos); + if (typeof BigInt !== "function") { + throw new Error("BigInt is not supported"); + } + /* global BigInt */ return BigInt(text); }; @@ -411,7 +409,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { var stmt = db.prepare("SELECT * FROM test"); while (stmt.step()) console.log(stmt.get(null, {useBigInt: true})); */ - Statement.prototype["get"] = function get(params, config = {}) { + Statement.prototype["get"] = function get(params, config) { if (params != null && this["bind"](params)) { this["step"](); } @@ -420,7 +418,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { for (var field = 0; field < ref; field += 1) { switch (sqlite3_column_type(this.stmt, field)) { case SQLITE_INTEGER: - var getfunc = config.useBigInt + var getfunc = config && config.useBigInt ? this.getBigInt(field) : this.getNumber(field); results1.push(getfunc); @@ -476,7 +474,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { console.log(stmt.getAsObject()); // Will print {nbr:5, data: Uint8Array([1,2,3]), null_value:null} */ - Statement.prototype["getAsObject"] = function getAsObject(params, config = {}) { + Statement.prototype["getAsObject"] = function getAsObject(params, config) { var values = this["get"](params, config); var names = this["getColumnNames"](); var rowObject = {}; @@ -572,15 +570,6 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { return true; }; - Statement.prototype.bindBigInt = function bindBigInt(num, pos) { - if (pos == null) { - pos = this.pos; - this.pos += 1; - } - this.db.handleError(sqlite3_bind_int64(this.stmt, pos, num)); - return true; - }; - Statement.prototype.bindNull = function bindNull(pos) { if (pos == null) { pos = this.pos; @@ -938,7 +927,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { (separated by `;`). This limitation does not apply to params as an object. * @return {Database.QueryExecResult[]} The results of each statement */ - Database.prototype["exec"] = function exec(sql, params, config = {}) { + Database.prototype["exec"] = function exec(sql, params, config) { if (!this.db) { throw "Database closed"; } @@ -1010,7 +999,9 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { function (row){console.log(row.name + " is a grown-up.")} ); */ - Database.prototype["each"] = function each(sql, params, callback, done, config = {}) { + Database.prototype["each"] = function each( + sql, params, callback, done, config + ) { var stmt; if (typeof params === "function") { done = callback; diff --git a/src/exported_functions.json b/src/exported_functions.json index 309161ae..b93b07d2 100644 --- a/src/exported_functions.json +++ b/src/exported_functions.json @@ -13,7 +13,6 @@ "_sqlite3_bind_blob", "_sqlite3_bind_double", "_sqlite3_bind_int", -"_sqlite3_bind_int64", "_sqlite3_bind_parameter_index", "_sqlite3_step", "_sqlite3_column_count", diff --git a/src/worker.js b/src/worker.js index aed0a82c..f75bf6a1 100644 --- a/src/worker.js +++ b/src/worker.js @@ -39,20 +39,20 @@ function onModuleReady(SQL) { if (db === null) { createDb(); } - var callbackfunc = function callback(row) { + var callback = function callback(row) { return postMessage({ id: data["id"], row: row, finished: false }); }; - var donefunc = function done() { + var done = function done() { return postMessage({ id: data["id"], finished: true }); }; - return db.each(data["sql"], data["params"], callbackfunc, donefunc, config); + return db.each(data["sql"], data["params"], callback, done, config); case "export": buff = db["export"](); result = { diff --git a/test/test_big_int.js b/test/test_big_int.js new file mode 100644 index 00000000..4745e531 --- /dev/null +++ b/test/test_big_int.js @@ -0,0 +1,35 @@ +exports.test = function(sql, assert){ + // Create a database + var db = new sql.Database(); + + // Create table, insert data + sqlstr = "CREATE TABLE IF NOT EXISTS Test_BigInt (someNumber BIGINT NOT NULL);" + + "INSERT INTO Test_BigInt (someNumber) VALUES (1628675501000);"; + db.exec(sqlstr); + + var config = {useBigInt: true}; + + var stmt = db.prepare("SELECT * FROM Test_BigInt;"); + stmt.step(); + + assert.strictEqual(typeof stmt.get()[0], 'number', "Reading number value"); + assert.strictEqual(typeof stmt.get(null, config)[0], 'bigint', "Reading bigint value"); + + db.close(); +}; + +if (module == require.main) { + const target_file = process.argv[2]; + const sql_loader = require('./load_sql_lib'); + sql_loader(target_file).then((sql)=>{ + require('test').run({ + 'test big int': function(assert){ + exports.test(sql, assert); + } + }); + }) + .catch((e)=>{ + console.error(e); + assert.fail(e); + }); +} From b30b00951ea4f97e76c1c3229b71feda13e16b77 Mon Sep 17 00:00:00 2001 From: Lukundo Kileha Date: Wed, 11 Aug 2021 20:59:19 +0300 Subject: [PATCH 3/3] Changes as per PR comments - future extension support --- src/api.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api.js b/src/api.js index b1d08b99..fba33e57 100644 --- a/src/api.js +++ b/src/api.js @@ -410,6 +410,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { while (stmt.step()) console.log(stmt.get(null, {useBigInt: true})); */ Statement.prototype["get"] = function get(params, config) { + config = config || {}; if (params != null && this["bind"](params)) { this["step"](); } @@ -418,7 +419,7 @@ Module["onRuntimeInitialized"] = function onRuntimeInitialized() { for (var field = 0; field < ref; field += 1) { switch (sqlite3_column_type(this.stmt, field)) { case SQLITE_INTEGER: - var getfunc = config && config.useBigInt + var getfunc = config["useBigInt"] ? this.getBigInt(field) : this.getNumber(field); results1.push(getfunc);