Skip to content

Commit b68524e

Browse files
araujoguijuanarbol
authored andcommitted
sqlite: add sqlite prepare options args
PR-URL: #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>
1 parent 4e4ffea commit b68524e

5 files changed

Lines changed: 457 additions & 1 deletion

File tree

doc/api/sqlite.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,23 @@ Opens the database specified in the `path` argument of the `DatabaseSync`
408408
constructor. This method should only be used when the database is not opened via
409409
the constructor. An exception is thrown if the database is already open.
410410

411-
### `database.prepare(sql)`
411+
### `database.prepare(sql[, options])`
412412

413413
<!-- YAML
414414
added: v22.5.0
415415
-->
416416

417417
* `sql` {string} A SQL string to compile to a prepared statement.
418+
* `options` {Object} Optional configuration for the prepared statement.
419+
* `readBigInts` {boolean} If `true`, integer fields are read as `BigInt`s.
420+
**Default:** inherited from database options or `false`.
421+
* `returnArrays` {boolean} If `true`, results are returned as arrays.
422+
**Default:** inherited from database options or `false`.
423+
* `allowBareNamedParameters` {boolean} If `true`, allows binding named
424+
parameters without the prefix character. **Default:** inherited from
425+
database options or `true`.
426+
* `allowUnknownNamedParameters` {boolean} If `true`, unknown named parameters
427+
are ignored. **Default:** inherited from database options or `false`.
418428
* Returns: {StatementSync} The prepared statement.
419429

420430
Compiles a SQL statement into a [prepared statement][]. This method is a wrapper

src/node_sqlite.cc

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,92 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
10751075
return;
10761076
}
10771077

1078+
std::optional<bool> return_arrays;
1079+
std::optional<bool> use_big_ints;
1080+
std::optional<bool> allow_bare_named_params;
1081+
std::optional<bool> allow_unknown_named_params;
1082+
1083+
if (args.Length() > 1 && !args[1]->IsUndefined()) {
1084+
if (!args[1]->IsObject()) {
1085+
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
1086+
"The \"options\" argument must be an object.");
1087+
return;
1088+
}
1089+
Local<Object> options = args[1].As<Object>();
1090+
1091+
Local<Value> return_arrays_v;
1092+
if (!options
1093+
->Get(env->context(),
1094+
FIXED_ONE_BYTE_STRING(env->isolate(), "returnArrays"))
1095+
.ToLocal(&return_arrays_v)) {
1096+
return;
1097+
}
1098+
if (!return_arrays_v->IsUndefined()) {
1099+
if (!return_arrays_v->IsBoolean()) {
1100+
THROW_ERR_INVALID_ARG_TYPE(
1101+
env->isolate(),
1102+
"The \"options.returnArrays\" argument must be a boolean.");
1103+
return;
1104+
}
1105+
return_arrays = return_arrays_v->IsTrue();
1106+
}
1107+
1108+
Local<Value> read_big_ints_v;
1109+
if (!options
1110+
->Get(env->context(),
1111+
FIXED_ONE_BYTE_STRING(env->isolate(), "readBigInts"))
1112+
.ToLocal(&read_big_ints_v)) {
1113+
return;
1114+
}
1115+
if (!read_big_ints_v->IsUndefined()) {
1116+
if (!read_big_ints_v->IsBoolean()) {
1117+
THROW_ERR_INVALID_ARG_TYPE(
1118+
env->isolate(),
1119+
"The \"options.readBigInts\" argument must be a boolean.");
1120+
return;
1121+
}
1122+
use_big_ints = read_big_ints_v->IsTrue();
1123+
}
1124+
1125+
Local<Value> allow_bare_named_params_v;
1126+
if (!options
1127+
->Get(env->context(),
1128+
FIXED_ONE_BYTE_STRING(env->isolate(),
1129+
"allowBareNamedParameters"))
1130+
.ToLocal(&allow_bare_named_params_v)) {
1131+
return;
1132+
}
1133+
if (!allow_bare_named_params_v->IsUndefined()) {
1134+
if (!allow_bare_named_params_v->IsBoolean()) {
1135+
THROW_ERR_INVALID_ARG_TYPE(
1136+
env->isolate(),
1137+
"The \"options.allowBareNamedParameters\" argument must be a "
1138+
"boolean.");
1139+
return;
1140+
}
1141+
allow_bare_named_params = allow_bare_named_params_v->IsTrue();
1142+
}
1143+
1144+
Local<Value> allow_unknown_named_params_v;
1145+
if (!options
1146+
->Get(env->context(),
1147+
FIXED_ONE_BYTE_STRING(env->isolate(),
1148+
"allowUnknownNamedParameters"))
1149+
.ToLocal(&allow_unknown_named_params_v)) {
1150+
return;
1151+
}
1152+
if (!allow_unknown_named_params_v->IsUndefined()) {
1153+
if (!allow_unknown_named_params_v->IsBoolean()) {
1154+
THROW_ERR_INVALID_ARG_TYPE(
1155+
env->isolate(),
1156+
"The \"options.allowUnknownNamedParameters\" argument must be a "
1157+
"boolean.");
1158+
return;
1159+
}
1160+
allow_unknown_named_params = allow_unknown_named_params_v->IsTrue();
1161+
}
1162+
}
1163+
10781164
Utf8Value sql(env->isolate(), args[0].As<String>());
10791165
sqlite3_stmt* s = nullptr;
10801166
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
@@ -1083,6 +1169,20 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
10831169
BaseObjectPtr<StatementSync> stmt =
10841170
StatementSync::Create(env, BaseObjectPtr<DatabaseSync>(db), s);
10851171
db->statements_.insert(stmt.get());
1172+
1173+
if (return_arrays.has_value()) {
1174+
stmt->return_arrays_ = return_arrays.value();
1175+
}
1176+
if (use_big_ints.has_value()) {
1177+
stmt->use_big_ints_ = use_big_ints.value();
1178+
}
1179+
if (allow_bare_named_params.has_value()) {
1180+
stmt->allow_bare_named_params_ = allow_bare_named_params.value();
1181+
}
1182+
if (allow_unknown_named_params.has_value()) {
1183+
stmt->allow_unknown_named_params_ = allow_unknown_named_params.value();
1184+
}
1185+
10861186
args.GetReturnValue().Set(stmt->object());
10871187
}
10881188

src/node_sqlite.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ class StatementSync : public BaseObject {
205205
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
206206
bool BindValue(const v8::Local<v8::Value>& value, const int index);
207207

208+
friend class DatabaseSync;
208209
friend class StatementSyncIterator;
209210
};
210211

test/parallel/test-sqlite-named-parameters.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,103 @@ suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => {
119119
});
120120
});
121121
});
122+
123+
suite('options.allowUnknownNamedParameters', () => {
124+
test('unknown named parameters are allowed when input is true', (t) => {
125+
const db = new DatabaseSync(':memory:');
126+
t.after(() => { db.close(); });
127+
const setup = db.exec(
128+
'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;'
129+
);
130+
t.assert.strictEqual(setup, undefined);
131+
const stmt = db.prepare(
132+
'INSERT INTO data (key, val) VALUES ($k, $v)',
133+
{ allowUnknownNamedParameters: true }
134+
);
135+
const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 };
136+
t.assert.deepStrictEqual(
137+
stmt.run(params),
138+
{ changes: 1, lastInsertRowid: 1 },
139+
);
140+
});
141+
142+
test('unknown named parameters throw when input is false', (t) => {
143+
const db = new DatabaseSync(':memory:');
144+
t.after(() => { db.close(); });
145+
const setup = db.exec(
146+
'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;'
147+
);
148+
t.assert.strictEqual(setup, undefined);
149+
const stmt = db.prepare(
150+
'INSERT INTO data (key, val) VALUES ($k, $v)',
151+
{ allowUnknownNamedParameters: false }
152+
);
153+
const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 };
154+
t.assert.throws(() => {
155+
stmt.run(params);
156+
}, {
157+
code: 'ERR_INVALID_STATE',
158+
message: /Unknown named parameter '\$a'/,
159+
});
160+
});
161+
162+
test('unknown named parameters throws error by default', (t) => {
163+
const db = new DatabaseSync(':memory:');
164+
t.after(() => { db.close(); });
165+
const setup = db.exec(
166+
'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;'
167+
);
168+
t.assert.strictEqual(setup, undefined);
169+
const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)');
170+
const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 };
171+
t.assert.throws(() => {
172+
stmt.run(params);
173+
}, {
174+
code: 'ERR_INVALID_STATE',
175+
message: /Unknown named parameter '\$a'/,
176+
});
177+
});
178+
179+
test('throws when option is not a boolean', (t) => {
180+
const db = new DatabaseSync(':memory:');
181+
t.after(() => { db.close(); });
182+
const setup = db.exec(
183+
'CREATE TABLE data(key INTEGER PRIMARY KEY, val INTEGER) STRICT;'
184+
);
185+
t.assert.strictEqual(setup, undefined);
186+
t.assert.throws(() => {
187+
db.prepare(
188+
'INSERT INTO data (key, val) VALUES ($k, $v)',
189+
{ allowUnknownNamedParameters: 'true' }
190+
);
191+
}, {
192+
code: 'ERR_INVALID_ARG_TYPE',
193+
message: /The "options\.allowUnknownNamedParameters" argument must be a boolean/,
194+
});
195+
});
196+
197+
test('setAllowUnknownNamedParameters can override prepare option', (t) => {
198+
const db = new DatabaseSync(':memory:');
199+
t.after(() => { db.close(); });
200+
const setup = db.exec(
201+
'CREATE TABLE data(key INTEGER, val INTEGER) STRICT;'
202+
);
203+
t.assert.strictEqual(setup, undefined);
204+
const stmt = db.prepare(
205+
'INSERT INTO data (key, val) VALUES ($k, $v)',
206+
{ allowUnknownNamedParameters: true }
207+
);
208+
const params = { $a: 1, $b: 2, $k: 42, $y: 25, $v: 84, $z: 99 };
209+
t.assert.deepStrictEqual(
210+
stmt.run(params),
211+
{ changes: 1, lastInsertRowid: 1 },
212+
);
213+
t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(false), undefined);
214+
t.assert.throws(() => {
215+
stmt.run(params);
216+
}, {
217+
code: 'ERR_INVALID_STATE',
218+
message: /Unknown named parameter '\$a'/,
219+
});
220+
});
221+
});

0 commit comments

Comments
 (0)