Pure OCaml B-tree implementation for persistent storage
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add transaction support to ocaml-sqlite

Add Pager.snapshot/rollback for capturing and restoring dirty page
state. Expose Table.save_root/restore_root and Index.save_root/
restore_root for B-tree root page rollback after splits.

Build Sqlite.with_transaction on top: snapshots pager state, B-tree
roots, and in-memory KV caches before running the user function. On
exception, all state is rolled back and the exception re-raised.

Five new tests cover commit, KV rollback, unique index rollback,
and nested transaction failure.

+74
+21
lib/btree.mli
··· 206 206 207 207 val sync : t -> unit 208 208 (** [sync t] syncs all dirty pages to disk. *) 209 + 210 + type snapshot 211 + (** An opaque snapshot of pager state. *) 212 + 213 + val snapshot : t -> snapshot 214 + (** [snapshot t] captures the current dirty page set and page count. *) 215 + 216 + val rollback : t -> snapshot -> unit 217 + (** [rollback t snap] restores the pager to the state captured by [snap]. *) 209 218 end 210 219 211 220 (** {1 Table B-tree} ··· 238 247 239 248 val fold : t -> init:'a -> f:(int64 -> string -> 'a -> 'a) -> 'a 240 249 (** [fold t ~init ~f] folds over all records in order. *) 250 + 251 + val save_root : t -> int 252 + (** [save_root t] returns the current root page for later restore. *) 253 + 254 + val restore_root : t -> int -> unit 255 + (** [restore_root t root] sets the root page to [root]. *) 241 256 end 242 257 243 258 (** {1 Index B-tree} ··· 281 296 282 297 val iter : t -> (string -> unit) -> unit 283 298 (** [iter t f] calls [f key] for each key in sorted order. *) 299 + 300 + val save_root : t -> int 301 + (** [save_root t] returns the current root page for later restore. *) 302 + 303 + val restore_root : t -> int -> unit 304 + (** [restore_root t root] sets the root page to [root]. *) 284 305 end
+3
lib/index.ml
··· 755 755 | _ -> failwith "Invalid page type" 756 756 in 757 757 iter_page t.root_page 758 + 759 + let save_root t = t.root_page 760 + let restore_root t root = t.root_page <- root
+6
lib/index.mli
··· 39 39 40 40 val iter : t -> (string -> unit) -> unit 41 41 (** [iter t f] calls [f key] for each key in sorted order. *) 42 + 43 + val save_root : t -> int 44 + (** [save_root t] returns the current root page for later restore. *) 45 + 46 + val restore_root : t -> int -> unit 47 + (** [restore_root t root] sets the root page to [root]. *)
+21
lib/pager.ml
··· 79 79 Eio.File.pwrite_all file ~file_offset:offset [ buf ]) 80 80 t.dirty); 81 81 Hashtbl.clear t.dirty 82 + 83 + (* Snapshots *) 84 + 85 + type snapshot = { s_dirty : (int, string) Hashtbl.t; s_page_count : int } 86 + 87 + let snapshot t = { s_dirty = Hashtbl.copy t.dirty; s_page_count = t.page_count } 88 + 89 + let rollback t snap = 90 + (* Invalidate cache for pages dirtied since the snapshot *) 91 + Hashtbl.iter 92 + (fun page _ -> 93 + if not (Hashtbl.mem snap.s_dirty page) then Hashtbl.remove t.cache page) 94 + t.dirty; 95 + (* Restore dirty set and update cache from snapshot *) 96 + Hashtbl.reset t.dirty; 97 + Hashtbl.iter 98 + (fun k v -> 99 + Hashtbl.replace t.dirty k v; 100 + Hashtbl.replace t.cache k v) 101 + snap.s_dirty; 102 + t.page_count <- snap.s_page_count
+14
lib/pager.mli
··· 32 32 33 33 val sync : t -> unit 34 34 (** [sync t] syncs all dirty pages to disk. *) 35 + 36 + (** {1 Snapshots} 37 + 38 + Snapshots capture the pager state for transaction rollback. *) 39 + 40 + type snapshot 41 + (** An opaque snapshot of pager state. *) 42 + 43 + val snapshot : t -> snapshot 44 + (** [snapshot t] captures the current dirty page set and page count. *) 45 + 46 + val rollback : t -> snapshot -> unit 47 + (** [rollback t snap] restores the pager to the state captured by [snap]. Pages 48 + modified since the snapshot are discarded from the dirty set and cache. *)
+3
lib/table.ml
··· 542 542 let acc = ref init in 543 543 iter t (fun rowid data -> acc := f rowid data !acc); 544 544 !acc 545 + 546 + let save_root t = t.root_page 547 + let restore_root t root = t.root_page <- root
+6
lib/table.mli
··· 31 31 32 32 val fold : t -> init:'a -> f:(int64 -> string -> 'a -> 'a) -> 'a 33 33 (** [fold t ~init ~f] folds over all records in order. *) 34 + 35 + val save_root : t -> int 36 + (** [save_root t] returns the current root page for later restore. *) 37 + 38 + val restore_root : t -> int -> unit 39 + (** [restore_root t root] sets the root page to [root]. *)