Skip to content

Commit

Permalink
sqlite: add readOnly option
Browse files Browse the repository at this point in the history
Allow opening existing SQLite databases with SQLITE_OPEN_READONLY set.

PR-URL: #55567
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Rafael Gonzaga <[email protected]>
  • Loading branch information
tniessen authored and ruyadorno committed Nov 27, 2024
1 parent 547cab9 commit 4e58785
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 1 deletion.
2 changes: 2 additions & 0 deletions doc/api/sqlite.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 19 additions & 1 deletion src/node_sqlite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions src/node_sqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -33,6 +37,7 @@ class DatabaseOpenConfiguration {

private:
std::string location_;
bool read_only_ = false;
bool enable_foreign_keys_ = true;
bool enable_dqs_ = false;
};
Expand Down
33 changes: 33 additions & 0 deletions test/parallel/test-sqlite-database-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down

0 comments on commit 4e58785

Please sign in to comment.