Commit 1bffd1d0a3b for nodejs
commit 1bffd1d0a3bf02201faa14551f5835cefe7f894c
Author: Guilherme Araújo <arauujogui@gmail.com>
Date: Thu Oct 22 19:59:47 2026 -0300
sqlite: propagate errors from Database option getters
The readBigInts, returnArrays, allowBareNamedParameters and
allowUnknownNamedParameters options were read with
`if (Get().ToLocal())`, so a throwing getter left an exception pending
while the constructor carried on. Option parsing continued, the
database was opened, and the exception was lost, leaving a database
file on disk for a constructor call that should have failed.
Return as soon as reading any of these options fails, matching how
every other option is handled.
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
Assisted-by: Claude
PR-URL: https://github.com/nodejs/node/pull/66748
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc
index b4fbfa88746..705d9b4a79a 100644
--- a/src/node_sqlite.cc
+++ b/src/node_sqlite.cc
@@ -1818,6 +1818,32 @@ void Database::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(session->object());
}
+// Reads an optional boolean property, leaving |out| unchanged when it is
+// undefined. Returns false if a JavaScript exception is pending, either from
+// the property getter or because the value is not a boolean.
+template <typename T>
+static bool ReadOptionalBool(Environment* env,
+ Local<Object> options,
+ const char* key,
+ T* out) {
+ Local<String> key_string =
+ OneByteString(env->isolate(), key, -1, NewStringType::kInternalized);
+ Local<Value> value;
+ if (!options->Get(env->context(), key_string).ToLocal(&value)) {
+ return false;
+ }
+ if (value->IsUndefined()) {
+ return true;
+ }
+ if (!value->IsBoolean()) {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env->isolate(), "The \"options.%s\" argument must be a boolean.", key);
+ return false;
+ }
+ *out = value->IsTrue();
+ return true;
+}
+
std::optional<std::string> ValidateDatabasePath(Environment* env,
Local<Value> path,
const std::string& field_name) {
@@ -1893,90 +1919,23 @@ void Database::New(const FunctionCallbackInfo<Value>& args) {
}
Local<Object> options = args[1].As<Object>();
- Local<String> open_string = FIXED_ONE_BYTE_STRING(env->isolate(), "open");
- Local<Value> open_v;
- if (!options->Get(env->context(), open_string).ToLocal(&open_v)) {
+ bool read_only = open_config.get_read_only();
+ bool enable_foreign_keys = open_config.get_enable_foreign_keys();
+ bool enable_dqs = open_config.get_enable_dqs();
+ if (!ReadOptionalBool(env, options, "open", &open)) return;
+ if (!ReadOptionalBool(env, options, "readOnly", &read_only)) return;
+ if (!ReadOptionalBool(
+ env, options, "enableForeignKeyConstraints", &enable_foreign_keys))
return;
- }
- if (!open_v->IsUndefined()) {
- if (!open_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(), "The \"options.open\" argument must be a boolean.");
- return;
- }
- open = open_v.As<Boolean>()->Value();
- }
-
- Local<String> read_only_string =
- FIXED_ONE_BYTE_STRING(env->isolate(), "readOnly");
- Local<Value> read_only_v;
- if (!options->Get(env->context(), read_only_string).ToLocal(&read_only_v)) {
+ if (!ReadOptionalBool(
+ env, options, "enableDoubleQuotedStringLiterals", &enable_dqs))
return;
- }
- if (!read_only_v->IsUndefined()) {
- if (!read_only_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.readOnly\" argument must be a boolean.");
- return;
- }
- open_config.set_read_only(read_only_v.As<Boolean>()->Value());
- }
-
- Local<String> enable_foreign_keys_string =
- FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints");
- Local<Value> enable_foreign_keys_v;
- if (!options->Get(env->context(), enable_foreign_keys_string)
- .ToLocal(&enable_foreign_keys_v)) {
+ if (!ReadOptionalBool(
+ env, options, "allowExtension", &allow_load_extension))
return;
- }
- if (!enable_foreign_keys_v->IsUndefined()) {
- if (!enable_foreign_keys_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.enableForeignKeyConstraints\" argument must be a "
- "boolean.");
- return;
- }
- open_config.set_enable_foreign_keys(
- enable_foreign_keys_v.As<Boolean>()->Value());
- }
-
- Local<String> enable_dqs_string = FIXED_ONE_BYTE_STRING(
- env->isolate(), "enableDoubleQuotedStringLiterals");
- Local<Value> enable_dqs_v;
- if (!options->Get(env->context(), enable_dqs_string)
- .ToLocal(&enable_dqs_v)) {
- return;
- }
- if (!enable_dqs_v->IsUndefined()) {
- if (!enable_dqs_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.enableDoubleQuotedStringLiterals\" argument must be "
- "a boolean.");
- return;
- }
- open_config.set_enable_dqs(enable_dqs_v.As<Boolean>()->Value());
- }
-
- Local<String> allow_extension_string =
- FIXED_ONE_BYTE_STRING(env->isolate(), "allowExtension");
- Local<Value> allow_extension_v;
- if (!options->Get(env->context(), allow_extension_string)
- .ToLocal(&allow_extension_v)) {
- return;
- }
-
- if (!allow_extension_v->IsUndefined()) {
- if (!allow_extension_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.allowExtension\" argument must be a boolean.");
- return;
- }
- allow_load_extension = allow_extension_v.As<Boolean>()->Value();
- }
+ open_config.set_read_only(read_only);
+ open_config.set_enable_foreign_keys(enable_foreign_keys);
+ open_config.set_enable_dqs(enable_dqs);
Local<Value> timeout_v;
if (!options->Get(env->context(), env->timeout_string())
@@ -1995,80 +1954,28 @@ void Database::New(const FunctionCallbackInfo<Value>& args) {
open_config.set_timeout(timeout_v.As<Int32>()->Value());
}
- Local<Value> read_bigints_v;
- if (options->Get(env->context(), env->read_bigints_string())
- .ToLocal(&read_bigints_v)) {
- if (!read_bigints_v->IsUndefined()) {
- if (!read_bigints_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- R"(The "options.readBigInts" argument must be a boolean.)");
- return;
- }
- open_config.set_use_big_ints(read_bigints_v.As<Boolean>()->Value());
- }
- }
-
- Local<Value> return_arrays_v;
- if (options->Get(env->context(), env->return_arrays_string())
- .ToLocal(&return_arrays_v)) {
- if (!return_arrays_v->IsUndefined()) {
- if (!return_arrays_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- R"(The "options.returnArrays" argument must be a boolean.)");
- return;
- }
- open_config.set_return_arrays(return_arrays_v.As<Boolean>()->Value());
- }
- }
-
- Local<Value> allow_bare_named_params_v;
- if (options->Get(env->context(), env->allow_bare_named_params_string())
- .ToLocal(&allow_bare_named_params_v)) {
- if (!allow_bare_named_params_v->IsUndefined()) {
- if (!allow_bare_named_params_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- R"(The "options.allowBareNamedParameters" )"
- "argument must be a boolean.");
- return;
- }
- open_config.set_allow_bare_named_params(
- allow_bare_named_params_v.As<Boolean>()->Value());
- }
- }
-
- Local<Value> allow_unknown_named_params_v;
- if (options->Get(env->context(), env->allow_unknown_named_params_string())
- .ToLocal(&allow_unknown_named_params_v)) {
- if (!allow_unknown_named_params_v->IsUndefined()) {
- if (!allow_unknown_named_params_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- R"(The "options.allowUnknownNamedParameters" )"
- "argument must be a boolean.");
- return;
- }
- open_config.set_allow_unknown_named_params(
- allow_unknown_named_params_v.As<Boolean>()->Value());
- }
- }
-
- Local<Value> defensive_v;
- if (!options->Get(env->context(), env->defensive_string())
- .ToLocal(&defensive_v)) {
+ bool use_big_ints = open_config.get_use_big_ints();
+ bool return_arrays = open_config.get_return_arrays();
+ bool allow_bare_named_params = open_config.get_allow_bare_named_params();
+ bool allow_unknown_named_params =
+ open_config.get_allow_unknown_named_params();
+ bool defensive = open_config.get_enable_defensive();
+ if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return;
+ if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return;
+ if (!ReadOptionalBool(
+ env, options, "allowBareNamedParameters", &allow_bare_named_params))
return;
- }
- if (!defensive_v->IsUndefined()) {
- if (!defensive_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.defensive\" argument must be a boolean.");
- return;
- }
- open_config.set_enable_defensive(defensive_v.As<Boolean>()->Value());
- }
+ if (!ReadOptionalBool(env,
+ options,
+ "allowUnknownNamedParameters",
+ &allow_unknown_named_params))
+ return;
+ if (!ReadOptionalBool(env, options, "defensive", &defensive)) return;
+ open_config.set_use_big_ints(use_big_ints);
+ open_config.set_return_arrays(return_arrays);
+ open_config.set_allow_bare_named_params(allow_bare_named_params);
+ open_config.set_allow_unknown_named_params(allow_unknown_named_params);
+ open_config.set_enable_defensive(defensive);
// Parse limits option
Local<Value> limits_v;
@@ -2218,85 +2125,17 @@ void Database::Prepare(const FunctionCallbackInfo<Value>& args) {
return;
}
Local<Object> options = args[1].As<Object>();
-
- Local<Value> return_arrays_v;
- if (!options->Get(env->context(), env->return_arrays_string())
- .ToLocal(&return_arrays_v)) {
+ if (!ReadOptionalBool(env, options, "returnArrays", &return_arrays)) return;
+ if (!ReadOptionalBool(env, options, "readBigInts", &use_big_ints)) return;
+ if (!ReadOptionalBool(
+ env, options, "allowBareNamedParameters", &allow_bare_named_params))
return;
- }
- if (!return_arrays_v->IsUndefined()) {
- if (!return_arrays_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.returnArrays\" argument must be a boolean.");
- return;
- }
- return_arrays = return_arrays_v->IsTrue();
- }
-
- Local<Value> read_big_ints_v;
- if (!options->Get(env->context(), env->read_bigints_string())
- .ToLocal(&read_big_ints_v)) {
- return;
- }
- if (!read_big_ints_v->IsUndefined()) {
- if (!read_big_ints_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.readBigInts\" argument must be a boolean.");
- return;
- }
- use_big_ints = read_big_ints_v->IsTrue();
- }
-
- Local<Value> allow_bare_named_params_v;
- if (!options->Get(env->context(), env->allow_bare_named_params_string())
- .ToLocal(&allow_bare_named_params_v)) {
- return;
- }
- if (!allow_bare_named_params_v->IsUndefined()) {
- if (!allow_bare_named_params_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.allowBareNamedParameters\" argument must be a "
- "boolean.");
- return;
- }
- allow_bare_named_params = allow_bare_named_params_v->IsTrue();
- }
-
- Local<Value> allow_unknown_named_params_v;
- if (!options->Get(env->context(), env->allow_unknown_named_params_string())
- .ToLocal(&allow_unknown_named_params_v)) {
+ if (!ReadOptionalBool(env,
+ options,
+ "allowUnknownNamedParameters",
+ &allow_unknown_named_params))
return;
- }
- if (!allow_unknown_named_params_v->IsUndefined()) {
- if (!allow_unknown_named_params_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.allowUnknownNamedParameters\" argument must be a "
- "boolean.");
- return;
- }
- allow_unknown_named_params = allow_unknown_named_params_v->IsTrue();
- }
-
- Local<Value> persistent_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "persistent"))
- .ToLocal(&persistent_v)) {
- return;
- }
- if (!persistent_v->IsUndefined()) {
- if (!persistent_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.persistent\" argument must be a boolean.");
- return;
- }
- persistent = persistent_v->IsTrue();
- }
+ if (!ReadOptionalBool(env, options, "persistent", &persistent)) return;
}
// Reading the options bag above can run user JavaScript through a property
@@ -2400,77 +2239,12 @@ void Database::CustomFunction(const FunctionCallbackInfo<Value>& args) {
}
Local<Object> options = args[1].As<Object>();
- Local<Value> use_bigint_args_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments"))
- .ToLocal(&use_bigint_args_v)) {
+ if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
return;
- }
-
- if (!use_bigint_args_v->IsUndefined()) {
- if (!use_bigint_args_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.useBigIntArguments\" argument must be a boolean.");
- return;
- }
- use_bigint_args = use_bigint_args_v.As<Boolean>()->Value();
- }
-
- Local<Value> varargs_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "varargs"))
- .ToLocal(&varargs_v)) {
+ if (!ReadOptionalBool(env, options, "varargs", &varargs)) return;
+ if (!ReadOptionalBool(env, options, "deterministic", &deterministic))
return;
- }
-
- if (!varargs_v->IsUndefined()) {
- if (!varargs_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.varargs\" argument must be a boolean.");
- return;
- }
- varargs = varargs_v.As<Boolean>()->Value();
- }
-
- Local<Value> deterministic_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "deterministic"))
- .ToLocal(&deterministic_v)) {
- return;
- }
-
- if (!deterministic_v->IsUndefined()) {
- if (!deterministic_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.deterministic\" argument must be a boolean.");
- return;
- }
- deterministic = deterministic_v.As<Boolean>()->Value();
- }
-
- Local<Value> direct_only_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly"))
- .ToLocal(&direct_only_v)) {
- return;
- }
-
- if (!direct_only_v->IsUndefined()) {
- if (!direct_only_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.directOnly\" argument must be a boolean.");
- return;
- }
- direct_only = direct_only_v.As<Boolean>()->Value();
- }
+ if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;
}
if (!args[fn_index]->IsFunction()) {
@@ -2750,60 +2524,11 @@ void Database::AggregateFunction(const FunctionCallbackInfo<Value>& args) {
bool use_bigint_args = false;
bool varargs = false;
bool direct_only = false;
- Local<Value> use_bigint_args_v;
Local<Function> inverseFunc = Local<Function>();
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments"))
- .ToLocal(&use_bigint_args_v)) {
+ if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
return;
- }
-
- if (!use_bigint_args_v->IsUndefined()) {
- if (!use_bigint_args_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.useBigIntArguments\" argument must be a boolean.");
- return;
- }
- use_bigint_args = use_bigint_args_v.As<Boolean>()->Value();
- }
-
- Local<Value> varargs_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "varargs"))
- .ToLocal(&varargs_v)) {
- return;
- }
-
- if (!varargs_v->IsUndefined()) {
- if (!varargs_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.varargs\" argument must be a boolean.");
- return;
- }
- varargs = varargs_v.As<Boolean>()->Value();
- }
-
- Local<Value> direct_only_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly"))
- .ToLocal(&direct_only_v)) {
- return;
- }
-
- if (!direct_only_v->IsUndefined()) {
- if (!direct_only_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.directOnly\" argument must be a boolean.");
- return;
- }
- direct_only = direct_only_v.As<Boolean>()->Value();
- }
+ if (!ReadOptionalBool(env, options, "varargs", &varargs)) return;
+ if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;
Local<Value> inverse_v;
if (!options->Get(env->context(), env->inverse_string())
@@ -2954,42 +2679,10 @@ void Database::CreateModule(const FunctionCallbackInfo<Value>& args) {
// Extract optional boolean options.
bool direct_only = false;
- Local<Value> direct_only_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "directOnly"))
- .ToLocal(&direct_only_v)) {
- return;
- }
-
- if (!direct_only_v->IsUndefined()) {
- if (!direct_only_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.directOnly\" argument must be a boolean.");
- return;
- }
- direct_only = direct_only_v.As<Boolean>()->Value();
- }
-
bool use_bigint_args = false;
- Local<Value> use_bigint_args_v;
- if (!options
- ->Get(env->context(),
- FIXED_ONE_BYTE_STRING(env->isolate(), "useBigIntArguments"))
- .ToLocal(&use_bigint_args_v)) {
+ if (!ReadOptionalBool(env, options, "directOnly", &direct_only)) return;
+ if (!ReadOptionalBool(env, options, "useBigIntArguments", &use_bigint_args))
return;
- }
-
- if (!use_bigint_args_v->IsUndefined()) {
- if (!use_bigint_args_v->IsBoolean()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.useBigIntArguments\" argument must be a boolean.");
- return;
- }
- use_bigint_args = use_bigint_args_v.As<Boolean>()->Value();
- }
// Build CREATE TABLE schema SQL from columns.
std::string schema_sql = "CREATE TABLE x(";
diff --git a/test/parallel/test-sqlite-database.js b/test/parallel/test-sqlite-database.js
index 79e1fe35d43..66d273b6d01 100644
--- a/test/parallel/test-sqlite-database.js
+++ b/test/parallel/test-sqlite-database.js
@@ -279,6 +279,24 @@ suite('Database() constructor', () => {
);
});
+ for (const option of [
+ 'readBigInts',
+ 'returnArrays',
+ 'allowBareNamedParameters',
+ 'allowUnknownNamedParameters',
+ ]) {
+ test(`propagates an error thrown by the options.${option} getter`, (t) => {
+ const dbPath = nextDb();
+ const error = new Error('boom');
+ t.assert.throws(() => {
+ new Database(dbPath, {
+ get [option]() { throw error; },
+ });
+ }, error);
+ t.assert.strictEqual(existsSync(dbPath), false);
+ });
+ }
+
test('has sqlite-type symbol property', (t) => {
const dbPath = nextDb();
using db = new Database(dbPath);