Skip to content

Commit d08513d

Browse files
sqlite: add support for readBigInts option in db connection level
PR-URL: #58697 Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Daniel Lemire <[email protected]> Reviewed-By: Edy Silva <[email protected]>
1 parent dff081e commit d08513d

File tree

5 files changed

+223
-6
lines changed

5 files changed

+223
-6
lines changed

doc/api/sqlite.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ exposed by this class execute synchronously.
9797

9898
<!-- YAML
9999
added: v22.5.0
100+
changes:
101+
- version: REPLACEME
102+
pr-url: https://github.com/nodejs/node/pull/58697
103+
description: Add new SQLite database options.
100104
-->
101105

102106
* `path` {string | Buffer | URL} The path of the database. A SQLite database can be
@@ -126,6 +130,14 @@ added: v22.5.0
126130
* `timeout` {number} The [busy timeout][] in milliseconds. This is the maximum amount of
127131
time that SQLite will wait for a database lock to be released before
128132
returning an error. **Default:** `0`.
133+
* `readBigInts` {boolean} If `true`, integer fields are read as JavaScript `BigInt` values. If `false`,
134+
integer fields are read as JavaScript numbers. **Default:** `false`.
135+
* `returnArrays` {boolean} If `true`, query results are returned as arrays instead of objects.
136+
**Default:** `false`.
137+
* `allowBareNamedParameters` {boolean} If `true`, allows binding named parameters without the prefix
138+
character (e.g., `foo` instead of `:foo`). **Default:** `true`.
139+
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters are ignored when binding.
140+
If `false`, an exception is thrown for unknown named parameters. **Default:** `false`.
129141

130142
Constructs a new `DatabaseSync` instance.
131143

src/env_properties.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
V(ack_string, "ack") \
7373
V(address_string, "address") \
7474
V(aliases_string, "aliases") \
75+
V(allow_bare_named_params_string, "allowBareNamedParameters") \
76+
V(allow_unknown_named_params_string, "allowUnknownNamedParameters") \
7577
V(alpn_callback_string, "ALPNCallback") \
7678
V(args_string, "args") \
7779
V(asn1curve_string, "asn1Curve") \
@@ -324,6 +326,7 @@
324326
V(raw_string, "raw") \
325327
V(read_host_object_string, "_readHostObject") \
326328
V(readable_string, "readable") \
329+
V(read_bigints_string, "readBigInts") \
327330
V(reason_string, "reason") \
328331
V(refresh_string, "refresh") \
329332
V(regexp_string, "regexp") \

src/node_sqlite.cc

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -966,6 +966,66 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
966966

967967
open_config.set_timeout(timeout_v.As<Int32>()->Value());
968968
}
969+
970+
Local<Value> read_bigints_v;
971+
if (options->Get(env->context(), env->read_bigints_string())
972+
.ToLocal(&read_bigints_v)) {
973+
if (!read_bigints_v->IsUndefined()) {
974+
if (!read_bigints_v->IsBoolean()) {
975+
THROW_ERR_INVALID_ARG_TYPE(
976+
env->isolate(),
977+
R"(The "options.readBigInts" argument must be a boolean.)");
978+
return;
979+
}
980+
open_config.set_use_big_ints(read_bigints_v.As<Boolean>()->Value());
981+
}
982+
}
983+
984+
Local<Value> return_arrays_v;
985+
if (options->Get(env->context(), env->return_arrays_string())
986+
.ToLocal(&return_arrays_v)) {
987+
if (!return_arrays_v->IsUndefined()) {
988+
if (!return_arrays_v->IsBoolean()) {
989+
THROW_ERR_INVALID_ARG_TYPE(
990+
env->isolate(),
991+
R"(The "options.returnArrays" argument must be a boolean.)");
992+
return;
993+
}
994+
open_config.set_return_arrays(return_arrays_v.As<Boolean>()->Value());
995+
}
996+
}
997+
998+
Local<Value> allow_bare_named_params_v;
999+
if (options->Get(env->context(), env->allow_bare_named_params_string())
1000+
.ToLocal(&allow_bare_named_params_v)) {
1001+
if (!allow_bare_named_params_v->IsUndefined()) {
1002+
if (!allow_bare_named_params_v->IsBoolean()) {
1003+
THROW_ERR_INVALID_ARG_TYPE(
1004+
env->isolate(),
1005+
R"(The "options.allowBareNamedParameters" )"
1006+
"argument must be a boolean.");
1007+
return;
1008+
}
1009+
open_config.set_allow_bare_named_params(
1010+
allow_bare_named_params_v.As<Boolean>()->Value());
1011+
}
1012+
}
1013+
1014+
Local<Value> allow_unknown_named_params_v;
1015+
if (options->Get(env->context(), env->allow_unknown_named_params_string())
1016+
.ToLocal(&allow_unknown_named_params_v)) {
1017+
if (!allow_unknown_named_params_v->IsUndefined()) {
1018+
if (!allow_unknown_named_params_v->IsBoolean()) {
1019+
THROW_ERR_INVALID_ARG_TYPE(
1020+
env->isolate(),
1021+
R"(The "options.allowUnknownNamedParameters" )"
1022+
"argument must be a boolean.");
1023+
return;
1024+
}
1025+
open_config.set_allow_unknown_named_params(
1026+
allow_unknown_named_params_v.As<Boolean>()->Value());
1027+
}
1028+
}
9691029
}
9701030

9711031
new DatabaseSync(
@@ -1772,12 +1832,11 @@ StatementSync::StatementSync(Environment* env,
17721832
: BaseObject(env, object), db_(std::move(db)) {
17731833
MakeWeak();
17741834
statement_ = stmt;
1775-
// In the future, some of these options could be set at the database
1776-
// connection level and inherited by statements to reduce boilerplate.
1777-
return_arrays_ = false;
1778-
use_big_ints_ = false;
1779-
allow_bare_named_params_ = true;
1780-
allow_unknown_named_params_ = false;
1835+
use_big_ints_ = db_->use_big_ints();
1836+
return_arrays_ = db_->return_arrays();
1837+
allow_bare_named_params_ = db_->allow_bare_named_params();
1838+
allow_unknown_named_params_ = db_->allow_unknown_named_params();
1839+
17811840
bare_named_params_ = std::nullopt;
17821841
}
17831842

src/node_sqlite.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,40 @@ class DatabaseOpenConfiguration {
3939

4040
inline int get_timeout() { return timeout_; }
4141

42+
inline void set_use_big_ints(bool flag) { use_big_ints_ = flag; }
43+
44+
inline bool get_use_big_ints() const { return use_big_ints_; }
45+
46+
inline void set_return_arrays(bool flag) { return_arrays_ = flag; }
47+
48+
inline bool get_return_arrays() const { return return_arrays_; }
49+
50+
inline void set_allow_bare_named_params(bool flag) {
51+
allow_bare_named_params_ = flag;
52+
}
53+
54+
inline bool get_allow_bare_named_params() const {
55+
return allow_bare_named_params_;
56+
}
57+
58+
inline void set_allow_unknown_named_params(bool flag) {
59+
allow_unknown_named_params_ = flag;
60+
}
61+
62+
inline bool get_allow_unknown_named_params() const {
63+
return allow_unknown_named_params_;
64+
}
65+
4266
private:
4367
std::string location_;
4468
bool read_only_ = false;
4569
bool enable_foreign_keys_ = true;
4670
bool enable_dqs_ = false;
4771
int timeout_ = 0;
72+
bool use_big_ints_ = false;
73+
bool return_arrays_ = false;
74+
bool allow_bare_named_params_ = true;
75+
bool allow_unknown_named_params_ = false;
4876
};
4977

5078
class StatementSync;
@@ -82,6 +110,14 @@ class DatabaseSync : public BaseObject {
82110
void FinalizeBackups();
83111
void UntrackStatement(StatementSync* statement);
84112
bool IsOpen();
113+
bool use_big_ints() const { return open_config_.get_use_big_ints(); }
114+
bool return_arrays() const { return open_config_.get_return_arrays(); }
115+
bool allow_bare_named_params() const {
116+
return open_config_.get_allow_bare_named_params();
117+
}
118+
bool allow_unknown_named_params() const {
119+
return open_config_.get_allow_unknown_named_params();
120+
}
85121
sqlite3* Connection();
86122

87123
// In some situations, such as when using custom functions, it is possible

test/parallel/test-sqlite-database-sync.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,113 @@ suite('DatabaseSync() constructor', () => {
174174
t.after(() => { db.close(); });
175175
db.exec('SELECT "foo";');
176176
});
177+
178+
test('throws if options.readBigInts is provided but is not a boolean', (t) => {
179+
t.assert.throws(() => {
180+
new DatabaseSync('foo', { readBigInts: 42 });
181+
}, {
182+
code: 'ERR_INVALID_ARG_TYPE',
183+
message: 'The "options.readBigInts" argument must be a boolean.',
184+
});
185+
});
186+
187+
test('allows reading big integers', (t) => {
188+
const dbPath = nextDb();
189+
const db = new DatabaseSync(dbPath, { readBigInts: true });
190+
t.after(() => { db.close(); });
191+
192+
const setup = db.exec(`
193+
CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;
194+
INSERT INTO data (key, val) VALUES (1, 42);
195+
`);
196+
t.assert.strictEqual(setup, undefined);
197+
198+
const query = db.prepare('SELECT val FROM data');
199+
t.assert.deepStrictEqual(query.get(), { __proto__: null, val: 42n });
200+
201+
const insert = db.prepare('INSERT INTO data (key) VALUES (?)');
202+
t.assert.deepStrictEqual(
203+
insert.run(20),
204+
{ changes: 1n, lastInsertRowid: 20n },
205+
);
206+
});
207+
208+
test('throws if options.returnArrays is provided but is not a boolean', (t) => {
209+
t.assert.throws(() => {
210+
new DatabaseSync('foo', { returnArrays: 42 });
211+
}, {
212+
code: 'ERR_INVALID_ARG_TYPE',
213+
message: 'The "options.returnArrays" argument must be a boolean.',
214+
});
215+
});
216+
217+
test('allows returning arrays', (t) => {
218+
const dbPath = nextDb();
219+
const db = new DatabaseSync(dbPath, { returnArrays: true });
220+
t.after(() => { db.close(); });
221+
const setup = db.exec(`
222+
CREATE TABLE data(key INTEGER PRIMARY KEY, val TEXT) STRICT;
223+
INSERT INTO data (key, val) VALUES (1, 'one');
224+
INSERT INTO data (key, val) VALUES (2, 'two');
225+
`);
226+
t.assert.strictEqual(setup, undefined);
227+
228+
const query = db.prepare('SELECT key, val FROM data WHERE key = 1');
229+
t.assert.deepStrictEqual(query.get(), [1, 'one']);
230+
});
231+
232+
test('throws if options.allowBareNamedParameters is provided but is not a boolean', (t) => {
233+
t.assert.throws(() => {
234+
new DatabaseSync('foo', { allowBareNamedParameters: 42 });
235+
}, {
236+
code: 'ERR_INVALID_ARG_TYPE',
237+
message: 'The "options.allowBareNamedParameters" argument must be a boolean.',
238+
});
239+
});
240+
241+
test('throws if bare named parameters are used when option is false', (t) => {
242+
const dbPath = nextDb();
243+
const db = new DatabaseSync(dbPath, { allowBareNamedParameters: false });
244+
t.after(() => { db.close(); });
245+
const setup = db.exec(
246+
'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;'
247+
);
248+
t.assert.strictEqual(setup, undefined);
249+
250+
const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)');
251+
t.assert.throws(() => {
252+
stmt.run({ k: 2, v: 4 });
253+
}, {
254+
code: 'ERR_INVALID_STATE',
255+
message: /Unknown named parameter 'k'/,
256+
});
257+
});
258+
259+
test('throws if options.allowUnknownNamedParameters is provided but is not a boolean', (t) => {
260+
t.assert.throws(() => {
261+
new DatabaseSync('foo', { allowUnknownNamedParameters: 42 });
262+
}, {
263+
code: 'ERR_INVALID_ARG_TYPE',
264+
message: 'The "options.allowUnknownNamedParameters" argument must be a boolean.',
265+
});
266+
});
267+
268+
test('allows unknown named parameters', (t) => {
269+
const dbPath = nextDb();
270+
const db = new DatabaseSync(dbPath, { allowUnknownNamedParameters: true });
271+
t.after(() => { db.close(); });
272+
const setup = db.exec(
273+
'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;'
274+
);
275+
t.assert.strictEqual(setup, undefined);
276+
277+
const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)');
278+
const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 };
279+
t.assert.deepStrictEqual(
280+
stmt.run(params),
281+
{ changes: 1, lastInsertRowid: 1 },
282+
);
283+
});
177284
});
178285

179286
suite('DatabaseSync.prototype.open()', () => {

0 commit comments

Comments
 (0)