(**************************************************************************) (* *) (* Copyright (c) 2014 - 2018. *) (* Dynamic Ledger Solutions, Inc. *) (* *) (* All rights reserved. No warranty, explicit or implicit, provided. *) (* *) (**************************************************************************) module List = ListLabels type t = LevelDB.db type key = string list type value = MBytes.t type error += Unknown of string list let () = Error_monad.register_error_kind `Permanent ~id:"raw_store.unknown" ~title:"Missing key in store" ~description:"Missing key in store" ~pp:(fun ppf keys -> Format.fprintf ppf "Missing key in store: %s" (String.concat "/" keys)) Data_encoding.(obj1 (req "key" (list string))) (function Unknown keys -> Some keys | _ -> None) (fun keys -> Unknown keys) let concat = String.concat "/" let split = String.split_on_char '/' let init path = try return (LevelDB.open_db path) with exn -> Lwt.return (error_exn exn) let close t = LevelDB.close t let known t key = Lwt.return (LevelDB.mem t (concat key)) let read_opt t key = Lwt.return (Option.map ~f:MBytes.of_string (LevelDB.get t (concat key))) let read t key = match LevelDB.get t (concat key) with | None -> fail (Unknown key) | Some k -> return (MBytes.of_string k) let read_exn t key = Lwt.wrap2 LevelDB.get_exn t (concat key) >|= MBytes.of_string let store t k v = LevelDB.put t (concat k) (MBytes.to_string v) ; Lwt.return_unit let remove t k = LevelDB.delete t (concat k) ; Lwt.return_unit let is_prefix s s' = String.(length s <= length s' && compare s (sub s' 0 (length s)) = 0) let known_dir t k = let ret = ref false in let k = concat k in LevelDB.iter_from begin fun kk _ -> if is_prefix k kk then ret := true ; false end t k ; Lwt.return !ret let remove_dir t k = let k = concat k in let batch = LevelDB.Batch.make () in LevelDB.iter_from begin fun kk _ -> if is_prefix k kk then begin LevelDB.Batch.delete batch kk ; true end else false end t k ; LevelDB.Batch.write t batch ; Lwt.return_unit let list_equal l1 l2 len = if len < 0 || len > List.length l1 || len > List.length l2 then invalid_arg "list_compare: invalid len" ; let rec inner l1 l2 len = match len, l1, l2 with | 0, _, _ -> true | _, [], _ | _, _, [] -> false | _, h1 :: t1, h2 :: t2 -> if h1 <> h2 then false else inner t1 t2 (pred len) in inner l1 l2 len let is_child ~parent ~child = let plen = List.length parent in let clen = List.length child in clen > plen && list_equal parent child plen let list_sub l pos len = if len < 0 || pos < 0 || pos + len > List.length l then invalid_arg "list_sub" ; let rec inner (acc, n) = function | [] -> List.rev acc | h :: t -> if n = 0 then List.rev acc else inner (h :: acc, pred n) t in inner ([], len) l let fold t k ~init ~f = let k_concat = concat k in let base_len = List.length k in let i = LevelDB.Iterator.make t in LevelDB.Iterator.seek i k_concat 0 (String.length k_concat) ; let returned = Hashtbl.create 31 in let rec inner acc = match LevelDB.Iterator.valid i with | false -> Lwt.return acc | true -> let kk = LevelDB.Iterator.get_key i in let kk_split = split kk in match is_child ~child:kk_split ~parent:k with | false -> Lwt.return acc | true -> let cur_len = List.length kk_split in LevelDB.Iterator.next i ; if cur_len = succ base_len then begin (f (`Key kk_split) acc) >>= inner end else begin let dir = list_sub kk_split 0 (succ base_len) in if Hashtbl.mem returned dir then inner acc else begin Hashtbl.add returned dir () ; (f (`Dir dir) acc) >>= inner end end ; in inner init let fold_keys s k ~init ~f = let rec loop k acc = fold s k ~init:acc ~f:(fun file acc -> match file with | `Key k -> f k acc | `Dir k -> loop k acc) in loop k init let keys t = fold_keys t ~init:[] ~f:(fun k acc -> Lwt.return (k :: acc))