Skip to content

Commit 21f700c

Browse files
tniessenlouwers
authored andcommitted
sqlite: add readOnly option
Allow opening existing SQLite databases with SQLITE_OPEN_READONLY set. PR-URL: nodejs#55567 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent b422cd4 commit 21f700c

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
lines changed

doc/api/sqlite.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ added: v22.5.0
107107
* `open` {boolean} If `true`, the database is opened by the constructor. When
108108
this value is `false`, the database must be opened via the `open()` method.
109109
**Default:** `true`.
110+
* `readOnly` {boolean} If `true`, the database is opened in read-only mode.
111+
If the database does not exist, opening it will fail. **Default:** `false`.
110112
* `enableForeignKeyConstraints` {boolean} If `true`, foreign key constraints
111113
are enabled. This is recommended but can be disabled for compatibility with
112114
legacy database schemas. The enforcement of foreign key constraints can be

src/node_sqlite.cc

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ bool DatabaseSync::Open() {
138138
}
139139

140140
// TODO(cjihrig): Support additional flags.
141-
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
141+
int flags = open_config_.get_read_only()
142+
? SQLITE_OPEN_READONLY
143+
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
142144
int r = sqlite3_open_v2(
143145
open_config_.location().c_str(), &connection_, flags, nullptr);
144146
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
@@ -231,6 +233,22 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
231233
open = open_v.As<Boolean>()->Value();
232234
}
233235

236+
Local<String> read_only_string =
237+
FIXED_ONE_BYTE_STRING(env->isolate(), "readOnly");
238+
Local<Value> read_only_v;
239+
if (!options->Get(env->context(), read_only_string).ToLocal(&read_only_v)) {
240+
return;
241+
}
242+
if (!read_only_v->IsUndefined()) {
243+
if (!read_only_v->IsBoolean()) {
244+
node::THROW_ERR_INVALID_ARG_TYPE(
245+
env->isolate(),
246+
"The \"options.readOnly\" argument must be a boolean.");
247+
return;
248+
}
249+
open_config.set_read_only(read_only_v.As<Boolean>()->Value());
250+
}
251+
234252
Local<String> enable_foreign_keys_string =
235253
FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints");
236254
Local<Value> enable_foreign_keys_v;

src/node_sqlite.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class DatabaseOpenConfiguration {
2323

2424
inline const std::string& location() const { return location_; }
2525

26+
inline bool get_read_only() const { return read_only_; }
27+
28+
inline void set_read_only(bool flag) { read_only_ = flag; }
29+
2630
inline bool get_enable_foreign_keys() const { return enable_foreign_keys_; }
2731

2832
inline void set_enable_foreign_keys(bool flag) {
@@ -35,6 +39,7 @@ class DatabaseOpenConfiguration {
3539

3640
private:
3741
std::string location_;
42+
bool read_only_ = false;
3843
bool enable_foreign_keys_ = true;
3944
bool enable_dqs_ = false;
4045
};

test/parallel/test-sqlite-database-sync.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,39 @@ suite('DatabaseSync() constructor', () => {
5151
});
5252
});
5353

54+
test('throws if options.readOnly is provided but is not a boolean', (t) => {
55+
t.assert.throws(() => {
56+
new DatabaseSync('foo', { readOnly: 5 });
57+
}, {
58+
code: 'ERR_INVALID_ARG_TYPE',
59+
message: /The "options\.readOnly" argument must be a boolean/,
60+
});
61+
});
62+
63+
test('is not read-only by default', (t) => {
64+
const dbPath = nextDb();
65+
const db = new DatabaseSync(dbPath);
66+
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
67+
});
68+
69+
test('is read-only if readOnly is set', (t) => {
70+
const dbPath = nextDb();
71+
{
72+
const db = new DatabaseSync(dbPath);
73+
db.exec('CREATE TABLE foo (id INTEGER PRIMARY KEY)');
74+
db.close();
75+
}
76+
{
77+
const db = new DatabaseSync(dbPath, { readOnly: true });
78+
t.assert.throws(() => {
79+
db.exec('CREATE TABLE bar (id INTEGER PRIMARY KEY)');
80+
}, {
81+
code: 'ERR_SQLITE_ERROR',
82+
message: /attempt to write a readonly database/,
83+
});
84+
}
85+
});
86+
5487
test('throws if options.enableForeignKeyConstraints is provided but is not a boolean', (t) => {
5588
t.assert.throws(() => {
5689
new DatabaseSync('foo', { enableForeignKeyConstraints: 5 });

0 commit comments

Comments
 (0)