diff options
| author | Marcos Magueta <maguetamarcos@gmail.com> | 2026-09-26 22:09:11 -0300 |
|---|---|---|
| committer | Marcos Magueta <maguetamarcos@gmail.com> | 2026-09-26 22:09:11 -0300 |
| commit | 320954996e921d7b34eb61f462799b5c6ab8ee09 (patch) | |
| tree | 16697bc161faf27f0b4c1ba57f6fcc24d240801b /lib | |
| parent | b555c28c7e6c315fc0394baf213ecb08b21ed959 (diff) | |
| download | Sakura-320954996e921d7b34eb61f462799b5c6ab8ee09.tar.gz Sakura-320954996e921d7b34eb61f462799b5c6ab8ee09.zip | |
new shrunk API for RNT on sakura
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/RNT.ml | 231 | ||||
| -rw-r--r-- | lib/RNT.mli | 35 | ||||
| -rw-r--r-- | lib/condition.ml | 49 | ||||
| -rw-r--r-- | lib/configuration.ml | 26 | ||||
| -rw-r--r-- | lib/drl/ast.ml | 3 | ||||
| -rw-r--r-- | lib/drl/executor.ml | 33 | ||||
| -rw-r--r-- | lib/drl/parser.ml | 10 | ||||
| -rw-r--r-- | lib/drl/sublanguage.ml | 7 | ||||
| -rw-r--r-- | lib/dune | 1 | ||||
| -rw-r--r-- | lib/nt.ml | 841 | ||||
| -rw-r--r-- | lib/session.ml | 77 | ||||
| -rw-r--r-- | lib/sublanguage.ml | 20 | ||||
| -rw-r--r-- | lib/sublanguage_context.ml | 70 | ||||
| -rw-r--r-- | lib/sublanguage_types.ml | 14 | ||||
| -rw-r--r-- | lib/value.ml | 6 |
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 @@ -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 |
