Skip to content

Commit b1a9da1

Browse files
committed
fix(database): improve error handling for deferred authorizer exceptions in Prepare and Exec methods
1 parent 84ca7df commit b1a9da1

File tree

1 file changed

+44
-6
lines changed

1 file changed

+44
-6
lines changed

src/sqlite_impl.cpp

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,10 @@ Napi::Value DatabaseSync::Prepare(const Napi::CallbackInfo &info) {
556556

557557
std::string sql = info[0].As<Napi::String>().Utf8Value();
558558

559+
// Clear any stale deferred exception from a previous operation
560+
ClearDeferredAuthorizerException();
561+
SetIgnoreNextSQLiteError(false);
562+
559563
try {
560564
// Create new StatementSync instance using addon data constructor
561565
AddonData *addon_data = GetAddonData(env);
@@ -573,6 +577,25 @@ Napi::Value DatabaseSync::Prepare(const Napi::CallbackInfo &info) {
573577

574578
return stmt_obj;
575579
} catch (const std::exception &e) {
580+
// Handle deferred authorizer exceptions:
581+
//
582+
// When an authorizer callback throws a JavaScript exception, we use a
583+
// "marker" exception pattern to safely propagate the error:
584+
//
585+
// 1. On Windows (MSVC), std::exception::what() can sometimes return an
586+
// empty string, causing message loss.
587+
//
588+
// 2. By storing the message in the DatabaseSync instance, we can retrieve
589+
// it here and throw a proper JavaScript exception with the original text.
590+
//
591+
// See also: StatementSync::InitStatement for the other half of this pattern.
592+
if (HasDeferredAuthorizerException()) {
593+
std::string deferred_msg = GetDeferredAuthorizerException();
594+
ClearDeferredAuthorizerException();
595+
SetIgnoreNextSQLiteError(false);
596+
Napi::Error::New(env, deferred_msg).ThrowAsJavaScriptException();
597+
return env.Undefined();
598+
}
576599
node::THROW_ERR_SQLITE_ERROR(env, e.what());
577600
return env.Undefined();
578601
}
@@ -597,6 +620,10 @@ Napi::Value DatabaseSync::Exec(const Napi::CallbackInfo &info) {
597620

598621
std::string sql = info[0].As<Napi::String>().Utf8Value();
599622

623+
// Clear any stale deferred exception from a previous operation
624+
ClearDeferredAuthorizerException();
625+
SetIgnoreNextSQLiteError(false);
626+
600627
char *error_msg = nullptr;
601628
int result =
602629
sqlite3_exec(connection(), sql.c_str(), nullptr, nullptr, &error_msg);
@@ -1459,12 +1486,23 @@ void StatementSync::InitStatement(DatabaseSync *database,
14591486
&statement_, &tail);
14601487

14611488
if (result != SQLITE_OK) {
1462-
// Check for deferred authorizer exception first
1489+
// Handle deferred authorizer exceptions:
1490+
//
1491+
// When an authorizer callback throws a JavaScript exception, we use a
1492+
// "marker" exception pattern to safely propagate the error:
1493+
//
1494+
// 1. On Windows (MSVC), std::exception::what() can sometimes return an
1495+
// empty string, causing message loss.
1496+
//
1497+
// 2. By storing the message in the DatabaseSync instance, the caller can
1498+
// retrieve it and throw a proper JavaScript exception with the original text.
1499+
//
1500+
// 3. This matches Node.js's behavior where JavaScript exceptions from
1501+
// authorizer callbacks propagate correctly to the caller.
14631502
if (database->HasDeferredAuthorizerException()) {
1464-
std::string deferred_msg = database->GetDeferredAuthorizerException();
1465-
database->ClearDeferredAuthorizerException();
1466-
database->SetIgnoreNextSQLiteError(false);
1467-
throw std::runtime_error(deferred_msg);
1503+
// Throw a marker exception - the actual message is stored in the database
1504+
// object and will be retrieved by the caller.
1505+
throw std::runtime_error("");
14681506
}
14691507
std::string error = sqlite3_errmsg(database->connection());
14701508
throw std::runtime_error("Failed to prepare statement: " + error);
@@ -2929,7 +2967,7 @@ int DatabaseSync::AuthorizerCallback(void *user_data, int action_code,
29292967
if (int_result != SQLITE_OK && int_result != SQLITE_DENY &&
29302968
int_result != SQLITE_IGNORE) {
29312969
db->SetDeferredAuthorizerException(
2932-
"Authorizer callback returned a invalid authorization code");
2970+
"Authorizer callback returned an invalid authorization code");
29332971
db->SetIgnoreNextSQLiteError(true);
29342972
return SQLITE_DENY;
29352973
}

0 commit comments

Comments
 (0)