Skip to content

Commit d2ded7b

Browse files
authored
Add WAL replay error handling + enable checksum configs to client APIs (#5949)
1 parent 8e119a6 commit d2ded7b

4 files changed

Lines changed: 100 additions & 2 deletions

File tree

src_cpp/include/node_database.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class NodeDatabase : public Napi::ObjectWrap<NodeDatabase> {
3232
uint64_t maxDBSize;
3333
bool autoCheckpoint;
3434
int64_t checkpointThreshold;
35+
bool throwOnWalReplayFailure;
36+
bool enableChecksums;
3537
std::shared_ptr<Database> database;
3638
};
3739

src_cpp/node_database.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ NodeDatabase::NodeDatabase(const Napi::CallbackInfo& info) : Napi::ObjectWrap<No
2626
maxDBSize = info[4].As<Napi::Number>().Int64Value();
2727
autoCheckpoint = info[5].As<Napi::Boolean>().Value();
2828
checkpointThreshold = info[6].As<Napi::Number>().Int64Value();
29+
throwOnWalReplayFailure = info[7].As<Napi::Boolean>().Value();
30+
enableChecksums = info[8].As<Napi::Boolean>().Value();
2931
}
3032

3133
Napi::Value NodeDatabase::InitSync(const Napi::CallbackInfo& info) {
@@ -63,6 +65,8 @@ void NodeDatabase::InitCppDatabase() {
6365
systemConfig.maxDBSize = maxDBSize;
6466
}
6567
systemConfig.autoCheckpoint = autoCheckpoint;
68+
systemConfig.throwOnWalReplayFailure = throwOnWalReplayFailure;
69+
systemConfig.enableChecksums = enableChecksums;
6670
if (checkpointThreshold >= 0) {
6771
systemConfig.checkpointThreshold = checkpointThreshold;
6872
}

src_js/database.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ class Database {
1717
* @param {Number} maxDBSize maximum size of the database file in bytes. Note that
1818
* this is introduced temporarily for now to get around with the default 8TB mmap
1919
* address space limit some environment.
20+
* @param {Boolean} autoCheckpoint If true, the database will automatically checkpoint when the size of
21+
* the WAL file exceeds the checkpoint threshold.
22+
* @param {Number} checkpointThreshold The threshold of the WAL file size in bytes. When the size of the
23+
* WAL file exceeds this threshold, the database will checkpoint if autoCheckpoint is true.
24+
* @param {Boolean} throwOnWalReplayFailure If true, any WAL replaying failure when loading the database
25+
* will throw an error. Otherwise, Kuzu will silently ignore the failure and replay up to where
26+
* the error occured.
27+
* @param {Boolean} enableChecksums If true, the database will use checksums to detect corruption in the
28+
* WAL file.
2029
*/
2130
constructor(
2231
databasePath,
@@ -25,7 +34,9 @@ class Database {
2534
readOnly = false,
2635
maxDBSize = 0,
2736
autoCheckpoint = true,
28-
checkpointThreshold = -1
37+
checkpointThreshold = -1,
38+
throwOnWalReplayFailure = true,
39+
enableChecksums = true,
2940
) {
3041
if (!databasePath) {
3142
databasePath = ":memory:";
@@ -52,7 +63,9 @@ class Database {
5263
readOnly,
5364
maxDBSize,
5465
autoCheckpoint,
55-
checkpointThreshold
66+
checkpointThreshold,
67+
throwOnWalReplayFailure,
68+
enableChecksums
5669
);
5770
this._isInitialized = false;
5871
this._initPromise = null;

test/test_database.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { assert } = require("chai");
22
const tmp = require("tmp");
33
const process = require("process");
44
const path = require("path");
5+
const fs = require('fs');
56

67
const spwan = require("child_process").spawn;
78

@@ -131,6 +132,84 @@ describe("Database constructor", function () {
131132
testDb.close();
132133
});
133134

135+
it("should create a database with throwOnWalReplayFailure configured", async function () {
136+
const tmpDbPath = await new Promise((resolve, reject) => {
137+
tmp.dir({ unsafeCleanup: true }, (err, path, _) => {
138+
if (err) {
139+
return reject(err);
140+
}
141+
return resolve(path);
142+
});
143+
});
144+
const dbPath = path.join(tmpDbPath, "db.kz");
145+
const walPath = dbPath + ".wal";
146+
fs.writeFileSync(walPath, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
147+
const testDb = new kuzu.Database(dbPath,
148+
1 << 28 /* 256MB */,
149+
true /* compression */,
150+
false /* readOnly */,
151+
1 << 30 /* 1GB */,
152+
true /* autoCheckpoint */,
153+
1234 /* checkpointThreshold */,
154+
false /* throwOnWalReplayFailure */
155+
);
156+
const conn = new kuzu.Connection(testDb);
157+
let res = await conn.query("RETURN 1");
158+
assert.equal(res.getNumTuples(), 1);
159+
const tuple = await res.getNext();
160+
assert.equal(tuple["1"], 1);
161+
res.close();
162+
conn.close();
163+
testDb.close();
164+
});
165+
166+
it("should create a database with enableChecksums configured", async function () {
167+
const tmpDbPath = await new Promise((resolve, reject) => {
168+
tmp.dir({ unsafeCleanup: true }, (err, path, _) => {
169+
if (err) {
170+
return reject(err);
171+
}
172+
return resolve(path);
173+
});
174+
});
175+
const dbPath = path.join(tmpDbPath, "db.kz");
176+
let testDb = new kuzu.Database(dbPath,
177+
1 << 28 /* 256MB */,
178+
true /* compression */,
179+
false /* readOnly */,
180+
1 << 30 /* 1GB */,
181+
false /* autoCheckpoint */,
182+
1234 /* checkpointThreshold */,
183+
true /* throwOnWalReplayFailure */,
184+
true /* enableChecksums */
185+
);
186+
let conn = new kuzu.Connection(testDb);
187+
let res = await conn.query("call force_checkpoint_on_close=false");
188+
let res1 = await conn.query("create node table testtest1(id int64 primary key)");
189+
res.close();
190+
res1.close();
191+
conn.close();
192+
testDb.close();
193+
194+
try {
195+
testDb = new kuzu.Database(dbPath,
196+
1 << 28 /* 256MB */,
197+
true /* compression */,
198+
false /* readOnly */,
199+
1 << 30 /* 1GB */,
200+
true /* autoCheckpoint */,
201+
1234 /* checkpointThreshold */,
202+
true /* throwOnWalReplayFailure */,
203+
false /* enableChecksums */
204+
);
205+
await testDb.init();
206+
assert.fail("No error thrown when enableChecksums config is invalid.");
207+
} catch (e) {
208+
assert.ok(e.message.includes("Please open your database using the correct enableChecksums config."));
209+
}
210+
testDb.close();
211+
});
212+
134213
it("should create a database in read-only mode", async function () {
135214
const tmpDbPath = await new Promise((resolve, reject) => {
136215
tmp.dir({ unsafeCleanup: true }, (err, path, _) => {

0 commit comments

Comments
 (0)