Minimal SQLite key-value store for OCaml
at main 188 lines 6.2 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: MIT 4 ---------------------------------------------------------------------------*) 5 6(** Fuzz tests for the pure OCaml B-tree backed key-value store. *) 7 8open Crowbar 9 10let truncate ?(max_len = 4096) s = 11 if String.length s > max_len then String.sub s 0 max_len else s 12 13(* Core KV operations *) 14 15(** Roundtrip - put then get must return same value. *) 16let test_roundtrip key value = 17 let key = truncate key in 18 let value = truncate value in 19 let db = Sqlite.in_memory () in 20 Sqlite.put db key value; 21 let result = Sqlite.find db key in 22 check_eq ~pp:Format.pp_print_string ~eq:( = ) (Option.get result) value 23 24(** Delete removes key. *) 25let test_delete_removes key value = 26 let key = truncate key in 27 let value = truncate value in 28 let db = Sqlite.in_memory () in 29 Sqlite.put db key value; 30 Sqlite.delete db key; 31 check (Option.is_none (Sqlite.find db key)) 32 33(** mem consistent with get. *) 34let test_mem_consistent key value = 35 let key = truncate key in 36 let value = truncate value in 37 let db = Sqlite.in_memory () in 38 Sqlite.put db key value; 39 check_eq ~pp:Format.pp_print_bool (Sqlite.mem db key) 40 (Option.is_some (Sqlite.find db key)) 41 42(** Overwrite replaces value - last put wins. *) 43let test_overwrite key value1 value2 = 44 let key = truncate key in 45 let value1 = truncate value1 in 46 let value2 = truncate value2 in 47 let db = Sqlite.in_memory () in 48 Sqlite.put db key value1; 49 Sqlite.put db key value2; 50 check_eq ~pp:Format.pp_print_string ~eq:( = ) 51 (Option.get (Sqlite.find db key)) 52 value2 53 54(* Table operations *) 55 56(** Table isolation - same key in different tables must be independent. *) 57let test_table_isolation key value1 value2 = 58 let key = truncate key in 59 let value1 = truncate value1 in 60 let value2 = truncate value2 in 61 let db = Sqlite.in_memory () in 62 let t1 = Sqlite.Table.create db ~name:"table1" in 63 let t2 = Sqlite.Table.create db ~name:"table2" in 64 Sqlite.Table.put t1 key value1; 65 Sqlite.Table.put t2 key value2; 66 check_eq ~pp:Format.pp_print_string ~eq:( = ) 67 (Option.get (Sqlite.Table.find t1 key)) 68 value1; 69 check_eq ~pp:Format.pp_print_string ~eq:( = ) 70 (Option.get (Sqlite.Table.find t2 key)) 71 value2 72 73(** Table roundtrip. *) 74let test_table_roundtrip key value = 75 let key = truncate key in 76 let value = truncate value in 77 let db = Sqlite.in_memory () in 78 let t = Sqlite.Table.create db ~name:"test" in 79 Sqlite.Table.put t key value; 80 check_eq ~pp:Format.pp_print_string ~eq:( = ) 81 (Option.get (Sqlite.Table.find t key)) 82 value 83 84(* Crash safety *) 85 86(** Put must not crash on arbitrary binary data. *) 87let test_put_crash key value = 88 let key = truncate key in 89 let value = truncate value in 90 let db = Sqlite.in_memory () in 91 try Sqlite.put db key value with _exn -> () 92 93(** Get must not crash on arbitrary key. *) 94let test_get_crash key = 95 let key = truncate key in 96 let db = Sqlite.in_memory () in 97 try ignore (Sqlite.find db key) with _exn -> () 98 99(** Delete must not crash on arbitrary key. *) 100let test_delete_crash key = 101 let key = truncate key in 102 let db = Sqlite.in_memory () in 103 try Sqlite.delete db key with _exn -> () 104 105(** Mem must not crash on arbitrary key. *) 106let test_mem_crash key = 107 let key = truncate key in 108 let db = Sqlite.in_memory () in 109 try ignore (Sqlite.mem db key) with _exn -> () 110 111(* Boundary conditions *) 112 113(** Empty key must work. *) 114let test_empty_key value = 115 let value = truncate value in 116 let db = Sqlite.in_memory () in 117 Sqlite.put db "" value; 118 check_eq ~pp:Format.pp_print_string ~eq:( = ) 119 (Option.get (Sqlite.find db "")) 120 value 121 122(** Empty value must work. *) 123let test_empty_value key = 124 let key = truncate key in 125 let db = Sqlite.in_memory () in 126 Sqlite.put db key ""; 127 check_eq ~pp:Format.pp_print_string ~eq:( = ) 128 (Option.get (Sqlite.find db key)) 129 "" 130 131(** Both empty must work. *) 132let test_both_empty () = 133 let db = Sqlite.in_memory () in 134 Sqlite.put db "" ""; 135 check_eq ~pp:Format.pp_print_string ~eq:( = ) 136 (Option.get (Sqlite.find db "")) 137 "" 138 139(* Sequence operations *) 140 141(** Multiple puts to same key must always have last value. *) 142let test_multiple_puts key values = 143 let key = truncate key in 144 let values = List.map truncate values in 145 if values = [] then check true 146 else begin 147 let db = Sqlite.in_memory () in 148 List.iter (fun v -> Sqlite.put db key v) values; 149 let last = List.hd (List.rev values) in 150 check_eq ~pp:Format.pp_print_string ~eq:( = ) 151 (Option.get (Sqlite.find db key)) 152 last 153 end 154 155(** Put then delete then put must have second value. *) 156let test_put_delete_put key value1 value2 = 157 let key = truncate key in 158 let value1 = truncate value1 in 159 let value2 = truncate value2 in 160 let db = Sqlite.in_memory () in 161 Sqlite.put db key value1; 162 Sqlite.delete db key; 163 Sqlite.put db key value2; 164 check_eq ~pp:Format.pp_print_string ~eq:( = ) 165 (Option.get (Sqlite.find db key)) 166 value2 167 168(* Register all tests *) 169 170let suite = 171 ( "sqlite", 172 [ 173 test_case "roundtrip" [ bytes; bytes ] test_roundtrip; 174 test_case "delete removes" [ bytes; bytes ] test_delete_removes; 175 test_case "mem consistent" [ bytes; bytes ] test_mem_consistent; 176 test_case "overwrite" [ bytes; bytes; bytes ] test_overwrite; 177 test_case "table isolation" [ bytes; bytes; bytes ] test_table_isolation; 178 test_case "table roundtrip" [ bytes; bytes ] test_table_roundtrip; 179 test_case "put crash safety" [ bytes; bytes ] test_put_crash; 180 test_case "get crash safety" [ bytes ] test_get_crash; 181 test_case "delete crash safety" [ bytes ] test_delete_crash; 182 test_case "mem crash safety" [ bytes ] test_mem_crash; 183 test_case "empty key" [ bytes ] test_empty_key; 184 test_case "empty value" [ bytes ] test_empty_value; 185 test_case "both empty" [ const () ] test_both_empty; 186 test_case "multiple puts" [ bytes; list bytes ] test_multiple_puts; 187 test_case "put delete put" [ bytes; bytes; bytes ] test_put_delete_put; 188 ] )