aboutsummaryrefslogtreecommitdiffstats
path: root/lib/RNT.ml
blob: 353621f26707ab84ebfe1faf6ce97cb6ebebafb5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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} )