aboutsummaryrefslogblamecommitdiffstats
path: root/lib/management/physical.ml
blob: 6063d793b59be69d4ffd4fdfc528fab446997969 (plain) (tree)

































                                                                                  
 







                                                                                           

   

                                                   
        


















































































































































                                                                                                    
 

                                                
(** Physical storage abstraction.

    This module defines a functor-based storage interface that can be backed
    by any key-value store (etcd, Redis, DynamoDB, PostgreSQL, filesystem, etc.).

    All data is content-addressed: keys are hashes, values are serialized objects.
    The storage is append-only - objects are never modified, only new versions
    are written with new hashes. *)

(** Backend module signature that any storage driver must implement *)
module type BACKEND = sig
  type connection
  type error

  (** Connect to the storage backend *)
  val connect : unit -> (connection, error) result

  (** Disconnect from the backend *)
  val disconnect : connection -> unit

  (** Get a value by its hash. Returns None if not found. *)
  val get : connection -> Conventions.Hash.t -> (bytes option, error) result

  (** Store a value at its hash *)
  val put : connection -> Conventions.Hash.t -> bytes -> (unit, error) result

  (** Check if a hash exists *)
  val exists : connection -> Conventions.Hash.t -> (bool, error) result

  (** Begin a transaction *)
  val begin_transaction : connection -> (unit, error) result

  (** Commit a transaction *)
  val commit : connection -> (unit, error) result

  (** Rollback a transaction *)
  val rollback : connection -> (unit, error) result

  (** Batch get multiple values *)
  val get_many : connection -> Conventions.Hash.t list -> (bytes option list, error) result

  (** Batch put multiple values *)
  val put_many : connection -> (Conventions.Hash.t * bytes) list -> (unit, error) result
end

(** Storage operations built on top of a backend *)
module type S = sig
  type t
  type error

  (** Create a storage instance from a backend connection *)
  val create : unit -> (t, error) result

  (** Close the storage *)
  val close : t -> unit

  (** Store an attribute value, returns its hash *)
  val store_attribute : t -> Conventions.AbstractValue.t -> (Conventions.Hash.t, error) result

  (** Load an attribute value by hash *)
  val load_attribute : t -> Conventions.Hash.t -> (Conventions.AbstractValue.t option, error) result

  (** Store raw bytes at a hash *)
  val store_raw : t -> Conventions.Hash.t -> bytes -> (unit, error) result

  (** Load raw bytes by hash *)
  val load_raw : t -> Conventions.Hash.t -> (bytes option, error) result

  (** Check if a hash exists *)
  val exists : t -> Conventions.Hash.t -> (bool, error) result

  (** Execute a function within a transaction *)
  val with_transaction : t -> (unit -> ('a, error) result) -> ('a, error) result
end

(** Functor to create storage operations from a backend *)
module Make (B : BACKEND) : S with type error = B.error = struct
  type t = B.connection
  type error = B.error

  let create () = B.connect ()

  let close = B.disconnect

  let store_attribute conn value =
    let bytes = Marshal.to_bytes value [] in
    let hash = Conventions.AbstractValue.hash value in
    match B.put conn hash bytes with
    | Ok () -> Ok hash
    | Error e -> Error e

  let load_attribute conn hash =
    match B.get conn hash with
    | Ok (Some bytes) -> Ok (Some (Marshal.from_bytes bytes 0))
    | Ok None -> Ok None
    | Error e -> Error e

  let store_raw conn hash bytes =
    B.put conn hash bytes

  let load_raw conn hash =
    B.get conn hash

  let exists conn hash =
    B.exists conn hash

  let with_transaction conn f =
    match B.begin_transaction conn with
    | Error e -> Error e
    | Ok () ->
      match f () with
      | Ok result ->
        (match B.commit conn with
         | Ok () -> Ok result
         | Error e -> Error e)
      | Error e ->
        let _ = B.rollback conn in
        Error e
end

(** In-memory backend for testing and development *)
module MemoryBackend : BACKEND with type error = string = struct
  type connection = {
    data : (string, bytes) Hashtbl.t;
    mutable in_transaction : bool;
    mutable transaction_buffer : (string * bytes) list;
  }
  type error = string

  let connect () =
    Ok {
      data = Hashtbl.create 1024;
      in_transaction = false;
      transaction_buffer = [];
    }

  let disconnect _ = ()

  let get conn hash =
    Ok (Hashtbl.find_opt conn.data hash)

  let put conn hash value =
    if conn.in_transaction then begin
      conn.transaction_buffer <- (hash, value) :: conn.transaction_buffer;
      Ok ()
    end else begin
      Hashtbl.replace conn.data hash value;
      Ok ()
    end

  let exists conn hash =
    Ok (Hashtbl.mem conn.data hash)

  let begin_transaction conn =
    if conn.in_transaction then
      Error "Already in transaction"
    else begin
      conn.in_transaction <- true;
      conn.transaction_buffer <- [];
      Ok ()
    end

  let commit conn =
    if not conn.in_transaction then
      Error "Not in transaction"
    else begin
      List.iter (fun (hash, value) ->
        Hashtbl.replace conn.data hash value
      ) conn.transaction_buffer;
      conn.in_transaction <- false;
      conn.transaction_buffer <- [];
      Ok ()
    end

  let rollback conn =
    if not conn.in_transaction then
      Error "Not in transaction"
    else begin
      conn.in_transaction <- false;
      conn.transaction_buffer <- [];
      Ok ()
    end

  let get_many conn hashes =
    Ok (List.map (fun h -> Hashtbl.find_opt conn.data h) hashes)

  let put_many conn pairs =
    List.iter (fun (hash, value) ->
      if conn.in_transaction then
        conn.transaction_buffer <- (hash, value) :: conn.transaction_buffer
      else
        Hashtbl.replace conn.data hash value
    ) pairs;
    Ok ()
end

(** Default in-memory storage for development *)
module Memory = Make(MemoryBackend)