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} )
|