Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ exposed by this class execute synchronously.

<!-- YAML
added: v22.5.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58697
description: Add new SQLite database options.
-->

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

Constructs a new `DatabaseSync` instance.

Expand Down
3 changes: 3 additions & 0 deletions src/env_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
V(ack_string, "ack") \
V(address_string, "address") \
V(aliases_string, "aliases") \
V(allow_bare_named_params_string, "allowBareNamedParameters") \
V(allow_unknown_named_params_string, "allowUnknownNamedParameters") \
V(alpn_callback_string, "ALPNCallback") \
V(args_string, "args") \
V(asn1curve_string, "asn1Curve") \
Expand Down Expand Up @@ -324,6 +326,7 @@
V(raw_string, "raw") \
V(read_host_object_string, "_readHostObject") \
V(readable_string, "readable") \
V(read_bigints_string, "readBigInts") \
V(reason_string, "reason") \
V(refresh_string, "refresh") \
V(regexp_string, "regexp") \
Expand Down
71 changes: 65 additions & 6 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,66 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {

open_config.set_timeout(timeout_v.As<Int32>()->Value());
}

Local<Value> read_bigints_v;
if (options->Get(env->context(), env->read_bigints_string())
.ToLocal(&read_bigints_v)) {
if (!read_bigints_v->IsUndefined()) {
if (!read_bigints_v->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
R"(The "options.readBigInts" argument must be a boolean.)");
return;
}
open_config.set_use_big_ints(read_bigints_v.As<Boolean>()->Value());
}
}

Local<Value> return_arrays_v;
if (options->Get(env->context(), env->return_arrays_string())
.ToLocal(&return_arrays_v)) {
if (!return_arrays_v->IsUndefined()) {
if (!return_arrays_v->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
R"(The "options.returnArrays" argument must be a boolean.)");
return;
}
open_config.set_return_arrays(return_arrays_v.As<Boolean>()->Value());
}
}

Local<Value> allow_bare_named_params_v;
if (options->Get(env->context(), env->allow_bare_named_params_string())
.ToLocal(&allow_bare_named_params_v)) {
if (!allow_bare_named_params_v->IsUndefined()) {
if (!allow_bare_named_params_v->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
R"(The "options.allowBareNamedParameters" )"
"argument must be a boolean.");
return;
}
open_config.set_allow_bare_named_params(
allow_bare_named_params_v.As<Boolean>()->Value());
}
}

Local<Value> allow_unknown_named_params_v;
if (options->Get(env->context(), env->allow_unknown_named_params_string())
.ToLocal(&allow_unknown_named_params_v)) {
if (!allow_unknown_named_params_v->IsUndefined()) {
if (!allow_unknown_named_params_v->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(
env->isolate(),
R"(The "options.allowUnknownNamedParameters" )"
"argument must be a boolean.");
return;
}
open_config.set_allow_unknown_named_params(
allow_unknown_named_params_v.As<Boolean>()->Value());
}
}
}

new DatabaseSync(
Expand Down Expand Up @@ -1772,12 +1832,11 @@ StatementSync::StatementSync(Environment* env,
: BaseObject(env, object), db_(std::move(db)) {
MakeWeak();
statement_ = stmt;
// 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;
use_big_ints_ = db_->use_big_ints();
return_arrays_ = db_->return_arrays();
allow_bare_named_params_ = db_->allow_bare_named_params();
allow_unknown_named_params_ = db_->allow_unknown_named_params();

bare_named_params_ = std::nullopt;
}

Expand Down
36 changes: 36 additions & 0 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,40 @@ 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_; }

inline void set_return_arrays(bool flag) { return_arrays_ = flag; }

inline bool get_return_arrays() const { return return_arrays_; }

inline void set_allow_bare_named_params(bool flag) {
allow_bare_named_params_ = flag;
}

inline bool get_allow_bare_named_params() const {
return allow_bare_named_params_;
}

inline void set_allow_unknown_named_params(bool flag) {
allow_unknown_named_params_ = flag;
}

inline bool get_allow_unknown_named_params() const {
return allow_unknown_named_params_;
}

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;
bool return_arrays_ = false;
bool allow_bare_named_params_ = true;
bool allow_unknown_named_params_ = false;
};

class StatementSync;
Expand Down Expand Up @@ -82,6 +110,14 @@ class DatabaseSync : public BaseObject {
void FinalizeBackups();
void UntrackStatement(StatementSync* statement);
bool IsOpen();
bool use_big_ints() const { return open_config_.get_use_big_ints(); }
bool return_arrays() const { return open_config_.get_return_arrays(); }
bool allow_bare_named_params() const {
return open_config_.get_allow_bare_named_params();
}
bool allow_unknown_named_params() const {
return open_config_.get_allow_unknown_named_params();
}
sqlite3* Connection();

// In some situations, such as when using custom functions, it is possible
Expand Down
107 changes: 107 additions & 0 deletions test/parallel/test-sqlite-database-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,113 @@ 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.deepStrictEqual(query.get(), { __proto__: null, val: 42n });

const insert = db.prepare('INSERT INTO data (key) VALUES (?)');
t.assert.deepStrictEqual(
insert.run(20),
{ changes: 1n, lastInsertRowid: 20n },
);
});

test('throws if options.returnArrays is provided but is not a boolean', (t) => {
t.assert.throws(() => {
new DatabaseSync('foo', { returnArrays: 42 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "options.returnArrays" argument must be a boolean.',
});
});

test('allows returning arrays', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { returnArrays: true });
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 WHERE key = 1');
t.assert.deepStrictEqual(query.get(), [1, 'one']);
});

test('throws if options.allowBareNamedParameters is provided but is not a boolean', (t) => {
t.assert.throws(() => {
new DatabaseSync('foo', { allowBareNamedParameters: 42 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "options.allowBareNamedParameters" argument must be a boolean.',
});
});

test('throws if bare named parameters are used when option is false', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { allowBareNamedParameters: false });
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)');
t.assert.throws(() => {
stmt.run({ k: 2, v: 4 });
}, {
code: 'ERR_INVALID_STATE',
message: /Unknown named parameter 'k'/,
});
});

test('throws if options.allowUnknownNamedParameters is provided but is not a boolean', (t) => {
t.assert.throws(() => {
new DatabaseSync('foo', { allowUnknownNamedParameters: 42 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "options.allowUnknownNamedParameters" argument must be a boolean.',
});
});

test('allows unknown named parameters', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { allowUnknownNamedParameters: true });
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.deepStrictEqual(
stmt.run(params),
{ changes: 1, lastInsertRowid: 1 },
);
});
});

suite('DatabaseSync.prototype.open()', () => {
Expand Down
Loading