@@ -13,6 +13,11 @@ pub const c = @cImport({
1313 @cInclude ("sqlite-vec.h" );
1414});
1515
16+ const posix = @cImport ({
17+ @cInclude ("sys/stat.h" );
18+ @cInclude ("sys/types.h" );
19+ });
20+
1621/// Bump when the on-disk schema changes; add a migration branch in
1722/// `migrateSchema` for each step. Persisted via SQLite's `PRAGMA user_version`.
1823/// v2: FTS5 full-text index for hybrid keyword + vector search.
@@ -33,6 +38,10 @@ pub const Database = struct {
3338 handle : * Sqlite3 ,
3439
3540 pub fn open (path : [* :0 ]const u8 ) ! Database {
41+ // Ensure parent directory exists (e.g. ~/.memxt/ for the default palace).
42+ // sqlite3_open does not create intermediate directories.
43+ ensureParentDirs (std .mem .span (path ));
44+
3645 var handle : ? * Sqlite3 = null ;
3746 if (c .sqlite3_open (path , & handle ) != SQLITE_OK ) {
3847 if (handle ) | h | _ = c .sqlite3_close (h );
@@ -408,6 +417,28 @@ pub const Database = struct {
408417 }
409418};
410419
420+ /// Create every intermediate directory for an absolute or relative DB path.
421+ /// Best-effort: ignores EEXIST and other races. sqlite3_open will not mkdir.
422+ fn ensureParentDirs (path : []const u8 ) void {
423+ const dir = std .fs .path .dirname (path ) orelse return ;
424+ if (dir .len == 0 ) return ;
425+ var i : usize = 0 ;
426+ while (i < dir .len ) : (i += 1 ) {
427+ if (dir [i ] != '/' ) continue ;
428+ if (i == 0 ) continue ; // skip root "/"
429+ var buf : [std .fs .max_path_bytes ]u8 = undefined ;
430+ if (i >= buf .len ) return ;
431+ @memcpy (buf [0.. i ], dir [0.. i ]);
432+ buf [i ] = 0 ;
433+ _ = posix .mkdir (& buf , 0o755 );
434+ }
435+ var final_buf : [std .fs .max_path_bytes ]u8 = undefined ;
436+ if (dir .len >= final_buf .len ) return ;
437+ @memcpy (final_buf [0.. dir .len ], dir );
438+ final_buf [dir .len ] = 0 ;
439+ _ = posix .mkdir (& final_buf , 0o755 );
440+ }
441+
411442// ── Statement helpers ──
412443
413444pub fn finalize (stmt : ? * Stmt ) void {
0 commit comments