aboutsummaryrefslogblamecommitdiffstats
path: root/lib/management/storage/lmdb.ml
blob: e1c43072b7ca401d3615b865976a3951e4550131 (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                             






                             
                                        
                                                                  
                                           











                                                             
               






                                                                                                   
                                                                       














                                                                          




                                        
 
                           


                                      

                                                                                      






                                 
                                                                              



                                     



                             
                           














                                                                                
 

                     


                                                          


                              
 




                               




                                                                         





                                                       
   
module B : Physical.BACKEND with type error = string = struct
  open External.Lmdb
  open Ctypes

  module MDB_Errors = struct
    let mdb_notfound = -30798
  end

  type configuration = { path : string }
  type connection = { env : mdb_env structure ptr; dbi : mdb_dbi }
  type error = string (* int *) (* FIXME *)

  type _ Effect.t +=
     | Current: mdb_txn structure ptr Effect.t
     | Rollback: unit Effect.t

  let parse (expr : Sexplib.Sexp.t) =
    let open Utilities.Result in
    let* keys = Configuration.map_of_sexp expr in
    let* path = Utilities.StringMap.find_opt "path" keys
                |> Option.to_result ~none:"Missing key: path"
                |> fmap (Configuration.atom_of_sexp)
    in
    Ok { path }

  let current_tx () = Effect.perform Current
  let current_tx' () = try current_tx () with Effect.Unhandled Current -> (from_voidp mdb_txn null)

  let with_transaction' env body =
    let open Utilities.Result in
    let success = ref false in
    let* tx = mdb_txn_begin' env (current_tx' ()) Unsigned.UInt.zero in
    Fun.protect
      (fun () ->
        match body () with
        | x ->
           let* () = mdb_txn_commit tx in
           success := true;
           Ok (Some x)
        | effect Current, k -> Effect.Deep.continue k tx
        (* Should a transaction abort be an error value? *)
        | effect Rollback, _ ->
           let* () = mdb_txn_abort tx in
           success := true;
           Ok None)
      ~finally:(fun () -> if not !success then mdb_txn_abort tx |> ignore)

  let with_transaction { env; _ } body =
    with_transaction' env body
    |> Result.map_error Int.to_string

  let abort _ = Effect.perform Rollback

  let connect { path; _ } =
    begin
      let open Utilities.Result in
      let* env = mdb_env_create' () in
      (* TODO: make the POSIX mode a config parameter *)
      match mdb_env_open env path Unsigned.UInt.zero (PosixTypes.Mode.of_int 420) with
      | Error x ->
         mdb_env_close env;
         Error x
      | Ok () ->
         Result.bind
           (with_transaction' env
              (fun () ->
                let* dbi = mdb_dbi_open' (current_tx ()) Unsigned.UInt.zero in
                Ok { env; dbi }))
           Option.get
    end
    |> Result.map_error Int.to_string

  let disconnect { env; _ } =
    mdb_env_close env

  let get { dbi; _ } hash =
    begin
      match mdb_get' (current_tx ()) dbi (Bytes.of_string hash) with
      | Ok x -> Ok (Some x)
      | Error e when e = MDB_Errors.mdb_notfound -> Ok (None)
      | Error e -> Error e
    end
    |> Result.map_error Int.to_string

  let put { dbi; _ } hash value =
    mdb_put' (current_tx ()) dbi (Bytes.of_string hash) value Unsigned.UInt.zero
    |> Result.map_error Int.to_string

  let exists conn hash =
    get conn hash
    |> Result.map Option.is_some

  let lift_result r =
    BatEnum.fold
      (fun acc x ->
        Result.bind acc
          (fun xs -> Result.bind x (fun x -> Ok (x::xs))))
      (Ok [])
      r
    |> Result.map BatList.enum

  let flatten_result = function
    | Error e -> Error e
    | Ok (Error e) -> Error e
    | Ok (Ok x) -> Ok x

  let get_many conn hashes = BatEnum.map (get conn) hashes |> lift_result

  let put_many conn values =
    with_transaction conn
      (fun () ->
        BatEnum.map (fun (k, v) -> put conn k v) values
        |> lift_result
        |> Result.map ignore)
    |> Result.map Option.get
    |> flatten_result

end