Skip to content

Commit 4e58785

Browse files
tniessenruyadorno
authored andcommitted
sqlite: add readOnly option
Allow opening existing SQLite databases with SQLITE_OPEN_READONLY set. PR-URL: #55567 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent 547cab9 commit 4e58785

File tree

4 files changed

+59
-1
lines changed

4 files changed

+59
-1
lines changed

doc/api/sqlite.md

+2
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

+19-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,9 @@ bool DatabaseSync::Open() {
126126
}
127127

128128
// TODO(cjihrig): Support additional flags.
129-
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
129+
int flags = open_config_.get_read_only()
130+
? SQLITE_OPEN_READONLY
131+
: SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
130132
int r = sqlite3_open_v2(
131133
open_config_.location().c_str(), &connection_, flags, nullptr);
132134
CHECK_ERROR_OR_THROW(env()->isolate(), connection_, r, SQLITE_OK, false);
@@ -219,6 +221,22 @@ void DatabaseSync::New(const FunctionCallbackInfo<Value>& args) {
219221
open = open_v.As<Boolean>()->Value();
220222
}
221223

224+
Local<String> read_only_string =
225+
FIXED_ONE_BYTE_STRING(env->isolate(), "readOnly");
226+
Local<Value> read_only_v;
227+
if (!options->Get(env->context(), read_only_string).ToLocal(&read_only_v)) {
228+
return;
229+
}
230+
if (!read_only_v->IsUndefined()) {
231+
if (!read_only_v->IsBoolean()) {
232+
node::THROW_ERR_INVALID_ARG_TYPE(
233+
env->isolate(),
234+
"The \"options.readOnly\" argument must be a boolean.");
235+
return;
236+
}
237+
open_config.set_read_only(read_only_v.As<Boolean>()->Value());
238+
}
239+
222240
Local<String> enable_foreign_keys_string =
223241
FIXED_ONE_BYTE_STRING(env->isolate(), "enableForeignKeyConstraints");
224242
Local<Value> enable_foreign_keys_v;

src/node_sqlite.h

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class DatabaseOpenConfiguration {
2121

2222
inline const std::string& location() const { return location_; }
2323

24+
inline bool get_read_only() const { return read_only_; }
25+
26+
inline void set_read_only(bool flag) { read_only_ = flag; }
27+
2428
inline bool get_enable_foreign_keys() const { return enable_foreign_keys_; }
2529

2630
inline void set_enable_foreign_keys(bool flag) {
@@ -33,6 +37,7 @@ class DatabaseOpenConfiguration {
3337

3438
private:
3539
std::string location_;
40+
bool read_only_ = false;
3641
bool enable_foreign_keys_ = true;
3742
bool enable_dqs_ = false;
3843
};

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

+33
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)