aboutsummaryrefslogblamecommitdiffstats
path: root/lib/RNT.ml
blob: 353621f26707ab84ebfe1faf6ce97cb6ebebafb5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                          
                                




                     








                                                                            





































                                                                                                   
                     

                                                        
                                                 

















































































                                                                                                   





                                                                          






                                                             


                                                  


                                                                                     

                                                          






























                                                                                          















                                                                       












































                                                                                       
open Utilities.Result
module S = Rnt.Backend.Storage.LMDB
module C = Rnt.Concepts
module P = Rnt.Protocols
module K = Rnt.Kernel
module FOL = Rnt.Evaluators.FOL
module I = K.Initialization.Make (S)
module B = K.Branch.Make (S)
module M = K.Multigroup.Make (S)
module SI = K.Storage.Make (S)
module SR = K.SubstantialRelation.Make (S)
module T = K.Tuple.Make (S)
module Sess = K.Session.Make (S)

module Error = struct
  open Condition

  let of_kernel c =
    let report = C.Condition.to_string_hum c in
    let report =
      match BatString.Exceptionless.split report ~by:"\n\nBacktrace:" with
      | Some (report, _) -> report
      | None -> report
    in
    condition "kernel-condition"
      (BatString.nreplace ~str:(BatString.trim report) ~sub:"\n\t" ~by:"; ")
      empty

  let invalid_path path =
    condition "invalid-path" "A path must start at \\ and have no empty segments"
      ("path" |=| Value.String path)

  let unnamed path =
    condition "unnamed-object" "The path does not name an object under a container"
      ("path" |=| Value.String path)

  let conflict path =
    condition "binding-conflict"
      "The path did not hold what was expected when the change was applied"
      ("path" |=| Value.String path)

  let unavailable path =
    condition "unavailable-object" "The object refused a new reference"
      ("path" |=| Value.String path)

  let heading_mismatch path =
    condition "heading-mismatch" "A tuple does not conform to the heading of its relation"
      ("path" |=| Value.String path)

  let not_a_relation name =
    condition "not-a-relation" "The object does not describe itself as a relation"
      ("object" |=| Value.String name)
end

type kernel = {connection: S.connection; root: P.Handle.t}
type handle = P.Handle.t
type path = string
type tuple = {type_: string; attributes: Value.t Utilities.StringMap.t}
type batch = {tuples: tuple list; exhausted: bool}

type blueprint =
  | Namespace
  | Branch
  | Multigroup
  | Relation of {heading: string Utilities.StringMap.t; tuples: Value.t Utilities.StringMap.t list}
  | Session of string

type plan = Base of path | Project of plan * string list
type session = {branch: string; snapshot: string}

let lift r = Result.map_error Error.of_kernel r
let value_of : C.Value.value -> Value.t = function String s -> String s | Integer n -> Integer n

let kernel_value_of : Value.t -> C.Value.value = function
  | String s -> String s
  | Integer n -> Integer n

let tuple_of ({type_; attributes} : C.Tuple.t) =
  { type_;
    attributes=
      BatMap.String.fold
        (fun name value -> Utilities.StringMap.add name (value_of value))
        attributes Utilities.StringMap.empty }

let segments_of path =
  match String.split_on_char '\\' path with
  | [""; ""] -> Ok []
  | "" :: (_ :: _ as segments) when not (List.mem "" segments) -> Ok segments
  | _ -> Error (Error.invalid_path path)

let named path =
  let* segments = segments_of path in
  match List.rev segments with
  | name :: parent -> Ok (List.rev parent, name)
  | [] -> Error (Error.unnamed path)

let using {root; _} segments f =
  match segments with
  | [] -> f root
  | _ ->
      let* handle = K.Path.lookup root (List.fold_right K.Path.( @/ ) segments K.Path.this) in
      Fun.protect ~finally:(fun () -> P.Handle.release handle) (fun () -> f handle)

let rec update kernel segments name reference value =
  using kernel segments (fun target ->
      match P.Registry.from target, List.rev segments with
      | Some registry, _ -> P.Registry.update registry name reference value
      | None, [] -> P.Handle.require P.Registry.from target |> Result.map (Fun.const false)
      | None, last :: parent ->
          let* directory = P.Handle.require P.Directory.from target in
          let* present = P.Directory.find directory name in
          Option.iter P.Handle.release present;
          if Option.is_some present <> Option.is_some reference then Ok false
          else
            let* associative = P.Handle.require P.Associative.from target in
            let* derived = P.Associative.update associative name value in
            update kernel (List.rev parent) last (Some target) (Some (P.Handle.from derived)) )

let store_relation connection name heading tuples =
  SI.with_transaction connection (fun tx ->
      let* schematics =
        C.Encoding.Bencode.(
          Dict (Utilities.StringMap.bindings heading |> List.map (fun (a, d) -> a, String d))
          |> to_blob )
        |> SI.store_blob tx
      in
      let* _ = SR.TupleSet.empty_under tx in
      let* node =
        SR.TupleSet.with_batch tx (fun () ->
            List.fold_left
              (fun node attributes ->
                let* node = node in
                let tuple =
                  { C.Tuple.type_= name;
                    attributes=
                      Utilities.StringMap.fold
                        (fun a v -> BatMap.String.add a (kernel_value_of v))
                        attributes BatMap.String.empty }
                in
                SR.TupleSet.insert tx (C.Tuple.hash tuple) tuple node )
              (Ok SR.TupleSet.empty) tuples )
      in
      let relation = {SR.schematics; tuples= SR.TupleSet.hash_of node} in
      let* _ = SI.store_blob tx (SR.encode relation) in
      Ok relation )
  |> fmap (SR.load connection)

let conforms heading attributes =
  Utilities.StringMap.(
    equal String.equal heading (map (fun v -> T.Attribute.domain (kernel_value_of v)) attributes) )

let pinned kernel branch =
  using kernel ["branch"; branch] (fun handle ->
      let* addressable = P.Handle.require P.Addressable.from handle in
      Ok {P.Session.branch; snapshot= P.Addressable.address addressable} )

let make ({connection; _} as kernel) path name = function
  | Namespace -> Ok (K.Namespace.make ())
  | Branch -> B.make connection |> lift
  | Multigroup -> M.make connection |> lift
  | Relation {heading; tuples} ->
      if List.for_all (conforms heading) tuples then
        store_relation connection name heading tuples |> lift
      else Error (Error.heading_mismatch path)
  | Session branch ->
      let* state = pinned kernel branch |> lift in
      Ok (Sess.make connection state)

let initialize configuration =
  let* connection = S.connect (C.Configuration.term_of_sexp configuration) |> lift in
  I.initialize ~evaluators:["fol", FOL.make ()] connection
  |> Result.map (fun root -> {connection; root})
  |> Result.map_error (fun c -> S.disconnect connection; Error.of_kernel c)

let shutdown {connection; _} = S.disconnect connection

let list kernel path =
  let* segments = segments_of path in
  using kernel segments (fun handle ->
      let* directory = P.Handle.require P.Directory.from handle in
      P.Directory.list directory )
  |> lift
  |> Result.map BatFingerTree.to_list

let lookup kernel path =
  let* segments = segments_of path in
  let* handle = using kernel segments (fun handle -> Ok (P.Handle.copy handle)) |> lift in
  Option.to_result ~none:(Error.unavailable path) handle

let create kernel path blueprint =
  let* parent, name = named path in
  let* value = make kernel path name blueprint in
  let* bound = update kernel parent name None (Some value) |> lift in
  if bound then Ok () else Error (Error.conflict path)

let remove kernel path =
  let* parent, name = named path in
  let* current = lookup kernel path in
  let removed = update kernel parent name (Some current) None in
  P.Handle.release current;
  let* removed = lift removed in
  if removed then Ok () else Error (Error.conflict path)

let with_session kernel path f =
  let* segments = segments_of path in
  using kernel segments (fun handle ->
      let* session = P.Session.require handle in
      Ok (f session) )
  |> lift

let session kernel path =
  with_session kernel path P.Session.state
  |> Result.map (fun {P.Session.branch; snapshot} ->
      {branch; snapshot= C.Hash.to_hum_string snapshot} )

let pin kernel path branch =
  let* state = pinned kernel branch |> lift in
  with_session kernel path (fun session -> P.Session.pin session state)

let release = P.Handle.release
let identity handle = P.Handle.hash handle |> C.Hash.to_hum_string

let describe handle =
  lift
    (let* schematics = P.Handle.require P.Schematics.from handle in
     P.Schematics.describe schematics )
  |> fmap (function
    | P.Schematics.Relation description ->
        Ok
          (BatMap.String.fold
             (fun name {P.Schematics.domain; _} -> Utilities.StringMap.add name domain)
             description Utilities.StringMap.empty )
    | _ -> Error (Error.not_a_relation (P.Handle.to_string handle)) )

let rec fol_plan kernel = function
  | Base path ->
      let* relation = lookup kernel path in
      Ok (FOL.Base relation, [relation])
  | Project (plan, attributes) ->
      let* plan, relations = fol_plan kernel plan in
      Ok (FOL.Project (plan, BatFingerTree.of_list attributes), relations)

let execute kernel plan =
  let* plan, relations = fol_plan kernel plan in
  let program = FOL.program plan in
  Fun.protect
    ~finally:(fun () -> List.iter P.Handle.release (program :: relations))
    (fun () ->
      using kernel ["evaluator"; "fol"] (fun evaluator ->
          let* evaluator = P.Evaluator.require evaluator in
          P.Evaluator.invoke evaluator program )
      |> lift )

let enumerate handle =
  lift
    (let* enumerable = P.Enumerable.require handle in
     P.Enumerable.enumerate enumerable )

let fetch cursor limit =
  lift
    (let* scan = P.Cursor.require cursor in
     P.Cursor.fetch scan limit )
  |> Result.map (fun ({tuples; exhausted} : P.Cursor.batch) ->
      {tuples= BatFingerTree.to_list tuples |> List.map tuple_of; exhausted} )