2017-02-24 20:17:53 +04:00
|
|
|
(**************************************************************************)
|
|
|
|
(* *)
|
2018-02-06 00:17:03 +04:00
|
|
|
(* Copyright (c) 2014 - 2018. *)
|
2017-02-24 20:17:53 +04:00
|
|
|
(* Dynamic Ledger Solutions, Inc. <contact@tezos.com> *)
|
|
|
|
(* *)
|
|
|
|
(* All rights reserved. No warranty, explicit or implicit, provided. *)
|
|
|
|
(* *)
|
|
|
|
(**************************************************************************)
|
|
|
|
|
2017-05-31 20:27:11 +04:00
|
|
|
module List = ListLabels
|
2017-02-24 20:17:53 +04:00
|
|
|
|
2017-05-31 20:27:11 +04:00
|
|
|
type t = LevelDB.db
|
2017-02-24 20:17:53 +04:00
|
|
|
type key = string list
|
|
|
|
type value = MBytes.t
|
|
|
|
|
|
|
|
type error += Unknown of string list
|
|
|
|
|
2017-09-29 20:43:13 +04:00
|
|
|
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)
|
|
|
|
|
2017-05-31 20:27:11 +04:00
|
|
|
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 =
|
2017-11-27 09:13:12 +04:00
|
|
|
Lwt.return (Option.map ~f:MBytes.of_string (LevelDB.get t (concat key)))
|
2017-03-30 15:16:21 +04:00
|
|
|
|
2017-02-24 20:17:53 +04:00
|
|
|
let read t key =
|
2017-05-31 20:27:11 +04:00
|
|
|
match LevelDB.get t (concat key) with
|
2017-02-24 20:17:53 +04:00
|
|
|
| None -> fail (Unknown key)
|
2017-05-31 20:27:11 +04:00
|
|
|
| Some k -> return (MBytes.of_string k)
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
let read_exn t key =
|
2017-05-31 20:27:11 +04:00
|
|
|
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
|
2017-02-24 20:17:53 +04:00
|
|
|
|
|
|
|
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))
|