mirror of https://github.com/nodejs/node.git
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:
parent
dd92abc405
commit
c4fb331390
|
@ -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); });
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in New Issue