sqlite: handle conflicting SQLite and JS errors

This commit adds support for the situation where SQLite is
trying to report an error while JavaScript already has an
exception pending.

Fixes: https://github.com/nodejs/node/issues/56772
PR-URL: https://github.com/nodejs/node/pull/56787
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
This commit is contained in:
Colin Ihrig 2025-02-04 08:58:33 -05:00 committed by GitHub
parent dd92abc405
commit c4fb331390
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 212 additions and 136 deletions

View File

@ -101,9 +101,16 @@ inline MaybeLocal<Object> CreateSQLiteError(Isolate* isolate, sqlite3* db) {
return e;
}
inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, sqlite3* db) {
class DatabaseSync;
inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, DatabaseSync* db) {
if (db->ShouldIgnoreSQLiteError()) {
db->SetIgnoreNextSQLiteError(false);
return;
}
Local<Object> e;
if (CreateSQLiteError(isolate, db).ToLocal(&e)) {
if (CreateSQLiteError(isolate, db->Connection()).ToLocal(&e)) {
isolate->ThrowException(e);
}
}
@ -128,122 +135,130 @@ inline void THROW_ERR_SQLITE_ERROR(Isolate* isolate, int errcode) {
isolate->ThrowException(error);
}
class UserDefinedFunction {
public:
explicit UserDefinedFunction(Environment* env,
Local<Function> fn,
bool use_bigint_args)
: env_(env), fn_(env->isolate(), fn), use_bigint_args_(use_bigint_args) {}
virtual ~UserDefinedFunction() {}
UserDefinedFunction::UserDefinedFunction(Environment* env,
Local<Function> fn,
DatabaseSync* db,
bool use_bigint_args)
: env_(env),
fn_(env->isolate(), fn),
db_(db),
use_bigint_args_(use_bigint_args) {}
static void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
UserDefinedFunction* self =
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
Environment* env = self->env_;
Isolate* isolate = env->isolate();
auto recv = Undefined(isolate);
auto fn = self->fn_.Get(isolate);
LocalVector<Value> js_argv(isolate);
UserDefinedFunction::~UserDefinedFunction() {}
for (int i = 0; i < argc; ++i) {
sqlite3_value* value = argv[i];
MaybeLocal<Value> js_val;
void UserDefinedFunction::xFunc(sqlite3_context* ctx,
int argc,
sqlite3_value** argv) {
UserDefinedFunction* self =
static_cast<UserDefinedFunction*>(sqlite3_user_data(ctx));
Environment* env = self->env_;
Isolate* isolate = env->isolate();
auto recv = Undefined(isolate);
auto fn = self->fn_.Get(isolate);
LocalVector<Value> js_argv(isolate);
switch (sqlite3_value_type(value)) {
case SQLITE_INTEGER: {
sqlite3_int64 val = sqlite3_value_int64(value);
if (self->use_bigint_args_) {
js_val = BigInt::New(isolate, val);
} else if (std::abs(val) <= kMaxSafeJsInteger) {
js_val = Number::New(isolate, val);
} else {
THROW_ERR_OUT_OF_RANGE(isolate,
"Value is too large to be represented as a "
"JavaScript number: %" PRId64,
val);
return;
}
break;
for (int i = 0; i < argc; ++i) {
sqlite3_value* value = argv[i];
MaybeLocal<Value> js_val;
switch (sqlite3_value_type(value)) {
case SQLITE_INTEGER: {
sqlite3_int64 val = sqlite3_value_int64(value);
if (self->use_bigint_args_) {
js_val = BigInt::New(isolate, val);
} else if (std::abs(val) <= kMaxSafeJsInteger) {
js_val = Number::New(isolate, val);
} else {
// Ignore the SQLite error because a JavaScript exception is being
// thrown.
self->db_->SetIgnoreNextSQLiteError(true);
sqlite3_result_error(ctx, "", 0);
THROW_ERR_OUT_OF_RANGE(isolate,
"Value is too large to be represented as a "
"JavaScript number: %" PRId64,
val);
return;
}
case SQLITE_FLOAT:
js_val = Number::New(isolate, sqlite3_value_double(value));
break;
case SQLITE_TEXT: {
const char* v =
reinterpret_cast<const char*>(sqlite3_value_text(value));
js_val = String::NewFromUtf8(isolate, v).As<Value>();
break;
}
case SQLITE_NULL:
js_val = Null(isolate);
break;
case SQLITE_BLOB: {
size_t size = static_cast<size_t>(sqlite3_value_bytes(value));
auto data =
reinterpret_cast<const uint8_t*>(sqlite3_value_blob(value));
auto store = ArrayBuffer::NewBackingStore(isolate, size);
memcpy(store->Data(), data, size);
auto ab = ArrayBuffer::New(isolate, std::move(store));
js_val = Uint8Array::New(ab, 0, size);
break;
}
default:
UNREACHABLE("Bad SQLite value");
break;
}
Local<Value> local;
if (!js_val.ToLocal(&local)) {
return;
case SQLITE_FLOAT:
js_val = Number::New(isolate, sqlite3_value_double(value));
break;
case SQLITE_TEXT: {
const char* v =
reinterpret_cast<const char*>(sqlite3_value_text(value));
js_val = String::NewFromUtf8(isolate, v).As<Value>();
break;
}
js_argv.emplace_back(local);
case SQLITE_NULL:
js_val = Null(isolate);
break;
case SQLITE_BLOB: {
size_t size = static_cast<size_t>(sqlite3_value_bytes(value));
auto data = reinterpret_cast<const uint8_t*>(sqlite3_value_blob(value));
auto store = ArrayBuffer::NewBackingStore(isolate, size);
memcpy(store->Data(), data, size);
auto ab = ArrayBuffer::New(isolate, std::move(store));
js_val = Uint8Array::New(ab, 0, size);
break;
}
default:
UNREACHABLE("Bad SQLite value");
}
MaybeLocal<Value> retval =
fn->Call(env->context(), recv, argc, js_argv.data());
Local<Value> result;
if (!retval.ToLocal(&result)) {
Local<Value> local;
if (!js_val.ToLocal(&local)) {
// Ignore the SQLite error because a JavaScript exception is pending.
self->db_->SetIgnoreNextSQLiteError(true);
sqlite3_result_error(ctx, "", 0);
return;
}
if (result->IsUndefined() || result->IsNull()) {
sqlite3_result_null(ctx);
} else if (result->IsNumber()) {
sqlite3_result_double(ctx, result.As<Number>()->Value());
} else if (result->IsString()) {
Utf8Value val(isolate, result.As<String>());
sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT);
} else if (result->IsArrayBufferView()) {
ArrayBufferViewContents<uint8_t> buf(result);
sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT);
} else if (result->IsBigInt()) {
bool lossless;
int64_t as_int = result.As<BigInt>()->Int64Value(&lossless);
if (!lossless) {
sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1);
return;
}
sqlite3_result_int64(ctx, as_int);
} else if (result->IsPromise()) {
sqlite3_result_error(
ctx, "Asynchronous user-defined functions are not supported", -1);
} else {
sqlite3_result_error(
ctx,
"Returned JavaScript value cannot be converted to a SQLite value",
-1);
js_argv.emplace_back(local);
}
MaybeLocal<Value> retval =
fn->Call(env->context(), recv, argc, js_argv.data());
Local<Value> result;
if (!retval.ToLocal(&result)) {
// Ignore the SQLite error because a JavaScript exception is pending.
self->db_->SetIgnoreNextSQLiteError(true);
sqlite3_result_error(ctx, "", 0);
return;
}
if (result->IsUndefined() || result->IsNull()) {
sqlite3_result_null(ctx);
} else if (result->IsNumber()) {
sqlite3_result_double(ctx, result.As<Number>()->Value());
} else if (result->IsString()) {
Utf8Value val(isolate, result.As<String>());
sqlite3_result_text(ctx, *val, val.length(), SQLITE_TRANSIENT);
} else if (result->IsArrayBufferView()) {
ArrayBufferViewContents<uint8_t> buf(result);
sqlite3_result_blob(ctx, buf.data(), buf.length(), SQLITE_TRANSIENT);
} else if (result->IsBigInt()) {
bool lossless;
int64_t as_int = result.As<BigInt>()->Int64Value(&lossless);
if (!lossless) {
sqlite3_result_error(ctx, "BigInt value is too large for SQLite", -1);
return;
}
sqlite3_result_int64(ctx, as_int);
} else if (result->IsPromise()) {
sqlite3_result_error(
ctx, "Asynchronous user-defined functions are not supported", -1);
} else {
sqlite3_result_error(
ctx,
"Returned JavaScript value cannot be converted to a SQLite value",
-1);
}
}
static void xDestroy(void* self) {
delete static_cast<UserDefinedFunction*>(self);
}
private:
Environment* env_;
Global<Function> fn_;
bool use_bigint_args_;
};
void UserDefinedFunction::xDestroy(void* self) {
delete static_cast<UserDefinedFunction*>(self);
}
DatabaseSync::DatabaseSync(Environment* env,
Local<Object> object,
@ -255,6 +270,7 @@ DatabaseSync::DatabaseSync(Environment* env,
connection_ = nullptr;
allow_load_extension_ = allow_load_extension;
enable_load_extension_ = allow_load_extension;
ignore_next_sqlite_error_ = false;
if (open) {
Open();
@ -297,18 +313,18 @@ bool DatabaseSync::Open() {
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
int r = sqlite3_open_v2(
open_config_.location().c_str(), &connection_, flags, nullptr);
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
r = sqlite3_db_config(connection_,
SQLITE_DBCONFIG_DQS_DML,
static_cast<int>(open_config_.get_enable_dqs()),
nullptr);
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
r = sqlite3_db_config(connection_,
SQLITE_DBCONFIG_DQS_DDL,
static_cast<int>(open_config_.get_enable_dqs()),
nullptr);
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
int foreign_keys_enabled;
r = sqlite3_db_config(
@ -316,7 +332,7 @@ bool DatabaseSync::Open() {
SQLITE_DBCONFIG_ENABLE_FKEY,
static_cast<int>(open_config_.get_enable_foreign_keys()),
&foreign_keys_enabled);
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), this, r, SQLITE_OK, false);
CHECK_EQ(foreign_keys_enabled, open_config_.get_enable_foreign_keys());
if (allow_load_extension_) {
@ -329,7 +345,7 @@ bool DatabaseSync::Open() {
const int load_extension_ret = sqlite3_db_config(
connection_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, nullptr);
CHECK_ERROR_OR_THROW(
env()->isolate(), connection_, load_extension_ret, SQLITE_OK, false);
env()->isolate(), this, load_extension_ret, SQLITE_OK, false);
}
return true;
@ -358,6 +374,14 @@ inline sqlite3* DatabaseSync::Connection() {
return connection_;
}
void DatabaseSync::SetIgnoreNextSQLiteError(bool ignore) {
ignore_next_sqlite_error_ = ignore;
}
bool DatabaseSync::ShouldIgnoreSQLiteError() {
return ignore_next_sqlite_error_;
}
void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
@ -491,7 +515,7 @@ void DatabaseSync::Close(const FunctionCallbackInfo<Value>& args) {
db->FinalizeStatements();
db->DeleteSessions();
int r = sqlite3_close_v2(db->connection_);
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
db->connection_ = nullptr;
}
@ -510,7 +534,7 @@ void DatabaseSync::Prepare(const FunctionCallbackInfo<Value>& args) {
Utf8Value sql(env->isolate(), args[0].As<String>());
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db->connection_, *sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
BaseObjectPtr<StatementSync> stmt = StatementSync::Create(env, db, s);
db->statements_.insert(stmt.get());
args.GetReturnValue().Set(stmt->object());
@ -530,7 +554,7 @@ void DatabaseSync::Exec(const FunctionCallbackInfo<Value>& args) {
Utf8Value sql(env->isolate(), args[0].As<String>());
int r = sqlite3_exec(db->connection_, *sql, nullptr, nullptr, nullptr);
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}
void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
@ -655,7 +679,7 @@ void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
}
UserDefinedFunction* user_data =
new UserDefinedFunction(env, fn, use_bigint_args);
new UserDefinedFunction(env, fn, db, use_bigint_args);
int text_rep = SQLITE_UTF8;
if (deterministic) {
@ -675,7 +699,7 @@ void DatabaseSync::CustomFunction(const FunctionCallbackInfo<Value>& args) {
nullptr,
nullptr,
UserDefinedFunction::xDestroy);
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
}
void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
@ -732,11 +756,11 @@ void DatabaseSync::CreateSession(const FunctionCallbackInfo<Value>& args) {
sqlite3_session* pSession;
int r = sqlite3session_create(db->connection_, db_name.c_str(), &pSession);
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
db->sessions_.insert(pSession);
r = sqlite3session_attach(pSession, table == "" ? nullptr : table.c_str());
CHECK_ERROR_OR_THROW(env->isolate(), db->connection_, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
BaseObjectPtr<Session> session =
Session::Create(env, BaseObjectWeakPtr<DatabaseSync>(db), pSession);
@ -881,8 +905,7 @@ void DatabaseSync::EnableLoadExtension(
db->enable_load_extension_ = enable;
const int load_extension_ret = sqlite3_db_config(
db->connection_, SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, enable, nullptr);
CHECK_ERROR_OR_THROW(
isolate, db->connection_, load_extension_ret, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(isolate, db, load_extension_ret, SQLITE_OK, void());
}
void DatabaseSync::LoadExtension(const FunctionCallbackInfo<Value>& args) {
@ -954,8 +977,7 @@ inline bool StatementSync::IsFinalized() {
bool StatementSync::BindParams(const FunctionCallbackInfo<Value>& args) {
int r = sqlite3_clear_bindings(statement_);
CHECK_ERROR_OR_THROW(
env()->isolate(), db_->Connection(), r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), db_, r, SQLITE_OK, false);
int anon_idx = 1;
int anon_start = 0;
@ -1085,8 +1107,7 @@ bool StatementSync::BindValue(const Local<Value>& value, const int index) {
return false;
}
CHECK_ERROR_OR_THROW(
env()->isolate(), db_->Connection(), r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env()->isolate(), db_, r, SQLITE_OK, false);
return true;
}
@ -1152,7 +1173,7 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
env, stmt->IsFinalized(), "statement has been finalized");
Isolate* isolate = env->isolate();
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(isolate, stmt->db_, r, SQLITE_OK, void());
if (!stmt->BindParams(args)) {
return;
@ -1181,8 +1202,7 @@ void StatementSync::All(const FunctionCallbackInfo<Value>& args) {
rows.emplace_back(row);
}
CHECK_ERROR_OR_THROW(
isolate, stmt->db_->Connection(), r, SQLITE_DONE, void());
CHECK_ERROR_OR_THROW(isolate, stmt->db_, r, SQLITE_DONE, void());
args.GetReturnValue().Set(Array::New(isolate, rows.data(), rows.size()));
}
@ -1250,8 +1270,7 @@ void StatementSync::IterateNextCallback(
int r = sqlite3_step(stmt->statement_);
if (r != SQLITE_ROW) {
CHECK_ERROR_OR_THROW(
env->isolate(), stmt->db_->Connection(), r, SQLITE_DONE, void());
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, r, SQLITE_DONE, void());
// cleanup when no more rows to fetch
sqlite3_reset(stmt->statement_);
@ -1303,8 +1322,7 @@ void StatementSync::Iterate(const FunctionCallbackInfo<Value>& args) {
auto isolate = env->isolate();
auto context = env->context();
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(
env->isolate(), stmt->db_->Connection(), r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, r, SQLITE_OK, void());
if (!stmt->BindParams(args)) {
return;
@ -1368,7 +1386,7 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
env, stmt->IsFinalized(), "statement has been finalized");
Isolate* isolate = env->isolate();
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(isolate, stmt->db_->Connection(), r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(isolate, stmt->db_, r, SQLITE_OK, void());
if (!stmt->BindParams(args)) {
return;
@ -1378,7 +1396,7 @@ void StatementSync::Get(const FunctionCallbackInfo<Value>& args) {
r = sqlite3_step(stmt->statement_);
if (r == SQLITE_DONE) return;
if (r != SQLITE_ROW) {
THROW_ERR_SQLITE_ERROR(isolate, stmt->db_->Connection());
THROW_ERR_SQLITE_ERROR(isolate, stmt->db_);
return;
}
@ -1414,8 +1432,7 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
THROW_AND_RETURN_ON_BAD_STATE(
env, stmt->IsFinalized(), "statement has been finalized");
int r = sqlite3_reset(stmt->statement_);
CHECK_ERROR_OR_THROW(
env->isolate(), stmt->db_->Connection(), r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(env->isolate(), stmt->db_, r, SQLITE_OK, void());
if (!stmt->BindParams(args)) {
return;
@ -1424,7 +1441,7 @@ void StatementSync::Run(const FunctionCallbackInfo<Value>& args) {
auto reset = OnScopeLeave([&]() { sqlite3_reset(stmt->statement_); });
r = sqlite3_step(stmt->statement_);
if (r != SQLITE_ROW && r != SQLITE_DONE) {
THROW_ERR_SQLITE_ERROR(env->isolate(), stmt->db_->Connection());
THROW_ERR_SQLITE_ERROR(env->isolate(), stmt->db_);
return;
}
@ -1649,7 +1666,6 @@ void Session::Changeset(const FunctionCallbackInfo<Value>& args) {
Session* session;
ASSIGN_OR_RETURN_UNWRAP(&session, args.This());
Environment* env = Environment::GetCurrent(args);
sqlite3* db = session->database_ ? session->database_->connection_ : nullptr;
THROW_AND_RETURN_ON_BAD_STATE(
env, !session->database_->IsOpen(), "database is not open");
THROW_AND_RETURN_ON_BAD_STATE(
@ -1658,7 +1674,8 @@ void Session::Changeset(const FunctionCallbackInfo<Value>& args) {
int nChangeset;
void* pChangeset;
int r = sqliteChangesetFunc(session->session_, &nChangeset, &pChangeset);
CHECK_ERROR_OR_THROW(env->isolate(), db, r, SQLITE_OK, void());
CHECK_ERROR_OR_THROW(
env->isolate(), session->database_, r, SQLITE_OK, void());
auto freeChangeset = OnScopeLeave([&] { sqlite3_free(pChangeset); });

View File

@ -68,6 +68,13 @@ class DatabaseSync : public BaseObject {
bool IsOpen();
sqlite3* Connection();
// In some situations, such as when using custom functions, it is possible
// that SQLite reports an error while JavaScript already has a pending
// exception. In this case, the SQLite error should be ignored. These methods
// enable that use case.
void SetIgnoreNextSQLiteError(bool ignore);
bool ShouldIgnoreSQLiteError();
SET_MEMORY_INFO_NAME(DatabaseSync)
SET_SELF_SIZE(DatabaseSync)
@ -80,6 +87,7 @@ class DatabaseSync : public BaseObject {
bool allow_load_extension_;
bool enable_load_extension_;
sqlite3* connection_;
bool ignore_next_sqlite_error_;
std::set<sqlite3_session*> sessions_;
std::unordered_set<StatementSync*> statements_;
@ -161,6 +169,23 @@ class Session : public BaseObject {
BaseObjectWeakPtr<DatabaseSync> database_; // The Parent Database
};
class UserDefinedFunction {
public:
UserDefinedFunction(Environment* env,
v8::Local<v8::Function> fn,
DatabaseSync* db,
bool use_bigint_args);
~UserDefinedFunction();
static void xFunc(sqlite3_context* ctx, int argc, sqlite3_value** argv);
static void xDestroy(void* self);
private:
Environment* env_;
v8::Global<v8::Function> fn_;
DatabaseSync* db_;
bool use_bigint_args_;
};
} // namespace sqlite
} // namespace node

View File

@ -339,6 +339,40 @@ suite('DatabaseSync.prototype.function()', () => {
});
});
suite('handles conflicting errors from SQLite and JavaScript', () => {
test('throws if value cannot fit in a number', () => {
const db = new DatabaseSync(':memory:');
const expected = { __proto__: null, id: 5, data: 'foo' };
db.function('custom', (arg) => {});
db.exec('CREATE TABLE test (id NUMBER NOT NULL PRIMARY KEY, data TEXT)');
db.prepare('INSERT INTO test (id, data) VALUES (?, ?)').run(5, 'foo');
assert.deepStrictEqual(db.prepare('SELECT * FROM test').get(), expected);
assert.throws(() => {
db.exec(`UPDATE test SET data = CUSTOM(${Number.MAX_SAFE_INTEGER + 1})`);
}, {
code: 'ERR_OUT_OF_RANGE',
message: /Value is too large to be represented as a JavaScript number: 9007199254740992/,
});
assert.deepStrictEqual(db.prepare('SELECT * FROM test').get(), expected);
});
test('propagates JavaScript errors', () => {
const db = new DatabaseSync(':memory:');
const expected = { __proto__: null, id: 5, data: 'foo' };
const err = new Error('boom');
db.function('throws', () => {
throw err;
});
db.exec('CREATE TABLE test (id NUMBER NOT NULL PRIMARY KEY, data TEXT)');
db.prepare('INSERT INTO test (id, data) VALUES (?, ?)').run(5, 'foo');
assert.deepStrictEqual(db.prepare('SELECT * FROM test').get(), expected);
assert.throws(() => {
db.exec('UPDATE test SET data = THROWS()');
}, err);
assert.deepStrictEqual(db.prepare('SELECT * FROM test').get(), expected);
});
});
test('supported argument types', () => {
const db = new DatabaseSync(':memory:');
db.function('arguments', (i, f, s, n, b) => {