feat: Auto-configure WAL mode on database open

- open() now automatically enables WAL mode for file-based databases
- Configures: journal_mode=WAL, synchronous=NORMAL, busy_timeout=5000ms
- Skips configuration for :memory: databases
- Added openRaw() for cases requiring default SQLite behavior
- Fixes: Database locked errors under concurrent load (Solo2 incident)

Benefits:
- Concurrent readers during writes
- 5s retry instead of immediate failure
- All projects using zcatsql benefit automatically

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
R.Eugenio 2026-01-12 12:01:27 +01:00
parent 11a75132db
commit 5f8ae72a5a
2 changed files with 37 additions and 0 deletions

View file

@ -59,6 +59,14 @@ pub const Database = struct {
// ======================================================================== // ========================================================================
/// Opens a database connection. /// Opens a database connection.
///
/// For file-based databases, automatically configures for high availability:
/// - WAL mode (concurrent reads during writes)
/// - synchronous=NORMAL (balance of safety and speed)
/// - busy_timeout=5000ms (retry instead of immediate failure)
///
/// For in-memory databases (`:memory:`), these settings are skipped.
/// Use `openRaw()` if you need the default SQLite behavior.
pub fn open(path: [:0]const u8) Error!Self { pub fn open(path: [:0]const u8) Error!Self {
var handle: ?*c.sqlite3 = null; var handle: ?*c.sqlite3 = null;
const result = c.sqlite3_open(path.ptr, &handle); const result = c.sqlite3_open(path.ptr, &handle);
@ -70,6 +78,35 @@ pub const Database = struct {
return resultToError(result); return resultToError(result);
} }
// Apply high-availability settings for file-based databases
// Skip for in-memory databases where WAL doesn't apply
if (!std.mem.eql(u8, path, ":memory:")) {
// WAL mode: allows concurrent readers during writes
_ = c.sqlite3_exec(handle, "PRAGMA journal_mode = WAL", null, null, null);
// NORMAL sync: good balance of durability and performance
_ = c.sqlite3_exec(handle, "PRAGMA synchronous = NORMAL", null, null, null);
// Wait 5 seconds instead of failing immediately when busy
_ = c.sqlite3_busy_timeout(handle, 5000);
}
return .{ .handle = handle };
}
/// Opens a database connection without automatic configuration.
///
/// Unlike `open()`, this does not configure WAL mode or busy timeout.
/// Use this when you need full control over SQLite settings.
pub fn openRaw(path: [:0]const u8) Error!Self {
var handle: ?*c.sqlite3 = null;
const result = c.sqlite3_open(path.ptr, &handle);
if (result != c.SQLITE_OK) {
if (handle) |h| {
_ = c.sqlite3_close(h);
}
return resultToError(result);
}
return .{ .handle = handle }; return .{ .handle = handle };
} }