From 9bca95a19d803216363915565b9f1c9f47016288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Wed, 7 Jan 2026 20:02:16 -0300 Subject: [PATCH 1/5] sqlite: add sqlite prepare options args --- src/node_sqlite.cc | 162 +++++++++--------- src/node_sqlite.h | 7 +- test/parallel/test-sqlite-named-parameters.js | 28 ++- test/parallel/test-sqlite-statement-sync.js | 141 ++++++++------- 4 files changed, 171 insertions(+), 167 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 6d35236dce0f82..a603062e84e2c6 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -1155,6 +1155,89 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { BaseObjectPtr stmt = StatementSync::Create(env, BaseObjectPtr(db), s); db->statements_.insert(stmt.get()); + + if (args.Length() > 1 && !args[1]->IsUndefined()) { + if (!args[1]->IsObject()) { + THROW_ERR_INVALID_ARG_TYPE(env->isolate(), + "The \"options\" argument must be an object."); + return; + } + Local options = args[1].As(); + + Local return_arrays_v; + if (!options + ->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "returnArrays")) + .ToLocal(&return_arrays_v)) { + return; + } + if (!return_arrays_v->IsUndefined()) { + if (!return_arrays_v->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.returnArrays\" argument must be a boolean."); + return; + } + stmt->return_arrays_ = return_arrays_v->IsTrue(); + } + + Local read_big_ints_v; + if (!options + ->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), "readBigInts")) + .ToLocal(&read_big_ints_v)) { + return; + } + if (!read_big_ints_v->IsUndefined()) { + if (!read_big_ints_v->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.readBigInts\" argument must be a boolean."); + return; + } + stmt->use_big_ints_ = read_big_ints_v->IsTrue(); + } + + Local allow_bare_named_params_v; + if (!options + ->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), + "allowBareNamedParameters")) + .ToLocal(&allow_bare_named_params_v)) { + return; + } + if (!allow_bare_named_params_v->IsUndefined()) { + if (!allow_bare_named_params_v->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.allowBareNamedParameters\" argument must be a " + "boolean."); + return; + } + stmt->allow_bare_named_params_ = allow_bare_named_params_v->IsTrue(); + } + + Local allow_unknown_named_params_v; + if (!options + ->Get(env->context(), + FIXED_ONE_BYTE_STRING(env->isolate(), + "allowUnknownNamedParameters")) + .ToLocal(&allow_unknown_named_params_v)) { + return; + } + if (!allow_unknown_named_params_v->IsUndefined()) { + if (!allow_unknown_named_params_v->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"options.allowUnknownNamedParameters\" argument must be a " + "boolean."); + return; + } + stmt->allow_unknown_named_params_ = + allow_unknown_named_params_v->IsTrue(); + } + } + args.GetReturnValue().Set(stmt->object()); } @@ -2587,73 +2670,6 @@ void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(result); } -void StatementSync::SetAllowBareNamedParameters( - const FunctionCallbackInfo& args) { - StatementSync* stmt; - ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_ON_BAD_STATE( - env, stmt->IsFinalized(), "statement has been finalized"); - - if (!args[0]->IsBoolean()) { - THROW_ERR_INVALID_ARG_TYPE( - env->isolate(), - "The \"allowBareNamedParameters\" argument must be a boolean."); - return; - } - - stmt->allow_bare_named_params_ = args[0]->IsTrue(); -} - -void StatementSync::SetAllowUnknownNamedParameters( - const FunctionCallbackInfo& args) { - StatementSync* stmt; - ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_ON_BAD_STATE( - env, stmt->IsFinalized(), "statement has been finalized"); - - if (!args[0]->IsBoolean()) { - THROW_ERR_INVALID_ARG_TYPE(env->isolate(), - "The \"enabled\" argument must be a boolean."); - return; - } - - stmt->allow_unknown_named_params_ = args[0]->IsTrue(); -} - -void StatementSync::SetReadBigInts(const FunctionCallbackInfo& args) { - StatementSync* stmt; - ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_ON_BAD_STATE( - env, stmt->IsFinalized(), "statement has been finalized"); - - if (!args[0]->IsBoolean()) { - THROW_ERR_INVALID_ARG_TYPE( - env->isolate(), "The \"readBigInts\" argument must be a boolean."); - return; - } - - stmt->use_big_ints_ = args[0]->IsTrue(); -} - -void StatementSync::SetReturnArrays(const FunctionCallbackInfo& args) { - StatementSync* stmt; - ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); - Environment* env = Environment::GetCurrent(args); - THROW_AND_RETURN_ON_BAD_STATE( - env, stmt->IsFinalized(), "statement has been finalized"); - - if (!args[0]->IsBoolean()) { - THROW_ERR_INVALID_ARG_TYPE( - env->isolate(), "The \"returnArrays\" argument must be a boolean."); - return; - } - - stmt->return_arrays_ = args[0]->IsTrue(); -} - void IllegalConstructor(const FunctionCallbackInfo& args) { THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } @@ -3002,18 +3018,6 @@ Local StatementSync::GetConstructorTemplate( tmpl, FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"), StatementSync::ExpandedSQLGetter); - SetProtoMethod(isolate, - tmpl, - "setAllowBareNamedParameters", - StatementSync::SetAllowBareNamedParameters); - SetProtoMethod(isolate, - tmpl, - "setAllowUnknownNamedParameters", - StatementSync::SetAllowUnknownNamedParameters); - SetProtoMethod( - isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts); - SetProtoMethod( - isolate, tmpl, "setReturnArrays", StatementSync::SetReturnArrays); env->set_sqlite_statement_sync_constructor_template(tmpl); } return tmpl; diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 2641c9d4f1e8c5..9e3b86374926b8 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -220,12 +220,6 @@ class StatementSync : public BaseObject { static void SourceSQLGetter(const v8::FunctionCallbackInfo& args); static void ExpandedSQLGetter( const v8::FunctionCallbackInfo& args); - static void SetAllowBareNamedParameters( - const v8::FunctionCallbackInfo& args); - static void SetAllowUnknownNamedParameters( - const v8::FunctionCallbackInfo& args); - static void SetReadBigInts(const v8::FunctionCallbackInfo& args); - static void SetReturnArrays(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ColumnToValue(const int column); v8::MaybeLocal ColumnNameToName(const int column); void Finalize(); @@ -246,6 +240,7 @@ class StatementSync : public BaseObject { bool BindParams(const v8::FunctionCallbackInfo& args); bool BindValue(const v8::Local& value, const int index); + friend class DatabaseSync; friend class StatementSyncIterator; friend class SQLTagStore; friend class StatementExecutionHelper; diff --git a/test/parallel/test-sqlite-named-parameters.js b/test/parallel/test-sqlite-named-parameters.js index e1acd0f38fa2f7..247d334c3c9778 100644 --- a/test/parallel/test-sqlite-named-parameters.js +++ b/test/parallel/test-sqlite-named-parameters.js @@ -79,22 +79,33 @@ suite('named parameters', () => { }); }); -suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => { - test('unknown named parameter support can be toggled', (t) => { +suite('allowUnknownNamedParameters', () => { + test('unknown named parameters can be allowed', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); - t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(true), undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { + allowUnknownNamedParameters: true, + }); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.deepStrictEqual( stmt.run(params), { changes: 1, lastInsertRowid: 1 }, ); - t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined); + }); + + test('unknown named parameters are rejected by default', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); + const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.throws(() => { stmt.run(params); }, { @@ -110,12 +121,13 @@ suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => { 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - stmt.setAllowUnknownNamedParameters(); + db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { + allowUnknownNamedParameters: 'true', + }); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "enabled" argument must be a boolean/, + message: /The "options\.allowUnknownNamedParameters" argument must be a boolean/, }); }); }); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 04494a02c692a8..2299e4b25be38e 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -305,8 +305,8 @@ suite('StatementSync.prototype.expandedSQL', () => { }); }); -suite('StatementSync.prototype.setReadBigInts()', () => { - test('BigInts support can be toggled', (t) => { +suite('readBigInts', () => { + test('returns BigInts when readBigInts option is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -315,43 +315,41 @@ suite('StatementSync.prototype.setReadBigInts()', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT val FROM data'); - t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); - t.assert.strictEqual(query.setReadBigInts(true), undefined); + const query = db.prepare('SELECT val FROM data', { readBigInts: true }); t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n }); - 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 (?)'); + const queryDefault = db.prepare('SELECT val FROM data'); + t.assert.deepStrictEqual(queryDefault.get(), { __proto__: null, val: 42 }); + }); + + test('returns BigInts in run() result when readBigInts option is true', (t) => { + const db = new DatabaseSync(nextDb()); + 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 insert = db.prepare('INSERT INTO data (key) VALUES (?)', { readBigInts: true }); t.assert.deepStrictEqual( insert.run(10), - { changes: 1, lastInsertRowid: 10 }, - ); - t.assert.strictEqual(insert.setReadBigInts(true), undefined); - t.assert.deepStrictEqual( - insert.run(20), - { changes: 1n, lastInsertRowid: 20n }, - ); - t.assert.strictEqual(insert.setReadBigInts(false), undefined); - t.assert.deepStrictEqual( - insert.run(30), - { changes: 1, lastInsertRowid: 30 }, + { changes: 1n, lastInsertRowid: 10n }, ); }); - test('throws when input is not a boolean', (t) => { + test('throws when readBigInts option is not a boolean', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - stmt.setReadBigInts(); + db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)', { readBigInts: 'true' }); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "readBigInts" argument must be a boolean/, + message: /The "options\.readBigInts" argument must be a boolean/, }); }); @@ -365,8 +363,7 @@ suite('StatementSync.prototype.setReadBigInts()', () => { code: 'ERR_OUT_OF_RANGE', message: /^Value is too large to be represented as a JavaScript number: 9007199254740992$/, }); - const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); - good.setReadBigInts(true); + const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`, { readBigInts: true }); t.assert.deepStrictEqual(good.get(), { __proto__: null, [`${Number.MAX_SAFE_INTEGER} + 1`]: 2n ** 53n, @@ -374,26 +371,25 @@ suite('StatementSync.prototype.setReadBigInts()', () => { }); }); -suite('StatementSync.prototype.setReturnArrays()', () => { - test('throws when input is not a boolean', (t) => { +suite('db.prepare() returnArrays option', () => { + test('throws when returnArrays option is not a boolean', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('SELECT key, val FROM data'); t.assert.throws(() => { - stmt.setReturnArrays(); + db.prepare('SELECT key, val FROM data', { returnArrays: 'true' }); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "returnArrays" argument must be a boolean/, + message: /The "options\.returnArrays" argument must be a boolean/, }); }); }); suite('StatementSync.prototype.get() with array output', () => { - test('returns array row when setReturnArrays is true', (t) => { + test('returns array row when returnArrays option is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -402,17 +398,14 @@ suite('StatementSync.prototype.get() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT key, val FROM data WHERE key = 1'); - t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); - - query.setReturnArrays(true); - t.assert.deepStrictEqual(query.get(), [1, 'one']); + const queryObj = db.prepare('SELECT key, val FROM data WHERE key = 1'); + t.assert.deepStrictEqual(queryObj.get(), { __proto__: null, key: 1, val: 'one' }); - query.setReturnArrays(false); - t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); + const queryArr = db.prepare('SELECT key, val FROM data WHERE key = 1', { returnArrays: true }); + t.assert.deepStrictEqual(queryArr.get(), [1, 'one']); }); - test('returns array rows with BigInts when both flags are set', (t) => { + test('returns array rows with BigInts when both options are set', (t) => { const expected = [1n, 9007199254740992n]; const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); @@ -422,9 +415,10 @@ suite('StatementSync.prototype.get() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT id, big_num FROM big_data'); - query.setReturnArrays(true); - query.setReadBigInts(true); + const query = db.prepare('SELECT id, big_num FROM big_data', { + returnArrays: true, + readBigInts: true, + }); const row = query.get(); t.assert.deepStrictEqual(row, expected); @@ -432,7 +426,7 @@ suite('StatementSync.prototype.get() with array output', () => { }); suite('StatementSync.prototype.all() with array output', () => { - test('returns array rows when setReturnArrays is true', (t) => { + test('returns array rows when returnArrays option is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -442,23 +436,17 @@ suite('StatementSync.prototype.all() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT key, val FROM data ORDER BY key'); - t.assert.deepStrictEqual(query.all(), [ + const queryObj = db.prepare('SELECT key, val FROM data ORDER BY key'); + t.assert.deepStrictEqual(queryObj.all(), [ { __proto__: null, key: 1, val: 'one' }, { __proto__: null, key: 2, val: 'two' }, ]); - query.setReturnArrays(true); - t.assert.deepStrictEqual(query.all(), [ + const queryArr = db.prepare('SELECT key, val FROM data ORDER BY key', { returnArrays: true }); + t.assert.deepStrictEqual(queryArr.all(), [ [1, 'one'], [2, 'two'], ]); - - query.setReturnArrays(false); - t.assert.deepStrictEqual(query.all(), [ - { __proto__: null, key: 1, val: 'one' }, - { __proto__: null, key: 2, val: 'two' }, - ]); }); test('handles array rows with many columns', (t) => { @@ -488,8 +476,7 @@ suite('StatementSync.prototype.all() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT * FROM wide_table'); - query.setReturnArrays(true); + const query = db.prepare('SELECT * FROM wide_table', { returnArrays: true }); const results = query.all(); t.assert.strictEqual(results.length, 1); @@ -498,7 +485,7 @@ suite('StatementSync.prototype.all() with array output', () => { }); suite('StatementSync.prototype.iterate() with array output', () => { - test('iterates array rows when setReturnArrays is true', (t) => { + test('iterates array rows when returnArrays option is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -508,11 +495,11 @@ suite('StatementSync.prototype.iterate() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT key, val FROM data ORDER BY key'); + const queryObj = db.prepare('SELECT key, val FROM data ORDER BY key'); // Test with objects first const objectRows = []; - for (const row of query.iterate()) { + for (const row of queryObj.iterate()) { objectRows.push(row); } t.assert.deepStrictEqual(objectRows, [ @@ -521,9 +508,9 @@ suite('StatementSync.prototype.iterate() with array output', () => { ]); // Test with arrays - query.setReturnArrays(true); + const queryArr = db.prepare('SELECT key, val FROM data ORDER BY key', { returnArrays: true }); const arrayRows = []; - for (const row of query.iterate()) { + for (const row of queryArr.iterate()) { arrayRows.push(row); } t.assert.deepStrictEqual(arrayRows, [ @@ -532,7 +519,7 @@ suite('StatementSync.prototype.iterate() with array output', () => { ]); // Test toArray() method - t.assert.deepStrictEqual(query.iterate().toArray(), [ + t.assert.deepStrictEqual(queryArr.iterate().toArray(), [ [1, 'one'], [2, 'two'], ]); @@ -545,8 +532,7 @@ suite('StatementSync.prototype.iterate() with array output', () => { INSERT INTO test (key, val) VALUES ('key1', 'val1'); INSERT INTO test (key, val) VALUES ('key2', 'val2'); `); - const stmt = db.prepare('SELECT key, val FROM test'); - stmt.setReturnArrays(true); + const stmt = db.prepare('SELECT key, val FROM test', { returnArrays: true }); const iterator = stmt.iterate(); const results = []; @@ -566,8 +552,8 @@ suite('StatementSync.prototype.iterate() with array output', () => { }); }); -suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { - test('bare named parameter support can be toggled', (t) => { +suite('db.prepare() allowBareNamedParameters option', () => { + test('bare named parameters work by default', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( @@ -579,18 +565,24 @@ suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { stmt.run({ k: 1, v: 2 }), { changes: 1, lastInsertRowid: 1 }, ); - t.assert.strictEqual(stmt.setAllowBareNamedParameters(false), undefined); + }); + + test('bare named parameters can be disabled', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { + allowBareNamedParameters: false, + }); t.assert.throws(() => { stmt.run({ k: 2, v: 4 }); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter 'k'/, }); - t.assert.strictEqual(stmt.setAllowBareNamedParameters(true), undefined); - t.assert.deepStrictEqual( - stmt.run({ k: 3, v: 6 }), - { changes: 1, lastInsertRowid: 3 }, - ); }); test('throws when input is not a boolean', (t) => { @@ -600,12 +592,13 @@ suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - stmt.setAllowBareNamedParameters(); + db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { + allowBareNamedParameters: 'false', + }); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "allowBareNamedParameters" argument must be a boolean/, + message: /The "options\.allowBareNamedParameters" argument must be a boolean/, }); }); }); From b3439a5287b5426a2a0fbe47dfdd2e7335454896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 8 Jan 2026 17:00:12 -0300 Subject: [PATCH 2/5] sqlite: validate options before creating stmt --- src/node_sqlite.cc | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index a603062e84e2c6..5663b46178aa63 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -1147,14 +1147,10 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { return; } - Utf8Value sql(env->isolate(), args[0].As()); - sqlite3_stmt* s = nullptr; - int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0); - - CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); - BaseObjectPtr stmt = - StatementSync::Create(env, BaseObjectPtr(db), s); - db->statements_.insert(stmt.get()); + std::optional return_arrays; + std::optional use_big_ints; + std::optional allow_bare_named_params; + std::optional allow_unknown_named_params; if (args.Length() > 1 && !args[1]->IsUndefined()) { if (!args[1]->IsObject()) { @@ -1178,7 +1174,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { "The \"options.returnArrays\" argument must be a boolean."); return; } - stmt->return_arrays_ = return_arrays_v->IsTrue(); + return_arrays = return_arrays_v->IsTrue(); } Local read_big_ints_v; @@ -1195,7 +1191,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { "The \"options.readBigInts\" argument must be a boolean."); return; } - stmt->use_big_ints_ = read_big_ints_v->IsTrue(); + use_big_ints = read_big_ints_v->IsTrue(); } Local allow_bare_named_params_v; @@ -1214,7 +1210,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { "boolean."); return; } - stmt->allow_bare_named_params_ = allow_bare_named_params_v->IsTrue(); + allow_bare_named_params = allow_bare_named_params_v->IsTrue(); } Local allow_unknown_named_params_v; @@ -1233,11 +1229,32 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo& args) { "boolean."); return; } - stmt->allow_unknown_named_params_ = - allow_unknown_named_params_v->IsTrue(); + allow_unknown_named_params = allow_unknown_named_params_v->IsTrue(); } } + Utf8Value sql(env->isolate(), args[0].As()); + sqlite3_stmt* s = nullptr; + int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0); + + CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void()); + BaseObjectPtr stmt = + StatementSync::Create(env, BaseObjectPtr(db), s); + db->statements_.insert(stmt.get()); + + if (return_arrays.has_value()) { + stmt->return_arrays_ = return_arrays.value(); + } + if (use_big_ints.has_value()) { + stmt->use_big_ints_ = use_big_ints.value(); + } + if (allow_bare_named_params.has_value()) { + stmt->allow_bare_named_params_ = allow_bare_named_params.value(); + } + if (allow_unknown_named_params.has_value()) { + stmt->allow_unknown_named_params_ = allow_unknown_named_params.value(); + } + args.GetReturnValue().Set(stmt->object()); } From 387000f6a2553665993df069f8f262897a92c729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 8 Jan 2026 17:08:54 -0300 Subject: [PATCH 3/5] sqlite: revert set methods --- src/node_sqlite.cc | 79 ++++++++++ src/node_sqlite.h | 6 + test/parallel/test-sqlite-named-parameters.js | 28 +--- test/parallel/test-sqlite-statement-sync.js | 141 +++++++++--------- 4 files changed, 167 insertions(+), 87 deletions(-) diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc index 5663b46178aa63..78e928de206e9d 100644 --- a/src/node_sqlite.cc +++ b/src/node_sqlite.cc @@ -2687,6 +2687,73 @@ void StatementSync::ExpandedSQLGetter(const FunctionCallbackInfo& args) { args.GetReturnValue().Set(result); } +void StatementSync::SetAllowBareNamedParameters( + const FunctionCallbackInfo& args) { + StatementSync* stmt; + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_ON_BAD_STATE( + env, stmt->IsFinalized(), "statement has been finalized"); + + if (!args[0]->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), + "The \"allowBareNamedParameters\" argument must be a boolean."); + return; + } + + stmt->allow_bare_named_params_ = args[0]->IsTrue(); +} + +void StatementSync::SetAllowUnknownNamedParameters( + const FunctionCallbackInfo& args) { + StatementSync* stmt; + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_ON_BAD_STATE( + env, stmt->IsFinalized(), "statement has been finalized"); + + if (!args[0]->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE(env->isolate(), + "The \"enabled\" argument must be a boolean."); + return; + } + + stmt->allow_unknown_named_params_ = args[0]->IsTrue(); +} + +void StatementSync::SetReadBigInts(const FunctionCallbackInfo& args) { + StatementSync* stmt; + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_ON_BAD_STATE( + env, stmt->IsFinalized(), "statement has been finalized"); + + if (!args[0]->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), "The \"readBigInts\" argument must be a boolean."); + return; + } + + stmt->use_big_ints_ = args[0]->IsTrue(); +} + +void StatementSync::SetReturnArrays(const FunctionCallbackInfo& args) { + StatementSync* stmt; + ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This()); + Environment* env = Environment::GetCurrent(args); + THROW_AND_RETURN_ON_BAD_STATE( + env, stmt->IsFinalized(), "statement has been finalized"); + + if (!args[0]->IsBoolean()) { + THROW_ERR_INVALID_ARG_TYPE( + env->isolate(), "The \"returnArrays\" argument must be a boolean."); + return; + } + + stmt->return_arrays_ = args[0]->IsTrue(); +} + void IllegalConstructor(const FunctionCallbackInfo& args) { THROW_ERR_ILLEGAL_CONSTRUCTOR(Environment::GetCurrent(args)); } @@ -3035,6 +3102,18 @@ Local StatementSync::GetConstructorTemplate( tmpl, FIXED_ONE_BYTE_STRING(isolate, "expandedSQL"), StatementSync::ExpandedSQLGetter); + SetProtoMethod(isolate, + tmpl, + "setAllowBareNamedParameters", + StatementSync::SetAllowBareNamedParameters); + SetProtoMethod(isolate, + tmpl, + "setAllowUnknownNamedParameters", + StatementSync::SetAllowUnknownNamedParameters); + SetProtoMethod( + isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts); + SetProtoMethod( + isolate, tmpl, "setReturnArrays", StatementSync::SetReturnArrays); env->set_sqlite_statement_sync_constructor_template(tmpl); } return tmpl; diff --git a/src/node_sqlite.h b/src/node_sqlite.h index 9e3b86374926b8..27622a15dbf1bb 100644 --- a/src/node_sqlite.h +++ b/src/node_sqlite.h @@ -220,6 +220,12 @@ class StatementSync : public BaseObject { static void SourceSQLGetter(const v8::FunctionCallbackInfo& args); static void ExpandedSQLGetter( const v8::FunctionCallbackInfo& args); + static void SetAllowBareNamedParameters( + const v8::FunctionCallbackInfo& args); + static void SetAllowUnknownNamedParameters( + const v8::FunctionCallbackInfo& args); + static void SetReadBigInts(const v8::FunctionCallbackInfo& args); + static void SetReturnArrays(const v8::FunctionCallbackInfo& args); v8::MaybeLocal ColumnToValue(const int column); v8::MaybeLocal ColumnNameToName(const int column); void Finalize(); diff --git a/test/parallel/test-sqlite-named-parameters.js b/test/parallel/test-sqlite-named-parameters.js index 247d334c3c9778..e1acd0f38fa2f7 100644 --- a/test/parallel/test-sqlite-named-parameters.js +++ b/test/parallel/test-sqlite-named-parameters.js @@ -79,33 +79,22 @@ suite('named parameters', () => { }); }); -suite('allowUnknownNamedParameters', () => { - test('unknown named parameters can be allowed', (t) => { +suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => { + test('unknown named parameter support can be toggled', (t) => { const db = new DatabaseSync(':memory:'); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { - allowUnknownNamedParameters: true, - }); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); + t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(true), undefined); const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; t.assert.deepStrictEqual( stmt.run(params), { changes: 1, lastInsertRowid: 1 }, ); - }); - - test('unknown named parameters are rejected by default', (t) => { - const db = new DatabaseSync(':memory:'); - t.after(() => { db.close(); }); - const setup = db.exec( - 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); - const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; + t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined); t.assert.throws(() => { stmt.run(params); }, { @@ -121,13 +110,12 @@ suite('allowUnknownNamedParameters', () => { 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { - allowUnknownNamedParameters: 'true', - }); + stmt.setAllowUnknownNamedParameters(); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "options\.allowUnknownNamedParameters" argument must be a boolean/, + message: /The "enabled" argument must be a boolean/, }); }); }); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 2299e4b25be38e..04494a02c692a8 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -305,8 +305,8 @@ suite('StatementSync.prototype.expandedSQL', () => { }); }); -suite('readBigInts', () => { - test('returns BigInts when readBigInts option is true', (t) => { +suite('StatementSync.prototype.setReadBigInts()', () => { + test('BigInts support can be toggled', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -315,41 +315,43 @@ suite('readBigInts', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT val FROM data', { readBigInts: true }); + const query = db.prepare('SELECT val FROM data'); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); + t.assert.strictEqual(query.setReadBigInts(true), undefined); t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n }); + t.assert.strictEqual(query.setReadBigInts(false), undefined); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); - const queryDefault = db.prepare('SELECT val FROM data'); - t.assert.deepStrictEqual(queryDefault.get(), { __proto__: null, val: 42 }); - }); - - test('returns BigInts in run() result when readBigInts option is true', (t) => { - const db = new DatabaseSync(nextDb()); - 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 insert = db.prepare('INSERT INTO data (key) VALUES (?)', { readBigInts: true }); + const insert = db.prepare('INSERT INTO data (key) VALUES (?)'); t.assert.deepStrictEqual( insert.run(10), - { changes: 1n, lastInsertRowid: 10n }, + { changes: 1, lastInsertRowid: 10 }, + ); + t.assert.strictEqual(insert.setReadBigInts(true), undefined); + t.assert.deepStrictEqual( + insert.run(20), + { changes: 1n, lastInsertRowid: 20n }, + ); + t.assert.strictEqual(insert.setReadBigInts(false), undefined); + t.assert.deepStrictEqual( + insert.run(30), + { changes: 1, lastInsertRowid: 30 }, ); }); - test('throws when readBigInts option is not a boolean', (t) => { + test('throws when input is not a boolean', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE types(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - db.prepare('INSERT INTO types (key, val) VALUES ($k, $v)', { readBigInts: 'true' }); + stmt.setReadBigInts(); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "options\.readBigInts" argument must be a boolean/, + message: /The "readBigInts" argument must be a boolean/, }); }); @@ -363,7 +365,8 @@ suite('readBigInts', () => { code: 'ERR_OUT_OF_RANGE', message: /^Value is too large to be represented as a JavaScript number: 9007199254740992$/, }); - const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`, { readBigInts: true }); + const good = db.prepare(`SELECT ${Number.MAX_SAFE_INTEGER} + 1`); + good.setReadBigInts(true); t.assert.deepStrictEqual(good.get(), { __proto__: null, [`${Number.MAX_SAFE_INTEGER} + 1`]: 2n ** 53n, @@ -371,25 +374,26 @@ suite('readBigInts', () => { }); }); -suite('db.prepare() returnArrays option', () => { - test('throws when returnArrays option is not a boolean', (t) => { +suite('StatementSync.prototype.setReturnArrays()', () => { + test('throws when input is not a boolean', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('SELECT key, val FROM data'); t.assert.throws(() => { - db.prepare('SELECT key, val FROM data', { returnArrays: 'true' }); + stmt.setReturnArrays(); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "options\.returnArrays" argument must be a boolean/, + message: /The "returnArrays" argument must be a boolean/, }); }); }); suite('StatementSync.prototype.get() with array output', () => { - test('returns array row when returnArrays option is true', (t) => { + test('returns array row when setReturnArrays is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -398,14 +402,17 @@ suite('StatementSync.prototype.get() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const queryObj = db.prepare('SELECT key, val FROM data WHERE key = 1'); - t.assert.deepStrictEqual(queryObj.get(), { __proto__: null, key: 1, val: 'one' }); + const query = db.prepare('SELECT key, val FROM data WHERE key = 1'); + t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); + + query.setReturnArrays(true); + t.assert.deepStrictEqual(query.get(), [1, 'one']); - const queryArr = db.prepare('SELECT key, val FROM data WHERE key = 1', { returnArrays: true }); - t.assert.deepStrictEqual(queryArr.get(), [1, 'one']); + query.setReturnArrays(false); + t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); }); - test('returns array rows with BigInts when both options are set', (t) => { + test('returns array rows with BigInts when both flags are set', (t) => { const expected = [1n, 9007199254740992n]; const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); @@ -415,10 +422,9 @@ suite('StatementSync.prototype.get() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT id, big_num FROM big_data', { - returnArrays: true, - readBigInts: true, - }); + const query = db.prepare('SELECT id, big_num FROM big_data'); + query.setReturnArrays(true); + query.setReadBigInts(true); const row = query.get(); t.assert.deepStrictEqual(row, expected); @@ -426,7 +432,7 @@ suite('StatementSync.prototype.get() with array output', () => { }); suite('StatementSync.prototype.all() with array output', () => { - test('returns array rows when returnArrays option is true', (t) => { + test('returns array rows when setReturnArrays is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -436,17 +442,23 @@ suite('StatementSync.prototype.all() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const queryObj = db.prepare('SELECT key, val FROM data ORDER BY key'); - t.assert.deepStrictEqual(queryObj.all(), [ + const query = db.prepare('SELECT key, val FROM data ORDER BY key'); + t.assert.deepStrictEqual(query.all(), [ { __proto__: null, key: 1, val: 'one' }, { __proto__: null, key: 2, val: 'two' }, ]); - const queryArr = db.prepare('SELECT key, val FROM data ORDER BY key', { returnArrays: true }); - t.assert.deepStrictEqual(queryArr.all(), [ + query.setReturnArrays(true); + t.assert.deepStrictEqual(query.all(), [ [1, 'one'], [2, 'two'], ]); + + query.setReturnArrays(false); + t.assert.deepStrictEqual(query.all(), [ + { __proto__: null, key: 1, val: 'one' }, + { __proto__: null, key: 2, val: 'two' }, + ]); }); test('handles array rows with many columns', (t) => { @@ -476,7 +488,8 @@ suite('StatementSync.prototype.all() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const query = db.prepare('SELECT * FROM wide_table', { returnArrays: true }); + const query = db.prepare('SELECT * FROM wide_table'); + query.setReturnArrays(true); const results = query.all(); t.assert.strictEqual(results.length, 1); @@ -485,7 +498,7 @@ suite('StatementSync.prototype.all() with array output', () => { }); suite('StatementSync.prototype.iterate() with array output', () => { - test('iterates array rows when returnArrays option is true', (t) => { + test('iterates array rows when setReturnArrays is true', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec(` @@ -495,11 +508,11 @@ suite('StatementSync.prototype.iterate() with array output', () => { `); t.assert.strictEqual(setup, undefined); - const queryObj = db.prepare('SELECT key, val FROM data ORDER BY key'); + const query = db.prepare('SELECT key, val FROM data ORDER BY key'); // Test with objects first const objectRows = []; - for (const row of queryObj.iterate()) { + for (const row of query.iterate()) { objectRows.push(row); } t.assert.deepStrictEqual(objectRows, [ @@ -508,9 +521,9 @@ suite('StatementSync.prototype.iterate() with array output', () => { ]); // Test with arrays - const queryArr = db.prepare('SELECT key, val FROM data ORDER BY key', { returnArrays: true }); + query.setReturnArrays(true); const arrayRows = []; - for (const row of queryArr.iterate()) { + for (const row of query.iterate()) { arrayRows.push(row); } t.assert.deepStrictEqual(arrayRows, [ @@ -519,7 +532,7 @@ suite('StatementSync.prototype.iterate() with array output', () => { ]); // Test toArray() method - t.assert.deepStrictEqual(queryArr.iterate().toArray(), [ + t.assert.deepStrictEqual(query.iterate().toArray(), [ [1, 'one'], [2, 'two'], ]); @@ -532,7 +545,8 @@ suite('StatementSync.prototype.iterate() with array output', () => { INSERT INTO test (key, val) VALUES ('key1', 'val1'); INSERT INTO test (key, val) VALUES ('key2', 'val2'); `); - const stmt = db.prepare('SELECT key, val FROM test', { returnArrays: true }); + const stmt = db.prepare('SELECT key, val FROM test'); + stmt.setReturnArrays(true); const iterator = stmt.iterate(); const results = []; @@ -552,8 +566,8 @@ suite('StatementSync.prototype.iterate() with array output', () => { }); }); -suite('db.prepare() allowBareNamedParameters option', () => { - test('bare named parameters work by default', (t) => { +suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { + test('bare named parameter support can be toggled', (t) => { const db = new DatabaseSync(nextDb()); t.after(() => { db.close(); }); const setup = db.exec( @@ -565,24 +579,18 @@ suite('db.prepare() allowBareNamedParameters option', () => { stmt.run({ k: 1, v: 2 }), { changes: 1, lastInsertRowid: 1 }, ); - }); - - test('bare named parameters can be disabled', (t) => { - const db = new DatabaseSync(nextDb()); - t.after(() => { db.close(); }); - const setup = db.exec( - 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' - ); - t.assert.strictEqual(setup, undefined); - const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { - allowBareNamedParameters: false, - }); + t.assert.strictEqual(stmt.setAllowBareNamedParameters(false), undefined); t.assert.throws(() => { stmt.run({ k: 2, v: 4 }); }, { code: 'ERR_INVALID_STATE', message: /Unknown named parameter 'k'/, }); + t.assert.strictEqual(stmt.setAllowBareNamedParameters(true), undefined); + t.assert.deepStrictEqual( + stmt.run({ k: 3, v: 6 }), + { changes: 1, lastInsertRowid: 3 }, + ); }); test('throws when input is not a boolean', (t) => { @@ -592,13 +600,12 @@ suite('db.prepare() allowBareNamedParameters option', () => { 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' ); t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); t.assert.throws(() => { - db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)', { - allowBareNamedParameters: 'false', - }); + stmt.setAllowBareNamedParameters(); }, { code: 'ERR_INVALID_ARG_TYPE', - message: /The "options\.allowBareNamedParameters" argument must be a boolean/, + message: /The "allowBareNamedParameters" argument must be a boolean/, }); }); }); From 6da81ddcb2e45d08326d4386a422b9e5c5a6939c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 8 Jan 2026 17:30:34 -0300 Subject: [PATCH 4/5] sqlite: create options test cases --- test/parallel/test-sqlite-named-parameters.js | 100 +++++++ test/parallel/test-sqlite-statement-sync.js | 245 ++++++++++++++++++ 2 files changed, 345 insertions(+) diff --git a/test/parallel/test-sqlite-named-parameters.js b/test/parallel/test-sqlite-named-parameters.js index e1acd0f38fa2f7..db8f46e6b6ce5a 100644 --- a/test/parallel/test-sqlite-named-parameters.js +++ b/test/parallel/test-sqlite-named-parameters.js @@ -119,3 +119,103 @@ suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => { }); }); }); + +suite('options.allowUnknownNamedParameters', () => { + test('unknown named parameters are allowed when input is true', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowUnknownNamedParameters: true } + ); + const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; + t.assert.deepStrictEqual( + stmt.run(params), + { changes: 1, lastInsertRowid: 1 }, + ); + }); + + test('unknown named parameters throw when input is false', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowUnknownNamedParameters: false } + ); + const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; + t.assert.throws(() => { + stmt.run(params); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter '\$a'/, + }); + }); + + test('unknown named parameters throws error by default', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)'); + const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; + t.assert.throws(() => { + stmt.run(params); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter '\$a'/, + }); + }); + + test('throws when option is not a boolean', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + t.assert.throws(() => { + db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowUnknownNamedParameters: 'true' } + ); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.allowUnknownNamedParameters" argument must be a boolean/, + }); + }); + + test('setAllowUnknownNamedParameters can override prepare option', (t) => { + const db = new DatabaseSync(':memory:'); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowUnknownNamedParameters: true } + ); + const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 }; + t.assert.deepStrictEqual( + stmt.run(params), + { changes: 1, lastInsertRowid: 1 }, + ); + t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined); + t.assert.throws(() => { + stmt.run(params); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter '\$a'/, + }); + }); +}); diff --git a/test/parallel/test-sqlite-statement-sync.js b/test/parallel/test-sqlite-statement-sync.js index 04494a02c692a8..62e95363f1c46a 100644 --- a/test/parallel/test-sqlite-statement-sync.js +++ b/test/parallel/test-sqlite-statement-sync.js @@ -609,3 +609,248 @@ suite('StatementSync.prototype.setAllowBareNamedParameters()', () => { }); }); }); + +suite('options.readBigInts', () => { + test('BigInts are returned when input is true', (t) => { + const db = new DatabaseSync(nextDb()); + 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', { readBigInts: true }); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n }); + }); + + test('numbers are returned when input is false', (t) => { + const db = new DatabaseSync(nextDb()); + 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', { readBigInts: false }); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); + }); + + test('throws when input is not a boolean', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + t.assert.throws(() => { + db.prepare('SELECT val FROM data', { readBigInts: 'true' }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.readBigInts" argument must be a boolean/, + }); + }); + + test('setReadBigInts can override prepare option', (t) => { + const db = new DatabaseSync(nextDb()); + 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', { readBigInts: true }); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n }); + t.assert.strictEqual(query.setReadBigInts(false), undefined); + t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42 }); + }); +}); + +suite('options.returnArrays', () => { + test('arrays are returned when input is true', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; + INSERT INTO data (key, val) VALUES (1, 'one'); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare( + 'SELECT key, val FROM data WHERE key = 1', + { returnArrays: true } + ); + t.assert.deepStrictEqual(query.get(), [1, 'one']); + }); + + test('objects are returned when input is false', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; + INSERT INTO data (key, val) VALUES (1, 'one'); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare( + 'SELECT key, val FROM data WHERE key = 1', + { returnArrays: false } + ); + t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); + }); + + test('throws when input is not a boolean', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + t.assert.throws(() => { + db.prepare('SELECT key, val FROM data', { returnArrays: 'true' }); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.returnArrays" argument must be a boolean/, + }); + }); + + test('setReturnArrays can override prepare option', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; + INSERT INTO data (key, val) VALUES (1, 'one'); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare( + 'SELECT key, val FROM data WHERE key = 1', + { returnArrays: true } + ); + t.assert.deepStrictEqual(query.get(), [1, 'one']); + t.assert.strictEqual(query.setReturnArrays(false), undefined); + t.assert.deepStrictEqual(query.get(), { __proto__: null, key: 1, val: 'one' }); + }); + + test('all() returns arrays when input is true', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; + INSERT INTO data (key, val) VALUES (1, 'one'); + INSERT INTO data (key, val) VALUES (2, 'two'); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare( + 'SELECT key, val FROM data ORDER BY key', + { returnArrays: true } + ); + t.assert.deepStrictEqual(query.all(), [ + [1, 'one'], + [2, 'two'], + ]); + }); + + test('iterate() returns arrays when input is true', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec(` + CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT; + INSERT INTO data (key, val) VALUES (1, 'one'); + INSERT INTO data (key, val) VALUES (2, 'two'); + `); + t.assert.strictEqual(setup, undefined); + + const query = db.prepare( + 'SELECT key, val FROM data ORDER BY key', + { returnArrays: true } + ); + t.assert.deepStrictEqual(query.iterate().toArray(), [ + [1, 'one'], + [2, 'two'], + ]); + }); +}); + +suite('options.allowBareNamedParameters', () => { + test('bare named parameters are allowed when input is true', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowBareNamedParameters: true } + ); + t.assert.deepStrictEqual( + stmt.run({ k: 1, v: 2 }), + { changes: 1, lastInsertRowid: 1 }, + ); + }); + + test('bare named parameters throw when input is false', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowBareNamedParameters: false } + ); + t.assert.throws(() => { + stmt.run({ k: 1, v: 2 }); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter 'k'/, + }); + }); + + test('throws when input is not a boolean', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + t.assert.throws(() => { + db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowBareNamedParameters: 'true' } + ); + }, { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.allowBareNamedParameters" argument must be a boolean/, + }); + }); + + test('setAllowBareNamedParameters can override prepare option', (t) => { + const db = new DatabaseSync(nextDb()); + t.after(() => { db.close(); }); + const setup = db.exec( + 'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;' + ); + t.assert.strictEqual(setup, undefined); + const stmt = db.prepare( + 'INSERT INTO data (key, val) VALUES ($k, $v)', + { allowBareNamedParameters: false } + ); + t.assert.throws(() => { + stmt.run({ k: 1, v: 2 }); + }, { + code: 'ERR_INVALID_STATE', + message: /Unknown named parameter 'k'/, + }); + t.assert.strictEqual(stmt.setAllowBareNamedParameters(true), undefined); + t.assert.deepStrictEqual( + stmt.run({ k: 2, v: 4 }), + { changes: 1, lastInsertRowid: 2 }, + ); + }); +}); From faaec9f78f75157148cab7a1e84f90f1f522cc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Ara=C3=BAjo?= Date: Thu, 8 Jan 2026 20:03:35 -0300 Subject: [PATCH 5/5] sqlite: add prepare options doc --- doc/api/sqlite.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/api/sqlite.md b/doc/api/sqlite.md index 186e70784b94d0..22faccb6bad92d 100644 --- a/doc/api/sqlite.md +++ b/doc/api/sqlite.md @@ -453,13 +453,23 @@ Opens the database specified in the `path` argument of the `DatabaseSync` constructor. This method should only be used when the database is not opened via the constructor. An exception is thrown if the database is already open. -### `database.prepare(sql)` +### `database.prepare(sql[, options])` * `sql` {string} A SQL string to compile to a prepared statement. +* `options` {Object} Optional configuration for the prepared statement. + * `readBigInts` {boolean} If `true`, integer fields are read as `BigInt`s. + **Default:** inherited from database options or `false`. + * `returnArrays` {boolean} If `true`, results are returned as arrays. + **Default:** inherited from database options or `false`. + * `allowBareNamedParameters` {boolean} If `true`, allows binding named + parameters without the prefix character. **Default:** inherited from + database options or `true`. + * `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters + are ignored. **Default:** inherited from database options or `false`. * Returns: {StatementSync} The prepared statement. Compiles a SQL statement into a [prepared statement][]. This method is a wrapper