sqlite: add support for unknown named parameters

This commit adds a method for toggling support for unknown named
parameters in prepared statements.

Fixes: https://github.com/nodejs/node/issues/55533
PR-URL: https://github.com/nodejs/node/pull/57552
Reviewed-By: Edy Silva <edigleyssonsilva@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Colin Ihrig authored and GitHub committed 2025-03-22 01:38:34 +00:00
1 parent ace5548ff0
commit ffc1cf6205
4 files changed
+84 -3

No files matched your search

+11
View File
@@ -542,6 +542,17 @@ are several caveats to be aware of when enabling bare named parameters:
statement will result in an exception as it cannot be determined how to bind
a bare name.
### `statement.setAllowUnknownNamedParameters(enabled)`
<!-- YAML
added: REPLACEME
-->
* `enabled` {boolean} Enables or disables support for unknown named parameters.
By default, if an unknown name is encountered while binding parameters, an
exception is thrown. This method allows unknown named parameters to be ignored.
### `statement.setReadBigInts(enabled)`
<!-- YAML
+29 -3
View File
@@ -1361,6 +1361,7 @@ StatementSync::StatementSync(Environment* env,
// connection level and inherited by statements to reduce boilerplate.
use_big_ints_ = false;
allow_bare_named_params_ = true;
allow_unknown_named_params_ = false;
bare_named_params_ = std::nullopt;
}
@@ -1443,9 +1444,13 @@ bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) {
}
if (r == 0) {
THROW_ERR_INVALID_STATE(
env(), "Unknown named parameter '%s'", *utf8_key);
return false;
if (allow_unknown_named_params_) {
continue;
} else {
THROW_ERR_INVALID_STATE(
env(), "Unknown named parameter '%s'", *utf8_key);
return false;
}
}
}
@@ -2033,6 +2038,23 @@ void StatementSync::SetAllowBareNamedParameters(
stmt->allow_bare_named_params_ = args[0]->IsTrue();
}
void StatementSync::SetAllowUnknownNamedParameters(
const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
Environment* env = Environment::GetCurrent(args);
THROW_AND_RETURN_ON_BAD_STATE(
env, stmt->IsFinalized(), "statement has been finalized");
if (!args[0]->IsBoolean()) {
THROW_ERR_INVALID_ARG_TYPE(env->isolate(),
"The \"enabled\" argument must be a boolean.");
return;
}
stmt->allow_unknown_named_params_ = args[0]->IsTrue();
}
void StatementSync::SetReadBigInts(const FunctionCallbackInfo<Value>& args) {
StatementSync* stmt;
ASSIGN_OR_RETURN_UNWRAP(&stmt, args.This());
@@ -2098,6 +2120,10 @@ Local<FunctionTemplate> StatementSync::GetConstructorTemplate(
tmpl,
"setAllowBareNamedParameters",
StatementSync::SetAllowBareNamedParameters);
SetProtoMethod(isolate,
tmpl,
"setAllowUnknownNamedParameters",
StatementSync::SetAllowUnknownNamedParameters);
SetProtoMethod(
isolate, tmpl, "setReadBigInts", StatementSync::SetReadBigInts);
env->set_sqlite_statement_sync_constructor_template(tmpl);
+3
View File
@@ -123,6 +123,8 @@ class StatementSync : public BaseObject {
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAllowBareNamedParameters(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetAllowUnknownNamedParameters(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void SetReadBigInts(const v8::FunctionCallbackInfo<v8::Value>& args);
void Finalize();
bool IsFinalized();
@@ -136,6 +138,7 @@ class StatementSync : public BaseObject {
sqlite3_stmt* statement_;
bool use_big_ints_;
bool allow_bare_named_params_;
bool allow_unknown_named_params_;
std::optional<std::map<std::string, std::string>> bare_named_params_;
bool BindParams(const v8::FunctionCallbackInfo<v8::Value>& args);
bool BindValue(const v8::Local<v8::Value>& value, const int index);
@@ -77,3 +77,44 @@ suite('named parameters', () => {
});
});
});
suite('StatementSync.prototype.setAllowUnknownNamedParameters()', () => {
test('unknown named parameter support can be toggled', (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)');
t.assert.strictEqual(stmt.setAllowUnknownNamedParameters(true), undefined);
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'/,
});
});
test('throws when input 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);
const stmt = db.prepare('INSERT INTO data (key, val) VALUES ($k, $v)');
t.assert.throws(() => {
stmt.setAllowUnknownNamedParameters();
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "enabled" argument must be a boolean/,
});
});
});