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
|
module Make (S : Abstract.Storage.STORAGE) = struct
module SI = Storage.Make (S)
module Error = struct
open Concepts.Condition
let malformed_relation () =
condition "malformed-substantial-relation"
"The on-disk representation of a substantial relation did not conform to what was \
expected. Is your database corrupted?"
empty
let malformed_tuple_key length =
condition "malformed-tuple-key" "A tuple key did not have the expected hash size"
("length" |=| Concepts.Value.Integer length)
let invalid_tuple_root root =
condition "invalid-tuple-root"
"The tuple-set root for a substantial relation is missing from the backend. Either your \
storage is corrupted, or this is a bug in RNT!"
("hash" |=| Concepts.Value.String (Concepts.Hash.to_hum_string root))
let missing_attribute name =
condition "missing-attribute" "A tuple to store has no value for an attribute of its relation"
("attribute" |=| Concepts.Value.String name)
let unexpected_attribute name =
condition "unexpected-attribute"
"A tuple to store has an attribute its relation does not have"
("attribute" |=| Concepts.Value.String name)
let unindexed_attribute name =
condition "unindexed-attribute" "A relation was looked up on an attribute it has no index for"
("attribute" |=| Concepts.Value.String name)
let invalid_index_root root =
condition "invalid-index-root"
"An index root for a substantial relation is missing from the backend. Either your storage \
is corrupted, or this is a bug in RNT!"
("hash" |=| Concepts.Value.String (Concepts.Hash.to_hum_string root))
end
module TupleKey = struct
type t = Concepts.Hash.hash
let encode = Concepts.Hash.blob_of_hash
let decode blob =
let raw = Concepts.Blob.bytes_of_blob blob |> Bytes.to_string in
if String.length raw = Concepts.Hash.size then Ok (Concepts.Hash.of_raw_string raw)
else Error (Error.malformed_tuple_key (String.length raw))
let compare = Concepts.Hash.compare
end
module TupleValue = struct
type t = Concepts.Tuple.t
let encode = Concepts.Tuple.Representation.to_blob
let decode = Concepts.Tuple.Representation.of_blob
end
module TupleSet = Merkle.Interface (S) (TupleKey) (TupleValue)
module Index = Merkle.Make (S) (Merkle.StringKey)
type t =
{ schematics: Concepts.Hash.hash;
tuples: TupleSet.address;
indexes: Index.address BatMap.String.t }
module rec Representation : (Concepts.Encoding.Record.S with type t = t) =
Concepts.Encoding.Record.Make (Body)
and Body : Concepts.Encoding.Record.BODY = struct
type nonrec t = t
let tag = 'R'
let malformed = Error.malformed_relation
let fields {schematics; tuples; indexes} =
let open Concepts.Encoding in
[ "schema", Value.bencode_of_hash schematics;
"tuples", Value.bencode_of_hash tuples;
( "indexes",
Bencode.Dict
(BatMap.String.bindings indexes |> List.map (fun (a, h) -> a, Value.bencode_of_hash h))
) ]
let of_fields fields =
let open Utilities.Result in
let open Concepts.Encoding in
let* schematics = Bencode.field "schema" fields |>> Value.hash_of_bencode in
let* tuples = Bencode.field "tuples" fields |>> Value.hash_of_bencode in
let* indexes = Bencode.field "indexes" fields |>> Bencode.as_dict in
let* indexes =
List.map (fun (a, h) -> Value.hash_of_bencode h |> Result.map (fun h -> a, h)) indexes
|> Utilities.List.sequence
in
Ok {schematics; tuples; indexes= BatMap.String.of_list indexes}
end
let encode = Representation.to_blob
let decode = Representation.of_blob
let tuple_node tx relation =
let open Utilities.Result in
let* tuples = TupleSet.find tx relation.tuples in
Option.to_result ~none:(Error.invalid_tuple_root relation.tuples) tuples
let store_schema tx description =
Concepts.Encoding.Bencode.(
Dict
( BatMap.String.bindings description
|> List.map (fun (name, {Protocols.Schematics.domain; _}) -> name, String domain) )
|> to_blob )
|> SI.store_blob tx
let schema_of tx schematics =
let open Utilities.Result in
let* data = SI.get_req tx (S.Hash schematics) in
let* fields = Concepts.Encoding.Bencode.of_blob data in
let* kvs = Concepts.Encoding.Bencode.as_dict fields in
kvs
|> List.map (fun (name, domain) ->
Concepts.Encoding.Bencode.as_string domain
|> Result.map (fun domain -> name, {Protocols.Schematics.domain; provenance= []}) )
|> Utilities.List.sequence
|> Result.map BatMap.String.of_list
let prefix value =
Concepts.Encoding.Bencode.to_bytes (Concepts.Tuple.bencode_of_value value) |> Bytes.to_string
let reading connection start walk =
let open Utilities.Result in
let* tx = S.start_read connection in
let close () = ignore (S.abort tx) in
let* node = start tx |> Result.map_error (fun c -> close (); c) in
Ok (Generator.cursor_of ~finally:close (walk tx node))
let enumerate connection relation =
reading connection
(fun tx -> tuple_node tx relation)
(fun tx node ~yield -> TupleSet.iter tx (fun _ tuple -> yield tuple) node)
let lookup connection relation attribute value =
let open Utilities.Result in
let* root =
BatMap.String.find_opt attribute relation.indexes
|> Option.to_result ~none:(Error.unindexed_attribute attribute)
in
let prefix = prefix value in
reading connection
(fun tx -> Index.find tx root |>> Option.to_result ~none:(Error.invalid_index_root root))
(fun tx node ~yield ->
Index.fold_left ~from:prefix
~until:(fun key -> not (String.starts_with ~prefix key))
tx
(fun read _ address ->
let* () = read in
let* tuple = SI.get_req tx (S.Hash address) |>> TupleValue.decode in
Ok (yield tuple) )
(Ok ()) node
|> Result.join )
class substantial_relation connection value description =
object (self)
inherit Lifecycle.null
method to_string = "relation"
val connection : S.connection = connection
val relation : t = value
method contains (tuple : Concepts.Tuple.t) =
SI.with_read connection (fun tx ->
let open Utilities.Result in
let* node = tuple_node tx relation in
TupleSet.mem tx (Concepts.Tuple.hash tuple) node )
method modes =
Ok
Concepts.(
Mode.mode [] Cardinality.Finite
:: Mode.mode (List.map fst (BatMap.String.bindings description)) (Cardinality.bounded 1)
:: List.map (fun a -> Mode.mode [a] Cardinality.Finite) self#indexes )
method indexes = List.map fst (BatMap.String.bindings relation.indexes)
method lookup attribute value = lookup connection relation attribute value
method describe () = Ok (Protocols.Schematics.Relation description)
method enumerate = enumerate connection relation
method protocols : Protocols.Handle.protocol list =
[ Protocols.Addressable.make self;
Protocols.Relation.make self;
Protocols.Enumerable.make self;
Protocols.Indexed.make self;
Protocols.Schematics.make self ]
method hash = encode relation |> Concepts.Hash.hash_of_blob
method address = self#hash
end
let load connection relation =
SI.with_read connection (fun tx -> schema_of tx relation.schematics)
|> Result.map (fun description ->
new substantial_relation connection relation description |> Protocols.Handle.make )
let conform description () {Concepts.Tuple.attributes; _} =
let outside one other =
List.find_opt
(fun (name, _) -> not (BatMap.String.mem name other))
(BatMap.String.bindings one)
in
match outside description attributes, outside attributes description with
| Some (name, _), _ -> Error (Error.missing_attribute name)
| None, Some (name, _) -> Error (Error.unexpected_attribute name)
| None, None -> Ok ()
let index tx hashed attribute =
List.map
(fun (hash, tuple) ->
( prefix (BatMap.String.find attribute tuple.Concepts.Tuple.attributes)
^ Concepts.Hash.to_raw_string hash,
hash ) )
hashed
|> Index.of_list tx
|> Result.map (fun node -> attribute, Index.hash_of node)
let store tx description tuples =
let open Utilities.Result in
let* () = Utilities.List.foldm_left (conform description) () tuples in
let* schematics = store_schema tx description in
let hashed = List.map (fun tuple -> Concepts.Tuple.hash tuple, tuple) tuples in
let* node = TupleSet.of_list tx hashed in
let* indexes =
BatMap.String.bindings description
|> List.map (fun (attribute, _) -> index tx hashed attribute)
|> Utilities.List.sequence
in
let relation =
{schematics; tuples= TupleSet.hash_of node; indexes= BatMap.String.of_list indexes}
in
let* _ = SI.store_blob tx (encode relation) in
Ok relation
let instantiate connection ~schematics =
let open Utilities.Result in
let* relation =
SI.with_transaction connection (fun tx ->
let* description = schema_of tx schematics in
store tx description [] )
in
load connection relation
end
|