├── .gitignore ├── .travis.yml ├── README.md ├── dune-project ├── irmin-fdb.opam ├── src ├── dune ├── irmin_fdb.ml ├── irmin_fdb.mli ├── watch.ml └── watch.mli └── test ├── dune ├── test.ml └── test_fdb.ml /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | _tests 3 | *.native 4 | *.byte 5 | *.install 6 | .merlin -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | sudo: false 3 | services: 4 | - docker 5 | install: wget https://raw.githubusercontent.com/ocaml/ocaml-travisci-skeleton/master/.travis-opam.sh 6 | script: bash -ex ./.travis-opam.sh 7 | before_install: 8 | - wget https://www.foundationdb.org/downloads/6.0.15/ubuntu/installers/foundationdb-clients_6.0.15-1_amd64.deb 9 | - wget https://www.foundationdb.org/downloads/6.0.15/ubuntu/installers/foundationdb-server_6.0.15-1_amd64.deb 10 | - sudo dpkg -i foundationdb-clients_6.0.15-1_amd64.deb foundationdb-server_6.0.15-1_amd64.deb 11 | env: 12 | global: 13 | - PINS="irmin-fdb:. fdb:https://github.com/andreas/ocaml-fdb.git irmin irmin-chunk bigstringaf irmin-test:https://github.com/mirage/irmin.git" 14 | matrix: 15 | - OCAML_VERSION="4.07" PACKAGE="irmin-fdb" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `irmin-fdb` 2 | 3 | `irmin-fdb` implements an [Irmin](https://github.com/mirage/irmin) store backed by [FoundationDB](https://foundationdb.org). The implementation is based on [`ocaml-fdb`](https://github.com/andreas/ocaml-fdb). 4 | 5 | See the [Irmin tutorial](https://irmin.io/tutorial/backend) for more information onwriting storage backends for Irmin. 6 | 7 | ## Requirements 8 | 9 | Installing `libfdb` ([download here](https://www.foundationdb.org/download/)) is a requirement for using `irmin-fdb`. 10 | 11 | ## Install 12 | 13 | This package is not available on OPAM yet, but in the meantime you can install it in the following manner: 14 | 15 | ``` 16 | opam pin add irmin-fdb git://github.com/andreas/irmin-fdb.git 17 | ``` 18 | 19 | ## Build 20 | 21 | ``` 22 | dune build 23 | ``` 24 | 25 | ## Test 26 | 27 | ``` 28 | dune runtest 29 | ``` 30 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.6) 2 | -------------------------------------------------------------------------------- /irmin-fdb.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | maintainer: "Andreas Garnaes " 3 | authors: "Andreas Garnaes " 4 | homepage: "https://github.com/andreas/irmin-fdb" 5 | doc: "https://andreas.github.io/irmin-fdb/" 6 | bug-reports: "https://github.com/andreas/irmin-fdb/issues" 7 | dev-repo: "git+https://github.com/andreas/irmin-fdb.git" 8 | 9 | build: [ 10 | ["dune" "build" "-p" name "-j" jobs] 11 | ["dune" "runtest" "-p" name "-j" jobs] {with-test} 12 | ] 13 | 14 | depends: [ 15 | "ocaml" {>= "4.03.0"} 16 | "dune" {build} 17 | "irmin" 18 | "irmin-chunk" 19 | "lwt" 20 | "fdb" 21 | "irmin-test" {with-test} 22 | ] 23 | 24 | synopsis: "Irmin store backed by FoundationDB" 25 | -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name irmin_fdb) 3 | (public_name irmin-fdb) 4 | (libraries irmin irmin-chunk fdb lwt lwt.unix)) 5 | -------------------------------------------------------------------------------- /src/irmin_fdb.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | 3 | let src = Logs.Src.create "irmin.fdb" ~doc:"Irmin FoundationDB store" 4 | module Log = (val Logs.src_log src : Logs.LOG) 5 | 6 | module type IO = Fdb.IO with type 'a t = 'a Lwt.t 7 | 8 | module Option = struct 9 | let bind t f = match t with None -> None | Some t -> f t 10 | end 11 | 12 | module Read_only (IO : IO) (K: Irmin.Type.S) (V: Irmin.Type.S) = struct 13 | module F = struct 14 | include Fdb.Make(IO) 15 | 16 | exception Error of string 17 | 18 | let fail_if_error x = 19 | x >>= function 20 | | Ok ok -> Lwt.return ok 21 | | Error err -> Lwt.fail (Error (Fdb.Error.to_string err)) 22 | end 23 | 24 | type key = K.t 25 | type value = V.t 26 | type 'a t = { 27 | db : F.Database.t; 28 | prefix : string; 29 | } 30 | let v prefix config = 31 | let open F.Infix in 32 | let module C = Irmin.Private.Conf in 33 | let prefix = match C.get config C.root with 34 | | Some root -> Fdb.Tuple.pack [`Bytes root; `Bytes prefix] 35 | | None -> Fdb.Tuple.pack [`Bytes prefix] 36 | in 37 | begin 38 | F.open_database () >>|? fun db -> 39 | { db; prefix } 40 | end 41 | |> F.fail_if_error 42 | 43 | let cast t = (t :> [`Read | `Write] t) 44 | let batch t f = f (cast t) 45 | 46 | let value v = 47 | match Irmin.Type.of_bin_string V.t v with 48 | | Ok v -> Some v 49 | | Error (`Msg e) -> 50 | Log.err (fun l -> l "Irmin_fdb.value %s" e); 51 | None 52 | 53 | let prefixed_key t k = 54 | let key_string = Irmin.Type.to_string K.t k in 55 | t.prefix ^ (Fdb.Tuple.pack [`Int 0; `Bytes key_string]) 56 | 57 | let find t key = 58 | let open F.Infix in 59 | let key = prefixed_key t key in 60 | Log.debug (fun f -> f "find %s" (String.escaped key)); 61 | begin 62 | F.Database.get t.db ~key 63 | >>|? function 64 | | None -> None 65 | | Some x -> value x 66 | end 67 | |> F.fail_if_error 68 | 69 | let mem t key = 70 | let open F.Infix in 71 | let key = prefixed_key t key in 72 | Log.debug (fun f -> f "mem %s" (String.escaped key)); 73 | let key_selector = Fdb.Key_selector.first_greater_or_equal key in 74 | begin 75 | F.Database.get_key t.db ~key_selector 76 | >>|? fun key' -> 77 | String.equal key key' 78 | end 79 | |> F.fail_if_error 80 | 81 | end 82 | 83 | module Append_only (IO : IO) (K: Irmin.Type.S) (V: Irmin.Type.S) = struct 84 | 85 | include Read_only(IO)(K)(V) 86 | 87 | let v config = 88 | v "obj" config 89 | 90 | let add t key value = 91 | let key = prefixed_key t key in 92 | Log.debug (fun f -> f "add -> %s" (String.escaped key)); 93 | let value = Irmin.Type.to_bin_string V.t value in 94 | F.Database.set t.db ~key ~value 95 | |> F.fail_if_error 96 | 97 | end 98 | 99 | module Atomic_write (IO : IO) (K: Irmin.Type.S) (V: Irmin.Type.S) = struct 100 | 101 | module RO = Read_only(IO)(K)(V) 102 | module F = RO.F 103 | module W = Watch.Make(K)(V)(IO) 104 | 105 | type t = { 106 | ro: unit RO.t; 107 | w: W.t; 108 | } 109 | type key = RO.key 110 | type value = RO.value 111 | type watch = W.watch 112 | 113 | let v config = 114 | RO.v "data" config >>= fun ro -> 115 | W.v ~db:ro.db ~prefix:ro.prefix >>= fun w -> 116 | Lwt.return { ro; w } 117 | 118 | let key_without_prefix k = 119 | match k with 120 | | [`Bytes _prefix; `Int 0; `Bytes k] -> k 121 | | _ -> failwith ("Invalid key: " ^ (Fdb.Tuple.pack k |> String.escaped)) 122 | 123 | let key_of_string k = 124 | match Irmin.Type.of_bin_string K.t k with 125 | | Ok v -> v 126 | | Error (`Msg e) -> failwith ("Irmin_fdb.key_of_string " ^ e) 127 | 128 | let find t = RO.find t.ro 129 | let mem t = RO.mem t.ro 130 | 131 | let watch t = W.watch t.w 132 | let watch_key t = W.watch_key t.w 133 | let unwatch t = W.unwatch t.w 134 | 135 | let list t = 136 | let open F.Infix in 137 | Log.debug (fun f -> f "list"); 138 | F.Database.with_tx t.ro.db ~f:(fun tx -> 139 | let prefix = Fdb.Key_selector.first_greater_than (t.ro.prefix ^ (Fdb.Tuple.pack [`Int 0])) in 140 | F.Transaction.get_range_prefix tx ~prefix >>=? fun range_result -> 141 | F.Range_result.to_list range_result 142 | ) 143 | >>|? List.map (fun kv -> 144 | kv 145 | |> Fdb.Key_value.key_bigstring 146 | |> Fdb.Tuple.unpack_bigstring 147 | |> key_without_prefix 148 | |> key_of_string 149 | ) 150 | |> F.fail_if_error 151 | 152 | let set t key value = 153 | let k = RO.prefixed_key t.ro key in 154 | Log.debug (fun f -> f "update %s" (String.escaped k)); 155 | let v = Irmin.Type.to_bin_string V.t value in 156 | F.Database.with_tx t.ro.db ~f:(fun tx -> 157 | F.Transaction.set tx ~key:k ~value:v; 158 | W.notify t.w tx key (Some v); 159 | Lwt.return (Ok ()) 160 | ) 161 | |> F.fail_if_error 162 | 163 | let remove t key = 164 | let k = RO.prefixed_key t.ro key in 165 | Log.debug (fun f -> f "remove %s" (String.escaped k)); 166 | F.Database.with_tx t.ro.db ~f:(fun tx -> 167 | F.Transaction.clear tx ~key:k; 168 | W.notify t.w tx key None; 169 | Lwt.return (Ok ()) 170 | ) 171 | |> F.fail_if_error 172 | 173 | let test_and_set t key ~test ~set = 174 | let open F.Infix in 175 | let k = RO.prefixed_key t.ro key in 176 | Log.debug (fun f -> f "test_and_set %s" (String.escaped k)); 177 | F.Database.with_tx t.ro.db ~f:(fun tx -> 178 | F.Transaction.get tx ~key:k >>=? fun v -> 179 | let v = Option.bind v RO.value in 180 | if Irmin.Type.(equal (option V.t)) test v then ( 181 | let () = match set with 182 | | None -> 183 | F.Transaction.clear tx ~key:k; 184 | W.notify t.w tx key None 185 | | Some set -> 186 | let value = Irmin.Type.to_bin_string V.t set in 187 | F.Transaction.set tx ~key:k ~value; 188 | W.notify t.w tx key (Some value) 189 | in 190 | Lwt.return (Ok true) 191 | ) else 192 | Lwt.return (Ok false) 193 | ) 194 | |> F.fail_if_error 195 | end 196 | 197 | let config () = Irmin.Private.Conf.empty 198 | 199 | module Make (IO : IO) 200 | (M: Irmin.Metadata.S) 201 | (C: Irmin.Contents.S) 202 | (P: Irmin.Path.S) 203 | (B: Irmin.Branch.S) 204 | (H: Irmin.Hash.S) 205 | = struct 206 | module AO = Append_only(IO) 207 | module AW = Atomic_write(IO) 208 | include Irmin.Make(Irmin_chunk.Content_addressable(AO))(AW)(M)(C)(P)(B)(H) 209 | end 210 | 211 | module KV (IO : IO) (C: Irmin.Contents.S) = 212 | Make 213 | (IO) 214 | (Irmin.Metadata.None) 215 | (C) 216 | (Irmin.Path.String_list) 217 | (Irmin.Branch.String) 218 | (Irmin.Hash.SHA1) 219 | -------------------------------------------------------------------------------- /src/irmin_fdb.mli: -------------------------------------------------------------------------------- 1 | (** FoundationDB store. *) 2 | 3 | val config: unit -> Irmin.config 4 | (** Configuration values. *) 5 | 6 | module type IO = Fdb.IO with type 'a t = 'a Lwt.t 7 | 8 | module Append_only (IO : IO): Irmin.APPEND_ONLY_STORE_MAKER 9 | (** A FoundationDB store for append-only values. *) 10 | 11 | module Atomic_write (IO : IO): Irmin.ATOMIC_WRITE_STORE_MAKER 12 | (** A FoundationDB store with atomic-write guarantees. *) 13 | 14 | module Make (IO : IO): Irmin.S_MAKER 15 | (** A FoundationDB Irmin store. *) 16 | 17 | module KV (IO : IO): Irmin.KV_MAKER 18 | (** A FoundationDB KV store. *) 19 | -------------------------------------------------------------------------------- /src/watch.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | 3 | let src = Logs.Src.create "irmin.fdb.watch" ~doc:"Irmin FoundationDB watches" 4 | module Log = (val Logs.src_log src : Logs.LOG) 5 | 6 | module type IO = Fdb.IO with type 'a t = 'a Lwt.t 7 | 8 | module Option = struct 9 | let bind t f = match t with None -> None | Some t -> f t 10 | end 11 | 12 | module List = struct 13 | include List 14 | 15 | let last t = 16 | List.nth t (List.length t - 1) 17 | end 18 | 19 | module Make(K : Irmin.Type.S)(V : Irmin.Type.S)(IO : IO) = struct 20 | module W = Irmin.Private.Watch.Make(K)(V) 21 | module F = struct 22 | include Fdb.Make(IO) 23 | 24 | exception Error of string 25 | 26 | let fail_if_error x = 27 | x >>= function 28 | | Ok ok -> Lwt.return ok 29 | | Error err -> Lwt.fail (Error (Fdb.Error.to_string err)) 30 | end 31 | 32 | module KMap = Map.Make(struct type t = K.t let compare = Irmin.Type.compare K.t end) 33 | 34 | type t = { 35 | db : F.Database.t; 36 | data_prefix : string; 37 | changelog_prefix : string; 38 | watch_all_key : string; 39 | w : W.t; 40 | mutable keys: (int * F.Watch.t ref) KMap.t; 41 | mutable glob : (int * F.Watch.t ref) option; 42 | } 43 | 44 | type watch = { 45 | key : [`Glob | `Key of K.t]; 46 | irmin_watch : W.watch; 47 | } 48 | 49 | type key = K.t 50 | type value = V.t 51 | 52 | let prefixed_key t k = 53 | let key_string = Irmin.Type.to_string K.t k in 54 | t.data_prefix ^ (Fdb.Tuple.pack [`Int 0; `Bytes key_string]) 55 | 56 | let key_of_string k = 57 | match Irmin.Type.of_bin_string K.t k with 58 | | Ok v -> v 59 | | Error (`Msg e) -> failwith ("Irmin_fdb.key_of_string " ^ e) 60 | 61 | let value_of_string v = 62 | match Irmin.Type.of_bin_string V.t v with 63 | | Ok v -> Some v 64 | | Error (`Msg e) -> 65 | Log.err (fun l -> l "Irmin_fdb.value_of_string %s" e); 66 | None 67 | 68 | let v ~db ~prefix = 69 | let changelog_prefix = prefix ^ (Fdb.Tuple.pack [`Int 1]) in 70 | let watch_all_key = changelog_prefix in 71 | F.Database.atomic_op db ~op:Fdb.Atomic_op.max ~key:watch_all_key ~param:"\000" |> F.fail_if_error >>= fun () -> 72 | let w = W.v () in 73 | let keys = KMap.empty in 74 | let glob = None in 75 | Lwt.return { db; data_prefix = prefix; changelog_prefix; watch_all_key; w; keys; glob } 76 | 77 | let keep_watching t key f = 78 | let open F.Infix in 79 | let rec aux watch = 80 | begin 81 | F.Watch.to_io !watch >>=? fun () -> 82 | F.Database.with_tx t.db ~f:(fun tx -> 83 | let w = F.Transaction.watch tx ~key in 84 | F.Transaction.get tx ~key >>|? fun value -> 85 | (w, value) 86 | ) 87 | end 88 | >>= function 89 | | Error err -> 90 | Log.err (fun f -> f "Watch failed: %s" (Fdb.Error.to_string err)); 91 | Lwt.return_unit 92 | | Ok (w, value) -> 93 | watch := w; 94 | f value 95 | >>= fun () -> 96 | aux watch 97 | in 98 | F.fail_if_error (F.Database.watch t.db ~key) 99 | >|= fun w -> 100 | let watch = ref w in 101 | Lwt.async (fun () -> 102 | aux watch 103 | ); 104 | watch 105 | 106 | let watch_key t key ?init f = 107 | Log.debug (fun f -> f "watch_key: %a" (Irmin.Type.pp K.t) key); 108 | begin match KMap.find key t.keys with 109 | | count, fdb_watch -> 110 | t.keys <- KMap.add key (count+1, fdb_watch) t.keys; 111 | Lwt.return_unit 112 | | exception Not_found -> 113 | let k = prefixed_key t key in 114 | keep_watching t k (fun value -> 115 | let v = Option.bind value value_of_string in 116 | W.notify t.w key v 117 | ) >|= fun fdb_watch -> 118 | t.keys <- KMap.add key (1, fdb_watch) t.keys 119 | end >>= fun () -> 120 | W.watch_key t.w key ?init f >|= fun irmin_watch -> 121 | { key = `Key key; irmin_watch } 122 | 123 | let unwatch_key t key = 124 | match KMap.find key t.keys with 125 | | 1, fdb_watch -> 126 | F.Watch.cancel !fdb_watch; 127 | t.keys <- KMap.remove key t.keys 128 | | count, fdb_watch -> 129 | t.keys <- KMap.add key (count - 1, fdb_watch) t.keys 130 | | exception Not_found -> () 131 | 132 | let unwatch_glob t = 133 | match t.glob with 134 | | Some (1, fdb_watch) -> 135 | F.Watch.cancel !fdb_watch; 136 | t.glob <- None 137 | | Some (count, fdb_watch) -> 138 | t.glob <- Some (count - 1, fdb_watch) 139 | | None -> () 140 | 141 | let unwatch t watch = 142 | Log.debug (fun f -> f "unwatch"); 143 | let () = match watch.key with 144 | | `Key k -> unwatch_key t k 145 | | `Glob -> unwatch_glob t 146 | in 147 | W.unwatch t.w watch.irmin_watch 148 | 149 | let start_fdb_watch_all t = 150 | let open F.Infix in 151 | let watch_all_end_key = Fdb.Tuple.strinc t.changelog_prefix in 152 | let stop = Fdb.Key_selector.first_greater_or_equal watch_all_end_key in 153 | let key_selector = Fdb.Key_selector.last_less_than watch_all_end_key in 154 | F.Database.get_key t.db ~key_selector |> F.fail_if_error 155 | >>= fun key -> 156 | let cursor = ref key in 157 | keep_watching t t.watch_all_key (fun _ -> 158 | let start = Fdb.Key_selector.first_greater_than !cursor in 159 | F.Database.with_tx t.db ~f:(fun tx -> 160 | F.Transaction.get_range tx ~start ~stop >>=? fun range_result -> 161 | F.Range_result.to_list range_result 162 | ) 163 | |> F.fail_if_error >>= fun events -> 164 | cursor := List.last events |> Fdb.Key_value.key; 165 | events 166 | |> List.map Fdb.Key_value.value_bigstring 167 | |> List.map Fdb.Tuple.unpack_bigstring 168 | |> Lwt_list.iter_s (function 169 | | [`Int 0; `Bytes k] -> 170 | let key = key_of_string k in 171 | W.notify t.w key None 172 | | [`Int 1; `Bytes k; `Bytes v] -> 173 | let key = key_of_string k in 174 | let value = value_of_string v in 175 | W.notify t.w key value 176 | | _ -> assert false 177 | ) 178 | ) 179 | 180 | let fdb_watch_all t = 181 | match t.glob with 182 | | Some _ -> Lwt.return_unit 183 | | None -> 184 | start_fdb_watch_all t >|= fun fdb_watch -> 185 | t.glob <- Some (1, fdb_watch) 186 | 187 | let watch t ?init f = 188 | Log.debug (fun f -> f "watch"); 189 | fdb_watch_all t >>= fun () -> 190 | W.watch t.w ?init f >|= fun irmin_watch -> 191 | { key = `Glob; irmin_watch } 192 | 193 | let notify t tx key value = 194 | Log.debug (fun f -> f "notify: %a" (Irmin.Type.pp K.t) key); 195 | F.Transaction.atomic_op tx ~key:t.watch_all_key ~op:Fdb.Atomic_op.add ~param:"\001"; 196 | let k = Irmin.Type.to_bin_string K.t key in 197 | let param = match value with 198 | | None -> 199 | Fdb.Tuple.pack [`Int 0; `Bytes k] 200 | | Some v -> 201 | Fdb.Tuple.pack [`Int 1; `Bytes k; `Bytes v] 202 | in 203 | let buf = Buffer.create 32 in 204 | Buffer.add_string buf t.changelog_prefix; 205 | Buffer.add_string buf "0000000000"; 206 | Buffer.add_char buf (Char.chr (String.length t.changelog_prefix)); 207 | Buffer.add_string buf "\000\000\000"; 208 | let key = Buffer.contents buf in 209 | F.Transaction.atomic_op tx ~key ~op:Fdb.Atomic_op.set_versionstamped_key ~param 210 | end 211 | -------------------------------------------------------------------------------- /src/watch.mli: -------------------------------------------------------------------------------- 1 | module type IO = Fdb.IO with type 'a t = 'a Lwt.t 2 | 3 | module Make(K : Irmin.Type.S)(V : Irmin.Type.S)(IO : IO) : sig 4 | type t 5 | type watch 6 | type key = K.t 7 | type value = V.t 8 | 9 | val v : db:Fdb.database -> prefix:string -> t Lwt.t 10 | val notify: t -> Fdb.transaction -> K.t -> string option -> unit 11 | val watch_key: t -> K.t -> ?init:value -> (value Irmin.Diff.t -> unit Lwt.t) -> watch Lwt.t 12 | val watch: t -> ?init:(key * value) list -> 13 | (key -> value Irmin.Diff.t -> unit Lwt.t) -> watch Lwt.t 14 | val unwatch: t -> watch -> unit Lwt.t 15 | end 16 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name test_fdb) 3 | (modules test_fdb) 4 | (libraries irmin-fdb irmin-test)) 5 | 6 | (executable 7 | (name test) 8 | (modules test) 9 | (libraries digestif.c test_fdb)) 10 | 11 | (alias 12 | (name runtest) 13 | (package irmin-fdb) 14 | (action (run ./test.exe -q --color=always))) 15 | -------------------------------------------------------------------------------- /test/test.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | Irmin_test.Store.run "irmin-fdb" ~misc:[] [ 3 | `Quick , Test_fdb.suite; 4 | ] 5 | -------------------------------------------------------------------------------- /test/test_fdb.ml: -------------------------------------------------------------------------------- 1 | open Lwt.Infix 2 | 3 | module IO = struct 4 | type +'a t = 'a Lwt.t 5 | 6 | type 'a u = 'a Lwt.t * 'a Lwt.u 7 | 8 | type notification = int 9 | 10 | let read = fst 11 | 12 | let fill (_, u) = Lwt.wakeup_later u 13 | 14 | let create = Lwt.wait 15 | 16 | let bind t ~f = Lwt.bind t f 17 | 18 | let map t ~f = Lwt.map f t 19 | 20 | let return = Lwt.return 21 | 22 | let make_notification f = 23 | Lwt_unix.make_notification ~once:true f 24 | 25 | let send_notification = Lwt_unix.send_notification 26 | end 27 | 28 | let store = 29 | Irmin_test.store (module Irmin_fdb.Make (IO)) (module Irmin.Metadata.None) 30 | 31 | let config = Irmin_fdb.config () 32 | 33 | module F = Fdb.Make (IO) 34 | 35 | let clean () = 36 | let open F.Infix in 37 | begin 38 | F.open_database () >>=? fun db -> 39 | F.Database.clear_range db ~start:"\x01" ~stop:"\xff" 40 | end >|= function 41 | | Ok () -> () 42 | | Error err -> failwith (Fdb.Error.to_string err) 43 | 44 | let init () = Lwt.return_unit 45 | let stats = None 46 | let suite = { Irmin_test.name = "FDB"; init; clean; config; store; stats } 47 | --------------------------------------------------------------------------------