blob: 74fe3de2d2b3dc2ad97614030f2fba384be739cd (
plain) (
tree)
|
|
(** 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
|