aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcos Magueta <maguetamarcos@gmail.com>2026-09-26 22:09:11 -0300
committerMarcos Magueta <maguetamarcos@gmail.com>2026-09-26 22:09:11 -0300
commit320954996e921d7b34eb61f462799b5c6ab8ee09 (patch)
tree16697bc161faf27f0b4c1ba57f6fcc24d240801b
parentb555c28c7e6c315fc0394baf213ecb08b21ed959 (diff)
downloadSakura-320954996e921d7b34eb61f462799b5c6ab8ee09.tar.gz
Sakura-320954996e921d7b34eb61f462799b5c6ab8ee09.zip
new shrunk API for RNT on sakura
-rw-r--r--lib/RNT.ml231
-rw-r--r--lib/RNT.mli35
-rw-r--r--lib/condition.ml49
-rw-r--r--lib/configuration.ml26
-rw-r--r--lib/drl/ast.ml3
-rw-r--r--lib/drl/executor.ml33
-rw-r--r--lib/drl/parser.ml10
-rw-r--r--lib/drl/sublanguage.ml7
-rw-r--r--lib/dune1
-rw-r--r--lib/nt.ml841
-rw-r--r--lib/session.ml77
-rw-r--r--lib/sublanguage.ml20
-rw-r--r--lib/sublanguage_context.ml70
-rw-r--r--lib/sublanguage_types.ml14
-rw-r--r--lib/value.ml6
15 files changed, 398 insertions, 1025 deletions
diff --git a/lib/RNT.ml b/lib/RNT.ml
new file mode 100644
index 0000000..173d09a
--- /dev/null
+++ b/lib/RNT.ml
@@ -0,0 +1,231 @@
+open Utilities.Result
+module S = Rnt.Backend.Storage.LMDB
+module C = Rnt.Concepts
+module P = Rnt.Protocols
+module K = Rnt.Kernel
+module FOL = Rnt.Evaluators.FOL
+module I = K.Initialization.Make (S)
+module B = K.Branch.Make (S)
+module M = K.Multigroup.Make (S)
+module SI = K.Storage.Make (S)
+module SR = K.SubstantialRelation.Make (S)
+module T = K.Tuple.Make (S)
+
+module Error = struct
+ open Condition
+
+ let of_kernel c =
+ condition "kernel-condition" "RNT signalled a condition"
+ ("report" |=| Value.String (C.Condition.to_string_hum c))
+
+ let invalid_path path =
+ condition "invalid-path" "A path must start at \\ and have no empty segments"
+ ("path" |=| Value.String path)
+
+ let unnamed path =
+ condition "unnamed-object" "The path does not name an object under a container"
+ ("path" |=| Value.String path)
+
+ let conflict path =
+ condition "binding-conflict"
+ "The path did not hold what was expected when the change was applied"
+ ("path" |=| Value.String path)
+
+ let unavailable path =
+ condition "unavailable-object" "The object refused a new reference"
+ ("path" |=| Value.String path)
+
+ let heading_mismatch path =
+ condition "heading-mismatch" "A tuple does not conform to the heading of its relation"
+ ("path" |=| Value.String path)
+
+ let not_a_relation name =
+ condition "not-a-relation" "The object does not describe itself as a relation"
+ ("object" |=| Value.String name)
+end
+
+type kernel = {connection: S.connection; root: P.Handle.t}
+type handle = P.Handle.t
+type path = string
+type tuple = {type_: string; attributes: Value.t Utilities.StringMap.t}
+type batch = {tuples: tuple list; exhausted: bool}
+
+type blueprint =
+ | Namespace
+ | Branch
+ | Multigroup
+ | Relation of {heading: string Utilities.StringMap.t; tuples: Value.t Utilities.StringMap.t list}
+
+type plan = Base of path | Project of plan * string list
+
+let lift r = Result.map_error Error.of_kernel r
+let value_of : C.Value.value -> Value.t = function String s -> String s | Integer n -> Integer n
+
+let kernel_value_of : Value.t -> C.Value.value = function
+ | String s -> String s
+ | Integer n -> Integer n
+
+let tuple_of ({type_; attributes} : C.Tuple.t) =
+ { type_;
+ attributes=
+ BatMap.String.fold
+ (fun name value -> Utilities.StringMap.add name (value_of value))
+ attributes Utilities.StringMap.empty }
+
+let segments_of path =
+ match String.split_on_char '\\' path with
+ | [""; ""] -> Ok []
+ | "" :: (_ :: _ as segments) when not (List.mem "" segments) -> Ok segments
+ | _ -> Error (Error.invalid_path path)
+
+let named path =
+ let* segments = segments_of path in
+ match List.rev segments with
+ | name :: parent -> Ok (List.rev parent, name)
+ | [] -> Error (Error.unnamed path)
+
+let using {root; _} segments f =
+ match segments with
+ | [] -> f root
+ | _ ->
+ let* handle = K.Path.lookup root (List.fold_right K.Path.( @/ ) segments K.Path.this) in
+ Fun.protect ~finally:(fun () -> P.Handle.release handle) (fun () -> f handle)
+
+let rec update kernel segments name reference value =
+ using kernel segments (fun target ->
+ match P.Registry.from target, List.rev segments with
+ | Some registry, _ -> P.Registry.update registry name reference value
+ | None, [] -> P.Handle.require P.Registry.from target |> Result.map (Fun.const false)
+ | None, last :: parent ->
+ let* directory = P.Handle.require P.Directory.from target in
+ let* present = P.Directory.find directory name in
+ Option.iter P.Handle.release present;
+ if Option.is_some present <> Option.is_some reference then Ok false
+ else
+ let* associative = P.Handle.require P.Associative.from target in
+ let* derived = P.Associative.update associative name value in
+ update kernel (List.rev parent) last (Some target) (Some (P.Handle.from derived)) )
+
+let store_relation connection name heading tuples =
+ SI.with_transaction connection (fun tx ->
+ let* schematics =
+ C.Encoding.Bencode.(
+ Dict (Utilities.StringMap.bindings heading |> List.map (fun (a, d) -> a, String d))
+ |> to_blob )
+ |> SI.store_blob tx
+ in
+ let* _ = SR.TupleSet.empty_under tx in
+ let* node =
+ SR.TupleSet.with_batch tx (fun () ->
+ List.fold_left
+ (fun node attributes ->
+ let* node = node in
+ let tuple =
+ { C.Tuple.type_= name;
+ attributes=
+ Utilities.StringMap.fold
+ (fun a v -> BatMap.String.add a (kernel_value_of v))
+ attributes BatMap.String.empty }
+ in
+ SR.TupleSet.insert tx (C.Tuple.hash tuple) tuple node )
+ (Ok SR.TupleSet.empty) tuples )
+ in
+ let relation = {SR.schematics; tuples= SR.TupleSet.hash_of node} in
+ let* _ = SI.store_blob tx (SR.encode relation) in
+ Ok relation )
+ |> fmap (SR.load connection)
+
+let conforms heading attributes =
+ Utilities.StringMap.(
+ equal String.equal heading (map (fun v -> T.Attribute.domain (kernel_value_of v)) attributes) )
+
+let make {connection; _} path name = function
+ | Namespace -> Ok (K.Namespace.make ())
+ | Branch -> B.make connection |> lift
+ | Multigroup -> M.make connection |> lift
+ | Relation {heading; tuples} ->
+ if List.for_all (conforms heading) tuples then
+ store_relation connection name heading tuples |> lift
+ else Error (Error.heading_mismatch path)
+
+let initialize configuration =
+ let* connection = S.connect (C.Configuration.term_of_sexp configuration) |> lift in
+ (let* root = I.initialize ~evaluators:["fol", FOL.make ()] connection in
+ let* _ = K.Path.(update root this "session" None (Some (K.Namespace.make ()))) in
+ Ok {connection; root} )
+ |> Result.map_error (fun c -> S.disconnect connection; Error.of_kernel c)
+
+let shutdown {connection; _} = S.disconnect connection
+
+let list kernel path =
+ let* segments = segments_of path in
+ using kernel segments (fun handle ->
+ let* directory = P.Handle.require P.Directory.from handle in
+ P.Directory.list directory )
+ |> lift
+ |> Result.map BatFingerTree.to_list
+
+let lookup kernel path =
+ let* segments = segments_of path in
+ let* handle = using kernel segments (fun handle -> Ok (P.Handle.copy handle)) |> lift in
+ Option.to_result ~none:(Error.unavailable path) handle
+
+let create kernel path blueprint =
+ let* parent, name = named path in
+ let* value = make kernel path name blueprint in
+ let* bound = update kernel parent name None (Some value) |> lift in
+ if bound then Ok () else Error (Error.conflict path)
+
+let remove kernel path =
+ let* parent, name = named path in
+ let* current = lookup kernel path in
+ let removed = update kernel parent name (Some current) None in
+ P.Handle.release current;
+ let* removed = lift removed in
+ if removed then Ok () else Error (Error.conflict path)
+
+let release = P.Handle.release
+let identity handle = P.Handle.hash handle |> C.Hash.to_hum_string
+
+let describe handle =
+ lift
+ (let* schematics = P.Handle.require P.Schematics.from handle in
+ P.Schematics.describe schematics )
+ |> fmap (function
+ | P.Schematics.Relation description ->
+ Ok
+ (BatMap.String.fold
+ (fun name {P.Schematics.domain; _} -> Utilities.StringMap.add name domain)
+ description Utilities.StringMap.empty )
+ | _ -> Error (Error.not_a_relation (P.Handle.to_string handle)) )
+
+let rec fol_plan kernel = function
+ | Base path ->
+ let* relation = lookup kernel path in
+ Ok (FOL.Base relation, [relation])
+ | Project (plan, attributes) ->
+ let* plan, relations = fol_plan kernel plan in
+ Ok (FOL.Project (plan, BatFingerTree.of_list attributes), relations)
+
+let execute kernel plan =
+ let* plan, relations = fol_plan kernel plan in
+ let program = FOL.program plan in
+ Fun.protect
+ ~finally:(fun () -> List.iter P.Handle.release (program :: relations))
+ (fun () ->
+ using kernel ["evaluator"; "fol"] (fun evaluator ->
+ let* evaluator = P.Evaluator.require evaluator in
+ P.Evaluator.invoke evaluator program )
+ |> lift )
+
+let enumerate handle =
+ lift
+ (let* enumerable = P.Enumerable.require handle in
+ P.Enumerable.enumerate enumerable )
+
+let fetch cursor limit =
+ lift
+ (let* scan = P.Cursor.require cursor in
+ P.Cursor.fetch scan limit )
+ |> Result.map (fun ({tuples; exhausted} : P.Cursor.batch) ->
+ {tuples= BatFingerTree.to_list tuples |> List.map tuple_of; exhausted} )
diff --git a/lib/RNT.mli b/lib/RNT.mli
new file mode 100644
index 0000000..841ab2d
--- /dev/null
+++ b/lib/RNT.mli
@@ -0,0 +1,35 @@
+type kernel
+type handle
+type path = string
+type tuple = {type_: string; attributes: Value.t Utilities.StringMap.t}
+type batch = {tuples: tuple list; exhausted: bool}
+
+type blueprint =
+ | Namespace
+ | Branch
+ | Multigroup
+ | Relation of {heading: string Utilities.StringMap.t; tuples: Value.t Utilities.StringMap.t list}
+
+type plan = Base of path | Project of plan * string list
+
+val initialize : Sexplib.Sexp.t -> (kernel, Condition.condition) result
+val shutdown : kernel -> unit
+val list : kernel -> path -> (string list, Condition.condition) result
+val lookup : kernel -> path -> (handle, Condition.condition) result
+val create : kernel -> path -> blueprint -> (unit, Condition.condition) result
+val remove : kernel -> path -> (unit, Condition.condition) result
+val release : handle -> unit
+val identity : handle -> string
+val describe : handle -> (string Utilities.StringMap.t, Condition.condition) result
+val execute : kernel -> plan -> (handle, Condition.condition) result
+val enumerate : handle -> (handle, Condition.condition) result
+val fetch : handle -> int -> (batch, Condition.condition) result
+
+(*
+val authenticate: unit -> unit
+val open_snapshot: unit -> unit
+val register_domain: unit -> unit
+val register_constraint: unit -> unit
+val insert_tuple: unit -> unit
+val retract_tuple: unit -> unit
+*)
diff --git a/lib/condition.ml b/lib/condition.ml
index c565b59..e4d4384 100644
--- a/lib/condition.ml
+++ b/lib/condition.ml
@@ -1,56 +1,31 @@
-class type serializable = object
- method as_sexp : Sexplib.Sexp.t
- method as_string : string
-end
+type properties = (string, Value.t) BatMap.t
+type ps = properties -> properties
-class literal (expr : Sexplib.Sexp.t) : serializable =
- object
- method as_sexp = expr
- method as_string = Sexplib.Sexp.to_string_hum expr
- end
+type condition =
+ {name: string; message: string; backtrace: Printexc.raw_backtrace; properties: properties}
-let of_sexp (expr : Sexplib.Sexp.t) = new literal expr
-let of_string (s : string) = new literal (Sexplib.Sexp.Atom s)
-let of_int (n : int) = of_string (Int.to_string n)
-
-let of_list (f : 'a -> serializable) (l : 'a list) : serializable =
- new literal (Sexplib.Sexp.List (List.map (fun e -> (f e)#as_sexp) l))
-
-type t =
- { name: string;
- message: string;
- backtrace: Printexc.raw_backtrace;
- properties: (string, serializable) BatMap.t }
+let ( |=| ) name value = fun p -> BatMap.add name value p
+let ( & ) l r = fun p -> r (l p)
+let empty = fun p -> p
let condition name message ?parent props =
{ name;
message;
- backtrace= Printexc.get_callstack 64;
+ backtrace= Printexc.get_callstack 256;
properties=
parent
|> Option.map (fun {properties; _} -> properties)
|> Option.value ~default:BatMap.empty
|> props }
-let ( |=| ) name value = fun p -> BatMap.add name value p
-let ( & ) l r = fun p -> r (l p)
-let empty = fun p -> p
-
-let to_sexp {name; message; properties; _} =
- let open Sexplib.Sexp in
- let properties' =
- properties
- |> BatMap.to_seq
- |> BatSeq.map (fun (k, v) -> List [Atom k; v#as_sexp])
- |> BatList.of_seq
- in
- List (Atom name :: Atom message :: properties')
+let complement props ({properties; _} as condition) = {condition with properties= props properties}
-let to_string {name; message; properties; backtrace} =
+let to_string_hum {name; message; properties; backtrace} =
let properties' =
properties
|> BatMap.to_seq
- |> BatSeq.to_string ~first:"" ~last:"" ~sep:"\n" (fun (k, v) -> "\t" ^ k ^ ": " ^ v#as_string)
+ |> BatSeq.to_string ~first:"" ~last:"" ~sep:"\n" (fun (k, v) ->
+ "\t" ^ k ^ ": " ^ Value.to_string v )
in
let backtrace' = Printexc.raw_backtrace_to_string backtrace in
name ^ ": " ^ message ^ "\n" ^ properties' ^ "\n\nBacktrace:\n" ^ backtrace'
diff --git a/lib/configuration.ml b/lib/configuration.ml
index af1635f..420dff6 100644
--- a/lib/configuration.ml
+++ b/lib/configuration.ml
@@ -1,10 +1,11 @@
module type CONFIGURABLE = sig
type configuration
- val parse : Sexplib.Sexp.t -> (configuration, Condition.t) result
+ val parse : Sexplib.Sexp.t -> (configuration, Condition.condition) result
end
module Error = struct
+ (*
open Condition
let unknown_section key =
@@ -15,12 +16,12 @@ module Error = struct
let empty_section key =
condition "empty-section" "Empty configuration section (expected a tagged value)"
- ("key" |=| of_string key)
+ ("key" |=| Value.String key)
let multiple_values_in_section key =
condition "multiple-values-in-section"
"Configuration section contains multiple values (expected exactly one)"
- ("key" |=| of_string key)
+ ("key" |=| Value.String key)
let invalid_toplevel toplevel =
condition "invalid-toplevel" "Configuration must be a (server ...) s-expression"
@@ -31,29 +32,33 @@ module Error = struct
("expression" |=| of_sexp expr)
let missing_section name =
- condition "missing-section" "Missing section in configuration" ("name" |=| of_string name)
+ condition "missing-section" "Missing section in configuration" ("name" |=| Value.String name)
let invalid_tag section tag valid_options =
condition "invalid-tag" "Invalid tag for section"
- ( "section" |=| of_string section
- & "tag" |=| of_string tag
- & "valid-options" |=| of_list of_string valid_options )
+ ( "section" |=| Value.String section
+ & "tag" |=| Value.String tag
+ & "valid-options" |=| of_list Value.String valid_options )
let failed_to_load_file path msg =
condition "failed-to-load-file" "Failed to load configuration file"
- ("path" |=| of_string path & "message" |=| of_string msg)
+ ("path" |=| Value.String path & "message" |=| Value.String msg)
let syntax_error path msg =
condition "syntax-error" "Failed to parse configuration file"
- ("path" |=| of_string path & "message" |=| of_string msg)
+ ("path" |=| Value.String path & "message" |=| Value.String msg)
+ *)
+
end
+(*
+
(** Section name -> raw sexp subtree. *)
type t = Sexplib.Sexp.t Utilities.StringMap.t
let find_section (key : string) (config : t) : Sexplib.Sexp.t option =
Utilities.StringMap.find_opt key config
-
+(
(** Check that [key] is expected and not duplicate, then add it. *)
let insert_section ~expected acc key body =
let ( let* ) = Result.bind in
@@ -109,3 +114,4 @@ let require_section ~(name : string) ~(valid_tags : string list) (config : t) :
if List.mem tag valid_tags then Ok () else Error (Error.invalid_tag name tag valid_tags)
in
Ok (tag, body)
+ *)
diff --git a/lib/drl/ast.ml b/lib/drl/ast.ml
new file mode 100644
index 0000000..d7eb089
--- /dev/null
+++ b/lib/drl/ast.ml
@@ -0,0 +1,3 @@
+open Sexplib.Std
+
+type query = Base of string | Project of string list * query [@@deriving sexp]
diff --git a/lib/drl/executor.ml b/lib/drl/executor.ml
new file mode 100644
index 0000000..e472ec5
--- /dev/null
+++ b/lib/drl/executor.ml
@@ -0,0 +1,33 @@
+open Utilities.Result
+
+let page_limit = 16
+
+(* [mg:rel] names a relation on the session's branch; a bare name, one the session created. *)
+let path_of (ctx : Sublanguage_context.t) name =
+ match String.index_opt name ':' with
+ | None -> ctx.session#namespace ^ "\\" ^ name
+ | Some i ->
+ String.concat "\\"
+ [ "";
+ "branch";
+ ctx.session#branch;
+ "multigroup";
+ String.sub name 0 i;
+ "relation";
+ String.sub name (i + 1) (String.length name - i - 1) ]
+
+let rec compile ctx = function
+ | Ast.Base name -> RNT.Base (path_of ctx name)
+ | Ast.Project (attributes, query) -> RNT.Project (compile ctx query, attributes)
+
+let execute (ctx : Sublanguage_context.t) query =
+ let* relation = RNT.execute ctx.kernel (compile ctx query) in
+ Fun.protect
+ ~finally:(fun () -> RNT.release relation)
+ (fun () ->
+ let* cursor = RNT.enumerate relation in
+ Fun.protect
+ ~finally:(fun () -> RNT.release cursor)
+ (fun () ->
+ let* {RNT.tuples; exhausted} = RNT.fetch cursor page_limit in
+ Ok (Sublanguage_types.Cursor {rows= tuples; has_more= not exhausted}) ) )
diff --git a/lib/drl/parser.ml b/lib/drl/parser.ml
new file mode 100644
index 0000000..f7d4512
--- /dev/null
+++ b/lib/drl/parser.ml
@@ -0,0 +1,10 @@
+module Error = struct
+ open Condition
+
+ let parse_error msg = condition "parse-error" msg empty
+end
+
+let of_sexp sexp =
+ match Ast.query_of_sexp sexp with
+ | q -> Ok q
+ | exception exn -> Error (Error.parse_error (Printexc.to_string exn))
diff --git a/lib/drl/sublanguage.ml b/lib/drl/sublanguage.ml
new file mode 100644
index 0000000..64b25cc
--- /dev/null
+++ b/lib/drl/sublanguage.ml
@@ -0,0 +1,7 @@
+type configuration = unit
+type ast = Ast.query
+
+let name = "drl"
+let parse _ = Ok ()
+let parse_sexp = Parser.of_sexp
+let execute = Executor.execute
diff --git a/lib/dune b/lib/dune
index 543ab60..90a6fc2 100644
--- a/lib/dune
+++ b/lib/dune
@@ -5,6 +5,7 @@
(public_name sakura.relational_engine)
(libraries
dynlink
+ rnt
sha
data-encoding
lwt
diff --git a/lib/nt.ml b/lib/nt.ml
deleted file mode 100644
index 15e9f61..0000000
--- a/lib/nt.ml
+++ /dev/null
@@ -1,841 +0,0 @@
-(* Interface to the RNT kernel *)
-
-open Ctypes
-
-(* Shared types independent of backend, shared across all Make instances *)
-
-type auth_method = Certificate | PlainText
-
-(** Opaque claim token returned by [authenticate]. *)
-type claims = string
-
-(** Opaque handle to an open branch (multigroup head) in the NT registry. *)
-type branch_handle = nativeint
-
-(** Opaque handle to an open relation object in the NT registry. *)
-type relation_handle = nativeint
-
-(** Distinguishes a simple single-relation cursor from a full VM plan cursor. *)
-type cursor_kind = Simple | Vm
-
-(** A streaming cursor over tuples produced either by a simple scan or the VM. *)
-type tuple_stream = {ts_cursor: nativeint; ts_relation: string; ts_kind: cursor_kind}
-
-type path_arg = Var of string | Const of string
-
-type plan_node =
- | Scan of {path: string; args: (string * path_arg) list}
- (* Given this is the query plan, this should probably specify a
- concrete join algorithm (e.g. HashJoin, NestedLoop, LeapFrog, &c) *)
- | Join of {left: plan_node; right: plan_node; on_attrs: string list}
- | Take of {limit: int; source: plan_node}
- | Project of {attrs: string list; source: plan_node}
-
-module Error = struct
- open Condition
-
- (* TODO: better error structure *)
- let auth_failed msg = condition "auth-failed" msg empty
- let handle_error rc msg = condition "handle-error" msg ("return-code" |=| of_int rc)
-
- let object_not_found kind id =
- condition "object-not-found" "Object not found"
- ("kind" |=| of_string kind & "identifier" |=| of_string id)
-
- let cursor_error msg = condition "cursor-error" msg empty
- let not_supported msg = condition "not-supported" msg empty
- let initialization_error = condition "initialization-error" "Couldn't initialize" empty
-end
-
-(* --------------------------------------------------------------------------
- Backend selector
- -------------------------------------------------------------------------- *)
-
-module type Backend = sig
- (* RNT storage driver: "sqlite" or "memory". *)
- val driver : string
-
- (* Passed to the driver as its init argument.
- For "sqlite": file path or ":memory:".
- For "memory": ignored. *)
- val init_arg : string
-end
-
-(* --------------------------------------------------------------------------
- Functor — one instance per backend
- -------------------------------------------------------------------------- *)
-
-module Make (B : Backend) = struct
- let ( let* ) = Result.bind
-
- (* --------------------------------------------------------------------------
- Authentication
- -------------------------------------------------------------------------- *)
-
- let authenticate (method_ : auth_method) : (claims, Condition.t) result =
- let method_str =
- match method_ with Certificate -> "certificate" | PlainText -> "plain_text"
- in
- let rc, claims_opt = Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_firewall method_str pp) in
- match rc, claims_opt with
- | 0, Some s -> Ok s
- | _, Some s -> Error (Error.auth_failed s)
- | _ -> Error (Error.auth_failed "firewall rejected connection")
-
- (* --------------------------------------------------------------------------
- Path helpers (shared by branch and relation lifecycle)
- -------------------------------------------------------------------------- *)
-
- let branch_path (branch_name : string) : string = "/system/branches/" ^ branch_name
-
- let multigroup_path ~(branch_name : string) ~(mg_name : string) : string =
- branch_path branch_name ^ "/multigroups/" ^ mg_name
-
- let relation_path ~(branch_name : string) ~(mg_name : string) ~(rel_name : string) : string =
- multigroup_path ~branch_name ~mg_name ^ "/relations/" ^ rel_name
-
- (* --------------------------------------------------------------------------
- Branch lifecycle
- --------------------------------------------------------------------------
- RNT now owns branch state: each branch is a named hash-pointer to an
- immutable snapshot. Sakura's [multigroup] is the OCaml-side mirror of
- that snapshot's content (relations, schemas, constraints, lineage).
- There is no payload to serialise — every relation/tuple mutation
- advances the branch to a new snapshot internally. *)
-
- let ensure_branch (path : string) : unit =
- (* Idempotent: rnt_register_branch with "" creates an unborn branch when
- missing, no-ops when present. *)
- ignore (Nt_ffi.rnt_register_branch path "")
-
- let branch_target_of_handle (bh : branch_handle) : (string, Condition.t) result =
- let rc, target_opt =
- Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_branch_target (Nt_ffi.nint_to_ptr bh) pp)
- in
- match rc, target_opt with
- | 0, Some s -> Ok s
- | 0, None -> Ok ""
- | _ -> Error (Error.handle_error rc "branch_target read failed")
-
- let list_relations ~(branch_name : string) ~(mg_name : string) :
- ((string * string) list, Condition.t) result =
- let path = multigroup_path ~branch_name ~mg_name in
- let rc, out_opt = Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_list_relations path pp) in
- match rc, out_opt with
- | 0, Some s ->
- let pairs =
- String.split_on_char '\n' s
- |> List.filter_map (fun line ->
- match String.split_on_char '\t' line with
- | [n; r] when n <> "" -> Some (n, r)
- | _ -> None )
- in
- Ok pairs
- | 0, None -> Ok []
- | _ -> Error (Error.handle_error rc ("list_relations failed: " ^ branch_name ^ "/" ^ mg_name))
-
- let list_branch_multigroups (branch_name : string) : ((string * string) list, Condition.t) result
- =
- let path = branch_path branch_name in
- let rc, out_opt =
- Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_list_branch_multigroups path pp)
- in
- match rc, out_opt with
- | 0, Some s ->
- let pairs =
- String.split_on_char '\n' s
- |> List.filter_map (fun line ->
- match String.split_on_char '\t' line with
- | [n; h] when n <> "" -> Some (n, h)
- | _ -> None )
- in
- Ok pairs
- | 0, None -> Ok []
- | _ -> Error (Error.handle_error rc ("list_branch_multigroups failed: " ^ branch_name))
-
- let session_open () : (string, Condition.t) result =
- let rc, id_opt =
- Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_session_open (from_voidp void null) pp)
- in
- match rc, id_opt with
- | 0, Some id -> Ok id
- | _ -> Error (Error.handle_error rc "session_open failed")
-
- let session_close (sid : string) : (unit, Condition.t) result =
- let rc = Nt_ffi.rnt_session_close sid in
- if rc = 0 then Ok () else Error (Error.handle_error rc "session_close failed")
-
- let session_set_branch (sid : string) (branch_name : string) (target_hash : string) :
- (unit, Condition.t) result =
- let rc = Nt_ffi.rnt_session_set_branch sid branch_name target_hash in
- if rc = 0 then Ok () else Error (Error.handle_error rc "session_set_branch failed")
-
- let open_branch (_claims : claims) (name : string) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- let path = branch_path name in
- ensure_branch path;
- let raw = Nt_ffi.rnt_open_handle path (from_voidp void null) in
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.object_not_found "branch" name)
- | Some bh ->
- let* _tip = branch_target_of_handle bh in
- let* rel_entries = list_relations ~branch_name:name ~mg_name:name in
- (* Populate mg with stored relations from RNT snapshot. Schema is not
- persisted in the snapshot codec so relations start schema-less; the
- session rebuilds schema state via DDL or catalog queries. *)
- let mg =
- List.fold_left
- (fun mg (rel_name, root) ->
- let rel =
- (new Relation.stored
- ~name:rel_name ~schema:[] ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
- ~lineage:(Some (Relation.Lineage.Base rel_name))
- ~provenance:(Some (Relation.Provenance.base ~relation:rel_name ~attributes:[]))
- ~membership_criteria:(fun _ -> true) )
- #set_tree_pointer
- (Some root)
- in
- mg#add_relation (rel :> Relation.relation) )
- (new Management.Multigroup.multigroup ~name)
- rel_entries
- in
- Ok (bh, mg)
-
- let close_branch (bh : branch_handle) : (unit, Condition.t) result =
- let rc = Nt_ffi.rnt_close_handle (Nt_ffi.nint_to_ptr bh) in
- if rc = 0 then Ok () else Error (Error.handle_error rc "close_branch failed")
-
- let list_snapshot_relations (snapshot_hash : string) :
- ((string * string) list, Condition.t) result =
- let rc, out_opt =
- Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_list_snapshot_relations snapshot_hash pp)
- in
- match rc, out_opt with
- | 0, Some s ->
- let pairs =
- String.split_on_char '\n' s
- |> List.filter_map (fun line ->
- match String.split_on_char '\t' line with
- | [n; r] when n <> "" -> Some (n, r)
- | _ -> None )
- in
- Ok pairs
- | 0, None -> Ok []
- | _ -> Error (Error.handle_error rc ("list_snapshot_relations failed: " ^ snapshot_hash))
-
- (* Opens a read-only view pinned to a specific snapshot hash.
- Returns (bh, mg) where bh is a handle on the snapshot object and mg
- reflects exactly the relation set at that hash. The caller is
- responsible for refusing mutations (see Branch.Detached). *)
- let open_snapshot (_claims : claims) (branch_name : string) (snapshot_hash : string) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- let snap_path = "/system/snapshots/" ^ snapshot_hash in
- let raw = Nt_ffi.rnt_open_handle snap_path (from_voidp void null) in
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.object_not_found "snapshot" snapshot_hash)
- | Some bh ->
- let* rel_entries = list_snapshot_relations snapshot_hash in
- let mg =
- List.fold_left
- (fun mg (rel_name, root) ->
- let rel =
- (new Relation.stored
- ~name:rel_name ~schema:[] ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
- ~lineage:(Some (Relation.Lineage.Base rel_name))
- ~provenance:(Some (Relation.Provenance.base ~relation:rel_name ~attributes:[]))
- ~membership_criteria:(fun _ -> true) )
- #set_tree_pointer
- (Some root)
- in
- mg#add_relation (rel :> Relation.relation) )
- (new Management.Multigroup.multigroup ~name:branch_name)
- rel_entries
- in
- Ok (bh, mg)
-
- (* --------------------------------------------------------------------------
- Relation handles
- -------------------------------------------------------------------------- *)
-
- let open_relation ~(branch_name : string) ~(mg_name : string) ~(rel_name : string) :
- (relation_handle, Condition.t) result =
- let path = relation_path ~branch_name ~mg_name ~rel_name in
- ignore (Nt_ffi.rnt_register_relation path);
- let raw = Nt_ffi.rnt_open_handle path (from_voidp void null) in
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.object_not_found "relation" rel_name)
- | Some rh -> Ok rh
-
- let close_relation (rh : relation_handle) : (unit, Condition.t) result =
- let rc = Nt_ffi.rnt_close_handle (Nt_ffi.nint_to_ptr rh) in
- if rc = 0 then Ok () else Error (Error.handle_error rc "close_relation failed")
-
- (* --------------------------------------------------------------------------
- Query execution via the Tarski VM plan builder
- -------------------------------------------------------------------------- *)
-
- (* Recursively builds a C side plan tree via rnt_plan_assemble.
- On partial failure the partially built plan is freed before returning
- Error (assemble itself frees child plans it took ownership of when it
- fails, hopefully). *)
- let make_action (op : int) : Nt_ffi.plan_action structure =
- let act = Ctypes.make Nt_ffi.plan_action in
- setf act Nt_ffi.action_operation op;
- act
-
- let rec build_plan (plan : plan_node) : (nativeint, Condition.t) result =
- match plan with
- | Scan {path; _} -> (
- let act = make_action Nt_ffi.fol_operation_scan in
- let cpath = Nt_ffi.cstring path in
- let scan = getf act Nt_ffi.action_scan in
- setf scan Nt_ffi.scan_relation_path (Ctypes.CArray.start cpath);
- let raw = Nt_ffi.rnt_plan_assemble act in
- (* keep the C string alive across the call above *)
- ignore (Sys.opaque_identity cpath);
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.cursor_error ("plan_scan failed for path: " ^ path))
- | Some p -> Ok p )
- | Join {left; right; on_attrs} -> (
- let* lp = build_plan left in
- match build_plan right with
- | Error e ->
- Nt_ffi.rnt_plan_free (Nt_ffi.nint_to_ptr lp);
- Error e
- | Ok rp -> (
- let act = make_action Nt_ffi.fol_operation_join in
- let join = getf act Nt_ffi.action_join in
- setf join Nt_ffi.join_left (Nt_ffi.nint_to_ptr lp);
- setf join Nt_ffi.join_right (Nt_ffi.nint_to_ptr rp);
- (* NULL-terminated const char**; backing arrays stay reachable until
- assemble returns *)
- let cattrs = List.map Nt_ffi.cstring on_attrs in
- let arr =
- Ctypes.CArray.of_list (Ctypes.ptr Ctypes.char)
- (List.map Ctypes.CArray.start cattrs @ [Nt_ffi.null_char_ptr])
- in
- setf join Nt_ffi.join_attrs (Ctypes.CArray.start arr);
- (* assemble takes ownership of both children *)
- let raw = Nt_ffi.rnt_plan_assemble act in
- ignore (Sys.opaque_identity (cattrs, arr));
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.cursor_error "plan_join failed")
- | Some p -> Ok p ) )
- | Take {limit; source} -> (
- let* sp = build_plan source in
- let act = make_action Nt_ffi.fol_operation_take in
- let take = getf act Nt_ffi.action_take in
- setf take Nt_ffi.take_source (Nt_ffi.nint_to_ptr sp);
- setf take Nt_ffi.take_limit (Unsigned.Size_t.of_int limit);
- (* assemble takes ownership of source *)
- match Nt_ffi.ptr_to_opt (Nt_ffi.rnt_plan_assemble act) with
- | None -> Error (Error.cursor_error "plan_take failed")
- | Some p -> Ok p )
- | Project {attrs; source} -> (
- let* sp = build_plan source in
- let act = make_action Nt_ffi.fol_operation_project in
- let proj = getf act Nt_ffi.action_project in
- setf proj Nt_ffi.project_source (Nt_ffi.nint_to_ptr sp);
- (* NULL-terminated const char**; backing arrays stay reachable until
- assemble returns *)
- let cattrs = List.map Nt_ffi.cstring attrs in
- let arr =
- Ctypes.CArray.of_list (Ctypes.ptr Ctypes.char)
- (List.map Ctypes.CArray.start cattrs @ [Nt_ffi.null_char_ptr])
- in
- setf proj Nt_ffi.project_attrs (Ctypes.CArray.start arr);
- let raw = Nt_ffi.rnt_plan_assemble act in
- ignore (Sys.opaque_identity (cattrs, arr));
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.cursor_error "plan_project failed")
- | Some p -> Ok p )
-
- let execute_query (plan : plan_node) ~(rel_name : string) : (tuple_stream, Condition.t) result =
- let* plan_ptr = build_plan plan in
- let raw = Nt_ffi.rnt_vm_execute_plan (Nt_ffi.nint_to_ptr plan_ptr) in
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.cursor_error "vm_execute_plan failed")
- | Some ts_cursor -> Ok {ts_cursor; ts_relation= rel_name; ts_kind= Vm}
-
- let parse_kv_tuple (relation : string) (kv : string) : Tuple.materialized =
- let attributes =
- String.split_on_char '\n' kv
- |> List.filter_map (fun line ->
- match String.split_on_char '=' line with
- | k :: rest when k <> "" ->
- let v = String.concat "=" rest in
- Some (k, Attribute.{value= Obj.repr v})
- | _ -> None )
- |> List.fold_left (fun m (k, v) -> Tuple.AttributeMap.add k v m) Tuple.AttributeMap.empty
- in
- {Tuple.relation; attributes}
-
- let stream_next (stream : tuple_stream) : (Tuple.materialized option, Condition.t) result =
- let next_fn =
- match stream.ts_kind with Simple -> Nt_ffi.rnt_cursor_next | Vm -> Nt_ffi.rnt_vm_cursor_next
- in
- let rc, kv_opt =
- Nt_ffi.with_out_string (fun pp -> next_fn (Nt_ffi.nint_to_ptr stream.ts_cursor) pp)
- in
- match rc with
- | 0 -> Ok None
- | 1 -> (
- match kv_opt with
- | None -> Ok None
- | Some kv -> Ok (Some (parse_kv_tuple stream.ts_relation kv)) )
- | _ -> Error (Error.cursor_error "cursor_next error")
-
- let stream_close (stream : tuple_stream) : (unit, Condition.t) result =
- let close_fn =
- match stream.ts_kind with
- | Simple -> Nt_ffi.rnt_cursor_close
- | Vm -> Nt_ffi.rnt_vm_cursor_close
- in
- let rc = close_fn (Nt_ffi.nint_to_ptr stream.ts_cursor) in
- if rc = 0 then Ok () else Error (Error.cursor_error "cursor_close failed")
-
- (* --------------------------------------------------------------------------
- Tuple storage helpers
- -------------------------------------------------------------------------- *)
-
- let materialized_to_kv (tuple : Tuple.materialized) : (string * string) list =
- Tuple.AttributeMap.fold
- (fun k v acc -> (k, Obj.obj v.Attribute.value) :: acc)
- tuple.Tuple.attributes []
-
- (* --------------------------------------------------------------------------
- Ephemeral relations
-
- RNT invokes a registered generator through a C function pointer for as
- long as the registry entry is aive. ctypes does not root the closure, so
- it is retained here keyed by registered path; reregistration replaces
- the previous retention. (A dropped view's closure is released on the
- next replacement, leaking one closure per named view until then is an
- accepted cost of this slice)
- -------------------------------------------------------------------------- *)
-
- let retained_generators :
- ( string,
- unit Ctypes.ptr -> string -> Unsigned.size_t -> Unsigned.size_t -> unit Ctypes.ptr -> int
- )
- Hashtbl.t =
- Hashtbl.create 16
-
- let register_ephemeral ~(session : string) ~(named : bool) ~(name : string)
- ~(generator : offset:int -> limit:int -> Tuple.materialized list)
- ~(cardinality : Conventions.Cardinality.t) ~(identity : string) ~(schema : Schema.t)
- ~(deps : string list) : (string, Condition.t) result =
- (* The callback re-enters the RNT API (the generator typically executes a
- VM plan over the base relations); the runtime is single threaded, so
- this is safe. Exceptions must not cross the FFI boundary! *)
- let cb _ctx _args offset limit sink =
- try
- let tuples =
- generator ~offset:(Unsigned.Size_t.to_int offset) ~limit:(Unsigned.Size_t.to_int limit)
- in
- List.iter
- (fun t ->
- let kv =
- materialized_to_kv t |> List.map (fun (k, v) -> k ^ "=" ^ v) |> String.concat "\n"
- in
- ignore (Nt_ffi.rnt_sink_emit sink (kv ^ "\n")) )
- tuples;
- 0
- with _ -> -1
- in
- let schema_kv = schema |> List.map (fun (n, d) -> n ^ "=" ^ d) |> String.concat "\n" in
- let card =
- match cardinality with
- | Conventions.Cardinality.Finite _ -> 0
- | Conventions.Cardinality.ConstrainedFinite -> 1
- | Conventions.Cardinality.AlephZero -> 2
- | Conventions.Cardinality.Continuum -> 3
- in
- let rc, path_opt =
- Nt_ffi.with_out_string (fun pp ->
- Nt_ffi.rnt_register_ephemeral_relation session
- (if named then 1 else 0)
- name cb (from_voidp void null) card identity schema_kv (String.concat "\n" deps) pp )
- in
- match rc, path_opt with
- | 0, Some path ->
- Hashtbl.replace retained_generators path cb;
- Ok path
- | rc, _ -> Error (Error.handle_error rc ("register_ephemeral failed: " ^ name))
-
- let drop_ephemeral ~(session : string) ~(name : string) : (unit, Condition.t) result =
- (* TODO: we leak the retained closure here. We can rebuild the path from
- session + name (same formula as Sublanguage_context.ephemeral_path) and
- Hashtbl.remove it, instead of holding onto one closure per dropped view
- until a later re-registration overwrites it. *)
- let rc = Nt_ffi.rnt_drop_ephemeral_relation session name in
- if rc = 0 then Ok () else Error (Error.handle_error rc ("drop_ephemeral failed: " ^ name))
-
- (* --------------------------------------------------------------------------
- Multigroup / branch mutations
-
- Each of these advances the underlying RNT branch to a new snapshot as a
- side effect of the [rnt_register_relation] / [rnt_link_tuple] / ... call.
- The OCaml multigroup is the pure-value mirror: the caller threads the
- returned [mg] through subsequent calls. There is no separate commit step.
- -------------------------------------------------------------------------- *)
-
- let create_multigroup (claims : claims) (name : string) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- open_branch claims name
-
- let create_relation (bh : branch_handle) (mg : Management.Multigroup.multigroup)
- ~(branch_name : string) ~(name : string) ~(schema : Schema.t) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- let mg_name = mg#name in
- let path = relation_path ~branch_name ~mg_name ~rel_name:name in
- let rc = Nt_ffi.rnt_register_relation path in
- if rc <> 0 then Error (Error.handle_error rc ("register_relation failed: " ^ name))
- else
- let rel_root =
- let rc, opt = Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_relation_root path pp) in
- if rc = 0 then Option.value opt ~default:"" else ""
- in
- let rel =
- (new Relation.stored
- ~name ~schema ~constraints:None ~cardinality:Conventions.Cardinality.AlephZero
- ~lineage:(Some (Relation.Lineage.Base name))
- ~provenance:
- (Some (Relation.Provenance.base ~relation:name ~attributes:(List.map fst schema)))
- ~membership_criteria:(fun _ -> true) )
- #set_tree_pointer
- (Some rel_root)
- in
- let* _tip = branch_target_of_handle bh in
- Ok (bh, mg#add_relation (rel :> Relation.relation))
-
- let retract_relation (bh : branch_handle) (mg : Management.Multigroup.multigroup) ~(name : string)
- : (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- (* TODO: there is no rnt_unregister_relation today; the OCaml mg drops the
- relation but RNT continues to carry it in the current snapshot. This
- drifts the two views — fine while the in-memory mg is the source of
- truth for the connection, but worth revisiting once a removal API
- exists on the RNT side. *)
- Ok (bh, mg#remove_relation name)
-
- let clear_relation (bh : branch_handle) (mg : Management.Multigroup.multigroup)
- ~(branch_name : string) ~(mg_name : string) (rel : Relation.relation) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- let path = relation_path ~branch_name ~mg_name ~rel_name:rel#name in
- let rc = Nt_ffi.rnt_clear_relation path in
- if rc <> 0 then Error (Error.handle_error rc ("clear_relation failed: " ^ rel#name))
- else
- let* _tip = branch_target_of_handle bh in
- Ok (bh, mg)
-
- let register_domain (_bh : branch_handle) (mg : Management.Multigroup.multigroup)
- (domain : Relation.domain) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- Ok (_bh, mg#add_relation (domain :> Relation.relation))
-
- let create_immutable_relation (bh : branch_handle) (mg : Management.Multigroup.multigroup) ~name
- ~schema ~generator ~membership_criteria ~cardinality ~(producer : Relation.producer option) :
- (branch_handle * Management.Multigroup.multigroup * Relation.relation, Condition.t) result =
- let rel : Relation.relation =
- match producer with
- | Some p ->
- ( new Relation.pseudo
- ~name ~schema ~constraints:None ~cardinality ~membership_criteria ~lineage:None
- ~provenance:Relation.Provenance.empty ~producer:p
- :> Relation.relation )
- | None ->
- ( new Relation.ephemeral
- ~name ~schema ~constraints:None ~cardinality ~membership_criteria ~lineage:None
- ~provenance:None ~generator
- :> Relation.relation )
- in
- Ok (bh, mg#add_relation rel, rel)
-
- let register_constraint (bh : branch_handle) (mg : Management.Multigroup.multigroup)
- ~constraint_name ~relation_name ~(body : Constraint.t) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- match mg#get_relation relation_name with
- | None -> Error (Error.object_not_found "relation" relation_name)
- | Some rel ->
- let existing = match rel#constraints with Some cs -> cs | None -> [] in
- let new_constraints = (constraint_name, body) :: existing in
- let updated_rel =
- ( Obj.magic rel
- : < set_constraints:
- Relation.RelationConstraint.t ->
- < constraints: Relation.RelationConstraint.t option ; .. > > )
- #set_constraints
- new_constraints
- in
- Ok (bh, mg#update_relation (updated_rel :> Relation.relation))
-
- (* --------------------------------------------------------------------------
- Tuple mutations
- -------------------------------------------------------------------------- *)
-
- let insert_tuple ~(branch_name : string) ~(mg_name : string) ~(rel_name : string)
- (attrs : (string * string) list) : (string, Condition.t) result =
- let path = relation_path ~branch_name ~mg_name ~rel_name in
- let kv_str = List.map (fun (k, v) -> k ^ "=" ^ v ^ "\n") attrs |> String.concat "" in
- let rc, hash_opt = Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_link_tuple path kv_str pp) in
- match rc, hash_opt with
- | 0, Some h -> Ok h
- | _ -> Error (Error.handle_error rc "insert_tuple failed")
-
- let create_tuple ~branch_name ~mg_name ~rel_name (tuple : Tuple.materialized) :
- (string, Condition.t) result =
- insert_tuple ~branch_name ~mg_name ~rel_name (materialized_to_kv tuple)
-
- let create_tuples ~branch_name ~mg_name ~rel_name (tuples : Tuple.materialized list) :
- (string, Condition.t) result =
- List.fold_left
- (fun acc t ->
- let* _ = acc in
- let* h = create_tuple ~branch_name ~mg_name ~rel_name t in
- Ok h )
- (Ok "") tuples
-
- let retract_tuple ~branch_name ~mg_name ~rel_name (hash : string) : (unit, Condition.t) result =
- let path = relation_path ~branch_name ~mg_name ~rel_name in
- let rc = Nt_ffi.rnt_unlink_tuple path hash in
- if rc = 0 then Ok () else Error (Error.handle_error rc "retract_tuple failed")
-
- let relation_root ~(branch_name : string) ~(mg_name : string) ~(rel_name : string) :
- (string, Condition.t) result =
- let path = relation_path ~branch_name ~mg_name ~rel_name in
- let rc, root_opt = Nt_ffi.with_out_string (fun pp -> Nt_ffi.rnt_relation_root path pp) in
- match rc, root_opt with
- | 0, Some r -> Ok r
- | 0, None -> Ok ""
- | _ -> Error (Error.handle_error rc "relation_root query failed")
-
- (* --------------------------------------------------------------------------
- Prelude / catalog bootstrap
- -------------------------------------------------------------------------- *)
-
- (* Seed the catalog prelude into a brand-new (empty) multigroup.
- Creates all catalog relations as stored relations and inserts the
- self-describing tuples into public:relation and public:attribute. Each
- mutation auto-advances the branch internally; there is no separate
- commit step. *)
- let seed_prelude ~(branch_name : string) (branch_handle : branch_handle)
- (multigroup : Management.Multigroup.multigroup) :
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result =
- let mg_name = multigroup#name in
- let* branch_handle, multigroup =
- List.fold_left
- (fun acc (name, schema) ->
- let* branch_handle, multigroup = acc in
- create_relation branch_handle multigroup ~branch_name ~name ~schema )
- (Ok (branch_handle, multigroup))
- Prelude.Catalog.catalog_definitions
- in
- let rel_tuples =
- List.map
- (fun (name, _) -> Prelude.Catalog.build_relation_tuple name)
- Prelude.Catalog.catalog_definitions
- in
- let* _ =
- create_tuples ~branch_name ~mg_name ~rel_name:Prelude.Catalog.relation_rel_name rel_tuples
- in
- let attr_tuples =
- List.concat_map
- (fun (rel_name, schema) ->
- Prelude.Catalog.build_attribute_tuples ~relation_name:rel_name schema )
- Prelude.Catalog.catalog_definitions
- in
- let* _ =
- create_tuples ~branch_name ~mg_name ~rel_name:Prelude.Catalog.attribute_rel_name attr_tuples
- in
- Ok (branch_handle, multigroup)
-
- (* Runtime initialization
- Seeds the catalog of the master branch when it doesn't exist yet; otherwise
- trusts the registry's existing snapshot. The seeded multigroup is
- discarded — each connection's [open_branch] starts from an empty mg
- and the caller threads catalog state from there. Persistence of the
- OCaml-side metadata across process restarts is a separate concern. *)
- let initialize () : (unit, Condition.t) result =
- let runtime_count = Nt_ffi.rnt_init B.driver B.init_arg in
- let* () = if runtime_count <> 0 then Error Error.initialization_error else Ok () in
- let path = "/system/branches/master" in
- ensure_branch path;
- let raw = Nt_ffi.rnt_open_handle path (from_voidp void null) in
- match Nt_ffi.ptr_to_opt raw with
- | None -> Error (Error.object_not_found "branch" "master")
- | Some bh ->
- let* target = branch_target_of_handle bh in
- let result =
- if target = "" then
- let mg = new Management.Multigroup.multigroup ~name:Prelude.Catalog.prelude_mg in
- seed_prelude ~branch_name:"master" bh mg |> Result.map ignore
- else Ok ()
- in
- ignore (Nt_ffi.rnt_close_handle (Nt_ffi.nint_to_ptr bh));
- result
-
- (* --------------------------------------------------------------------------
- Multigroup queries
- -------------------------------------------------------------------------- *)
-
- let get_relation (mg : Management.Multigroup.multigroup) (name : string) :
- Relation.ephemeral option =
- match mg#get_relation name with
- | None -> None
- | Some rel ->
- let gen : Generator.t =
- match rel#kind with
- | `Ephemeral | `Domain -> (Obj.magic rel : < generator: Generator.t >)#generator
- | `Pseudo ->
- ( Obj.magic rel
- : < as_generator: (string * Conventions.AbstractValue.t) list -> Generator.t > )
- #as_generator
- []
- | `Stored ->
- fun _pos ->
- Generator.Error
- ("stored relation must be accessed via Nt.execute_query: " ^ rel#name)
- in
- Some
- (new Relation.ephemeral
- ~name:rel#name ~schema:rel#schema ~constraints:rel#constraints
- ~cardinality:rel#cardinality ~membership_criteria:rel#membership_criteria
- ~lineage:(Some rel#lineage) ~provenance:(Some rel#provenance) ~generator:gen )
-end
-
-module type S = sig
- val initialize : unit -> (unit, Condition.t) result
- val authenticate : auth_method -> (claims, Condition.t) result
- val session_open : unit -> (string, Condition.t) result
- val session_close : string -> (unit, Condition.t) result
- val session_set_branch : string -> string -> string -> (unit, Condition.t) result
-
- val open_branch :
- claims -> string -> (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val open_snapshot :
- claims ->
- string ->
- string ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val close_branch : branch_handle -> (unit, Condition.t) result
- val branch_target_of_handle : branch_handle -> (string, Condition.t) result
-
- val list_relations :
- branch_name:string -> mg_name:string -> ((string * string) list, Condition.t) result
-
- val list_branch_multigroups : string -> ((string * string) list, Condition.t) result
- val list_snapshot_relations : string -> ((string * string) list, Condition.t) result
-
- val open_relation :
- branch_name:string -> mg_name:string -> rel_name:string -> (relation_handle, Condition.t) result
-
- val close_relation : relation_handle -> (unit, Condition.t) result
-
- val register_ephemeral :
- session:string ->
- named:bool ->
- name:string ->
- generator:(offset:int -> limit:int -> Tuple.materialized list) ->
- cardinality:Conventions.Cardinality.t ->
- identity:string ->
- schema:Schema.t ->
- deps:string list ->
- (string, Condition.t) result
-
- val drop_ephemeral : session:string -> name:string -> (unit, Condition.t) result
- val execute_query : plan_node -> rel_name:string -> (tuple_stream, Condition.t) result
- val stream_next : tuple_stream -> (Tuple.materialized option, Condition.t) result
- val stream_close : tuple_stream -> (unit, Condition.t) result
- val get_relation : Management.Multigroup.multigroup -> string -> Relation.ephemeral option
-
- val create_multigroup :
- claims -> string -> (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val create_relation :
- branch_handle ->
- Management.Multigroup.multigroup ->
- branch_name:string ->
- name:string ->
- schema:Schema.t ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val retract_relation :
- branch_handle ->
- Management.Multigroup.multigroup ->
- name:string ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val clear_relation :
- branch_handle ->
- Management.Multigroup.multigroup ->
- branch_name:string ->
- mg_name:string ->
- Relation.relation ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val register_domain :
- branch_handle ->
- Management.Multigroup.multigroup ->
- Relation.domain ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val create_immutable_relation :
- branch_handle ->
- Management.Multigroup.multigroup ->
- name:string ->
- schema:Schema.t ->
- generator:Generator.t ->
- membership_criteria:(Tuple.t -> bool) ->
- cardinality:Conventions.Cardinality.t ->
- producer:Relation.producer option ->
- (branch_handle * Management.Multigroup.multigroup * Relation.relation, Condition.t) result
-
- val register_constraint :
- branch_handle ->
- Management.Multigroup.multigroup ->
- constraint_name:string ->
- relation_name:string ->
- body:Constraint.t ->
- (branch_handle * Management.Multigroup.multigroup, Condition.t) result
-
- val create_tuple :
- branch_name:string ->
- mg_name:string ->
- rel_name:string ->
- Tuple.materialized ->
- (string, Condition.t) result
-
- val create_tuples :
- branch_name:string ->
- mg_name:string ->
- rel_name:string ->
- Tuple.materialized list ->
- (string, Condition.t) result
-
- val retract_tuple :
- branch_name:string -> mg_name:string -> rel_name:string -> string -> (unit, Condition.t) result
-
- val insert_tuple :
- branch_name:string ->
- mg_name:string ->
- rel_name:string ->
- (string * string) list ->
- (string, Condition.t) result
-
- val relation_root :
- branch_name:string -> mg_name:string -> rel_name:string -> (string, Condition.t) result
-end
-
-module Sqlite = Make (struct
- let driver = "sqlite"
- let init_arg = ":memory:"
-end)
-
-module Memory = Make (struct
- let driver = "memory"
- let init_arg = ""
-end)
diff --git a/lib/session.ml b/lib/session.ml
index cac181f..ba4d96d 100644
--- a/lib/session.ml
+++ b/lib/session.ml
@@ -1,44 +1,47 @@
-(* Session: outermost connection object.
- Each user connection mints a session registered in RNT at
- /system/sessions/<id>. The session owns exactly one branch at a time;
- DDL / DML / DRL sublanguages operate through that branch. *)
+type t = {identifier: string; branch: string; snapshot: string; commit: unit}
-module Make (NT : Nt.S) = struct
- module B = Branch.Make (NT)
+let namespace identifier = "\\session\\" ^ identifier
+let branch_path name = "\\branch\\" ^ name
- let ( let* ) = Result.bind
+let snapshot_of kernel branch =
+ let open Utilities.Result in
+ let* handle = RNT.lookup kernel (branch_path branch) in
+ let snapshot = RNT.identity handle in
+ RNT.release handle; Ok snapshot
- class session ~(sid : string) ~(branch : B.branch) =
- object
- val mutable br : B.branch = branch
- method id = sid
- method branch = br
- method snapshot = br#tip
- method tip = br#tip
+class session ~kernel ~identifier ~branch ~snapshot ~commit =
+ object
+ val mutable session : t = {identifier; branch; snapshot; commit}
+ method identifier = session.identifier
+ method branch = session.branch
+ method snapshot = session.snapshot
+ method namespace = namespace session.identifier
- (* Switch to a different branch within the same session. *)
- method set_branch (b : B.branch) = br <- b
+ (** Switch to a different branch within the same session. *)
+ method set_branch_to branch =
+ let open Utilities.Result in
+ let* snapshot = snapshot_of kernel branch in
+ session <- {session with branch; snapshot};
+ Ok ()
- method close () =
- let* () = br#close () in
- NT.session_close sid
- end
+ method close = RNT.remove kernel (namespace session.identifier)
+ end
- (* Open a session on a live (mutable) branch.
- Creates the branch if it does not exist yet. *)
- let open_session claims ~branch_name =
- let* sid = NT.session_open () in
- let* branch = B.open_branch claims branch_name in
- Ok (new session ~sid ~branch)
+(* Open a session on a live (mutable) branch.
+ Creates the branch if it does not exist yet. *)
+let open_session kernel ~branch_name =
+ let open Utilities.Result in
+ let* branches = RNT.list kernel "\\branch" in
+ let* () =
+ if List.mem branch_name branches then Ok ()
+ else RNT.create kernel (branch_path branch_name) RNT.Branch
+ in
+ let* snapshot = snapshot_of kernel branch_name in
+ let identifier = Printf.sprintf "%016Lx" (Random.State.bits64 (Random.State.make_self_init ())) in
+ let* () = RNT.create kernel (namespace identifier) RNT.Namespace in
+ Ok (new session ~kernel ~identifier ~branch:branch_name ~snapshot ~commit:())
- (* Open a session pinned to a specific snapshot hash (detached / read-only).
- Mutations through the returned branch are refused. *)
- let open_snapshot_session claims ~branch_name ~snapshot_hash =
- let* sid = NT.session_open () in
- let* branch = B.open_snapshot claims ~branch_name ~snapshot_hash in
- (* Inform RNT of the session's snapshot override so future session-path
- resolution (once NamespaceReferenceManager handles /system/sessions/...)
- can route queries through the pinned snapshot. *)
- let* () = NT.session_set_branch sid branch_name snapshot_hash in
- Ok (new session ~sid ~branch)
-end
+(* Open a session pinned to a specific snapshot hash (detached / read-only).
+ Mutations through the returned branch are refused. *)
+(* let open_snapshot_session claims ~branch_name ~snapshot_hash = *)
+(* Error *)
diff --git a/lib/sublanguage.ml b/lib/sublanguage.ml
index db66b27..1bdead1 100644
--- a/lib/sublanguage.ml
+++ b/lib/sublanguage.ml
@@ -1,19 +1,3 @@
-(** Effect classification for sublanguage operations.
-
- Categorically:
- - Query: a natural transformation between instance functors (DRL).
- Side-effect-free; the database state is unchanged.
- - Transition: a morphism in the category of database states (DDL, DML, ICL,
- DCL, and future TCL). Produces a new state via substitution (Harper, PFPL
- ch. 3-4).
-
- Future sublanguages:
- - TCL -> Transition (transaction boundaries are state morphisms with
- atomicity constraints)
- - PPL -> new effect class: potentially non-terminating computation
- (Turing-complete; breaks the termination guarantee of DRL)
- - ACL -> a functor restriction: narrows the admissible natural
- transformations based on authorization context *)
include Sublanguage_types
module type S = sig
@@ -22,6 +6,6 @@ module type S = sig
type ast
val name : string
- val parse_sexp : Sexplib.Sexp.t -> (ast, Condition.t) Result.t
- val execute : Sublanguage_context.t -> ast -> (result, Condition.t) Result.t
+ val parse_sexp : Sexplib.Sexp.t -> (ast, Condition.condition) Result.t
+ val execute : Sublanguage_context.t -> ast -> (result, Condition.condition) Result.t
end
diff --git a/lib/sublanguage_context.ml b/lib/sublanguage_context.ml
index df537a7..16b850e 100644
--- a/lib/sublanguage_context.ml
+++ b/lib/sublanguage_context.ml
@@ -1,69 +1 @@
-(** Execution context passed to every sublanguage.
-
- - [write_handle] NT branch handle; sublanguages use it for mutations
- (create_relation, link_tuple, etc). Read-only sublanguages (DRL) ignore
- it.
-
- - [branch] Structural view over the active branch — gives every executor
- random-access to every multigroup bound to the branch via [mg_of]. Schema
- lookups for an FQN [{mg; name}] use
- [branch#mg_of mg |> Option.bind (fun m -> m#get_relation name)].
-
- - [resolve] Builds an absolute RNT path for an FQN. Pass [~branch:b] to
- address a relation on a different live branch (cross-multigroup reads).
-
- - [switch_branch] Closes the current branch and opens the named one,
- returning the delta describing the new branch's mg set. Provided by the
- listener so VCL does not need direct access to session state. *)
-
-type branch_view =
- < name: string
- ; tip: string
- ; mg_of: string -> Management.Multigroup.multigroup option
- ; multigroups: (string * Management.Multigroup.multigroup) list
- ; add_multigroup: name:string -> unit
- ; set_mg: name:string -> Management.Multigroup.multigroup -> unit
- ; path: ?branch:string -> Qualified_name.t -> string >
-
-type t =
- { write_handle: Nt.branch_handle;
- branch: branch_view;
- resolve: ?branch:string -> Qualified_name.t -> string;
- switch_branch: string -> (Sublanguage_types.transition_delta, Condition.t) result;
- session_hash: string
- (** RNT session hash owning this connection. Session-scoped ephemeral
- relations (with DML [Define]) register under
- /system/sessions/<session_hash>/ephemeral/<name>. *)
- }
-
-(** Absolute RNT path of a session-scoped ephemeral relation. An unqualified
- name in a DRL [Base] resolves here: named bindings shadow nothing since
- stored relations always carry an [mg:] qualifier. *)
-let ephemeral_path (ctx : t) (name : string) : string =
- "/system/sessions/" ^ ctx.session_hash ^ "/ephemeral/" ^ name
-
-module Make (NT : Nt.S) = struct
- module Session = Session.Make (NT)
- module Branch = Branch.Make (NT)
-
- let make_ctx (session : Session.session) (claims : string) : t =
- let br = session#branch in
- { write_handle= br#branch_handle;
- branch= (br :> branch_view);
- resolve= br#path;
- session_hash= session#id;
- switch_branch=
- (fun name ->
- match br#close () with
- | Error e -> Error e
- | Ok () ->
- begin match Branch.open_branch claims name with
- | Error e ->
- begin match Branch.open_branch claims "master" with
- | Ok mbr -> session#set_branch mbr
- | Error _ -> ()
- end;
- Error e
- | Ok new_br -> session#set_branch new_br; Ok new_br#multigroups
- end ) }
-end
+type t = {kernel: RNT.kernel; session: Session.session}
diff --git a/lib/sublanguage_types.ml b/lib/sublanguage_types.ml
index a8d6077..73d213a 100644
--- a/lib/sublanguage_types.ml
+++ b/lib/sublanguage_types.ml
@@ -1,13 +1 @@
-type cursor_result = {cursor_id: string; rows: Tuple.materialized list; has_more: bool}
-
-(** A Transition delta names every multigroup that the statement touched and the
- updated value for each. Most statements yield a single-element list;
- VCL/SCL/ICL statements may touch more than one. *)
-type transition_delta = (string * Management.Multigroup.multigroup) list
-
-type result =
- | Query of Relation.relation
- | Transition of transition_delta
- | Cursor of cursor_result
- | SessionSwitch of string
- | CreateMultigroup of string
+type result = Cursor of {rows: RNT.tuple list; has_more: bool}
diff --git a/lib/value.ml b/lib/value.ml
new file mode 100644
index 0000000..eb60b7d
--- /dev/null
+++ b/lib/value.ml
@@ -0,0 +1,6 @@
+type t = String of string | Integer of int
+
+let to_string = function String s -> s | Integer n -> Int.to_string n
+
+let equal x y =
+ match x, y with String x, String y -> x = y | Integer x, Integer y -> x = y | _, _ -> false