sqlite: add readOnly option

Allow opening existing SQLite databases with SQLITE_OPEN_READONLY set.

PR-URL: https://github.com/nodejs/node/pull/55567
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
This commit is contained in:
Tobias Nießen 2024-10-31 12:41:14 +01:00 committed by GitHub
parent 996708042b
commit a465b206d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 59 additions and 1 deletions

View File

@ -107,6 +107,8 @@ added: v22.5.0
* `open` {boolean} If `true`, the database is opened by the constructor. When
this value is `false`, the database must be opened via the `open()` method.
**Default:** `true`.
* `readOnly` {boolean} If `true`, the database is opened in read-only mode.
If the database does not exist, opening it will fail. **Default:** `false`.
* `enableForeignKeyConstraints` {boolean} If `true`, foreign key constraints
are enabled. This is recommended but can be disabled for compatibility with
legacy database schemas. The enforcement of foreign key constraints can be

View File

@ -126,7 +126,9 @@ bool DatabaseSync::Open() {
}
// TODO(cjihrig): Support additional flags.
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
int flags = open_config_.get_read_only()
? SQLITE_OPEN_READONLY
: 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);
@ -219,6 +221,22 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
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)) {
return;
}
if (!read_only_v->IsUndefined()) {
if (!read_only_v->IsBoolean()) {
node::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;

View File

@ -21,6 +21,10 @@ class DatabaseOpenConfiguration {
inline const std::string& location() const { return location_; }
inline bool get_read_only() const { return read_only_; }
inline void set_read_only(bool flag) { read_only_ = flag; }
inline bool get_enable_foreign_keys() const { return enable_foreign_keys_; }
inline void set_enable_foreign_keys(bool flag) {
@ -33,6 +37,7 @@ class DatabaseOpenConfiguration {
private:
std::string location_;
bool read_only_ = false;
bool enable_foreign_keys_ = true;
bool enable_dqs_ = false;
};

View File

@ -51,6 +51,39 @@ suite('DatabaseSync() constructor', () => {
});
});
test('throws if options.readOnly is provided but is not a boolean', (t) => {
t.assert.throws(() => {
new DatabaseSync('foo', { readOnly: 5 });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.readOnly" argument must be a boolean/,
});
});
test('is not read-only by default', (t) => {
const dbPath = nextDb();
const db = new DatabaseSync(dbPath);
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
});
test('is read-only if readOnly is set', (t) => {
const dbPath = nextDb();
{
const db = new DatabaseSync(dbPath);
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
db.close();
}
{
const db = new DatabaseSync(dbPath, { readOnly: true });
t.assert.throws(() => {
db.exec('CREATE TABLE bar (id INTEGER PRIMARY KEY)');
}, {
code: 'ERR_SQLITE_ERROR',
message: /attempt to write a readonly database/,
});
}
});
test('throws if options.enableForeignKeyConstraints is provided but is not a boolean', (t) => {
t.assert.throws(() => {
new DatabaseSync('foo', { enableForeignKeyConstraints: 5 });