sqlite: replace ToLocalChecked and improve filter error handling

PR-URL: https://github.com/nodejs/node/pull/60028
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Edy Silva 2025-09-26 14:04:05 -03:00 committed by Anna Henningsen
parent 075936b413
commit 413693481f
No known key found for this signature in database
2 changed files with 51 additions and 23 deletions

View File

@ -1763,21 +1763,27 @@ void DatabaseSync::ApplyChangeset(const FunctionCallbackInfo<Value>& args) {
Local<Function> filterFunc = filterValue.As<Function>(); Local<Function> filterFunc = filterValue.As<Function>();
context.filterCallback = [env, context.filterCallback = [&](std::string_view item) -> bool {
filterFunc](std::string_view item) -> bool { // If there was an error in the previous call to the filter's
// TODO(@jasnell): The use of ToLocalChecked here means that if // callback, we skip calling it again.
// the filter function throws an error the process will crash. if (db->ignore_next_sqlite_error_) {
// The filterCallback should be updated to avoid the check and return false;
// propagate the error correctly. }
Local<Value> argv[] = {
String::NewFromUtf8(env->isolate(), Local<Value> argv[1];
item.data(), if (!ToV8Value(env->context(), item, env->isolate())
NewStringType::kNormal, .ToLocal(&argv[0])) {
static_cast<int>(item.size())) db->SetIgnoreNextSQLiteError(true);
.ToLocalChecked()}; return false;
Local<Value> result = }
filterFunc->Call(env->context(), Null(env->isolate()), 1, argv)
.ToLocalChecked(); Local<Value> result;
if (!filterFunc->Call(env->context(), Null(env->isolate()), 1, argv)
.ToLocal(&result)) {
db->SetIgnoreNextSQLiteError(true);
return false;
}
return result->BooleanValue(env->isolate()); return result->BooleanValue(env->isolate());
}; };
} }
@ -2239,9 +2245,11 @@ Local<Value> StatementExecutionHelper::Get(Environment* env,
LocalVector<Name> keys(isolate); LocalVector<Name> keys(isolate);
keys.reserve(num_cols); keys.reserve(num_cols);
for (int i = 0; i < num_cols; ++i) { for (int i = 0; i < num_cols; ++i) {
MaybeLocal<Name> key = ColumnNameToName(env, stmt, i); Local<Name> key;
if (key.IsEmpty()) return Undefined(isolate); if (!ColumnNameToName(env, stmt, i).ToLocal(&key)) {
keys.emplace_back(key.ToLocalChecked()); return Undefined(isolate);
}
keys.emplace_back(key);
} }
DCHECK_EQ(keys.size(), row_values.size()); DCHECK_EQ(keys.size(), row_values.size());
@ -2755,12 +2763,8 @@ BaseObjectPtr<StatementSync> SQLTagStore::PrepareStatement(
if (stmt == nullptr) { if (stmt == nullptr) {
sqlite3_stmt* s = nullptr; sqlite3_stmt* s = nullptr;
Local<String> sql_str =
String::NewFromUtf8(isolate, sql.c_str()).ToLocalChecked();
Utf8Value sql_utf8(isolate, sql_str);
int r = sqlite3_prepare_v2( int r = sqlite3_prepare_v2(
session->database_->connection_, *sql_utf8, -1, &s, 0); session->database_->connection_, sql.c_str(), -1, &s, 0);
if (r != SQLITE_OK) { if (r != SQLITE_OK) {
THROW_ERR_SQLITE_ERROR(isolate, "Failed to prepare statement"); THROW_ERR_SQLITE_ERROR(isolate, "Failed to prepare statement");

View File

@ -366,6 +366,30 @@ suite('conflict resolution', () => {
}); });
}); });
test('filter handler throws', (t) => {
const database1 = new DatabaseSync(':memory:');
const database2 = new DatabaseSync(':memory:');
const createTableSql = 'CREATE TABLE data1(key INTEGER PRIMARY KEY); CREATE TABLE data2(key INTEGER PRIMARY KEY);';
database1.exec(createTableSql);
database2.exec(createTableSql);
const session = database1.createSession();
database1.exec('INSERT INTO data1 (key) VALUES (1), (2), (3)');
database1.exec('INSERT INTO data2 (key) VALUES (1), (2), (3), (4), (5)');
t.assert.throws(() => {
database2.applyChangeset(session.changeset(), {
filter: (tableName) => {
throw new Error(`Error filtering table ${tableName}`);
}
});
}, {
name: 'Error',
message: 'Error filtering table data1'
});
});
test('database.createSession() - filter changes', (t) => { test('database.createSession() - filter changes', (t) => {
const database1 = new DatabaseSync(':memory:'); const database1 = new DatabaseSync(':memory:');
const database2 = new DatabaseSync(':memory:'); const database2 = new DatabaseSync(':memory:');