mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
sqlite: add sqlite prepare options args
PR-URL: https://github.com/nodejs/node/pull/61311 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: René <contact.9a5d6388@renegade334.me.uk> Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day> Reviewed-By: Claudio Wunder <cwunder@gnome.org>
This commit is contained in:
1 parent
bc9ffc32bd
commit
efa7621132
5 files changed
+457
-1
No files matched your search
+11
-1
@@ -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])`
|
||||
|
||||
<!-- YAML
|
||||
added: v22.5.0
|
||||
-->
|
||||
|
||||
* `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
|
||||
|
||||
@@ -1147,6 +1147,92 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<bool> return_arrays;
|
||||
std::optional<bool> use_big_ints;
|
||||
std::optional<bool> allow_bare_named_params;
|
||||
std::optional<bool> allow_unknown_named_params;
|
||||
|
||||
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<Object> options = args[1].As<Object>();
|
||||
|
||||
Local<Value> 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;
|
||||
}
|
||||
return_arrays = return_arrays_v->IsTrue();
|
||||
}
|
||||
|
||||
Local<Value> 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;
|
||||
}
|
||||
use_big_ints = read_big_ints_v->IsTrue();
|
||||
}
|
||||
|
||||
Local<Value> 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;
|
||||
}
|
||||
allow_bare_named_params = allow_bare_named_params_v->IsTrue();
|
||||
}
|
||||
|
||||
Local<Value> 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;
|
||||
}
|
||||
allow_unknown_named_params = allow_unknown_named_params_v->IsTrue();
|
||||
}
|
||||
}
|
||||
|
||||
Utf8Value sql(env->isolate(), args[0].As<String>());
|
||||
sqlite3_stmt* s = nullptr;
|
||||
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
|
||||
@@ -1155,6 +1241,20 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
|
||||
BaseObjectPtr<StatementSync> stmt =
|
||||
StatementSync::Create(env, BaseObjectPtr<DatabaseSync>(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());
|
||||
}
|
||||
|
||||
|
||||
@@ -246,6 +246,7 @@ class StatementSync : public BaseObject {
|
||||
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
bool BindValue(const v8::Local<v8::Value>& value, const int index);
|
||||
|
||||
friend class DatabaseSync;
|
||||
friend class StatementSyncIterator;
|
||||
friend class SQLTagStore;
|
||||
friend class StatementExecutionHelper;
|
||||
|
||||
@@ -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'/,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 },
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user