From 6836df6a7e7523e2c3ebc60b0c1a758f7de150ca Mon Sep 17 00:00:00 2001 From: Miguel Marcondes Date: Wed, 11 Jun 2025 17:50:56 -0300 Subject: [PATCH] sqlite: add support for readBigInts option in db connection level --- src/node_sqlite.cc | 18 ++++++- src/node_sqlite.h | 6 +++ test/parallel/test-sqlite-database-sync.js | 55 ++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 078c8d96ed2541..35f4aa4441ce85 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -950,6 +950,22 @@ void DatabaseSync::New(const FunctionCallbackInfo& args) { allow_load_extension = allow_extension_v.As()->Value(); } + Local read_bigints_string = + FIXED_ONE_BYTE_STRING(env->isolate(), "readBigInts"); + Local read_bigints_v; + if (options->Get(env->context(), read_bigints_string) + .ToLocal(&read_bigints_v)) { + if (!read_bigints_v->IsUndefined()) { + if (!read_bigints_v->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.readBigInts\" argument must be a boolean."); + return; + } + open_config.set_use_big_ints(read_bigints_v.As()->Value()); + } + } + Local timeout_v; if (!options->Get(env->context(), env->timeout_string()) .ToLocal(&timeout_v)) { @@ -1772,10 +1788,10 @@ StatementSync::StatementSync(Environment* env, : BaseObject(env, object), db_(std::move(db)) { MakeWeak(); statement_ = stmt; + use_big_ints_ = db_->use_big_ints(); // In the future, some of these options could be set at the database // connection level and inherited by statements to reduce boilerplate. return_arrays_ = false; - use_big_ints_ = false; allow_bare_named_params_ = true; allow_unknown_named_params_ = false; bare_named_params_ = std::nullopt; diff --git a/src/node_sqlite.h b/src/node_sqlite.h index e02c1df9c2b7b8..cf915ae604d352 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -39,12 +39,17 @@ class DatabaseOpenConfiguration { inline int get_timeout() { return timeout_; } + inline void set_use_big_ints(bool flag) { use_big_ints_ = flag; } + + inline bool get_use_big_ints() const { return use_big_ints_; } + private: std::string location_; bool read_only_ = false; bool enable_foreign_keys_ = true; bool enable_dqs_ = false; int timeout_ = 0; + bool use_big_ints_ = false; }; class StatementSync; @@ -82,6 +87,7 @@ class DatabaseSync : public BaseObject { void FinalizeBackups(); void UntrackStatement(StatementSync* statement); bool IsOpen(); + bool use_big_ints() const { return open_config_.get_use_big_ints(); } sqlite3* Connection(); // In some situations, such as when using custom functions, it is possible diff --git a/test/parallel/test-sqlite-database-sync.js b/test/parallel/test-sqlite-database-sync.js index b7a6718cd99a34..74e78c123f9076 100644 --- a/test/parallel/test-sqlite-database-sync.js +++ b/test/parallel/test-sqlite-database-sync.js @@ -174,6 +174,61 @@ suite('DatabaseSync() constructor', () => { t.after(() => { db.close(); }); db.exec('SELECT "foo";'); }); + + test('throws if options.readBigInts is provided but is not a boolean', (t) => { + t.assert.throws(() => { + new DatabaseSync('foo', { readBigInts: 42 }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.readBigInts" argument must be a boolean/, + }); + }); + + test('allows reading big integers', (t) => { + const dbPath = nextDb(); + const db = new DatabaseSync(dbPath, { readBigInts: true }); + t.after(() => { db.close(); }); + + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; + INSERT INTO data (key, val) VALUES (1, 42); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare('SELECT val FROM data'); + t.assert.strictEqual(query.setReadBigInts(true), undefined); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n }); + + const insert = db.prepare('INSERT INTO data (key) VALUES (?)'); + t.assert.strictEqual(insert.setReadBigInts(true), undefined); + t.assert.deepStrictEqual( + insert.run(20), + { changes: 1n, lastInsertRowid: 20n }, + ); + }); + + test('allows reading numbers', (t) => { + const dbPath = nextDb(); + const db = new DatabaseSync(dbPath, { readBigInts: false }); + t.after(() => { db.close(); }); + + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT; + INSERT INTO data (key, val) VALUES (1, 42); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare('SELECT val FROM data'); + t.assert.strictEqual(query.setReadBigInts(false), undefined); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); + + const insert = db.prepare('INSERT INTO data (key) VALUES (?)'); + t.assert.strictEqual(insert.setReadBigInts(false), undefined); + t.assert.deepStrictEqual( + insert.run(20), + { changes: 1, lastInsertRowid: 20 }, + ); + }); }); suite('DatabaseSync.prototype.open()', () => {