···33 SPDX-License-Identifier: MIT
44 ---------------------------------------------------------------------------*)
5566-(* Varint encoding - SQLite style *)
77-module Varint = struct
88- let decode buf off =
99- let rec loop acc shift i =
1010- if i >= String.length buf then (acc, i - off)
1111- else
1212- let byte = Char.code buf.[i] in
1313- let value = Int64.of_int (byte land 0x7f) in
1414- let acc = Int64.logor acc (Int64.shift_left value shift) in
1515- if byte land 0x80 = 0 then (acc, i - off + 1)
1616- else if shift >= 56 then
1717- (* 9th byte - use all 8 bits *)
1818- let byte9 = Char.code buf.[i + 1] in
1919- let acc =
2020- Int64.logor acc (Int64.shift_left (Int64.of_int byte9) 56)
2121- in
2222- (acc, i - off + 2)
2323- else loop acc (shift + 7) (i + 1)
2424- in
2525- loop 0L 0 off
2626-2727- let size n =
2828- if n < 0L then 9
2929- else if n < 128L then 1
3030- else if n < 16384L then 2
3131- else if n < 2097152L then 3
3232- else if n < 268435456L then 4
3333- else if n < 34359738368L then 5
3434- else if n < 4398046511104L then 6
3535- else if n < 562949953421312L then 7
3636- else if n < 72057594037927936L then 8
3737- else 9
3838-3939- let encode n =
4040- let sz = size n in
4141- let buf = Bytes.create sz in
4242- let rec loop n i =
4343- if i = sz - 1 then Bytes.set_uint8 buf i (Int64.to_int n land 0x7f)
4444- else begin
4545- Bytes.set_uint8 buf i (Int64.to_int n land 0x7f lor 0x80);
4646- loop (Int64.shift_right_logical n 7) (i + 1)
4747- end
4848- in
4949- loop n 0;
5050- Bytes.unsafe_to_string buf
5151-end
5252-5353-(* Page types *)
5454-type page_type = Interior_index | Interior_table | Leaf_index | Leaf_table
5555-5656-let pp_page_type ppf = function
5757- | Interior_index -> Format.pp_print_string ppf "interior_index"
5858- | Interior_table -> Format.pp_print_string ppf "interior_table"
5959- | Leaf_index -> Format.pp_print_string ppf "leaf_index"
6060- | Leaf_table -> Format.pp_print_string ppf "leaf_table"
6161-6262-let page_type_of_byte = function
6363- | 0x02 -> Interior_index
6464- | 0x05 -> Interior_table
6565- | 0x0a -> Leaf_index
6666- | 0x0d -> Leaf_table
6767- | b -> failwith (Printf.sprintf "Invalid page type: 0x%02x" b)
6868-6969-let byte_of_page_type = function
7070- | Interior_index -> 0x02
7171- | Interior_table -> 0x05
7272- | Leaf_index -> 0x0a
7373- | Leaf_table -> 0x0d
7474-7575-let page_header_size = function
7676- | Interior_index | Interior_table -> 12
7777- | Leaf_index | Leaf_table -> 8
7878-7979-let is_interior = function
8080- | Interior_index | Interior_table -> true
8181- | Leaf_index | Leaf_table -> false
8282-8383-(* Page header *)
8484-type page_header = {
8585- page_type : page_type;
8686- first_freeblock : int;
8787- cell_count : int;
8888- cell_content_start : int;
8989- fragmented_bytes : int;
9090- right_child : int option;
9191-}
9292-9393-let get_u16_be buf off = (Char.code buf.[off] lsl 8) lor Char.code buf.[off + 1]
9494-9595-let get_u32_be buf off =
9696- (Char.code buf.[off] lsl 24)
9797- lor (Char.code buf.[off + 1] lsl 16)
9898- lor (Char.code buf.[off + 2] lsl 8)
9999- lor Char.code buf.[off + 3]
100100-101101-let set_u16_be buf off v =
102102- Bytes.set_uint8 buf off (v lsr 8);
103103- Bytes.set_uint8 buf (off + 1) (v land 0xff)
104104-105105-let set_u32_be buf off v =
106106- Bytes.set_uint8 buf off (v lsr 24);
107107- Bytes.set_uint8 buf (off + 1) ((v lsr 16) land 0xff);
108108- Bytes.set_uint8 buf (off + 2) ((v lsr 8) land 0xff);
109109- Bytes.set_uint8 buf (off + 3) (v land 0xff)
110110-111111-let parse_page_header buf off =
112112- let page_type = page_type_of_byte (Char.code buf.[off]) in
113113- let first_freeblock = get_u16_be buf (off + 1) in
114114- let cell_count = get_u16_be buf (off + 3) in
115115- let cell_content_start =
116116- let v = get_u16_be buf (off + 5) in
117117- if v = 0 then 65536 else v
118118- in
119119- let fragmented_bytes = Char.code buf.[off + 7] in
120120- let right_child =
121121- if is_interior page_type then Some (get_u32_be buf (off + 8)) else None
122122- in
123123- {
124124- page_type;
125125- first_freeblock;
126126- cell_count;
127127- cell_content_start;
128128- fragmented_bytes;
129129- right_child;
130130- }
131131-132132-(* Cells *)
133133-module Cell = struct
134134- type table_leaf = {
135135- rowid : int64;
136136- payload : string;
137137- overflow_page : int option;
138138- }
139139-140140- type table_interior = { left_child : int; rowid : int64 }
141141- type index_leaf = { payload : string; overflow_page : int option }
142142-143143- type index_interior = {
144144- left_child : int;
145145- payload : string;
146146- overflow_page : int option;
147147- }
148148-149149- (* Calculate max payload on page - simplified *)
150150- let max_local ~usable_size ~is_table =
151151- if is_table then usable_size - 35 else ((usable_size - 12) * 64 / 255) - 23
152152-153153- let min_local ~usable_size = ((usable_size - 12) * 32 / 255) - 23
154154-155155- let parse_table_leaf buf off ~usable_size =
156156- let payload_size, consumed1 = Varint.decode buf off in
157157- let rowid, consumed2 = Varint.decode buf (off + consumed1) in
158158- let header_len = consumed1 + consumed2 in
159159- let payload_size = Int64.to_int payload_size in
160160- let max_local = max_local ~usable_size ~is_table:true in
161161- let min_local = min_local ~usable_size in
162162- let local_size, overflow_page =
163163- if payload_size <= max_local then (payload_size, None)
164164- else
165165- let k =
166166- min_local + ((payload_size - min_local) mod (usable_size - 4))
167167- in
168168- let local = if k <= max_local then k else min_local in
169169- let overflow = get_u32_be buf (off + header_len + local) in
170170- (local, Some overflow)
171171- in
172172- let payload = String.sub buf (off + header_len) local_size in
173173- let total_consumed =
174174- header_len + local_size + if overflow_page = None then 0 else 4
175175- in
176176- ({ rowid; payload; overflow_page }, total_consumed)
177177-178178- let parse_table_interior buf off =
179179- let left_child = get_u32_be buf off in
180180- let rowid, consumed = Varint.decode buf (off + 4) in
181181- ({ left_child; rowid }, 4 + consumed)
182182-183183- (* Parse index leaf cell - returns local payload and overflow pointer *)
184184- let parse_index_leaf_raw buf off ~usable_size =
185185- let payload_size, consumed = Varint.decode buf off in
186186- let payload_size = Int64.to_int payload_size in
187187- let max_local = max_local ~usable_size ~is_table:false in
188188- let min_local = min_local ~usable_size in
189189- let local_size, overflow_page =
190190- if payload_size <= max_local then (payload_size, None)
191191- else
192192- let k =
193193- min_local + ((payload_size - min_local) mod (usable_size - 4))
194194- in
195195- let local = if k <= max_local then k else min_local in
196196- let overflow = get_u32_be buf (off + consumed + local) in
197197- (local, Some overflow)
198198- in
199199- let local_payload = String.sub buf (off + consumed) local_size in
200200- let total = consumed + local_size + if overflow_page = None then 0 else 4 in
201201- (payload_size, local_payload, overflow_page, total)
202202-203203- let parse_index_leaf buf off ~usable_size =
204204- let payload_size, local_payload, overflow_page, total =
205205- parse_index_leaf_raw buf off ~usable_size
206206- in
207207- (* For now, return just the local payload - caller must handle overflow *)
208208- ignore payload_size;
209209- ({ payload = local_payload; overflow_page }, total)
210210-211211- (* Parse index interior cell - returns local payload and overflow pointer *)
212212- let parse_index_interior_raw buf off ~usable_size =
213213- let left_child = get_u32_be buf off in
214214- let payload_size, consumed = Varint.decode buf (off + 4) in
215215- let payload_size = Int64.to_int payload_size in
216216- let max_local = max_local ~usable_size ~is_table:false in
217217- let min_local = min_local ~usable_size in
218218- let local_size, overflow_page =
219219- if payload_size <= max_local then (payload_size, None)
220220- else
221221- let k =
222222- min_local + ((payload_size - min_local) mod (usable_size - 4))
223223- in
224224- let local = if k <= max_local then k else min_local in
225225- let overflow = get_u32_be buf (off + 4 + consumed + local) in
226226- (local, Some overflow)
227227- in
228228- let local_payload = String.sub buf (off + 4 + consumed) local_size in
229229- let total =
230230- 4 + consumed + local_size + if overflow_page = None then 0 else 4
231231- in
232232- (left_child, payload_size, local_payload, overflow_page, total)
233233-234234- let parse_index_interior buf off ~usable_size =
235235- let left_child, _payload_size, local_payload, overflow_page, total =
236236- parse_index_interior_raw buf off ~usable_size
237237- in
238238- ({ left_child; payload = local_payload; overflow_page }, total)
239239-end
240240-241241-(* Record format *)
242242-module Record = struct
243243- type serial_type =
244244- | Null
245245- | Int8
246246- | Int16
247247- | Int24
248248- | Int32
249249- | Int48
250250- | Int64
251251- | Float64
252252- | Zero
253253- | One
254254- | Blob of int
255255- | Text of int
256256-257257- type value =
258258- | Vnull
259259- | Vint of int64
260260- | Vfloat of float
261261- | Vblob of string
262262- | Vtext of string
263263-264264- let serial_type_of_int = function
265265- | 0 -> Null
266266- | 1 -> Int8
267267- | 2 -> Int16
268268- | 3 -> Int24
269269- | 4 -> Int32
270270- | 5 -> Int48
271271- | 6 -> Int64
272272- | 7 -> Float64
273273- | 8 -> Zero
274274- | 9 -> One
275275- | n when n >= 12 && n mod 2 = 0 -> Blob ((n - 12) / 2)
276276- | n when n >= 13 -> Text ((n - 13) / 2)
277277- | n -> failwith (Printf.sprintf "Invalid serial type: %d" n)
278278-279279- let decode_int buf off len =
280280- let rec loop acc i =
281281- if i >= len then acc
282282- else
283283- let b = Char.code buf.[off + i] in
284284- let acc = Int64.logor (Int64.shift_left acc 8) (Int64.of_int b) in
285285- loop acc (i + 1)
286286- in
287287- (* Sign extend for negative values *)
288288- let v = loop 0L 0 in
289289- if len > 0 && Char.code buf.[off] land 0x80 <> 0 then
290290- let mask = Int64.shift_left (-1L) (len * 8) in
291291- Int64.logor v mask
292292- else v
293293-294294- let decode payload =
295295- let header_size, consumed = Varint.decode payload 0 in
296296- let header_size = Int64.to_int header_size in
297297- (* Parse serial types *)
298298- let rec parse_types off acc =
299299- if off >= header_size then List.rev acc
300300- else
301301- let st, consumed = Varint.decode payload off in
302302- let st = serial_type_of_int (Int64.to_int st) in
303303- parse_types (off + consumed) (st :: acc)
304304- in
305305- let types = parse_types consumed [] in
306306- (* Parse values *)
307307- let rec parse_values types off acc =
308308- match types with
309309- | [] -> List.rev acc
310310- | st :: rest ->
311311- let value, sz =
312312- match st with
313313- | Null -> (Vnull, 0)
314314- | Zero -> (Vint 0L, 0)
315315- | One -> (Vint 1L, 0)
316316- | Int8 -> (Vint (decode_int payload off 1), 1)
317317- | Int16 -> (Vint (decode_int payload off 2), 2)
318318- | Int24 -> (Vint (decode_int payload off 3), 3)
319319- | Int32 -> (Vint (decode_int payload off 4), 4)
320320- | Int48 -> (Vint (decode_int payload off 6), 6)
321321- | Int64 -> (Vint (decode_int payload off 8), 8)
322322- | Float64 ->
323323- let bits = decode_int payload off 8 in
324324- (Vfloat (Int64.float_of_bits bits), 8)
325325- | Blob n -> (Vblob (String.sub payload off n), n)
326326- | Text n -> (Vtext (String.sub payload off n), n)
327327- in
328328- parse_values rest (off + sz) (value :: acc)
329329- in
330330- parse_values types header_size []
331331-332332- let serial_type_of_value = function
333333- | Vnull -> (0, 0)
334334- | Vint 0L -> (8, 0)
335335- | Vint 1L -> (9, 0)
336336- | Vint n ->
337337- if n >= -128L && n <= 127L then (1, 1)
338338- else if n >= -32768L && n <= 32767L then (2, 2)
339339- else if n >= -8388608L && n <= 8388607L then (3, 3)
340340- else if n >= -2147483648L && n <= 2147483647L then (4, 4)
341341- else if n >= -140737488355328L && n <= 140737488355327L then (5, 6)
342342- else (6, 8)
343343- | Vfloat _ -> (7, 8)
344344- | Vblob s -> (12 + (String.length s * 2), String.length s)
345345- | Vtext s -> (13 + (String.length s * 2), String.length s)
346346-347347- let encode_int buf off n len =
348348- for i = 0 to len - 1 do
349349- let shift = (len - 1 - i) * 8 in
350350- Bytes.set_uint8 buf (off + i)
351351- (Int64.to_int (Int64.shift_right n shift) land 0xff)
352352- done
353353-354354- let encode values =
355355- (* Calculate header *)
356356- let types_and_sizes = List.map serial_type_of_value values in
357357- let header_types =
358358- List.map (fun (st, _) -> Varint.encode (Int64.of_int st)) types_and_sizes
359359- in
360360- let header_body = String.concat "" header_types in
361361- let header_size = 1 + String.length header_body in
362362- (* header size varint + types *)
363363- let body_size =
364364- List.fold_left (fun acc (_, sz) -> acc + sz) 0 types_and_sizes
365365- in
366366- let total = header_size + body_size in
367367- let buf = Bytes.create total in
368368- (* Write header size *)
369369- Bytes.set_uint8 buf 0 header_size;
370370- (* Write serial types *)
371371- let _ =
372372- List.fold_left
373373- (fun off s ->
374374- Bytes.blit_string s 0 buf off (String.length s);
375375- off + String.length s)
376376- 1 header_types
377377- in
378378- (* Write values *)
379379- let _ =
380380- List.fold_left2
381381- (fun off value (_, sz) ->
382382- (match value with
383383- | Vnull | Vint 0L | Vint 1L -> ()
384384- | Vint n -> encode_int buf off n sz
385385- | Vfloat f -> encode_int buf off (Int64.bits_of_float f) 8
386386- | Vblob s | Vtext s -> Bytes.blit_string s 0 buf off sz);
387387- off + sz)
388388- header_size values types_and_sizes
389389- in
390390- Bytes.unsafe_to_string buf
391391-392392- let pp_value ppf = function
393393- | Vnull -> Format.pp_print_string ppf "NULL"
394394- | Vint n -> Format.fprintf ppf "%Ld" n
395395- | Vfloat f -> Format.fprintf ppf "%f" f
396396- | Vblob s -> Format.fprintf ppf "BLOB(%d)" (String.length s)
397397- | Vtext s -> Format.fprintf ppf "%S" s
398398-end
399399-400400-(* Pager *)
401401-module Pager = struct
402402- type t = {
403403- file : Eio.File.rw_ty Eio.Resource.t;
404404- page_size : int;
405405- mutable page_count : int;
406406- cache : (int, string) Hashtbl.t;
407407- dirty : (int, string) Hashtbl.t;
408408- }
409409-410410- let create ~page_size file =
411411- let stat = Eio.File.stat file in
412412- let file_size = Optint.Int63.to_int stat.size in
413413- let page_count = if file_size = 0 then 0 else file_size / page_size in
414414- {
415415- file;
416416- page_size;
417417- page_count;
418418- cache = Hashtbl.create 64;
419419- dirty = Hashtbl.create 16;
420420- }
421421-422422- let page_size t = t.page_size
423423- let page_count t = t.page_count
424424-425425- let read t page_num =
426426- if page_num < 1 || page_num > t.page_count then
427427- failwith (Printf.sprintf "Invalid page number: %d" page_num);
428428- match Hashtbl.find_opt t.dirty page_num with
429429- | Some data -> data
430430- | None -> (
431431- match Hashtbl.find_opt t.cache page_num with
432432- | Some data -> data
433433- | None ->
434434- let offset = Optint.Int63.of_int ((page_num - 1) * t.page_size) in
435435- let buf = Cstruct.create t.page_size in
436436- Eio.File.pread_exact t.file ~file_offset:offset [ buf ];
437437- let data = Cstruct.to_string buf in
438438- Hashtbl.replace t.cache page_num data;
439439- data)
440440-441441- let write t page_num data =
442442- if String.length data <> t.page_size then failwith "Invalid page size";
443443- Hashtbl.replace t.dirty page_num data;
444444- Hashtbl.replace t.cache page_num data
445445-446446- let allocate t =
447447- t.page_count <- t.page_count + 1;
448448- let data = String.make t.page_size '\x00' in
449449- Hashtbl.replace t.dirty t.page_count data;
450450- Hashtbl.replace t.cache t.page_count data;
451451- t.page_count
452452-453453- let sync t =
454454- Hashtbl.iter
455455- (fun page_num data ->
456456- let offset = Optint.Int63.of_int ((page_num - 1) * t.page_size) in
457457- let buf = Cstruct.of_string data in
458458- Eio.File.pwrite_all t.file ~file_offset:offset [ buf ])
459459- t.dirty;
460460- Hashtbl.clear t.dirty
461461-end
462462-463463-(* Table B-tree *)
464464-module Table = struct
465465- type t = { pager : Pager.t; mutable root_page : int }
466466-467467- (* Initialize a page as empty leaf or interior *)
468468- let init_page ~page_size ~page_type =
469469- let buf = Bytes.create page_size in
470470- Bytes.set_uint8 buf 0 (byte_of_page_type page_type);
471471- set_u16_be buf 1 0;
472472- (* first freeblock *)
473473- set_u16_be buf 3 0;
474474- (* cell count *)
475475- set_u16_be buf 5 page_size;
476476- (* cell content start *)
477477- Bytes.set_uint8 buf 7 0;
478478- (* fragmented bytes *)
479479- if is_interior page_type then set_u32_be buf 8 0;
480480- (* right child *)
481481- buf
482482-483483- let create pager =
484484- let root = Pager.allocate pager in
485485- let page_size = Pager.page_size pager in
486486- let buf = init_page ~page_size ~page_type:Leaf_table in
487487- Pager.write pager root (Bytes.unsafe_to_string buf);
488488- { pager; root_page = root }
489489-490490- let open_ pager ~root_page = { pager; root_page }
491491- let root_page t = t.root_page
492492- let usable_size t = Pager.page_size t.pager
493493-494494- (* Find cell pointers in a page *)
495495- let cell_pointers page header_offset header =
496496- let ptrs = Array.make header.cell_count 0 in
497497- let ptr_start = header_offset + page_header_size header.page_type in
498498- for i = 0 to header.cell_count - 1 do
499499- ptrs.(i) <- get_u16_be page (ptr_start + (i * 2))
500500- done;
501501- ptrs
502502-503503- (* Calculate free space in a page *)
504504- let free_space header ~page_type =
505505- let header_size = page_header_size page_type in
506506- let ptr_area_end = header_size + (header.cell_count * 2) in
507507- header.cell_content_start - ptr_area_end - header.fragmented_bytes
508508-509509- (* Encode a table leaf cell *)
510510- let encode_table_leaf_cell ~rowid ~data =
511511- let rowid_varint = Varint.encode rowid in
512512- let payload_size_varint =
513513- Varint.encode (Int64.of_int (String.length data))
514514- in
515515- let cell =
516516- Bytes.create
517517- (String.length payload_size_varint
518518- + String.length rowid_varint + String.length data)
519519- in
520520- Bytes.blit_string payload_size_varint 0 cell 0
521521- (String.length payload_size_varint);
522522- Bytes.blit_string rowid_varint 0 cell
523523- (String.length payload_size_varint)
524524- (String.length rowid_varint);
525525- Bytes.blit_string data 0 cell
526526- (String.length payload_size_varint + String.length rowid_varint)
527527- (String.length data);
528528- Bytes.unsafe_to_string cell
529529-530530- (* Encode a table interior cell *)
531531- let encode_table_interior_cell ~left_child ~rowid =
532532- let rowid_varint = Varint.encode rowid in
533533- let cell = Bytes.create (4 + String.length rowid_varint) in
534534- set_u32_be cell 0 left_child;
535535- Bytes.blit_string rowid_varint 0 cell 4 (String.length rowid_varint);
536536- Bytes.unsafe_to_string cell
537537-538538- (* Write a cell into a page buffer, returns new cell_content_start *)
539539- let write_cell buf ~cell_content_start ~cell =
540540- let cell_len = String.length cell in
541541- let new_start = cell_content_start - cell_len in
542542- Bytes.blit_string cell 0 buf new_start cell_len;
543543- new_start
544544-545545- (* Insert a cell pointer at index, shifting others *)
546546- let insert_cell_pointer buf ~header_offset ~page_type ~cell_count ~index ~ptr
547547- =
548548- let ptr_start = header_offset + page_header_size page_type in
549549- (* Shift existing pointers right *)
550550- for i = cell_count - 1 downto index do
551551- let old_ptr =
552552- get_u16_be (Bytes.unsafe_to_string buf) (ptr_start + (i * 2))
553553- in
554554- set_u16_be buf (ptr_start + ((i + 1) * 2)) old_ptr
555555- done;
556556- set_u16_be buf (ptr_start + (index * 2)) ptr
557557-558558- (* Binary search for rowid in leaf page *)
559559- let search_leaf t page header rowid =
560560- let ptrs = cell_pointers page 0 header in
561561- let usable = usable_size t in
562562- let rec loop lo hi =
563563- if lo > hi then None
564564- else
565565- let mid = (lo + hi) / 2 in
566566- let cell, _ =
567567- Cell.parse_table_leaf page ptrs.(mid) ~usable_size:usable
568568- in
569569- if cell.rowid = rowid then Some cell.payload
570570- else if cell.rowid < rowid then loop (mid + 1) hi
571571- else loop lo (mid - 1)
572572- in
573573- loop 0 (header.cell_count - 1)
574574-575575- (* Find insertion index for rowid in leaf page *)
576576- let find_insert_idx t page header rowid =
577577- let ptrs = cell_pointers page 0 header in
578578- let usable = usable_size t in
579579- let rec find i =
580580- if i >= header.cell_count then i
581581- else
582582- let cell, _ = Cell.parse_table_leaf page ptrs.(i) ~usable_size:usable in
583583- if rowid < cell.rowid then i else find (i + 1)
584584- in
585585- find 0
586586-587587- (* Find child page for rowid in interior page.
588588- SQLite B-tree: keys < separator go left, keys >= separator go right. *)
589589- let find_child _t page header rowid =
590590- let ptrs = cell_pointers page 0 header in
591591- let rec loop i =
592592- if i >= header.cell_count then Option.get header.right_child
593593- else
594594- let cell, _ = Cell.parse_table_interior page ptrs.(i) in
595595- if rowid < cell.rowid then cell.left_child else loop (i + 1)
596596- in
597597- loop 0
598598-599599- (* Find child index for rowid in interior page *)
600600- let find_child_idx page header rowid =
601601- let ptrs = cell_pointers page 0 header in
602602- let rec loop i =
603603- if i >= header.cell_count then i (* right child *)
604604- else
605605- let cell, _ = Cell.parse_table_interior page ptrs.(i) in
606606- if rowid < cell.rowid then i else loop (i + 1)
607607- in
608608- loop 0
609609-610610- let rec find_in_page t page_num rowid =
611611- let page = Pager.read t.pager page_num in
612612- let header = parse_page_header page 0 in
613613- match header.page_type with
614614- | Leaf_table -> search_leaf t page header rowid
615615- | Interior_table ->
616616- let child = find_child t page header rowid in
617617- find_in_page t child rowid
618618- | _ -> failwith "Invalid page type in table B-tree"
619619-620620- let find t rowid = find_in_page t t.root_page rowid
621621-622622- (* Split result: new page number and separator rowid *)
623623- type split_result = { new_page : int; separator_rowid : int64 }
624624-625625- (* Split a leaf page, returns info about the new page *)
626626- let split_leaf t page_num =
627627- let page = Pager.read t.pager page_num in
628628- let header = parse_page_header page 0 in
629629- let ptrs = cell_pointers page 0 header in
630630- let usable = usable_size t in
631631- let page_size = Pager.page_size t.pager in
632632-633633- (* Find split point (middle) *)
634634- let split_idx = header.cell_count / 2 in
635635-636636- (* Get separator rowid (first key that goes to right page) *)
637637- let sep_cell, _ =
638638- Cell.parse_table_leaf page ptrs.(split_idx) ~usable_size:usable
639639- in
640640- let separator_rowid = sep_cell.rowid in
641641-642642- (* Create new right page *)
643643- let new_page_num = Pager.allocate t.pager in
644644- let new_buf = init_page ~page_size ~page_type:Leaf_table in
645645-646646- (* Copy cells [split_idx..cell_count-1] to new page *)
647647- let new_cell_content_start = ref page_size in
648648- for i = split_idx to header.cell_count - 1 do
649649- let cell_off = ptrs.(i) in
650650- let cell, cell_len =
651651- Cell.parse_table_leaf page cell_off ~usable_size:usable
652652- in
653653- let cell_data =
654654- encode_table_leaf_cell ~rowid:cell.rowid ~data:cell.payload
655655- in
656656- new_cell_content_start :=
657657- write_cell new_buf ~cell_content_start:!new_cell_content_start
658658- ~cell:cell_data;
659659- let new_idx = i - split_idx in
660660- let ptr_off = page_header_size Leaf_table + (new_idx * 2) in
661661- set_u16_be new_buf ptr_off !new_cell_content_start;
662662- ignore cell_len
663663- done;
664664- let new_cell_count = header.cell_count - split_idx in
665665- set_u16_be new_buf 3 new_cell_count;
666666- set_u16_be new_buf 5 !new_cell_content_start;
667667- Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
668668-669669- (* Update original page to only have cells [0..split_idx-1] *)
670670- let old_buf = Bytes.of_string page in
671671- set_u16_be old_buf 3 split_idx;
672672- (* Recalculate cell content start for remaining cells *)
673673- if split_idx > 0 then begin
674674- let min_ptr = ref page_size in
675675- for i = 0 to split_idx - 1 do
676676- if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
677677- done;
678678- set_u16_be old_buf 5 !min_ptr
679679- end
680680- else set_u16_be old_buf 5 page_size;
681681- Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
682682-683683- { new_page = new_page_num; separator_rowid }
684684-685685- (* Split an interior page *)
686686- let split_interior t page_num =
687687- let page = Pager.read t.pager page_num in
688688- let header = parse_page_header page 0 in
689689- let ptrs = cell_pointers page 0 header in
690690- let page_size = Pager.page_size t.pager in
691691-692692- (* Split point - middle cell becomes separator, doesn't go to either page *)
693693- let split_idx = header.cell_count / 2 in
694694- let sep_cell, _ = Cell.parse_table_interior page ptrs.(split_idx) in
695695- let separator_rowid = sep_cell.rowid in
696696-697697- (* Create new right page *)
698698- let new_page_num = Pager.allocate t.pager in
699699- let new_buf = init_page ~page_size ~page_type:Interior_table in
700700-701701- (* The right child of split cell becomes the left-most child of new page *)
702702- (* Cells [split_idx+1..cell_count-1] go to new page *)
703703- let new_cell_content_start = ref page_size in
704704- for i = split_idx + 1 to header.cell_count - 1 do
705705- let cell_off = ptrs.(i) in
706706- let cell, _ = Cell.parse_table_interior page cell_off in
707707- let cell_data =
708708- encode_table_interior_cell ~left_child:cell.left_child ~rowid:cell.rowid
709709- in
710710- new_cell_content_start :=
711711- write_cell new_buf ~cell_content_start:!new_cell_content_start
712712- ~cell:cell_data;
713713- let new_idx = i - split_idx - 1 in
714714- let ptr_off = page_header_size Interior_table + (new_idx * 2) in
715715- set_u16_be new_buf ptr_off !new_cell_content_start
716716- done;
717717- let new_cell_count = header.cell_count - split_idx - 1 in
718718- set_u16_be new_buf 3 new_cell_count;
719719- set_u16_be new_buf 5 !new_cell_content_start;
720720- (* Right child of new page is same as original *)
721721- set_u32_be new_buf 8 (Option.get header.right_child);
722722- Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
723723-724724- (* Update original page: keep cells [0..split_idx-1], right child = sep_cell.left_child *)
725725- let old_buf = Bytes.of_string page in
726726- set_u16_be old_buf 3 split_idx;
727727- set_u32_be old_buf 8 sep_cell.left_child;
728728- if split_idx > 0 then begin
729729- let min_ptr = ref page_size in
730730- for i = 0 to split_idx - 1 do
731731- if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
732732- done;
733733- set_u16_be old_buf 5 !min_ptr
734734- end
735735- else set_u16_be old_buf 5 page_size;
736736- Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
737737-738738- { new_page = new_page_num; separator_rowid }
739739-740740- (* Insert a separator into an interior page, potentially splitting *)
741741- let rec insert_into_interior t page_num ~left_child ~separator_rowid
742742- ~right_child =
743743- let page = Pager.read t.pager page_num in
744744- let header = parse_page_header page 0 in
745745- let cell = encode_table_interior_cell ~left_child ~rowid:separator_rowid in
746746- let cell_len = String.length cell in
747747- let space_needed = cell_len + 2 in
748748- (* cell + pointer *)
749749-750750- if free_space header ~page_type:Interior_table >= space_needed then begin
751751- (* Fits - insert directly *)
752752- let buf = Bytes.of_string page in
753753- let ptrs = cell_pointers page 0 header in
754754-755755- (* Find insert position *)
756756- let insert_idx =
757757- let rec find i =
758758- if i >= header.cell_count then i
759759- else
760760- let c, _ = Cell.parse_table_interior page ptrs.(i) in
761761- if separator_rowid < c.rowid then i else find (i + 1)
762762- in
763763- find 0
764764- in
765765-766766- (* Write cell *)
767767- let cell_start =
768768- write_cell buf ~cell_content_start:header.cell_content_start ~cell
769769- in
770770- set_u16_be buf 5 cell_start;
771771-772772- (* Insert pointer *)
773773- insert_cell_pointer buf ~header_offset:0 ~page_type:Interior_table
774774- ~cell_count:header.cell_count ~index:insert_idx ~ptr:cell_start;
775775-776776- (* Update cell count *)
777777- set_u16_be buf 3 (header.cell_count + 1);
778778-779779- (* Update child pointers: the cell we displaced (now at insert_idx+1)
780780- needs its left_child updated to right_child, OR if we inserted at end,
781781- update the page's right_child. *)
782782- if insert_idx < header.cell_count then begin
783783- (* Update the displaced cell's left_child to right_child *)
784784- let ptr_start = page_header_size Interior_table in
785785- let displaced_ptr =
786786- get_u16_be
787787- (Bytes.unsafe_to_string buf)
788788- (ptr_start + ((insert_idx + 1) * 2))
789789- in
790790- set_u32_be buf displaced_ptr right_child
791791- end
792792- else set_u32_be buf 8 right_child;
793793-794794- Pager.write t.pager page_num (Bytes.unsafe_to_string buf);
795795- None
796796- end
797797- else begin
798798- (* Need to split interior page *)
799799- let split = split_interior t page_num in
800800-801801- (* Determine which page gets the new separator *)
802802- if separator_rowid < split.separator_rowid then begin
803803- (* Insert into left (original) page *)
804804- ignore
805805- (insert_into_interior t page_num ~left_child ~separator_rowid
806806- ~right_child)
807807- end
808808- else begin
809809- (* Insert into right (new) page *)
810810- ignore
811811- (insert_into_interior t split.new_page ~left_child ~separator_rowid
812812- ~right_child)
813813- end;
814814-815815- (* Return split info to propagate up *)
816816- Some { split with separator_rowid = split.separator_rowid }
817817- end
818818-819819- (* Insert into a leaf page, potentially splitting *)
820820- let rec insert_into_leaf t page_num ~rowid ~data ~parent_stack =
821821- let page = Pager.read t.pager page_num in
822822- let header = parse_page_header page 0 in
823823- let cell = encode_table_leaf_cell ~rowid ~data in
824824- let cell_len = String.length cell in
825825- let space_needed = cell_len + 2 in
826826- (* cell + pointer *)
827827-828828- if free_space header ~page_type:Leaf_table >= space_needed then begin
829829- (* Fits - insert directly *)
830830- let buf = Bytes.of_string page in
831831- let insert_idx = find_insert_idx t page header rowid in
832832-833833- (* Write cell *)
834834- let cell_start =
835835- write_cell buf ~cell_content_start:header.cell_content_start ~cell
836836- in
837837- set_u16_be buf 5 cell_start;
838838-839839- (* Insert pointer *)
840840- insert_cell_pointer buf ~header_offset:0 ~page_type:Leaf_table
841841- ~cell_count:header.cell_count ~index:insert_idx ~ptr:cell_start;
842842-843843- (* Update cell count *)
844844- set_u16_be buf 3 (header.cell_count + 1);
845845-846846- Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
847847- end
848848- else begin
849849- (* Need to split *)
850850- let split = split_leaf t page_num in
851851-852852- (* Determine target page and insert directly (no recursion needed -
853853- after split, both pages have ~half capacity, plenty of room) *)
854854- let target_page =
855855- if rowid < split.separator_rowid then page_num else split.new_page
856856- in
857857- let target = Pager.read t.pager target_page in
858858- let target_header = parse_page_header target 0 in
859859- let target_buf = Bytes.of_string target in
860860- let insert_idx = find_insert_idx t target target_header rowid in
861861- let cell_start =
862862- write_cell target_buf
863863- ~cell_content_start:target_header.cell_content_start ~cell
864864- in
865865- set_u16_be target_buf 5 cell_start;
866866- insert_cell_pointer target_buf ~header_offset:0 ~page_type:Leaf_table
867867- ~cell_count:target_header.cell_count ~index:insert_idx ~ptr:cell_start;
868868- set_u16_be target_buf 3 (target_header.cell_count + 1);
869869- Pager.write t.pager target_page (Bytes.unsafe_to_string target_buf);
870870-871871- (* Propagate split up *)
872872- propagate_split t ~parent_stack ~left_page:page_num
873873- ~separator_rowid:split.separator_rowid ~right_page:split.new_page
874874- end
875875-876876- and propagate_split t ~parent_stack ~left_page ~separator_rowid ~right_page =
877877- match parent_stack with
878878- | [] ->
879879- (* Splitting root - create new root *)
880880- let page_size = Pager.page_size t.pager in
881881- let new_root = Pager.allocate t.pager in
882882- let buf = init_page ~page_size ~page_type:Interior_table in
883883-884884- (* Single cell pointing to left page *)
885885- let cell =
886886- encode_table_interior_cell ~left_child:left_page
887887- ~rowid:separator_rowid
888888- in
889889- let cell_start = write_cell buf ~cell_content_start:page_size ~cell in
890890- set_u16_be buf 5 cell_start;
891891- set_u16_be buf (page_header_size Interior_table) cell_start;
892892- set_u16_be buf 3 1;
893893- set_u32_be buf 8 right_page;
894894-895895- Pager.write t.pager new_root (Bytes.unsafe_to_string buf);
896896- t.root_page <- new_root
897897- | parent_page :: rest -> (
898898- match
899899- insert_into_interior t parent_page ~left_child:left_page
900900- ~separator_rowid ~right_child:right_page
901901- with
902902- | None -> () (* Fit in parent *)
903903- | Some split ->
904904- (* Parent also split, propagate up *)
905905- propagate_split t ~parent_stack:rest ~left_page:parent_page
906906- ~separator_rowid:split.separator_rowid ~right_page:split.new_page)
907907-908908- (* Main insert - traverses tree and handles splits *)
909909- let insert t ~rowid data =
910910- let rec traverse page_num parent_stack =
911911- let page = Pager.read t.pager page_num in
912912- let header = parse_page_header page 0 in
913913- match header.page_type with
914914- | Leaf_table -> insert_into_leaf t page_num ~rowid ~data ~parent_stack
915915- | Interior_table ->
916916- let child_idx = find_child_idx page header rowid in
917917- let child_page =
918918- if child_idx >= header.cell_count then Option.get header.right_child
919919- else
920920- let ptrs = cell_pointers page 0 header in
921921- let cell, _ = Cell.parse_table_interior page ptrs.(child_idx) in
922922- cell.left_child
923923- in
924924- traverse child_page (page_num :: parent_stack)
925925- | _ -> failwith "Invalid page type in table B-tree"
926926- in
927927- traverse t.root_page []
928928-929929- let delete _t _rowid = failwith "Delete not yet implemented"
930930-931931- let iter t f =
932932- let rec iter_page page_num =
933933- let page = Pager.read t.pager page_num in
934934- let header = parse_page_header page 0 in
935935- let ptrs = cell_pointers page 0 header in
936936- let usable = usable_size t in
937937- match header.page_type with
938938- | Leaf_table ->
939939- for i = 0 to header.cell_count - 1 do
940940- let cell, _ =
941941- Cell.parse_table_leaf page ptrs.(i) ~usable_size:usable
942942- in
943943- f cell.rowid cell.payload
944944- done
945945- | Interior_table ->
946946- for i = 0 to header.cell_count - 1 do
947947- let cell, _ = Cell.parse_table_interior page ptrs.(i) in
948948- iter_page cell.left_child
949949- done;
950950- Option.iter iter_page header.right_child
951951- | _ -> failwith "Invalid page type"
952952- in
953953- iter_page t.root_page
954954-955955- let fold t ~init ~f =
956956- let acc = ref init in
957957- iter t (fun rowid data -> acc := f rowid data !acc);
958958- !acc
959959-end
960960-961961-(* Index B-tree *)
962962-module Index = struct
963963- type t = { pager : Pager.t; mutable root_page : int }
964964-965965- (* Initialize a page as empty leaf or interior *)
966966- let init_page ~page_size ~page_type =
967967- let buf = Bytes.create page_size in
968968- Bytes.set_uint8 buf 0 (byte_of_page_type page_type);
969969- set_u16_be buf 1 0;
970970- (* first freeblock *)
971971- set_u16_be buf 3 0;
972972- (* cell count *)
973973- set_u16_be buf 5 page_size;
974974- (* cell content start *)
975975- Bytes.set_uint8 buf 7 0;
976976- (* fragmented bytes *)
977977- if is_interior page_type then set_u32_be buf 8 0;
978978- (* right child *)
979979- buf
980980-981981- let create pager =
982982- let root = Pager.allocate pager in
983983- let page_size = Pager.page_size pager in
984984- let buf = init_page ~page_size ~page_type:Leaf_index in
985985- Pager.write pager root (Bytes.unsafe_to_string buf);
986986- { pager; root_page = root }
987987-988988- let open_ pager ~root_page = { pager; root_page }
989989- let root_page t = t.root_page
990990- let usable_size t = Pager.page_size t.pager
991991-992992- (* Overflow page handling *)
993993- let read_overflow_chain pager first_page ~remaining_size =
994994- let usable = Pager.page_size pager in
995995- let overflow_content_size = usable - 4 in
996996- let buf = Buffer.create remaining_size in
997997- let rec read page_num remaining =
998998- if remaining <= 0 || page_num = 0 then ()
999999- else begin
10001000- let page = Pager.read pager page_num in
10011001- let next_page = get_u32_be page 0 in
10021002- let to_read = min remaining overflow_content_size in
10031003- Buffer.add_substring buf page 4 to_read;
10041004- read next_page (remaining - to_read)
10051005- end
10061006- in
10071007- read first_page remaining_size;
10081008- Buffer.contents buf
10091009-10101010- let write_overflow_chain pager payload ~offset =
10111011- let usable = Pager.page_size pager in
10121012- let overflow_content_size = usable - 4 in
10131013- let remaining = String.length payload - offset in
10141014- if remaining <= 0 then 0
10151015- else begin
10161016- let first_page = ref 0 in
10171017- let prev_page_buf = ref None in
10181018- let prev_page_num = ref 0 in
10191019- let pos = ref offset in
10201020- while !pos < String.length payload do
10211021- let page_num = Pager.allocate pager in
10221022- if !first_page = 0 then first_page := page_num;
10231023- (* Link previous page to this one *)
10241024- (match !prev_page_buf with
10251025- | Some buf ->
10261026- set_u32_be buf 0 page_num;
10271027- Pager.write pager !prev_page_num (Bytes.unsafe_to_string buf)
10281028- | None -> ());
10291029- (* Write this page *)
10301030- let page_buf = Bytes.create usable in
10311031- set_u32_be page_buf 0 0;
10321032- (* Next page = 0 for now *)
10331033- let to_write =
10341034- min (String.length payload - !pos) overflow_content_size
10351035- in
10361036- Bytes.blit_string payload !pos page_buf 4 to_write;
10371037- prev_page_buf := Some page_buf;
10381038- prev_page_num := page_num;
10391039- pos := !pos + to_write
10401040- done;
10411041- (* Write final page *)
10421042- (match !prev_page_buf with
10431043- | Some buf ->
10441044- Pager.write pager !prev_page_num (Bytes.unsafe_to_string buf)
10451045- | None -> ());
10461046- !first_page
10471047- end
10481048-10491049- (* Read full payload including overflow pages for leaf cells *)
10501050- let read_full_payload t page off ~usable_size =
10511051- let payload_size, local_payload, overflow_page, _consumed =
10521052- Cell.parse_index_leaf_raw page off ~usable_size
10531053- in
10541054- match overflow_page with
10551055- | None -> local_payload
10561056- | Some first_overflow ->
10571057- let remaining = payload_size - String.length local_payload in
10581058- let overflow_data =
10591059- read_overflow_chain t.pager first_overflow ~remaining_size:remaining
10601060- in
10611061- local_payload ^ overflow_data
10621062-10631063- (* Read full payload including overflow pages for interior cells *)
10641064- let read_full_interior_payload t page off ~usable_size =
10651065- let _left_child, payload_size, local_payload, overflow_page, _consumed =
10661066- Cell.parse_index_interior_raw page off ~usable_size
10671067- in
10681068- match overflow_page with
10691069- | None -> local_payload
10701070- | Some first_overflow ->
10711071- let remaining = payload_size - String.length local_payload in
10721072- let overflow_data =
10731073- read_overflow_chain t.pager first_overflow ~remaining_size:remaining
10741074- in
10751075- local_payload ^ overflow_data
10761076-10771077- let cell_pointers page header =
10781078- let ptrs = Array.make header.cell_count 0 in
10791079- let ptr_start = page_header_size header.page_type in
10801080- for i = 0 to header.cell_count - 1 do
10811081- ptrs.(i) <- get_u16_be page (ptr_start + (i * 2))
10821082- done;
10831083- ptrs
10841084-10851085- (* Calculate free space in a page *)
10861086- let free_space header ~page_type =
10871087- let header_size = page_header_size page_type in
10881088- let ptr_area_end = header_size + (header.cell_count * 2) in
10891089- header.cell_content_start - ptr_area_end - header.fragmented_bytes
10901090-10911091- (* Encode an index leaf cell - handles overflow for large payloads *)
10921092- let encode_index_leaf_cell_with_overflow t ~payload =
10931093- let payload_size = String.length payload in
10941094- let usable_size = usable_size t in
10951095- let max_local = Cell.max_local ~usable_size ~is_table:false in
10961096- let min_local = Cell.min_local ~usable_size in
10971097- let payload_size_varint = Varint.encode (Int64.of_int payload_size) in
10981098- let varint_len = String.length payload_size_varint in
10991099- if payload_size <= max_local then begin
11001100- (* Fits entirely - no overflow needed *)
11011101- let cell = Bytes.create (varint_len + payload_size) in
11021102- Bytes.blit_string payload_size_varint 0 cell 0 varint_len;
11031103- Bytes.blit_string payload 0 cell varint_len payload_size;
11041104- Bytes.unsafe_to_string cell
11051105- end
11061106- else begin
11071107- (* Need overflow pages *)
11081108- let k = min_local + ((payload_size - min_local) mod (usable_size - 4)) in
11091109- let local_size = if k <= max_local then k else min_local in
11101110- (* Write overflow pages for data beyond local_size *)
11111111- let overflow_page =
11121112- write_overflow_chain t.pager payload ~offset:local_size
11131113- in
11141114- (* Create cell: varint(size) + local_payload + overflow_ptr *)
11151115- let cell = Bytes.create (varint_len + local_size + 4) in
11161116- Bytes.blit_string payload_size_varint 0 cell 0 varint_len;
11171117- Bytes.blit_string payload 0 cell varint_len local_size;
11181118- set_u32_be cell (varint_len + local_size) overflow_page;
11191119- Bytes.unsafe_to_string cell
11201120- end
11211121-11221122- (* Encode an index interior cell *)
11231123- let encode_index_interior_cell ~left_child ~payload =
11241124- let payload_size_varint =
11251125- Varint.encode (Int64.of_int (String.length payload))
11261126- in
11271127- let cell =
11281128- Bytes.create
11291129- (4 + String.length payload_size_varint + String.length payload)
11301130- in
11311131- set_u32_be cell 0 left_child;
11321132- Bytes.blit_string payload_size_varint 0 cell 4
11331133- (String.length payload_size_varint);
11341134- Bytes.blit_string payload 0 cell
11351135- (4 + String.length payload_size_varint)
11361136- (String.length payload);
11371137- Bytes.unsafe_to_string cell
11381138-11391139- (* Write a cell into a page buffer, returns new cell_content_start *)
11401140- let write_cell buf ~cell_content_start ~cell =
11411141- let cell_len = String.length cell in
11421142- let new_start = cell_content_start - cell_len in
11431143- Bytes.blit_string cell 0 buf new_start cell_len;
11441144- new_start
11451145-11461146- (* Insert a cell pointer at index, shifting others *)
11471147- let insert_cell_pointer buf ~header_offset ~page_type ~cell_count ~index ~ptr
11481148- =
11491149- let ptr_start = header_offset + page_header_size page_type in
11501150- (* Shift existing pointers right *)
11511151- for i = cell_count - 1 downto index do
11521152- let old_ptr =
11531153- get_u16_be (Bytes.unsafe_to_string buf) (ptr_start + (i * 2))
11541154- in
11551155- set_u16_be buf (ptr_start + ((i + 1) * 2)) old_ptr
11561156- done;
11571157- set_u16_be buf (ptr_start + (index * 2)) ptr
11581158-11591159- (* Compare for btree navigation: key is strictly less than payload.
11601160- Special case: if key is a prefix of payload, they are equal for navigation purposes
11611161- (i.e., entries starting with key might be at or after payload's position). *)
11621162- let key_less_than key payload =
11631163- let key_len = String.length key in
11641164- let payload_len = String.length payload in
11651165- let cmp_len = min key_len payload_len in
11661166- let cmp =
11671167- String.compare (String.sub key 0 cmp_len) (String.sub payload 0 cmp_len)
11681168- in
11691169- if cmp <> 0 then cmp < 0
11701170- else
11711171- (* Prefixes match - if key is shorter or equal length, it's NOT strictly less *)
11721172- false
11731173-11741174- let rec mem_in_page t page_num key =
11751175- let page = Pager.read t.pager page_num in
11761176- let header = parse_page_header page 0 in
11771177- let ptrs = cell_pointers page header in
11781178- let usable = usable_size t in
11791179- match header.page_type with
11801180- | Leaf_index ->
11811181- let rec search lo hi =
11821182- if lo > hi then false
11831183- else
11841184- let mid = (lo + hi) / 2 in
11851185- let cell, _ =
11861186- Cell.parse_index_leaf page ptrs.(mid) ~usable_size:usable
11871187- in
11881188- let cmp = String.compare key cell.payload in
11891189- if cmp = 0 then true
11901190- else if cmp < 0 then search lo (mid - 1)
11911191- else search (mid + 1) hi
11921192- in
11931193- search 0 (header.cell_count - 1)
11941194- | Interior_index ->
11951195- let rec find_child i =
11961196- if i >= header.cell_count then Option.get header.right_child
11971197- else
11981198- let cell, _ =
11991199- Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
12001200- in
12011201- if key_less_than key cell.payload then cell.left_child
12021202- else find_child (i + 1)
12031203- in
12041204- mem_in_page t (find_child 0) key
12051205- | _ -> failwith "Invalid page type in index B-tree"
12061206-12071207- let mem t key = mem_in_page t t.root_page key
12081208-12091209- (* Find exact key, returns payload *)
12101210- let rec find_in_page t page_num key =
12111211- let page = Pager.read t.pager page_num in
12121212- let header = parse_page_header page 0 in
12131213- let ptrs = cell_pointers page header in
12141214- let usable = usable_size t in
12151215- match header.page_type with
12161216- | Leaf_index ->
12171217- let rec search lo hi =
12181218- if lo > hi then None
12191219- else
12201220- let mid = (lo + hi) / 2 in
12211221- (* Read full payload including overflow *)
12221222- let full_payload =
12231223- read_full_payload t page ptrs.(mid) ~usable_size:usable
12241224- in
12251225- let cmp = String.compare key full_payload in
12261226- if cmp = 0 then Some full_payload
12271227- else if cmp < 0 then search lo (mid - 1)
12281228- else search (mid + 1) hi
12291229- in
12301230- search 0 (header.cell_count - 1)
12311231- | Interior_index ->
12321232- let rec find_child_rec i =
12331233- if i >= header.cell_count then Option.get header.right_child
12341234- else
12351235- (* Read full payload for interior comparison *)
12361236- let full_payload =
12371237- read_full_interior_payload t page ptrs.(i) ~usable_size:usable
12381238- in
12391239- if key_less_than key full_payload then
12401240- let cell, _ =
12411241- Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
12421242- in
12431243- cell.left_child
12441244- else find_child_rec (i + 1)
12451245- in
12461246- find_in_page t (find_child_rec 0) key
12471247- | _ -> failwith "Invalid page type in index B-tree"
66+(** SQLite-compatible B-tree implementation.
1248712491249- let find t key = find_in_page t t.root_page key
88+ This library provides read/write access to SQLite B-tree structures,
99+ supporting both table B-trees (for row storage) and index B-trees (for key
1010+ storage).
12501112511251- (* Find a key in leaf page, returns (payload, index) if found *)
12521252- let find_in_leaf t page header key =
12531253- let ptrs = cell_pointers page header in
12541254- let usable = usable_size t in
12551255- let rec search lo hi =
12561256- if lo > hi then None
12571257- else
12581258- let mid = (lo + hi) / 2 in
12591259- let full_payload =
12601260- read_full_payload t page ptrs.(mid) ~usable_size:usable
12611261- in
12621262- let cmp = String.compare key full_payload in
12631263- if cmp = 0 then Some (full_payload, mid)
12641264- else if cmp < 0 then search lo (mid - 1)
12651265- else search (mid + 1) hi
12661266- in
12671267- search 0 (header.cell_count - 1)
1212+ The implementation is split into several modules:
1313+ - {!Varint}: SQLite-style variable-length integer encoding
1414+ - {!Page}: Page types, headers, and binary helpers
1515+ - {!Cell}: Cell types and parsing for all B-tree node types
1616+ - {!Record}: SQLite record format encoding/decoding
1717+ - {!Pager}: Page cache and file I/O
1818+ - {!Table}: Table B-tree for row storage (rowid -> data)
1919+ - {!Index}: Index B-tree for key storage (key sets) *)
12682012691269- (* Find insertion index for key in leaf page *)
12701270- let find_insert_idx t page header key =
12711271- let ptrs = cell_pointers page header in
12721272- let usable = usable_size t in
12731273- let rec find i =
12741274- if i >= header.cell_count then i
12751275- else
12761276- let full_payload =
12771277- read_full_payload t page ptrs.(i) ~usable_size:usable
12781278- in
12791279- if key < full_payload then i else find (i + 1)
12801280- in
12811281- find 0
12821282-12831283- (* Find child page for key in interior page *)
12841284- let find_child t page header key =
12851285- let ptrs = cell_pointers page header in
12861286- let usable = usable_size t in
12871287- let rec loop i =
12881288- if i >= header.cell_count then Option.get header.right_child
12891289- else
12901290- let full_payload =
12911291- read_full_interior_payload t page ptrs.(i) ~usable_size:usable
12921292- in
12931293- if key_less_than key full_payload then
12941294- let cell, _ =
12951295- Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
12961296- in
12971297- cell.left_child
12981298- else loop (i + 1)
12991299- in
13001300- loop 0
13011301-13021302- (* Find child index for key in interior page *)
13031303- let find_child_idx t page header key =
13041304- let ptrs = cell_pointers page header in
13051305- let usable = usable_size t in
13061306- let rec loop i =
13071307- if i >= header.cell_count then i (* right child *)
13081308- else
13091309- let full_payload =
13101310- read_full_interior_payload t page ptrs.(i) ~usable_size:usable
13111311- in
13121312- if key_less_than key full_payload then i else loop (i + 1)
13131313- in
13141314- loop 0
13151315-13161316- (* Split result: new page number and separator key *)
13171317- type split_result = { new_page : int; separator_key : string }
13181318-13191319- (* Split a leaf page, returns info about the new page *)
13201320- let split_leaf t page_num =
13211321- let page = Pager.read t.pager page_num in
13221322- let header = parse_page_header page 0 in
13231323- let ptrs = cell_pointers page header in
13241324- let usable = usable_size t in
13251325- let page_size = Pager.page_size t.pager in
13261326-13271327- (* Find split point (middle) *)
13281328- let split_idx = header.cell_count / 2 in
13291329-13301330- (* Get separator key (first key that goes to right page) - read full payload *)
13311331- let separator_key =
13321332- read_full_payload t page ptrs.(split_idx) ~usable_size:usable
13331333- in
13341334-13351335- (* Create new right page *)
13361336- let new_page_num = Pager.allocate t.pager in
13371337- let new_buf = init_page ~page_size ~page_type:Leaf_index in
13381338-13391339- (* Copy cells [split_idx..cell_count-1] to new page *)
13401340- let new_cell_content_start = ref page_size in
13411341- for i = split_idx to header.cell_count - 1 do
13421342- let cell_off = ptrs.(i) in
13431343- (* Read full payload including from overflow pages *)
13441344- let full_payload =
13451345- read_full_payload t page cell_off ~usable_size:usable
13461346- in
13471347- (* Re-encode with overflow support *)
13481348- let cell_data =
13491349- encode_index_leaf_cell_with_overflow t ~payload:full_payload
13501350- in
13511351- new_cell_content_start :=
13521352- write_cell new_buf ~cell_content_start:!new_cell_content_start
13531353- ~cell:cell_data;
13541354- let new_idx = i - split_idx in
13551355- let ptr_off = page_header_size Leaf_index + (new_idx * 2) in
13561356- set_u16_be new_buf ptr_off !new_cell_content_start
13571357- done;
13581358- let new_cell_count = header.cell_count - split_idx in
13591359- set_u16_be new_buf 3 new_cell_count;
13601360- set_u16_be new_buf 5 !new_cell_content_start;
13611361- Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
13621362-13631363- (* Update original page to only have cells [0..split_idx-1] *)
13641364- let old_buf = Bytes.of_string page in
13651365- set_u16_be old_buf 3 split_idx;
13661366- (* Recalculate cell content start for remaining cells *)
13671367- if split_idx > 0 then begin
13681368- let min_ptr = ref page_size in
13691369- for i = 0 to split_idx - 1 do
13701370- if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
13711371- done;
13721372- set_u16_be old_buf 5 !min_ptr
13731373- end
13741374- else set_u16_be old_buf 5 page_size;
13751375- Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
13761376-13771377- { new_page = new_page_num; separator_key }
13781378-13791379- (* Split an interior page *)
13801380- let split_interior t page_num =
13811381- let page = Pager.read t.pager page_num in
13821382- let header = parse_page_header page 0 in
13831383- let ptrs = cell_pointers page header in
13841384- let usable = usable_size t in
13851385- let page_size = Pager.page_size t.pager in
13861386-13871387- (* Split point - middle cell becomes separator, doesn't go to either page *)
13881388- let split_idx = header.cell_count / 2 in
13891389- let sep_cell, _ =
13901390- Cell.parse_index_interior page ptrs.(split_idx) ~usable_size:usable
13911391- in
13921392- let separator_key = sep_cell.payload in
13931393-13941394- (* Create new right page *)
13951395- let new_page_num = Pager.allocate t.pager in
13961396- let new_buf = init_page ~page_size ~page_type:Interior_index in
13971397-13981398- (* Cells [split_idx+1..cell_count-1] go to new page *)
13991399- let new_cell_content_start = ref page_size in
14001400- for i = split_idx + 1 to header.cell_count - 1 do
14011401- let cell_off = ptrs.(i) in
14021402- let cell, _ =
14031403- Cell.parse_index_interior page cell_off ~usable_size:usable
14041404- in
14051405- let cell_data =
14061406- encode_index_interior_cell ~left_child:cell.left_child
14071407- ~payload:cell.payload
14081408- in
14091409- new_cell_content_start :=
14101410- write_cell new_buf ~cell_content_start:!new_cell_content_start
14111411- ~cell:cell_data;
14121412- let new_idx = i - split_idx - 1 in
14131413- let ptr_off = page_header_size Interior_index + (new_idx * 2) in
14141414- set_u16_be new_buf ptr_off !new_cell_content_start
14151415- done;
14161416- let new_cell_count = header.cell_count - split_idx - 1 in
14171417- set_u16_be new_buf 3 new_cell_count;
14181418- set_u16_be new_buf 5 !new_cell_content_start;
14191419- (* Right child of new page is same as original *)
14201420- set_u32_be new_buf 8 (Option.get header.right_child);
14211421- Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
14221422-14231423- (* Update original page: keep cells [0..split_idx-1], right child = sep_cell.left_child *)
14241424- let old_buf = Bytes.of_string page in
14251425- set_u16_be old_buf 3 split_idx;
14261426- set_u32_be old_buf 8 sep_cell.left_child;
14271427- if split_idx > 0 then begin
14281428- let min_ptr = ref page_size in
14291429- for i = 0 to split_idx - 1 do
14301430- if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
14311431- done;
14321432- set_u16_be old_buf 5 !min_ptr
14331433- end
14341434- else set_u16_be old_buf 5 page_size;
14351435- Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
14361436-14371437- { new_page = new_page_num; separator_key }
14381438-14391439- (* Insert a separator into an interior page, potentially splitting *)
14401440- let rec insert_into_interior t page_num ~left_child ~separator_key
14411441- ~right_child =
14421442- let page = Pager.read t.pager page_num in
14431443- let header = parse_page_header page 0 in
14441444- let cell = encode_index_interior_cell ~left_child ~payload:separator_key in
14451445- let cell_len = String.length cell in
14461446- let space_needed = cell_len + 2 in
14471447- (* cell + pointer *)
14481448-14491449- if free_space header ~page_type:Interior_index >= space_needed then begin
14501450- (* Fits - insert directly *)
14511451- let buf = Bytes.of_string page in
14521452- let ptrs = cell_pointers page header in
14531453- let usable = usable_size t in
14541454-14551455- (* Find insert position *)
14561456- let insert_idx =
14571457- let rec find i =
14581458- if i >= header.cell_count then i
14591459- else
14601460- let c, _ =
14611461- Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
14621462- in
14631463- if separator_key < c.payload then i else find (i + 1)
14641464- in
14651465- find 0
14661466- in
14671467-14681468- (* Write cell *)
14691469- let cell_start =
14701470- write_cell buf ~cell_content_start:header.cell_content_start ~cell
14711471- in
14721472- set_u16_be buf 5 cell_start;
14731473-14741474- (* Insert pointer *)
14751475- insert_cell_pointer buf ~header_offset:0 ~page_type:Interior_index
14761476- ~cell_count:header.cell_count ~index:insert_idx ~ptr:cell_start;
14771477-14781478- (* Update cell count *)
14791479- set_u16_be buf 3 (header.cell_count + 1);
14801480-14811481- (* Update child pointers *)
14821482- if insert_idx < header.cell_count then begin
14831483- let ptr_start = page_header_size Interior_index in
14841484- let displaced_ptr =
14851485- get_u16_be
14861486- (Bytes.unsafe_to_string buf)
14871487- (ptr_start + ((insert_idx + 1) * 2))
14881488- in
14891489- set_u32_be buf displaced_ptr right_child
14901490- end
14911491- else set_u32_be buf 8 right_child;
14921492-14931493- Pager.write t.pager page_num (Bytes.unsafe_to_string buf);
14941494- None
14951495- end
14961496- else begin
14971497- (* Need to split interior page *)
14981498- let split = split_interior t page_num in
14991499-15001500- (* Determine which page gets the new separator *)
15011501- if separator_key < split.separator_key then begin
15021502- ignore
15031503- (insert_into_interior t page_num ~left_child ~separator_key
15041504- ~right_child)
15051505- end
15061506- else begin
15071507- ignore
15081508- (insert_into_interior t split.new_page ~left_child ~separator_key
15091509- ~right_child)
15101510- end;
15111511-15121512- Some { split with separator_key = split.separator_key }
15131513- end
15141514-15151515- (* Insert into a leaf page, potentially splitting *)
15161516- let rec insert_into_leaf t page_num ~key ~parent_stack =
15171517- let page = Pager.read t.pager page_num in
15181518- let header = parse_page_header page 0 in
15191519- let cell = encode_index_leaf_cell_with_overflow t ~payload:key in
15201520- let cell_len = String.length cell in
15211521- let space_needed = cell_len + 2 in
15221522- (* cell + pointer *)
15231523-15241524- if free_space header ~page_type:Leaf_index >= space_needed then begin
15251525- (* Fits - insert directly *)
15261526- let buf = Bytes.of_string page in
15271527- let insert_idx = find_insert_idx t page header key in
15281528-15291529- (* Write cell *)
15301530- let cell_start =
15311531- write_cell buf ~cell_content_start:header.cell_content_start ~cell
15321532- in
15331533- set_u16_be buf 5 cell_start;
15341534-15351535- (* Insert pointer *)
15361536- insert_cell_pointer buf ~header_offset:0 ~page_type:Leaf_index
15371537- ~cell_count:header.cell_count ~index:insert_idx ~ptr:cell_start;
15381538-15391539- (* Update cell count *)
15401540- set_u16_be buf 3 (header.cell_count + 1);
15411541-15421542- Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
15431543- end
15441544- else begin
15451545- (* Need to split *)
15461546- let split = split_leaf t page_num in
15471547-15481548- (* Determine target page and insert *)
15491549- let target_page =
15501550- if key < split.separator_key then page_num else split.new_page
15511551- in
15521552- let target = Pager.read t.pager target_page in
15531553- let target_header = parse_page_header target 0 in
15541554- let target_buf = Bytes.of_string target in
15551555- let insert_idx = find_insert_idx t target target_header key in
15561556- let cell_start =
15571557- write_cell target_buf
15581558- ~cell_content_start:target_header.cell_content_start ~cell
15591559- in
15601560- set_u16_be target_buf 5 cell_start;
15611561- insert_cell_pointer target_buf ~header_offset:0 ~page_type:Leaf_index
15621562- ~cell_count:target_header.cell_count ~index:insert_idx ~ptr:cell_start;
15631563- set_u16_be target_buf 3 (target_header.cell_count + 1);
15641564- Pager.write t.pager target_page (Bytes.unsafe_to_string target_buf);
15651565-15661566- (* Propagate split up *)
15671567- propagate_split t ~parent_stack ~left_page:page_num
15681568- ~separator_key:split.separator_key ~right_page:split.new_page
15691569- end
15701570-15711571- and propagate_split t ~parent_stack ~left_page ~separator_key ~right_page =
15721572- match parent_stack with
15731573- | [] ->
15741574- (* Splitting root - create new root *)
15751575- let page_size = Pager.page_size t.pager in
15761576- let new_root = Pager.allocate t.pager in
15771577- let buf = init_page ~page_size ~page_type:Interior_index in
15781578-15791579- (* Single cell pointing to left page *)
15801580- let cell =
15811581- encode_index_interior_cell ~left_child:left_page
15821582- ~payload:separator_key
15831583- in
15841584- let cell_start = write_cell buf ~cell_content_start:page_size ~cell in
15851585- set_u16_be buf 5 cell_start;
15861586- set_u16_be buf (page_header_size Interior_index) cell_start;
15871587- set_u16_be buf 3 1;
15881588- set_u32_be buf 8 right_page;
15891589-15901590- Pager.write t.pager new_root (Bytes.unsafe_to_string buf);
15911591- t.root_page <- new_root
15921592- | parent_page :: rest -> (
15931593- match
15941594- insert_into_interior t parent_page ~left_child:left_page
15951595- ~separator_key ~right_child:right_page
15961596- with
15971597- | None -> () (* Fit in parent *)
15981598- | Some split ->
15991599- (* Parent also split, propagate up *)
16001600- propagate_split t ~parent_stack:rest ~left_page:parent_page
16011601- ~separator_key:split.separator_key ~right_page:split.new_page)
16021602-16031603- (* Main insert - traverses tree and handles splits *)
16041604- let insert t key =
16051605- let rec traverse page_num parent_stack =
16061606- let page = Pager.read t.pager page_num in
16071607- let header = parse_page_header page 0 in
16081608- match header.page_type with
16091609- | Leaf_index -> (
16101610- (* Check if key already exists *)
16111611- match find_in_leaf t page header key with
16121612- | Some _ -> () (* Key exists, do nothing (set semantics) *)
16131613- | None -> insert_into_leaf t page_num ~key ~parent_stack)
16141614- | Interior_index ->
16151615- let child_idx = find_child_idx t page header key in
16161616- let child_page =
16171617- if child_idx >= header.cell_count then Option.get header.right_child
16181618- else
16191619- let ptrs = cell_pointers page header in
16201620- let usable = usable_size t in
16211621- let cell, _ =
16221622- Cell.parse_index_interior page ptrs.(child_idx)
16231623- ~usable_size:usable
16241624- in
16251625- cell.left_child
16261626- in
16271627- traverse child_page (page_num :: parent_stack)
16281628- | _ -> failwith "Invalid page type in index B-tree"
16291629- in
16301630- traverse t.root_page []
16311631-16321632- (* Delete a cell from a leaf page at given index *)
16331633- let delete_from_leaf t page_num ~index =
16341634- let page = Pager.read t.pager page_num in
16351635- let header = parse_page_header page 0 in
16361636- if header.cell_count = 0 then ()
16371637- else begin
16381638- let buf = Bytes.of_string page in
16391639- let ptr_start = page_header_size Leaf_index in
16401640-16411641- (* Shift pointers left to remove the entry at index *)
16421642- for i = index to header.cell_count - 2 do
16431643- let next_ptr = get_u16_be page (ptr_start + ((i + 1) * 2)) in
16441644- set_u16_be buf (ptr_start + (i * 2)) next_ptr
16451645- done;
16461646-16471647- (* Decrease cell count *)
16481648- set_u16_be buf 3 (header.cell_count - 1);
16491649-16501650- (* Note: We don't reclaim space - it becomes fragmented.
16511651- A proper implementation would compact or track free space. *)
16521652- Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
16531653- end
16541654-16551655- (* Delete implementation - simplified, doesn't rebalance *)
16561656- let delete t key =
16571657- let rec traverse page_num =
16581658- let page = Pager.read t.pager page_num in
16591659- let header = parse_page_header page 0 in
16601660- match header.page_type with
16611661- | Leaf_index -> (
16621662- match find_in_leaf t page header key with
16631663- | Some (_, idx) -> delete_from_leaf t page_num ~index:idx
16641664- | None -> () (* Key not found, nothing to do *))
16651665- | Interior_index ->
16661666- let child = find_child t page header key in
16671667- traverse child
16681668- | _ -> failwith "Invalid page type in index B-tree"
16691669- in
16701670- traverse t.root_page
16711671-16721672- (* Find by prefix - returns first entry starting with prefix *)
16731673- let rec find_by_prefix_in_page t page_num prefix =
16741674- let page = Pager.read t.pager page_num in
16751675- let header = parse_page_header page 0 in
16761676- let ptrs = cell_pointers page header in
16771677- let usable = usable_size t in
16781678- let prefix_len = String.length prefix in
16791679- let starts_with payload =
16801680- String.length payload >= prefix_len
16811681- && String.sub payload 0 prefix_len = prefix
16821682- in
16831683- match header.page_type with
16841684- | Leaf_index ->
16851685- (* Linear search for first entry with prefix *)
16861686- let rec find_first i =
16871687- if i >= header.cell_count then None
16881688- else
16891689- let full_payload =
16901690- read_full_payload t page ptrs.(i) ~usable_size:usable
16911691- in
16921692- if starts_with full_payload then Some full_payload
16931693- else if full_payload > prefix then None
16941694- else find_first (i + 1)
16951695- in
16961696- find_first 0
16971697- | Interior_index ->
16981698- let child = find_child t page header prefix in
16991699- find_by_prefix_in_page t child prefix
17001700- | _ -> failwith "Invalid page type in index B-tree"
17011701-17021702- let find_by_prefix t prefix = find_by_prefix_in_page t t.root_page prefix
17031703-17041704- (* Delete by prefix - deletes first entry starting with prefix *)
17051705- let rec delete_by_prefix_in_page t page_num prefix =
17061706- let page = Pager.read t.pager page_num in
17071707- let header = parse_page_header page 0 in
17081708- let ptrs = cell_pointers page header in
17091709- let usable = usable_size t in
17101710- let prefix_len = String.length prefix in
17111711- let starts_with payload =
17121712- String.length payload >= prefix_len
17131713- && String.sub payload 0 prefix_len = prefix
17141714- in
17151715- match header.page_type with
17161716- | Leaf_index -> (
17171717- (* Find first entry with prefix *)
17181718- let rec find_idx i =
17191719- if i >= header.cell_count then None
17201720- else
17211721- let full_payload =
17221722- read_full_payload t page ptrs.(i) ~usable_size:usable
17231723- in
17241724- if starts_with full_payload then Some i
17251725- else if full_payload > prefix then None
17261726- else find_idx (i + 1)
17271727- in
17281728- match find_idx 0 with
17291729- | Some idx -> delete_from_leaf t page_num ~index:idx
17301730- | None -> ())
17311731- | Interior_index ->
17321732- let child = find_child t page header prefix in
17331733- delete_by_prefix_in_page t child prefix
17341734- | _ -> failwith "Invalid page type in index B-tree"
17351735-17361736- let delete_by_prefix t prefix = delete_by_prefix_in_page t t.root_page prefix
17371737-17381738- let iter t f =
17391739- let rec iter_page page_num =
17401740- let page = Pager.read t.pager page_num in
17411741- let header = parse_page_header page 0 in
17421742- let ptrs = cell_pointers page header in
17431743- let usable = usable_size t in
17441744- match header.page_type with
17451745- | Leaf_index ->
17461746- for i = 0 to header.cell_count - 1 do
17471747- let full_payload =
17481748- read_full_payload t page ptrs.(i) ~usable_size:usable
17491749- in
17501750- f full_payload
17511751- done
17521752- | Interior_index ->
17531753- for i = 0 to header.cell_count - 1 do
17541754- let cell, _ =
17551755- Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
17561756- in
17571757- iter_page cell.left_child
17581758- done;
17591759- Option.iter iter_page header.right_child
17601760- | _ -> failwith "Invalid page type"
17611761- in
17621762- iter_page t.root_page
17631763-end
2121+module Varint = Varint
2222+module Page = Page
2323+module Cell = Cell
2424+module Record = Record
2525+module Pager = Pager
2626+module Table = Table
2727+module Index = Index
+60-23
lib/btree.mli
···33 SPDX-License-Identifier: MIT
44 ---------------------------------------------------------------------------*)
5566-(** Pure OCaml B-tree for persistent storage.
66+(** SQLite-compatible B-tree implementation.
7788- Implements SQLite-compatible B-tree pages for table and index storage. *)
88+ This library provides read/write access to SQLite B-tree structures,
99+ supporting both table B-trees (for row storage) and index B-trees (for key
1010+ storage).
1111+1212+ The implementation is split into several modules:
1313+ - {!Varint}: SQLite-style variable-length integer encoding
1414+ - {!Page}: Page types, headers, and binary helpers
1515+ - {!Cell}: Cell types and parsing for all B-tree node types
1616+ - {!Record}: SQLite record format encoding/decoding
1717+ - {!Pager}: Page cache and file I/O
1818+ - {!Table}: Table B-tree for row storage (rowid -> data)
1919+ - {!Index}: Index B-tree for key storage (key sets) *)
9201021(** {1 Varint Encoding}
1122···2334 (** [size n] returns the number of bytes needed to encode [n]. *)
2435end
25362626-(** {1 Page Types} *)
3737+(** {1 Page Types and Headers} *)
27382828-type page_type =
2929- | Interior_index (** 0x02 *)
3030- | Interior_table (** 0x05 *)
3131- | Leaf_index (** 0x0a *)
3232- | Leaf_table (** 0x0d *)
3939+module Page : sig
4040+ type page_type =
4141+ | Interior_index (** 0x02 *)
4242+ | Interior_table (** 0x05 *)
4343+ | Leaf_index (** 0x0a *)
4444+ | Leaf_table (** 0x0d *)
4545+4646+ val pp_page_type : Format.formatter -> page_type -> unit
4747+4848+ type header = {
4949+ page_type : page_type;
5050+ first_freeblock : int;
5151+ cell_count : int;
5252+ cell_content_start : int;
5353+ fragmented_bytes : int;
5454+ right_child : int option; (** Interior pages only *)
5555+ }
33563434-val pp_page_type : Format.formatter -> page_type -> unit
5757+ val parse_header : string -> int -> header
5858+ (** [parse_header buf off] parses a page header starting at [off]. For page 1,
5959+ [off] should be 100 (after database header). *)
35603636-(** {1 Page Header} *)
6161+ val header_size : page_type -> int
6262+ (** [header_size typ] is 8 for leaf pages, 12 for interior pages. *)
37633838-type page_header = {
3939- page_type : page_type;
4040- first_freeblock : int;
4141- cell_count : int;
4242- cell_content_start : int;
4343- fragmented_bytes : int;
4444- right_child : int option; (** Interior pages only *)
4545-}
6464+ val init : page_size:int -> page_type:page_type -> bytes
6565+ (** [init ~page_size ~page_type] creates a new empty page buffer. *)
46664747-val parse_page_header : string -> int -> page_header
4848-(** [parse_page_header buf off] parses a page header starting at [off]. For page
4949- 1, [off] should be 100 (after database header). *)
6767+ val cell_pointers : string -> int -> header -> int array
6868+ (** [cell_pointers page header_offset header] returns cell pointer array. *)
50695151-val page_header_size : page_type -> int
5252-(** [page_header_size typ] is 8 for leaf pages, 12 for interior pages. *)
7070+ val get_u16_be : string -> int -> int
7171+ val get_u32_be : string -> int -> int
7272+ val set_u16_be : bytes -> int -> int -> unit
7373+ val set_u32_be : bytes -> int -> int -> unit
7474+end
53755476(** {1 Cells}
5577···7698 }
7799 (** Index interior cell: child page + payload *)
78100101101+ val max_local : usable_size:int -> is_table:bool -> int
102102+ val min_local : usable_size:int -> int
103103+79104 val parse_table_leaf : string -> int -> usable_size:int -> table_leaf * int
80105 (** [parse_table_leaf buf off ~usable_size] parses a table leaf cell. Returns
81106 [(cell, bytes_consumed)]. *)
···86111 val parse_index_leaf : string -> int -> usable_size:int -> index_leaf * int
87112 (** [parse_index_leaf buf off ~usable_size] parses an index leaf cell. *)
88113114114+ val parse_index_leaf_raw :
115115+ string -> int -> usable_size:int -> int * string * int option * int
116116+ (** [parse_index_leaf_raw buf off ~usable_size] parses an index leaf cell.
117117+ Returns [(payload_size, local_payload, overflow_page, bytes_consumed)]. *)
118118+89119 val parse_index_interior :
90120 string -> int -> usable_size:int -> index_interior * int
91121 (** [parse_index_interior buf off ~usable_size] parses an index interior cell.
122122+ *)
123123+124124+ val parse_index_interior_raw :
125125+ string -> int -> usable_size:int -> int * int * string * int option * int
126126+ (** [parse_index_interior_raw buf off ~usable_size] parses an index interior
127127+ cell. Returns
128128+ [(left_child, payload_size, local_payload, overflow_page, bytes_consumed)].
92129 *)
93130end
94131
+105
lib/cell.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** B-tree cell parsing and encoding. *)
77+88+type table_leaf = {
99+ rowid : int64;
1010+ payload : string;
1111+ overflow_page : int option;
1212+}
1313+1414+type table_interior = { left_child : int; rowid : int64 }
1515+type index_leaf = { payload : string; overflow_page : int option }
1616+1717+type index_interior = {
1818+ left_child : int;
1919+ payload : string;
2020+ overflow_page : int option;
2121+}
2222+2323+(* Calculate max payload on page - simplified *)
2424+let max_local ~usable_size ~is_table =
2525+ if is_table then usable_size - 35 else ((usable_size - 12) * 64 / 255) - 23
2626+2727+let min_local ~usable_size = ((usable_size - 12) * 32 / 255) - 23
2828+2929+let parse_table_leaf buf off ~usable_size =
3030+ let payload_size, consumed1 = Varint.decode buf off in
3131+ let rowid, consumed2 = Varint.decode buf (off + consumed1) in
3232+ let header_len = consumed1 + consumed2 in
3333+ let payload_size = Int64.to_int payload_size in
3434+ let max_local = max_local ~usable_size ~is_table:true in
3535+ let min_local = min_local ~usable_size in
3636+ let local_size, overflow_page =
3737+ if payload_size <= max_local then (payload_size, None)
3838+ else
3939+ let k = min_local + ((payload_size - min_local) mod (usable_size - 4)) in
4040+ let local = if k <= max_local then k else min_local in
4141+ let overflow = Page.get_u32_be buf (off + header_len + local) in
4242+ (local, Some overflow)
4343+ in
4444+ let payload = String.sub buf (off + header_len) local_size in
4545+ let total_consumed =
4646+ header_len + local_size + if overflow_page = None then 0 else 4
4747+ in
4848+ ({ rowid; payload; overflow_page }, total_consumed)
4949+5050+let parse_table_interior buf off =
5151+ let left_child = Page.get_u32_be buf off in
5252+ let rowid, consumed = Varint.decode buf (off + 4) in
5353+ ({ left_child; rowid }, 4 + consumed)
5454+5555+(* Parse index leaf cell - returns local payload and overflow pointer *)
5656+let parse_index_leaf_raw buf off ~usable_size =
5757+ let payload_size, consumed = Varint.decode buf off in
5858+ let payload_size = Int64.to_int payload_size in
5959+ let max_local = max_local ~usable_size ~is_table:false in
6060+ let min_local = min_local ~usable_size in
6161+ let local_size, overflow_page =
6262+ if payload_size <= max_local then (payload_size, None)
6363+ else
6464+ let k = min_local + ((payload_size - min_local) mod (usable_size - 4)) in
6565+ let local = if k <= max_local then k else min_local in
6666+ let overflow = Page.get_u32_be buf (off + consumed + local) in
6767+ (local, Some overflow)
6868+ in
6969+ let local_payload = String.sub buf (off + consumed) local_size in
7070+ let total = consumed + local_size + if overflow_page = None then 0 else 4 in
7171+ (payload_size, local_payload, overflow_page, total)
7272+7373+let parse_index_leaf buf off ~usable_size =
7474+ let payload_size, local_payload, overflow_page, total =
7575+ parse_index_leaf_raw buf off ~usable_size
7676+ in
7777+ ignore payload_size;
7878+ ({ payload = local_payload; overflow_page }, total)
7979+8080+(* Parse index interior cell - returns local payload and overflow pointer *)
8181+let parse_index_interior_raw buf off ~usable_size =
8282+ let left_child = Page.get_u32_be buf off in
8383+ let payload_size, consumed = Varint.decode buf (off + 4) in
8484+ let payload_size = Int64.to_int payload_size in
8585+ let max_local = max_local ~usable_size ~is_table:false in
8686+ let min_local = min_local ~usable_size in
8787+ let local_size, overflow_page =
8888+ if payload_size <= max_local then (payload_size, None)
8989+ else
9090+ let k = min_local + ((payload_size - min_local) mod (usable_size - 4)) in
9191+ let local = if k <= max_local then k else min_local in
9292+ let overflow = Page.get_u32_be buf (off + 4 + consumed + local) in
9393+ (local, Some overflow)
9494+ in
9595+ let local_payload = String.sub buf (off + 4 + consumed) local_size in
9696+ let total =
9797+ 4 + consumed + local_size + if overflow_page = None then 0 else 4
9898+ in
9999+ (left_child, payload_size, local_payload, overflow_page, total)
100100+101101+let parse_index_interior buf off ~usable_size =
102102+ let left_child, _payload_size, local_payload, overflow_page, total =
103103+ parse_index_interior_raw buf off ~usable_size
104104+ in
105105+ ({ left_child; payload = local_payload; overflow_page }, total)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** Index B-tree for SQLite key storage. *)
77+88+type t = { pager : Pager.t; mutable root_page : int }
99+1010+let create pager =
1111+ let root = Pager.allocate pager in
1212+ let page_size = Pager.page_size pager in
1313+ let buf = Page.init ~page_size ~page_type:Page.Leaf_index in
1414+ Pager.write pager root (Bytes.unsafe_to_string buf);
1515+ { pager; root_page = root }
1616+1717+let open_ pager ~root_page = { pager; root_page }
1818+let root_page t = t.root_page
1919+let usable_size t = Pager.page_size t.pager
2020+2121+(* Overflow page handling *)
2222+let read_overflow_chain pager first_page ~remaining_size =
2323+ let usable = Pager.page_size pager in
2424+ let overflow_content_size = usable - 4 in
2525+ let buf = Buffer.create remaining_size in
2626+ let rec read page_num remaining =
2727+ if remaining <= 0 || page_num = 0 then ()
2828+ else begin
2929+ let page = Pager.read pager page_num in
3030+ let next_page = Page.get_u32_be page 0 in
3131+ let to_read = min remaining overflow_content_size in
3232+ Buffer.add_substring buf page 4 to_read;
3333+ read next_page (remaining - to_read)
3434+ end
3535+ in
3636+ read first_page remaining_size;
3737+ Buffer.contents buf
3838+3939+let write_overflow_chain pager payload ~offset =
4040+ let usable = Pager.page_size pager in
4141+ let overflow_content_size = usable - 4 in
4242+ let remaining = String.length payload - offset in
4343+ if remaining <= 0 then 0
4444+ else begin
4545+ let first_page = ref 0 in
4646+ let prev_page_buf = ref None in
4747+ let prev_page_num = ref 0 in
4848+ let pos = ref offset in
4949+ while !pos < String.length payload do
5050+ let page_num = Pager.allocate pager in
5151+ if !first_page = 0 then first_page := page_num;
5252+ (* Link previous page to this one *)
5353+ (match !prev_page_buf with
5454+ | Some buf ->
5555+ Page.set_u32_be buf 0 page_num;
5656+ Pager.write pager !prev_page_num (Bytes.unsafe_to_string buf)
5757+ | None -> ());
5858+ (* Write this page *)
5959+ let page_buf = Bytes.create usable in
6060+ Page.set_u32_be page_buf 0 0;
6161+ (* Next page = 0 for now *)
6262+ let to_write = min (String.length payload - !pos) overflow_content_size in
6363+ Bytes.blit_string payload !pos page_buf 4 to_write;
6464+ prev_page_buf := Some page_buf;
6565+ prev_page_num := page_num;
6666+ pos := !pos + to_write
6767+ done;
6868+ (* Write final page *)
6969+ (match !prev_page_buf with
7070+ | Some buf -> Pager.write pager !prev_page_num (Bytes.unsafe_to_string buf)
7171+ | None -> ());
7272+ !first_page
7373+ end
7474+7575+(* Read full payload including overflow pages for leaf cells *)
7676+let read_full_payload t page off ~usable_size =
7777+ let payload_size, local_payload, overflow_page, _consumed =
7878+ Cell.parse_index_leaf_raw page off ~usable_size
7979+ in
8080+ match overflow_page with
8181+ | None -> local_payload
8282+ | Some first_overflow ->
8383+ let remaining = payload_size - String.length local_payload in
8484+ let overflow_data =
8585+ read_overflow_chain t.pager first_overflow ~remaining_size:remaining
8686+ in
8787+ local_payload ^ overflow_data
8888+8989+(* Read full payload including overflow pages for interior cells *)
9090+let read_full_interior_payload t page off ~usable_size =
9191+ let _left_child, payload_size, local_payload, overflow_page, _consumed =
9292+ Cell.parse_index_interior_raw page off ~usable_size
9393+ in
9494+ match overflow_page with
9595+ | None -> local_payload
9696+ | Some first_overflow ->
9797+ let remaining = payload_size - String.length local_payload in
9898+ let overflow_data =
9999+ read_overflow_chain t.pager first_overflow ~remaining_size:remaining
100100+ in
101101+ local_payload ^ overflow_data
102102+103103+let cell_pointers page header =
104104+ let ptrs = Array.make header.Page.cell_count 0 in
105105+ let ptr_start = Page.header_size header.Page.page_type in
106106+ for i = 0 to header.Page.cell_count - 1 do
107107+ ptrs.(i) <- Page.get_u16_be page (ptr_start + (i * 2))
108108+ done;
109109+ ptrs
110110+111111+(* Calculate free space in a page *)
112112+let free_space header ~page_type =
113113+ let header_size = Page.header_size page_type in
114114+ let ptr_area_end = header_size + (header.Page.cell_count * 2) in
115115+ header.Page.cell_content_start - ptr_area_end - header.Page.fragmented_bytes
116116+117117+(* Encode an index leaf cell - handles overflow for large payloads *)
118118+let encode_index_leaf_cell_with_overflow t ~payload =
119119+ let payload_size = String.length payload in
120120+ let usable_size = usable_size t in
121121+ let max_local = Cell.max_local ~usable_size ~is_table:false in
122122+ let min_local = Cell.min_local ~usable_size in
123123+ let payload_size_varint = Varint.encode (Int64.of_int payload_size) in
124124+ let varint_len = String.length payload_size_varint in
125125+ if payload_size <= max_local then begin
126126+ (* Fits entirely - no overflow needed *)
127127+ let cell = Bytes.create (varint_len + payload_size) in
128128+ Bytes.blit_string payload_size_varint 0 cell 0 varint_len;
129129+ Bytes.blit_string payload 0 cell varint_len payload_size;
130130+ Bytes.unsafe_to_string cell
131131+ end
132132+ else begin
133133+ (* Need overflow pages *)
134134+ let k = min_local + ((payload_size - min_local) mod (usable_size - 4)) in
135135+ let local_size = if k <= max_local then k else min_local in
136136+ (* Write overflow pages for data beyond local_size *)
137137+ let overflow_page =
138138+ write_overflow_chain t.pager payload ~offset:local_size
139139+ in
140140+ (* Create cell: varint(size) + local_payload + overflow_ptr *)
141141+ let cell = Bytes.create (varint_len + local_size + 4) in
142142+ Bytes.blit_string payload_size_varint 0 cell 0 varint_len;
143143+ Bytes.blit_string payload 0 cell varint_len local_size;
144144+ Page.set_u32_be cell (varint_len + local_size) overflow_page;
145145+ Bytes.unsafe_to_string cell
146146+ end
147147+148148+(* Encode an index interior cell *)
149149+let encode_index_interior_cell ~left_child ~payload =
150150+ let payload_size_varint =
151151+ Varint.encode (Int64.of_int (String.length payload))
152152+ in
153153+ let cell =
154154+ Bytes.create (4 + String.length payload_size_varint + String.length payload)
155155+ in
156156+ Page.set_u32_be cell 0 left_child;
157157+ Bytes.blit_string payload_size_varint 0 cell 4
158158+ (String.length payload_size_varint);
159159+ Bytes.blit_string payload 0 cell
160160+ (4 + String.length payload_size_varint)
161161+ (String.length payload);
162162+ Bytes.unsafe_to_string cell
163163+164164+(* Write a cell into a page buffer, returns new cell_content_start *)
165165+let write_cell buf ~cell_content_start ~cell =
166166+ let cell_len = String.length cell in
167167+ let new_start = cell_content_start - cell_len in
168168+ Bytes.blit_string cell 0 buf new_start cell_len;
169169+ new_start
170170+171171+(* Insert a cell pointer at index, shifting others *)
172172+let insert_cell_pointer buf ~header_offset ~page_type ~cell_count ~index ~ptr =
173173+ let ptr_start = header_offset + Page.header_size page_type in
174174+ (* Shift existing pointers right *)
175175+ for i = cell_count - 1 downto index do
176176+ let old_ptr =
177177+ Page.get_u16_be (Bytes.unsafe_to_string buf) (ptr_start + (i * 2))
178178+ in
179179+ Page.set_u16_be buf (ptr_start + ((i + 1) * 2)) old_ptr
180180+ done;
181181+ Page.set_u16_be buf (ptr_start + (index * 2)) ptr
182182+183183+(* Compare for btree navigation: key is strictly less than payload.
184184+ Special case: if key is a prefix of payload, they are equal for navigation purposes
185185+ (i.e., entries starting with key might be at or after payload's position). *)
186186+let key_less_than key payload =
187187+ let key_len = String.length key in
188188+ let payload_len = String.length payload in
189189+ let cmp_len = min key_len payload_len in
190190+ let cmp =
191191+ String.compare (String.sub key 0 cmp_len) (String.sub payload 0 cmp_len)
192192+ in
193193+ if cmp <> 0 then cmp < 0
194194+ else
195195+ (* Prefixes match - if key is shorter or equal length, it's NOT strictly less *)
196196+ false
197197+198198+let rec mem_in_page t page_num key =
199199+ let page = Pager.read t.pager page_num in
200200+ let header = Page.parse_header page 0 in
201201+ let ptrs = cell_pointers page header in
202202+ let usable = usable_size t in
203203+ match header.Page.page_type with
204204+ | Page.Leaf_index ->
205205+ let rec search lo hi =
206206+ if lo > hi then false
207207+ else
208208+ let mid = (lo + hi) / 2 in
209209+ let cell, _ =
210210+ Cell.parse_index_leaf page ptrs.(mid) ~usable_size:usable
211211+ in
212212+ let cmp = String.compare key cell.Cell.payload in
213213+ if cmp = 0 then true
214214+ else if cmp < 0 then search lo (mid - 1)
215215+ else search (mid + 1) hi
216216+ in
217217+ search 0 (header.Page.cell_count - 1)
218218+ | Page.Interior_index ->
219219+ let rec find_child i =
220220+ if i >= header.Page.cell_count then Option.get header.Page.right_child
221221+ else
222222+ let cell, _ =
223223+ Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
224224+ in
225225+ if key_less_than key cell.Cell.payload then cell.Cell.left_child
226226+ else find_child (i + 1)
227227+ in
228228+ mem_in_page t (find_child 0) key
229229+ | _ -> failwith "Invalid page type in index B-tree"
230230+231231+let mem t key = mem_in_page t t.root_page key
232232+233233+(* Find exact key, returns payload *)
234234+let rec find_in_page t page_num key =
235235+ let page = Pager.read t.pager page_num in
236236+ let header = Page.parse_header page 0 in
237237+ let ptrs = cell_pointers page header in
238238+ let usable = usable_size t in
239239+ match header.Page.page_type with
240240+ | Page.Leaf_index ->
241241+ let rec search lo hi =
242242+ if lo > hi then None
243243+ else
244244+ let mid = (lo + hi) / 2 in
245245+ (* Read full payload including overflow *)
246246+ let full_payload =
247247+ read_full_payload t page ptrs.(mid) ~usable_size:usable
248248+ in
249249+ let cmp = String.compare key full_payload in
250250+ if cmp = 0 then Some full_payload
251251+ else if cmp < 0 then search lo (mid - 1)
252252+ else search (mid + 1) hi
253253+ in
254254+ search 0 (header.Page.cell_count - 1)
255255+ | Page.Interior_index ->
256256+ let rec find_child_rec i =
257257+ if i >= header.Page.cell_count then Option.get header.Page.right_child
258258+ else
259259+ (* Read full payload for interior comparison *)
260260+ let full_payload =
261261+ read_full_interior_payload t page ptrs.(i) ~usable_size:usable
262262+ in
263263+ if key_less_than key full_payload then
264264+ let cell, _ =
265265+ Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
266266+ in
267267+ cell.Cell.left_child
268268+ else find_child_rec (i + 1)
269269+ in
270270+ find_in_page t (find_child_rec 0) key
271271+ | _ -> failwith "Invalid page type in index B-tree"
272272+273273+let find t key = find_in_page t t.root_page key
274274+275275+(* Find a key in leaf page, returns (payload, index) if found *)
276276+let find_in_leaf t page header key =
277277+ let ptrs = cell_pointers page header in
278278+ let usable = usable_size t in
279279+ let rec search lo hi =
280280+ if lo > hi then None
281281+ else
282282+ let mid = (lo + hi) / 2 in
283283+ let full_payload =
284284+ read_full_payload t page ptrs.(mid) ~usable_size:usable
285285+ in
286286+ let cmp = String.compare key full_payload in
287287+ if cmp = 0 then Some (full_payload, mid)
288288+ else if cmp < 0 then search lo (mid - 1)
289289+ else search (mid + 1) hi
290290+ in
291291+ search 0 (header.Page.cell_count - 1)
292292+293293+(* Find insertion index for key in leaf page *)
294294+let find_insert_idx t page header key =
295295+ let ptrs = cell_pointers page header in
296296+ let usable = usable_size t in
297297+ let rec find i =
298298+ if i >= header.Page.cell_count then i
299299+ else
300300+ let full_payload =
301301+ read_full_payload t page ptrs.(i) ~usable_size:usable
302302+ in
303303+ if key < full_payload then i else find (i + 1)
304304+ in
305305+ find 0
306306+307307+(* Find child page for key in interior page *)
308308+let find_child t page header key =
309309+ let ptrs = cell_pointers page header in
310310+ let usable = usable_size t in
311311+ let rec loop i =
312312+ if i >= header.Page.cell_count then Option.get header.Page.right_child
313313+ else
314314+ let full_payload =
315315+ read_full_interior_payload t page ptrs.(i) ~usable_size:usable
316316+ in
317317+ if key_less_than key full_payload then
318318+ let cell, _ =
319319+ Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
320320+ in
321321+ cell.Cell.left_child
322322+ else loop (i + 1)
323323+ in
324324+ loop 0
325325+326326+(* Find child index for key in interior page *)
327327+let find_child_idx t page header key =
328328+ let ptrs = cell_pointers page header in
329329+ let usable = usable_size t in
330330+ let rec loop i =
331331+ if i >= header.Page.cell_count then i (* right child *)
332332+ else
333333+ let full_payload =
334334+ read_full_interior_payload t page ptrs.(i) ~usable_size:usable
335335+ in
336336+ if key_less_than key full_payload then i else loop (i + 1)
337337+ in
338338+ loop 0
339339+340340+(* Split result: new page number and separator key *)
341341+type split_result = { new_page : int; separator_key : string }
342342+343343+(* Split a leaf page, returns info about the new page *)
344344+let split_leaf t page_num =
345345+ let page = Pager.read t.pager page_num in
346346+ let header = Page.parse_header page 0 in
347347+ let ptrs = cell_pointers page header in
348348+ let usable = usable_size t in
349349+ let page_size = Pager.page_size t.pager in
350350+351351+ (* Find split point (middle) *)
352352+ let split_idx = header.Page.cell_count / 2 in
353353+354354+ (* Get separator key (first key that goes to right page) - read full payload *)
355355+ let separator_key =
356356+ read_full_payload t page ptrs.(split_idx) ~usable_size:usable
357357+ in
358358+359359+ (* Create new right page *)
360360+ let new_page_num = Pager.allocate t.pager in
361361+ let new_buf = Page.init ~page_size ~page_type:Page.Leaf_index in
362362+363363+ (* Copy cells [split_idx..cell_count-1] to new page *)
364364+ let new_cell_content_start = ref page_size in
365365+ for i = split_idx to header.Page.cell_count - 1 do
366366+ let cell_off = ptrs.(i) in
367367+ (* Read full payload including from overflow pages *)
368368+ let full_payload = read_full_payload t page cell_off ~usable_size:usable in
369369+ (* Re-encode with overflow support *)
370370+ let cell_data =
371371+ encode_index_leaf_cell_with_overflow t ~payload:full_payload
372372+ in
373373+ new_cell_content_start :=
374374+ write_cell new_buf ~cell_content_start:!new_cell_content_start
375375+ ~cell:cell_data;
376376+ let new_idx = i - split_idx in
377377+ let ptr_off = Page.header_size Page.Leaf_index + (new_idx * 2) in
378378+ Page.set_u16_be new_buf ptr_off !new_cell_content_start
379379+ done;
380380+ let new_cell_count = header.Page.cell_count - split_idx in
381381+ Page.set_u16_be new_buf 3 new_cell_count;
382382+ Page.set_u16_be new_buf 5 !new_cell_content_start;
383383+ Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
384384+385385+ (* Update original page to only have cells [0..split_idx-1] *)
386386+ let old_buf = Bytes.of_string page in
387387+ Page.set_u16_be old_buf 3 split_idx;
388388+ (* Recalculate cell content start for remaining cells *)
389389+ if split_idx > 0 then begin
390390+ let min_ptr = ref page_size in
391391+ for i = 0 to split_idx - 1 do
392392+ if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
393393+ done;
394394+ Page.set_u16_be old_buf 5 !min_ptr
395395+ end
396396+ else Page.set_u16_be old_buf 5 page_size;
397397+ Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
398398+399399+ { new_page = new_page_num; separator_key }
400400+401401+(* Split an interior page *)
402402+let split_interior t page_num =
403403+ let page = Pager.read t.pager page_num in
404404+ let header = Page.parse_header page 0 in
405405+ let ptrs = cell_pointers page header in
406406+ let usable = usable_size t in
407407+ let page_size = Pager.page_size t.pager in
408408+409409+ (* Split point - middle cell becomes separator, doesn't go to either page *)
410410+ let split_idx = header.Page.cell_count / 2 in
411411+ let sep_cell, _ =
412412+ Cell.parse_index_interior page ptrs.(split_idx) ~usable_size:usable
413413+ in
414414+ let separator_key = sep_cell.Cell.payload in
415415+416416+ (* Create new right page *)
417417+ let new_page_num = Pager.allocate t.pager in
418418+ let new_buf = Page.init ~page_size ~page_type:Page.Interior_index in
419419+420420+ (* Cells [split_idx+1..cell_count-1] go to new page *)
421421+ let new_cell_content_start = ref page_size in
422422+ for i = split_idx + 1 to header.Page.cell_count - 1 do
423423+ let cell_off = ptrs.(i) in
424424+ let cell, _ = Cell.parse_index_interior page cell_off ~usable_size:usable in
425425+ let cell_data =
426426+ encode_index_interior_cell ~left_child:cell.Cell.left_child
427427+ ~payload:cell.Cell.payload
428428+ in
429429+ new_cell_content_start :=
430430+ write_cell new_buf ~cell_content_start:!new_cell_content_start
431431+ ~cell:cell_data;
432432+ let new_idx = i - split_idx - 1 in
433433+ let ptr_off = Page.header_size Page.Interior_index + (new_idx * 2) in
434434+ Page.set_u16_be new_buf ptr_off !new_cell_content_start
435435+ done;
436436+ let new_cell_count = header.Page.cell_count - split_idx - 1 in
437437+ Page.set_u16_be new_buf 3 new_cell_count;
438438+ Page.set_u16_be new_buf 5 !new_cell_content_start;
439439+ (* Right child of new page is same as original *)
440440+ Page.set_u32_be new_buf 8 (Option.get header.Page.right_child);
441441+ Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
442442+443443+ (* Update original page: keep cells [0..split_idx-1], right child = sep_cell.left_child *)
444444+ let old_buf = Bytes.of_string page in
445445+ Page.set_u16_be old_buf 3 split_idx;
446446+ Page.set_u32_be old_buf 8 sep_cell.Cell.left_child;
447447+ if split_idx > 0 then begin
448448+ let min_ptr = ref page_size in
449449+ for i = 0 to split_idx - 1 do
450450+ if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
451451+ done;
452452+ Page.set_u16_be old_buf 5 !min_ptr
453453+ end
454454+ else Page.set_u16_be old_buf 5 page_size;
455455+ Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
456456+457457+ { new_page = new_page_num; separator_key }
458458+459459+(* Insert a separator into an interior page, potentially splitting *)
460460+let rec insert_into_interior t page_num ~left_child ~separator_key ~right_child
461461+ =
462462+ let page = Pager.read t.pager page_num in
463463+ let header = Page.parse_header page 0 in
464464+ let cell = encode_index_interior_cell ~left_child ~payload:separator_key in
465465+ let cell_len = String.length cell in
466466+ let space_needed = cell_len + 2 in
467467+ (* cell + pointer *)
468468+469469+ if free_space header ~page_type:Page.Interior_index >= space_needed then begin
470470+ (* Fits - insert directly *)
471471+ let buf = Bytes.of_string page in
472472+ let ptrs = cell_pointers page header in
473473+ let usable = usable_size t in
474474+475475+ (* Find insert position *)
476476+ let insert_idx =
477477+ let rec find i =
478478+ if i >= header.Page.cell_count then i
479479+ else
480480+ let c, _ =
481481+ Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
482482+ in
483483+ if separator_key < c.Cell.payload then i else find (i + 1)
484484+ in
485485+ find 0
486486+ in
487487+488488+ (* Write cell *)
489489+ let cell_start =
490490+ write_cell buf ~cell_content_start:header.Page.cell_content_start ~cell
491491+ in
492492+ Page.set_u16_be buf 5 cell_start;
493493+494494+ (* Insert pointer *)
495495+ insert_cell_pointer buf ~header_offset:0 ~page_type:Page.Interior_index
496496+ ~cell_count:header.Page.cell_count ~index:insert_idx ~ptr:cell_start;
497497+498498+ (* Update cell count *)
499499+ Page.set_u16_be buf 3 (header.Page.cell_count + 1);
500500+501501+ (* Update child pointers *)
502502+ if insert_idx < header.Page.cell_count then begin
503503+ let ptr_start = Page.header_size Page.Interior_index in
504504+ let displaced_ptr =
505505+ Page.get_u16_be
506506+ (Bytes.unsafe_to_string buf)
507507+ (ptr_start + ((insert_idx + 1) * 2))
508508+ in
509509+ Page.set_u32_be buf displaced_ptr right_child
510510+ end
511511+ else Page.set_u32_be buf 8 right_child;
512512+513513+ Pager.write t.pager page_num (Bytes.unsafe_to_string buf);
514514+ None
515515+ end
516516+ else begin
517517+ (* Need to split interior page *)
518518+ let split = split_interior t page_num in
519519+520520+ (* Determine which page gets the new separator *)
521521+ if separator_key < split.separator_key then begin
522522+ ignore
523523+ (insert_into_interior t page_num ~left_child ~separator_key ~right_child)
524524+ end
525525+ else begin
526526+ ignore
527527+ (insert_into_interior t split.new_page ~left_child ~separator_key
528528+ ~right_child)
529529+ end;
530530+531531+ Some { split with separator_key = split.separator_key }
532532+ end
533533+534534+(* Insert into a leaf page, potentially splitting *)
535535+let rec insert_into_leaf t page_num ~key ~parent_stack =
536536+ let page = Pager.read t.pager page_num in
537537+ let header = Page.parse_header page 0 in
538538+ let cell = encode_index_leaf_cell_with_overflow t ~payload:key in
539539+ let cell_len = String.length cell in
540540+ let space_needed = cell_len + 2 in
541541+ (* cell + pointer *)
542542+543543+ if free_space header ~page_type:Page.Leaf_index >= space_needed then begin
544544+ (* Fits - insert directly *)
545545+ let buf = Bytes.of_string page in
546546+ let insert_idx = find_insert_idx t page header key in
547547+548548+ (* Write cell *)
549549+ let cell_start =
550550+ write_cell buf ~cell_content_start:header.Page.cell_content_start ~cell
551551+ in
552552+ Page.set_u16_be buf 5 cell_start;
553553+554554+ (* Insert pointer *)
555555+ insert_cell_pointer buf ~header_offset:0 ~page_type:Page.Leaf_index
556556+ ~cell_count:header.Page.cell_count ~index:insert_idx ~ptr:cell_start;
557557+558558+ (* Update cell count *)
559559+ Page.set_u16_be buf 3 (header.Page.cell_count + 1);
560560+561561+ Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
562562+ end
563563+ else begin
564564+ (* Need to split *)
565565+ let split = split_leaf t page_num in
566566+567567+ (* Determine target page and insert *)
568568+ let target_page =
569569+ if key < split.separator_key then page_num else split.new_page
570570+ in
571571+ let target = Pager.read t.pager target_page in
572572+ let target_header = Page.parse_header target 0 in
573573+ let target_buf = Bytes.of_string target in
574574+ let insert_idx = find_insert_idx t target target_header key in
575575+ let cell_start =
576576+ write_cell target_buf
577577+ ~cell_content_start:target_header.Page.cell_content_start ~cell
578578+ in
579579+ Page.set_u16_be target_buf 5 cell_start;
580580+ insert_cell_pointer target_buf ~header_offset:0 ~page_type:Page.Leaf_index
581581+ ~cell_count:target_header.Page.cell_count ~index:insert_idx
582582+ ~ptr:cell_start;
583583+ Page.set_u16_be target_buf 3 (target_header.Page.cell_count + 1);
584584+ Pager.write t.pager target_page (Bytes.unsafe_to_string target_buf);
585585+586586+ (* Propagate split up *)
587587+ propagate_split t ~parent_stack ~left_page:page_num
588588+ ~separator_key:split.separator_key ~right_page:split.new_page
589589+ end
590590+591591+and propagate_split t ~parent_stack ~left_page ~separator_key ~right_page =
592592+ match parent_stack with
593593+ | [] ->
594594+ (* Splitting root - create new root *)
595595+ let page_size = Pager.page_size t.pager in
596596+ let new_root = Pager.allocate t.pager in
597597+ let buf = Page.init ~page_size ~page_type:Page.Interior_index in
598598+599599+ (* Single cell pointing to left page *)
600600+ let cell =
601601+ encode_index_interior_cell ~left_child:left_page ~payload:separator_key
602602+ in
603603+ let cell_start = write_cell buf ~cell_content_start:page_size ~cell in
604604+ Page.set_u16_be buf 5 cell_start;
605605+ Page.set_u16_be buf (Page.header_size Page.Interior_index) cell_start;
606606+ Page.set_u16_be buf 3 1;
607607+ Page.set_u32_be buf 8 right_page;
608608+609609+ Pager.write t.pager new_root (Bytes.unsafe_to_string buf);
610610+ t.root_page <- new_root
611611+ | parent_page :: rest -> (
612612+ match
613613+ insert_into_interior t parent_page ~left_child:left_page ~separator_key
614614+ ~right_child:right_page
615615+ with
616616+ | None -> () (* Fit in parent *)
617617+ | Some split ->
618618+ (* Parent also split, propagate up *)
619619+ propagate_split t ~parent_stack:rest ~left_page:parent_page
620620+ ~separator_key:split.separator_key ~right_page:split.new_page)
621621+622622+(* Main insert - traverses tree and handles splits *)
623623+let insert t key =
624624+ let rec traverse page_num parent_stack =
625625+ let page = Pager.read t.pager page_num in
626626+ let header = Page.parse_header page 0 in
627627+ match header.Page.page_type with
628628+ | Page.Leaf_index -> (
629629+ (* Check if key already exists *)
630630+ match find_in_leaf t page header key with
631631+ | Some _ -> () (* Key exists, do nothing (set semantics) *)
632632+ | None -> insert_into_leaf t page_num ~key ~parent_stack)
633633+ | Page.Interior_index ->
634634+ let child_idx = find_child_idx t page header key in
635635+ let child_page =
636636+ if child_idx >= header.Page.cell_count then
637637+ Option.get header.Page.right_child
638638+ else
639639+ let ptrs = cell_pointers page header in
640640+ let usable = usable_size t in
641641+ let cell, _ =
642642+ Cell.parse_index_interior page ptrs.(child_idx)
643643+ ~usable_size:usable
644644+ in
645645+ cell.Cell.left_child
646646+ in
647647+ traverse child_page (page_num :: parent_stack)
648648+ | _ -> failwith "Invalid page type in index B-tree"
649649+ in
650650+ traverse t.root_page []
651651+652652+(* Delete a cell from a leaf page at given index *)
653653+let delete_from_leaf t page_num ~index =
654654+ let page = Pager.read t.pager page_num in
655655+ let header = Page.parse_header page 0 in
656656+ if header.Page.cell_count = 0 then ()
657657+ else begin
658658+ let buf = Bytes.of_string page in
659659+ let ptr_start = Page.header_size Page.Leaf_index in
660660+661661+ (* Shift pointers left to remove the entry at index *)
662662+ for i = index to header.Page.cell_count - 2 do
663663+ let next_ptr = Page.get_u16_be page (ptr_start + ((i + 1) * 2)) in
664664+ Page.set_u16_be buf (ptr_start + (i * 2)) next_ptr
665665+ done;
666666+667667+ (* Decrease cell count *)
668668+ Page.set_u16_be buf 3 (header.Page.cell_count - 1);
669669+670670+ (* Note: We don't reclaim space - it becomes fragmented.
671671+ A proper implementation would compact or track free space. *)
672672+ Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
673673+ end
674674+675675+(* Delete implementation - simplified, doesn't rebalance *)
676676+let delete t key =
677677+ let rec traverse page_num =
678678+ let page = Pager.read t.pager page_num in
679679+ let header = Page.parse_header page 0 in
680680+ match header.Page.page_type with
681681+ | Page.Leaf_index -> (
682682+ match find_in_leaf t page header key with
683683+ | Some (_, idx) -> delete_from_leaf t page_num ~index:idx
684684+ | None -> () (* Key not found, nothing to do *))
685685+ | Page.Interior_index ->
686686+ let child = find_child t page header key in
687687+ traverse child
688688+ | _ -> failwith "Invalid page type in index B-tree"
689689+ in
690690+ traverse t.root_page
691691+692692+(* Find by prefix - returns first entry starting with prefix *)
693693+let rec find_by_prefix_in_page t page_num prefix =
694694+ let page = Pager.read t.pager page_num in
695695+ let header = Page.parse_header page 0 in
696696+ let ptrs = cell_pointers page header in
697697+ let usable = usable_size t in
698698+ let prefix_len = String.length prefix in
699699+ let starts_with payload =
700700+ String.length payload >= prefix_len
701701+ && String.sub payload 0 prefix_len = prefix
702702+ in
703703+ match header.Page.page_type with
704704+ | Page.Leaf_index ->
705705+ (* Linear search for first entry with prefix *)
706706+ let rec find_first i =
707707+ if i >= header.Page.cell_count then None
708708+ else
709709+ let full_payload =
710710+ read_full_payload t page ptrs.(i) ~usable_size:usable
711711+ in
712712+ if starts_with full_payload then Some full_payload
713713+ else if full_payload > prefix then None
714714+ else find_first (i + 1)
715715+ in
716716+ find_first 0
717717+ | Page.Interior_index ->
718718+ let child = find_child t page header prefix in
719719+ find_by_prefix_in_page t child prefix
720720+ | _ -> failwith "Invalid page type in index B-tree"
721721+722722+let find_by_prefix t prefix = find_by_prefix_in_page t t.root_page prefix
723723+724724+(* Delete by prefix - deletes first entry starting with prefix *)
725725+let rec delete_by_prefix_in_page t page_num prefix =
726726+ let page = Pager.read t.pager page_num in
727727+ let header = Page.parse_header page 0 in
728728+ let ptrs = cell_pointers page header in
729729+ let usable = usable_size t in
730730+ let prefix_len = String.length prefix in
731731+ let starts_with payload =
732732+ String.length payload >= prefix_len
733733+ && String.sub payload 0 prefix_len = prefix
734734+ in
735735+ match header.Page.page_type with
736736+ | Page.Leaf_index -> (
737737+ (* Find first entry with prefix *)
738738+ let rec find_idx i =
739739+ if i >= header.Page.cell_count then None
740740+ else
741741+ let full_payload =
742742+ read_full_payload t page ptrs.(i) ~usable_size:usable
743743+ in
744744+ if starts_with full_payload then Some i
745745+ else if full_payload > prefix then None
746746+ else find_idx (i + 1)
747747+ in
748748+ match find_idx 0 with
749749+ | Some idx -> delete_from_leaf t page_num ~index:idx
750750+ | None -> ())
751751+ | Page.Interior_index ->
752752+ let child = find_child t page header prefix in
753753+ delete_by_prefix_in_page t child prefix
754754+ | _ -> failwith "Invalid page type in index B-tree"
755755+756756+let delete_by_prefix t prefix = delete_by_prefix_in_page t t.root_page prefix
757757+758758+let iter t f =
759759+ let rec iter_page page_num =
760760+ let page = Pager.read t.pager page_num in
761761+ let header = Page.parse_header page 0 in
762762+ let ptrs = cell_pointers page header in
763763+ let usable = usable_size t in
764764+ match header.Page.page_type with
765765+ | Page.Leaf_index ->
766766+ for i = 0 to header.Page.cell_count - 1 do
767767+ let full_payload =
768768+ read_full_payload t page ptrs.(i) ~usable_size:usable
769769+ in
770770+ f full_payload
771771+ done
772772+ | Page.Interior_index ->
773773+ for i = 0 to header.Page.cell_count - 1 do
774774+ let cell, _ =
775775+ Cell.parse_index_interior page ptrs.(i) ~usable_size:usable
776776+ in
777777+ iter_page cell.Cell.left_child
778778+ done;
779779+ Option.iter iter_page header.Page.right_child
780780+ | _ -> failwith "Invalid page type"
781781+ in
782782+ iter_page t.root_page
+131
lib/page.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** B-tree page types and header parsing. *)
77+88+type page_type = Interior_index | Interior_table | Leaf_index | Leaf_table
99+1010+let pp_page_type ppf = function
1111+ | Interior_index -> Format.pp_print_string ppf "interior_index"
1212+ | Interior_table -> Format.pp_print_string ppf "interior_table"
1313+ | Leaf_index -> Format.pp_print_string ppf "leaf_index"
1414+ | Leaf_table -> Format.pp_print_string ppf "leaf_table"
1515+1616+let page_type_of_byte = function
1717+ | 0x02 -> Interior_index
1818+ | 0x05 -> Interior_table
1919+ | 0x0a -> Leaf_index
2020+ | 0x0d -> Leaf_table
2121+ | b -> failwith (Printf.sprintf "Invalid page type: 0x%02x" b)
2222+2323+let byte_of_page_type = function
2424+ | Interior_index -> 0x02
2525+ | Interior_table -> 0x05
2626+ | Leaf_index -> 0x0a
2727+ | Leaf_table -> 0x0d
2828+2929+let header_size = function
3030+ | Interior_index | Interior_table -> 12
3131+ | Leaf_index | Leaf_table -> 8
3232+3333+let is_interior = function
3434+ | Interior_index | Interior_table -> true
3535+ | Leaf_index | Leaf_table -> false
3636+3737+type header = {
3838+ page_type : page_type;
3939+ first_freeblock : int;
4040+ cell_count : int;
4141+ cell_content_start : int;
4242+ fragmented_bytes : int;
4343+ right_child : int option;
4444+}
4545+(** Page header. *)
4646+4747+(* Binary helpers *)
4848+4949+let get_u16_be buf off = (Char.code buf.[off] lsl 8) lor Char.code buf.[off + 1]
5050+5151+let get_u32_be buf off =
5252+ (Char.code buf.[off] lsl 24)
5353+ lor (Char.code buf.[off + 1] lsl 16)
5454+ lor (Char.code buf.[off + 2] lsl 8)
5555+ lor Char.code buf.[off + 3]
5656+5757+let set_u16_be buf off v =
5858+ Bytes.set_uint8 buf off (v lsr 8);
5959+ Bytes.set_uint8 buf (off + 1) (v land 0xff)
6060+6161+let set_u32_be buf off v =
6262+ Bytes.set_uint8 buf off (v lsr 24);
6363+ Bytes.set_uint8 buf (off + 1) ((v lsr 16) land 0xff);
6464+ Bytes.set_uint8 buf (off + 2) ((v lsr 8) land 0xff);
6565+ Bytes.set_uint8 buf (off + 3) (v land 0xff)
6666+6767+let parse_header buf off =
6868+ let page_type = page_type_of_byte (Char.code buf.[off]) in
6969+ let first_freeblock = get_u16_be buf (off + 1) in
7070+ let cell_count = get_u16_be buf (off + 3) in
7171+ let cell_content_start =
7272+ let v = get_u16_be buf (off + 5) in
7373+ if v = 0 then 65536 else v
7474+ in
7575+ let fragmented_bytes = Char.code buf.[off + 7] in
7676+ let right_child =
7777+ if is_interior page_type then Some (get_u32_be buf (off + 8)) else None
7878+ in
7979+ {
8080+ page_type;
8181+ first_freeblock;
8282+ cell_count;
8383+ cell_content_start;
8484+ fragmented_bytes;
8585+ right_child;
8686+ }
8787+8888+let free_space header ~page_type =
8989+ let hdr_size = header_size page_type in
9090+ let ptr_area_end = hdr_size + (header.cell_count * 2) in
9191+ header.cell_content_start - ptr_area_end - header.fragmented_bytes
9292+9393+let init ~page_size ~page_type =
9494+ let buf = Bytes.create page_size in
9595+ Bytes.set_uint8 buf 0 (byte_of_page_type page_type);
9696+ set_u16_be buf 1 0;
9797+ (* first freeblock *)
9898+ set_u16_be buf 3 0;
9999+ (* cell count *)
100100+ set_u16_be buf 5 page_size;
101101+ (* cell content start *)
102102+ Bytes.set_uint8 buf 7 0;
103103+ (* fragmented bytes *)
104104+ if is_interior page_type then set_u32_be buf 8 0;
105105+ (* right child *)
106106+ buf
107107+108108+let write_cell buf ~cell_content_start ~cell =
109109+ let cell_len = String.length cell in
110110+ let cell_start = cell_content_start - cell_len in
111111+ Bytes.blit_string cell 0 buf cell_start cell_len;
112112+ cell_start
113113+114114+let cell_pointers page header_offset header =
115115+ let ptrs = Array.make header.cell_count 0 in
116116+ let ptr_start = header_offset + header_size header.page_type in
117117+ for i = 0 to header.cell_count - 1 do
118118+ ptrs.(i) <- get_u16_be page (ptr_start + (i * 2))
119119+ done;
120120+ ptrs
121121+122122+let insert_cell_pointer buf ~header_offset ~page_type ~cell_count ~index ~ptr =
123123+ let ptr_start = header_offset + header_size page_type in
124124+ (* Shift existing pointers right *)
125125+ for i = cell_count - 1 downto index do
126126+ let old_ptr =
127127+ get_u16_be (Bytes.unsafe_to_string buf) (ptr_start + (i * 2))
128128+ in
129129+ set_u16_be buf (ptr_start + ((i + 1) * 2)) old_ptr
130130+ done;
131131+ set_u16_be buf (ptr_start + (index * 2)) ptr
+66
lib/pager.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** Page cache and file I/O for B-tree storage. *)
77+88+type t = {
99+ file : Eio.File.rw_ty Eio.Resource.t;
1010+ page_size : int;
1111+ mutable page_count : int;
1212+ cache : (int, string) Hashtbl.t;
1313+ dirty : (int, string) Hashtbl.t;
1414+}
1515+1616+let create ~page_size file =
1717+ let stat = Eio.File.stat file in
1818+ let file_size = Optint.Int63.to_int stat.size in
1919+ let page_count = if file_size = 0 then 0 else file_size / page_size in
2020+ {
2121+ file;
2222+ page_size;
2323+ page_count;
2424+ cache = Hashtbl.create 64;
2525+ dirty = Hashtbl.create 16;
2626+ }
2727+2828+let page_size t = t.page_size
2929+let page_count t = t.page_count
3030+3131+let read t page_num =
3232+ if page_num < 1 || page_num > t.page_count then
3333+ failwith (Printf.sprintf "Invalid page number: %d" page_num);
3434+ match Hashtbl.find_opt t.dirty page_num with
3535+ | Some data -> data
3636+ | None -> (
3737+ match Hashtbl.find_opt t.cache page_num with
3838+ | Some data -> data
3939+ | None ->
4040+ let offset = Optint.Int63.of_int ((page_num - 1) * t.page_size) in
4141+ let buf = Cstruct.create t.page_size in
4242+ Eio.File.pread_exact t.file ~file_offset:offset [ buf ];
4343+ let data = Cstruct.to_string buf in
4444+ Hashtbl.replace t.cache page_num data;
4545+ data)
4646+4747+let write t page_num data =
4848+ if String.length data <> t.page_size then failwith "Invalid page size";
4949+ Hashtbl.replace t.dirty page_num data;
5050+ Hashtbl.replace t.cache page_num data
5151+5252+let allocate t =
5353+ t.page_count <- t.page_count + 1;
5454+ let data = String.make t.page_size '\x00' in
5555+ Hashtbl.replace t.dirty t.page_count data;
5656+ Hashtbl.replace t.cache t.page_count data;
5757+ t.page_count
5858+5959+let sync t =
6060+ Hashtbl.iter
6161+ (fun page_num data ->
6262+ let offset = Optint.Int63.of_int ((page_num - 1) * t.page_size) in
6363+ let buf = Cstruct.of_string data in
6464+ Eio.File.pwrite_all t.file ~file_offset:offset [ buf ])
6565+ t.dirty;
6666+ Hashtbl.clear t.dirty
+162
lib/record.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** SQLite record format encoding and decoding. *)
77+88+type serial_type =
99+ | Null
1010+ | Int8
1111+ | Int16
1212+ | Int24
1313+ | Int32
1414+ | Int48
1515+ | Int64
1616+ | Float64
1717+ | Zero
1818+ | One
1919+ | Blob of int
2020+ | Text of int
2121+2222+type value =
2323+ | Vnull
2424+ | Vint of int64
2525+ | Vfloat of float
2626+ | Vblob of string
2727+ | Vtext of string
2828+2929+let serial_type_of_int = function
3030+ | 0 -> Null
3131+ | 1 -> Int8
3232+ | 2 -> Int16
3333+ | 3 -> Int24
3434+ | 4 -> Int32
3535+ | 5 -> Int48
3636+ | 6 -> Int64
3737+ | 7 -> Float64
3838+ | 8 -> Zero
3939+ | 9 -> One
4040+ | n when n >= 12 && n mod 2 = 0 -> Blob ((n - 12) / 2)
4141+ | n when n >= 13 -> Text ((n - 13) / 2)
4242+ | n -> failwith (Printf.sprintf "Invalid serial type: %d" n)
4343+4444+let decode_int buf off len =
4545+ let rec loop acc i =
4646+ if i >= len then acc
4747+ else
4848+ let b = Char.code buf.[off + i] in
4949+ let acc = Int64.logor (Int64.shift_left acc 8) (Int64.of_int b) in
5050+ loop acc (i + 1)
5151+ in
5252+ (* Sign extend for negative values *)
5353+ let v = loop 0L 0 in
5454+ if len > 0 && Char.code buf.[off] land 0x80 <> 0 then
5555+ let mask = Int64.shift_left (-1L) (len * 8) in
5656+ Int64.logor v mask
5757+ else v
5858+5959+let decode payload =
6060+ let header_size, consumed = Varint.decode payload 0 in
6161+ let header_size = Int64.to_int header_size in
6262+ (* Parse serial types *)
6363+ let rec parse_types off acc =
6464+ if off >= header_size then List.rev acc
6565+ else
6666+ let st, consumed = Varint.decode payload off in
6767+ let st = serial_type_of_int (Int64.to_int st) in
6868+ parse_types (off + consumed) (st :: acc)
6969+ in
7070+ let types = parse_types consumed [] in
7171+ (* Parse values *)
7272+ let rec parse_values types off acc =
7373+ match types with
7474+ | [] -> List.rev acc
7575+ | st :: rest ->
7676+ let value, sz =
7777+ match st with
7878+ | Null -> (Vnull, 0)
7979+ | Zero -> (Vint 0L, 0)
8080+ | One -> (Vint 1L, 0)
8181+ | Int8 -> (Vint (decode_int payload off 1), 1)
8282+ | Int16 -> (Vint (decode_int payload off 2), 2)
8383+ | Int24 -> (Vint (decode_int payload off 3), 3)
8484+ | Int32 -> (Vint (decode_int payload off 4), 4)
8585+ | Int48 -> (Vint (decode_int payload off 6), 6)
8686+ | Int64 -> (Vint (decode_int payload off 8), 8)
8787+ | Float64 ->
8888+ let bits = decode_int payload off 8 in
8989+ (Vfloat (Int64.float_of_bits bits), 8)
9090+ | Blob n -> (Vblob (String.sub payload off n), n)
9191+ | Text n -> (Vtext (String.sub payload off n), n)
9292+ in
9393+ parse_values rest (off + sz) (value :: acc)
9494+ in
9595+ parse_values types header_size []
9696+9797+let serial_type_of_value = function
9898+ | Vnull -> (0, 0)
9999+ | Vint 0L -> (8, 0)
100100+ | Vint 1L -> (9, 0)
101101+ | Vint n ->
102102+ if n >= -128L && n <= 127L then (1, 1)
103103+ else if n >= -32768L && n <= 32767L then (2, 2)
104104+ else if n >= -8388608L && n <= 8388607L then (3, 3)
105105+ else if n >= -2147483648L && n <= 2147483647L then (4, 4)
106106+ else if n >= -140737488355328L && n <= 140737488355327L then (5, 6)
107107+ else (6, 8)
108108+ | Vfloat _ -> (7, 8)
109109+ | Vblob s -> (12 + (String.length s * 2), String.length s)
110110+ | Vtext s -> (13 + (String.length s * 2), String.length s)
111111+112112+let encode_int buf off n len =
113113+ for i = 0 to len - 1 do
114114+ let shift = (len - 1 - i) * 8 in
115115+ Bytes.set_uint8 buf (off + i)
116116+ (Int64.to_int (Int64.shift_right n shift) land 0xff)
117117+ done
118118+119119+let encode values =
120120+ (* Calculate header *)
121121+ let types_and_sizes = List.map serial_type_of_value values in
122122+ let header_types =
123123+ List.map (fun (st, _) -> Varint.encode (Int64.of_int st)) types_and_sizes
124124+ in
125125+ let header_body = String.concat "" header_types in
126126+ let header_size = 1 + String.length header_body in
127127+ (* header size varint + types *)
128128+ let body_size =
129129+ List.fold_left (fun acc (_, sz) -> acc + sz) 0 types_and_sizes
130130+ in
131131+ let total = header_size + body_size in
132132+ let buf = Bytes.create total in
133133+ (* Write header size *)
134134+ Bytes.set_uint8 buf 0 header_size;
135135+ (* Write serial types *)
136136+ let _ =
137137+ List.fold_left
138138+ (fun off s ->
139139+ Bytes.blit_string s 0 buf off (String.length s);
140140+ off + String.length s)
141141+ 1 header_types
142142+ in
143143+ (* Write values *)
144144+ let _ =
145145+ List.fold_left2
146146+ (fun off value (_, sz) ->
147147+ (match value with
148148+ | Vnull | Vint 0L | Vint 1L -> ()
149149+ | Vint n -> encode_int buf off n sz
150150+ | Vfloat f -> encode_int buf off (Int64.bits_of_float f) 8
151151+ | Vblob s | Vtext s -> Bytes.blit_string s 0 buf off sz);
152152+ off + sz)
153153+ header_size values types_and_sizes
154154+ in
155155+ Bytes.unsafe_to_string buf
156156+157157+let pp_value ppf = function
158158+ | Vnull -> Format.pp_print_string ppf "NULL"
159159+ | Vint n -> Format.fprintf ppf "%Ld" n
160160+ | Vfloat f -> Format.fprintf ppf "%f" f
161161+ | Vblob s -> Format.fprintf ppf "BLOB(%d)" (String.length s)
162162+ | Vtext s -> Format.fprintf ppf "%S" s
+473
lib/table.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** Table B-tree for SQLite row storage. *)
77+88+type t = { pager : Pager.t; mutable root_page : int }
99+1010+let create pager =
1111+ let root = Pager.allocate pager in
1212+ let page_size = Pager.page_size pager in
1313+ let buf = Page.init ~page_size ~page_type:Page.Leaf_table in
1414+ Pager.write pager root (Bytes.unsafe_to_string buf);
1515+ { pager; root_page = root }
1616+1717+let open_ pager ~root_page = { pager; root_page }
1818+let root_page t = t.root_page
1919+let usable_size t = Pager.page_size t.pager
2020+2121+(* Calculate free space in a page *)
2222+let free_space header ~page_type =
2323+ let header_size = Page.header_size page_type in
2424+ let ptr_area_end = header_size + (header.Page.cell_count * 2) in
2525+ header.Page.cell_content_start - ptr_area_end - header.Page.fragmented_bytes
2626+2727+(* Encode a table leaf cell *)
2828+let encode_table_leaf_cell ~rowid ~data =
2929+ let rowid_varint = Varint.encode rowid in
3030+ let payload_size_varint = Varint.encode (Int64.of_int (String.length data)) in
3131+ let cell =
3232+ Bytes.create
3333+ (String.length payload_size_varint
3434+ + String.length rowid_varint + String.length data)
3535+ in
3636+ Bytes.blit_string payload_size_varint 0 cell 0
3737+ (String.length payload_size_varint);
3838+ Bytes.blit_string rowid_varint 0 cell
3939+ (String.length payload_size_varint)
4040+ (String.length rowid_varint);
4141+ Bytes.blit_string data 0 cell
4242+ (String.length payload_size_varint + String.length rowid_varint)
4343+ (String.length data);
4444+ Bytes.unsafe_to_string cell
4545+4646+(* Encode a table interior cell *)
4747+let encode_table_interior_cell ~left_child ~rowid =
4848+ let rowid_varint = Varint.encode rowid in
4949+ let cell = Bytes.create (4 + String.length rowid_varint) in
5050+ Page.set_u32_be cell 0 left_child;
5151+ Bytes.blit_string rowid_varint 0 cell 4 (String.length rowid_varint);
5252+ Bytes.unsafe_to_string cell
5353+5454+(* Write a cell into a page buffer, returns new cell_content_start *)
5555+let write_cell buf ~cell_content_start ~cell =
5656+ let cell_len = String.length cell in
5757+ let new_start = cell_content_start - cell_len in
5858+ Bytes.blit_string cell 0 buf new_start cell_len;
5959+ new_start
6060+6161+(* Insert a cell pointer at index, shifting others *)
6262+let insert_cell_pointer buf ~header_offset ~page_type ~cell_count ~index ~ptr =
6363+ let ptr_start = header_offset + Page.header_size page_type in
6464+ (* Shift existing pointers right *)
6565+ for i = cell_count - 1 downto index do
6666+ let old_ptr =
6767+ Page.get_u16_be (Bytes.unsafe_to_string buf) (ptr_start + (i * 2))
6868+ in
6969+ Page.set_u16_be buf (ptr_start + ((i + 1) * 2)) old_ptr
7070+ done;
7171+ Page.set_u16_be buf (ptr_start + (index * 2)) ptr
7272+7373+(* Binary search for rowid in leaf page *)
7474+let search_leaf t page header rowid =
7575+ let ptrs = Page.cell_pointers page 0 header in
7676+ let usable = usable_size t in
7777+ let rec loop lo hi =
7878+ if lo > hi then None
7979+ else
8080+ let mid = (lo + hi) / 2 in
8181+ let cell, _ = Cell.parse_table_leaf page ptrs.(mid) ~usable_size:usable in
8282+ if cell.Cell.rowid = rowid then Some cell.Cell.payload
8383+ else if cell.Cell.rowid < rowid then loop (mid + 1) hi
8484+ else loop lo (mid - 1)
8585+ in
8686+ loop 0 (header.Page.cell_count - 1)
8787+8888+(* Find insertion index for rowid in leaf page *)
8989+let find_insert_idx t page header rowid =
9090+ let ptrs = Page.cell_pointers page 0 header in
9191+ let usable = usable_size t in
9292+ let rec find i =
9393+ if i >= header.Page.cell_count then i
9494+ else
9595+ let cell, _ = Cell.parse_table_leaf page ptrs.(i) ~usable_size:usable in
9696+ if rowid < cell.Cell.rowid then i else find (i + 1)
9797+ in
9898+ find 0
9999+100100+(* Find child page for rowid in interior page.
101101+ SQLite B-tree: keys < separator go left, keys >= separator go right. *)
102102+let find_child _t page header rowid =
103103+ let ptrs = Page.cell_pointers page 0 header in
104104+ let rec loop i =
105105+ if i >= header.Page.cell_count then Option.get header.Page.right_child
106106+ else
107107+ let cell, _ = Cell.parse_table_interior page ptrs.(i) in
108108+ if rowid < cell.Cell.rowid then cell.Cell.left_child else loop (i + 1)
109109+ in
110110+ loop 0
111111+112112+(* Find child index for rowid in interior page *)
113113+let find_child_idx page header rowid =
114114+ let ptrs = Page.cell_pointers page 0 header in
115115+ let rec loop i =
116116+ if i >= header.Page.cell_count then i (* right child *)
117117+ else
118118+ let cell, _ = Cell.parse_table_interior page ptrs.(i) in
119119+ if rowid < cell.Cell.rowid then i else loop (i + 1)
120120+ in
121121+ loop 0
122122+123123+let rec find_in_page t page_num rowid =
124124+ let page = Pager.read t.pager page_num in
125125+ let header = Page.parse_header page 0 in
126126+ match header.Page.page_type with
127127+ | Page.Leaf_table -> search_leaf t page header rowid
128128+ | Page.Interior_table ->
129129+ let child = find_child t page header rowid in
130130+ find_in_page t child rowid
131131+ | _ -> failwith "Invalid page type in table B-tree"
132132+133133+let find t rowid = find_in_page t t.root_page rowid
134134+135135+(* Split result: new page number and separator rowid *)
136136+type split_result = { new_page : int; separator_rowid : int64 }
137137+138138+(* Split a leaf page, returns info about the new page *)
139139+let split_leaf t page_num =
140140+ let page = Pager.read t.pager page_num in
141141+ let header = Page.parse_header page 0 in
142142+ let ptrs = Page.cell_pointers page 0 header in
143143+ let usable = usable_size t in
144144+ let page_size = Pager.page_size t.pager in
145145+146146+ (* Find split point (middle) *)
147147+ let split_idx = header.Page.cell_count / 2 in
148148+149149+ (* Get separator rowid (first key that goes to right page) *)
150150+ let sep_cell, _ =
151151+ Cell.parse_table_leaf page ptrs.(split_idx) ~usable_size:usable
152152+ in
153153+ let separator_rowid = sep_cell.Cell.rowid in
154154+155155+ (* Create new right page *)
156156+ let new_page_num = Pager.allocate t.pager in
157157+ let new_buf = Page.init ~page_size ~page_type:Page.Leaf_table in
158158+159159+ (* Copy cells [split_idx..cell_count-1] to new page *)
160160+ let new_cell_content_start = ref page_size in
161161+ for i = split_idx to header.Page.cell_count - 1 do
162162+ let cell_off = ptrs.(i) in
163163+ let cell, cell_len =
164164+ Cell.parse_table_leaf page cell_off ~usable_size:usable
165165+ in
166166+ let cell_data =
167167+ encode_table_leaf_cell ~rowid:cell.Cell.rowid ~data:cell.Cell.payload
168168+ in
169169+ new_cell_content_start :=
170170+ write_cell new_buf ~cell_content_start:!new_cell_content_start
171171+ ~cell:cell_data;
172172+ let new_idx = i - split_idx in
173173+ let ptr_off = Page.header_size Page.Leaf_table + (new_idx * 2) in
174174+ Page.set_u16_be new_buf ptr_off !new_cell_content_start;
175175+ ignore cell_len
176176+ done;
177177+ let new_cell_count = header.Page.cell_count - split_idx in
178178+ Page.set_u16_be new_buf 3 new_cell_count;
179179+ Page.set_u16_be new_buf 5 !new_cell_content_start;
180180+ Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
181181+182182+ (* Update original page to only have cells [0..split_idx-1] *)
183183+ let old_buf = Bytes.of_string page in
184184+ Page.set_u16_be old_buf 3 split_idx;
185185+ (* Recalculate cell content start for remaining cells *)
186186+ if split_idx > 0 then begin
187187+ let min_ptr = ref page_size in
188188+ for i = 0 to split_idx - 1 do
189189+ if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
190190+ done;
191191+ Page.set_u16_be old_buf 5 !min_ptr
192192+ end
193193+ else Page.set_u16_be old_buf 5 page_size;
194194+ Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
195195+196196+ { new_page = new_page_num; separator_rowid }
197197+198198+(* Split an interior page *)
199199+let split_interior t page_num =
200200+ let page = Pager.read t.pager page_num in
201201+ let header = Page.parse_header page 0 in
202202+ let ptrs = Page.cell_pointers page 0 header in
203203+ let page_size = Pager.page_size t.pager in
204204+205205+ (* Split point - middle cell becomes separator, doesn't go to either page *)
206206+ let split_idx = header.Page.cell_count / 2 in
207207+ let sep_cell, _ = Cell.parse_table_interior page ptrs.(split_idx) in
208208+ let separator_rowid = sep_cell.Cell.rowid in
209209+210210+ (* Create new right page *)
211211+ let new_page_num = Pager.allocate t.pager in
212212+ let new_buf = Page.init ~page_size ~page_type:Page.Interior_table in
213213+214214+ (* The right child of split cell becomes the left-most child of new page *)
215215+ (* Cells [split_idx+1..cell_count-1] go to new page *)
216216+ let new_cell_content_start = ref page_size in
217217+ for i = split_idx + 1 to header.Page.cell_count - 1 do
218218+ let cell_off = ptrs.(i) in
219219+ let cell, _ = Cell.parse_table_interior page cell_off in
220220+ let cell_data =
221221+ encode_table_interior_cell ~left_child:cell.Cell.left_child
222222+ ~rowid:cell.Cell.rowid
223223+ in
224224+ new_cell_content_start :=
225225+ write_cell new_buf ~cell_content_start:!new_cell_content_start
226226+ ~cell:cell_data;
227227+ let new_idx = i - split_idx - 1 in
228228+ let ptr_off = Page.header_size Page.Interior_table + (new_idx * 2) in
229229+ Page.set_u16_be new_buf ptr_off !new_cell_content_start
230230+ done;
231231+ let new_cell_count = header.Page.cell_count - split_idx - 1 in
232232+ Page.set_u16_be new_buf 3 new_cell_count;
233233+ Page.set_u16_be new_buf 5 !new_cell_content_start;
234234+ (* Right child of new page is same as original *)
235235+ Page.set_u32_be new_buf 8 (Option.get header.Page.right_child);
236236+ Pager.write t.pager new_page_num (Bytes.unsafe_to_string new_buf);
237237+238238+ (* Update original page: keep cells [0..split_idx-1], right child = sep_cell.left_child *)
239239+ let old_buf = Bytes.of_string page in
240240+ Page.set_u16_be old_buf 3 split_idx;
241241+ Page.set_u32_be old_buf 8 sep_cell.Cell.left_child;
242242+ if split_idx > 0 then begin
243243+ let min_ptr = ref page_size in
244244+ for i = 0 to split_idx - 1 do
245245+ if ptrs.(i) < !min_ptr then min_ptr := ptrs.(i)
246246+ done;
247247+ Page.set_u16_be old_buf 5 !min_ptr
248248+ end
249249+ else Page.set_u16_be old_buf 5 page_size;
250250+ Pager.write t.pager page_num (Bytes.unsafe_to_string old_buf);
251251+252252+ { new_page = new_page_num; separator_rowid }
253253+254254+(* Insert a separator into an interior page, potentially splitting *)
255255+let rec insert_into_interior t page_num ~left_child ~separator_rowid
256256+ ~right_child =
257257+ let page = Pager.read t.pager page_num in
258258+ let header = Page.parse_header page 0 in
259259+ let cell = encode_table_interior_cell ~left_child ~rowid:separator_rowid in
260260+ let cell_len = String.length cell in
261261+ let space_needed = cell_len + 2 in
262262+ (* cell + pointer *)
263263+264264+ if free_space header ~page_type:Page.Interior_table >= space_needed then begin
265265+ (* Fits - insert directly *)
266266+ let buf = Bytes.of_string page in
267267+ let ptrs = Page.cell_pointers page 0 header in
268268+269269+ (* Find insert position *)
270270+ let insert_idx =
271271+ let rec find i =
272272+ if i >= header.Page.cell_count then i
273273+ else
274274+ let c, _ = Cell.parse_table_interior page ptrs.(i) in
275275+ if separator_rowid < c.Cell.rowid then i else find (i + 1)
276276+ in
277277+ find 0
278278+ in
279279+280280+ (* Write cell *)
281281+ let cell_start =
282282+ write_cell buf ~cell_content_start:header.Page.cell_content_start ~cell
283283+ in
284284+ Page.set_u16_be buf 5 cell_start;
285285+286286+ (* Insert pointer *)
287287+ insert_cell_pointer buf ~header_offset:0 ~page_type:Page.Interior_table
288288+ ~cell_count:header.Page.cell_count ~index:insert_idx ~ptr:cell_start;
289289+290290+ (* Update cell count *)
291291+ Page.set_u16_be buf 3 (header.Page.cell_count + 1);
292292+293293+ (* Update child pointers: the cell we displaced (now at insert_idx+1)
294294+ needs its left_child updated to right_child, OR if we inserted at end,
295295+ update the page's right_child. *)
296296+ if insert_idx < header.Page.cell_count then begin
297297+ (* Update the displaced cell's left_child to right_child *)
298298+ let ptr_start = Page.header_size Page.Interior_table in
299299+ let displaced_ptr =
300300+ Page.get_u16_be
301301+ (Bytes.unsafe_to_string buf)
302302+ (ptr_start + ((insert_idx + 1) * 2))
303303+ in
304304+ Page.set_u32_be buf displaced_ptr right_child
305305+ end
306306+ else Page.set_u32_be buf 8 right_child;
307307+308308+ Pager.write t.pager page_num (Bytes.unsafe_to_string buf);
309309+ None
310310+ end
311311+ else begin
312312+ (* Need to split interior page *)
313313+ let split = split_interior t page_num in
314314+315315+ (* Determine which page gets the new separator *)
316316+ if separator_rowid < split.separator_rowid then begin
317317+ (* Insert into left (original) page *)
318318+ ignore
319319+ (insert_into_interior t page_num ~left_child ~separator_rowid
320320+ ~right_child)
321321+ end
322322+ else begin
323323+ (* Insert into right (new) page *)
324324+ ignore
325325+ (insert_into_interior t split.new_page ~left_child ~separator_rowid
326326+ ~right_child)
327327+ end;
328328+329329+ (* Return split info to propagate up *)
330330+ Some { split with separator_rowid = split.separator_rowid }
331331+ end
332332+333333+(* Insert into a leaf page, potentially splitting *)
334334+let rec insert_into_leaf t page_num ~rowid ~data ~parent_stack =
335335+ let page = Pager.read t.pager page_num in
336336+ let header = Page.parse_header page 0 in
337337+ let cell = encode_table_leaf_cell ~rowid ~data in
338338+ let cell_len = String.length cell in
339339+ let space_needed = cell_len + 2 in
340340+ (* cell + pointer *)
341341+342342+ if free_space header ~page_type:Page.Leaf_table >= space_needed then begin
343343+ (* Fits - insert directly *)
344344+ let buf = Bytes.of_string page in
345345+ let insert_idx = find_insert_idx t page header rowid in
346346+347347+ (* Write cell *)
348348+ let cell_start =
349349+ write_cell buf ~cell_content_start:header.Page.cell_content_start ~cell
350350+ in
351351+ Page.set_u16_be buf 5 cell_start;
352352+353353+ (* Insert pointer *)
354354+ insert_cell_pointer buf ~header_offset:0 ~page_type:Page.Leaf_table
355355+ ~cell_count:header.Page.cell_count ~index:insert_idx ~ptr:cell_start;
356356+357357+ (* Update cell count *)
358358+ Page.set_u16_be buf 3 (header.Page.cell_count + 1);
359359+360360+ Pager.write t.pager page_num (Bytes.unsafe_to_string buf)
361361+ end
362362+ else begin
363363+ (* Need to split *)
364364+ let split = split_leaf t page_num in
365365+366366+ (* Determine target page and insert directly (no recursion needed -
367367+ after split, both pages have ~half capacity, plenty of room) *)
368368+ let target_page =
369369+ if rowid < split.separator_rowid then page_num else split.new_page
370370+ in
371371+ let target = Pager.read t.pager target_page in
372372+ let target_header = Page.parse_header target 0 in
373373+ let target_buf = Bytes.of_string target in
374374+ let insert_idx = find_insert_idx t target target_header rowid in
375375+ let cell_start =
376376+ write_cell target_buf
377377+ ~cell_content_start:target_header.Page.cell_content_start ~cell
378378+ in
379379+ Page.set_u16_be target_buf 5 cell_start;
380380+ insert_cell_pointer target_buf ~header_offset:0 ~page_type:Page.Leaf_table
381381+ ~cell_count:target_header.Page.cell_count ~index:insert_idx
382382+ ~ptr:cell_start;
383383+ Page.set_u16_be target_buf 3 (target_header.Page.cell_count + 1);
384384+ Pager.write t.pager target_page (Bytes.unsafe_to_string target_buf);
385385+386386+ (* Propagate split up *)
387387+ propagate_split t ~parent_stack ~left_page:page_num
388388+ ~separator_rowid:split.separator_rowid ~right_page:split.new_page
389389+ end
390390+391391+and propagate_split t ~parent_stack ~left_page ~separator_rowid ~right_page =
392392+ match parent_stack with
393393+ | [] ->
394394+ (* Splitting root - create new root *)
395395+ let page_size = Pager.page_size t.pager in
396396+ let new_root = Pager.allocate t.pager in
397397+ let buf = Page.init ~page_size ~page_type:Page.Interior_table in
398398+399399+ (* Single cell pointing to left page *)
400400+ let cell =
401401+ encode_table_interior_cell ~left_child:left_page ~rowid:separator_rowid
402402+ in
403403+ let cell_start = write_cell buf ~cell_content_start:page_size ~cell in
404404+ Page.set_u16_be buf 5 cell_start;
405405+ Page.set_u16_be buf (Page.header_size Page.Interior_table) cell_start;
406406+ Page.set_u16_be buf 3 1;
407407+ Page.set_u32_be buf 8 right_page;
408408+409409+ Pager.write t.pager new_root (Bytes.unsafe_to_string buf);
410410+ t.root_page <- new_root
411411+ | parent_page :: rest -> (
412412+ match
413413+ insert_into_interior t parent_page ~left_child:left_page
414414+ ~separator_rowid ~right_child:right_page
415415+ with
416416+ | None -> () (* Fit in parent *)
417417+ | Some split ->
418418+ (* Parent also split, propagate up *)
419419+ propagate_split t ~parent_stack:rest ~left_page:parent_page
420420+ ~separator_rowid:split.separator_rowid ~right_page:split.new_page)
421421+422422+(* Main insert - traverses tree and handles splits *)
423423+let insert t ~rowid data =
424424+ let rec traverse page_num parent_stack =
425425+ let page = Pager.read t.pager page_num in
426426+ let header = Page.parse_header page 0 in
427427+ match header.Page.page_type with
428428+ | Page.Leaf_table -> insert_into_leaf t page_num ~rowid ~data ~parent_stack
429429+ | Page.Interior_table ->
430430+ let child_idx = find_child_idx page header rowid in
431431+ let child_page =
432432+ if child_idx >= header.Page.cell_count then
433433+ Option.get header.Page.right_child
434434+ else
435435+ let ptrs = Page.cell_pointers page 0 header in
436436+ let cell, _ = Cell.parse_table_interior page ptrs.(child_idx) in
437437+ cell.Cell.left_child
438438+ in
439439+ traverse child_page (page_num :: parent_stack)
440440+ | _ -> failwith "Invalid page type in table B-tree"
441441+ in
442442+ traverse t.root_page []
443443+444444+let delete _t _rowid = failwith "Delete not yet implemented"
445445+446446+let iter t f =
447447+ let rec iter_page page_num =
448448+ let page = Pager.read t.pager page_num in
449449+ let header = Page.parse_header page 0 in
450450+ let ptrs = Page.cell_pointers page 0 header in
451451+ let usable = usable_size t in
452452+ match header.Page.page_type with
453453+ | Page.Leaf_table ->
454454+ for i = 0 to header.Page.cell_count - 1 do
455455+ let cell, _ =
456456+ Cell.parse_table_leaf page ptrs.(i) ~usable_size:usable
457457+ in
458458+ f cell.Cell.rowid cell.Cell.payload
459459+ done
460460+ | Page.Interior_table ->
461461+ for i = 0 to header.Page.cell_count - 1 do
462462+ let cell, _ = Cell.parse_table_interior page ptrs.(i) in
463463+ iter_page cell.Cell.left_child
464464+ done;
465465+ Option.iter iter_page header.Page.right_child
466466+ | _ -> failwith "Invalid page type"
467467+ in
468468+ iter_page t.root_page
469469+470470+let fold t ~init ~f =
471471+ let acc = ref init in
472472+ iter t (fun rowid data -> acc := f rowid data !acc);
473473+ !acc
+48
lib/varint.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
33+ SPDX-License-Identifier: MIT
44+ ---------------------------------------------------------------------------*)
55+66+(** SQLite-style variable-length integer encoding. *)
77+88+let decode buf off =
99+ let rec loop acc shift i =
1010+ if i >= String.length buf then (acc, i - off)
1111+ else
1212+ let byte = Char.code buf.[i] in
1313+ let value = Int64.of_int (byte land 0x7f) in
1414+ let acc = Int64.logor acc (Int64.shift_left value shift) in
1515+ if byte land 0x80 = 0 then (acc, i - off + 1)
1616+ else if shift >= 56 then
1717+ (* 9th byte - use all 8 bits *)
1818+ let byte9 = Char.code buf.[i + 1] in
1919+ let acc = Int64.logor acc (Int64.shift_left (Int64.of_int byte9) 56) in
2020+ (acc, i - off + 2)
2121+ else loop acc (shift + 7) (i + 1)
2222+ in
2323+ loop 0L 0 off
2424+2525+let size n =
2626+ if n < 0L then 9
2727+ else if n < 128L then 1
2828+ else if n < 16384L then 2
2929+ else if n < 2097152L then 3
3030+ else if n < 268435456L then 4
3131+ else if n < 34359738368L then 5
3232+ else if n < 4398046511104L then 6
3333+ else if n < 562949953421312L then 7
3434+ else if n < 72057594037927936L then 8
3535+ else 9
3636+3737+let encode n =
3838+ let sz = size n in
3939+ let buf = Bytes.create sz in
4040+ let rec loop n i =
4141+ if i = sz - 1 then Bytes.set_uint8 buf i (Int64.to_int n land 0x7f)
4242+ else begin
4343+ Bytes.set_uint8 buf i (Int64.to_int n land 0x7f lor 0x80);
4444+ loop (Int64.shift_right_logical n 7) (i + 1)
4545+ end
4646+ in
4747+ loop n 0;
4848+ Bytes.unsafe_to_string buf
+8-7
test/test_btree.ml
···110110 (* cell content start = 0x0f00 *)
111111 Bytes.set_uint8 page 7 0;
112112 (* fragmented = 0 *)
113113- let header = Btree.parse_page_header (Bytes.unsafe_to_string page) 0 in
114114- Alcotest.(check int) "cell_count" 5 header.cell_count;
115115- Alcotest.(check int) "cell_content_start" 0x0f00 header.cell_content_start;
113113+ let header = Btree.Page.parse_header (Bytes.unsafe_to_string page) 0 in
114114+ Alcotest.(check int) "cell_count" 5 header.Btree.Page.cell_count;
115115+ Alcotest.(check int)
116116+ "cell_content_start" 0x0f00 header.Btree.Page.cell_content_start;
116117 Alcotest.(check bool)
117118 "no right_child" true
118118- (Option.is_none header.right_child)
119119+ (Option.is_none header.Btree.Page.right_child)
119120120121let test_page_header_interior () =
121122 let page = Bytes.create 4096 in
···133134 Bytes.set_uint8 page 9 0;
134135 Bytes.set_uint8 page 10 0;
135136 Bytes.set_uint8 page 11 42;
136136- let header = Btree.parse_page_header (Bytes.unsafe_to_string page) 0 in
137137- Alcotest.(check int) "cell_count" 3 header.cell_count;
138138- match header.right_child with
137137+ let header = Btree.Page.parse_header (Bytes.unsafe_to_string page) 0 in
138138+ Alcotest.(check int) "cell_count" 3 header.Btree.Page.cell_count;
139139+ match header.Btree.Page.right_child with
139140 | Some n -> Alcotest.(check int) "right_child" 42 n
140141 | None -> Alcotest.fail "expected right_child"
141142