From 5f8ae72a5a0ea837633b6b9a2f934ee475335e7e Mon Sep 17 00:00:00 2001 From: "R.Eugenio" Date: Mon, 12 Jan 2026 12:01:27 +0100 Subject: [PATCH] feat: Auto-configure WAL mode on database open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- file::memory:?cache=shared | 0 src/database.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) delete mode 100644 file::memory:?cache=shared diff --git a/file::memory:?cache=shared b/file::memory:?cache=shared deleted file mode 100644 index e69de29..0000000 diff --git a/src/database.zig b/src/database.zig index d6b1e3e..3b93861 100644 --- a/src/database.zig +++ b/src/database.zig @@ -59,6 +59,14 @@ pub const Database = struct { // ======================================================================== /// 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 { var handle: ?*c.sqlite3 = null; const result = c.sqlite3_open(path.ptr, &handle); @@ -70,6 +78,35 @@ pub const Database = struct { 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 }; }