Minimal SQLite key-value store for OCaml
at main 150 lines 5.1 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. 3 SPDX-License-Identifier: MIT 4 ---------------------------------------------------------------------------*) 5 6(** Pure OCaml B-tree backed key-value store. 7 8 A simple key-value store with SQLite-compatible semantics using a pure OCaml 9 B-tree implementation. Supports namespaced tables and reading any SQLite 10 database. *) 11 12type t 13(** A B-tree backed key-value store. *) 14 15val pp : t Fmt.t 16(** Pretty-print a database handle. *) 17 18(** {1 Record Values} 19 20 Re-exported from {!Btree.Record} so users don't need to depend on btree 21 directly. *) 22 23type value = Btree.Record.value = 24 | Vnull 25 | Vint of int64 26 | Vfloat of float 27 | Vblob of string 28 | Vtext of string (** Column values decoded from SQLite records. *) 29 30val pp_value : Format.formatter -> value -> unit 31(** Pretty-print a value. *) 32 33(** {1 Schema} *) 34 35type column = { 36 col_name : string; 37 col_affinity : string; 38 col_is_rowid_alias : bool; 39} 40(** A column definition parsed from CREATE TABLE SQL. [col_is_rowid_alias] is 41 [true] for [INTEGER PRIMARY KEY] columns, which are aliases for the rowid. 42*) 43 44type schema = { tbl_name : string; columns : column list; sql : string } 45(** Table schema with the original CREATE TABLE SQL. *) 46 47(** {1 Database Lifecycle} *) 48 49val v : sw:Eio.Switch.t -> Eio.Fs.dir_ty Eio.Path.t -> t 50(** [v ~sw path] creates a new database at [path]. If a file already exists at 51 [path], it will be truncated. The switch [sw] controls the lifetime of the 52 underlying file handle. *) 53 54val in_memory : unit -> t 55(** [in_memory ()] creates a purely in-memory database. No file I/O is 56 performed. Useful for testing. *) 57 58val open_ : sw:Eio.Switch.t -> Eio.Fs.dir_ty Eio.Path.t -> t 59(** [open_ ~sw path] opens an existing database at [path]. Works with any SQLite 60 database, not just ones created by this library. If the database contains a 61 [kv] table, the KV API functions below will work; otherwise, use the generic 62 read API. 63 @raise Failure if the file doesn't exist or is not a valid SQLite database. 64*) 65 66val sync : t -> unit 67(** [sync t] flushes all pending writes to disk. *) 68 69val close : t -> unit 70(** [close t] syncs and closes the database. *) 71 72(** {1 Key-Value API} 73 74 These functions operate on the default [kv] table. They raise [Failure] if 75 the database was opened from a file that has no [kv] table. *) 76 77val find : t -> string -> string option 78(** [find t key] returns the value for [key], or [None] if not found. *) 79 80val put : t -> string -> string -> unit 81(** [put t key value] stores [value] at [key], replacing any existing value. *) 82 83val delete : t -> string -> unit 84(** [delete t key] removes [key] from the store. No-op if key doesn't exist. *) 85 86val mem : t -> string -> bool 87(** [mem t key] is [true] if [key] exists in the store. *) 88 89val iter : t -> f:(string -> string -> unit) -> unit 90(** [iter t ~f] calls [f key value] for each entry in the store. *) 91 92val fold : t -> init:'a -> f:(string -> string -> 'a -> 'a) -> 'a 93(** [fold t ~init ~f] folds over all entries in the store. *) 94 95(** {1 Generic Read API} 96 97 Read any table in the database, regardless of schema. *) 98 99val tables : t -> schema list 100(** [tables t] returns the schema of every table in the database. *) 101 102val iter_table : t -> string -> f:(int64 -> value list -> unit) -> unit 103(** [iter_table t name ~f] calls [f rowid values] for each row in table [name]. 104 For [INTEGER PRIMARY KEY] columns, [Vnull] is replaced with [Vint rowid]. 105 Trailing [Vnull]s are padded if the record has fewer values than columns. 106 @raise Failure if the table doesn't exist. *) 107 108val fold_table : 109 t -> string -> init:'a -> f:(int64 -> value list -> 'a -> 'a) -> 'a 110(** [fold_table t name ~init ~f] folds over all rows in table [name]. *) 111 112val read_table : t -> string -> (int64 * value list) list 113(** [read_table t name] returns all rows from table [name] as a list. *) 114 115(** {1 Schema Parsing} *) 116 117val parse_create_table : string -> column list 118(** [parse_create_table sql] parses a CREATE TABLE statement and returns the 119 column definitions. Returns an empty list if parsing fails. *) 120 121(** {1 Namespaced Tables} 122 123 Tables provide isolated key-value namespaces within a single database. *) 124 125module Table : sig 126 type db = t 127 (** The parent database type. *) 128 129 type t 130 (** A namespaced table within a database. *) 131 132 val create : db -> name:string -> t 133 (** [create db ~name] creates or opens a table named [name] within [db]. The 134 table name must be a valid SQL identifier. *) 135 136 val find : t -> string -> string option 137 (** [find t key] returns the value for [key], or [None]. *) 138 139 val put : t -> string -> string -> unit 140 (** [put t key value] stores [value] at [key]. *) 141 142 val delete : t -> string -> unit 143 (** [delete t key] removes [key] from the table. *) 144 145 val mem : t -> string -> bool 146 (** [mem t key] is [true] if [key] exists in the table. *) 147 148 val iter : t -> f:(string -> string -> unit) -> unit 149 (** [iter t ~f] calls [f key value] for each entry in the table. *) 150end