Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 8 additions & 0 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,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
70 changes: 64 additions & 6 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,65 @@ 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(),
"The \"options.readBigInts\" argument must be a boolean.");
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
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(),
"The \"options.returnArrays\" argument must be a boolean.");
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
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(),
"The \"options.allowBareNamedParameters\" "
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
"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(),
"The \"options.allowUnknownNamedParameters\" "
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
"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 +1831,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
182 changes: 182 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,188 @@ 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);
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
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);
Comment thread
miguelmarcondesf marked this conversation as resolved.
Outdated
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 },
);
});

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('allows returning objects', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { returnArrays: false });
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(), { __proto__: null, key: 1, val: '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('allows bare named parameters', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { allowBareNamedParameters: true });
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.deepStrictEqual(
stmt.run({ k: 1, v: 2 }),
{ changes: 1, lastInsertRowid: 1 },
);
});

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 },
);
});

test('throws if unknown named parameters are used when option is false', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath, { allowUnknownNamedParameters: false });
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'/,
});
});
});

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