Minimal SQLite key-value store for OCaml

ocaml-sqlite: Update ocamlformat to 0.28.1

Formatting changes only.

+86 -42
+1 -1
.ocamlformat
··· 1 - version=0.27.0 1 + version=0.28.1
+4 -7
fuzz/fuzz_sqlite.ml
··· 22 22 (fun () -> 23 23 Sqlite.put db key value; 24 24 let result = Sqlite.get db key in 25 - check_eq ~pp:Format.pp_print_string 26 - ~eq:( = ) 27 - (Option.get result) value) 25 + check_eq ~pp:Format.pp_print_string ~eq:( = ) (Option.get result) value) 28 26 29 27 (* Test that delete actually removes the key *) 30 28 let test_delete_removes key value = ··· 84 82 Sqlite.put db key value1; 85 83 Sqlite.put db key value2; 86 84 let result = Sqlite.get db key in 87 - check_eq ~pp:Format.pp_print_string 88 - ~eq:( = ) 89 - (Option.get result) value2) 85 + check_eq ~pp:Format.pp_print_string ~eq:( = ) (Option.get result) value2) 90 86 91 87 (* Test table isolation: same key in different tables *) 92 88 let test_table_isolation key value1 value2 = ··· 118 114 add_test ~name:"sqlite: delete removes" [ bytes; bytes ] test_delete_removes; 119 115 add_test ~name:"sqlite: mem consistent" [ bytes; bytes ] test_mem_consistent; 120 116 add_test ~name:"sqlite: overwrite" [ bytes; bytes; bytes ] test_overwrite; 121 - add_test ~name:"sqlite: table isolation" [ bytes; bytes; bytes ] test_table_isolation 117 + add_test ~name:"sqlite: table isolation" [ bytes; bytes; bytes ] 118 + test_table_isolation
+20 -10
lib/sqlite.ml
··· 30 30 (* Prepare statements *) 31 31 let get_stmt = Sqlite3.prepare db "SELECT value FROM kv WHERE key = ?" in 32 32 let put_stmt = 33 - Sqlite3.prepare db 34 - "INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)" 33 + Sqlite3.prepare db "INSERT OR REPLACE INTO kv (key, value) VALUES (?, ?)" 35 34 in 36 35 let delete_stmt = Sqlite3.prepare db "DELETE FROM kv WHERE key = ?" in 37 36 let mem_stmt = Sqlite3.prepare db "SELECT 1 FROM kv WHERE key = ?" in ··· 45 44 match Sqlite3.step stmt with 46 45 | Sqlite3.Rc.ROW -> Some (Sqlite3.column_blob stmt 0) 47 46 | Sqlite3.Rc.DONE -> None 48 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 47 + | rc -> 48 + failwith 49 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 49 50 50 51 let put t key value = 51 52 let stmt = t.put_stmt in ··· 67 68 match Sqlite3.step stmt with 68 69 | Sqlite3.Rc.ROW -> true 69 70 | Sqlite3.Rc.DONE -> false 70 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 71 + | rc -> 72 + failwith 73 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 71 74 72 75 let iter t ~f = 73 76 let stmt = t.iter_stmt in ··· 80 83 f key value; 81 84 loop () 82 85 | Sqlite3.Rc.DONE -> () 83 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 86 + | rc -> 87 + failwith 88 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 84 89 in 85 90 loop () 86 91 ··· 155 160 (Printf.sprintf "SELECT 1 FROM %s WHERE key = ?" table_name) 156 161 in 157 162 let iter_stmt = 158 - Sqlite3.prepare db 159 - (Printf.sprintf "SELECT key, value FROM %s" table_name) 163 + Sqlite3.prepare db (Printf.sprintf "SELECT key, value FROM %s" table_name) 160 164 in 161 165 { parent; name; get_stmt; put_stmt; delete_stmt; mem_stmt; iter_stmt } 162 166 ··· 167 171 match Sqlite3.step stmt with 168 172 | Sqlite3.Rc.ROW -> Some (Sqlite3.column_blob stmt 0) 169 173 | Sqlite3.Rc.DONE -> None 170 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 174 + | rc -> 175 + failwith 176 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 171 177 172 178 let put t key value = 173 179 let stmt = t.put_stmt in ··· 189 195 match Sqlite3.step stmt with 190 196 | Sqlite3.Rc.ROW -> true 191 197 | Sqlite3.Rc.DONE -> false 192 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 198 + | rc -> 199 + failwith 200 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 193 201 194 202 let iter t ~f = 195 203 let stmt = t.iter_stmt in ··· 202 210 f key value; 203 211 loop () 204 212 | Sqlite3.Rc.DONE -> () 205 - | rc -> failwith (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 213 + | rc -> 214 + failwith 215 + (Printf.sprintf "SQLite step error: %s" (Sqlite3.Rc.to_string rc)) 206 216 in 207 217 loop () 208 218 end
+4 -4
lib/sqlite.mli
··· 12 12 (** A SQLite-backed key-value store. *) 13 13 14 14 val create : Eio.Fs.dir_ty Eio.Path.t -> t 15 - (** [create path] opens or creates a SQLite database at [path]. 16 - Enables WAL mode for concurrent access. *) 15 + (** [create path] opens or creates a SQLite database at [path]. Enables WAL mode 16 + for concurrent access. *) 17 17 18 18 val get : t -> string -> string option 19 19 (** [get t key] returns the value for [key], or [None] if not found. *) ··· 51 51 (** A namespaced table within a database. *) 52 52 53 53 val create : db -> name:string -> t 54 - (** [create db ~name] creates or opens a table named [name] within [db]. 55 - The table name must be a valid SQL identifier. *) 54 + (** [create db ~name] creates or opens a table named [name] within [db]. The 55 + table name must be a valid SQL identifier. *) 56 56 57 57 val get : t -> string -> string option 58 58 (** [get t key] returns the value for [key], or [None]. *)
+33
sqlite.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "Minimal SQLite key-value store for OCaml" 4 + description: 5 + "A simple key-value store backed by SQLite with support for namespaced tables, WAL mode, and efficient batch operations." 6 + maintainer: ["Thomas Gazagnaire"] 7 + authors: ["Thomas Gazagnaire"] 8 + license: "MIT" 9 + depends: [ 10 + "dune" {>= "3.0"} 11 + "ocaml" {>= "5.1"} 12 + "eio" {>= "1.0"} 13 + "sqlite3" {>= "5.0"} 14 + "alcotest" {with-test} 15 + "eio_main" {with-test} 16 + "crowbar" {with-test} 17 + "odoc" {with-doc} 18 + ] 19 + build: [ 20 + ["dune" "subst"] {dev} 21 + [ 22 + "dune" 23 + "build" 24 + "-p" 25 + name 26 + "-j" 27 + jobs 28 + "@install" 29 + "@runtest" {with-test} 30 + "@doc" {with-doc} 31 + ] 32 + ] 33 + dev-repo: "https://tangled.org/gazagnaire.org/ocaml-sqlite"
+2 -1
test/dune
··· 3 3 (libraries sqlite alcotest eio_main)) 4 4 5 5 (cram 6 - (deps (package sqlite))) 6 + (deps 7 + (package sqlite)))
+22 -19
test/test_sqlite.ml
··· 10 10 let tmp_dir = Eio.Path.(cwd / "_build" / "test_sqlite") in 11 11 (try Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 tmp_dir with _ -> ()); 12 12 let path = 13 - Eio.Path.( 14 - tmp_dir / Printf.sprintf "test_%d.db" (Random.int 1_000_000)) 13 + Eio.Path.(tmp_dir / Printf.sprintf "test_%d.db" (Random.int 1_000_000)) 15 14 in 16 15 let db = Sqlite.create path in 17 16 Fun.protect ~finally:(fun () -> Sqlite.close db) (fun () -> f fs db) ··· 22 21 with_temp_db @@ fun _fs db -> 23 22 Sqlite.put db "key1" "value1"; 24 23 let result = Sqlite.get db "key1" in 25 - Alcotest.(check (option string)) "get returns put value" (Some "value1") result 24 + Alcotest.(check (option string)) 25 + "get returns put value" (Some "value1") result 26 26 27 27 let test_get_missing () = 28 28 with_temp_db @@ fun _fs db -> ··· 53 53 with_temp_db @@ fun _fs db -> 54 54 Sqlite.put db "key1" "value1"; 55 55 Alcotest.(check bool) "mem finds existing key" true (Sqlite.mem db "key1"); 56 - Alcotest.(check bool) "mem returns false for missing" false (Sqlite.mem db "missing") 56 + Alcotest.(check bool) 57 + "mem returns false for missing" false (Sqlite.mem db "missing") 57 58 58 59 let test_iter () = 59 60 with_temp_db @@ fun _fs db -> ··· 114 115 Sqlite.Table.put t2 "key" "value2"; 115 116 (* Also put in default table *) 116 117 Sqlite.put db "key" "default"; 117 - Alcotest.(check (option string)) "t1 isolated" (Some "value1") (Sqlite.Table.get t1 "key"); 118 - Alcotest.(check (option string)) "t2 isolated" (Some "value2") (Sqlite.Table.get t2 "key"); 119 - Alcotest.(check (option string)) "default isolated" (Some "default") (Sqlite.get db "key") 118 + Alcotest.(check (option string)) 119 + "t1 isolated" (Some "value1") 120 + (Sqlite.Table.get t1 "key"); 121 + Alcotest.(check (option string)) 122 + "t2 isolated" (Some "value2") 123 + (Sqlite.Table.get t2 "key"); 124 + Alcotest.(check (option string)) 125 + "default isolated" (Some "default") (Sqlite.get db "key") 120 126 121 127 let test_table_mem_delete () = 122 128 with_temp_db @@ fun _fs db -> ··· 165 171 let test_sql_injection_value () = 166 172 with_temp_db @@ fun _fs db -> 167 173 let malicious_values = 168 - [ 169 - "'; DROP TABLE kv; --"; 170 - "value' OR '1'='1"; 171 - "\x00\x00\x00"; 172 - ] 174 + [ "'; DROP TABLE kv; --"; "value' OR '1'='1"; "\x00\x00\x00" ] 173 175 in 174 176 List.iter 175 177 (fun value -> ··· 203 205 204 206 let test_valid_table_names () = 205 207 with_temp_db @@ fun _fs db -> 206 - let valid_names = [ "blocks"; "refs"; "meta"; "Table1"; "my_table"; "a"; "A123_test" ] in 208 + let valid_names = 209 + [ "blocks"; "refs"; "meta"; "Table1"; "my_table"; "a"; "A123_test" ] 210 + in 207 211 List.iter 208 212 (fun name -> 209 213 let table = Sqlite.Table.create db ~name in ··· 243 247 (* sync should not raise *) 244 248 Sqlite.sync db; 245 249 let result = Sqlite.get db "key" in 246 - Alcotest.(check (option string)) "data persists after sync" (Some "value") result 250 + Alcotest.(check (option string)) 251 + "data persists after sync" (Some "value") result 247 252 248 253 let suite = 249 254 [ ··· 275 280 [ 276 281 Alcotest.test_case "sql injection key" `Quick test_sql_injection_key; 277 282 Alcotest.test_case "sql injection value" `Quick test_sql_injection_value; 278 - Alcotest.test_case "table name validation" `Quick test_table_name_validation; 283 + Alcotest.test_case "table name validation" `Quick 284 + test_table_name_validation; 279 285 Alcotest.test_case "valid table names" `Quick test_valid_table_names; 280 286 ] ); 281 287 ( "unicode", ··· 283 289 Alcotest.test_case "unicode keys" `Quick test_unicode_keys; 284 290 Alcotest.test_case "unicode values" `Quick test_unicode_values; 285 291 ] ); 286 - ( "persistence", 287 - [ 288 - Alcotest.test_case "sync" `Quick test_sync; 289 - ] ); 292 + ("persistence", [ Alcotest.test_case "sync" `Quick test_sync ]); 290 293 ]