aboutsummaryrefslogblamecommitdiffstats
path: root/lib/catalog.ml
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