Commit 5f602d5d916 for nodejs
commit 5f602d5d916d0cf1afe64de1f41c63ef92b63444
Author: Guilherme Araújo <arauujogui@gmail.com>
Date: Sat Nov 7 13:25:42 2026 -0300
sqlite: add ReadOptionalString helper
Use it for the dbName, table, db, source and target options so they
all treat a missing or undefined value as the default. Also mark
ReadOptionalBool and ReadOptionalString as [[nodiscard]].
Signed-off-by: Guilherme Araújo <arauujogui@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/67042
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
diff --git a/src/env_properties.h b/src/env_properties.h
index 80b41b7a826..c7a26ce3a58 100644
--- a/src/env_properties.h
+++ b/src/env_properties.h
@@ -351,8 +351,6 @@
V(stream_count_string, "streamCount") \
V(synthetic_string, "synthetic") \
V(syscall_string, "syscall") \
- V(table_string, "table") \
- V(target_string, "target") \
V(then_string, "then") \
V(thread_id_string, "threadId") \
V(thread_name_string, "threadName") \
diff --git a/src/node_sqlite.cc b/src/node_sqlite.cc
index 77617e011fa..8341b27a4fa 100644
--- a/src/node_sqlite.cc
+++ b/src/node_sqlite.cc
@@ -1816,10 +1816,10 @@ void Database::CreateTagStore(const FunctionCallbackInfo<Value>& args) {
// 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) {
+[[nodiscard]] 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;
@@ -1838,6 +1838,31 @@ static bool ReadOptionalBool(Environment* env,
return true;
}
+// Reads options[key] into *out as a string, leaving *out unchanged if the value
+// is undefined. Returns false if a JavaScript exception is pending, either from
+// the property getter or because the value is not a string.
+[[nodiscard]] static bool ReadOptionalString(Environment* env,
+ Local<Object> options,
+ const char* key,
+ std::string* 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->IsString()) {
+ THROW_ERR_INVALID_ARG_TYPE(
+ env->isolate(), "The \"options.%s\" argument must be a string.", key);
+ return false;
+ }
+ *out = Utf8Value(env->isolate(), value.As<String>()).ToString();
+ return true;
+}
+
std::optional<std::string> ValidateDatabasePath(Environment* env,
Local<Value> path,
const std::string& field_name) {
@@ -2409,22 +2434,7 @@ void Database::Deserialize(const FunctionCallbackInfo<Value>& args) {
}
Local<Object> options = args[1].As<Object>();
- Local<String> db_name_key = FIXED_ONE_BYTE_STRING(env->isolate(), "dbName");
- Local<Value> db_name_value;
- if (!options->Get(env->context(), db_name_key).ToLocal(&db_name_value)) {
- return;
- }
-
- if (!db_name_value->IsUndefined()) {
- if (!db_name_value->IsString()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.dbName\" argument must be a string.");
- return;
- }
- db_name =
- Utf8Value(env->isolate(), db_name_value.As<String>()).ToString();
- }
+ if (!ReadOptionalString(env, options, "dbName", &db_name)) return;
}
// Reading the options bag above can run user JavaScript through a property
@@ -2825,46 +2835,8 @@ void Database::CreateSession(const FunctionCallbackInfo<Value>& args) {
}
Local<Object> options = args[0].As<Object>();
-
- Local<String> table_key = env->table_string();
- bool hasIt;
- if (!options->HasOwnProperty(env->context(), table_key).To(&hasIt)) {
- return;
- }
- if (hasIt) {
- Local<Value> table_value;
- if (!options->Get(env->context(), table_key).ToLocal(&table_value)) {
- return;
- }
-
- if (table_value->IsString()) {
- table = Utf8Value(env->isolate(), table_value).ToString();
- } else {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(), "The \"options.table\" argument must be a string.");
- return;
- }
- }
-
- Local<String> db_key = FIXED_ONE_BYTE_STRING(env->isolate(), "db");
-
- if (!options->HasOwnProperty(env->context(), db_key).To(&hasIt)) {
- return;
- }
- if (hasIt) {
- Local<Value> db_value;
- if (!options->Get(env->context(), db_key).ToLocal(&db_value)) {
- // An error will have been scheduled.
- return;
- }
- if (db_value->IsString()) {
- db_name = Utf8Value(env->isolate(), db_value).ToString();
- } else {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(), "The \"options.db\" argument must be a string.");
- return;
- }
- }
+ if (!ReadOptionalString(env, options, "table", &table)) return;
+ if (!ReadOptionalString(env, options, "db", &db_name)) return;
}
// Reading the options bag above can run user JavaScript through a property
@@ -2942,39 +2914,8 @@ void Backup(const FunctionCallbackInfo<Value>& args) {
}
}
- Local<Value> source_v;
- if (!options->Get(env->context(), env->source_string())
- .ToLocal(&source_v)) {
- return;
- }
-
- if (!source_v->IsUndefined()) {
- if (!source_v->IsString()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.source\" argument must be a string.");
- return;
- }
-
- source_db = Utf8Value(env->isolate(), source_v.As<String>()).ToString();
- }
-
- Local<Value> target_v;
- if (!options->Get(env->context(), env->target_string())
- .ToLocal(&target_v)) {
- return;
- }
-
- if (!target_v->IsUndefined()) {
- if (!target_v->IsString()) {
- THROW_ERR_INVALID_ARG_TYPE(
- env->isolate(),
- "The \"options.target\" argument must be a string.");
- return;
- }
-
- dest_db = Utf8Value(env->isolate(), target_v.As<String>()).ToString();
- }
+ if (!ReadOptionalString(env, options, "source", &source_db)) return;
+ if (!ReadOptionalString(env, options, "target", &dest_db)) return;
Local<Value> progress_v;
if (!options->Get(env->context(), env->progress_string())
diff --git a/test/parallel/test-sqlite-backup.mjs b/test/parallel/test-sqlite-backup.mjs
index d3d6a433993..212bf54e98c 100644
--- a/test/parallel/test-sqlite-backup.mjs
+++ b/test/parallel/test-sqlite-backup.mjs
@@ -195,6 +195,20 @@ test('database backup', async (t) => {
});
});
+test('backup treats undefined source and target as main', async (t) => {
+ const database = makeSourceDb();
+ const destDb = nextDb();
+
+ await backup(database, destDb, { source: undefined, target: undefined });
+
+ const backupDb = new Database(destDb);
+ t.after(() => {
+ database.close();
+ backupDb.close();
+ });
+ t.assert.strictEqual(backupDb.prepare('SELECT count(*) AS n FROM main.data').get().n, 2);
+});
+
test('backup database using location as URL', async (t) => {
const database = makeSourceDb();
const destDb = pathToFileURL(nextDb());
diff --git a/test/parallel/test-sqlite-serialize.js b/test/parallel/test-sqlite-serialize.js
index 29e02699cb0..f52d4268f11 100644
--- a/test/parallel/test-sqlite-serialize.js
+++ b/test/parallel/test-sqlite-serialize.js
@@ -251,6 +251,18 @@ suite('Database.prototype.deserialize()', () => {
db.close();
});
+ test('treats undefined options.dbName as main', (t) => {
+ const db1 = new Database(':memory:');
+ db1.exec('CREATE TABLE t(x INTEGER)');
+ const buf = db1.serialize();
+ db1.close();
+
+ const db2 = new Database(':memory:');
+ db2.deserialize(buf, { dbName: undefined });
+ t.assert.strictEqual(db2.prepare('SELECT count(*) AS n FROM main.t').get().n, 0);
+ db2.close();
+ });
+
test('accepts a Buffer as input', (t) => {
const db1 = new Database(':memory:');
db1.exec('CREATE TABLE t(x INTEGER)');
diff --git a/test/parallel/test-sqlite-session.js b/test/parallel/test-sqlite-session.js
index e5a478e1706..ee0c2cfb3af 100644
--- a/test/parallel/test-sqlite-session.js
+++ b/test/parallel/test-sqlite-session.js
@@ -540,6 +540,14 @@ test('database.createSession() - specify other database', (t) => {
t.assert.strictEqual(sessionTest.changeset().length, 0);
});
+test('database.createSession() - undefined options use defaults', (t) => {
+ const database = new Database(':memory:');
+ const session = database.createSession({ table: undefined, db: undefined });
+ database.exec('CREATE TABLE data (key INTEGER PRIMARY KEY)');
+ database.exec('INSERT INTO data (key) VALUES (1)');
+ t.assert.notStrictEqual(session.changeset().length, 0);
+});
+
test('database.createSession() - wrong arguments', (t) => {
const database = new Database(':memory:');
t.assert.throws(() => {