Skip to content
Open
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: 11 additions & 1 deletion doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
100 changes: 100 additions & 0 deletions src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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());
}

Expand Down
1 change: 1 addition & 0 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
100 changes: 100 additions & 0 deletions test/parallel/test-sqlite-named-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'/,
});
});
});
Loading
Loading