(*--------------------------------------------------------------------------- Copyright (c) 2025 Thomas Gazagnaire. All rights reserved. SPDX-License-Identifier: MIT ---------------------------------------------------------------------------*) (** Pure OCaml B-tree backed key-value store. A simple key-value store with SQLite-compatible semantics using a pure OCaml B-tree implementation. Supports namespaced tables and reading any SQLite database. *) type t (** A B-tree backed key-value store. *) val pp : t Fmt.t (** Pretty-print a database handle. *) (** {1 Record Values} Re-exported from {!Btree.Record} so users don't need to depend on btree directly. *) type value = Btree.Record.value = | Vnull | Vint of int64 | Vfloat of float | Vblob of string | Vtext of string (** Column values decoded from SQLite records. *) val pp_value : Format.formatter -> value -> unit (** Pretty-print a value. *) (** {1 Schema} *) type column = { col_name : string; col_affinity : string; col_is_rowid_alias : bool; } (** A column definition parsed from CREATE TABLE SQL. [col_is_rowid_alias] is [true] for [INTEGER PRIMARY KEY] columns, which are aliases for the rowid. *) type schema = { tbl_name : string; columns : column list; sql : string } (** Table schema with the original CREATE TABLE SQL. *) (** {1 Database Lifecycle} *) val in_memory : unit -> t (** [in_memory ()] creates a purely in-memory database. No file I/O is performed. Useful for testing. *) val open_ : sw:Eio.Switch.t -> ?create:bool -> Eio.Fs.dir_ty Eio.Path.t -> t (** [open_ ~sw ?create path] opens a database at [path]. If [create] is [true] (the default), the database is created if it does not exist. If [create] is [false], the file must already exist or [Sys_error] is raised. {b Warning}: does {b not} truncate existing files. Works with any SQLite database, not just ones created by this library. If the database contains a [kv] table, the KV API functions below will work; otherwise, use the generic read API. @raise Failure if the file doesn't exist or is not a valid SQLite database. *) val sync : t -> unit (** [sync t] flushes all pending writes to disk. Uses a write-ahead log for crash safety when backed by a file. *) val close : t -> unit (** [close t] syncs and closes the database. *) val with_transaction : t -> (unit -> 'a) -> 'a (** [with_transaction t f] runs [f ()] atomically. If [f] raises an exception, all modifications to the database are rolled back and the exception is re-raised. If [f] returns normally, the changes are kept in memory but not yet synced to disk — call {!sync} for durability. Transactions do not nest: calling [with_transaction] inside [f] is permitted but the inner transaction has no independent rollback — a failure in the inner transaction rolls back to the outermost savepoint. *) (** {1 Key-Value API} These functions operate on the default [kv] table. They raise [Failure] if the database was opened from a file that has no [kv] table. *) val find : t -> string -> string option (** [find t key] returns the value for [key], or [None] if not found. *) val put : t -> string -> string -> unit (** [put t key value] stores [value] at [key], replacing any existing value. *) val delete : t -> string -> unit (** [delete t key] removes [key] from the store. No-op if key doesn't exist. *) val mem : t -> string -> bool (** [mem t key] is [true] if [key] exists in the store. *) val iter : t -> f:(string -> string -> unit) -> unit (** [iter t ~f] calls [f key value] for each entry in the store. *) val fold : t -> init:'a -> f:(string -> string -> 'a -> 'a) -> 'a (** [fold t ~init ~f] folds over all entries in the store. *) (** {1 Generic Read API} Read any table in the database, regardless of schema. *) val tables : t -> schema list (** [tables t] returns the schema of every table in the database. *) val iter_table : t -> string -> f:(int64 -> value list -> unit) -> unit (** [iter_table t name ~f] calls [f rowid values] for each row in table [name]. For [INTEGER PRIMARY KEY] columns, [Vnull] is replaced with [Vint rowid]. Trailing [Vnull]s are padded if the record has fewer values than columns. @raise Failure if the table doesn't exist. *) val fold_table : t -> string -> init:'a -> f:(int64 -> value list -> 'a -> 'a) -> 'a (** [fold_table t name ~init ~f] folds over all rows in table [name]. *) val read_table : t -> string -> (int64 * value list) list (** [read_table t name] returns all rows from table [name] as a list. *) (** {1 Generic Write API} Create arbitrary tables and insert rows. *) exception Unique_violation of string (** Raised by {!insert} when a row would violate a [UNIQUE] constraint. The string names the constrained columns (e.g. ["provider, provider_uid"]). *) val create_table : t -> sql:string -> unit (** [create_table t ~sql] creates a new table from a CREATE TABLE statement. The SQL is stored in sqlite_master and the column definitions are parsed for schema metadata. [UNIQUE] constraints (both column-level and table-level) are parsed and enforced on subsequent {!insert} calls. *) val insert : t -> table:string -> value list -> int64 (** [insert t ~table values] inserts a row into [table] with the given column values. Returns the rowid of the inserted row. For tables with an [INTEGER PRIMARY KEY] column, if the corresponding value is [Vint n], the row is inserted with rowid [n]. If the value is [Vnull], the rowid is auto-assigned. @raise Failure if the table doesn't exist. @raise Unique_violation if the row violates a [UNIQUE] constraint. *) val delete_row : t -> table:string -> int64 -> unit (** [delete_row t ~table rowid] deletes the row with the given [rowid] from [table]. No-op if the rowid doesn't exist. @raise Failure if the table doesn't exist. *) (** {1 Schema Parsing} *) val parse_create_table : string -> column list (** [parse_create_table sql] parses a CREATE TABLE statement and returns the column definitions. Returns an empty list if parsing fails. *) (** {1 Namespaced Tables} Tables provide isolated key-value namespaces within a single database. *) module Table : sig type db = t (** The parent database type. *) type t (** A namespaced table within a database. *) val create : db -> name:string -> t (** [create db ~name] creates or opens a table named [name] within [db]. The table name must be a valid SQL identifier. *) val find : t -> string -> string option (** [find t key] returns the value for [key], or [None]. *) val put : t -> string -> string -> unit (** [put t key value] stores [value] at [key]. *) val delete : t -> string -> unit (** [delete t key] removes [key] from the table. *) val mem : t -> string -> bool (** [mem t key] is [true] if [key] exists in the table. *) val iter : t -> f:(string -> string -> unit) -> unit (** [iter t ~f] calls [f key value] for each entry in the table. *) end