blob: 605c40ad621769b03e0cbd9ce441f6543c369495 (
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
|
(** Multi-multigroup catalog.
Each multigroup gets its own [Atomic.t], so mutations to independent
multigroups never contend. The [sakura] multigroup is auto-bootstrapped,
undeletable, and self-referential: it carries a [public:multigroup] relation
listing every registered multigroup. *)
type t = {
multigroups : Management.Database.t Atomic.t Utilities.StringMap.t Atomic.t;
default_multigroup : string;
mutex : Mutex.t;
}
let sakura_name = "sakura"
module Make (S : Management.Physical.S with type error = string) = struct
open Utilities.Result
module Manip = Manipulation.Make (S)
type nonrec t = t
let map_error r =
Result.map_error (fun e -> Sexplib.Sexp.to_string (Error.sexp_of_error e)) r
let get catalog name =
Utilities.StringMap.find_opt name (Atomic.get catalog.multigroups)
let list catalog =
Utilities.StringMap.bindings (Atomic.get catalog.multigroups)
|> List.map fst
let publish_multigroups catalog ~expected ~updated =
if Atomic.compare_and_set catalog.multigroups expected updated then Ok ()
else Error "Concurrent multigroup map update detected"
let get_string_attr attr (t : Tuple.materialized) =
Option.map
(fun a -> (Obj.magic a.Attribute.value : string))
(Tuple.AttributeMap.find_opt attr t.attributes)
(** Dynlink every library recorded in public:loaded_library. No-op on a fresh
DB; effective when loading a previously persisted multigroup. *)
let hydrate_prl_libraries storage (db : Management.Database.t) =
match
Management.Database.get_relation db
Prelude.Catalog.loaded_library_rel_name
with
| None -> ()
| Some rel -> (
let hashes =
match rel.Relation.tree with None -> [] | Some t -> Merkle.keys t
in
match Manip.load_tuples storage hashes with
| Error _ -> ()
| Ok tuples ->
List.iter
(fun t ->
match get_string_attr "path" t with
| None -> ()
| Some path -> ignore (Prl.Runtime.load_library path))
tuples)
(** Swallow errors: a missing prelude relation is non-fatal. *)
let register_prelude_relation storage db (rel : Relation.t) =
match
Manip.create_immutable_relation storage db ~name:rel.name
~schema:rel.schema ~generator:(Option.get rel.generator)
~membership_criteria:rel.membership_criteria
~cardinality:rel.cardinality ~producer:None
with
| Ok (new_db, _) -> new_db
| Error e ->
Printf.eprintf "Warning: prelude relation %s: %s\n%!" rel.name
(Sexplib.Sexp.to_string (Error.sexp_of_error e));
db
(** Create DB + seed catalog + fold in [prelude_relations] + hydrate PRL. *)
let bootstrap_multigroup storage ~prelude_relations ~name =
let* db = Manip.create_database storage ~name |> map_error in
let db =
List.fold_left (register_prelude_relation storage) db prelude_relations
in
hydrate_prl_libraries storage db;
Ok db
(** Create additional catalog relations beyond the base six. Used to seed
[public:multigroup] into the sakura multigroup only. *)
let seed_extra_catalog storage ~definitions db =
List.fold_left
(fun acc (name, schema) ->
let* db = acc in
let* new_db, _ =
Manip.create_relation storage db ~name ~schema |> map_error
in
Ok new_db)
(Ok db) definitions
(** Write a tuple into sakura's [public:multigroup] and update the ref. No-op
if the relation doesn't exist yet (bootstrap ordering). *)
let register_in_sakura storage sakura_ref mg_name =
let rec go () =
let sakura = Atomic.get sakura_ref in
match
Management.Database.get_relation sakura
Prelude.Catalog.multigroup_rel_name
with
| None -> Ok ()
| Some mg_rel ->
let tuple = Prelude.Catalog.build_multigroup_tuple mg_name in
let* new_sakura, _, _ =
Manip.create_tuple storage sakura mg_rel tuple |> map_error
in
if Atomic.compare_and_set sakura_ref sakura new_sakura then Ok ()
else go ()
in
go ()
let create storage ~prelude_relations =
let* sakura_db =
bootstrap_multigroup storage ~prelude_relations ~name:sakura_name
in
let* sakura_db =
seed_extra_catalog storage
~definitions:Prelude.Catalog.sakura_only_definitions sakura_db
in
let sakura_ref = Atomic.make sakura_db in
let* () = register_in_sakura storage sakura_ref sakura_name in
let map = Utilities.StringMap.singleton sakura_name sakura_ref in
Ok
{
multigroups = Atomic.make map;
default_multigroup = sakura_name;
mutex = Mutex.create ();
}
let add catalog storage ~prelude_relations name =
Mutex.protect catalog.mutex (fun () ->
let map = Atomic.get catalog.multigroups in
if Utilities.StringMap.mem name map then
Error (Printf.sprintf "Multigroup %s already exists" name)
else
let* db = bootstrap_multigroup storage ~prelude_relations ~name in
let db_ref = Atomic.make db in
let* () =
match Utilities.StringMap.find_opt sakura_name map with
| Some sakura_ref -> register_in_sakura storage sakura_ref name
| None -> Ok ()
in
let new_map = Utilities.StringMap.add name db_ref map in
let* () =
publish_multigroups catalog ~expected:map ~updated:new_map
in
Ok db_ref)
let unregister_from_sakura storage sakura_ref mg_name =
let rec go () =
let sakura = Atomic.get sakura_ref in
match
Management.Database.get_relation sakura
Prelude.Catalog.multigroup_rel_name
with
| None -> Ok ()
| Some mg_rel ->
let tuple_hash =
Hashing.hash_tuple (Prelude.Catalog.build_multigroup_tuple mg_name)
in
let* new_sakura, _ =
Manip.retract_tuple storage sakura mg_rel ~tuple_hash |> map_error
in
if Atomic.compare_and_set sakura_ref sakura new_sakura then Ok ()
else go ()
in
go ()
let remove catalog storage name =
if name = sakura_name then Error "Cannot remove the sakura system catalog"
else
Mutex.protect catalog.mutex (fun () ->
let map = Atomic.get catalog.multigroups in
if not (Utilities.StringMap.mem name map) then
Error (Printf.sprintf "Multigroup %s does not exist" name)
else
let* () =
match Utilities.StringMap.find_opt sakura_name map with
| None -> Ok ()
| Some sakura_ref ->
unregister_from_sakura storage sakura_ref name
in
let new_map = Utilities.StringMap.remove name map in
let* () =
publish_multigroups catalog ~expected:map ~updated:new_map
in
Ok ())
end
|