Skip to content

Commit f74aa92

Browse files
sqlite: implement Database#enableDefensive()
1 parent ccce723 commit f74aa92

File tree

3 files changed

+151
-52
lines changed

3 files changed

+151
-52
lines changed

src/node_sqlite.cc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4647,6 +4647,27 @@ class LocationOperation : private OperationBase {
46474647
std::pmr::string db_name_;
46484648
};
46494649

4650+
class UpdateDbConfigOperation : private OperationBase {
4651+
public:
4652+
UpdateDbConfigOperation(Global<Promise::Resolver>&& resolver,
4653+
int db_config,
4654+
int value)
4655+
: OperationBase(std::move(resolver)),
4656+
db_config_(db_config),
4657+
value_(value) {}
4658+
4659+
OperationResult operator()(sqlite3* connection) {
4660+
int error_code = sqlite3_db_config(connection, db_config_, value_, nullptr);
4661+
return error_code == SQLITE_OK
4662+
? OperationResult::ResolveVoid(this)
4663+
: OperationResult::RejectLastError(this, connection);
4664+
}
4665+
4666+
private:
4667+
int db_config_;
4668+
int value_;
4669+
};
4670+
46504671
using Operation = std::variant<ExecOperation,
46514672
StatementGetOperation,
46524673
StatementAllOperation,
@@ -4655,6 +4676,7 @@ using Operation = std::variant<ExecOperation,
46554676
FinalizeStatementOperation,
46564677
IsInTransactionOperation,
46574678
LocationOperation,
4679+
UpdateDbConfigOperation,
46584680
CloseOperation>;
46594681

46604682
template <typename T, typename V>
@@ -4958,6 +4980,7 @@ v8::Local<v8::FunctionTemplate> CreateDatabaseConstructorTemplate(
49584980
SetProtoMethod(isolate, tmpl, "exec", Database::Exec);
49594981
SetProtoMethod(isolate, tmpl, "isInTransaction", Database::IsInTransaction);
49604982
SetProtoMethod(isolate, tmpl, "location", Database::Location);
4983+
SetProtoMethod(isolate, tmpl, "enableDefensive", Database::EnableDefensive);
49614984

49624985
Local<String> sqlite_type_key = FIXED_ONE_BYTE_STRING(isolate, "sqlite-type");
49634986
Local<v8::Symbol> sqlite_type_symbol =
@@ -5243,6 +5266,25 @@ void Database::Location(const v8::FunctionCallbackInfo<v8::Value>& args) {
52435266
db->Schedule<LocationOperation>(std::move(db_name)));
52445267
}
52455268

5269+
void Database::EnableDefensive(
5270+
const v8::FunctionCallbackInfo<v8::Value>& args) {
5271+
Database* db;
5272+
ASSIGN_OR_RETURN_UNWRAP(&db, args.This());
5273+
Environment* env = Environment::GetCurrent(args);
5274+
REJECT_AND_RETURN_ON_INVALID_STATE(
5275+
env, args, !db->IsOpen(), "database is not open");
5276+
5277+
REJECT_AND_RETURN_ON_INVALID_ARG_TYPE(
5278+
env,
5279+
args,
5280+
!args[0]->IsBoolean() || args.Length() != 1,
5281+
"\"enableDefensive\" requires exactly one boolean argument.");
5282+
5283+
auto enable_defensive = args[0].As<Boolean>()->Value();
5284+
args.GetReturnValue().Set(db->Schedule<UpdateDbConfigOperation>(
5285+
SQLITE_DBCONFIG_DEFENSIVE, enable_defensive ? 1 : 0));
5286+
}
5287+
52465288
Statement::~Statement() {
52475289
if (!IsDisposed()) {
52485290
// Our operations keep a BaseObjectPtr to this Statement, so we can be sure

src/node_sqlite.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -472,14 +472,7 @@ class Database final : public DatabaseCommon {
472472
static void Exec(const v8::FunctionCallbackInfo<v8::Value>& args);
473473
static void IsInTransaction(const v8::FunctionCallbackInfo<v8::Value>& args);
474474
static void Location(const v8::FunctionCallbackInfo<v8::Value>& args);
475-
// static void CreateTagStore(const v8::FunctionCallbackInfo<v8::Value>&
476-
// args); static void Location(const v8::FunctionCallbackInfo<v8::Value>&
477-
// args); static void EnableLoadExtension(
478-
// const v8::FunctionCallbackInfo<v8::Value>& args);
479-
// static void EnableDefensive(const v8::FunctionCallbackInfo<v8::Value>&
480-
// args); static void LoadExtension(const v8::FunctionCallbackInfo<v8::Value>&
481-
// args); void FinalizeStatements(); void UntrackStatement(StatementSync*
482-
// statement);
475+
static void EnableDefensive(const v8::FunctionCallbackInfo<v8::Value>& args);
483476

484477
template <typename Op, typename... Args>
485478
[[nodiscard]] v8::Local<v8::Promise> Schedule(Args&&... args);
Lines changed: 108 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,127 @@
11
'use strict';
22
const { skipIfSQLiteMissing } = require('../common/index.mjs');
3-
const { test } = require('node:test');
4-
const assert = require('node:assert');
3+
const { test, suite } = require('node:test');
54
skipIfSQLiteMissing();
6-
const { DatabaseSync } = require('node:sqlite');
5+
const { DatabaseSync, Database } = require('node:sqlite');
76

8-
function checkDefensiveMode(db) {
9-
function journalMode() {
10-
return db.prepare('PRAGMA journal_mode').get().journal_mode;
11-
}
7+
suite('DatabaseSync config', () => {
8+
function checkDefensiveMode(t, db) {
9+
function journalMode() {
10+
return db.prepare('PRAGMA journal_mode').get().journal_mode;
11+
}
1212

13-
assert.strictEqual(journalMode(), 'memory');
14-
db.exec('PRAGMA journal_mode=OFF');
13+
t.assert.strictEqual(journalMode(), 'memory');
14+
db.exec('PRAGMA journal_mode=OFF');
1515

16-
switch (journalMode()) {
17-
case 'memory': return true; // journal_mode unchanged, defensive mode must be active
18-
case 'off': return false; // journal_mode now 'off', so defensive mode not active
19-
default: throw new Error('unexpected journal_mode');
16+
switch (journalMode()) {
17+
case 'memory': return true; // journal_mode unchanged, defensive mode must be active
18+
case 'off': return false; // journal_mode now 'off', so defensive mode not active
19+
default: throw new Error('unexpected journal_mode');
20+
}
2021
}
21-
}
2222

23-
test('by default, defensive mode is on', (t) => {
24-
const db = new DatabaseSync(':memory:');
25-
t.assert.strictEqual(checkDefensiveMode(db), true);
26-
});
23+
test('by default, defensive mode is on', (t) => {
24+
const db = new DatabaseSync(':memory:');
25+
t.assert.strictEqual(checkDefensiveMode(t, db), true);
26+
});
2727

28-
test('when passing { defensive: true } as config, defensive mode is on', (t) => {
29-
const db = new DatabaseSync(':memory:', {
30-
defensive: true
28+
test('when passing { defensive: true } as config, defensive mode is on', (t) => {
29+
const db = new DatabaseSync(':memory:', {
30+
defensive: true
31+
});
32+
t.assert.strictEqual(checkDefensiveMode(t, db), true);
3133
});
32-
t.assert.strictEqual(checkDefensiveMode(db), true);
33-
});
3434

35-
test('when passing { defensive: false } as config, defensive mode is off', (t) => {
36-
const db = new DatabaseSync(':memory:', {
37-
defensive: false
35+
test('when passing { defensive: false } as config, defensive mode is off', (t) => {
36+
const db = new DatabaseSync(':memory:', {
37+
defensive: false
38+
});
39+
t.assert.strictEqual(checkDefensiveMode(t, db), false);
3840
});
39-
t.assert.strictEqual(checkDefensiveMode(db), false);
40-
});
4141

42-
test('defensive mode on after calling db.enableDefensive(true)', (t) => {
43-
const db = new DatabaseSync(':memory:');
44-
db.enableDefensive(true);
45-
t.assert.strictEqual(checkDefensiveMode(db), true);
46-
});
42+
test('defensive mode on after calling db.enableDefensive(true)', (t) => {
43+
const db = new DatabaseSync(':memory:');
44+
db.enableDefensive(true);
45+
t.assert.strictEqual(checkDefensiveMode(t, db), true);
46+
});
47+
48+
test('defensive mode off after calling db.enableDefensive(false)', (t) => {
49+
const db = new DatabaseSync(':memory:', {
50+
defensive: true
51+
});
52+
db.enableDefensive(false);
53+
t.assert.strictEqual(checkDefensiveMode(t, db), false);
54+
});
4755

48-
test('defensive mode off after calling db.enableDefensive(false)', (t) => {
49-
const db = new DatabaseSync(':memory:', {
50-
defensive: true
56+
test('throws if options.defensive is provided but is not a boolean', (t) => {
57+
t.assert.throws(() => {
58+
new DatabaseSync(':memory:', { defensive: 42 });
59+
}, {
60+
code: 'ERR_INVALID_ARG_TYPE',
61+
message: 'The "options.defensive" argument must be a boolean.',
62+
});
5163
});
52-
db.enableDefensive(false);
53-
t.assert.strictEqual(checkDefensiveMode(db), false);
5464
});
5565

56-
test('throws if options.defensive is provided but is not a boolean', (t) => {
57-
t.assert.throws(() => {
58-
new DatabaseSync(':memory:', { defensive: 42 });
59-
}, {
60-
code: 'ERR_INVALID_ARG_TYPE',
61-
message: 'The "options.defensive" argument must be a boolean.',
66+
suite('Database config', { timeout: 1000 }, () => {
67+
async function checkDefensiveMode(t, db) {
68+
async function journalMode() {
69+
return (await (await db.prepare('PRAGMA journal_mode')).get()).journal_mode;
70+
}
71+
72+
t.assert.strictEqual(await journalMode(), 'memory');
73+
await db.exec('PRAGMA journal_mode=OFF');
74+
75+
switch (await journalMode()) {
76+
case 'memory': return true; // journal_mode unchanged, defensive mode must be active
77+
case 'off': return false; // journal_mode now 'off', so defensive mode not active
78+
default: throw new Error('unexpected journal_mode');
79+
}
80+
}
81+
82+
test('by default, defensive mode is on', async (t) => {
83+
const db = new Database(':memory:');
84+
t.after(async () => await db.close());
85+
t.assert.strictEqual(await checkDefensiveMode(t, db), true);
86+
});
87+
88+
test('when passing { defensive: true } as config, defensive mode is on', async (t) => {
89+
const db = new Database(':memory:', {
90+
defensive: true
91+
});
92+
t.after(async () => await db.close());
93+
t.assert.strictEqual(await checkDefensiveMode(t, db), true);
94+
});
95+
96+
test('when passing { defensive: false } as config, defensive mode is off', async (t) => {
97+
const db = new Database(':memory:', {
98+
defensive: false
99+
});
100+
t.after(async () => await db.close());
101+
t.assert.strictEqual(await checkDefensiveMode(t, db), false);
102+
});
103+
104+
test('defensive mode on after calling db.enableDefensive(true)', async (t) => {
105+
const db = new Database(':memory:');
106+
await db.enableDefensive(true);
107+
t.assert.strictEqual(await checkDefensiveMode(t, db), true);
108+
});
109+
110+
test('defensive mode off after calling db.enableDefensive(false)', async (t) => {
111+
const db = new Database(':memory:', {
112+
defensive: true
113+
});
114+
t.after(async () => await db.close());
115+
await db.enableDefensive(false);
116+
t.assert.strictEqual(await checkDefensiveMode(t, db), false);
117+
});
118+
119+
test('throws if options.defensive is provided but is not a boolean', (t) => {
120+
t.assert.throws(() => {
121+
new Database(':memory:', { defensive: 42 });
122+
}, {
123+
code: 'ERR_INVALID_ARG_TYPE',
124+
message: 'The "options.defensive" argument must be a boolean.',
125+
});
62126
});
63127
});

0 commit comments

Comments
 (0)