Repository: nodejs/node
Pull request: 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

Opening this PR since there hasn't been a reply on #56773 yet.

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: src/node_sqlite.cc
patch: @@ -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() {}
-
-  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);
-
-    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 {
-            THROW_ERR_OUT_OF_RANGE(isolate,
-                                   "Value is too large to be represented as a "
-                                   "JavaScript number: %" PRId64,
-                                   val);
-            return;
-          }
-          break;
-        }
-        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;
+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) {}
+
+UserDefinedFunction::~UserDefinedFunction() {}
+
+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);
+
+  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;
         }
-        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);
   }
 
-  static void xDestroy(void* self) {
-    delete static_cast<UserDefinedFunction*>(self);
+  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;
   }
 
- private:
-  Environment* env_;
-  Global<Function> fn_;
-  bool use_bigint_args_;
-};
+  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);
+  }
+}
+
+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,26 +313,26 @@ 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(
       connection_,
       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); });
 

filename: src/node_sqlite.h
patch: @@ -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
 

filename: test/parallel/test-sqlite-custom-functions.js
patch: @@ -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) => {