aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bin/bench.ml202
-rw-r--r--bin/main.ml18
-rw-r--r--bin/server.ml339
-rw-r--r--lib/algebra.ml363
-rw-r--r--lib/attribute.ml10
-rw-r--r--lib/conventions.ml15
-rw-r--r--lib/core_functions.ml1
-rw-r--r--lib/dbms_language.ml42
-rw-r--r--lib/dcl/ast.ml6
-rw-r--r--lib/dcl/executor.ml160
-rw-r--r--lib/dcl/parser.ml14
-rw-r--r--lib/dcl/sublanguage.ml8
-rw-r--r--lib/ddl/ast.ml4
-rw-r--r--lib/ddl/executor.ml82
-rw-r--r--lib/ddl/parser.ml7
-rw-r--r--lib/ddl/sublanguage.ml8
-rw-r--r--lib/dml/ast.ml15
-rw-r--r--lib/dml/executor.ml156
-rw-r--r--lib/dml/parser.ml7
-rw-r--r--lib/dml/sublanguage.ml8
-rw-r--r--lib/drl/ast.ml36
-rw-r--r--lib/drl/executor.ml100
-rw-r--r--lib/drl/gate.ml81
-rw-r--r--lib/drl/parser.ml7
-rw-r--r--lib/drl/sublanguage.ml16
-rw-r--r--lib/drl_verified/drl_verified.ml99
-rw-r--r--lib/drl_verified/drl_verified.mli28
-rw-r--r--lib/dune1
-rw-r--r--lib/envelope.ml15
-rw-r--r--lib/generator.ml4
-rw-r--r--lib/hashing.ml37
-rw-r--r--lib/icl/ast.ml14
-rw-r--r--lib/icl/parser.ml7
-rw-r--r--lib/icl/sublanguage.ml8
-rw-r--r--lib/interop.ml3
-rw-r--r--lib/management/branch.ml99
-rw-r--r--lib/management/merge.ml435
-rw-r--r--lib/management/physical.ml135
-rw-r--r--lib/merkle.ml41
-rw-r--r--lib/prelude/catalog.ml135
-rw-r--r--lib/prelude/domains.ml105
-rw-r--r--lib/prelude/standard.ml332
-rw-r--r--lib/relation.ml4
-rw-r--r--lib/schema.ml15
-rw-r--r--lib/scl/executor.ml84
-rw-r--r--lib/scl/sublanguage.ml13
-rw-r--r--lib/session.ml89
-rw-r--r--lib/storable.ml35
-rw-r--r--lib/sublanguage.ml26
-rw-r--r--lib/tuple.ml23
-rw-r--r--proof/drl_verified.ml451
-rw-r--r--test/test_relational_engine.ml5942
52 files changed, 5358 insertions, 4527 deletions
diff --git a/bin/bench.ml b/bin/bench.ml
deleted file mode 100644
index 8f496f2..0000000
--- a/bin/bench.ml
+++ /dev/null
@@ -1,202 +0,0 @@
-open Relational_engine
-
-(* helpers *)
-
-let time label f =
- let t0 = Unix.gettimeofday () in
- f ();
- let elapsed = Unix.gettimeofday () -. t0 in
- Printf.printf " %-60s %.4f s\n%!" label elapsed
-
-let mk_int (n : int) : Conventions.AbstractValue.t = Obj.magic n
-
-let make_tuple relation pairs =
- let attributes =
- List.fold_left
- (fun acc (k, v) ->
- Tuple.AttributeMap.add k { Attribute.value = mk_int v } acc)
- Tuple.AttributeMap.empty
- pairs
- in
- { Tuple.relation; attributes }
-
-let unwrap label = function
- | Ok v -> v
- | Error _ -> failwith label
-
-let get_rel db name =
- match Manipulation.Memory.get_relation db ~name with
- | Some r -> r
- | None -> failwith ("relation not found: " ^ name)
-
-(* database setup *)
-
-(*
- Schema:
- Department { dept_id }
- Employee { emp_id, dept_id }
-
- Constraint on Employee:
- Exists d in Department, MemberOf Department (dept_id = Var "dept_id")
-
- This is a classic FK: every employee must reference an existing department.
- On the sublanguages branch, deleting a referenced Department triggers a
- cascade re-check of affected Employee tuples and rejects the delete.
- On master (no cascade), the delete succeeds unconditionally.
-*)
-let setup storage ~n_depts ~employees_per_dept =
- let db =
- unwrap "create_database"
- (Manipulation.Memory.create_database storage ~name:"bench")
- in
- let db =
- fst (unwrap "create Department"
- (Manipulation.Memory.create_relation storage db ~name:"Department"
- ~schema:(Schema.empty |> Schema.add "dept_id" "natural")))
- in
- let db =
- fst (unwrap "create Employee"
- (Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty
- |> Schema.add "emp_id" "natural"
- |> Schema.add "dept_id" "natural")))
- in
- let fk_body =
- Constraint.Exists {
- variable = "d";
- quantifier = "Department";
- body = Constraint.MemberOf {
- target = "Department";
- binding = Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id");
- };
- }
- in
- let db =
- unwrap "register_constraint"
- (Manipulation.Memory.register_constraint storage db
- ~constraint_name:"fk_dept"
- ~relation_name:"Employee"
- ~body:fk_body)
- in
- let dept_tuples =
- List.init n_depts (fun i ->
- make_tuple "Department" [("dept_id", i + 1)])
- in
- let db =
- let (db, _, _) = unwrap "insert departments"
- (Manipulation.Memory.create_tuples storage db
- (get_rel db "Department") dept_tuples)
- in db
- in
- let emp_tuples =
- List.init (n_depts * employees_per_dept) (fun i ->
- make_tuple "Employee" [
- ("emp_id", i + 1);
- ("dept_id", (i mod n_depts) + 1);
- ])
- in
- let db =
- let (db, _, _) = unwrap "insert employees"
- (Manipulation.Memory.create_tuples storage db
- (get_rel db "Employee") emp_tuples)
- in db
- in
- db
-
-(* benchmark cases *)
-
-(*
- Attempt to delete each department that has employees referencing it.
-
- sublanguages: retract_tuple runs the cascade check — uses polarity analysis
- to identify Employee as the constrained relation, then focused_filter to
- narrow the scan to only employees with dept_id = deleted dept_id. Finds a
- match → returns ConstraintViolation. Delete is rejected.
-
- master: no cascade logic; retract_tuple succeeds unconditionally and db
- becomes inconsistent.
-
- db is not threaded through since on sublanguages every delete is rejected
- (db state is unchanged after each Error), and on master we deliberately
- want to measure per-delete cost against a consistent employee set.
-*)
-let bench_retract_referenced storage db ~n_depts =
- let rel = get_rel db "Department" in
- for i = 1 to n_depts do
- let hash = Hashing.hash_tuple (make_tuple "Department" [("dept_id", i)]) in
- ignore (Manipulation.Memory.retract_tuple storage db rel ~tuple_hash:hash)
- done
-
-(*
- Delete departments that have NO referencing employees. Neither branch
- needs to scan Employee tuples (focused_filter finds no matches), so this
- measures raw retract_tuple cost: Merkle tree update + storage write.
-
- We thread db through the loop so each delete builds on the previous state
- and only one live tree version accumulates in memory at a time.
-
- Orphan dept ids start at offset to avoid colliding with the employee-owned
- depts (1..n_depts).
-*)
-let bench_retract_unreferenced storage db ~n_depts ~offset =
- let orphan_tuples =
- List.init n_depts (fun i ->
- make_tuple "Department" [("dept_id", offset + i + 1)])
- in
- let db =
- let (db, _, _) = unwrap "insert orphan depts"
- (Manipulation.Memory.create_tuples storage db
- (get_rel db "Department") orphan_tuples)
- in db
- in
- let _db =
- List.fold_left (fun db i ->
- let rel = get_rel db "Department" in
- let hash = Hashing.hash_tuple (make_tuple "Department" [("dept_id", offset + i)]) in
- match Manipulation.Memory.retract_tuple storage db rel ~tuple_hash:hash with
- | Ok (db, _) -> db
- | Error _ -> db)
- db
- (List.init n_depts (fun i -> i + 1))
- in
- ()
-
-(* entry point *)
-
-let () =
- let n_depts = 100 in
- let employees_per_dept = 200 in
- let total_employees = n_depts * employees_per_dept in
-
- Printf.printf "\nSakura constraint benchmark\n";
- Printf.printf " Departments : %d\n" n_depts;
- Printf.printf " Employees/dept : %d (%d total)\n\n"
- employees_per_dept total_employees;
-
- let make_storage () =
- match Management.Physical.Memory.create () with
- | Ok s -> s
- | Error _ -> failwith "create storage"
- in
-
- (* Warmup build — not measured *)
- let storage = make_storage () in
- ignore (setup storage ~n_depts ~employees_per_dept);
-
- (* Fresh storage for measured runs *)
- let storage = make_storage () in
- let db = setup storage ~n_depts ~employees_per_dept in
-
- Printf.printf "Scenarios:\n";
-
- time (Printf.sprintf
- "retract %d referenced depts (cascade check, violations expected)"
- n_depts)
- (fun () -> bench_retract_referenced storage db ~n_depts);
-
- time (Printf.sprintf
- "retract %d unreferenced depts (no matching employees)"
- n_depts)
- (fun () -> bench_retract_unreferenced storage db ~n_depts ~offset:1_000_000);
-
- Printf.printf "\n"
diff --git a/bin/main.ml b/bin/main.ml
index 46bb9f1..c77d756 100644
--- a/bin/main.ml
+++ b/bin/main.ml
@@ -1,11 +1,17 @@
let main () =
- let digest = Relational_engine.Interop.Sha256.compute_hash (Bytes.of_string "sakura") in
+ let digest =
+ Relational_engine.Interop.Sha256.compute_hash (Bytes.of_string "sakura")
+ in
print_endline digest
let () =
- let tuple =
- Relational_engine.Tuple.make_materialized
- ~relation: "natural_less_than"
- ~attributes: (Relational_engine.Tuple.AttributeMap.of_list [("left", Obj.magic 0); ("right", Obj.magic 1)])
- in print_endline @@ string_of_bool @@ Relational_engine.Prelude.Standard.less_than_natural.membership_criteria tuple;
+ let tuple =
+ Relational_engine.Tuple.make_materialized ~relation:"natural_less_than"
+ ~attributes:
+ (Relational_engine.Tuple.AttributeMap.of_list
+ [ ("left", Obj.magic 0); ("right", Obj.magic 1) ])
+ in
+ print_endline @@ string_of_bool
+ @@ Relational_engine.Prelude.Standard.less_than_natural.membership_criteria
+ tuple;
main ()
diff --git a/bin/server.ml b/bin/server.ml
index 2b9e651..82965dc 100644
--- a/bin/server.ml
+++ b/bin/server.ml
@@ -15,16 +15,17 @@ let materialize_generator gen limit =
if count >= limit then (List.rev acc, true)
else
match gen (Some pos) with
- | Generator.Done -> (List.rev acc, false)
- | Generator.Error _ -> (List.rev acc, false)
- | Generator.Value (t, next) ->
- let mat = match t with
- | Tuple.Materialized m -> Some m
- | Tuple.NonMaterialized _ -> None
- in
- (match mat with
- | None -> go next (pos + 1) acc count
- | Some m -> go next (pos + 1) (m :: acc) (count + 1))
+ | Generator.Done -> (List.rev acc, false)
+ | Generator.Error _ -> (List.rev acc, false)
+ | Generator.Value (t, next) -> (
+ let mat =
+ match t with
+ | Tuple.Materialized m -> Some m
+ | Tuple.NonMaterialized _ -> None
+ in
+ match mat with
+ | None -> go next (pos + 1) acc count
+ | Some m -> go next (pos + 1) (m :: acc) (count + 1))
in
go gen 0 [] 0
@@ -34,106 +35,116 @@ let materialize_relation storage (rel : Relation.t) limit =
let short_hash (h : string) = String.sub h 0 (min 8 (String.length h))
-module BranchOps = Management.Branch.Make(Management.Physical.Memory)
-module Dispatch = Dbms_language.Memory
+module BranchOps = Management.Branch.Make (Management.Physical.Memory)
+module Dispatch = Dbms_language.Memory
let get_branch storage =
- match BranchOps.get_head storage with
- | Ok (Some name) -> name
- | _ -> "--"
+ match BranchOps.get_head storage with Ok (Some name) -> name | _ -> "--"
let tuple_to_sexp (t : Tuple.materialized) =
let open Sexplib.Sexp in
- List (Tuple.AttributeMap.bindings t.Tuple.attributes
- |> List.map (fun (k, attr) ->
- List [Atom k; Conventions.AbstractValue.sexp_of_t attr.Attribute.value]))
+ List
+ (Tuple.AttributeMap.bindings t.Tuple.attributes
+ |> List.map (fun (k, attr) ->
+ List
+ [ Atom k; Conventions.AbstractValue.sexp_of_t attr.Attribute.value ])
+ )
let relation_to_sexp storage db_name db_hash (rel : Relation.t) limit =
let open Sexplib.Sexp in
let schema_sexp =
- List (List.map (fun (a, d) -> List [Atom a; Atom d]) rel.Relation.schema)
+ List (List.map (fun (a, d) -> List [ Atom a; Atom d ]) rel.Relation.schema)
in
- let (tuples, truncated) = materialize_relation storage rel limit in
+ let tuples, truncated = materialize_relation storage rel limit in
let rows_sexp = List (List.map tuple_to_sexp tuples) in
- to_string @@ List [
- Atom "relation";
- List [Atom "name"; Atom rel.Relation.name];
- List [Atom "schema"; schema_sexp];
- List [Atom "rows"; rows_sexp];
- List [Atom "row_count"; Atom (string_of_int (List.length tuples))];
- List [Atom "truncated"; Atom (string_of_bool truncated)];
- List [Atom "db_hash"; Atom (short_hash db_hash)];
- List [Atom "db_name"; Atom db_name];
- List [Atom "branch"; Atom (get_branch storage)];
- ]
+ to_string
+ @@ List
+ [
+ Atom "relation";
+ List [ Atom "name"; Atom rel.Relation.name ];
+ List [ Atom "schema"; schema_sexp ];
+ List [ Atom "rows"; rows_sexp ];
+ List [ Atom "row_count"; Atom (string_of_int (List.length tuples)) ];
+ List [ Atom "truncated"; Atom (string_of_bool truncated) ];
+ List [ Atom "db_hash"; Atom (short_hash db_hash) ];
+ List [ Atom "db_name"; Atom db_name ];
+ List [ Atom "branch"; Atom (get_branch storage) ];
+ ]
let ok_response storage db_name db_hash msg =
let open Sexplib.Sexp in
- to_string @@ List [
- Atom "ok";
- List [Atom "message"; Atom msg];
- List [Atom "db_hash"; Atom (short_hash db_hash)];
- List [Atom "db_name"; Atom db_name];
- List [Atom "branch"; Atom (get_branch storage)];
- ]
+ to_string
+ @@ List
+ [
+ Atom "ok";
+ List [ Atom "message"; Atom msg ];
+ List [ Atom "db_hash"; Atom (short_hash db_hash) ];
+ List [ Atom "db_name"; Atom db_name ];
+ List [ Atom "branch"; Atom (get_branch storage) ];
+ ]
let error_response storage db_name db_hash err_sexp =
let open Sexplib.Sexp in
- to_string @@ List [
- Atom "error";
- err_sexp;
- List [Atom "db_hash"; Atom (short_hash db_hash)];
- List [Atom "db_name"; Atom db_name];
- List [Atom "branch"; Atom (get_branch storage)];
- ]
+ to_string
+ @@ List
+ [
+ Atom "error";
+ err_sexp;
+ List [ Atom "db_hash"; Atom (short_hash db_hash) ];
+ List [ Atom "db_name"; Atom db_name ];
+ List [ Atom "branch"; Atom (get_branch storage) ];
+ ]
let advance_head_branch storage new_hash =
match BranchOps.get_head storage with
| Ok (Some branch_name) ->
- ignore (BranchOps.update_tip storage ~name:branch_name ~tip:new_hash)
+ ignore (BranchOps.update_tip storage ~name:branch_name ~tip:new_hash)
| _ -> ()
-let dbms_dispatch = Dispatch.create [
- (module Drl.Sublanguage.Memory : Dispatch.SubS);
- (module Ddl.Sublanguage.Memory : Dispatch.SubS);
- (module Dml.Sublanguage.Memory : Dispatch.SubS);
- (module Icl.Sublanguage.Memory : Dispatch.SubS);
- (module Dcl.Sublanguage.Memory : Dispatch.SubS);
- (module Scl.Sublanguage.Memory : Dispatch.SubS);
-]
+let dbms_dispatch =
+ Dispatch.create
+ [
+ (module Drl.Sublanguage.Memory : Dispatch.SubS);
+ (module Ddl.Sublanguage.Memory : Dispatch.SubS);
+ (module Dml.Sublanguage.Memory : Dispatch.SubS);
+ (module Icl.Sublanguage.Memory : Dispatch.SubS);
+ (module Dcl.Sublanguage.Memory : Dispatch.SubS);
+ (module Scl.Sublanguage.Memory : Dispatch.SubS);
+ ]
let cursor_to_sexp storage db_name db_hash cursor_id rows has_more =
let open Sexplib.Sexp in
let row_sexps = List.map tuple_to_sexp rows in
let rows_sexp = List row_sexps in
- to_string @@ List [
- Atom "cursor";
- List [Atom "id"; Atom cursor_id];
- List [Atom "rows"; rows_sexp];
- List [Atom "row_count"; Atom (string_of_int (List.length row_sexps))];
- List [Atom "has_more"; Atom (string_of_bool has_more)];
- List [Atom "db_hash"; Atom (short_hash db_hash)];
- List [Atom "db_name"; Atom db_name];
- List [Atom "branch"; Atom (get_branch storage)];
- ]
+ to_string
+ @@ List
+ [
+ Atom "cursor";
+ List [ Atom "id"; Atom cursor_id ];
+ List [ Atom "rows"; rows_sexp ];
+ List [ Atom "row_count"; Atom (string_of_int (List.length row_sexps)) ];
+ List [ Atom "has_more"; Atom (string_of_bool has_more) ];
+ List [ Atom "db_hash"; Atom (short_hash db_hash) ];
+ List [ Atom "db_name"; Atom db_name ];
+ List [ Atom "branch"; Atom (get_branch storage) ];
+ ]
let execute_command storage db_ref cmd =
- let db = !db_ref in
- let h = db.Management.Database.hash in
+ let db = !db_ref in
+ let h = db.Management.Database.hash in
let name = db.Management.Database.name in
- let ok = ok_response storage name in
- let err = error_response storage name in
+ let ok = ok_response storage name in
+ let err = error_response storage name in
match Dispatch.execute dbms_dispatch storage db cmd with
| Ok (Sublanguage.Query rel) ->
- relation_to_sexp storage name h rel default_limit
+ relation_to_sexp storage name h rel default_limit
| Ok (Sublanguage.Transition (new_db, msg)) ->
- db_ref := new_db;
- advance_head_branch storage new_db.Management.Database.hash;
- ok new_db.Management.Database.hash msg
+ db_ref := new_db;
+ advance_head_branch storage new_db.Management.Database.hash;
+ ok new_db.Management.Database.hash msg
| Ok (Sublanguage.Cursor { cursor_id; rows; has_more }) ->
- cursor_to_sexp storage name h cursor_id rows has_more
- | Error e ->
- err h (Dispatch.sexp_of_dispatch_error e)
+ cursor_to_sexp storage name h cursor_id rows has_more
+ | Error e -> err h (Dispatch.sexp_of_dispatch_error e)
(* TODO: registration failures are silently ignored; a broken prelude
relation leaves the catalog in a partially-seeded state with no
@@ -142,29 +153,30 @@ let register_prelude_relations storage db =
let open Prelude.Standard in
List.fold_left
(fun db (rel : Relation.t) ->
- match Manipulation.Memory.create_immutable_relation storage db
- ~name:rel.name
- ~schema:rel.schema
- ~generator:(Option.get rel.generator)
- ~membership_criteria:rel.membership_criteria
- ~cardinality:rel.cardinality
+ match
+ Manipulation.Memory.create_immutable_relation storage db ~name:rel.name
+ ~schema:rel.schema ~generator:(Option.get rel.generator)
+ ~membership_criteria:rel.membership_criteria
+ ~cardinality:rel.cardinality
with
| Ok (new_db, _) -> new_db
- | Error e ->
- Printf.eprintf "Warning: failed to register prelude relation %s: %s\n%!"
- rel.name (Manipulation.string_of_error e);
- db)
+ | Error e ->
+ Printf.eprintf
+ "Warning: failed to register prelude relation %s: %s\n%!" rel.name
+ (Manipulation.string_of_error e);
+ db)
db
- [ less_than_natural
- ; less_than_or_equal_natural
- ; greater_than_natural
- ; greater_than_or_equal_natural
- ; equal_natural
- ; not_equal_natural
- ; plus_natural
- ; times_natural
- ; minus_natural
- ; divide_natural
+ [
+ less_than_natural;
+ less_than_or_equal_natural;
+ greater_than_natural;
+ greater_than_or_equal_natural;
+ equal_natural;
+ not_equal_natural;
+ plus_natural;
+ times_natural;
+ minus_natural;
+ divide_natural;
]
let print_with_time str =
@@ -173,9 +185,9 @@ let print_with_time str =
let orange = "\027[38;5;208m" in
let reset = "\027[0m" in
let formatted_time =
- Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d"
- (tm.Unix.tm_year + 1900) (tm.Unix.tm_mon + 1) tm.Unix.tm_mday
- tm.Unix.tm_hour tm.Unix.tm_min tm.Unix.tm_sec
+ Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d" (tm.Unix.tm_year + 1900)
+ (tm.Unix.tm_mon + 1) tm.Unix.tm_mday tm.Unix.tm_hour tm.Unix.tm_min
+ tm.Unix.tm_sec
in
print_endline @@ Printf.sprintf "%s[%s]%s %s" orange formatted_time reset str
@@ -184,25 +196,26 @@ let print_with_time str =
let handle_client storage db_ref fd =
let ic = Unix.in_channel_of_descr fd in
let oc = Unix.out_channel_of_descr fd in
- (try
- while true do
- let line = input_line ic in
- let line = String.trim line in
- if line <> "" then begin
- let response =
- try execute_command storage db_ref line
- with e ->
- let db = !db_ref in
- error_response storage db.Management.Database.name
- db.Management.Database.hash
- Sexplib.Sexp.(List [Atom "uncaught-error"; Atom (Printexc.to_string e)])
- in
- print_with_time response;
- output_string oc (response ^ "\n");
- flush oc
- end
- done
- with End_of_file | Unix.Unix_error _ -> ())
+ try
+ while true do
+ let line = input_line ic in
+ let line = String.trim line in
+ if line <> "" then begin
+ let response =
+ try execute_command storage db_ref line
+ with e ->
+ let db = !db_ref in
+ error_response storage db.Management.Database.name
+ db.Management.Database.hash
+ Sexplib.Sexp.(
+ List [ Atom "uncaught-error"; Atom (Printexc.to_string e) ])
+ in
+ print_with_time response;
+ output_string oc (response ^ "\n");
+ flush oc
+ end
+ done
+ with End_of_file | Unix.Unix_error _ -> ()
let setup () =
let ( let* ) = Result.bind in
@@ -220,62 +233,62 @@ let register_branch_relations storage db =
(* Register ephemeral sakura:branch and sakura:head relations backed by
the raw branch registry — no stored tuples, no circularity. *)
let register db rel =
- match Manipulation.Memory.create_immutable_relation storage db
- ~name:rel.Relation.name
- ~schema:rel.Relation.schema
- ~generator:(Option.get rel.Relation.generator)
- ~membership_criteria:rel.Relation.membership_criteria
- ~cardinality:rel.Relation.cardinality
+ match
+ Manipulation.Memory.create_immutable_relation storage db
+ ~name:rel.Relation.name ~schema:rel.Relation.schema
+ ~generator:(Option.get rel.Relation.generator)
+ ~membership_criteria:rel.Relation.membership_criteria
+ ~cardinality:rel.Relation.cardinality
with
| Ok (db, _) -> db
| Error e ->
- Printf.eprintf "Warning: failed to register %s: %s\n%!"
- rel.Relation.name (Manipulation.string_of_error e);
- db
+ Printf.eprintf "Warning: failed to register %s: %s\n%!"
+ rel.Relation.name
+ (Manipulation.string_of_error e);
+ db
in
db
|> Fun.flip register (BranchOps.branch_relation storage)
|> Fun.flip register (BranchOps.head_relation storage)
-let warn_on_error fmt = function
- | Ok () -> ()
- | Error e -> Printf.eprintf fmt e
-
-let cursor_gc_max_age = 300.0 (* 5 minutes *)
-let cursor_gc_interval = 60.0 (* run GC at most once per minute *)
+let warn_on_error fmt = function Ok () -> () | Error e -> Printf.eprintf fmt e
+let cursor_gc_max_age = 300.0 (* 5 minutes *)
+let cursor_gc_interval = 60.0 (* run GC at most once per minute *)
let () =
match setup () with
| Error msg -> failwith msg
| Ok (storage, db) ->
- let db = db
- |> register_prelude_relations storage
- |> register_branch_relations storage
- in
- (* Initialise cursor session registry for SCL *)
- let session_reg = Session.create () in
- Scl.Executor.set_sessions session_reg;
- (* Create default master branch in raw registry and set HEAD *)
- BranchOps.create storage ~name:"master" ~tip:db.Management.Database.hash
- |> warn_on_error "Warning: failed to create master branch: %s\n%!";
- BranchOps.checkout storage "master"
- |> warn_on_error "Warning: failed to checkout master: %s\n%!";
- let db_ref = ref db in
- let last_gc = ref 0.0 in
- let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
- Unix.setsockopt sock Unix.SO_REUSEADDR true;
- Unix.setsockopt sock Unix.SO_REUSEPORT true;
- Unix.bind sock (Unix.ADDR_INET (Unix.inet_addr_loopback, default_port));
- Unix.listen sock 5;
- Printf.printf "Sakura server listening on 127.0.0.1:%d\n" default_port;
- Printf.printf "Database hash: %s\n%!" (short_hash db.Management.Database.hash);
- while true do
- let (client_fd, _addr) = Unix.accept sock in
- handle_client storage db_ref client_fd;
- (try Unix.close client_fd with _ -> ());
- let now = Unix.gettimeofday () in
- if now -. !last_gc >= cursor_gc_interval then begin
- Session.gc session_reg ~max_age:cursor_gc_max_age;
- last_gc := now
- end
- done
+ let db =
+ db
+ |> register_prelude_relations storage
+ |> register_branch_relations storage
+ in
+ (* Initialise cursor session registry for SCL *)
+ let session_reg = Session.create () in
+ Scl.Executor.set_sessions session_reg;
+ (* Create default master branch in raw registry and set HEAD *)
+ BranchOps.create storage ~name:"master" ~tip:db.Management.Database.hash
+ |> warn_on_error "Warning: failed to create master branch: %s\n%!";
+ BranchOps.checkout storage "master"
+ |> warn_on_error "Warning: failed to checkout master: %s\n%!";
+ let db_ref = ref db in
+ let last_gc = ref 0.0 in
+ let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
+ Unix.setsockopt sock Unix.SO_REUSEADDR true;
+ Unix.setsockopt sock Unix.SO_REUSEPORT true;
+ Unix.bind sock (Unix.ADDR_INET (Unix.inet_addr_loopback, default_port));
+ Unix.listen sock 5;
+ Printf.printf "Sakura server listening on 127.0.0.1:%d\n" default_port;
+ Printf.printf "Database hash: %s\n%!"
+ (short_hash db.Management.Database.hash);
+ while true do
+ let client_fd, _addr = Unix.accept sock in
+ handle_client storage db_ref client_fd;
+ (try Unix.close client_fd with _ -> ());
+ let now = Unix.gettimeofday () in
+ if now -. !last_gc >= cursor_gc_interval then begin
+ Session.gc session_reg ~max_age:cursor_gc_max_age;
+ last_gc := now
+ end
+ done
diff --git a/lib/algebra.ml b/lib/algebra.ml
index a1970bd..30ed1a6 100644
--- a/lib/algebra.ml
+++ b/lib/algebra.ml
@@ -1,39 +1,40 @@
-type error =
- | StorageError of string
- | GeneratorError of string
+type error = StorageError of string | GeneratorError of string
+
+module Make (Storage : Management.Physical.S) = struct
+ module Ops = Manipulation.Make (Storage)
-module Make(Storage : Management.Physical.S) = struct
- module Ops = Manipulation.Make(Storage)
type storage = Storage.t
(* Internal generator utilities *)
let rec list_generator : Tuple.t list -> Generator.t = function
- | [] -> (fun _pos -> Generator.Done)
- | t :: rest -> (fun _pos -> Generator.Value (t, list_generator rest))
+ | [] -> fun _pos -> Generator.Done
+ | t :: rest -> fun _pos -> Generator.Value (t, list_generator rest)
let drain gen =
let rec go gen pos acc =
match gen (Some pos) with
- | Generator.Done -> Ok (List.rev acc)
- | Generator.Error e -> Error (GeneratorError e)
+ | Generator.Done -> Ok (List.rev acc)
+ | Generator.Error e -> Error (GeneratorError e)
| Generator.Value (t, next) -> go next (pos + 1) (t :: acc)
- in go gen 0 []
+ in
+ go gen 0 []
let to_generator (storage : storage) (rel : Relation.t) : Generator.t =
match rel.Relation.generator with
| Some gen -> gen
- | None ->
- (* TODO: Avoid materializing all tuple hashes/tuples at once.
+ | None -> (
+ (* TODO: Avoid materializing all tuple hashes/tuples at once.
Current implementation calls tuple_hashes rel which loads all hashes
into memory, then load_tuples which loads all tuples. This defeats
lazy evaluation and scales badly (fails for billions of tuples).
Replace with paginated tuple_hashes or Merkle streaming to load
hashes/tuples on demand in chunks. *)
- let hashes = Ops.tuple_hashes rel in
- (match Ops.load_tuples storage hashes with
- | Error _ -> (fun _pos -> Generator.Error ("Failed to load: " ^ rel.Relation.name))
- | Ok ts -> list_generator (List.map (fun t -> Tuple.Materialized t) ts))
+ let hashes = Ops.tuple_hashes rel in
+ match Ops.load_tuples storage hashes with
+ | Error _ ->
+ fun _pos -> Generator.Error ("Failed to load: " ^ rel.Relation.name)
+ | Ok ts -> list_generator (List.map (fun t -> Tuple.Materialized t) ts))
let of_generator ~name ~schema ?constraints ?cardinality gen =
let cardinality =
@@ -41,22 +42,24 @@ module Make(Storage : Management.Physical.S) = struct
| Some c -> c
| None -> Conventions.Cardinality.AlephZero
in
- Relation.make
- ~hash:None ~name ~schema ~tree:None ~constraints
- ~cardinality
+ Relation.make ~hash:None ~name ~schema ~tree:None ~constraints ~cardinality
~generator:(Some gen)
~membership_criteria:(fun _ -> true)
~provenance:Relation.Provenance.Undefined
~lineage:(Relation.Lineage.Base "derived")
- let const_relation (pairs : (string * Conventions.AbstractValue.t) list) : Relation.t =
- let attrs = List.fold_left (fun acc (k, v) ->
- Tuple.AttributeMap.add k { Attribute.value = v } acc)
- Tuple.AttributeMap.empty pairs
+ let const_relation (pairs : (string * Conventions.AbstractValue.t) list) :
+ Relation.t =
+ let attrs =
+ List.fold_left
+ (fun acc (k, v) -> Tuple.AttributeMap.add k { Attribute.value = v } acc)
+ Tuple.AttributeMap.empty pairs
+ in
+ let tuple =
+ Tuple.Materialized { Tuple.relation = "const"; attributes = attrs }
in
- let tuple = Tuple.Materialized { Tuple.relation = "const"; attributes = attrs } in
let schema = List.map (fun (k, _) -> (k, "abstract")) pairs in
- of_generator ~name:"const" ~schema (list_generator [tuple])
+ of_generator ~name:"const" ~schema (list_generator [ tuple ])
(* Operators *)
@@ -65,141 +68,181 @@ module Make(Storage : Management.Physical.S) = struct
let lazy_gen =
let rec go g pos =
match g pos with
- | Generator.Done -> Generator.Done
+ | Generator.Done -> Generator.Done
| Generator.Error e -> Generator.Error e
| Generator.Value (t, next) ->
- if predicate t then Generator.Value (t, go next)
- else go next (Option.map ((+) 1) pos)
- in fun pos -> go gen pos
+ if predicate t then Generator.Value (t, go next)
+ else go next (Option.map (( + ) 1) pos)
+ in
+ fun pos -> go gen pos
in
- Ok (of_generator ~name:("σ_" ^ rel.Relation.name) ~schema:rel.Relation.schema
- ?constraints:rel.Relation.constraints lazy_gen)
+ Ok
+ (of_generator
+ ~name:("σ_" ^ rel.Relation.name)
+ ~schema:rel.Relation.schema ?constraints:rel.Relation.constraints
+ lazy_gen)
let project storage (attrs : string list) rel =
let gen = to_generator storage rel in
let project_tuple = function
| Tuple.Materialized t ->
- let attrs' = List.fold_left (fun acc k ->
- match Tuple.AttributeMap.find_opt k t.Tuple.attributes with
- | None -> acc
- | Some v -> Tuple.AttributeMap.add k v acc)
- Tuple.AttributeMap.empty attrs in
- Tuple.Materialized { t with attributes = attrs' }
+ let attrs' =
+ List.fold_left
+ (fun acc k ->
+ match Tuple.AttributeMap.find_opt k t.Tuple.attributes with
+ | None -> acc
+ | Some v -> Tuple.AttributeMap.add k v acc)
+ Tuple.AttributeMap.empty attrs
+ in
+ Tuple.Materialized { t with attributes = attrs' }
| other -> other
in
- let new_schema = List.filter (fun (n, _) -> List.mem n attrs) rel.Relation.schema in
+ let new_schema =
+ List.filter (fun (n, _) -> List.mem n attrs) rel.Relation.schema
+ in
let lazy_gen =
- let rec go g pos = match g pos with
- | Generator.Done -> Generator.Done
+ let rec go g pos =
+ match g pos with
+ | Generator.Done -> Generator.Done
| Generator.Error e -> Generator.Error e
| Generator.Value (t, next) -> Generator.Value (project_tuple t, go next)
- in fun pos -> go gen pos
+ in
+ fun pos -> go gen pos
in
let filtered_constraints =
match rel.Relation.constraints with
| None | Some [] -> None
- | Some cs ->
- let kept =
- List.filter_map
- (fun (name, c) ->
- match Constraint.filter_by_attrs attrs c with
- | Some c' -> Some (name, c')
- | None -> None)
- cs
- in
- (match kept with [] -> None | _ -> Some kept)
+ | Some cs -> (
+ let kept =
+ List.filter_map
+ (fun (name, c) ->
+ match Constraint.filter_by_attrs attrs c with
+ | Some c' -> Some (name, c')
+ | None -> None)
+ cs
+ in
+ match kept with [] -> None | _ -> Some kept)
in
- Ok (of_generator ~name:("π_" ^ rel.Relation.name) ~schema:new_schema
- ?constraints:filtered_constraints lazy_gen)
+ Ok
+ (of_generator
+ ~name:("π_" ^ rel.Relation.name)
+ ~schema:new_schema ?constraints:filtered_constraints lazy_gen)
let rename storage (renames : (string * string) list) rel =
- let rename_key k = match List.assoc_opt k renames with Some k' -> k' | None -> k in
+ let rename_key k =
+ match List.assoc_opt k renames with Some k' -> k' | None -> k
+ in
let rename_tuple = function
| Tuple.Materialized t ->
- let attrs' = Tuple.AttributeMap.fold (fun k v acc ->
- Tuple.AttributeMap.add (rename_key k) v acc)
- t.Tuple.attributes Tuple.AttributeMap.empty in
- Tuple.Materialized { t with attributes = attrs' }
+ let attrs' =
+ Tuple.AttributeMap.fold
+ (fun k v acc -> Tuple.AttributeMap.add (rename_key k) v acc)
+ t.Tuple.attributes Tuple.AttributeMap.empty
+ in
+ Tuple.Materialized { t with attributes = attrs' }
| other -> other
in
let gen = to_generator storage rel in
- let new_schema = List.map (fun (n, d) -> (rename_key n, d)) rel.Relation.schema in
+ let new_schema =
+ List.map (fun (n, d) -> (rename_key n, d)) rel.Relation.schema
+ in
let lazy_gen =
- let rec go g pos = match g pos with
- | Generator.Done -> Generator.Done
+ let rec go g pos =
+ match g pos with
+ | Generator.Done -> Generator.Done
| Generator.Error e -> Generator.Error e
| Generator.Value (t, next) -> Generator.Value (rename_tuple t, go next)
- in fun pos -> go gen pos
+ in
+ fun pos -> go gen pos
in
let renamed_constraints =
match rel.Relation.constraints with
| None | Some [] -> None
| Some cs ->
- Some
- (List.map
- (fun (name, c) -> (name, Constraint.rename_vars renames c))
- cs)
+ Some
+ (List.map
+ (fun (name, c) -> (name, Constraint.rename_vars renames c))
+ cs)
in
- Ok (of_generator ~name:("ρ_" ^ rel.Relation.name) ~schema:new_schema
- ?constraints:renamed_constraints lazy_gen)
+ Ok
+ (of_generator
+ ~name:("ρ_" ^ rel.Relation.name)
+ ~schema:new_schema ?constraints:renamed_constraints lazy_gen)
let equijoin storage (attrs : string list) left right =
let right_gen = to_generator storage right in
match drain right_gen with
| Error e -> Error e
| Ok right_tuples ->
- let left_gen = to_generator storage left in
- let get_vals attr_list = function
- | Tuple.Materialized t ->
- List.map (fun a -> Tuple.AttributeMap.find_opt a t.Tuple.attributes) attr_list
- | _ -> List.map (fun _ -> None) attr_list
- in
- let merge lt rt = match lt, rt with
- | Tuple.Materialized l, Tuple.Materialized r ->
- Tuple.Materialized { l with attributes =
- Tuple.AttributeMap.union (fun _ a _ -> Some a)
- l.Tuple.attributes r.Tuple.attributes }
- | _ -> lt
- in
- let merged_schema =
- left.Relation.schema @
- List.filter (fun (n, _) ->
- not (List.mem n attrs) &&
- not (List.exists (fun (m, _) -> m = n) left.Relation.schema))
- right.Relation.schema
- in
- let join_gen =
- let rec chain_with_cont g cont pos =
- match g pos with
- | Generator.Done -> cont pos
- | Generator.Value (t, next) -> Generator.Value (t, fun p -> chain_with_cont next cont p)
- | Generator.Error e -> Generator.Error e
+ let left_gen = to_generator storage left in
+ let get_vals attr_list = function
+ | Tuple.Materialized t ->
+ List.map
+ (fun a -> Tuple.AttributeMap.find_opt a t.Tuple.attributes)
+ attr_list
+ | _ -> List.map (fun _ -> None) attr_list
in
- let rec from_left lgen lpos =
- fun pos ->
- match lgen (Some lpos) with
- | Generator.Done -> Generator.Done
- | Generator.Error e -> Generator.Error e
- | Generator.Value (lt, next_left) ->
- let lv = get_vals attrs lt in
- let matches = List.filter_map (fun rt ->
- if get_vals attrs rt = lv then Some (merge lt rt) else None) right_tuples in
- (match matches with
- | [] -> from_left next_left (lpos + 1) pos
- | _ ->
- chain_with_cont (list_generator matches)
- (from_left next_left (lpos + 1)) pos)
+ let merge lt rt =
+ match (lt, rt) with
+ | Tuple.Materialized l, Tuple.Materialized r ->
+ Tuple.Materialized
+ {
+ l with
+ attributes =
+ Tuple.AttributeMap.union
+ (fun _ a _ -> Some a)
+ l.Tuple.attributes r.Tuple.attributes;
+ }
+ | _ -> lt
in
- from_left left_gen 0
- in
- let merged_constraints =
- match left.Relation.constraints, right.Relation.constraints with
- | None, None -> None
- | Some cs, None | None, Some cs -> Some cs
- | Some cs1, Some cs2 -> Some (Constraint.merge cs1 cs2)
- in
- let name = "⋈_" ^ left.Relation.name ^ "_" ^ right.Relation.name in
- Ok (of_generator ~name ~schema:merged_schema
+ let merged_schema =
+ left.Relation.schema
+ @ List.filter
+ (fun (n, _) ->
+ (not (List.mem n attrs))
+ && not (List.exists (fun (m, _) -> m = n) left.Relation.schema))
+ right.Relation.schema
+ in
+ let join_gen =
+ let rec chain_with_cont g cont pos =
+ match g pos with
+ | Generator.Done -> cont pos
+ | Generator.Value (t, next) ->
+ Generator.Value (t, fun p -> chain_with_cont next cont p)
+ | Generator.Error e -> Generator.Error e
+ in
+ let rec from_left lgen lpos =
+ fun pos ->
+ match lgen (Some lpos) with
+ | Generator.Done -> Generator.Done
+ | Generator.Error e -> Generator.Error e
+ | Generator.Value (lt, next_left) -> (
+ let lv = get_vals attrs lt in
+ let matches =
+ List.filter_map
+ (fun rt ->
+ if get_vals attrs rt = lv then Some (merge lt rt)
+ else None)
+ right_tuples
+ in
+ match matches with
+ | [] -> from_left next_left (lpos + 1) pos
+ | _ ->
+ chain_with_cont (list_generator matches)
+ (from_left next_left (lpos + 1))
+ pos)
+ in
+ from_left left_gen 0
+ in
+ let merged_constraints =
+ match (left.Relation.constraints, right.Relation.constraints) with
+ | None, None -> None
+ | Some cs, None | None, Some cs -> Some cs
+ | Some cs1, Some cs2 -> Some (Constraint.merge cs1 cs2)
+ in
+ let name = "⋈_" ^ left.Relation.name ^ "_" ^ right.Relation.name in
+ Ok
+ (of_generator ~name ~schema:merged_schema
?constraints:merged_constraints join_gen)
let union storage rel1 rel2 =
@@ -208,47 +251,55 @@ module Make(Storage : Management.Physical.S) = struct
let chain_gen =
let rec go g1 g2 pos =
match g1 pos with
- | Generator.Done -> g2 pos
+ | Generator.Done -> g2 pos
| Generator.Error _ -> g2 pos
| Generator.Value (t, next) -> Generator.Value (t, fun p -> go next g2 p)
- in fun pos -> go gen1 gen2 pos
+ in
+ fun pos -> go gen1 gen2 pos
in
let name = "∪_" ^ rel1.Relation.name ^ "_" ^ rel2.Relation.name in
(* Conservative: drop constraints since they only hold if both inputs agree *)
Ok (of_generator ~name ~schema:rel1.Relation.schema chain_gen)
- let attrs_equal
- (m1 : Attribute.materialized Tuple.AttributeMap.t)
+ let attrs_equal (m1 : Attribute.materialized Tuple.AttributeMap.t)
(m2 : Attribute.materialized Tuple.AttributeMap.t) =
Tuple.AttributeMap.equal
- (fun a b -> Stdlib.(=) a.Attribute.value b.Attribute.value) m1 m2
+ (fun a b -> Stdlib.( = ) a.Attribute.value b.Attribute.value)
+ m1 m2
let diff storage rel1 rel2 =
let right_gen = to_generator storage rel2 in
match drain right_gen with
| Error e -> Error e
| Ok right_tuples ->
- let right_mats = List.filter_map (function
- | Tuple.Materialized t -> Some t | _ -> None) right_tuples in
- let left_gen = to_generator storage rel1 in
- let not_in_right = function
- | Tuple.Materialized t ->
- not (List.exists (fun r ->
- attrs_equal t.Tuple.attributes r.Tuple.attributes) right_mats)
- | _ -> true
- in
- let lazy_gen =
- let rec go g pos =
- match g pos with
- | Generator.Done -> Generator.Done
- | Generator.Error e -> Generator.Error e
- | Generator.Value (t, next) ->
- if not_in_right t then Generator.Value (t, go next)
- else go next (Option.map ((+) 1) pos)
- in fun pos -> go left_gen pos
- in
- let name = "−_" ^ rel1.Relation.name ^ "_" ^ rel2.Relation.name in
- Ok (of_generator ~name ~schema:rel1.Relation.schema
+ let right_mats =
+ List.filter_map
+ (function Tuple.Materialized t -> Some t | _ -> None)
+ right_tuples
+ in
+ let left_gen = to_generator storage rel1 in
+ let not_in_right = function
+ | Tuple.Materialized t ->
+ not
+ (List.exists
+ (fun r -> attrs_equal t.Tuple.attributes r.Tuple.attributes)
+ right_mats)
+ | _ -> true
+ in
+ let lazy_gen =
+ let rec go g pos =
+ match g pos with
+ | Generator.Done -> Generator.Done
+ | Generator.Error e -> Generator.Error e
+ | Generator.Value (t, next) ->
+ if not_in_right t then Generator.Value (t, go next)
+ else go next (Option.map (( + ) 1) pos)
+ in
+ fun pos -> go left_gen pos
+ in
+ let name = "−_" ^ rel1.Relation.name ^ "_" ^ rel2.Relation.name in
+ Ok
+ (of_generator ~name ~schema:rel1.Relation.schema
?constraints:rel1.Relation.constraints lazy_gen)
let take storage n rel =
@@ -256,22 +307,28 @@ module Make(Storage : Management.Physical.S) = struct
let lazy_gen =
let rec go g count pos =
if count = 0 then Generator.Done
- else match g pos with
- | Generator.Done -> Generator.Done
+ else
+ match g pos with
+ | Generator.Done -> Generator.Done
| Generator.Error e -> Generator.Error e
| Generator.Value (t, next) -> Generator.Value (t, go next (count - 1))
- in fun pos -> go gen n pos
+ in
+ fun pos -> go gen n pos
in
- Ok (of_generator ~name:("τ_" ^ rel.Relation.name) ~schema:rel.Relation.schema
- ?constraints:rel.Relation.constraints
- ~cardinality:(Conventions.Cardinality.Finite n) lazy_gen)
+ Ok
+ (of_generator
+ ~name:("τ_" ^ rel.Relation.name)
+ ~schema:rel.Relation.schema ?constraints:rel.Relation.constraints
+ ~cardinality:(Conventions.Cardinality.Finite n) lazy_gen)
let materialize storage rel : (Tuple.materialized list, error) Result.t =
match drain (to_generator storage rel) with
| Error e -> Error e
| Ok tuples ->
- Ok (List.filter_map (function Tuple.Materialized t -> Some t | _ -> None) tuples)
-
+ Ok
+ (List.filter_map
+ (function Tuple.Materialized t -> Some t | _ -> None)
+ tuples)
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/attribute.ml b/lib/attribute.ml
index f3fc0b1..b828d64 100644
--- a/lib/attribute.ml
+++ b/lib/attribute.ml
@@ -1,15 +1,11 @@
type referenced = { value_hash : Conventions.Hash.t } [@@deriving sexp_of]
+type materialized = { value : Conventions.AbstractValue.t } [@@deriving sexp_of]
-type materialized = { value: Conventions.AbstractValue.t } [@@deriving sexp_of]
-
-type t =
- | Referenced of referenced
- | Materialized of materialized
+type t = Referenced of referenced | Materialized of materialized
[@@deriving sexp_of]
let make_referenced ~value =
let value_hash = Conventions.AbstractValue.hash value in
Referenced { value_hash }
-let make_materialized ~value =
- Materialized { value } \ No newline at end of file
+let make_materialized ~value = Materialized { value }
diff --git a/lib/conventions.ml b/lib/conventions.ml
index 60069b0..b6f82a5 100644
--- a/lib/conventions.ml
+++ b/lib/conventions.ml
@@ -1,5 +1,6 @@
module Hash = struct
type t = Interop.Sha256.t
+
let hash_text text = Interop.Sha256.compute_hash (Bytes.of_string text)
let compare = String.compare
let sexp_of_t = Sexplib.Std.sexp_of_string
@@ -7,21 +8,25 @@ end
module Name = struct
type t = string
+
let sexp_of_t = Sexplib.Std.sexp_of_string
end
module Cardinality = struct
type t = Finite of int | ConstrainedFinite | AlephZero | Continuum
+
let sexp_of_t = function
- | Finite n -> Sexplib.Sexp.(List [Atom "Finite"; Atom (string_of_int n)])
+ | Finite n -> Sexplib.Sexp.(List [ Atom "Finite"; Atom (string_of_int n) ])
| ConstrainedFinite -> Sexplib.Sexp.Atom "ConstrainedFinite"
- | AlephZero -> Sexplib.Sexp.Atom "AlephZero"
- | Continuum -> Sexplib.Sexp.Atom "Continuum"
+ | AlephZero -> Sexplib.Sexp.Atom "AlephZero"
+ | Continuum -> Sexplib.Sexp.Atom "Continuum"
end
module AbstractValue = struct
type t = Obj.t
- let hash (elem: t) = Interop.Sha256.compute_hash @@ Marshal.to_bytes elem []
+
+ let hash (elem : t) = Interop.Sha256.compute_hash @@ Marshal.to_bytes elem []
+
let sexp_of_t (v : t) =
let open Sexplib.Sexp in
if Obj.is_int v then Atom (string_of_int (Obj.obj v : int))
@@ -33,4 +38,4 @@ module AbstractValue = struct
if Float.is_nan f || Float.is_infinite f then Atom "nan"
else Atom (string_of_float f)
else Atom "<opaque>"
-end \ No newline at end of file
+end
diff --git a/lib/core_functions.ml b/lib/core_functions.ml
index 39094cc..9cb6213 100644
--- a/lib/core_functions.ml
+++ b/lib/core_functions.ml
@@ -20,5 +20,4 @@ module Functions (F : Ctypes.FOREIGN) = struct
let compute_hash =
Foreign.foreign ~from:libmerklecpp "compute_hash"
(ptr char @-> Ctypes.string @-> returning void)
-
end
diff --git a/lib/dbms_language.ml b/lib/dbms_language.ml
index 8d61aa3..47e5a99 100644
--- a/lib/dbms_language.ml
+++ b/lib/dbms_language.ml
@@ -1,39 +1,37 @@
(** Composable dispatch over sublanguages.
- All commands must use the tagged envelope format: (tag payload)
- where [tag] is the sublanguage name (drl, dml, ddl, icl, dcl, ...).
- Adding a new sublanguage is just extending the list passed to [create]. *)
+ All commands must use the tagged envelope format: (tag payload) where [tag]
+ is the sublanguage name (drl, dml, ddl, icl, dcl, ...). Adding a new
+ sublanguage is just extending the list passed to [create]. *)
module Make (Storage : Management.Physical.S with type error = string) = struct
module type SubS = Sublanguage.S with type storage = Storage.t
type dispatch_error = NoMatch of Sexplib.Sexp.t
-
type dispatch = (module SubS) list
let create (langs : (module SubS) list) : dispatch = langs
- let execute
- (langs : dispatch)
- (storage : Storage.t)
- (db : Management.Database.t)
- (input : string)
- : (Sublanguage.result, dispatch_error) Result.t =
- let no_match s = Error (NoMatch Sexplib.Sexp.(List [Atom "parse-error"; Atom s])) in
+ let execute (langs : dispatch) (storage : Storage.t)
+ (db : Management.Database.t) (input : string) :
+ (Sublanguage.result, dispatch_error) Result.t =
+ let no_match s =
+ Error (NoMatch Sexplib.Sexp.(List [ Atom "parse-error"; Atom s ]))
+ in
match Envelope.parse input with
| Error msg -> no_match msg
- | Ok { tag; payload } ->
- match List.find_opt (fun (module L : SubS) -> L.name = tag) langs with
- | None -> no_match ("unknown sublanguage: " ^ tag)
- | Some (module L) ->
- match L.parse_sexp payload with
- | Error e -> Error (NoMatch (L.sexp_of_error e))
- | Ok ast ->
- match L.execute storage db ast with
- | Ok result -> Ok result
- | Error e -> Error (NoMatch (L.sexp_of_error e))
+ | Ok { tag; payload } -> (
+ match List.find_opt (fun (module L : SubS) -> L.name = tag) langs with
+ | None -> no_match ("unknown sublanguage: " ^ tag)
+ | Some (module L) -> (
+ match L.parse_sexp payload with
+ | Error e -> Error (NoMatch (L.sexp_of_error e))
+ | Ok ast -> (
+ match L.execute storage db ast with
+ | Ok result -> Ok result
+ | Error e -> Error (NoMatch (L.sexp_of_error e)))))
let sexp_of_dispatch_error (NoMatch sexp) = sexp
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/dcl/ast.ml b/lib/dcl/ast.ml
index e1dae13..0b6107f 100644
--- a/lib/dcl/ast.ml
+++ b/lib/dcl/ast.ml
@@ -4,10 +4,10 @@ type merge_strategy = PreferLeft | PreferRight | RevertToAncestor
[@@deriving sexp]
type statement =
- | CreateBranch of { name: string; hash: string option [@sexp.option] }
+ | CreateBranch of { name : string; hash : string option [@sexp.option] }
| Checkout of string
| GetHead
| GetBranchTip of string
- | UpdateBranchTip of { name: string; hash: string }
- | Merge of { left: string; right: string; strategy: merge_strategy }
+ | UpdateBranchTip of { name : string; hash : string }
+ | Merge of { left : string; right : string; strategy : merge_strategy }
[@@deriving sexp]
diff --git a/lib/dcl/executor.ml b/lib/dcl/executor.ml
index cf2cfed..8c2fbdc 100644
--- a/lib/dcl/executor.ml
+++ b/lib/dcl/executor.ml
@@ -1,94 +1,94 @@
module Make (Storage : Management.Physical.S with type error = string) = struct
- module Ops = Manipulation.Make(Storage)
- module Branch = Management.Branch.Make(Storage)
- module MergeOps = Management.Merge.Make(Storage)(Ops)
+ module Ops = Manipulation.Make (Storage)
+ module Branch = Management.Branch.Make (Storage)
+ module MergeOps = Management.Merge.Make (Storage) (Ops)
type error =
- | ParseError of string
+ | ParseError of string
| BranchError of string
- | MergeError of string
+ | MergeError of string
let sexp_of_error e =
let open Sexplib.Sexp in
match e with
- | ParseError s -> List [Atom "parse-error"; Atom s]
- | BranchError s -> List [Atom "branch-error"; Atom s]
- | MergeError s -> List [Atom "merge-error"; Atom s]
+ | ParseError s -> List [ Atom "parse-error"; Atom s ]
+ | BranchError s -> List [ Atom "branch-error"; Atom s ]
+ | MergeError s -> List [ Atom "merge-error"; Atom s ]
- let convert_strategy : Ast.merge_strategy -> Management.Merge.strategy = function
- | Ast.PreferLeft -> Management.Merge.PreferLeft
- | Ast.PreferRight -> Management.Merge.PreferRight
+ let convert_strategy : Ast.merge_strategy -> Management.Merge.strategy =
+ function
+ | Ast.PreferLeft -> Management.Merge.PreferLeft
+ | Ast.PreferRight -> Management.Merge.PreferRight
| Ast.RevertToAncestor -> Management.Merge.RevertToAncestor
- let execute
- (storage : Storage.t)
- (db : Management.Database.t)
- (stmt : Ast.statement)
- : (Management.Database.t * string, error) result =
+ let execute (storage : Storage.t) (db : Management.Database.t)
+ (stmt : Ast.statement) : (Management.Database.t * string, error) result =
match stmt with
-
- | Ast.CreateBranch { name; hash } ->
- let tip = match hash with
- | Some h -> h
- | None -> db.Management.Database.hash
- in
- (match Branch.create storage ~name ~tip with
- | Error e -> Error (BranchError e)
- | Ok () -> Ok (db, "Branch " ^ name ^ " created"))
-
- | Ast.Checkout branch_name ->
- (match Branch.get_tip storage branch_name with
- | Error e -> Error (BranchError e)
- | Ok None -> Error (BranchError ("Branch not found: " ^ branch_name))
- | Ok (Some tip) ->
- (match Branch.checkout storage branch_name with
- | Error e -> Error (BranchError e)
- | Ok () ->
- (match Ops.load_database storage tip with
- | Error e -> Error (BranchError (Manipulation.string_of_error e))
- | Ok None -> Error (BranchError ("No database at hash: " ^ tip))
- | Ok (Some loaded_db) ->
- Ok (loaded_db, "HEAD:" ^ branch_name))))
-
- | Ast.GetHead ->
- (match Branch.get_head storage with
- | Error e -> Error (BranchError e)
- | Ok None -> Ok (db, "HEAD is unset")
- | Ok (Some name) -> Ok (db, "HEAD:" ^ name))
-
- | Ast.GetBranchTip name ->
- (match Branch.get_tip storage name with
- | Error e -> Error (BranchError e)
- | Ok None -> Error (BranchError ("Branch not found: " ^ name))
- | Ok (Some hash) -> Ok (db, "branch:" ^ name ^ "=" ^ hash))
-
- | Ast.UpdateBranchTip { name; hash } ->
- (match Branch.update_tip storage ~name ~tip:hash with
- | Error e -> Error (BranchError e)
- | Ok () -> Ok (db, "Branch " ^ name ^ " updated"))
-
- | Ast.Merge { left; right; strategy } ->
- (match Branch.get_tip storage left, Branch.get_tip storage right with
- | Error e, _ | _, Error e -> Error (BranchError e)
- | Ok None, _ -> Error (BranchError ("Branch not found: " ^ left))
- | _, Ok None -> Error (BranchError ("Branch not found: " ^ right))
- | Ok (Some left_tip), Ok (Some right_tip) ->
- let strat = convert_strategy strategy in
- (match MergeOps.merge ~storage ~strategy:strat ~left_tip ~right_tip with
- | Error e -> Error (MergeError (Manipulation.string_of_error e))
- | Ok (Management.Merge.Failed conflicts) ->
- let msgs = List.map (function
- | Management.Merge.TupleConflict { relation; hash } ->
- "tuple conflict in " ^ relation ^ " (" ^ hash ^ ")"
- | Management.Merge.SchemaConflict r -> "schema conflict in " ^ r
- | Management.Merge.ConstraintConflict r -> "constraint conflict in " ^ r
- ) conflicts in
- Error (MergeError ("Merge failed: " ^ String.concat "; " msgs))
- | Ok (Management.Merge.Clean merged_db) ->
- let _ = Branch.update_tip storage ~name:left
- ~tip:merged_db.Management.Database.hash in
- Ok (merged_db,
- "Merged:" ^ right ^ "→" ^ left)))
+ | Ast.CreateBranch { name; hash } -> (
+ let tip =
+ match hash with Some h -> h | None -> db.Management.Database.hash
+ in
+ match Branch.create storage ~name ~tip with
+ | Error e -> Error (BranchError e)
+ | Ok () -> Ok (db, "Branch " ^ name ^ " created"))
+ | Ast.Checkout branch_name -> (
+ match Branch.get_tip storage branch_name with
+ | Error e -> Error (BranchError e)
+ | Ok None -> Error (BranchError ("Branch not found: " ^ branch_name))
+ | Ok (Some tip) -> (
+ match Branch.checkout storage branch_name with
+ | Error e -> Error (BranchError e)
+ | Ok () -> (
+ match Ops.load_database storage tip with
+ | Error e ->
+ Error (BranchError (Manipulation.string_of_error e))
+ | Ok None -> Error (BranchError ("No database at hash: " ^ tip))
+ | Ok (Some loaded_db) -> Ok (loaded_db, "HEAD:" ^ branch_name)))
+ )
+ | Ast.GetHead -> (
+ match Branch.get_head storage with
+ | Error e -> Error (BranchError e)
+ | Ok None -> Ok (db, "HEAD is unset")
+ | Ok (Some name) -> Ok (db, "HEAD:" ^ name))
+ | Ast.GetBranchTip name -> (
+ match Branch.get_tip storage name with
+ | Error e -> Error (BranchError e)
+ | Ok None -> Error (BranchError ("Branch not found: " ^ name))
+ | Ok (Some hash) -> Ok (db, "branch:" ^ name ^ "=" ^ hash))
+ | Ast.UpdateBranchTip { name; hash } -> (
+ match Branch.update_tip storage ~name ~tip:hash with
+ | Error e -> Error (BranchError e)
+ | Ok () -> Ok (db, "Branch " ^ name ^ " updated"))
+ | Ast.Merge { left; right; strategy } -> (
+ match (Branch.get_tip storage left, Branch.get_tip storage right) with
+ | Error e, _ | _, Error e -> Error (BranchError e)
+ | Ok None, _ -> Error (BranchError ("Branch not found: " ^ left))
+ | _, Ok None -> Error (BranchError ("Branch not found: " ^ right))
+ | Ok (Some left_tip), Ok (Some right_tip) -> (
+ let strat = convert_strategy strategy in
+ match
+ MergeOps.merge ~storage ~strategy:strat ~left_tip ~right_tip
+ with
+ | Error e -> Error (MergeError (Manipulation.string_of_error e))
+ | Ok (Management.Merge.Failed conflicts) ->
+ let msgs =
+ List.map
+ (function
+ | Management.Merge.TupleConflict { relation; hash } ->
+ "tuple conflict in " ^ relation ^ " (" ^ hash ^ ")"
+ | Management.Merge.SchemaConflict r ->
+ "schema conflict in " ^ r
+ | Management.Merge.ConstraintConflict r ->
+ "constraint conflict in " ^ r)
+ conflicts
+ in
+ Error (MergeError ("Merge failed: " ^ String.concat "; " msgs))
+ | Ok (Management.Merge.Clean merged_db) ->
+ let _ =
+ Branch.update_tip storage ~name:left
+ ~tip:merged_db.Management.Database.hash
+ in
+ Ok (merged_db, "Merged:" ^ right ^ "→" ^ left)))
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/dcl/parser.ml b/lib/dcl/parser.ml
index ef64924..79d8fc7 100644
--- a/lib/dcl/parser.ml
+++ b/lib/dcl/parser.ml
@@ -3,16 +3,12 @@ type error = ParseError of string
let of_sexp sexp =
(* ppx_sexp_conv encodes no-argument variants as bare atoms, so
unwrap a single-element list (e.g. (GetHead)) → atom (GetHead). *)
- let sexp = match sexp with
- | Sexplib.Sexp.List [atom] -> atom
- | other -> other
+ let sexp =
+ match sexp with Sexplib.Sexp.List [ atom ] -> atom | other -> other
in
match Ast.statement_of_sexp sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
-let of_string s =
- of_sexp (Sexplib.Sexp.of_string s)
-
-let to_string stmt =
- Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
+let of_string s = of_sexp (Sexplib.Sexp.of_string s)
+let to_string stmt = Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
diff --git a/lib/dcl/sublanguage.ml b/lib/dcl/sublanguage.ml
index 112b6ed..c36e2a4 100644
--- a/lib/dcl/sublanguage.ml
+++ b/lib/dcl/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S with type error = string) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.statement
- type error = Exec.error
+ type ast = Ast.statement
+ type error = Exec.error
let name = "dcl"
@@ -20,4 +20,4 @@ module Make (Storage : Management.Physical.S with type error = string) = struct
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/ddl/ast.ml b/lib/ddl/ast.ml
index 7faee40..ee39edb 100644
--- a/lib/ddl/ast.ml
+++ b/lib/ddl/ast.ml
@@ -9,8 +9,8 @@ type cardinality_spec =
type statement =
| CreateDatabase of string
- | CreateRelation of { name: string; schema: (string * string) list }
+ | CreateRelation of { name : string; schema : (string * string) list }
| RetractRelation of string
| ClearRelation of string
- | RegisterDomain of { name: string; cardinality: cardinality_spec }
+ | RegisterDomain of { name : string; cardinality : cardinality_spec }
[@@deriving sexp]
diff --git a/lib/ddl/executor.ml b/lib/ddl/executor.ml
index fbb2f62..d374ffe 100644
--- a/lib/ddl/executor.ml
+++ b/lib/ddl/executor.ml
@@ -1,68 +1,64 @@
module Make (Storage : Management.Physical.S) = struct
- module Ops = Manipulation.Make(Storage)
+ module Ops = Manipulation.Make (Storage)
type error =
- | ParseError of string
+ | ParseError of string
| ManipulationError of Manipulation.error
- | RelationNotFound of string
+ | RelationNotFound of string
let sexp_of_error e =
let open Sexplib.Sexp in
match e with
- | ParseError s -> List [Atom "parse-error"; Atom s]
+ | ParseError s -> List [ Atom "parse-error"; Atom s ]
| ManipulationError e -> Manipulation.sexp_of_error e
- | RelationNotFound s -> List [Atom "relation-not-found"; Atom s]
+ | RelationNotFound s -> List [ Atom "relation-not-found"; Atom s ]
let ( let* ) = Result.bind
-
let wrap_manip r = Result.map_error (fun e -> ManipulationError e) r
let get_rel db name =
- Ops.get_relation db ~name
- |> Option.to_result ~none:(RelationNotFound name)
+ Ops.get_relation db ~name |> Option.to_result ~none:(RelationNotFound name)
- let convert_cardinality : Ast.cardinality_spec -> Conventions.Cardinality.t = function
- | Ast.Finite n -> Conventions.Cardinality.Finite n
- | Ast.AlephZero -> Conventions.Cardinality.AlephZero
- | Ast.Continuum -> Conventions.Cardinality.Continuum
+ let convert_cardinality : Ast.cardinality_spec -> Conventions.Cardinality.t =
+ function
+ | Ast.Finite n -> Conventions.Cardinality.Finite n
+ | Ast.AlephZero -> Conventions.Cardinality.AlephZero
+ | Ast.Continuum -> Conventions.Cardinality.Continuum
| Ast.ConstrainedFinite -> Conventions.Cardinality.ConstrainedFinite
- let execute
- (storage : Storage.t)
- (db : Management.Database.t)
- (stmt : Ast.statement)
- : (Management.Database.t * string, error) result =
+ let execute (storage : Storage.t) (db : Management.Database.t)
+ (stmt : Ast.statement) : (Management.Database.t * string, error) result =
match stmt with
| Ast.CreateDatabase name ->
- let* db = Ops.create_database storage ~name |> wrap_manip in
- Ok (db, "Database created: " ^ name)
-
+ let* db = Ops.create_database storage ~name |> wrap_manip in
+ Ok (db, "Database created: " ^ name)
| Ast.CreateRelation { name; schema = schema_pairs } ->
- let schema =
- List.fold_left (fun s (attr, dom) -> Schema.add attr dom s) Schema.empty schema_pairs
- in
- let* (db, _) = Ops.create_relation storage db ~name ~schema |> wrap_manip in
- Ok (db, "Relation created: " ^ name)
-
+ let schema =
+ List.fold_left
+ (fun s (attr, dom) -> Schema.add attr dom s)
+ Schema.empty schema_pairs
+ in
+ let* db, _ =
+ Ops.create_relation storage db ~name ~schema |> wrap_manip
+ in
+ Ok (db, "Relation created: " ^ name)
| Ast.RetractRelation name ->
- let* db = Ops.retract_relation storage db ~name |> wrap_manip in
- Ok (db, "Relation retracted: " ^ name)
-
+ let* db = Ops.retract_relation storage db ~name |> wrap_manip in
+ Ok (db, "Relation retracted: " ^ name)
| Ast.ClearRelation name ->
- let* rel = get_rel db name in
- let* (db, _) = Ops.clear_relation storage db rel |> wrap_manip in
- Ok (db, "Relation cleared: " ^ name)
-
+ let* rel = get_rel db name in
+ let* db, _ = Ops.clear_relation storage db rel |> wrap_manip in
+ Ok (db, "Relation cleared: " ^ name)
| Ast.RegisterDomain { name; cardinality } ->
- let domain = Domain.make
- ~name
- ~generator:(fun _ -> Generator.Error "not enumerable via DDL")
- ~membership_criteria:(fun _ -> true)
- ~cardinality:(convert_cardinality cardinality)
- ~compare:Stdlib.compare
- in
- let* db = Ops.register_domain storage db domain |> wrap_manip in
- Ok (db, "Domain registered: " ^ name)
+ let domain =
+ Domain.make ~name
+ ~generator:(fun _ -> Generator.Error "not enumerable via DDL")
+ ~membership_criteria:(fun _ -> true)
+ ~cardinality:(convert_cardinality cardinality)
+ ~compare:Stdlib.compare
+ in
+ let* db = Ops.register_domain storage db domain |> wrap_manip in
+ Ok (db, "Domain registered: " ^ name)
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/ddl/parser.ml b/lib/ddl/parser.ml
index 28f8b6c..5678b47 100644
--- a/lib/ddl/parser.ml
+++ b/lib/ddl/parser.ml
@@ -2,13 +2,12 @@ type error = ParseError of string
let of_sexp sexp =
match Ast.statement_of_sexp sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
let of_string s =
match Sexplib.Sexp.of_string s |> Ast.statement_of_sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
-let to_string stmt =
- Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
+let to_string stmt = Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
diff --git a/lib/ddl/sublanguage.ml b/lib/ddl/sublanguage.ml
index 26a012f..bf40a05 100644
--- a/lib/ddl/sublanguage.ml
+++ b/lib/ddl/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.statement
- type error = Exec.error
+ type ast = Ast.statement
+ type error = Exec.error
let name = "ddl"
@@ -20,4 +20,4 @@ module Make (Storage : Management.Physical.S) = struct
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/dml/ast.ml b/lib/dml/ast.ml
index 479437a..d8defe6 100644
--- a/lib/dml/ast.ml
+++ b/lib/dml/ast.ml
@@ -1,13 +1,12 @@
open Sexplib.Std
-type attr_value = string * Drl.Ast.value
-[@@deriving sexp]
+type attr_value = string * Drl.Ast.value [@@deriving sexp]
type statement =
- | InsertTuple of { relation: string; attributes: attr_value list }
- | InsertTuples of { relation: string; tuples: attr_value list list }
- | DeleteTuple of { relation: string; attributes: attr_value list }
- | Assign of { target: string; body: Drl.Ast.query }
- | InsertFrom of { target: string; source: Drl.Ast.query }
- | DeleteWhere of { target: string; predicate: Drl.Ast.query }
+ | InsertTuple of { relation : string; attributes : attr_value list }
+ | InsertTuples of { relation : string; tuples : attr_value list list }
+ | DeleteTuple of { relation : string; attributes : attr_value list }
+ | Assign of { target : string; body : Drl.Ast.query }
+ | InsertFrom of { target : string; source : Drl.Ast.query }
+ | DeleteWhere of { target : string; predicate : Drl.Ast.query }
[@@deriving sexp]
diff --git a/lib/dml/executor.ml b/lib/dml/executor.ml
index 7c08a24..08f1a26 100644
--- a/lib/dml/executor.ml
+++ b/lib/dml/executor.ml
@@ -1,117 +1,131 @@
module Make (Storage : Management.Physical.S) = struct
- module Ops = Manipulation.Make(Storage)
- module Alg = Algebra.Make(Storage)
- module DrlExec = Drl.Executor.Make(Storage)
+ module Ops = Manipulation.Make (Storage)
+ module Alg = Algebra.Make (Storage)
+ module DrlExec = Drl.Executor.Make (Storage)
type error =
- | ParseError of string
+ | ParseError of string
| ManipulationError of Manipulation.error
- | RelationNotFound of string
- | AlgebraError of Algebra.error
+ | RelationNotFound of string
+ | AlgebraError of Algebra.error
let sexp_of_error e =
let open Sexplib.Sexp in
match e with
- | ParseError s -> List [Atom "parse-error"; Atom s]
- | ManipulationError e -> Manipulation.sexp_of_error e
- | RelationNotFound s -> List [Atom "relation-not-found"; Atom s]
- | AlgebraError (Algebra.StorageError s) -> List [Atom "storage-error"; Atom s]
- | AlgebraError (Algebra.GeneratorError s) -> List [Atom "generator-error"; Atom s]
+ | ParseError s -> List [ Atom "parse-error"; Atom s ]
+ | ManipulationError e -> Manipulation.sexp_of_error e
+ | RelationNotFound s -> List [ Atom "relation-not-found"; Atom s ]
+ | AlgebraError (Algebra.StorageError s) ->
+ List [ Atom "storage-error"; Atom s ]
+ | AlgebraError (Algebra.GeneratorError s) ->
+ List [ Atom "generator-error"; Atom s ]
let ( let* ) = Result.bind
-
let wrap_manip r = Result.map_error (fun e -> ManipulationError e) r
-
let wrap_alg e = AlgebraError e
let get_rel db name =
match Ops.get_relation db ~name with
| Some r -> Ok r
- | None -> Error (RelationNotFound name)
+ | None -> Error (RelationNotFound name)
- let retarget target (t : Tuple.materialized) = { t with Tuple.relation = target }
+ let retarget target (t : Tuple.materialized) =
+ { t with Tuple.relation = target }
(** Build a materialized tuple from a list of (attr_name, ast_value) pairs *)
- let build_tuple ~relation (attributes : Ast.attr_value list) : Tuple.materialized =
+ let build_tuple ~relation (attributes : Ast.attr_value list) :
+ Tuple.materialized =
let attr_map =
List.fold_left
(fun acc (name, value) ->
Tuple.AttributeMap.add name
{ Attribute.value = Drl.Ast.value_to_abstract value }
acc)
- Tuple.AttributeMap.empty
- attributes
+ Tuple.AttributeMap.empty attributes
in
{ Tuple.relation; attributes = attr_map }
let eval_query storage db query =
match DrlExec.execute storage db query with
| Ok rel -> Ok rel
- | Error (DrlExec.ParseError s) -> Error (ParseError s)
+ | Error (DrlExec.ParseError s) -> Error (ParseError s)
| Error (DrlExec.RelationNotFound s) -> Error (RelationNotFound s)
- | Error (DrlExec.AlgebraError e) -> Error (AlgebraError e)
+ | Error (DrlExec.AlgebraError e) -> Error (AlgebraError e)
let materialize_tuples storage rel =
Result.map_error wrap_alg (Alg.materialize storage rel)
- let execute
- (storage : Storage.t)
- (db : Management.Database.t)
- (stmt : Ast.statement)
- : (Management.Database.t, error) result =
+ let execute (storage : Storage.t) (db : Management.Database.t)
+ (stmt : Ast.statement) : (Management.Database.t, error) result =
match stmt with
| Ast.InsertTuple { relation; attributes } ->
- let* rel = get_rel db relation in
- let tuple = build_tuple ~relation attributes in
- let* (db, _, _) = Ops.create_tuple storage db rel tuple |> wrap_manip in
- Ok db
-
+ let* rel = get_rel db relation in
+ let tuple = build_tuple ~relation attributes in
+ let* db, _, _ = Ops.create_tuple storage db rel tuple |> wrap_manip in
+ Ok db
| Ast.InsertTuples { relation; tuples } ->
- let* rel = get_rel db relation in
- let tuple_list = List.map (build_tuple ~relation) tuples in
- let* (db, _, _) = Ops.create_tuples storage db rel tuple_list |> wrap_manip in
- Ok db
-
+ let* rel = get_rel db relation in
+ let tuple_list = List.map (build_tuple ~relation) tuples in
+ let* db, _, _ =
+ Ops.create_tuples storage db rel tuple_list |> wrap_manip
+ in
+ Ok db
| Ast.DeleteTuple { relation; attributes } ->
- let* rel = get_rel db relation in
- let tuple = build_tuple ~relation attributes in
- let tuple_hash = Hashing.hash_tuple tuple in
- let* (db, _) = Ops.retract_tuple storage db rel ~tuple_hash |> wrap_manip in
- Ok db
-
+ let* rel = get_rel db relation in
+ let tuple = build_tuple ~relation attributes in
+ let tuple_hash = Hashing.hash_tuple tuple in
+ let* db, _ =
+ Ops.retract_tuple storage db rel ~tuple_hash |> wrap_manip
+ in
+ Ok db
| Ast.Assign { target; body } ->
- let* rel = get_rel db target in
- let* result_rel = eval_query storage db body in
- let* tuples = materialize_tuples storage result_rel in
- let* (db, rel) = Ops.clear_relation storage db rel |> wrap_manip in
- let* (db, _, _) = Ops.create_tuples storage db rel (List.map (retarget target) tuples) |> wrap_manip in
- Ok db
-
+ let* rel = get_rel db target in
+ let* result_rel = eval_query storage db body in
+ let* tuples = materialize_tuples storage result_rel in
+ let* db, rel = Ops.clear_relation storage db rel |> wrap_manip in
+ let* db, _, _ =
+ Ops.create_tuples storage db rel (List.map (retarget target) tuples)
+ |> wrap_manip
+ in
+ Ok db
| Ast.InsertFrom { target; source } ->
- let* rel = get_rel db target in
- let* result_rel = eval_query storage db source in
- let* tuples = materialize_tuples storage result_rel in
- let* (db, _, _) = Ops.create_tuples storage db rel (List.map (retarget target) tuples) |> wrap_manip in
- Ok db
-
+ let* rel = get_rel db target in
+ let* result_rel = eval_query storage db source in
+ let* tuples = materialize_tuples storage result_rel in
+ let* db, _, _ =
+ Ops.create_tuples storage db rel (List.map (retarget target) tuples)
+ |> wrap_manip
+ in
+ Ok db
| Ast.DeleteWhere { target; predicate } ->
- let* rel = get_rel db target in
- let* pred_rel = eval_query storage db predicate in
- let common = List.filter_map
- (fun (n, _) -> if List.exists (fun (m, _) -> m = n) pred_rel.Relation.schema
- then Some n else None)
- rel.Relation.schema in
- let* joined = Alg.equijoin storage common rel pred_rel |> Result.map_error wrap_alg in
- let* to_delete = Alg.project storage (List.map fst rel.Relation.schema) joined
- |> Result.map_error wrap_alg in
- let* tuples = materialize_tuples storage to_delete in
- List.fold_left (fun acc t ->
- let* db = acc in
let* rel = get_rel db target in
- let tuple_hash = Hashing.hash_tuple (retarget target t) in
- let* (db, _) = Ops.retract_tuple storage db rel ~tuple_hash |> wrap_manip in
- Ok db)
- (Ok db) tuples
+ let* pred_rel = eval_query storage db predicate in
+ let common =
+ List.filter_map
+ (fun (n, _) ->
+ if List.exists (fun (m, _) -> m = n) pred_rel.Relation.schema then
+ Some n
+ else None)
+ rel.Relation.schema
+ in
+ let* joined =
+ Alg.equijoin storage common rel pred_rel |> Result.map_error wrap_alg
+ in
+ let* to_delete =
+ Alg.project storage (List.map fst rel.Relation.schema) joined
+ |> Result.map_error wrap_alg
+ in
+ let* tuples = materialize_tuples storage to_delete in
+ List.fold_left
+ (fun acc t ->
+ let* db = acc in
+ let* rel = get_rel db target in
+ let tuple_hash = Hashing.hash_tuple (retarget target t) in
+ let* db, _ =
+ Ops.retract_tuple storage db rel ~tuple_hash |> wrap_manip
+ in
+ Ok db)
+ (Ok db) tuples
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/dml/parser.ml b/lib/dml/parser.ml
index 28f8b6c..5678b47 100644
--- a/lib/dml/parser.ml
+++ b/lib/dml/parser.ml
@@ -2,13 +2,12 @@ type error = ParseError of string
let of_sexp sexp =
match Ast.statement_of_sexp sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
let of_string s =
match Sexplib.Sexp.of_string s |> Ast.statement_of_sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
-let to_string stmt =
- Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
+let to_string stmt = Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
diff --git a/lib/dml/sublanguage.ml b/lib/dml/sublanguage.ml
index 29f951e..29b1b77 100644
--- a/lib/dml/sublanguage.ml
+++ b/lib/dml/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.statement
- type error = Exec.error
+ type ast = Ast.statement
+ type error = Exec.error
let name = "dml"
@@ -20,4 +20,4 @@ module Make (Storage : Management.Physical.S) = struct
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/drl/ast.ml b/lib/drl/ast.ml
index 2beb010..6b191bb 100644
--- a/lib/drl/ast.ml
+++ b/lib/drl/ast.ml
@@ -1,32 +1,28 @@
open Sexplib.Std
(** Literal values for Const nodes *)
-type value =
- | Int of int
- | Float of float
- | Str of string
- | Bool of bool
+type value = Int of int | Float of float | Str of string | Bool of bool
[@@deriving sexp]
(** Convert an AST value to an AbstractValue.t (Obj.t) via Obj.repr *)
let value_to_abstract : value -> Conventions.AbstractValue.t = function
- | Int n -> Obj.repr n
+ | Int n -> Obj.repr n
| Float f -> Obj.repr f
- | Str s -> Obj.repr s
- | Bool b -> Obj.repr b
+ | Str s -> Obj.repr s
+ | Bool b -> Obj.repr b
-type direction = Asc | Desc
-[@@deriving sexp]
+type direction = Asc | Desc [@@deriving sexp]
type query =
- | Base of string (** base relation by name *)
- | Const of (string * value) list (** constant single-tuple relation *)
- | Select of query * query (** σ semijoin: (filter, source) *)
- | Join of string list * query * query (** ⋈ natural equijoin on named attrs *)
- | Cartesian of query * query (** × Cartesian product *)
- | Project of string list * query (** π restrict columns *)
- | Rename of (string * string) list * query (** ρ rename (old,new) pairs *)
- | Union of query * query (** ∪ — compatible schemas assumed *)
- | Diff of query * query (** − *)
- | Take of int * query (** τ *)
+ | Base of string (** base relation by name *)
+ | Const of (string * value) list (** constant single-tuple relation *)
+ | Select of query * query (** σ semijoin: (filter, source) *)
+ | Join of string list * query * query
+ (** ⋈ natural equijoin on named attrs *)
+ | Cartesian of query * query (** × Cartesian product *)
+ | Project of string list * query (** π restrict columns *)
+ | Rename of (string * string) list * query (** ρ rename (old,new) pairs *)
+ | Union of query * query (** ∪ — compatible schemas assumed *)
+ | Diff of query * query (** − *)
+ | Take of int * query (** τ *)
[@@deriving sexp]
diff --git a/lib/drl/executor.ml b/lib/drl/executor.ml
index 322fada..f1732eb 100644
--- a/lib/drl/executor.ml
+++ b/lib/drl/executor.ml
@@ -1,74 +1,74 @@
-module Make(Storage : Management.Physical.S) = struct
- module Alg = Algebra.Make(Storage)
- module Ops = Manipulation.Make(Storage)
+module Make (Storage : Management.Physical.S) = struct
+ module Alg = Algebra.Make (Storage)
+ module Ops = Manipulation.Make (Storage)
type error =
- | ParseError of string
+ | ParseError of string
| RelationNotFound of string
- | AlgebraError of Algebra.error
+ | AlgebraError of Algebra.error
let sexp_of_error e =
let open Sexplib.Sexp in
match e with
- | ParseError s -> List [Atom "parse-error"; Atom s]
- | RelationNotFound s -> List [Atom "relation-not-found"; Atom s]
- | AlgebraError (Algebra.StorageError s) -> List [Atom "storage-error"; Atom s]
- | AlgebraError (Algebra.GeneratorError s) -> List [Atom "generator-error"; Atom s]
+ | ParseError s -> List [ Atom "parse-error"; Atom s ]
+ | RelationNotFound s -> List [ Atom "relation-not-found"; Atom s ]
+ | AlgebraError (Algebra.StorageError s) ->
+ List [ Atom "storage-error"; Atom s ]
+ | AlgebraError (Algebra.GeneratorError s) ->
+ List [ Atom "generator-error"; Atom s ]
let wrap = Result.map_error (fun e -> AlgebraError e)
-
let ast_value_to_abstract = Ast.value_to_abstract
- let rec execute
- (storage : Storage.t)
- (db : Management.Database.t)
- (q : Ast.query)
- : (Relation.t, error) Result.t =
+ let rec execute (storage : Storage.t) (db : Management.Database.t)
+ (q : Ast.query) : (Relation.t, error) Result.t =
let ( >>= ) = Result.bind in
match q with
- | Ast.Base name ->
- (match Ops.get_relation db ~name with
- | None -> Error (RelationNotFound name)
- | Some rel -> Ok rel)
+ | Ast.Base name -> (
+ match Ops.get_relation db ~name with
+ | None -> Error (RelationNotFound name)
+ | Some rel -> Ok rel)
| Ast.Const pairs ->
- Ok (Alg.const_relation
- (List.map (fun (k, v) -> (k, ast_value_to_abstract v)) pairs))
+ Ok
+ (Alg.const_relation
+ (List.map (fun (k, v) -> (k, ast_value_to_abstract v)) pairs))
| Ast.Select (filter_q, source_q) ->
- execute storage db filter_q >>= fun filter ->
- execute storage db source_q >>= fun source ->
- (* semijoin = project(source attrs, equijoin on common attrs) *)
- let common = List.filter_map (fun (n, _) ->
- if List.exists (fun (m, _) -> m = n) filter.Relation.schema
- then Some n else None) source.Relation.schema in
- let source_attrs = List.map fst source.Relation.schema in
- wrap (Alg.equijoin storage common source filter) >>= fun joined ->
- wrap (Alg.project storage source_attrs joined)
+ execute storage db filter_q >>= fun filter ->
+ execute storage db source_q >>= fun source ->
+ (* semijoin = project(source attrs, equijoin on common attrs) *)
+ let common =
+ List.filter_map
+ (fun (n, _) ->
+ if List.exists (fun (m, _) -> m = n) filter.Relation.schema then
+ Some n
+ else None)
+ source.Relation.schema
+ in
+ let source_attrs = List.map fst source.Relation.schema in
+ wrap (Alg.equijoin storage common source filter) >>= fun joined ->
+ wrap (Alg.project storage source_attrs joined)
| Ast.Join (attrs, q1, q2) ->
- execute storage db q1 >>= fun r1 ->
- execute storage db q2 >>= fun r2 ->
- wrap (Alg.equijoin storage attrs r1 r2)
+ execute storage db q1 >>= fun r1 ->
+ execute storage db q2 >>= fun r2 ->
+ wrap (Alg.equijoin storage attrs r1 r2)
| Ast.Project (attrs, q) ->
- execute storage db q >>= fun rel ->
- wrap (Alg.project storage attrs rel)
+ execute storage db q >>= fun rel -> wrap (Alg.project storage attrs rel)
| Ast.Rename (renames, q) ->
- execute storage db q >>= fun rel ->
- wrap (Alg.rename storage renames rel)
+ execute storage db q >>= fun rel ->
+ wrap (Alg.rename storage renames rel)
| Ast.Cartesian (q1, q2) ->
- execute storage db q1 >>= fun r1 ->
- execute storage db q2 >>= fun r2 ->
- (* Cartesian product = join on no attrs *)
- wrap (Alg.equijoin storage [] r1 r2)
+ execute storage db q1 >>= fun r1 ->
+ execute storage db q2 >>= fun r2 ->
+ (* Cartesian product = join on no attrs *)
+ wrap (Alg.equijoin storage [] r1 r2)
| Ast.Union (q1, q2) ->
- execute storage db q1 >>= fun r1 ->
- execute storage db q2 >>= fun r2 ->
- wrap (Alg.union storage r1 r2)
+ execute storage db q1 >>= fun r1 ->
+ execute storage db q2 >>= fun r2 -> wrap (Alg.union storage r1 r2)
| Ast.Diff (q1, q2) ->
- execute storage db q1 >>= fun r1 ->
- execute storage db q2 >>= fun r2 ->
- wrap (Alg.diff storage r1 r2)
+ execute storage db q1 >>= fun r1 ->
+ execute storage db q2 >>= fun r2 -> wrap (Alg.diff storage r1 r2)
| Ast.Take (n, q) ->
- execute storage db q >>= fun rel ->
- wrap (Alg.take storage n rel)
+ execute storage db q >>= fun rel -> wrap (Alg.take storage n rel)
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/drl/gate.ml b/lib/drl/gate.ml
index 222e935..a314bce 100644
--- a/lib/drl/gate.ml
+++ b/lib/drl/gate.ml
@@ -1,8 +1,8 @@
(** Finiteness gate for DRL queries.
- Converts Sakura types to Coq-extracted types and calls the
- verified [predicted_finite] function. Rejects queries whose
- result cannot be statically proven finite. *)
+ Converts Sakura types to Coq-extracted types and calls the verified
+ [predicted_finite] function. Rejects queries whose result cannot be
+ statically proven finite. *)
(* -- string -> char list impedance --------------------------------- *)
@@ -12,71 +12,68 @@ let string_to_chars (s : string) : char list =
(* -- Sakura AST -> extracted AST ----------------------------------- *)
let rec query_of_ast : Ast.query -> Drl_verified.query = function
- | Ast.Base name ->
- Drl_verified.Base (string_to_chars name)
+ | Ast.Base name -> Drl_verified.Base (string_to_chars name)
| Ast.Const pairs ->
- Drl_verified.Const
- (List.map
- (fun (k, v) ->
- (string_to_chars k, value_of_ast_value v))
- pairs)
+ Drl_verified.Const
+ (List.map
+ (fun (k, v) -> (string_to_chars k, value_of_ast_value v))
+ pairs)
| Ast.Select (filter, source) ->
- Drl_verified.Select (query_of_ast filter, query_of_ast source)
+ Drl_verified.Select (query_of_ast filter, query_of_ast source)
| Ast.Join (attrs, q1, q2) ->
- Drl_verified.Join
- (List.map string_to_chars attrs, query_of_ast q1, query_of_ast q2)
+ Drl_verified.Join
+ (List.map string_to_chars attrs, query_of_ast q1, query_of_ast q2)
| Ast.Cartesian (q1, q2) ->
- Drl_verified.Cartesian (query_of_ast q1, query_of_ast q2)
+ Drl_verified.Cartesian (query_of_ast q1, query_of_ast q2)
| Ast.Project (attrs, q) ->
- Drl_verified.Project (List.map string_to_chars attrs, query_of_ast q)
+ Drl_verified.Project (List.map string_to_chars attrs, query_of_ast q)
| Ast.Rename (renames, q) ->
- Drl_verified.Rename
- (List.map
- (fun (o, n) -> (string_to_chars o, string_to_chars n))
- renames,
- query_of_ast q)
- | Ast.Union (q1, q2) ->
- Drl_verified.Union (query_of_ast q1, query_of_ast q2)
- | Ast.Diff (q1, q2) ->
- Drl_verified.Diff (query_of_ast q1, query_of_ast q2)
- | Ast.Take (n, q) ->
- Drl_verified.Take (n, query_of_ast q)
+ Drl_verified.Rename
+ ( List.map (fun (o, n) -> (string_to_chars o, string_to_chars n)) renames,
+ query_of_ast q )
+ | Ast.Union (q1, q2) -> Drl_verified.Union (query_of_ast q1, query_of_ast q2)
+ | Ast.Diff (q1, q2) -> Drl_verified.Diff (query_of_ast q1, query_of_ast q2)
+ | Ast.Take (n, q) -> Drl_verified.Take (n, query_of_ast q)
and value_of_ast_value : Ast.value -> Drl_verified.literal = function
- | Ast.Int n -> Drl_verified.LitInt n
+ | Ast.Int n -> Drl_verified.LitInt n
(* The Coq model has no float literal. This truncation is harmless:
the finiteness gate inspects only relation names and query structure,
never literal values. *)
| Ast.Float f -> Drl_verified.LitInt (int_of_float f)
- | Ast.Str s -> Drl_verified.LitStr (string_to_chars s)
- | Ast.Bool b -> Drl_verified.LitBool b
+ | Ast.Str s -> Drl_verified.LitStr (string_to_chars s)
+ | Ast.Bool b -> Drl_verified.LitBool b
(* -- database snapshot --------------------------------------------- *)
let is_finite (c : Conventions.Cardinality.t) : bool =
match c with
- | Conventions.Cardinality.Finite _ | Conventions.Cardinality.ConstrainedFinite -> true
- | Conventions.Cardinality.AlephZero | Conventions.Cardinality.Continuum -> false
+ | Conventions.Cardinality.Finite _ | Conventions.Cardinality.ConstrainedFinite
+ ->
+ true
+ | Conventions.Cardinality.AlephZero | Conventions.Cardinality.Continuum ->
+ false
let database_snapshot (db : Management.Database.t) : Drl_verified.database =
Management.Database.RelationMap.fold
(fun name (rel : Relation.t) acc ->
- let entry : Drl_verified.relation =
- { rel_name = string_to_chars name;
- rel_finite = is_finite rel.cardinality }
- in
- (string_to_chars name, entry) :: acc)
- db.relations
- []
+ let entry : Drl_verified.relation =
+ {
+ rel_name = string_to_chars name;
+ rel_finite = is_finite rel.cardinality;
+ }
+ in
+ (string_to_chars name, entry) :: acc)
+ db.relations []
(* -- public interface ---------------------------------------------- *)
-let admit (db : Management.Database.t) (ast : Ast.query) : (unit, string) result =
+let admit (db : Management.Database.t) (ast : Ast.query) : (unit, string) result
+ =
let vdb = database_snapshot db in
let vq = query_of_ast ast in
match Drl_verified.predicted_finite vdb vq with
| Some true -> Ok ()
| Some false ->
- Error "query produces potentially infinite result; use Take to bound it"
- | None ->
- Error "referenced relation not found in finiteness check"
+ Error "query produces potentially infinite result; use Take to bound it"
+ | None -> Error "referenced relation not found in finiteness check"
diff --git a/lib/drl/parser.ml b/lib/drl/parser.ml
index 36ce15a..76d4b4e 100644
--- a/lib/drl/parser.ml
+++ b/lib/drl/parser.ml
@@ -2,13 +2,12 @@ type error = ParseError of string
let of_sexp sexp =
match Ast.query_of_sexp sexp with
- | q -> Ok q
+ | q -> Ok q
| exception exn -> Error (ParseError (Printexc.to_string exn))
let of_string s =
match Sexplib.Sexp.of_string s |> Ast.query_of_sexp with
- | q -> Ok q
+ | q -> Ok q
| exception exn -> Error (ParseError (Printexc.to_string exn))
-let to_string q =
- Ast.sexp_of_query q |> Sexplib.Sexp.to_string_hum
+let to_string q = Ast.sexp_of_query q |> Sexplib.Sexp.to_string_hum
diff --git a/lib/drl/sublanguage.ml b/lib/drl/sublanguage.ml
index f57af95..ce69b56 100644
--- a/lib/drl/sublanguage.ml
+++ b/lib/drl/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.query
- type error = Exec.error
+ type ast = Ast.query
+ type error = Exec.error
let name = "drl"
@@ -15,12 +15,12 @@ module Make (Storage : Management.Physical.S) = struct
let execute storage db ast =
match Gate.admit db ast with
| Error msg -> Error (Exec.ParseError msg)
- | Ok () ->
- match Exec.execute storage db ast with
- | Ok rel -> Ok (Sublanguage.Query rel)
- | Error e -> Error e
+ | Ok () -> (
+ match Exec.execute storage db ast with
+ | Ok rel -> Ok (Sublanguage.Query rel)
+ | Error e -> Error e)
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/drl_verified/drl_verified.ml b/lib/drl_verified/drl_verified.ml
index b5aeaa7..868af2a 100644
--- a/lib/drl_verified/drl_verified.ml
+++ b/lib/drl_verified/drl_verified.ml
@@ -1,76 +1,67 @@
-
(** val eqb : char list -> char list -> bool **)
let rec eqb s1 s2 =
match s1 with
- | [] -> (match s2 with
- | [] -> true
- | _::_ -> false)
- | c1::s1' ->
- (match s2 with
- | [] -> false
- | c2::s2' -> if (=) c1 c2 then eqb s1' s2' else false)
+ | [] -> ( match s2 with [] -> true | _ :: _ -> false)
+ | c1 :: s1' -> (
+ match s2 with
+ | [] -> false
+ | c2 :: s2' -> if c1 = c2 then eqb s1' s2' else false)
type attr_name = char list
-
type relation = { rel_name : char list; rel_finite : bool }
-
type database = (char list * relation) list
(** val db_lookup : char list -> database -> relation option **)
let rec db_lookup name = function
-| [] -> None
-| p :: rest ->
- let (n, r) = p in if eqb name n then Some r else db_lookup name rest
+ | [] -> None
+ | p :: rest ->
+ let n, r = p in
+ if eqb name n then Some r else db_lookup name rest
-type literal =
-| LitInt of int
-| LitStr of char list
-| LitBool of bool
+type literal = LitInt of int | LitStr of char list | LitBool of bool
type query =
-| Base of char list
-| Const of (attr_name * literal) list
-| Select of query * query
-| Join of attr_name list * query * query
-| Cartesian of query * query
-| Project of attr_name list * query
-| Rename of (attr_name * attr_name) list * query
-| Union of query * query
-| Diff of query * query
-| Take of int * query
+ | Base of char list
+ | Const of (attr_name * literal) list
+ | Select of query * query
+ | Join of attr_name list * query * query
+ | Cartesian of query * query
+ | Project of attr_name list * query
+ | Rename of (attr_name * attr_name) list * query
+ | Union of query * query
+ | Diff of query * query
+ | Take of int * query
(** val predicted_finite : database -> query -> bool option **)
let rec predicted_finite db = function
-| Base name ->
- (match db_lookup name db with
- | Some r -> Some r.rel_finite
- | None -> None)
-| Select (_, source) -> predicted_finite db source
-| Join (_, q1, q2) ->
- (match predicted_finite db q1 with
- | Some f1 ->
- (match predicted_finite db q2 with
- | Some f2 -> Some ((&&) f1 f2)
+ | Base name -> (
+ match db_lookup name db with Some r -> Some r.rel_finite | None -> None)
+ | Select (_, source) -> predicted_finite db source
+ | Join (_, q1, q2) -> (
+ match predicted_finite db q1 with
+ | Some f1 -> (
+ match predicted_finite db q2 with
+ | Some f2 -> Some (f1 && f2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Cartesian (q1, q2) ->
- (match predicted_finite db q1 with
- | Some f1 ->
- (match predicted_finite db q2 with
- | Some f2 -> Some ((&&) f1 f2)
+ | Cartesian (q1, q2) -> (
+ match predicted_finite db q1 with
+ | Some f1 -> (
+ match predicted_finite db q2 with
+ | Some f2 -> Some (f1 && f2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Project (_, q0) -> predicted_finite db q0
-| Rename (_, q0) -> predicted_finite db q0
-| Union (q1, q2) ->
- (match predicted_finite db q1 with
- | Some f1 ->
- (match predicted_finite db q2 with
- | Some f2 -> Some ((&&) f1 f2)
+ | Project (_, q0) -> predicted_finite db q0
+ | Rename (_, q0) -> predicted_finite db q0
+ | Union (q1, q2) -> (
+ match predicted_finite db q1 with
+ | Some f1 -> (
+ match predicted_finite db q2 with
+ | Some f2 -> Some (f1 && f2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Diff (q1, _) -> predicted_finite db q1
-| _ -> Some true
+ | Diff (q1, _) -> predicted_finite db q1
+ | _ -> Some true
diff --git a/lib/drl_verified/drl_verified.mli b/lib/drl_verified/drl_verified.mli
index 31acc7a..dac930e 100644
--- a/lib/drl_verified/drl_verified.mli
+++ b/lib/drl_verified/drl_verified.mli
@@ -1,29 +1,23 @@
-
val eqb : char list -> char list -> bool
type attr_name = char list
-
type relation = { rel_name : char list; rel_finite : bool }
-
type database = (char list * relation) list
val db_lookup : char list -> database -> relation option
-type literal =
-| LitInt of int
-| LitStr of char list
-| LitBool of bool
+type literal = LitInt of int | LitStr of char list | LitBool of bool
type query =
-| Base of char list
-| Const of (attr_name * literal) list
-| Select of query * query
-| Join of attr_name list * query * query
-| Cartesian of query * query
-| Project of attr_name list * query
-| Rename of (attr_name * attr_name) list * query
-| Union of query * query
-| Diff of query * query
-| Take of int * query
+ | Base of char list
+ | Const of (attr_name * literal) list
+ | Select of query * query
+ | Join of attr_name list * query * query
+ | Cartesian of query * query
+ | Project of attr_name list * query
+ | Rename of (attr_name * attr_name) list * query
+ | Union of query * query
+ | Diff of query * query
+ | Take of int * query
val predicted_finite : database -> query -> bool option
diff --git a/lib/dune b/lib/dune
index 9b7462c..a6b503d 100644
--- a/lib/dune
+++ b/lib/dune
@@ -1,4 +1,5 @@
(include_subdirs qualified)
+
(library
(name relational_engine)
(libraries
diff --git a/lib/envelope.ml b/lib/envelope.ml
index d8404d9..bed8bc0 100644
--- a/lib/envelope.ml
+++ b/lib/envelope.ml
@@ -1,18 +1,17 @@
(** Envelope parser for tagged S-expressions.
- Parses commands of the form (tag payload...) where the tag selects
- which sublanguage handles the payload. The tag set is open — future
- sublanguages (tcl, ppl, acl) just add new tags. *)
+ Parses commands of the form (tag payload...) where the tag selects which
+ sublanguage handles the payload. The tag set is open — future sublanguages
+ (tcl, ppl, acl) just add new tags. *)
type tagged = { tag : string; payload : Sexplib.Sexp.t }
let parse (input : string) : (tagged, string) Result.t =
match Sexplib.Sexp.of_string input with
| Sexplib.Sexp.List (Sexplib.Sexp.Atom tag :: rest) ->
- let payload = match rest with
- | [single] -> single
- | many -> Sexplib.Sexp.List many
- in
- Ok { tag; payload }
+ let payload =
+ match rest with [ single ] -> single | many -> Sexplib.Sexp.List many
+ in
+ Ok { tag; payload }
| _ -> Error "expected (tag payload...)"
| exception exn -> Error (Printexc.to_string exn)
diff --git a/lib/generator.ml b/lib/generator.ml
index 44f1579..5939ec9 100644
--- a/lib/generator.ml
+++ b/lib/generator.ml
@@ -2,5 +2,5 @@ type result =
| Done
| Value of Tuple.t * (int option -> result)
| Error of string
-
- and t = int option -> result \ No newline at end of file
+
+and t = int option -> result
diff --git a/lib/hashing.ml b/lib/hashing.ml
index 4709ded..9475e46 100644
--- a/lib/hashing.ml
+++ b/lib/hashing.ml
@@ -1,8 +1,8 @@
(** Hash computation for tuples and relations.
This module provides content-addressable hashing for the relational engine.
- All hashes are deterministic - the same content always produces the same hash.
- This enables:
+ All hashes are deterministic - the same content always produces the same
+ hash. This enables:
- Deduplication of tuples
- Merkle tree construction for relations and databases
- Time-travel queries (load any historical state by hash) *)
@@ -16,38 +16,41 @@ let bytes_to_hex bytes =
let hi = c lsr 4 in
let lo = c land 0xf in
Bytes.set hex (i * 2) (Char.chr (if hi < 10 then hi + 48 else hi + 87));
- Bytes.set hex (i * 2 + 1) (Char.chr (if lo < 10 then lo + 48 else lo + 87));
+ Bytes.set hex
+ ((i * 2) + 1)
+ (Char.chr (if lo < 10 then lo + 48 else lo + 87))
done;
Bytes.to_string hex
-(** Compute hash for a tuple.
- The hash is computed from:
+(** Compute hash for a tuple. The hash is computed from:
- The relation name
- - All attribute names and their marshalled values (sorted by name for determinism) *)
+ - All attribute names and their marshalled values (sorted by name for
+ determinism) *)
let hash_tuple (tuple : Tuple.materialized) : Conventions.Hash.t =
let sorted_attrs =
Tuple.AttributeMap.bindings tuple.attributes
|> List.sort (fun (a, _) (b, _) -> String.compare a b)
in
let content =
- List.fold_left (fun acc (name, attr) ->
- (* Marshal the value and hex-encode to avoid null bytes *)
- let value_bytes = Marshal.to_bytes attr.Attribute.value [Marshal.Closures] in
- let value_hex = bytes_to_hex value_bytes in
- acc ^ name ^ ":" ^ value_hex ^ ";"
- ) (tuple.relation ^ "|") sorted_attrs
+ List.fold_left
+ (fun acc (name, attr) ->
+ (* Marshal the value and hex-encode to avoid null bytes *)
+ let value_bytes =
+ Marshal.to_bytes attr.Attribute.value [ Marshal.Closures ]
+ in
+ let value_hex = bytes_to_hex value_bytes in
+ acc ^ name ^ ":" ^ value_hex ^ ";")
+ (tuple.relation ^ "|") sorted_attrs
in
Conventions.Hash.hash_text content
-(** Compute hash for a relation.
- The hash is computed from:
+(** Compute hash for a relation. The hash is computed from:
- The relation name
- The schema (attribute names and types)
- The merkle tree root hash (representing all tuples) *)
let hash_relation ~name ~(schema : Schema.t) ~tree : Conventions.Hash.t =
let schema_str = Schema.to_string schema in
- let tree_hash = match Merkle.root_hash tree with
- | Some h -> h
- | None -> "empty"
+ let tree_hash =
+ match Merkle.root_hash tree with Some h -> h | None -> "empty"
in
Conventions.Hash.hash_text (name ^ "|" ^ schema_str ^ "|" ^ tree_hash)
diff --git a/lib/icl/ast.ml b/lib/icl/ast.ml
index 95e0271..30b0fa7 100644
--- a/lib/icl/ast.ml
+++ b/lib/icl/ast.ml
@@ -6,18 +6,18 @@ type binding_expr =
[@@deriving sexp]
type constraint_body =
- | MemberOf of { target: string; binding: (string * binding_expr) list }
- | Not of { body: constraint_body; universe: string }
+ | MemberOf of { target : string; binding : (string * binding_expr) list }
+ | Not of { body : constraint_body; universe : string }
| And of constraint_body list
| Or of constraint_body list
- | Exists of { variable: string; quantifier: string; body: constraint_body }
- | Forall of { variable: string; quantifier: string; body: constraint_body }
+ | Exists of { variable : string; quantifier : string; body : constraint_body }
+ | Forall of { variable : string; quantifier : string; body : constraint_body }
[@@deriving sexp]
type statement =
| RegisterConstraint of {
- constraint_name: string;
- relation_name: string;
- body: constraint_body;
+ constraint_name : string;
+ relation_name : string;
+ body : constraint_body;
}
[@@deriving sexp]
diff --git a/lib/icl/parser.ml b/lib/icl/parser.ml
index 28f8b6c..5678b47 100644
--- a/lib/icl/parser.ml
+++ b/lib/icl/parser.ml
@@ -2,13 +2,12 @@ type error = ParseError of string
let of_sexp sexp =
match Ast.statement_of_sexp sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
let of_string s =
match Sexplib.Sexp.of_string s |> Ast.statement_of_sexp with
- | stmt -> Ok stmt
+ | stmt -> Ok stmt
| exception exn -> Error (ParseError (Printexc.to_string exn))
-let to_string stmt =
- Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
+let to_string stmt = Ast.sexp_of_statement stmt |> Sexplib.Sexp.to_string_hum
diff --git a/lib/icl/sublanguage.ml b/lib/icl/sublanguage.ml
index 4e74082..56f3e4b 100644
--- a/lib/icl/sublanguage.ml
+++ b/lib/icl/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.statement
- type error = Exec.error
+ type ast = Ast.statement
+ type error = Exec.error
let name = "icl"
@@ -20,4 +20,4 @@ module Make (Storage : Management.Physical.S) = struct
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/interop.ml b/lib/interop.ml
index ea25e0c..8cbc05c 100644
--- a/lib/interop.ml
+++ b/lib/interop.ml
@@ -2,7 +2,8 @@ open Ctypes
module Sha256 = struct
type t = string
- let compute_hash content: t =
+
+ let compute_hash content : t =
let size_with_null_terminator = C.Types.hash_size + 1 in
let buf = Ctypes.allocate_n char ~count:size_with_null_terminator in
let () = C.Functions.compute_hash buf (Bytes.to_string content) in
diff --git a/lib/management/branch.ml b/lib/management/branch.ml
index 70dfd85..ea15311 100644
--- a/lib/management/branch.ml
+++ b/lib/management/branch.ml
@@ -4,13 +4,8 @@
- "branch_names" → serialized string list (index for enumeration)
- "head" → branch name as raw bytes *)
-type t = {
- name : string;
- tip : Conventions.Hash.t;
-}
-
+type t = { name : string; tip : Conventions.Hash.t }
type head = string
-
type error = string
let branch_key name = "branch:" ^ name
@@ -18,7 +13,6 @@ let head_key = "head"
let names_key = "branch_names"
module Make (Storage : Physical.S with type error = string) = struct
-
let load_names storage =
match Storage.load_raw storage names_key with
| Ok (Some bytes) -> (Marshal.from_bytes bytes 0 : string list)
@@ -31,13 +25,12 @@ module Make (Storage : Physical.S with type error = string) = struct
let create storage ~name ~tip =
let record = { name; tip } in
let bytes = Marshal.to_bytes record [] in
- (match Storage.store_raw storage (branch_key name) bytes with
- | Error e -> Error e
- | Ok () ->
- let names = load_names storage in
- if not (List.mem name names) then
- store_names storage (name :: names);
- Ok ())
+ match Storage.store_raw storage (branch_key name) bytes with
+ | Error e -> Error e
+ | Ok () ->
+ let names = load_names storage in
+ if not (List.mem name names) then store_names storage (name :: names);
+ Ok ()
let checkout storage branch_name =
let bytes = Bytes.of_string branch_name in
@@ -52,27 +45,30 @@ module Make (Storage : Physical.S with type error = string) = struct
let get_tip storage name =
match Storage.load_raw storage (branch_key name) with
| Ok (Some bytes) ->
- let record : t = Marshal.from_bytes bytes 0 in
- Ok (Some record.tip)
+ let record : t = Marshal.from_bytes bytes 0 in
+ Ok (Some record.tip)
| Ok None -> Ok None
| Error e -> Error e
let update_tip storage ~name ~tip =
match Storage.load_raw storage (branch_key name) with
| Ok (Some _) ->
- let record = { name; tip } in
- let bytes = Marshal.to_bytes record [] in
- Storage.store_raw storage (branch_key name) bytes
+ let record = { name; tip } in
+ let bytes = Marshal.to_bytes record [] in
+ Storage.store_raw storage (branch_key name) bytes
| Ok None -> Error ("Branch not found: " ^ name)
| Error e -> Error e
let list storage =
let names = load_names storage in
- let branches = List.filter_map (fun name ->
- match get_tip storage name with
- | Ok (Some tip) -> Some (name, tip)
- | _ -> None
- ) names in
+ let branches =
+ List.filter_map
+ (fun name ->
+ match get_tip storage name with
+ | Ok (Some tip) -> Some (name, tip)
+ | _ -> None)
+ names
+ in
Ok branches
let branch_relation storage : Relation.t =
@@ -83,23 +79,22 @@ module Make (Storage : Physical.S with type error = string) = struct
let rec generator position =
match position with
| None -> Generator.Error "random access not supported for sakura:branch"
- | Some pos ->
- (match list storage with
- | Error _ -> Generator.Done
- | Ok branches ->
- if pos >= List.length branches then Generator.Done
- else
- let (name, tip) = List.nth branches pos in
- let short = String.sub tip 0 (min 8 (String.length tip)) in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("name", mk name)
- ; ("hash", mk short)
- ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:Prelude.Catalog.branch_rel_name ~attributes
- , generator ))
+ | Some pos -> (
+ match list storage with
+ | Error _ -> Generator.Done
+ | Ok branches ->
+ if pos >= List.length branches then Generator.Done
+ else
+ let name, tip = List.nth branches pos in
+ let short = String.sub tip 0 (min 8 (String.length tip)) in
+ let attributes =
+ Tuple.AttributeMap.of_list
+ [ ("name", mk name); ("hash", mk short) ]
+ in
+ Generator.Value
+ ( Tuple.make_materialized
+ ~relation:Prelude.Catalog.branch_rel_name ~attributes,
+ generator ))
in
Relation.make ~hash:None ~name:Prelude.Catalog.branch_rel_name ~schema
~tree:None ~constraints:None
@@ -115,16 +110,17 @@ module Make (Storage : Physical.S with type error = string) = struct
let generator position =
match position with
| None -> Generator.Error "random access not supported for sakura:head"
- | Some 0 ->
- (match get_head storage with
- | Ok None | Error _ -> Generator.Done
- | Ok (Some branch_name) ->
- let attributes =
- Tuple.AttributeMap.of_list [ ("branch", mk branch_name) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:Prelude.Catalog.head_rel_name ~attributes
- , fun _ -> Generator.Done ))
+ | Some 0 -> (
+ match get_head storage with
+ | Ok None | Error _ -> Generator.Done
+ | Ok (Some branch_name) ->
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("branch", mk branch_name) ]
+ in
+ Generator.Value
+ ( Tuple.make_materialized
+ ~relation:Prelude.Catalog.head_rel_name ~attributes,
+ fun _ -> Generator.Done ))
| Some _ -> Generator.Done
in
Relation.make ~hash:None ~name:Prelude.Catalog.head_rel_name ~schema
@@ -134,5 +130,4 @@ module Make (Storage : Physical.S with type error = string) = struct
~membership_criteria:(fun _ -> true)
~provenance:(Relation.Provenance.Base Prelude.Catalog.head_rel_name)
~lineage:(Relation.Lineage.Base Prelude.Catalog.head_rel_name)
-
end
diff --git a/lib/management/merge.ml b/lib/management/merge.ml
index e50d0e6..d30545a 100644
--- a/lib/management/merge.ml
+++ b/lib/management/merge.ml
@@ -1,40 +1,37 @@
-(** 3-way merge of two branch tips using their Lowest Common Ancestor.
- Walks the history chain to find it, diffs both sides against it,
- applies merge rules, validates constraints, and persists the result. *)
+(** 3-way merge of two branch tips using their Lowest Common Ancestor. Walks the
+ history chain to find it, diffs both sides against it, applies merge rules,
+ validates constraints, and persists the result. *)
type conflict =
- | TupleConflict of { relation: string; hash: Conventions.Hash.t }
- | SchemaConflict of string
+ | TupleConflict of { relation : string; hash : Conventions.Hash.t }
+ | SchemaConflict of string
| ConstraintConflict of string
type strategy = PreferLeft | PreferRight | RevertToAncestor
-
-type merge_result =
- | Clean of Database.t
- | Failed of conflict list
+type merge_result = Clean of Database.t | Failed of conflict list
(** Module type for the subset of Manipulation.Make we need. *)
module type MANIPULATION = sig
type storage
type error
- val of_string_error : string -> error
- val load_database : storage -> Conventions.Hash.t -> (Database.t option, error) result
+
+ val of_string_error : string -> error
+
+ val load_database :
+ storage -> Conventions.Hash.t -> (Database.t option, error) result
+
val store_database : storage -> Database.t -> (unit, error) result
end
-module StringSet = Set.Make(String)
+module StringSet = Set.Make (String)
(** Walk history to find LCA. First hash in right's ancestry that also appears
in left's ancestry set. *)
let find_lca left_db right_db =
let left_ancestors =
- StringSet.of_list (left_db.Database.hash
- :: left_db.Database.history)
- in
- let right_chain =
- right_db.Database.hash
- :: right_db.Database.history
+ StringSet.of_list (left_db.Database.hash :: left_db.Database.history)
in
+ let right_chain = right_db.Database.hash :: right_db.Database.history in
List.find_opt (fun h -> StringSet.mem h left_ancestors) right_chain
(** Apply a single relation diff to a database, using strategy on conflicts. *)
@@ -42,104 +39,140 @@ let apply_diff db ~strategy ~left_db ~right_db ~(left_diff : Diff.relation_diff)
~(right_diff : Diff.relation_diff option) : Database.t * conflict list =
match left_diff with
| Diff.RelationAdded rel ->
- (* Only left added it; right_diff must be None or same *)
- (Database.add_relation db ~relation:rel, [])
-
- | Diff.RelationRemoved name ->
- (Database.remove_relation db ~name, [])
-
- | Diff.RelationModified { name; added_tuples; removed_tuples; schema_changed } ->
- let conflicts = ref [] in
- (* Determine the merged relation's Merkle tree *)
- let base_rel_opt = Database.get_relation db name in
- (match base_rel_opt with
- | None -> (db, [])
- | Some base_rel ->
- let base_tree = Option.value base_rel.Relation.tree ~default:Merkle.empty in
- (* Handle schema conflict *)
- let (resolved_rel, schema_conflict) =
- if schema_changed then
- match right_diff with
- | Some (Diff.RelationModified { schema_changed = true; _ }) ->
- (* Both changed schema — apply strategy *)
- let winner = match strategy with
- | PreferLeft -> Database.get_relation left_db name
- | PreferRight -> Database.get_relation right_db name
- | RevertToAncestor -> Some base_rel
- in
- (Option.value winner ~default:base_rel, [SchemaConflict name])
- | _ ->
- (* Only left changed schema — take left's relation *)
- let left_rel = Option.value
- (Database.get_relation left_db name) ~default:base_rel in
- (left_rel, [])
- else (base_rel, [])
- in
- conflicts := schema_conflict @ !conflicts;
- (* Apply tuple adds/removes from left *)
- let merged_tree =
- List.fold_left (fun t h -> Merkle.insert h t) base_tree added_tuples
- in
- let merged_tree =
- List.fold_left (fun t h -> Merkle.delete h t) merged_tree removed_tuples
- in
- (* Apply tuple adds/removes from right if disjoint *)
- let merged_tree = match right_diff with
- | Some (Diff.RelationModified
+ (* Only left added it; right_diff must be None or same *)
+ (Database.add_relation db ~relation:rel, [])
+ | Diff.RelationRemoved name -> (Database.remove_relation db ~name, [])
+ | Diff.RelationModified { name; added_tuples; removed_tuples; schema_changed }
+ -> (
+ let conflicts = ref [] in
+ (* Determine the merged relation's Merkle tree *)
+ let base_rel_opt = Database.get_relation db name in
+ match base_rel_opt with
+ | None -> (db, [])
+ | Some base_rel ->
+ let base_tree =
+ Option.value base_rel.Relation.tree ~default:Merkle.empty
+ in
+ (* Handle schema conflict *)
+ let resolved_rel, schema_conflict =
+ if schema_changed then
+ match right_diff with
+ | Some (Diff.RelationModified { schema_changed = true; _ }) ->
+ (* Both changed schema — apply strategy *)
+ let winner =
+ match strategy with
+ | PreferLeft -> Database.get_relation left_db name
+ | PreferRight -> Database.get_relation right_db name
+ | RevertToAncestor -> Some base_rel
+ in
+ ( Option.value winner ~default:base_rel,
+ [ SchemaConflict name ] )
+ | _ ->
+ (* Only left changed schema — take left's relation *)
+ let left_rel =
+ Option.value
+ (Database.get_relation left_db name)
+ ~default:base_rel
+ in
+ (left_rel, [])
+ else (base_rel, [])
+ in
+ conflicts := schema_conflict @ !conflicts;
+ (* Apply tuple adds/removes from left *)
+ let merged_tree =
+ List.fold_left (fun t h -> Merkle.insert h t) base_tree added_tuples
+ in
+ let merged_tree =
+ List.fold_left
+ (fun t h -> Merkle.delete h t)
+ merged_tree removed_tuples
+ in
+ (* Apply tuple adds/removes from right if disjoint *)
+ let merged_tree =
+ match right_diff with
+ | Some
+ (Diff.RelationModified
{ added_tuples = r_add; removed_tuples = r_rem; _ }) ->
- (* Detect tuple conflicts: same hash both added and removed *)
- let left_add = StringSet.of_list added_tuples in
- let left_rem = StringSet.of_list removed_tuples in
- let right_add = StringSet.of_list r_add in
- let right_rem = StringSet.of_list r_rem in
- let conflict_hashes =
- StringSet.inter left_add right_rem
- |> StringSet.union (StringSet.inter left_rem right_add)
- in
- if not (StringSet.is_empty conflict_hashes) then begin
- StringSet.iter (fun h ->
- conflicts := TupleConflict { relation = name; hash = h } :: !conflicts
- ) conflict_hashes;
- (* Resolve conflicting tuples per strategy *)
- match strategy with
- | PreferLeft ->
- (* keep left's merged_tree as-is, ignore conflicting right ops *)
- let safe_add = StringSet.diff right_add conflict_hashes in
- let safe_rem = StringSet.diff right_rem conflict_hashes in
- let t = StringSet.fold (fun h acc -> Merkle.insert h acc) safe_add merged_tree in
- StringSet.fold (fun h acc -> Merkle.delete h acc) safe_rem t
- | PreferRight ->
- (* revert left's conflicting ops, apply right's *)
- let t = StringSet.fold (fun h acc -> Merkle.delete h acc) conflict_hashes merged_tree in
- let t = StringSet.fold (fun h acc -> Merkle.insert h acc) right_add t in
- StringSet.fold (fun h acc -> Merkle.delete h acc) right_rem t
- | RevertToAncestor ->
- (* drop conflicting hashes from merged tree *)
- StringSet.fold (fun h acc -> Merkle.delete h acc) conflict_hashes merged_tree
- end else begin
- (* Disjoint — union both sides *)
- let t = List.fold_left (fun acc h -> Merkle.insert h acc) merged_tree r_add in
- List.fold_left (fun acc h -> Merkle.delete h acc) t r_rem
- end
- | _ -> merged_tree
- in
- let new_hash = Hashing.hash_relation
- ~name:resolved_rel.Relation.name
- ~schema:resolved_rel.Relation.schema
- ~tree:merged_tree
- in
- let updated_rel = { resolved_rel with
- Relation.tree = Some merged_tree;
- hash = Some new_hash;
- } in
- let new_db = Database.update_relation db ~relation:updated_rel in
- (new_db, !conflicts))
+ (* Detect tuple conflicts: same hash both added and removed *)
+ let left_add = StringSet.of_list added_tuples in
+ let left_rem = StringSet.of_list removed_tuples in
+ let right_add = StringSet.of_list r_add in
+ let right_rem = StringSet.of_list r_rem in
+ let conflict_hashes =
+ StringSet.inter left_add right_rem
+ |> StringSet.union (StringSet.inter left_rem right_add)
+ in
+ if not (StringSet.is_empty conflict_hashes) then begin
+ StringSet.iter
+ (fun h ->
+ conflicts :=
+ TupleConflict { relation = name; hash = h }
+ :: !conflicts)
+ conflict_hashes;
+ (* Resolve conflicting tuples per strategy *)
+ match strategy with
+ | PreferLeft ->
+ (* keep left's merged_tree as-is, ignore conflicting right ops *)
+ let safe_add = StringSet.diff right_add conflict_hashes in
+ let safe_rem = StringSet.diff right_rem conflict_hashes in
+ let t =
+ StringSet.fold
+ (fun h acc -> Merkle.insert h acc)
+ safe_add merged_tree
+ in
+ StringSet.fold
+ (fun h acc -> Merkle.delete h acc)
+ safe_rem t
+ | PreferRight ->
+ (* revert left's conflicting ops, apply right's *)
+ let t =
+ StringSet.fold
+ (fun h acc -> Merkle.delete h acc)
+ conflict_hashes merged_tree
+ in
+ let t =
+ StringSet.fold
+ (fun h acc -> Merkle.insert h acc)
+ right_add t
+ in
+ StringSet.fold
+ (fun h acc -> Merkle.delete h acc)
+ right_rem t
+ | RevertToAncestor ->
+ (* drop conflicting hashes from merged tree *)
+ StringSet.fold
+ (fun h acc -> Merkle.delete h acc)
+ conflict_hashes merged_tree
+ end
+ else begin
+ (* Disjoint — union both sides *)
+ let t =
+ List.fold_left
+ (fun acc h -> Merkle.insert h acc)
+ merged_tree r_add
+ in
+ List.fold_left (fun acc h -> Merkle.delete h acc) t r_rem
+ end
+ | _ -> merged_tree
+ in
+ let new_hash =
+ Hashing.hash_relation ~name:resolved_rel.Relation.name
+ ~schema:resolved_rel.Relation.schema ~tree:merged_tree
+ in
+ let updated_rel =
+ {
+ resolved_rel with
+ Relation.tree = Some merged_tree;
+ hash = Some new_hash;
+ }
+ in
+ let new_db = Database.update_relation db ~relation:updated_rel in
+ (new_db, !conflicts))
module Make
(Storage : Physical.S)
- (Manip : MANIPULATION with type storage = Storage.t) =
+ (Manip : MANIPULATION with type storage = Storage.t) =
struct
-
let err s = Error (Manip.of_string_error s)
(* Load both tips, find the lowest common ancestor, compute the diffs,
@@ -151,85 +184,103 @@ struct
match Manip.load_database storage left_tip with
| Error e -> Error e
| Ok None -> err ("Left tip not found: " ^ left_tip)
- | Ok (Some left_db) ->
- match Manip.load_database storage right_tip with
- | Error e -> Error e
- | Ok None -> err ("Right tip not found: " ^ right_tip)
- | Ok (Some right_db) ->
- match find_lca left_db right_db with
- | None -> err "No common ancestor found between branches"
- | Some ancestor_hash ->
- match Manip.load_database storage ancestor_hash with
- | Error e -> Error e
- | Ok None -> err ("Ancestor not found: " ^ ancestor_hash)
- | Ok (Some ancestor_db) ->
- let left_diffs = Diff.diff ~ancestor:ancestor_db ~target:left_db in
- let right_diffs = Diff.diff ~ancestor:ancestor_db ~target:right_db in
- let right_diff_map =
- List.fold_left (fun m d ->
- let key = match d with
- | Diff.RelationAdded r -> r.Relation.name
- | Diff.RelationRemoved n -> n
- | Diff.RelationModified { name; _ } -> name
- in
- Database.RelationMap.add key d m
- ) Database.RelationMap.empty right_diffs
- in
- let (merged_db, all_conflicts) =
- let (db_after_left, left_conflicts) =
- List.fold_left (fun (db, conflicts) left_d ->
- let rel_name = match left_d with
- | Diff.RelationAdded r -> r.Relation.name
- | Diff.RelationRemoved n -> n
- | Diff.RelationModified { name; _ } -> name
- in
- let right_d = Database.RelationMap.find_opt rel_name right_diff_map in
- let (new_db, cs) =
- apply_diff db ~strategy ~left_db ~right_db
- ~left_diff:left_d ~right_diff:right_d
- in
- (new_db, cs @ conflicts)
- ) (ancestor_db, []) left_diffs
- in
- let left_names =
- List.fold_left (fun s d ->
- let n = match d with
- | Diff.RelationAdded r -> r.Relation.name
- | Diff.RelationRemoved n -> n
- | Diff.RelationModified { name; _ } -> name
- in
- StringSet.add n s
- ) StringSet.empty left_diffs
- in
- let right_only = List.filter (fun d ->
- let n = match d with
- | Diff.RelationAdded r -> r.Relation.name
- | Diff.RelationRemoved n -> n
- | Diff.RelationModified { name; _ } -> name
- in
- not (StringSet.mem n left_names)
- ) right_diffs in
- let (db_final, right_conflicts) =
- List.fold_left (fun (db, conflicts) right_d ->
- let (new_db, cs) =
- apply_diff db ~strategy ~left_db ~right_db
- ~left_diff:right_d ~right_diff:None
- in
- (new_db, cs @ conflicts)
- ) (db_after_left, left_conflicts) right_only
- in
- (db_final, right_conflicts)
- in
- let unresolvable = List.filter (function
- | ConstraintConflict _ -> true
- | _ -> false
- ) all_conflicts in
- if unresolvable <> [] then
- Ok (Failed unresolvable)
- else begin
- match Manip.store_database storage merged_db with
- | Error e -> Error e
- | Ok () -> Ok (Clean merged_db)
- end
-
+ | Ok (Some left_db) -> (
+ match Manip.load_database storage right_tip with
+ | Error e -> Error e
+ | Ok None -> err ("Right tip not found: " ^ right_tip)
+ | Ok (Some right_db) -> (
+ match find_lca left_db right_db with
+ | None -> err "No common ancestor found between branches"
+ | Some ancestor_hash -> (
+ match Manip.load_database storage ancestor_hash with
+ | Error e -> Error e
+ | Ok None -> err ("Ancestor not found: " ^ ancestor_hash)
+ | Ok (Some ancestor_db) ->
+ let left_diffs =
+ Diff.diff ~ancestor:ancestor_db ~target:left_db
+ in
+ let right_diffs =
+ Diff.diff ~ancestor:ancestor_db ~target:right_db
+ in
+ let right_diff_map =
+ List.fold_left
+ (fun m d ->
+ let key =
+ match d with
+ | Diff.RelationAdded r -> r.Relation.name
+ | Diff.RelationRemoved n -> n
+ | Diff.RelationModified { name; _ } -> name
+ in
+ Database.RelationMap.add key d m)
+ Database.RelationMap.empty right_diffs
+ in
+ let merged_db, all_conflicts =
+ let db_after_left, left_conflicts =
+ List.fold_left
+ (fun (db, conflicts) left_d ->
+ let rel_name =
+ match left_d with
+ | Diff.RelationAdded r -> r.Relation.name
+ | Diff.RelationRemoved n -> n
+ | Diff.RelationModified { name; _ } -> name
+ in
+ let right_d =
+ Database.RelationMap.find_opt rel_name
+ right_diff_map
+ in
+ let new_db, cs =
+ apply_diff db ~strategy ~left_db ~right_db
+ ~left_diff:left_d ~right_diff:right_d
+ in
+ (new_db, cs @ conflicts))
+ (ancestor_db, []) left_diffs
+ in
+ let left_names =
+ List.fold_left
+ (fun s d ->
+ let n =
+ match d with
+ | Diff.RelationAdded r -> r.Relation.name
+ | Diff.RelationRemoved n -> n
+ | Diff.RelationModified { name; _ } -> name
+ in
+ StringSet.add n s)
+ StringSet.empty left_diffs
+ in
+ let right_only =
+ List.filter
+ (fun d ->
+ let n =
+ match d with
+ | Diff.RelationAdded r -> r.Relation.name
+ | Diff.RelationRemoved n -> n
+ | Diff.RelationModified { name; _ } -> name
+ in
+ not (StringSet.mem n left_names))
+ right_diffs
+ in
+ let db_final, right_conflicts =
+ List.fold_left
+ (fun (db, conflicts) right_d ->
+ let new_db, cs =
+ apply_diff db ~strategy ~left_db ~right_db
+ ~left_diff:right_d ~right_diff:None
+ in
+ (new_db, cs @ conflicts))
+ (db_after_left, left_conflicts)
+ right_only
+ in
+ (db_final, right_conflicts)
+ in
+ let unresolvable =
+ List.filter
+ (function ConstraintConflict _ -> true | _ -> false)
+ all_conflicts
+ in
+ if unresolvable <> [] then Ok (Failed unresolvable)
+ else begin
+ match Manip.store_database storage merged_db with
+ | Error e -> Error e
+ | Ok () -> Ok (Clean merged_db)
+ end)))
end
diff --git a/lib/management/physical.ml b/lib/management/physical.ml
index 6063d79..3fc2cf7 100644
--- a/lib/management/physical.ml
+++ b/lib/management/physical.ml
@@ -1,46 +1,48 @@
(** Physical storage abstraction.
- This module defines a functor-based storage interface that can be backed
- by any key-value store (etcd, Redis, DynamoDB, PostgreSQL, filesystem, etc.).
+ This module defines a functor-based storage interface that can be backed by
+ any key-value store (etcd, Redis, DynamoDB, PostgreSQL, filesystem, etc.).
- All data is content-addressed: keys are hashes, values are serialized objects.
- The storage is append-only - objects are never modified, only new versions
- are written with new hashes. *)
+ All data is content-addressed: keys are hashes, values are serialized
+ objects. The storage is append-only - objects are never modified, only new
+ versions are written with new hashes. *)
(** Backend module signature that any storage driver must implement *)
module type BACKEND = sig
type connection
type error
- (** Connect to the storage backend *)
val connect : unit -> (connection, error) result
+ (** Connect to the storage backend *)
- (** Disconnect from the backend *)
val disconnect : connection -> unit
+ (** Disconnect from the backend *)
- (** Get a value by its hash. Returns None if not found. *)
val get : connection -> Conventions.Hash.t -> (bytes option, error) result
+ (** Get a value by its hash. Returns None if not found. *)
- (** Store a value at its hash *)
val put : connection -> Conventions.Hash.t -> bytes -> (unit, error) result
+ (** Store a value at its hash *)
- (** Check if a hash exists *)
val exists : connection -> Conventions.Hash.t -> (bool, error) result
+ (** Check if a hash exists *)
- (** Begin a transaction *)
val begin_transaction : connection -> (unit, error) result
+ (** Begin a transaction *)
- (** Commit a transaction *)
val commit : connection -> (unit, error) result
+ (** Commit a transaction *)
- (** Rollback a transaction *)
val rollback : connection -> (unit, error) result
+ (** Rollback a transaction *)
+ val get_many :
+ connection -> Conventions.Hash.t list -> (bytes option list, error) result
(** Batch get multiple values *)
- val get_many : connection -> Conventions.Hash.t list -> (bytes option list, error) result
+ val put_many :
+ connection -> (Conventions.Hash.t * bytes) list -> (unit, error) result
(** Batch put multiple values *)
- val put_many : connection -> (Conventions.Hash.t * bytes) list -> (unit, error) result
end
(** Storage operations built on top of a backend *)
@@ -48,29 +50,33 @@ module type S = sig
type t
type error
- (** Create a storage instance from a backend connection *)
val create : unit -> (t, error) result
+ (** Create a storage instance from a backend connection *)
- (** Close the storage *)
val close : t -> unit
+ (** Close the storage *)
+ val store_attribute :
+ t -> Conventions.AbstractValue.t -> (Conventions.Hash.t, error) result
(** Store an attribute value, returns its hash *)
- val store_attribute : t -> Conventions.AbstractValue.t -> (Conventions.Hash.t, error) result
+ val load_attribute :
+ t ->
+ Conventions.Hash.t ->
+ (Conventions.AbstractValue.t option, error) result
(** Load an attribute value by hash *)
- val load_attribute : t -> Conventions.Hash.t -> (Conventions.AbstractValue.t option, error) result
- (** Store raw bytes at a hash *)
val store_raw : t -> Conventions.Hash.t -> bytes -> (unit, error) result
+ (** Store raw bytes at a hash *)
- (** Load raw bytes by hash *)
val load_raw : t -> Conventions.Hash.t -> (bytes option, error) result
+ (** Load raw bytes by hash *)
- (** Check if a hash exists *)
val exists : t -> Conventions.Hash.t -> (bool, error) result
+ (** Check if a hash exists *)
- (** Execute a function within a transaction *)
val with_transaction : t -> (unit -> ('a, error) result) -> ('a, error) result
+ (** Execute a function within a transaction *)
end
(** Functor to create storage operations from a backend *)
@@ -79,15 +85,12 @@ module Make (B : BACKEND) : S with type error = B.error = struct
type error = B.error
let create () = B.connect ()
-
let close = B.disconnect
let store_attribute conn value =
let bytes = Marshal.to_bytes value [] in
let hash = Conventions.AbstractValue.hash value in
- match B.put conn hash bytes with
- | Ok () -> Ok hash
- | Error e -> Error e
+ match B.put conn hash bytes with Ok () -> Ok hash | Error e -> Error e
let load_attribute conn hash =
match B.get conn hash with
@@ -95,27 +98,20 @@ module Make (B : BACKEND) : S with type error = B.error = struct
| Ok None -> Ok None
| Error e -> Error e
- let store_raw conn hash bytes =
- B.put conn hash bytes
-
- let load_raw conn hash =
- B.get conn hash
-
- let exists conn hash =
- B.exists conn hash
+ let store_raw conn hash bytes = B.put conn hash bytes
+ let load_raw conn hash = B.get conn hash
+ let exists conn hash = B.exists conn hash
let with_transaction conn f =
match B.begin_transaction conn with
| Error e -> Error e
- | Ok () ->
- match f () with
- | Ok result ->
- (match B.commit conn with
- | Ok () -> Ok result
- | Error e -> Error e)
- | Error e ->
- let _ = B.rollback conn in
- Error e
+ | Ok () -> (
+ match f () with
+ | Ok result -> (
+ match B.commit conn with Ok () -> Ok result | Error e -> Error e)
+ | Error e ->
+ let _ = B.rollback conn in
+ Error e)
end
(** In-memory backend for testing and development *)
@@ -125,35 +121,34 @@ module MemoryBackend : BACKEND with type error = string = struct
mutable in_transaction : bool;
mutable transaction_buffer : (string * bytes) list;
}
+
type error = string
let connect () =
- Ok {
- data = Hashtbl.create 1024;
- in_transaction = false;
- transaction_buffer = [];
- }
+ Ok
+ {
+ data = Hashtbl.create 1024;
+ in_transaction = false;
+ transaction_buffer = [];
+ }
let disconnect _ = ()
-
- let get conn hash =
- Ok (Hashtbl.find_opt conn.data hash)
+ let get conn hash = Ok (Hashtbl.find_opt conn.data hash)
let put conn hash value =
if conn.in_transaction then begin
conn.transaction_buffer <- (hash, value) :: conn.transaction_buffer;
Ok ()
- end else begin
+ end
+ else begin
Hashtbl.replace conn.data hash value;
Ok ()
end
- let exists conn hash =
- Ok (Hashtbl.mem conn.data hash)
+ let exists conn hash = Ok (Hashtbl.mem conn.data hash)
let begin_transaction conn =
- if conn.in_transaction then
- Error "Already in transaction"
+ if conn.in_transaction then Error "Already in transaction"
else begin
conn.in_transaction <- true;
conn.transaction_buffer <- [];
@@ -161,20 +156,18 @@ module MemoryBackend : BACKEND with type error = string = struct
end
let commit conn =
- if not conn.in_transaction then
- Error "Not in transaction"
+ if not conn.in_transaction then Error "Not in transaction"
else begin
- List.iter (fun (hash, value) ->
- Hashtbl.replace conn.data hash value
- ) conn.transaction_buffer;
+ List.iter
+ (fun (hash, value) -> Hashtbl.replace conn.data hash value)
+ conn.transaction_buffer;
conn.in_transaction <- false;
conn.transaction_buffer <- [];
Ok ()
end
let rollback conn =
- if not conn.in_transaction then
- Error "Not in transaction"
+ if not conn.in_transaction then Error "Not in transaction"
else begin
conn.in_transaction <- false;
conn.transaction_buffer <- [];
@@ -185,14 +178,14 @@ module MemoryBackend : BACKEND with type error = string = struct
Ok (List.map (fun h -> Hashtbl.find_opt conn.data h) hashes)
let put_many conn pairs =
- List.iter (fun (hash, value) ->
- if conn.in_transaction then
- conn.transaction_buffer <- (hash, value) :: conn.transaction_buffer
- else
- Hashtbl.replace conn.data hash value
- ) pairs;
+ List.iter
+ (fun (hash, value) ->
+ if conn.in_transaction then
+ conn.transaction_buffer <- (hash, value) :: conn.transaction_buffer
+ else Hashtbl.replace conn.data hash value)
+ pairs;
Ok ()
end
+module Memory = Make (MemoryBackend)
(** Default in-memory storage for development *)
-module Memory = Make(MemoryBackend)
diff --git a/lib/merkle.ml b/lib/merkle.ml
index ffa7171..6c4be52 100644
--- a/lib/merkle.ml
+++ b/lib/merkle.ml
@@ -2,57 +2,52 @@
This is an opaque interface that can be backed by different implementations:
- A simple hash set (current placeholder)
- - A proper radix/patricia merkle tree (e.g., plebeia when OCaml 5 support arrives)
+ - A proper radix/patricia merkle tree (e.g., plebeia when OCaml 5 support
+ arrives)
- The tree stores hashes as keys (like a set) and computes a root hash
- from all contained elements. *)
+ The tree stores hashes as keys (like a set) and computes a root hash from
+ all contained elements. *)
module type S = sig
type t
- (** The empty tree *)
val empty : t
+ (** The empty tree *)
- (** Check if tree is empty *)
val is_empty : t -> bool
+ (** Check if tree is empty *)
- (** Insert a hash into the tree. Returns a new tree. *)
val insert : Conventions.Hash.t -> t -> t
+ (** Insert a hash into the tree. Returns a new tree. *)
- (** Delete a hash from the tree. Returns a new tree. *)
val delete : Conventions.Hash.t -> t -> t
+ (** Delete a hash from the tree. Returns a new tree. *)
- (** Check if a hash is in the tree *)
val member : Conventions.Hash.t -> t -> bool
+ (** Check if a hash is in the tree *)
- (** Get all hashes in the tree *)
val keys : t -> Conventions.Hash.t list
+ (** Get all hashes in the tree *)
- (** Compute the root hash of the tree. None if empty. *)
val root_hash : t -> Conventions.Hash.t option
+ (** Compute the root hash of the tree. None if empty. *)
- (** Number of elements in the tree *)
val size : t -> int
+ (** Number of elements in the tree *)
end
-(** Simple hash set implementation.
- This is a placeholder until a proper radix-merkle library
- with OCaml 5 support becomes available. *)
+(** Simple hash set implementation. This is a placeholder until a proper
+ radix-merkle library with OCaml 5 support becomes available. *)
module HashSet : S = struct
- module StringSet = Set.Make(String)
+ module StringSet = Set.Make (String)
type t = StringSet.t
let empty = StringSet.empty
-
let is_empty = StringSet.is_empty
-
let insert hash tree = StringSet.add hash tree
-
let delete hash tree = StringSet.remove hash tree
-
let member hash tree = StringSet.mem hash tree
-
let keys tree = StringSet.elements tree
(* TODO: Streaming/pagination for large tuple sets. Currently materializes
entire keyset into memory, which fails for relations with billions of tuples.
@@ -60,10 +55,8 @@ module HashSet : S = struct
to avoid loading all hashes upfront. Consider replacing HashSet with proper
radix-merkle tree (plebeia) for native lazy traversal. *)
-
let root_hash tree =
- if StringSet.is_empty tree then
- None
+ if StringSet.is_empty tree then None
else
(* Compute root hash by hashing sorted concatenation of all hashes *)
let sorted_hashes = StringSet.elements tree in
@@ -73,5 +66,5 @@ module HashSet : S = struct
let size = StringSet.cardinal
end
-(** Default implementation using HashSet *)
include HashSet
+(** Default implementation using HashSet *)
diff --git a/lib/prelude/catalog.ml b/lib/prelude/catalog.ml
index 4a7241a..ce36f1e 100644
--- a/lib/prelude/catalog.ml
+++ b/lib/prelude/catalog.ml
@@ -1,39 +1,35 @@
-(** System catalog helpers.
- Pure definitions — no storage access, no manipulation dependency.
- Consumed by {!Manipulation.Make} to bootstrap and maintain the six
- catalog relations that make the database self-describing. *)
+(** System catalog helpers. Pure definitions — no storage access, no
+ manipulation dependency. Consumed by {!Manipulation.Make} to bootstrap and
+ maintain the six catalog relations that make the database self-describing.
+*)
(* TODO: on_rel_name and timing_rel_name are defined and seeded but never
written to by any executor. They appear to be stubs for trigger/timing
metadata that was never implemented. *)
let catalog_prefix = "sakura:"
-
-let relation_rel_name = catalog_prefix ^ "relation"
-let domain_rel_name = catalog_prefix ^ "domain"
-let attribute_rel_name = catalog_prefix ^ "attribute"
+let relation_rel_name = catalog_prefix ^ "relation"
+let domain_rel_name = catalog_prefix ^ "domain"
+let attribute_rel_name = catalog_prefix ^ "attribute"
let constraint_rel_name = catalog_prefix ^ "constraint"
-let on_rel_name = catalog_prefix ^ "on"
-let timing_rel_name = catalog_prefix ^ "timing"
-let branch_rel_name = catalog_prefix ^ "branch"
-let head_rel_name = catalog_prefix ^ "head"
+let on_rel_name = catalog_prefix ^ "on"
+let timing_rel_name = catalog_prefix ^ "timing"
+let branch_rel_name = catalog_prefix ^ "branch"
+let head_rel_name = catalog_prefix ^ "head"
let catalog_relation_names =
- [ relation_rel_name
- ; domain_rel_name
- ; attribute_rel_name
- ; constraint_rel_name
- ; on_rel_name
- ; timing_rel_name
+ [
+ relation_rel_name;
+ domain_rel_name;
+ attribute_rel_name;
+ constraint_rel_name;
+ on_rel_name;
+ timing_rel_name;
]
let is_catalog_relation name = List.mem name catalog_relation_names
-
-let relation_schema : Schema.t =
- Schema.empty |> Schema.add "name" "string"
-
-let domain_schema : Schema.t =
- Schema.empty |> Schema.add "name" "string"
+let relation_schema : Schema.t = Schema.empty |> Schema.add "name" "string"
+let domain_schema : Schema.t = Schema.empty |> Schema.add "name" "string"
let attribute_schema : Schema.t =
Schema.empty
@@ -42,23 +38,20 @@ let attribute_schema : Schema.t =
|> Schema.add "domain_name" "string"
let constraint_schema : Schema.t =
- Schema.empty
- |> Schema.add "name" "string"
+ Schema.empty |> Schema.add "name" "string"
|> Schema.add "relation_name" "string"
-let on_schema : Schema.t =
- Schema.empty |> Schema.add "event" "string"
-
-let timing_schema : Schema.t =
- Schema.empty |> Schema.add "timing" "string"
+let on_schema : Schema.t = Schema.empty |> Schema.add "event" "string"
+let timing_schema : Schema.t = Schema.empty |> Schema.add "timing" "string"
let catalog_definitions =
- [ (relation_rel_name, relation_schema)
- ; (domain_rel_name, domain_schema)
- ; (attribute_rel_name, attribute_schema)
- ; (constraint_rel_name, constraint_schema)
- ; (on_rel_name, on_schema)
- ; (timing_rel_name, timing_schema)
+ [
+ (relation_rel_name, relation_schema);
+ (domain_rel_name, domain_schema);
+ (attribute_rel_name, attribute_schema);
+ (constraint_rel_name, constraint_schema);
+ (on_rel_name, on_schema);
+ (timing_rel_name, timing_schema);
]
(* TODO: all tuple builders use Obj.magic to coerce string values into
@@ -66,51 +59,59 @@ let catalog_definitions =
should use Obj.repr instead. *)
let build_relation_tuple rel_name : Tuple.materialized =
- { Tuple.relation = relation_rel_name
- ; attributes =
+ {
+ Tuple.relation = relation_rel_name;
+ attributes =
Tuple.AttributeMap.singleton "name"
- { Attribute.value = Obj.magic rel_name }
+ { Attribute.value = Obj.magic rel_name };
}
let build_domain_tuple dom_name : Tuple.materialized =
- { Tuple.relation = domain_rel_name
- ; attributes =
+ {
+ Tuple.relation = domain_rel_name;
+ attributes =
Tuple.AttributeMap.singleton "name"
- { Attribute.value = Obj.magic dom_name }
+ { Attribute.value = Obj.magic dom_name };
}
-let build_attribute_tuples ~relation_name (schema : Schema.t) : Tuple.materialized list =
- List.map (fun (attr_n, dom_n) ->
- { Tuple.relation = attribute_rel_name
- ; attributes =
- Tuple.AttributeMap.of_list
- [ ("relation_name", { Attribute.value = Obj.magic relation_name })
- ; ("attr_name", { Attribute.value = Obj.magic attr_n })
- ; ("domain_name", { Attribute.value = Obj.magic dom_n })
- ]
- }
- ) schema
+let build_attribute_tuples ~relation_name (schema : Schema.t) :
+ Tuple.materialized list =
+ List.map
+ (fun (attr_n, dom_n) ->
+ {
+ Tuple.relation = attribute_rel_name;
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("relation_name", { Attribute.value = Obj.magic relation_name });
+ ("attr_name", { Attribute.value = Obj.magic attr_n });
+ ("domain_name", { Attribute.value = Obj.magic dom_n });
+ ];
+ })
+ schema
let build_constraint_tuple cname rel_name : Tuple.materialized =
- { Tuple.relation = constraint_rel_name
- ; attributes =
+ {
+ Tuple.relation = constraint_rel_name;
+ attributes =
Tuple.AttributeMap.of_list
- [ ("name", { Attribute.value = Obj.magic cname })
- ; ("relation_name", { Attribute.value = Obj.magic rel_name })
- ]
+ [
+ ("name", { Attribute.value = Obj.magic cname });
+ ("relation_name", { Attribute.value = Obj.magic rel_name });
+ ];
}
let build_on_tuple event : Tuple.materialized =
- { Tuple.relation = on_rel_name
- ; attributes =
- Tuple.AttributeMap.singleton "event"
- { Attribute.value = Obj.magic event }
+ {
+ Tuple.relation = on_rel_name;
+ attributes =
+ Tuple.AttributeMap.singleton "event" { Attribute.value = Obj.magic event };
}
let build_timing_tuple timing : Tuple.materialized =
- { Tuple.relation = timing_rel_name
- ; attributes =
+ {
+ Tuple.relation = timing_rel_name;
+ attributes =
Tuple.AttributeMap.singleton "timing"
- { Attribute.value = Obj.magic timing }
+ { Attribute.value = Obj.magic timing };
}
-
diff --git a/lib/prelude/domains.ml b/lib/prelude/domains.ml
index 29104bb..d892696 100644
--- a/lib/prelude/domains.ml
+++ b/lib/prelude/domains.ml
@@ -1,21 +1,19 @@
-(** Predefined fundamental domains, ported from the Erlang implementation.
- Each domain carries a generator (for enumeration) and membership criteria
- (for validation). Values are stored as {!Conventions.AbstractValue.t} via
+(** Predefined fundamental domains, ported from the Erlang implementation. Each
+ domain carries a generator (for enumeration) and membership criteria (for
+ validation). Values are stored as {!Conventions.AbstractValue.t} via
[Obj.magic], following the same convention as {!Standard}. *)
(** Bijection from natural number to integer: 0→0, 1→1, 2→-1, 3→2, 4→-2, … *)
let nat_to_int n =
- if n = 0 then 0
- else if n mod 2 = 1 then (n + 1) / 2
- else -(n / 2)
+ if n = 0 then 0 else if n mod 2 = 1 then (n + 1) / 2 else -(n / 2)
(** Cantor pairing inverse: given position [n], returns a pair [(a, b)] of
natural numbers such that [a + b] lies on the [n]-th diagonal. *)
let cantor_unpair n =
let w =
- int_of_float (floor ((sqrt (float_of_int (8 * n + 1)) -. 1.) /. 2.))
+ int_of_float (floor ((sqrt (float_of_int ((8 * n) + 1)) -. 1.) /. 2.))
in
- let t = (w * (w + 1)) / 2 in
+ let t = w * (w + 1) / 2 in
let b = n - t in
let a = w - b in
(a, b)
@@ -26,84 +24,84 @@ let integer : Domain.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let v = nat_to_int n in
- let attributes = Tuple.AttributeMap.of_list [ ("value", Obj.magic v) ] in
- Generator.Value
- (Tuple.make_materialized ~relation:"integer" ~attributes, generator)
+ let v = nat_to_int n in
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("value", Obj.magic v) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"integer" ~attributes, generator)
| None ->
- Generator.Error
- "Cannot produce a randomly enumerated value for the integer domain."
+ Generator.Error
+ "Cannot produce a randomly enumerated value for the integer domain."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized _ -> true
| Tuple.NonMaterialized _ -> false
in
Domain.make ~name:"integer" ~generator ~membership_criteria
- ~cardinality:Conventions.Cardinality.AlephZero
- ~compare:(fun a b -> Stdlib.compare (Obj.magic a : int) (Obj.magic b : int))
+ ~cardinality:Conventions.Cardinality.AlephZero ~compare:(fun a b ->
+ Stdlib.compare (Obj.magic a : int) (Obj.magic b : int))
-(** Natural number domain: non-negative integers (0, 1, 2, …).
- Schema: [value: natural]. *)
+(** Natural number domain: non-negative integers (0, 1, 2, …). Schema:
+ [value: natural]. *)
let natural : Domain.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let attributes = Tuple.AttributeMap.of_list [ ("value", Obj.magic n) ] in
- Generator.Value
- (Tuple.make_materialized ~relation:"natural" ~attributes, generator)
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("value", Obj.magic n) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"natural" ~attributes, generator)
| None ->
- Generator.Error
- "Cannot produce a randomly enumerated value for the natural domain."
+ Generator.Error
+ "Cannot produce a randomly enumerated value for the natural domain."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match Tuple.AttributeMap.find_opt "value" m.attributes with
- | Some attr -> (Obj.magic attr : int) >= 0
- | None -> false)
+ match Tuple.AttributeMap.find_opt "value" m.attributes with
+ | Some attr -> (Obj.magic attr : int) >= 0
+ | None -> false)
| Tuple.NonMaterialized _ -> false
in
Domain.make ~name:"natural" ~generator ~membership_criteria
- ~cardinality:Conventions.Cardinality.AlephZero
- ~compare:(fun a b -> Stdlib.compare (Obj.magic a : int) (Obj.magic b : int))
+ ~cardinality:Conventions.Cardinality.AlephZero ~compare:(fun a b ->
+ Stdlib.compare (Obj.magic a : int) (Obj.magic b : int))
(** Rational number domain: pairs [(numerator, denominator)] where the
- denominator is never zero. The generator enumerates all such pairs via
- Cantor pairing on the integers.
- Schema: [numerator: integer, denominator: integer]. *)
+ denominator is never zero. The generator enumerates all such pairs via
+ Cantor pairing on the integers. Schema:
+ [numerator: integer, denominator: integer]. *)
let rational : Domain.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_unpair n in
- let num = nat_to_int a in
- (* Map b → non-zero integer: 0→1, 1→-1, 2→2, 3→-2, … *)
- let den =
- if b mod 2 = 0 then b / 2 + 1 else -((b + 1) / 2)
- in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("numerator", Obj.magic num); ("denominator", Obj.magic den) ]
- in
- Generator.Value
- (Tuple.make_materialized ~relation:"rational" ~attributes, generator)
+ let a, b = cantor_unpair n in
+ let num = nat_to_int a in
+ (* Map b → non-zero integer: 0→1, 1→-1, 2→2, 3→-2, … *)
+ let den = if b mod 2 = 0 then (b / 2) + 1 else -((b + 1) / 2) in
+ let attributes =
+ Tuple.AttributeMap.of_list
+ [ ("numerator", Obj.magic num); ("denominator", Obj.magic den) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"rational" ~attributes, generator)
| None ->
- Generator.Error
- "Cannot produce a randomly enumerated value for the rational domain."
+ Generator.Error
+ "Cannot produce a randomly enumerated value for the rational domain."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match Tuple.AttributeMap.find_opt "denominator" m.attributes with
- | Some attr -> (Obj.magic attr : int) <> 0
- | None -> false)
+ match Tuple.AttributeMap.find_opt "denominator" m.attributes with
+ | Some attr -> (Obj.magic attr : int) <> 0
+ | None -> false)
| Tuple.NonMaterialized _ -> false
in
Domain.make ~name:"rational" ~generator ~membership_criteria
- ~cardinality:Conventions.Cardinality.AlephZero
- ~compare:(fun a b ->
+ ~cardinality:Conventions.Cardinality.AlephZero ~compare:(fun a b ->
Stdlib.compare (Obj.magic a : float) (Obj.magic b : float))
-(** String domain: any string value. Not enumerable.
- Schema: [value: string]. *)
+(** String domain: any string value. Not enumerable. Schema: [value: string]. *)
let string : Domain.t =
let generator (_ : int option) : Generator.result =
Generator.Error "Strings are not enumerable."
@@ -113,6 +111,5 @@ let string : Domain.t =
| Tuple.NonMaterialized _ -> false
in
Domain.make ~name:"string" ~generator ~membership_criteria
- ~cardinality:Conventions.Cardinality.AlephZero
- ~compare:(fun a b ->
+ ~cardinality:Conventions.Cardinality.AlephZero ~compare:(fun a b ->
String.compare (Obj.magic a : string) (Obj.magic b : string))
diff --git a/lib/prelude/standard.ml b/lib/prelude/standard.ml
index 68012fc..6bdab0c 100644
--- a/lib/prelude/standard.ml
+++ b/lib/prelude/standard.ml
@@ -7,22 +7,23 @@
let mk v = { Attribute.value = Obj.repr v }
-(** Build a comparison relation over a single domain.
- [pred] receives the result of [Stdlib.compare left right]. *)
+(** Build a comparison relation over a single domain. [pred] receives the result
+ of [Stdlib.compare left right]. *)
let make_comparison ~name ~domain_name ~pred ~cardinality ~generator =
let schema =
- Schema.empty |> Schema.add "left" domain_name
+ Schema.empty
+ |> Schema.add "left" domain_name
|> Schema.add "right" domain_name
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "left" m.attributes,
- Tuple.AttributeMap.find_opt "right" m.attributes )
- with
- | Some l, Some r ->
- pred (Stdlib.compare l.Attribute.value r.Attribute.value)
- | _ -> false)
+ match
+ ( Tuple.AttributeMap.find_opt "left" m.attributes,
+ Tuple.AttributeMap.find_opt "right" m.attributes )
+ with
+ | Some l, Some r ->
+ pred (Stdlib.compare l.Attribute.value r.Attribute.value)
+ | _ -> false)
| Tuple.NonMaterialized _ -> false
in
Relation.make ~hash:None ~name ~schema ~tree:None ~constraints:None
@@ -33,38 +34,35 @@ let make_comparison ~name ~domain_name ~pred ~cardinality ~generator =
(** Enumerate pairs (a, b) where a < b using triangular indexing. *)
let pair_of_nat_lt n =
let nf = float_of_int n in
- let r = int_of_float (floor ((1. +. sqrt (1. +. 8. *. nf)) /. 2.)) in
- let t_prev = (r * (r - 1)) / 2 in
+ let r = int_of_float (floor ((1. +. sqrt (1. +. (8. *. nf))) /. 2.)) in
+ let t_prev = r * (r - 1) / 2 in
let left = n - t_prev in
(left, r)
(** Enumerate all pairs (a, b) via Cantor pairing. *)
let cantor_pair_of_nat n =
let w =
- int_of_float (floor ((sqrt (float_of_int (8 * n + 1)) -. 1.) /. 2.))
+ int_of_float (floor ((sqrt (float_of_int ((8 * n) + 1)) -. 1.) /. 2.))
in
- let t = (w * (w + 1)) / 2 in
+ let t = w * (w + 1) / 2 in
let b = n - t in
let a = w - b in
(a, b)
-
let less_than_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some position ->
- let left, right = pair_of_nat_lt position in
- let attributes : Attribute.materialized Tuple.AttributeMap.t =
- Tuple.AttributeMap.of_list
- [ ("left", mk left); ("right", mk right) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"less_than" ~attributes,
- generator )
+ let left, right = pair_of_nat_lt position in
+ let attributes : Attribute.materialized Tuple.AttributeMap.t =
+ Tuple.AttributeMap.of_list [ ("left", mk left); ("right", mk right) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"less_than" ~attributes, generator)
| None ->
- Error
- "Cannot produce a randomly enumerated value for less_than over \
- naturals."
+ Error
+ "Cannot produce a randomly enumerated value for less_than over \
+ naturals."
in
make_comparison ~name:"natural_natural_less_than" ~domain_name:"natural"
~pred:(fun c -> c < 0)
@@ -74,17 +72,16 @@ let less_than_or_equal_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_pair_of_nat n in
- (* a <= b is always true when a <= b; we enumerate all (a, b) with a <= b *)
- let left = min a b in
- let right = max a b in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("left", mk left); ("right", mk right) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"less_than_or_equal" ~attributes,
- generator )
+ let a, b = cantor_pair_of_nat n in
+ (* a <= b is always true when a <= b; we enumerate all (a, b) with a <= b *)
+ let left = min a b in
+ let right = max a b in
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("left", mk left); ("right", mk right) ]
+ in
+ Generator.Value
+ ( Tuple.make_materialized ~relation:"less_than_or_equal" ~attributes,
+ generator )
| None -> Error "Cannot enumerate less_than_or_equal randomly."
in
make_comparison ~name:"natural_natural_less_than_or_equal"
@@ -96,15 +93,14 @@ let greater_than_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let left, right = pair_of_nat_lt n in
- (* Swap: right > left *)
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("left", mk right); ("right", mk left) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"greater_than" ~attributes,
- generator )
+ let left, right = pair_of_nat_lt n in
+ (* Swap: right > left *)
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("left", mk right); ("right", mk left) ]
+ in
+ Generator.Value
+ ( Tuple.make_materialized ~relation:"greater_than" ~attributes,
+ generator )
| None -> Error "Cannot enumerate greater_than randomly."
in
make_comparison ~name:"natural_natural_greater_than" ~domain_name:"natural"
@@ -115,16 +111,15 @@ let greater_than_or_equal_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_pair_of_nat n in
- let left = max a b in
- let right = min a b in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("left", mk left); ("right", mk right) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"greater_than_or_equal" ~attributes,
- generator )
+ let a, b = cantor_pair_of_nat n in
+ let left = max a b in
+ let right = min a b in
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("left", mk left); ("right", mk right) ]
+ in
+ Generator.Value
+ ( Tuple.make_materialized ~relation:"greater_than_or_equal" ~attributes,
+ generator )
| None -> Error "Cannot enumerate greater_than_or_equal randomly."
in
make_comparison ~name:"natural_natural_greater_than_or_equal"
@@ -136,12 +131,11 @@ let equal_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("left", mk n); ("right", mk n) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"equal" ~attributes, generator )
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("left", mk n); ("right", mk n) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"equal" ~attributes, generator)
| None -> Error "Cannot enumerate equal randomly."
in
make_comparison ~name:"natural_natural_equal" ~domain_name:"natural"
@@ -152,21 +146,19 @@ let not_equal_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_pair_of_nat n in
- let left, right = if a = b then (a, b + 1) else (a, b) in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("left", mk left); ("right", mk right) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"not_equal" ~attributes, generator )
+ let a, b = cantor_pair_of_nat n in
+ let left, right = if a = b then (a, b + 1) else (a, b) in
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("left", mk left); ("right", mk right) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"not_equal" ~attributes, generator)
| None -> Error "Cannot enumerate not_equal randomly."
in
make_comparison ~name:"natural_natural_not_equal" ~domain_name:"natural"
~pred:(fun c -> c <> 0)
~cardinality:Conventions.Cardinality.AlephZero ~generator
-
let plus_natural : Relation.t =
let schema =
Schema.empty |> Schema.add "a" "natural" |> Schema.add "b" "natural"
@@ -175,175 +167,159 @@ let plus_natural : Relation.t =
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_pair_of_nat n in
- let s = a + b in
- let attributes =
- Tuple.AttributeMap.of_list
- [ ("a", mk a); ("b", mk b); ("sum", mk s) ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"plus" ~attributes, generator )
+ let a, b = cantor_pair_of_nat n in
+ let s = a + b in
+ let attributes =
+ Tuple.AttributeMap.of_list [ ("a", mk a); ("b", mk b); ("sum", mk s) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"plus" ~attributes, generator)
| None -> Error "Cannot enumerate plus randomly."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "a" m.attributes,
- Tuple.AttributeMap.find_opt "b" m.attributes,
- Tuple.AttributeMap.find_opt "sum" m.attributes )
- with
- | Some a, Some b, Some s ->
- (Obj.magic a.Attribute.value : int)
- + (Obj.magic b.Attribute.value : int)
- = (Obj.magic s.Attribute.value : int)
- | _ -> false)
+ match
+ ( Tuple.AttributeMap.find_opt "a" m.attributes,
+ Tuple.AttributeMap.find_opt "b" m.attributes,
+ Tuple.AttributeMap.find_opt "sum" m.attributes )
+ with
+ | Some a, Some b, Some s ->
+ (Obj.magic a.Attribute.value : int)
+ + (Obj.magic b.Attribute.value : int)
+ = (Obj.magic s.Attribute.value : int)
+ | _ -> false)
| Tuple.NonMaterialized _ -> false
in
Relation.make ~hash:None ~name:"natural_plus" ~schema ~tree:None
- ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
+ ~constraints:None ~cardinality:Conventions.Cardinality.AlephZero
~generator:(Some generator) ~membership_criteria
~provenance:(Relation.Provenance.Base "plus")
~lineage:(Relation.Lineage.Base "plus")
let times_natural : Relation.t =
let schema =
- Schema.empty |> Schema.add "a" "natural"
- |> Schema.add "b" "natural"
+ Schema.empty |> Schema.add "a" "natural" |> Schema.add "b" "natural"
|> Schema.add "product" "natural"
in
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- let a, b = cantor_pair_of_nat n in
- let p = a * b in
- let attributes =
- Tuple.AttributeMap.of_list
- [
- ("a", mk a); ("b", mk b); ("product", mk p);
- ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"times" ~attributes, generator )
+ let a, b = cantor_pair_of_nat n in
+ let p = a * b in
+ let attributes =
+ Tuple.AttributeMap.of_list
+ [ ("a", mk a); ("b", mk b); ("product", mk p) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"times" ~attributes, generator)
| None -> Error "Cannot enumerate times randomly."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "a" m.attributes,
- Tuple.AttributeMap.find_opt "b" m.attributes,
- Tuple.AttributeMap.find_opt "product" m.attributes )
- with
- | Some a, Some b, Some p ->
- (Obj.magic a.Attribute.value : int)
- * (Obj.magic b.Attribute.value : int)
- = (Obj.magic p.Attribute.value : int)
- | _ -> false)
+ match
+ ( Tuple.AttributeMap.find_opt "a" m.attributes,
+ Tuple.AttributeMap.find_opt "b" m.attributes,
+ Tuple.AttributeMap.find_opt "product" m.attributes )
+ with
+ | Some a, Some b, Some p ->
+ (Obj.magic a.Attribute.value : int)
+ * (Obj.magic b.Attribute.value : int)
+ = (Obj.magic p.Attribute.value : int)
+ | _ -> false)
| Tuple.NonMaterialized _ -> false
in
Relation.make ~hash:None ~name:"natural_times" ~schema ~tree:None
- ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
+ ~constraints:None ~cardinality:Conventions.Cardinality.AlephZero
~generator:(Some generator) ~membership_criteria
~provenance:(Relation.Provenance.Base "times")
~lineage:(Relation.Lineage.Base "times")
let minus_natural : Relation.t =
let schema =
- Schema.empty |> Schema.add "a" "natural"
- |> Schema.add "b" "natural"
+ Schema.empty |> Schema.add "a" "natural" |> Schema.add "b" "natural"
|> Schema.add "difference" "natural"
in
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- (* Enumerate pairs where a >= b, so difference >= 0 *)
- let b, diff = cantor_pair_of_nat n in
- let a = b + diff in
- let attributes =
- Tuple.AttributeMap.of_list
- [
- ("a", mk a);
- ("b", mk b);
- ("difference", mk diff);
- ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"minus" ~attributes, generator )
+ (* Enumerate pairs where a >= b, so difference >= 0 *)
+ let b, diff = cantor_pair_of_nat n in
+ let a = b + diff in
+ let attributes =
+ Tuple.AttributeMap.of_list
+ [ ("a", mk a); ("b", mk b); ("difference", mk diff) ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"minus" ~attributes, generator)
| None -> Error "Cannot enumerate minus randomly."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "a" m.attributes,
- Tuple.AttributeMap.find_opt "b" m.attributes,
- Tuple.AttributeMap.find_opt "difference" m.attributes )
- with
- | Some a, Some b, Some d ->
- let av = (Obj.magic a.Attribute.value : int) in
- let bv = (Obj.magic b.Attribute.value : int) in
- let dv = (Obj.magic d.Attribute.value : int) in
- av - bv = dv && dv >= 0
- | _ -> false)
+ match
+ ( Tuple.AttributeMap.find_opt "a" m.attributes,
+ Tuple.AttributeMap.find_opt "b" m.attributes,
+ Tuple.AttributeMap.find_opt "difference" m.attributes )
+ with
+ | Some a, Some b, Some d ->
+ let av = (Obj.magic a.Attribute.value : int) in
+ let bv = (Obj.magic b.Attribute.value : int) in
+ let dv = (Obj.magic d.Attribute.value : int) in
+ av - bv = dv && dv >= 0
+ | _ -> false)
| Tuple.NonMaterialized _ -> false
in
Relation.make ~hash:None ~name:"natural_minus" ~schema ~tree:None
- ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
+ ~constraints:None ~cardinality:Conventions.Cardinality.AlephZero
~generator:(Some generator) ~membership_criteria
~provenance:(Relation.Provenance.Base "minus")
~lineage:(Relation.Lineage.Base "minus")
let divide_natural : Relation.t =
let schema =
- Schema.empty |> Schema.add "a" "natural"
- |> Schema.add "b" "natural"
+ Schema.empty |> Schema.add "a" "natural" |> Schema.add "b" "natural"
|> Schema.add "quotient" "natural"
|> Schema.add "remainder" "natural"
in
let rec generator (position : int option) : Generator.result =
match position with
| Some n ->
- (* Enumerate (b, q, r) triples where b > 0, then a = b*q + r, 0 <= r < b *)
- (* We use a nested Cantor pairing: n -> (b_idx, qr_idx) -> (q, r) *)
- let b_idx, qr_idx = cantor_pair_of_nat n in
- let b = b_idx + 1 in (* b > 0 *)
- let q, r_raw = cantor_pair_of_nat qr_idx in
- let r = r_raw mod b in
- let a = b * q + r in
- let attributes =
- Tuple.AttributeMap.of_list
- [
- ("a", mk a);
- ("b", mk b);
- ("quotient", mk q);
- ("remainder", mk r);
- ]
- in
- Generator.Value
- ( Tuple.make_materialized ~relation:"divide" ~attributes, generator )
+ (* Enumerate (b, q, r) triples where b > 0, then a = b*q + r, 0 <= r < b *)
+ (* We use a nested Cantor pairing: n -> (b_idx, qr_idx) -> (q, r) *)
+ let b_idx, qr_idx = cantor_pair_of_nat n in
+ let b = b_idx + 1 in
+ (* b > 0 *)
+ let q, r_raw = cantor_pair_of_nat qr_idx in
+ let r = r_raw mod b in
+ let a = (b * q) + r in
+ let attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("a", mk a); ("b", mk b); ("quotient", mk q); ("remainder", mk r);
+ ]
+ in
+ Generator.Value
+ (Tuple.make_materialized ~relation:"divide" ~attributes, generator)
| None -> Error "Cannot enumerate divide randomly."
in
let membership_criteria : Tuple.t -> bool = function
| Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "a" m.attributes,
- Tuple.AttributeMap.find_opt "b" m.attributes,
- Tuple.AttributeMap.find_opt "quotient" m.attributes,
- Tuple.AttributeMap.find_opt "remainder" m.attributes )
- with
- | Some a, Some b, Some q, Some r ->
- let av = (Obj.magic a.Attribute.value : int) in
- let bv = (Obj.magic b.Attribute.value : int) in
- let qv = (Obj.magic q.Attribute.value : int) in
- let rv = (Obj.magic r.Attribute.value : int) in
- bv > 0 && av = bv * qv + rv && rv >= 0 && rv < bv
- | _ -> false)
+ match
+ ( Tuple.AttributeMap.find_opt "a" m.attributes,
+ Tuple.AttributeMap.find_opt "b" m.attributes,
+ Tuple.AttributeMap.find_opt "quotient" m.attributes,
+ Tuple.AttributeMap.find_opt "remainder" m.attributes )
+ with
+ | Some a, Some b, Some q, Some r ->
+ let av = (Obj.magic a.Attribute.value : int) in
+ let bv = (Obj.magic b.Attribute.value : int) in
+ let qv = (Obj.magic q.Attribute.value : int) in
+ let rv = (Obj.magic r.Attribute.value : int) in
+ bv > 0 && av = (bv * qv) + rv && rv >= 0 && rv < bv
+ | _ -> false)
| Tuple.NonMaterialized _ -> false
in
Relation.make ~hash:None ~name:"natural_divide" ~schema ~tree:None
- ~constraints:None
- ~cardinality:Conventions.Cardinality.AlephZero
+ ~constraints:None ~cardinality:Conventions.Cardinality.AlephZero
~generator:(Some generator) ~membership_criteria
~provenance:(Relation.Provenance.Base "divide")
~lineage:(Relation.Lineage.Base "divide")
diff --git a/lib/relation.ml b/lib/relation.ml
index 0773c6c..285bcef 100644
--- a/lib/relation.ml
+++ b/lib/relation.ml
@@ -1,5 +1,5 @@
-(** Use the Merkle module for tuple hash storage *)
module Tree = Merkle
+(** Use the Merkle module for tuple hash storage *)
module RelationConstraint = struct
type name = string
@@ -54,4 +54,4 @@ let make ~hash ~name ~schema ~tree ~constraints ~cardinality ~generator
membership_criteria;
provenance;
lineage;
- } \ No newline at end of file
+ }
diff --git a/lib/schema.ml b/lib/schema.ml
index e17f7a5..923284a 100644
--- a/lib/schema.ml
+++ b/lib/schema.ml
@@ -1,7 +1,7 @@
(** Schema: maps attribute names to domain/type names.
- A schema defines the structure of a relation by listing each attribute
- and its associated domain (type). This is used for:
+ A schema defines the structure of a relation by listing each attribute and
+ its associated domain (type). This is used for:
- Hashing relations (schema is part of the relation identity)
- Validation (checking tuple membership)
- Persistence (storing and loading relations with their structure) *)
@@ -9,13 +9,8 @@
type t = (string * string) list
let empty : t = []
-
-let add name domain_name (schema : t) : t =
- (name, domain_name) :: schema
-
-let attributes (schema : t) =
- List.map fst schema
+let add name domain_name (schema : t) : t = (name, domain_name) :: schema
+let attributes (schema : t) = List.map fst schema
let to_string schema =
- List.map (fun (n, t) -> n ^ ":" ^ t) schema
- |> String.concat ","
+ List.map (fun (n, t) -> n ^ ":" ^ t) schema |> String.concat ","
diff --git a/lib/scl/executor.ml b/lib/scl/executor.ml
index 6cb17b8..d48d852 100644
--- a/lib/scl/executor.ml
+++ b/lib/scl/executor.ml
@@ -6,72 +6,68 @@ type cursor_batch = {
has_more : bool;
}
-type exec_result =
- | Batch of cursor_batch
- | Closed of Management.Database.t
+type exec_result = Batch of cursor_batch | Closed of Management.Database.t
let sessions : Session.t option ref = ref None
-
let set_sessions s = sessions := Some s
module Make (Storage : Management.Physical.S) = struct
- module DrlExec = Drl.Executor.Make(Storage)
- module Alg = Algebra.Make(Storage)
+ module DrlExec = Drl.Executor.Make (Storage)
+ module Alg = Algebra.Make (Storage)
type error =
- | ParseError of string
- | QueryError of DrlExec.error
+ | ParseError of string
+ | QueryError of DrlExec.error
| CursorError of string
let sexp_of_error e =
let open Sexplib.Sexp in
match e with
- | ParseError s -> List [Atom "parse-error"; Atom s]
- | QueryError e -> DrlExec.sexp_of_error e
- | CursorError s -> List [Atom "cursor-error"; Atom s]
+ | ParseError s -> List [ Atom "parse-error"; Atom s ]
+ | QueryError e -> DrlExec.sexp_of_error e
+ | CursorError s -> List [ Atom "cursor-error"; Atom s ]
let get_sessions () =
match !sessions with
| Some s -> Ok s
- | None -> Error (CursorError "The session registry has not been initialised; ensure `set_sessions` is called at startup.")
+ | None ->
+ Error
+ (CursorError
+ "The session registry has not been initialised; ensure \
+ `set_sessions` is called at startup.")
let ( let* ) = Result.bind
- let execute
- (storage : Storage.t)
- (db : Management.Database.t)
- (stmt : Ast.statement)
- : (exec_result, error) result =
+ let execute (storage : Storage.t) (db : Management.Database.t)
+ (stmt : Ast.statement) : (exec_result, error) result =
match stmt with
| Ast.Begin { query; limit } ->
- let* reg = get_sessions () in
- let* rel =
- DrlExec.execute storage db query
- |> Result.map_error (fun e -> QueryError e)
- in
- let gen = Alg.to_generator storage rel in
- let query_str = Drl.Parser.to_string query in
- let cur = Session.register reg ~db ~query:query_str ~generator:gen in
- let batch = Option.value ~default:default_batch limit in
- let* (rows, has_more) =
- Session.fetch reg ~id:cur.Session.id ~limit:batch
- |> Result.map_error (fun s -> CursorError s)
- in
- Ok (Batch { cursor_id = cur.Session.id; rows; has_more })
-
+ let* reg = get_sessions () in
+ let* rel =
+ DrlExec.execute storage db query
+ |> Result.map_error (fun e -> QueryError e)
+ in
+ let gen = Alg.to_generator storage rel in
+ let query_str = Drl.Parser.to_string query in
+ let cur = Session.register reg ~db ~query:query_str ~generator:gen in
+ let batch = Option.value ~default:default_batch limit in
+ let* rows, has_more =
+ Session.fetch reg ~id:cur.Session.id ~limit:batch
+ |> Result.map_error (fun s -> CursorError s)
+ in
+ Ok (Batch { cursor_id = cur.Session.id; rows; has_more })
| Ast.Fetch { cursor; limit } ->
- let* reg = get_sessions () in
- let batch = Option.value ~default:default_batch limit in
- let* (rows, has_more) =
- Session.fetch reg ~id:cursor ~limit:batch
- |> Result.map_error (fun s -> CursorError s)
- in
- Ok (Batch { cursor_id = cursor; rows; has_more })
-
+ let* reg = get_sessions () in
+ let batch = Option.value ~default:default_batch limit in
+ let* rows, has_more =
+ Session.fetch reg ~id:cursor ~limit:batch
+ |> Result.map_error (fun s -> CursorError s)
+ in
+ Ok (Batch { cursor_id = cursor; rows; has_more })
| Ast.Close { cursor } ->
- let* reg = get_sessions () in
- Session.close reg ~id:cursor;
- Ok (Closed db)
+ let* reg = get_sessions () in
+ Session.close reg ~id:cursor;
+ Ok (Closed db)
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/scl/sublanguage.ml b/lib/scl/sublanguage.ml
index 7098ced..b39e720 100644
--- a/lib/scl/sublanguage.ml
+++ b/lib/scl/sublanguage.ml
@@ -1,9 +1,9 @@
module Make (Storage : Management.Physical.S) = struct
- module Exec = Executor.Make(Storage)
+ module Exec = Executor.Make (Storage)
type storage = Storage.t
- type ast = Ast.statement
- type error = Exec.error
+ type ast = Ast.statement
+ type error = Exec.error
let name = "scl"
@@ -16,11 +16,10 @@ module Make (Storage : Management.Physical.S) = struct
Exec.execute storage db ast
|> Result.map (function
| Executor.Batch { cursor_id; rows; has_more } ->
- Sublanguage.Cursor { cursor_id; rows; has_more }
- | Executor.Closed db ->
- Sublanguage.Transition (db, "cursor closed"))
+ Sublanguage.Cursor { cursor_id; rows; has_more }
+ | Executor.Closed db -> Sublanguage.Transition (db, "cursor closed"))
let sexp_of_error = Exec.sexp_of_error
end
-module Memory = Make(Management.Physical.Memory)
+module Memory = Make (Management.Physical.Memory)
diff --git a/lib/session.ml b/lib/session.ml
index 272a419..a58d00b 100644
--- a/lib/session.ml
+++ b/lib/session.ml
@@ -1,8 +1,8 @@
(** Cursor registry for streaming tuple consumption.
- Each cursor wraps a [Generator.t] produced by a DRL query and
- tracks the current position so that successive [fetch] calls
- return the next batch of rows without replaying the query. *)
+ Each cursor wraps a [Generator.t] produced by a DRL query and tracks the
+ current position so that successive [fetch] calls return the next batch of
+ rows without replaying the query. *)
type cursor = {
id : string;
@@ -13,70 +13,67 @@ type cursor = {
created_at : float;
}
-type t = {
- tbl : (string, cursor) Hashtbl.t;
- mutable counter : int;
-}
+type t = { tbl : (string, cursor) Hashtbl.t; mutable counter : int }
-let create () : t =
- { tbl = Hashtbl.create 16; counter = 0 }
+let create () : t = { tbl = Hashtbl.create 16; counter = 0 }
let register (reg : t) ~(db : Management.Database.t) ~(query : string)
~(generator : Generator.t) : cursor =
let raw = string_of_int reg.counter ^ query ^ db.Management.Database.hash in
let id = Conventions.Hash.hash_text raw in
reg.counter <- reg.counter + 1;
- let cur = {
- id;
- generator;
- position = 0;
- db_snapshot = db;
- query_sexp = query;
- created_at = Unix.gettimeofday ();
- } in
+ let cur =
+ {
+ id;
+ generator;
+ position = 0;
+ db_snapshot = db;
+ query_sexp = query;
+ created_at = Unix.gettimeofday ();
+ }
+ in
Hashtbl.replace reg.tbl id cur;
cur
-let fetch (reg : t) ~(id : string) ~(limit : int)
- : (Tuple.materialized list * bool, string) result =
+let fetch (reg : t) ~(id : string) ~(limit : int) :
+ (Tuple.materialized list * bool, string) result =
match Hashtbl.find_opt reg.tbl id with
- | None -> Error ("The cursor with identifier `" ^ id ^ "` was not found in the session registry.")
+ | None ->
+ Error
+ ("The cursor with identifier `" ^ id
+ ^ "` was not found in the session registry.")
| Some cur ->
- let rec go gen pos acc count =
- if count >= limit then (List.rev acc, gen, pos, true)
- else
- match gen (Some pos) with
- | Generator.Done -> (List.rev acc, gen, pos, false)
- (* Generator errors are treated as exhaustion: the partial batch is
+ let rec go gen pos acc count =
+ if count >= limit then (List.rev acc, gen, pos, true)
+ else
+ match gen (Some pos) with
+ | Generator.Done -> (List.rev acc, gen, pos, false)
+ (* Generator errors are treated as exhaustion: the partial batch is
returned with has_more = false and the cursor is auto-removed.
The caller receives no indication that the stream terminated
abnormally. When fetch gains a richer return type this arm
should surface the error message instead. *)
- | Generator.Error _ -> (List.rev acc, gen, pos, false)
- | Generator.Value (t, next) ->
- begin match t with
- | Tuple.Materialized m ->
- go next (pos + 1) (m :: acc) (count + 1)
- | Tuple.NonMaterialized _ ->
- go next (pos + 1) acc count
- end
- in
- let (rows, gen', pos', has_more) =
- go cur.generator cur.position [] 0
- in
- cur.generator <- gen';
- cur.position <- pos';
- if not has_more then Hashtbl.remove reg.tbl id;
- Ok (rows, has_more)
+ | Generator.Error _ -> (List.rev acc, gen, pos, false)
+ | Generator.Value (t, next) -> begin
+ match t with
+ | Tuple.Materialized m -> go next (pos + 1) (m :: acc) (count + 1)
+ | Tuple.NonMaterialized _ -> go next (pos + 1) acc count
+ end
+ in
+ let rows, gen', pos', has_more = go cur.generator cur.position [] 0 in
+ cur.generator <- gen';
+ cur.position <- pos';
+ if not has_more then Hashtbl.remove reg.tbl id;
+ Ok (rows, has_more)
-let close (reg : t) ~(id : string) : unit =
- Hashtbl.remove reg.tbl id
+let close (reg : t) ~(id : string) : unit = Hashtbl.remove reg.tbl id
let gc (reg : t) ~(max_age : float) : unit =
let now = Unix.gettimeofday () in
let to_remove =
- Hashtbl.fold (fun k cur acc ->
- if now -. cur.created_at >= max_age then k :: acc else acc)
+ Hashtbl.fold
+ (fun k cur acc ->
+ if now -. cur.created_at >= max_age then k :: acc else acc)
reg.tbl []
in
List.iter (Hashtbl.remove reg.tbl) to_remove
diff --git a/lib/storable.ml b/lib/storable.ml
index 058773c..ec0bce2 100644
--- a/lib/storable.ml
+++ b/lib/storable.ml
@@ -3,7 +3,8 @@
These modules define the wire format for persisting relational engine
objects to storage. Each type has to_bytes/of_bytes for serialization.
- The stored formats capture only the essential data needed for reconstruction:
+ The stored formats capture only the essential data needed for
+ reconstruction:
- Tuple: relation name and attribute hashes
- Relation: name, schema, tuple hashes, cardinality
- Database: name, relation hashes, merkle tree keys, history *)
@@ -12,14 +13,12 @@
module Tuple = struct
type t = {
relation : string;
- attributes : (string * Conventions.Hash.t) list; (* attr_name -> attr_value_hash *)
+ attributes : (string * Conventions.Hash.t) list;
+ (* attr_name -> attr_value_hash *)
}
- let to_bytes (st : t) : bytes =
- Marshal.to_bytes st [Marshal.Closures]
-
- let of_bytes (b : bytes) : t =
- Marshal.from_bytes b 0
+ let to_bytes (st : t) : bytes = Marshal.to_bytes st [ Marshal.Closures ]
+ let of_bytes (b : bytes) : t = Marshal.from_bytes b 0
end
(** Stored relation format for serialization *)
@@ -27,30 +26,26 @@ module Relation = struct
type t = {
name : string;
schema : Schema.t;
- tree_keys : Conventions.Hash.t list; (* Merkle tree contents - tuple hashes *)
+ tree_keys : Conventions.Hash.t list;
+ (* Merkle tree contents - tuple hashes *)
cardinality : Conventions.Cardinality.t;
}
- let to_bytes (sr : t) : bytes =
- Marshal.to_bytes sr [Marshal.Closures]
-
- let of_bytes (b : bytes) : t =
- Marshal.from_bytes b 0
+ let to_bytes (sr : t) : bytes = Marshal.to_bytes sr [ Marshal.Closures ]
+ let of_bytes (b : bytes) : t = Marshal.from_bytes b 0
end
(** Stored database format for serialization *)
module Database = struct
type t = {
name : string;
- relations : (string * Conventions.Hash.t) list; (* name -> relation_hash *)
- tree_keys : Conventions.Hash.t list; (* Merkle tree contents - relation hashes *)
+ relations : (string * Conventions.Hash.t) list; (* name -> relation_hash *)
+ tree_keys : Conventions.Hash.t list;
+ (* Merkle tree contents - relation hashes *)
history : Conventions.Hash.t list;
timestamp : float;
}
- let to_bytes (sd : t) : bytes =
- Marshal.to_bytes sd [Marshal.Closures]
-
- let of_bytes (b : bytes) : t =
- Marshal.from_bytes b 0
+ let to_bytes (sd : t) : bytes = Marshal.to_bytes sd [ Marshal.Closures ]
+ let of_bytes (b : bytes) : t = Marshal.from_bytes b 0
end
diff --git a/lib/sublanguage.ml b/lib/sublanguage.ml
index d92358e..4cfe913 100644
--- a/lib/sublanguage.ml
+++ b/lib/sublanguage.ml
@@ -1,24 +1,24 @@
+type cursor_result = {
+ cursor_id : string;
+ rows : Tuple.materialized list;
+ has_more : bool;
+}
(** 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).
+ - 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)
+ - 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 *)
-type cursor_result = {
- cursor_id : string;
- rows : Tuple.materialized list;
- has_more : bool;
-}
type result =
| Query of Relation.t
@@ -29,8 +29,12 @@ module type S = sig
type storage
type ast
type error
+
val name : string
val parse_sexp : Sexplib.Sexp.t -> (ast, error) Result.t
- val execute : storage -> Management.Database.t -> ast -> (result, error) Result.t
+
+ val execute :
+ storage -> Management.Database.t -> ast -> (result, error) Result.t
+
val sexp_of_error : error -> Sexplib.Sexp.t
end
diff --git a/lib/tuple.ml b/lib/tuple.ml
index 11fc508..1ce73b0 100644
--- a/lib/tuple.ml
+++ b/lib/tuple.ml
@@ -1,8 +1,10 @@
module AttributeMap = struct
- include Map.Make(String)
+ include Map.Make (String)
+
let sexp_of_t sexp_of_v m =
Sexplib.Sexp.List
- (List.map (fun (k, v) -> Sexplib.Sexp.List [Sexplib.Sexp.Atom k; sexp_of_v v])
+ (List.map
+ (fun (k, v) -> Sexplib.Sexp.List [ Sexplib.Sexp.Atom k; sexp_of_v v ])
(bindings m))
end
@@ -10,16 +12,17 @@ type non_materialized = {
hash : Conventions.Hash.t;
relation : Conventions.Name.t;
attributes : Attribute.referenced AttributeMap.t;
-} [@@deriving sexp_of]
+}
+[@@deriving sexp_of]
type materialized = {
- relation: Conventions.Name.t;
- attributes: Attribute.materialized AttributeMap.t;
-} [@@deriving sexp_of]
+ relation : Conventions.Name.t;
+ attributes : Attribute.materialized AttributeMap.t;
+}
+[@@deriving sexp_of]
-type t =
- | Materialized of materialized
- | NonMaterialized of non_materialized
+type t = Materialized of materialized | NonMaterialized of non_materialized
[@@deriving sexp_of]
-let make_materialized ~relation ~attributes = Materialized { relation; attributes }
+let make_materialized ~relation ~attributes =
+ Materialized { relation; attributes }
diff --git a/proof/drl_verified.ml b/proof/drl_verified.ml
index f4c282a..137ad86 100644
--- a/proof/drl_verified.ml
+++ b/proof/drl_verified.ml
@@ -1,112 +1,84 @@
-
(** val negb : bool -> bool **)
-let negb = function
-| true -> false
-| false -> true
+let negb = function true -> false | false -> true
(** val fst : ('a1 * 'a2) -> 'a1 **)
-let fst = function
-| (x, _) -> x
+let fst = function x, _ -> x
(** val snd : ('a1 * 'a2) -> 'a2 **)
-let snd = function
-| (_, y) -> y
+let snd = function _, y -> y
(** val app : 'a1 list -> 'a1 list -> 'a1 list **)
-let rec app l m =
- match l with
- | [] -> m
- | a :: l1 -> a :: (app l1 m)
+let rec app l m = match l with [] -> m | a :: l1 -> a :: app l1 m
(** val map : ('a1 -> 'a2) -> 'a1 list -> 'a2 list **)
-let rec map f = function
-| [] -> []
-| a :: l0 -> (f a) :: (map f l0)
+let rec map f = function [] -> [] | a :: l0 -> f a :: map f l0
(** val firstn : int -> 'a1 list -> 'a1 list **)
let rec firstn n l =
- (fun fO fS n -> if n=0 then fO () else fS (n-1))
+ (fun fO fS n -> if n = 0 then fO () else fS (n - 1))
(fun _ -> [])
- (fun n0 -> match l with
- | [] -> []
- | a :: l0 -> a :: (firstn n0 l0))
+ (fun n0 -> match l with [] -> [] | a :: l0 -> a :: firstn n0 l0)
n
(** val flat_map : ('a1 -> 'a2 list) -> 'a1 list -> 'a2 list **)
-let rec flat_map f = function
-| [] -> []
-| x :: l0 -> app (f x) (flat_map f l0)
+let rec flat_map f = function [] -> [] | x :: l0 -> app (f x) (flat_map f l0)
(** val existsb : ('a1 -> bool) -> 'a1 list -> bool **)
-let rec existsb f = function
-| [] -> false
-| a :: l0 -> (||) (f a) (existsb f l0)
+let rec existsb f = function [] -> false | a :: l0 -> f a || existsb f l0
(** val forallb : ('a1 -> bool) -> 'a1 list -> bool **)
-let rec forallb f = function
-| [] -> true
-| a :: l0 -> (&&) (f a) (forallb f l0)
+let rec forallb f = function [] -> true | a :: l0 -> f a && forallb f l0
(** val filter : ('a1 -> bool) -> 'a1 list -> 'a1 list **)
let rec filter f = function
-| [] -> []
-| x :: l0 -> if f x then x :: (filter f l0) else filter f l0
+ | [] -> []
+ | x :: l0 -> if f x then x :: filter f l0 else filter f l0
(** val find : ('a1 -> bool) -> 'a1 list -> 'a1 option **)
let rec find f = function
-| [] -> None
-| x :: tl -> if f x then Some x else find f tl
+ | [] -> None
+ | x :: tl -> if f x then Some x else find f tl
(** val eqb : char list -> char list -> bool **)
let rec eqb s1 s2 =
match s1 with
- | [] -> (match s2 with
- | [] -> true
- | _::_ -> false)
- | c1::s1' ->
- (match s2 with
- | [] -> false
- | c2::s2' -> if (=) c1 c2 then eqb s1' s2' else false)
+ | [] -> ( match s2 with [] -> true | _ :: _ -> false)
+ | c1 :: s1' -> (
+ match s2 with
+ | [] -> false
+ | c2 :: s2' -> if c1 = c2 then eqb s1' s2' else false)
(** val append : char list -> char list -> char list **)
-let rec append s1 s2 =
- match s1 with
- | [] -> s2
- | c::s1' -> c::(append s1' s2)
+let rec append s1 s2 = match s1 with [] -> s2 | c :: s1' -> c :: append s1' s2
type attr_name = char list
-
type domain_name = char list
-
type schema = (attr_name * domain_name) list
(** val has_attr : attr_name -> schema -> bool **)
-let has_attr a s =
- existsb (fun p -> eqb a (fst p)) s
+let has_attr a s = existsb (fun p -> eqb a (fst p)) s
(** val attr_names : schema -> attr_name list **)
-let attr_names s =
- map fst s
+let attr_names s = map fst s
(** val filter_schema : attr_name list -> schema -> schema **)
-let filter_schema attrs s =
- filter (fun p -> existsb (eqb (fst p)) attrs) s
+let filter_schema attrs s = filter (fun p -> existsb (eqb (fst p)) attrs) s
(** val merge_schema : schema -> schema -> schema **)
@@ -118,48 +90,52 @@ let merge_schema s1 s2 =
let remove_attrs attrs s =
filter (fun p -> negb (existsb (eqb (fst p)) attrs)) s
-(** val rename_in_schema :
- (attr_name * attr_name) list -> schema -> schema **)
+(** val rename_in_schema : (attr_name * attr_name) list -> schema -> schema **)
let rename_in_schema renames s =
- map (fun p ->
- let a = fst p in
- let d = snd p in
- (match find (fun r -> eqb a (fst r)) renames with
- | Some p0 -> let (_, a') = p0 in (a', d)
- | None -> (a, d)))
+ map
+ (fun p ->
+ let a = fst p in
+ let d = snd p in
+ match find (fun r -> eqb a (fst r)) renames with
+ | Some p0 ->
+ let _, a' = p0 in
+ (a', d)
+ | None -> (a, d))
s
type value (* AXIOM TO BE REALIZED *)
(** val value_eqb : value -> value -> bool **)
-let value_eqb =
- failwith "AXIOM TO BE REALIZED (Sakura.Relation.value_eqb)"
+let value_eqb = failwith "AXIOM TO BE REALIZED (Sakura.Relation.value_eqb)"
type tuple = (attr_name * value) list
(** val tuple_lookup : attr_name -> tuple -> value option **)
let rec tuple_lookup a = function
-| [] -> None
-| p :: rest ->
- let (a', v) = p in if eqb a a' then Some v else tuple_lookup a rest
+ | [] -> None
+ | p :: rest ->
+ let a', v = p in
+ if eqb a a' then Some v else tuple_lookup a rest
(** val project_tuple : attr_name list -> tuple -> tuple **)
-let project_tuple attrs t =
- filter (fun p -> existsb (eqb (fst p)) attrs) t
+let project_tuple attrs t = filter (fun p -> existsb (eqb (fst p)) attrs) t
(** val rename_tuple : (attr_name * attr_name) list -> tuple -> tuple **)
let rename_tuple renames t =
- map (fun p ->
- let a = fst p in
- let v = snd p in
- (match find (fun r -> eqb a (fst r)) renames with
- | Some p0 -> let (_, a') = p0 in (a', v)
- | None -> (a, v)))
+ map
+ (fun p ->
+ let a = fst p in
+ let v = snd p in
+ match find (fun r -> eqb a (fst r)) renames with
+ | Some p0 ->
+ let _, a' = p0 in
+ (a', v)
+ | None -> (a, v))
t
(** val merge_tuples : tuple -> tuple -> tuple **)
@@ -170,86 +146,85 @@ let merge_tuples t1 t2 =
(** val tuples_agree_on : attr_name list -> tuple -> tuple -> bool **)
let tuples_agree_on attrs t1 t2 =
- forallb (fun a ->
- match tuple_lookup a t1 with
- | Some v1 ->
- (match tuple_lookup a t2 with
- | Some v2 -> value_eqb v1 v2
- | None -> false)
- | None -> (match tuple_lookup a t2 with
- | Some _ -> false
- | None -> true))
+ forallb
+ (fun a ->
+ match tuple_lookup a t1 with
+ | Some v1 -> (
+ match tuple_lookup a t2 with
+ | Some v2 -> value_eqb v1 v2
+ | None -> false)
+ | None -> ( match tuple_lookup a t2 with Some _ -> false | None -> true))
attrs
-type relation = { rel_name : char list; rel_schema : schema;
- rel_tuples : tuple list }
+type relation = {
+ rel_name : char list;
+ rel_schema : schema;
+ rel_tuples : tuple list;
+}
type database = (char list * relation) list
(** val db_lookup : char list -> database -> relation option **)
let rec db_lookup name = function
-| [] -> None
-| p :: rest ->
- let (n, r) = p in if eqb name n then Some r else db_lookup name rest
+ | [] -> None
+ | p :: rest ->
+ let n, r = p in
+ if eqb name n then Some r else db_lookup name rest
-type literal =
-| LitInt of int
-| LitStr of char list
-| LitBool of bool
+type literal = LitInt of int | LitStr of char list | LitBool of bool
type query =
-| Base of char list
-| Const of (attr_name * literal) list
-| Select of query * query
-| Join of attr_name list * query * query
-| Cartesian of query * query
-| Project of attr_name list * query
-| Rename of (attr_name * attr_name) list * query
-| Union of query * query
-| Diff of query * query
-| Take of int * query
+ | Base of char list
+ | Const of (attr_name * literal) list
+ | Select of query * query
+ | Join of attr_name list * query * query
+ | Cartesian of query * query
+ | Project of attr_name list * query
+ | Rename of (attr_name * attr_name) list * query
+ | Union of query * query
+ | Diff of query * query
+ | Take of int * query
(** val predicted_schema : database -> query -> schema option **)
let rec predicted_schema db = function
-| Base name ->
- (match db_lookup name db with
- | Some r -> Some r.rel_schema
- | None -> None)
-| Const pairs ->
- Some
- (map (fun p -> ((fst p),
- ('a'::('b'::('s'::('t'::('r'::('a'::('c'::('t'::[])))))))))) pairs)
-| Select (_, source) -> predicted_schema db source
-| Join (attrs, q1, q2) ->
- (match predicted_schema db q1 with
- | Some s1 ->
- (match predicted_schema db q2 with
- | Some s2 ->
- Some
- (merge_schema s1
- (remove_attrs (attr_names s1) (remove_attrs attrs s2)))
+ | Base name -> (
+ match db_lookup name db with Some r -> Some r.rel_schema | None -> None)
+ | Const pairs ->
+ Some
+ (map
+ (fun p -> (fst p, [ 'a'; 'b'; 's'; 't'; 'r'; 'a'; 'c'; 't' ]))
+ pairs)
+ | Select (_, source) -> predicted_schema db source
+ | Join (attrs, q1, q2) -> (
+ match predicted_schema db q1 with
+ | Some s1 -> (
+ match predicted_schema db q2 with
+ | Some s2 ->
+ Some
+ (merge_schema s1
+ (remove_attrs (attr_names s1) (remove_attrs attrs s2)))
+ | None -> None)
+ | None -> None)
+ | Cartesian (q1, q2) -> (
+ match predicted_schema db q1 with
+ | Some s1 -> (
+ match predicted_schema db q2 with
+ | Some s2 -> Some (merge_schema s1 s2)
+ | None -> None)
+ | None -> None)
+ | Project (attrs, q') -> (
+ match predicted_schema db q' with
+ | Some s -> Some (filter_schema attrs s)
| None -> None)
- | None -> None)
-| Cartesian (q1, q2) ->
- (match predicted_schema db q1 with
- | Some s1 ->
- (match predicted_schema db q2 with
- | Some s2 -> Some (merge_schema s1 s2)
+ | Rename (renames, q') -> (
+ match predicted_schema db q' with
+ | Some s -> Some (rename_in_schema renames s)
| None -> None)
- | None -> None)
-| Project (attrs, q') ->
- (match predicted_schema db q' with
- | Some s -> Some (filter_schema attrs s)
- | None -> None)
-| Rename (renames, q') ->
- (match predicted_schema db q' with
- | Some s -> Some (rename_in_schema renames s)
- | None -> None)
-| Union (q1, _) -> predicted_schema db q1
-| Diff (q1, _) -> predicted_schema db q1
-| Take (_, q') -> predicted_schema db q'
+ | Union (q1, _) -> predicted_schema db q1
+ | Diff (q1, _) -> predicted_schema db q1
+ | Take (_, q') -> predicted_schema db q'
(** val literal_to_value : literal -> value **)
@@ -259,152 +234,178 @@ let literal_to_value =
(** val eval_const : (attr_name * literal) list -> relation **)
let eval_const pairs =
- let tuple0 = map (fun p -> ((fst p), (literal_to_value (snd p)))) pairs in
+ let tuple0 = map (fun p -> (fst p, literal_to_value (snd p))) pairs in
let sch =
- map (fun p -> ((fst p),
- ('a'::('b'::('s'::('t'::('r'::('a'::('c'::('t'::[])))))))))) pairs
+ map (fun p -> (fst p, [ 'a'; 'b'; 's'; 't'; 'r'; 'a'; 'c'; 't' ])) pairs
in
- { rel_name = ('c'::('o'::('n'::('s'::('t'::[]))))); rel_schema = sch;
- rel_tuples = (tuple0 :: []) }
+ {
+ rel_name = [ 'c'; 'o'; 'n'; 's'; 't' ];
+ rel_schema = sch;
+ rel_tuples = tuple0 :: [];
+ }
(** val semijoin : relation -> relation -> relation **)
let semijoin filter_rel source =
let common =
- filter (fun a -> has_attr a filter_rel.rel_schema)
+ filter
+ (fun a -> has_attr a filter_rel.rel_schema)
(attr_names source.rel_schema)
in
- let matches = fun t ->
+ let matches =
+ fun t ->
existsb (fun ft -> tuples_agree_on common t ft) filter_rel.rel_tuples
in
- { rel_name = source.rel_name; rel_schema = source.rel_schema; rel_tuples =
- (filter matches source.rel_tuples) }
+ {
+ rel_name = source.rel_name;
+ rel_schema = source.rel_schema;
+ rel_tuples = filter matches source.rel_tuples;
+ }
(** val equijoin : attr_name list -> relation -> relation -> relation **)
let equijoin attrs left right =
let joined =
- flat_map (fun lt ->
- flat_map (fun rt ->
- if tuples_agree_on attrs lt rt then (merge_tuples lt rt) :: [] else [])
- right.rel_tuples)
+ flat_map
+ (fun lt ->
+ flat_map
+ (fun rt ->
+ if tuples_agree_on attrs lt rt then merge_tuples lt rt :: [] else [])
+ right.rel_tuples)
left.rel_tuples
in
let new_schema =
merge_schema left.rel_schema
- (remove_attrs (attr_names left.rel_schema)
- (remove_attrs attrs right.rel_schema))
+ (remove_attrs
+ (attr_names left.rel_schema)
+ (remove_attrs attrs right.rel_schema))
in
- { rel_name =
- (append ('j'::('o'::('i'::('n'::('_'::[])))))
- (append left.rel_name (append ('_'::[]) right.rel_name)));
- rel_schema = new_schema; rel_tuples = joined }
+ {
+ rel_name =
+ append
+ [ 'j'; 'o'; 'i'; 'n'; '_' ]
+ (append left.rel_name (append ('_' :: []) right.rel_name));
+ rel_schema = new_schema;
+ rel_tuples = joined;
+ }
(** val cartesian : relation -> relation -> relation **)
let cartesian left right =
let joined =
- flat_map (fun lt -> map (fun rt -> merge_tuples lt rt) right.rel_tuples)
+ flat_map
+ (fun lt -> map (fun rt -> merge_tuples lt rt) right.rel_tuples)
left.rel_tuples
in
- { rel_name =
- (append ('c'::('a'::('r'::('t'::('_'::[])))))
- (append left.rel_name (append ('_'::[]) right.rel_name)));
- rel_schema = (merge_schema left.rel_schema right.rel_schema); rel_tuples =
- joined }
+ {
+ rel_name =
+ append
+ [ 'c'; 'a'; 'r'; 't'; '_' ]
+ (append left.rel_name (append ('_' :: []) right.rel_name));
+ rel_schema = merge_schema left.rel_schema right.rel_schema;
+ rel_tuples = joined;
+ }
(** val project : attr_name list -> relation -> relation **)
let project attrs r =
- { rel_name = (append ('p'::('r'::('o'::('j'::('_'::[]))))) r.rel_name);
- rel_schema = (filter_schema attrs r.rel_schema); rel_tuples =
- (map (project_tuple attrs) r.rel_tuples) }
+ {
+ rel_name = append [ 'p'; 'r'; 'o'; 'j'; '_' ] r.rel_name;
+ rel_schema = filter_schema attrs r.rel_schema;
+ rel_tuples = map (project_tuple attrs) r.rel_tuples;
+ }
(** val rename : (attr_name * attr_name) list -> relation -> relation **)
let rename renames r =
- { rel_name =
- (append ('r'::('e'::('n'::('a'::('m'::('e'::('_'::[]))))))) r.rel_name);
- rel_schema = (rename_in_schema renames r.rel_schema); rel_tuples =
- (map (rename_tuple renames) r.rel_tuples) }
+ {
+ rel_name = append [ 'r'; 'e'; 'n'; 'a'; 'm'; 'e'; '_' ] r.rel_name;
+ rel_schema = rename_in_schema renames r.rel_schema;
+ rel_tuples = map (rename_tuple renames) r.rel_tuples;
+ }
(** val rel_union : relation -> relation -> relation **)
let rel_union r1 r2 =
- { rel_name =
- (append ('u'::('n'::('i'::('o'::('n'::('_'::[]))))))
- (append r1.rel_name (append ('_'::[]) r2.rel_name)));
- rel_schema = r1.rel_schema; rel_tuples =
- (app r1.rel_tuples r2.rel_tuples) }
+ {
+ rel_name =
+ append
+ [ 'u'; 'n'; 'i'; 'o'; 'n'; '_' ]
+ (append r1.rel_name (append ('_' :: []) r2.rel_name));
+ rel_schema = r1.rel_schema;
+ rel_tuples = app r1.rel_tuples r2.rel_tuples;
+ }
(** val rel_diff : relation -> relation -> relation **)
let rel_diff r1 r2 =
- let in_r2 = fun t ->
- existsb (fun t2 -> tuples_agree_on (attr_names r1.rel_schema) t t2)
+ let in_r2 =
+ fun t ->
+ existsb
+ (fun t2 -> tuples_agree_on (attr_names r1.rel_schema) t t2)
r2.rel_tuples
in
- { rel_name =
- (append ('d'::('i'::('f'::('f'::('_'::[])))))
- (append r1.rel_name (append ('_'::[]) r2.rel_name)));
- rel_schema = r1.rel_schema; rel_tuples =
- (filter (fun t -> negb (in_r2 t)) r1.rel_tuples) }
+ {
+ rel_name =
+ append
+ [ 'd'; 'i'; 'f'; 'f'; '_' ]
+ (append r1.rel_name (append ('_' :: []) r2.rel_name));
+ rel_schema = r1.rel_schema;
+ rel_tuples = filter (fun t -> negb (in_r2 t)) r1.rel_tuples;
+ }
(** val take : int -> relation -> relation **)
let take n r =
- { rel_name = (append ('t'::('a'::('k'::('e'::('_'::[]))))) r.rel_name);
- rel_schema = r.rel_schema; rel_tuples = (firstn n r.rel_tuples) }
+ {
+ rel_name = append [ 't'; 'a'; 'k'; 'e'; '_' ] r.rel_name;
+ rel_schema = r.rel_schema;
+ rel_tuples = firstn n r.rel_tuples;
+ }
(** val eval : database -> query -> relation option **)
let rec eval db = function
-| Base name -> db_lookup name db
-| Const pairs -> Some (eval_const pairs)
-| Select (filter_q, source_q) ->
- (match eval db filter_q with
- | Some filter_rel ->
- (match eval db source_q with
- | Some source_rel -> Some (semijoin filter_rel source_rel)
+ | Base name -> db_lookup name db
+ | Const pairs -> Some (eval_const pairs)
+ | Select (filter_q, source_q) -> (
+ match eval db filter_q with
+ | Some filter_rel -> (
+ match eval db source_q with
+ | Some source_rel -> Some (semijoin filter_rel source_rel)
+ | None -> None)
| None -> None)
- | None -> None)
-| Join (attrs, q1, q2) ->
- (match eval db q1 with
- | Some r1 ->
- (match eval db q2 with
- | Some r2 -> Some (equijoin attrs r1 r2)
+ | Join (attrs, q1, q2) -> (
+ match eval db q1 with
+ | Some r1 -> (
+ match eval db q2 with
+ | Some r2 -> Some (equijoin attrs r1 r2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Cartesian (q1, q2) ->
- (match eval db q1 with
- | Some r1 ->
- (match eval db q2 with
- | Some r2 -> Some (cartesian r1 r2)
+ | Cartesian (q1, q2) -> (
+ match eval db q1 with
+ | Some r1 -> (
+ match eval db q2 with
+ | Some r2 -> Some (cartesian r1 r2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Project (attrs, q') ->
- (match eval db q' with
- | Some r -> Some (project attrs r)
- | None -> None)
-| Rename (renames, q') ->
- (match eval db q' with
- | Some r -> Some (rename renames r)
- | None -> None)
-| Union (q1, q2) ->
- (match eval db q1 with
- | Some r1 ->
- (match eval db q2 with
- | Some r2 -> Some (rel_union r1 r2)
+ | Project (attrs, q') -> (
+ match eval db q' with Some r -> Some (project attrs r) | None -> None)
+ | Rename (renames, q') -> (
+ match eval db q' with Some r -> Some (rename renames r) | None -> None)
+ | Union (q1, q2) -> (
+ match eval db q1 with
+ | Some r1 -> (
+ match eval db q2 with
+ | Some r2 -> Some (rel_union r1 r2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Diff (q1, q2) ->
- (match eval db q1 with
- | Some r1 ->
- (match eval db q2 with
- | Some r2 -> Some (rel_diff r1 r2)
+ | Diff (q1, q2) -> (
+ match eval db q1 with
+ | Some r1 -> (
+ match eval db q2 with
+ | Some r2 -> Some (rel_diff r1 r2)
+ | None -> None)
| None -> None)
- | None -> None)
-| Take (n, q') ->
- (match eval db q' with
- | Some r -> Some (take n r)
- | None -> None)
+ | Take (n, q') -> (
+ match eval db q' with Some r -> Some (take n r) | None -> None)
diff --git a/test/test_relational_engine.ml b/test/test_relational_engine.ml
index 619bc27..1d8f1ca 100644
--- a/test/test_relational_engine.ml
+++ b/test/test_relational_engine.ml
@@ -58,84 +58,78 @@ let%test_unit "merkle: root hash changes on insert" =
assert (root1 <> root2)
let%test_unit "merkle: same elements produce same root hash" =
- let tree1 = Merkle.empty
- |> Merkle.insert "a"
- |> Merkle.insert "b" in
- let tree2 = Merkle.empty
- |> Merkle.insert "b"
- |> Merkle.insert "a" in
+ let tree1 = Merkle.empty |> Merkle.insert "a" |> Merkle.insert "b" in
+ let tree2 = Merkle.empty |> Merkle.insert "b" |> Merkle.insert "a" in
assert (Merkle.root_hash tree1 = Merkle.root_hash tree2)
let%test_unit "storage: create and close" =
match Management.Physical.Memory.create () with
| Error _ -> assert false
- | Ok storage ->
- Management.Physical.Memory.close storage
+ | Ok storage -> Management.Physical.Memory.close storage
let%test_unit "storage: store and load attribute" =
match Management.Physical.Memory.create () with
| Error _ -> assert false
| Ok storage ->
- let value = Obj.magic 42 in
- (match Management.Physical.Memory.store_attribute storage value with
- | Error _ -> assert false
- | Ok hash ->
- (match Management.Physical.Memory.load_attribute storage hash with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded) ->
- assert ((Obj.magic loaded : int) = 42)));
- Management.Physical.Memory.close storage
+ let value = Obj.magic 42 in
+ (match Management.Physical.Memory.store_attribute storage value with
+ | Error _ -> assert false
+ | Ok hash -> (
+ match Management.Physical.Memory.load_attribute storage hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded) -> assert ((Obj.magic loaded : int) = 42)));
+ Management.Physical.Memory.close storage
let%test_unit "storage: store and load raw bytes" =
match Management.Physical.Memory.create () with
| Error _ -> assert false
| Ok storage ->
- let hash = "test_hash" in
- let data = Bytes.of_string "hello world" in
- (match Management.Physical.Memory.store_raw storage hash data with
- | Error _ -> assert false
- | Ok () ->
- (match Management.Physical.Memory.load_raw storage hash with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded) ->
- assert (Bytes.equal loaded data)));
- Management.Physical.Memory.close storage
+ let hash = "test_hash" in
+ let data = Bytes.of_string "hello world" in
+ (match Management.Physical.Memory.store_raw storage hash data with
+ | Error _ -> assert false
+ | Ok () -> (
+ match Management.Physical.Memory.load_raw storage hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded) -> assert (Bytes.equal loaded data)));
+ Management.Physical.Memory.close storage
let%test_unit "storage: exists check" =
match Management.Physical.Memory.create () with
| Error _ -> assert false
| Ok storage ->
- let hash = "exists_test" in
- let data = Bytes.of_string "data" in
- (match Management.Physical.Memory.exists storage hash with
- | Error _ -> assert false
- | Ok exists -> assert (not exists));
- (match Management.Physical.Memory.store_raw storage hash data with
- | Error _ -> assert false
- | Ok () ->
- (match Management.Physical.Memory.exists storage hash with
- | Error _ -> assert false
- | Ok exists -> assert exists));
- Management.Physical.Memory.close storage
+ let hash = "exists_test" in
+ let data = Bytes.of_string "data" in
+ (match Management.Physical.Memory.exists storage hash with
+ | Error _ -> assert false
+ | Ok exists -> assert (not exists));
+ (match Management.Physical.Memory.store_raw storage hash data with
+ | Error _ -> assert false
+ | Ok () -> (
+ match Management.Physical.Memory.exists storage hash with
+ | Error _ -> assert false
+ | Ok exists -> assert exists));
+ Management.Physical.Memory.close storage
let%test_unit "storage: transaction commit" =
match Management.Physical.Memory.create () with
| Error _ -> assert false
| Ok storage ->
- let result = Management.Physical.Memory.with_transaction storage (fun () ->
- let hash = "tx_test" in
- let data = Bytes.of_string "tx_data" in
- Management.Physical.Memory.store_raw storage hash data
- ) in
- (match result with
- | Error _ -> assert false
- | Ok () ->
- (match Management.Physical.Memory.exists storage "tx_test" with
- | Error _ -> assert false
- | Ok exists -> assert exists));
- Management.Physical.Memory.close storage
+ let result =
+ Management.Physical.Memory.with_transaction storage (fun () ->
+ let hash = "tx_test" in
+ let data = Bytes.of_string "tx_data" in
+ Management.Physical.Memory.store_raw storage hash data)
+ in
+ (match result with
+ | Error _ -> assert false
+ | Ok () -> (
+ match Management.Physical.Memory.exists storage "tx_test" with
+ | Error _ -> assert false
+ | Ok exists -> assert exists));
+ Management.Physical.Memory.close storage
let%test_unit "database: create empty" =
let db = Management.Database.empty ~name:"test_db" in
@@ -147,17 +141,13 @@ let%test_unit "database: create empty" =
let%test_unit "database: add relation" =
let db = Management.Database.empty ~name:"test_db" in
(* Create a minimal relation for testing *)
- let relation = Relation.make
- ~hash:(Some "rel_hash_1")
- ~name:"users"
- ~schema:Schema.empty
- ~tree:(Some Merkle.empty)
- ~constraints:None
- ~cardinality:(Conventions.Cardinality.Finite 0)
- ~generator:None
- ~membership_criteria:(fun _ -> true)
- ~provenance:Relation.Provenance.Undefined
- ~lineage:(Relation.Lineage.Base "users")
+ let relation =
+ Relation.make ~hash:(Some "rel_hash_1") ~name:"users" ~schema:Schema.empty
+ ~tree:(Some Merkle.empty) ~constraints:None
+ ~cardinality:(Conventions.Cardinality.Finite 0) ~generator:None
+ ~membership_criteria:(fun _ -> true)
+ ~provenance:Relation.Provenance.Undefined
+ ~lineage:(Relation.Lineage.Base "users")
in
let db = Management.Database.add_relation db ~relation in
assert (Management.Database.has_relation db "users");
@@ -166,17 +156,13 @@ let%test_unit "database: add relation" =
let%test_unit "database: remove relation" =
let db = Management.Database.empty ~name:"test_db" in
- let relation = Relation.make
- ~hash:(Some "hash1")
- ~name:"users"
- ~schema:Schema.empty
- ~tree:(Some Merkle.empty)
- ~constraints:None
- ~cardinality:(Conventions.Cardinality.Finite 0)
- ~generator:None
- ~membership_criteria:(fun _ -> true)
- ~provenance:Relation.Provenance.Undefined
- ~lineage:(Relation.Lineage.Base "users")
+ let relation =
+ Relation.make ~hash:(Some "hash1") ~name:"users" ~schema:Schema.empty
+ ~tree:(Some Merkle.empty) ~constraints:None
+ ~cardinality:(Conventions.Cardinality.Finite 0) ~generator:None
+ ~membership_criteria:(fun _ -> true)
+ ~provenance:Relation.Provenance.Undefined
+ ~lineage:(Relation.Lineage.Base "users")
in
let db = Management.Database.add_relation db ~relation in
assert (Management.Database.has_relation db "users");
@@ -185,17 +171,13 @@ let%test_unit "database: remove relation" =
let%test_unit "database: update relation" =
let db = Management.Database.empty ~name:"test_db" in
- let relation = Relation.make
- ~hash:(Some "hash1")
- ~name:"users"
- ~schema:Schema.empty
- ~tree:(Some Merkle.empty)
- ~constraints:None
- ~cardinality:(Conventions.Cardinality.Finite 0)
- ~generator:None
- ~membership_criteria:(fun _ -> true)
- ~provenance:Relation.Provenance.Undefined
- ~lineage:(Relation.Lineage.Base "users")
+ let relation =
+ Relation.make ~hash:(Some "hash1") ~name:"users" ~schema:Schema.empty
+ ~tree:(Some Merkle.empty) ~constraints:None
+ ~cardinality:(Conventions.Cardinality.Finite 0) ~generator:None
+ ~membership_criteria:(fun _ -> true)
+ ~provenance:Relation.Provenance.Undefined
+ ~lineage:(Relation.Lineage.Base "users")
in
let db = Management.Database.add_relation db ~relation in
let old_db_hash = db.hash in
@@ -207,29 +189,21 @@ let%test_unit "database: update relation" =
let%test_unit "database: get relation names" =
let db = Management.Database.empty ~name:"test_db" in
- let rel1 = Relation.make
- ~hash:(Some "h1")
- ~name:"users"
- ~schema:Schema.empty
- ~tree:(Some Merkle.empty)
- ~constraints:None
- ~cardinality:(Conventions.Cardinality.Finite 0)
- ~generator:None
- ~membership_criteria:(fun _ -> true)
- ~provenance:Relation.Provenance.Undefined
- ~lineage:(Relation.Lineage.Base "users")
- in
- let rel2 = Relation.make
- ~hash:(Some "h2")
- ~name:"orders"
- ~schema:Schema.empty
- ~tree:(Some Merkle.empty)
- ~constraints:None
- ~cardinality:(Conventions.Cardinality.Finite 0)
- ~generator:None
- ~membership_criteria:(fun _ -> true)
- ~provenance:Relation.Provenance.Undefined
- ~lineage:(Relation.Lineage.Base "orders")
+ let rel1 =
+ Relation.make ~hash:(Some "h1") ~name:"users" ~schema:Schema.empty
+ ~tree:(Some Merkle.empty) ~constraints:None
+ ~cardinality:(Conventions.Cardinality.Finite 0) ~generator:None
+ ~membership_criteria:(fun _ -> true)
+ ~provenance:Relation.Provenance.Undefined
+ ~lineage:(Relation.Lineage.Base "users")
+ in
+ let rel2 =
+ Relation.make ~hash:(Some "h2") ~name:"orders" ~schema:Schema.empty
+ ~tree:(Some Merkle.empty) ~constraints:None
+ ~cardinality:(Conventions.Cardinality.Finite 0) ~generator:None
+ ~membership_criteria:(fun _ -> true)
+ ~provenance:Relation.Provenance.Undefined
+ ~lineage:(Relation.Lineage.Base "orders")
in
let db = Management.Database.add_relation db ~relation:rel1 in
let db = Management.Database.add_relation db ~relation:rel2 in
@@ -243,780 +217,1039 @@ let with_storage f =
match Management.Physical.Memory.create () with
| Error _ -> assert false
| Ok storage ->
- let result = f storage in
- Management.Physical.Memory.close storage;
- result
+ let result = f storage in
+ Management.Physical.Memory.close storage;
+ result
let%test_unit "manipulation: create database" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"my_db" with
- | Error _ -> assert false
- | Ok db -> assert (db.name = "my_db"))
+ match Manipulation.Memory.create_database storage ~name:"my_db" with
+ | Error _ -> assert false
+ | Ok db -> assert (db.name = "my_db"))
let%test_unit "manipulation: create relation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "name" "string"
- |> Schema.add "age" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- assert (relation.name = "users");
- assert (relation.schema = schema);
- assert (Management.Database.has_relation db "users"))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema =
+ Schema.empty |> Schema.add "name" "string" |> Schema.add "age" "integer"
+ in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) ->
+ assert (relation.name = "users");
+ assert (relation.schema = schema);
+ assert (Management.Database.has_relation db "users"))
let%test_unit "manipulation: create relation already exists" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false
- | Ok (db, _) ->
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error (Manipulation.RelationAlreadyExists _) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> (
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error (Manipulation.RelationAlreadyExists _) -> ()
+ | _ -> assert false))
let%test_unit "manipulation: retract relation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false
- | Ok (db, _) ->
- assert (Management.Database.has_relation db "users");
- match Manipulation.Memory.retract_relation storage db ~name:"users" with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
| Error _ -> assert false
- | Ok db ->
- assert (not (Management.Database.has_relation db "users")))
+ | Ok (db, _) -> (
+ assert (Management.Database.has_relation db "users");
+ match
+ Manipulation.Memory.retract_relation storage db ~name:"users"
+ with
+ | Error _ -> assert false
+ | Ok db -> assert (not (Management.Database.has_relation db "users"))))
let%test_unit "manipulation: create tuple with storage" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "name" "string" in
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "name" { Attribute.value = Obj.magic "Alice" } in
- let tuple : Tuple.materialized = { relation = "users"; attributes = attrs } in
- match Manipulation.Memory.create_tuple storage db relation tuple with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "name" "string" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
| Error _ -> assert false
- | Ok (_db, new_relation, _hash) ->
- assert (Manipulation.Memory.tuple_count new_relation = 1))
+ | Ok (db, relation) -> (
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "name"
+ { Attribute.value = Obj.magic "Alice" }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "users"; attributes = attrs }
+ in
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok (_db, new_relation, _hash) ->
+ assert (Manipulation.Memory.tuple_count new_relation = 1)))
let%test_unit "manipulation: create and load tuple" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "value" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"numbers" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "value" { Attribute.value = Obj.magic 42 } in
- let tuple : Tuple.materialized = { relation = "numbers"; attributes = attrs } in
- match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok (_db, _relation, tuple_hash) ->
- (* Now load the tuple back from storage *)
- match Manipulation.Memory.load_tuple storage tuple_hash with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
| Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded_tuple) ->
- assert (loaded_tuple.relation = "numbers");
- let loaded_value = Tuple.AttributeMap.find "value" loaded_tuple.attributes in
- assert ((Obj.magic loaded_value.Attribute.value : int) = 42))
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "value" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"numbers" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) -> (
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "value" { Attribute.value = Obj.magic 42 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "numbers"; attributes = attrs }
+ in
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok (_db, _relation, tuple_hash) -> (
+ (* Now load the tuple back from storage *)
+ match Manipulation.Memory.load_tuple storage tuple_hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded_tuple) ->
+ assert (loaded_tuple.relation = "numbers");
+ let loaded_value =
+ Tuple.AttributeMap.find "value" loaded_tuple.attributes
+ in
+ assert ((Obj.magic loaded_value.Attribute.value : int) = 42))))
let%test_unit "manipulation: create multiple tuples with storage" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "id" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"items" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let make_tuple id =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.magic id } in
- ({ Tuple.relation = "items"; attributes = attrs } : Tuple.materialized)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- let tuples = [make_tuple 1; make_tuple 2; make_tuple 3] in
- match Manipulation.Memory.create_tuples storage db relation tuples with
+ let schema = Schema.empty |> Schema.add "id" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"items" ~schema
+ with
| Error _ -> assert false
- | Ok (_db, new_relation, hashes) ->
- assert (Manipulation.Memory.tuple_count new_relation = 3);
- assert (List.length hashes = 3))
+ | Ok (db, relation) -> (
+ let make_tuple id =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.magic id }
+ in
+ ({ Tuple.relation = "items"; attributes = attrs }
+ : Tuple.materialized)
+ in
+ let tuples = [ make_tuple 1; make_tuple 2; make_tuple 3 ] in
+ match
+ Manipulation.Memory.create_tuples storage db relation tuples
+ with
+ | Error _ -> assert false
+ | Ok (_db, new_relation, hashes) ->
+ assert (Manipulation.Memory.tuple_count new_relation = 3);
+ assert (List.length hashes = 3)))
let%test_unit "manipulation: load multiple tuples" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "n" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"test" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let make_tuple n =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n } in
- ({ Tuple.relation = "test"; attributes = attrs } : Tuple.materialized)
- in
- match Manipulation.Memory.create_tuples storage db relation [make_tuple 10; make_tuple 20] with
- | Error _ -> assert false
- | Ok (_db, _relation, hashes) ->
- match Manipulation.Memory.load_tuples storage hashes with
- | Error _ -> assert false
- | Ok loaded ->
- assert (List.length loaded = 2);
- let values = List.map (fun t ->
- let attr = Tuple.AttributeMap.find "n" t.Tuple.attributes in
- (Obj.magic attr.Attribute.value : int)
- ) loaded in
- assert (List.mem 10 values);
- assert (List.mem 20 values))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "n" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"test" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) -> (
+ let make_tuple n =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n }
+ in
+ ({ Tuple.relation = "test"; attributes = attrs }
+ : Tuple.materialized)
+ in
+ match
+ Manipulation.Memory.create_tuples storage db relation
+ [ make_tuple 10; make_tuple 20 ]
+ with
+ | Error _ -> assert false
+ | Ok (_db, _relation, hashes) -> (
+ match Manipulation.Memory.load_tuples storage hashes with
+ | Error _ -> assert false
+ | Ok loaded ->
+ assert (List.length loaded = 2);
+ let values =
+ List.map
+ (fun t ->
+ let attr =
+ Tuple.AttributeMap.find "n" t.Tuple.attributes
+ in
+ (Obj.magic attr.Attribute.value : int))
+ loaded
+ in
+ assert (List.mem 10 values);
+ assert (List.mem 20 values))))
let%test_unit "manipulation: retract tuple" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "value" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"numbers" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "value" { Attribute.value = Obj.magic 42 } in
- let tuple : Tuple.materialized = { relation = "numbers"; attributes = attrs } in
- match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok (db, relation, tuple_hash) ->
- assert (Manipulation.Memory.tuple_count relation = 1);
- match Manipulation.Memory.retract_tuple storage db relation ~tuple_hash with
- | Error _ -> assert false
- | Ok (_db, relation) ->
- assert (Manipulation.Memory.tuple_count relation = 0);
- (* Tuple data still exists in storage (append-only) *)
- match Manipulation.Memory.load_tuple storage tuple_hash with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "value" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"numbers" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) -> (
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "value" { Attribute.value = Obj.magic 42 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "numbers"; attributes = attrs }
+ in
+ match Manipulation.Memory.create_tuple storage db relation tuple with
| Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some _) -> () (* Data is still there *))
+ | Ok (db, relation, tuple_hash) -> (
+ assert (Manipulation.Memory.tuple_count relation = 1);
+ match
+ Manipulation.Memory.retract_tuple storage db relation
+ ~tuple_hash
+ with
+ | Error _ -> assert false
+ | Ok (_db, relation) -> (
+ assert (Manipulation.Memory.tuple_count relation = 0);
+ (* Tuple data still exists in storage (append-only) *)
+ match Manipulation.Memory.load_tuple storage tuple_hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some _) -> () (* Data is still there *)))))
let%test_unit "manipulation: tuple hashes" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- match Manipulation.Memory.create_relation storage db ~name:"test" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let make_tuple n =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n } in
- ({ Tuple.relation = "test"; attributes = attrs } : Tuple.materialized)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- match Manipulation.Memory.create_tuples storage db relation [make_tuple 1; make_tuple 2] with
+ let schema = Schema.empty in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"test" ~schema
+ with
| Error _ -> assert false
- | Ok (_db, relation, _) ->
- let hashes = Manipulation.Memory.tuple_hashes relation in
- assert (List.length hashes = 2))
+ | Ok (db, relation) -> (
+ let make_tuple n =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n }
+ in
+ ({ Tuple.relation = "test"; attributes = attrs }
+ : Tuple.materialized)
+ in
+ match
+ Manipulation.Memory.create_tuples storage db relation
+ [ make_tuple 1; make_tuple 2 ]
+ with
+ | Error _ -> assert false
+ | Ok (_db, relation, _) ->
+ let hashes = Manipulation.Memory.tuple_hashes relation in
+ assert (List.length hashes = 2)))
let%test_unit "manipulation: clear relation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- match Manipulation.Memory.create_relation storage db ~name:"test" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let make_tuple n =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n } in
- ({ Tuple.relation = "test"; attributes = attrs } : Tuple.materialized)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- match Manipulation.Memory.create_tuples storage db relation [make_tuple 1; make_tuple 2; make_tuple 3] with
+ let schema = Schema.empty in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"test" ~schema
+ with
| Error _ -> assert false
- | Ok (db, relation, _) ->
- assert (Manipulation.Memory.tuple_count relation = 3);
- match Manipulation.Memory.clear_relation storage db relation with
- | Error _ -> assert false
- | Ok (_db, relation) ->
- assert (Manipulation.Memory.tuple_count relation = 0))
+ | Ok (db, relation) -> (
+ let make_tuple n =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "n" { Attribute.value = Obj.magic n }
+ in
+ ({ Tuple.relation = "test"; attributes = attrs }
+ : Tuple.materialized)
+ in
+ match
+ Manipulation.Memory.create_tuples storage db relation
+ [ make_tuple 1; make_tuple 2; make_tuple 3 ]
+ with
+ | Error _ -> assert false
+ | Ok (db, relation, _) -> (
+ assert (Manipulation.Memory.tuple_count relation = 3);
+ match Manipulation.Memory.clear_relation storage db relation with
+ | Error _ -> assert false
+ | Ok (_db, relation) ->
+ assert (Manipulation.Memory.tuple_count relation = 0))))
let%test_unit "manipulation: duplicate tuple rejected" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "x" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"test" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 } in
- let tuple : Tuple.materialized = { relation = "test"; attributes = attrs } in
- match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok (db, relation, _) ->
- (* Try to insert the same tuple again *)
- match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error (Manipulation.DuplicateTuple _) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "x" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"test" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) -> (
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "test"; attributes = attrs }
+ in
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok (db, relation, _) -> (
+ (* Try to insert the same tuple again *)
+ match
+ Manipulation.Memory.create_tuple storage db relation tuple
+ with
+ | Error (Manipulation.DuplicateTuple _) -> ()
+ | _ -> assert false)))
let%test_unit "manipulation: tuple_exists check" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- match Manipulation.Memory.create_relation storage db ~name:"test" ~schema with
- | Error _ -> assert false
- | Ok (db, relation) ->
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "v" { Attribute.value = Obj.magic 99 } in
- let tuple : Tuple.materialized = { relation = "test"; attributes = attrs } in
- let tuple_hash = Manipulation.hash_tuple tuple in
- assert (not (Manipulation.Memory.tuple_exists relation tuple_hash));
- match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok (_db, relation, hash) ->
- assert (hash = tuple_hash);
- assert (Manipulation.Memory.tuple_exists relation tuple_hash))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"test" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, relation) -> (
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "v" { Attribute.value = Obj.magic 99 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "test"; attributes = attrs }
+ in
+ let tuple_hash = Manipulation.hash_tuple tuple in
+ assert (not (Manipulation.Memory.tuple_exists relation tuple_hash));
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok (_db, relation, hash) ->
+ assert (hash = tuple_hash);
+ assert (Manipulation.Memory.tuple_exists relation tuple_hash)))
let%test_unit "manipulation: hash_tuple deterministic" =
- let attrs = Tuple.AttributeMap.empty
+ let attrs =
+ Tuple.AttributeMap.empty
|> Tuple.AttributeMap.add "a" { Attribute.value = Obj.magic 1 }
- |> Tuple.AttributeMap.add "b" { Attribute.value = Obj.magic 2 } in
+ |> Tuple.AttributeMap.add "b" { Attribute.value = Obj.magic 2 }
+ in
let tuple : Tuple.materialized = { relation = "test"; attributes = attrs } in
let hash1 = Manipulation.hash_tuple tuple in
let hash2 = Manipulation.hash_tuple tuple in
assert (hash1 = hash2)
let%test_unit "manipulation: different tuples different hashes" =
- let tuple1 : Tuple.materialized = {
- relation = "test";
- attributes = Tuple.AttributeMap.singleton "x" { Attribute.value = Obj.magic 1 }
- } in
- let tuple2 : Tuple.materialized = {
- relation = "test";
- attributes = Tuple.AttributeMap.singleton "x" { Attribute.value = Obj.magic 2 }
- } in
+ let tuple1 : Tuple.materialized =
+ {
+ relation = "test";
+ attributes =
+ Tuple.AttributeMap.singleton "x" { Attribute.value = Obj.magic 1 };
+ }
+ in
+ let tuple2 : Tuple.materialized =
+ {
+ relation = "test";
+ attributes =
+ Tuple.AttributeMap.singleton "x" { Attribute.value = Obj.magic 2 };
+ }
+ in
let hash1 = Manipulation.hash_tuple tuple1 in
let hash2 = Manipulation.hash_tuple tuple2 in
assert (hash1 <> hash2)
let%test_unit "manipulation: get_relation from database" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "id" "integer" in
- match Manipulation.Memory.create_relation storage db ~name:"items" ~schema with
- | Error _ -> assert false
- | Ok (db, _relation) ->
- (* Now get the relation from the database *)
- match Manipulation.Memory.get_relation db ~name:"items" with
- | None -> assert false
- | Some rel ->
- assert (rel.name = "items");
- assert (rel.schema = schema))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "id" "integer" in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"items" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _relation) -> (
+ (* Now get the relation from the database *)
+ match Manipulation.Memory.get_relation db ~name:"items" with
+ | None -> assert false
+ | Some rel ->
+ assert (rel.name = "items");
+ assert (rel.schema = schema)))
let%test_unit "schema: persisted and loaded correctly" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty
- |> Schema.add "id" "integer"
- |> Schema.add "name" "string"
- |> Schema.add "email" "string" in
- match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false
- | Ok (db, _relation) ->
- (* Get the relation hash *)
- let rel_hash = Option.get (Management.Database.get_relation_hash db "users") in
- (* Load the relation from storage *)
- match Manipulation.Memory.load_relation storage rel_hash with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema =
+ Schema.empty |> Schema.add "id" "integer" |> Schema.add "name" "string"
+ |> Schema.add "email" "string"
+ in
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
| Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded_rel) ->
- (* Verify schema was persisted and loaded correctly *)
- assert (loaded_rel.schema = schema);
- assert (List.mem ("id", "integer") loaded_rel.schema);
- assert (List.mem ("name", "string") loaded_rel.schema);
- assert (List.mem ("email", "string") loaded_rel.schema))
+ | Ok (db, _relation) -> (
+ (* Get the relation hash *)
+ let rel_hash =
+ Option.get (Management.Database.get_relation_hash db "users")
+ in
+ (* Load the relation from storage *)
+ match Manipulation.Memory.load_relation storage rel_hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded_rel) ->
+ (* Verify schema was persisted and loaded correctly *)
+ assert (loaded_rel.schema = schema);
+ assert (List.mem ("id", "integer") loaded_rel.schema);
+ assert (List.mem ("name", "string") loaded_rel.schema);
+ assert (List.mem ("email", "string") loaded_rel.schema)))
let%test_unit "catalog: create_database seeds 6 catalog relations" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let catalog_names = Prelude.Catalog.catalog_relation_names in
- List.iter (fun name ->
- assert (Management.Database.has_relation db name)
- ) catalog_names)
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let catalog_names = Prelude.Catalog.catalog_relation_names in
+ List.iter
+ (fun name -> assert (Management.Database.has_relation db name))
+ catalog_names)
let%test_unit "catalog: sakura:relation contains all 6 catalog names" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let rel = match Manipulation.Memory.get_relation db ~name:"sakura:relation" with
- | None -> assert false
- | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count rel = 6))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let rel =
+ match
+ Manipulation.Memory.get_relation db ~name:"sakura:relation"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 6))
let%test_unit "catalog: sakura:on contains insert, update, delete" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let on_rel = match Manipulation.Memory.get_relation db ~name:"sakura:on" with
- | None -> assert false
- | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count on_rel = 3))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let on_rel =
+ match Manipulation.Memory.get_relation db ~name:"sakura:on" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count on_rel = 3))
let%test_unit "catalog: sakura:timing contains immediate, deferred" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let timing_rel = match Manipulation.Memory.get_relation db ~name:"sakura:timing" with
- | None -> assert false
- | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count timing_rel = 2))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let timing_rel =
+ match Manipulation.Memory.get_relation db ~name:"sakura:timing" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count timing_rel = 2))
let%test_unit "catalog: sakura:domain seeded with 4 prelude domains" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let dom_rel = match Manipulation.Memory.get_relation db ~name:"sakura:domain" with
- | None -> assert false
- | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count dom_rel = 4))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let dom_rel =
+ match Manipulation.Memory.get_relation db ~name:"sakura:domain" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count dom_rel = 4))
let%test_unit "catalog: create_relation updates sakura:relation" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let schema = Schema.empty |> Schema.add "id" "natural" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"employees" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let rel = match Manipulation.Memory.get_relation db ~name:"sakura:relation" with
- | None -> assert false | Some r -> r
- in
- (* 6 catalog relations + 1 user relation *)
- assert (Manipulation.Memory.tuple_count rel = 7))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let schema = Schema.empty |> Schema.add "id" "natural" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"employees"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let rel =
+ match
+ Manipulation.Memory.get_relation db ~name:"sakura:relation"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ (* 6 catalog relations + 1 user relation *)
+ assert (Manipulation.Memory.tuple_count rel = 7))
let%test_unit "catalog: create_relation updates sakura:attribute" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let schema = Schema.empty |> Schema.add "id" "natural" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"employees" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let attr_rel = match Manipulation.Memory.get_relation db ~name:"sakura:attribute" with
- | None -> assert false | Some r -> r
- in
- (* catalog attribute tuples (sum of schema sizes of all 6 catalog relations) + 1 for employees *)
- (* sakura:relation: 1, sakura:domain: 1, sakura:attribute: 3, sakura:constraint: 2,
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let schema = Schema.empty |> Schema.add "id" "natural" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"employees"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let attr_rel =
+ match
+ Manipulation.Memory.get_relation db ~name:"sakura:attribute"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ (* catalog attribute tuples (sum of schema sizes of all 6 catalog relations) + 1 for employees *)
+ (* sakura:relation: 1, sakura:domain: 1, sakura:attribute: 3, sakura:constraint: 2,
sakura:on: 1, sakura:timing: 1 => 9 catalog attrs + 1 for employees = 10 *)
- let count = Manipulation.Memory.tuple_count attr_rel in
- assert (count = 10))
+ let count = Manipulation.Memory.tuple_count attr_rel in
+ assert (count = 10))
let%test_unit "catalog: retract_relation removes from sakura:relation" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let schema = Schema.empty |> Schema.add "id" "natural" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"employees" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Manipulation.Memory.retract_relation storage db ~name:"employees" with
- | Error _ -> assert false | Ok db -> db
- in
- let rel = match Manipulation.Memory.get_relation db ~name:"sakura:relation" with
- | None -> assert false | Some r -> r
- in
- (* Back to 6 catalog relations *)
- assert (Manipulation.Memory.tuple_count rel = 6))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let schema = Schema.empty |> Schema.add "id" "natural" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"employees"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.retract_relation storage db ~name:"employees"
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match
+ Manipulation.Memory.get_relation db ~name:"sakura:relation"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ (* Back to 6 catalog relations *)
+ assert (Manipulation.Memory.tuple_count rel = 6))
let%test_unit "catalog: register_constraint inserts into sakura:constraint" =
with_storage (fun storage ->
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db ->
- let schema = Schema.empty |> Schema.add "id" "natural" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"orders" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Manipulation.Memory.register_constraint storage db
- ~constraint_name:"orders_id_positive"
- ~relation_name:"orders"
- ~body:(Constraint.And []) with
- | Error _ -> assert false | Ok db -> db
- in
- let con_rel = match Manipulation.Memory.get_relation db ~name:"sakura:constraint" with
- | None -> assert false | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count con_rel = 1))
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db ->
+ let schema = Schema.empty |> Schema.add "id" "natural" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"orders"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"orders_id_positive" ~relation_name:"orders"
+ ~body:(Constraint.And [])
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let con_rel =
+ match
+ Manipulation.Memory.get_relation db ~name:"sakura:constraint"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count con_rel = 1))
let%test_unit "integration: full workflow with storage" =
with_storage (fun storage ->
- (* Create database *)
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
-
- (* Create products relation *)
- let schema = Schema.empty
- |> Schema.add "id" "integer"
- |> Schema.add "name" "string"
- |> Schema.add "price" "integer" in
+ (* Create database *)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
- let (db, products) = match Manipulation.Memory.create_relation storage db ~name:"products" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ (* Create products relation *)
+ let schema =
+ Schema.empty |> Schema.add "id" "integer" |> Schema.add "name" "string"
+ |> Schema.add "price" "integer"
+ in
- (* Insert some products *)
- let make_product id name price =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.magic id }
- |> Tuple.AttributeMap.add "name" { Attribute.value = Obj.magic name }
- |> Tuple.AttributeMap.add "price" { Attribute.value = Obj.magic price } in
- ({ Tuple.relation = "products"; attributes = attrs } : Tuple.materialized)
- in
+ let db, products =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"products"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- let tuples = [
- make_product 1 "Apple" 100;
- make_product 2 "Banana" 50;
- make_product 3 "Cherry" 200;
- ] in
+ (* Insert some products *)
+ let make_product id name price =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.magic id }
+ |> Tuple.AttributeMap.add "name" { Attribute.value = Obj.magic name }
+ |> Tuple.AttributeMap.add "price"
+ { Attribute.value = Obj.magic price }
+ in
+ ({ Tuple.relation = "products"; attributes = attrs }
+ : Tuple.materialized)
+ in
- let (db, products, hashes) = match Manipulation.Memory.create_tuples storage db products tuples with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let tuples =
+ [
+ make_product 1 "Apple" 100;
+ make_product 2 "Banana" 50;
+ make_product 3 "Cherry" 200;
+ ]
+ in
- assert (Manipulation.Memory.tuple_count products = 3);
- assert (List.length hashes = 3);
+ let db, products, hashes =
+ match Manipulation.Memory.create_tuples storage db products tuples with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Verify we can load all tuples back *)
- (match Manipulation.Memory.load_tuples storage hashes with
- | Error _ -> assert false
- | Ok loaded -> assert (List.length loaded = 3));
+ assert (Manipulation.Memory.tuple_count products = 3);
+ assert (List.length hashes = 3);
- (* Delete one product *)
- let banana_hash = Manipulation.hash_tuple (make_product 2 "Banana" 50) in
- let (_db, products) = match Manipulation.Memory.retract_tuple storage db products ~tuple_hash:banana_hash with
+ (* Verify we can load all tuples back *)
+ (match Manipulation.Memory.load_tuples storage hashes with
| Error _ -> assert false
- | Ok x -> x
- in
+ | Ok loaded -> assert (List.length loaded = 3));
+
+ (* Delete one product *)
+ let banana_hash = Manipulation.hash_tuple (make_product 2 "Banana" 50) in
+ let _db, products =
+ match
+ Manipulation.Memory.retract_tuple storage db products
+ ~tuple_hash:banana_hash
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- assert (Manipulation.Memory.tuple_count products = 2);
+ assert (Manipulation.Memory.tuple_count products = 2);
- (* Verify remaining hashes *)
- let remaining_hashes = Manipulation.Memory.tuple_hashes products in
- assert (List.length remaining_hashes = 2);
- assert (not (List.mem banana_hash remaining_hashes));
+ (* Verify remaining hashes *)
+ let remaining_hashes = Manipulation.Memory.tuple_hashes products in
+ assert (List.length remaining_hashes = 2);
+ assert (not (List.mem banana_hash remaining_hashes));
- (* But banana data is still in storage (append-only) *)
- match Manipulation.Memory.load_tuple storage banana_hash with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded) ->
- let name_attr = Tuple.AttributeMap.find "name" loaded.attributes in
- assert ((Obj.magic name_attr.Attribute.value : string) = "Banana"))
+ (* But banana data is still in storage (append-only) *)
+ match Manipulation.Memory.load_tuple storage banana_hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded) ->
+ let name_attr = Tuple.AttributeMap.find "name" loaded.attributes in
+ assert ((Obj.magic name_attr.Attribute.value : string) = "Banana"))
let%test_unit "integration: database history tracking" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"versioned" with
- | Error _ -> assert false | Ok db -> db
- in
- let initial_hash = db.hash in
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"versioned" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let initial_hash = db.hash in
- let schema = Schema.empty in
- let (db, _) = match Manipulation.Memory.create_relation storage db ~name:"rel1" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let schema = Schema.empty in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"rel1" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* After first change, initial hash should be in history *)
- assert (db.hash <> initial_hash);
+ (* After first change, initial hash should be in history *)
+ assert (db.hash <> initial_hash);
- let hash_after_rel1 = db.hash in
+ let hash_after_rel1 = db.hash in
- let (db, _) = match Manipulation.Memory.create_relation storage db ~name:"rel2" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"rel2" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* After second change, previous hash should be in history *)
- assert (db.hash <> hash_after_rel1);
- assert (List.mem hash_after_rel1 db.history))
+ (* After second change, previous hash should be in history *)
+ assert (db.hash <> hash_after_rel1);
+ assert (List.mem hash_after_rel1 db.history))
let%test_unit "integration: hash bubbles up correctly" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
- let (db, relation) = match Manipulation.Memory.create_relation storage db ~name:"items" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db, relation =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"items" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- let db_hash_before = db.hash in
- let rel_hash_before = relation.hash in
+ let db_hash_before = db.hash in
+ let rel_hash_before = relation.hash in
- (* Insert a tuple *)
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 } in
- let tuple : Tuple.materialized = { relation = "items"; attributes = attrs } in
+ (* Insert a tuple *)
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "items"; attributes = attrs }
+ in
- let (db, relation, _) = match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db, relation, _ =
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Relation hash should change *)
- assert (relation.hash <> rel_hash_before);
+ (* Relation hash should change *)
+ assert (relation.hash <> rel_hash_before);
- (* Database hash should change *)
- assert (db.hash <> db_hash_before);
+ (* Database hash should change *)
+ assert (db.hash <> db_hash_before);
- (* Old database hash should be in history *)
- assert (List.mem db_hash_before db.history);
+ (* Old database hash should be in history *)
+ assert (List.mem db_hash_before db.history);
- (* Database's merkle tree should contain the new relation hash *)
- match relation.hash with
- | None -> assert false
- | Some rel_hash ->
- assert (Merkle.member rel_hash db.tree))
+ (* Database's merkle tree should contain the new relation hash *)
+ match relation.hash with
+ | None -> assert false
+ | Some rel_hash -> assert (Merkle.member rel_hash db.tree))
let%test_unit "branching: load database from historical hash" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
- (* Create relation and add a tuple *)
- let (db, relation) = match Manipulation.Memory.create_relation storage db ~name:"items" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ (* Create relation and add a tuple *)
+ let db, relation =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"items" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 } in
- let tuple : Tuple.materialized = { relation = "items"; attributes = attrs } in
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 1 }
+ in
+ let tuple : Tuple.materialized =
+ { relation = "items"; attributes = attrs }
+ in
- let (db, _relation, _) = match Manipulation.Memory.create_tuple storage db relation tuple with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db, _relation, _ =
+ match Manipulation.Memory.create_tuple storage db relation tuple with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Capture hash H1 - database with 1 tuple *)
- let h1 = db.hash in
+ (* Capture hash H1 - database with 1 tuple *)
+ let h1 = db.hash in
- (* Add another tuple *)
- let attrs2 = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 2 } in
- let tuple2 : Tuple.materialized = { relation = "items"; attributes = attrs2 } in
+ (* Add another tuple *)
+ let attrs2 =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "x" { Attribute.value = Obj.magic 2 }
+ in
+ let tuple2 : Tuple.materialized =
+ { relation = "items"; attributes = attrs2 }
+ in
- (* Get relation from database - it's now integrated! *)
- let relation = match Manipulation.Memory.get_relation db ~name:"items" with
- | None -> assert false
- | Some r -> r
- in
+ (* Get relation from database - it's now integrated! *)
+ let relation =
+ match Manipulation.Memory.get_relation db ~name:"items" with
+ | None -> assert false
+ | Some r -> r
+ in
- let (db, _relation, _) = match Manipulation.Memory.create_tuple storage db relation tuple2 with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db, _relation, _ =
+ match Manipulation.Memory.create_tuple storage db relation tuple2 with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- let h2 = db.hash in
- assert (h1 <> h2);
+ let h2 = db.hash in
+ assert (h1 <> h2);
- (* Now load H1 from storage - should get database state with 1 tuple *)
- match Manipulation.Memory.load_database storage h1 with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded_db) ->
- assert (loaded_db.hash = h1);
- assert (loaded_db.name = "test");
- (* The relation is now loaded directly in the database *)
- match Manipulation.Memory.get_relation loaded_db ~name:"items" with
- | None -> assert false
- | Some loaded_rel ->
- (* Should have 1 tuple *)
- assert (Manipulation.Memory.tuple_count loaded_rel = 1))
+ (* Now load H1 from storage - should get database state with 1 tuple *)
+ match Manipulation.Memory.load_database storage h1 with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded_db) -> (
+ assert (loaded_db.hash = h1);
+ assert (loaded_db.name = "test");
+ (* The relation is now loaded directly in the database *)
+ match Manipulation.Memory.get_relation loaded_db ~name:"items" with
+ | None -> assert false
+ | Some loaded_rel ->
+ (* Should have 1 tuple *)
+ assert (Manipulation.Memory.tuple_count loaded_rel = 1)))
let%test_unit "branching: branch from historical state" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
- (* Create relation *)
- let (db, relation) = match Manipulation.Memory.create_relation storage db ~name:"data" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ (* Create relation *)
+ let db, relation =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"data" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Add tuple A *)
- let tuple_a : Tuple.materialized = {
- relation = "data";
- attributes = Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "A" }
- } in
- let (db, relation, _) = match Manipulation.Memory.create_tuple storage db relation tuple_a with
- | Error _ -> assert false
- | Ok x -> x
- in
+ (* Add tuple A *)
+ let tuple_a : Tuple.materialized =
+ {
+ relation = "data";
+ attributes =
+ Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "A" };
+ }
+ in
+ let db, relation, _ =
+ match Manipulation.Memory.create_tuple storage db relation tuple_a with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Capture H1 - state with tuple A *)
- let h1 = db.hash in
+ (* Capture H1 - state with tuple A *)
+ let h1 = db.hash in
- (* Continue on main branch: add tuple B *)
- let tuple_b : Tuple.materialized = {
- relation = "data";
- attributes = Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "B" }
- } in
- let (db_main, relation_main, _) = match Manipulation.Memory.create_tuple storage db relation tuple_b with
- | Error _ -> assert false
- | Ok x -> x
- in
- let h2 = db_main.hash in
+ (* Continue on main branch: add tuple B *)
+ let tuple_b : Tuple.materialized =
+ {
+ relation = "data";
+ attributes =
+ Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "B" };
+ }
+ in
+ let db_main, relation_main, _ =
+ match Manipulation.Memory.create_tuple storage db relation tuple_b with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let h2 = db_main.hash in
- (* Now branch from H1 and add tuple C instead *)
- let db_branch = match Manipulation.Memory.load_database storage h1 with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some d) -> d
- in
- (* Relation is now loaded directly in the database *)
- let relation_branch = match Manipulation.Memory.get_relation db_branch ~name:"data" with
- | None -> assert false
- | Some r -> r
- in
+ (* Now branch from H1 and add tuple C instead *)
+ let db_branch =
+ match Manipulation.Memory.load_database storage h1 with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some d) -> d
+ in
+ (* Relation is now loaded directly in the database *)
+ let relation_branch =
+ match Manipulation.Memory.get_relation db_branch ~name:"data" with
+ | None -> assert false
+ | Some r -> r
+ in
- let tuple_c : Tuple.materialized = {
- relation = "data";
- attributes = Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "C" }
- } in
- let (db_branch, _relation_branch, _) = match Manipulation.Memory.create_tuple storage db_branch relation_branch tuple_c with
- | Error _ -> assert false
- | Ok x -> x
- in
- let h3 = db_branch.hash in
+ let tuple_c : Tuple.materialized =
+ {
+ relation = "data";
+ attributes =
+ Tuple.AttributeMap.singleton "v" { Attribute.value = Obj.magic "C" };
+ }
+ in
+ let db_branch, _relation_branch, _ =
+ match
+ Manipulation.Memory.create_tuple storage db_branch relation_branch
+ tuple_c
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let h3 = db_branch.hash in
- (* Verify: H1, H2, H3 are all different *)
- assert (h1 <> h2);
- assert (h1 <> h3);
- assert (h2 <> h3);
+ (* Verify: H1, H2, H3 are all different *)
+ assert (h1 <> h2);
+ assert (h1 <> h3);
+ assert (h2 <> h3);
- (* H2 (main branch) has tuples A and B *)
- assert (Manipulation.Memory.tuple_count relation_main = 2);
+ (* H2 (main branch) has tuples A and B *)
+ assert (Manipulation.Memory.tuple_count relation_main = 2);
- (* H1 is still loadable and unchanged *)
- match Manipulation.Memory.load_database storage h1 with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded_h1) ->
- match Manipulation.Memory.get_relation loaded_h1 ~name:"data" with
- | None -> assert false
- | Some r ->
- assert (Manipulation.Memory.tuple_count r = 1))
+ (* H1 is still loadable and unchanged *)
+ match Manipulation.Memory.load_database storage h1 with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded_h1) -> (
+ match Manipulation.Memory.get_relation loaded_h1 ~name:"data" with
+ | None -> assert false
+ | Some r -> assert (Manipulation.Memory.tuple_count r = 1)))
let%test_unit "branching: full reconstruction from hash" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
-
- (* Create relation *)
- let (db, relation) = match Manipulation.Memory.create_relation storage db ~name:"products" ~schema with
- | Error _ -> assert false
- | Ok x -> x
- in
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
+
+ (* Create relation *)
+ let db, relation =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"products"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- (* Add products *)
- let make_product name =
- { Tuple.relation = "products";
- attributes = Tuple.AttributeMap.singleton "name" { Attribute.value = Obj.magic name } }
- in
- let (db, _relation, _) = match Manipulation.Memory.create_tuples storage db relation
- [make_product "Apple"; make_product "Banana"; make_product "Cherry"] with
- | Error _ -> assert false
- | Ok x -> x
- in
+ (* Add products *)
+ let make_product name =
+ {
+ Tuple.relation = "products";
+ attributes =
+ Tuple.AttributeMap.singleton "name"
+ { Attribute.value = Obj.magic name };
+ }
+ in
+ let db, _relation, _ =
+ match
+ Manipulation.Memory.create_tuples storage db relation
+ [
+ make_product "Apple"; make_product "Banana"; make_product "Cherry";
+ ]
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
- let saved_hash = db.hash in
+ let saved_hash = db.hash in
- (* Now reconstruct everything from just the hash *)
- match Manipulation.Memory.load_database storage saved_hash with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some loaded_db) ->
- (* Relation is now loaded directly in the database *)
- match Manipulation.Memory.get_relation loaded_db ~name:"products" with
- | None -> assert false
- | Some loaded_rel ->
- (* Get tuple hashes from relation *)
- let tuple_hashes = Manipulation.Memory.tuple_hashes loaded_rel in
- assert (List.length tuple_hashes = 3);
-
- (* Load all tuples *)
- match Manipulation.Memory.load_tuples storage tuple_hashes with
- | Error _ -> assert false
- | Ok tuples ->
- assert (List.length tuples = 3);
- let names = List.map (fun t ->
- let attr = Tuple.AttributeMap.find "name" t.Tuple.attributes in
- (Obj.magic attr.Attribute.value : string)
- ) tuples in
- assert (List.mem "Apple" names);
- assert (List.mem "Banana" names);
- assert (List.mem "Cherry" names))
+ (* Now reconstruct everything from just the hash *)
+ match Manipulation.Memory.load_database storage saved_hash with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some loaded_db) -> (
+ (* Relation is now loaded directly in the database *)
+ match Manipulation.Memory.get_relation loaded_db ~name:"products" with
+ | None -> assert false
+ | Some loaded_rel -> (
+ (* Get tuple hashes from relation *)
+ let tuple_hashes = Manipulation.Memory.tuple_hashes loaded_rel in
+ assert (List.length tuple_hashes = 3);
+
+ (* Load all tuples *)
+ match Manipulation.Memory.load_tuples storage tuple_hashes with
+ | Error _ -> assert false
+ | Ok tuples ->
+ assert (List.length tuples = 3);
+ let names =
+ List.map
+ (fun t ->
+ let attr =
+ Tuple.AttributeMap.find "name" t.Tuple.attributes
+ in
+ (Obj.magic attr.Attribute.value : string))
+ tuples
+ in
+ assert (List.mem "Apple" names);
+ assert (List.mem "Banana" names);
+ assert (List.mem "Cherry" names))))
(* Helper: build a materialized tuple with int attributes *)
let make_int_tuple relation pairs =
let attributes =
- List.fold_left (fun acc (k, v) ->
- Tuple.AttributeMap.add k { Attribute.value = Obj.repr (v : int) } acc)
+ List.fold_left
+ (fun acc (k, v) ->
+ Tuple.AttributeMap.add k { Attribute.value = Obj.repr (v : int) } acc)
Tuple.AttributeMap.empty pairs
in
(Tuple.Materialized { Tuple.relation; attributes } : Tuple.t)
@@ -1024,289 +1257,429 @@ let make_int_tuple relation pairs =
(* Helper: build a stored relation with one integer column and given values *)
let stored_relation_with_ints storage db ~name ~attr values =
let schema = Schema.empty |> Schema.add attr "integer" in
- let (db, rel) = match Manipulation.Memory.create_relation storage db ~name ~schema with
- | Error _ -> assert false | Ok x -> x
+ let db, rel =
+ match Manipulation.Memory.create_relation storage db ~name ~schema with
+ | Error _ -> assert false
+ | Ok x -> x
in
- let tuples = List.map (fun v ->
- let attributes =
- Tuple.AttributeMap.singleton attr { Attribute.value = Obj.repr (v : int) }
- in
- ({ Tuple.relation = name; attributes } : Tuple.materialized)
- ) values in
- let (db, rel, _) = match Manipulation.Memory.create_tuples storage db rel tuples with
- | Error _ -> assert false | Ok x -> x
+ let tuples =
+ List.map
+ (fun v ->
+ let attributes =
+ Tuple.AttributeMap.singleton attr
+ { Attribute.value = Obj.repr (v : int) }
+ in
+ ({ Tuple.relation = name; attributes } : Tuple.materialized))
+ values
+ in
+ let db, rel, _ =
+ match Manipulation.Memory.create_tuples storage db rel tuples with
+ | Error _ -> assert false
+ | Ok x -> x
in
(db, rel)
let%test_unit "algebra: const_relation single tuple" =
with_storage (fun storage ->
- let rel = Algebra.Memory.const_relation
- [ "x", Obj.repr (42 : int);
- "y", Obj.repr (99 : int) ] in
- match Algebra.Memory.materialize storage rel with
- | Error _ -> assert false
- | Ok [tup] ->
- let x: int = Obj.obj (Tuple.AttributeMap.find "x" tup.Tuple.attributes).Attribute.value in
- assert (x = 42)
- | Ok _ -> assert false)
+ let rel =
+ Algebra.Memory.const_relation
+ [ ("x", Obj.repr (42 : int)); ("y", Obj.repr (99 : int)) ]
+ in
+ match Algebra.Memory.materialize storage rel with
+ | Error _ -> assert false
+ | Ok [ tup ] ->
+ let x : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "x" tup.Tuple.attributes).Attribute.value
+ in
+ assert (x = 42)
+ | Ok _ -> assert false)
let%test_unit "algebra: select_fn with predicate" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (_, rel) = stored_relation_with_ints storage db ~name:"nums" ~attr:"n" [1;2;3;4;5] in
- let predicate = function
- | Tuple.Materialized t ->
- let v: int = Obj.obj (Tuple.AttributeMap.find "n" t.Tuple.attributes).Attribute.value in
- v > 3
- | _ -> false
- in
- match Algebra.Memory.select_fn storage predicate rel with
- | Error _ -> assert false
- | Ok result ->
- match Algebra.Memory.materialize storage result with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let _, rel =
+ stored_relation_with_ints storage db ~name:"nums" ~attr:"n"
+ [ 1; 2; 3; 4; 5 ]
+ in
+ let predicate = function
+ | Tuple.Materialized t ->
+ let v : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "n" t.Tuple.attributes).Attribute.value
+ in
+ v > 3
+ | _ -> false
+ in
+ match Algebra.Memory.select_fn storage predicate rel with
| Error _ -> assert false
- | Ok rows ->
- assert (List.length rows = 2);
- List.iter (fun t ->
- let v: int = Obj.obj (Tuple.AttributeMap.find "n" t.Tuple.attributes).Attribute.value in
- assert (v > 3)) rows)
+ | Ok result -> (
+ match Algebra.Memory.materialize storage result with
+ | Error _ -> assert false
+ | Ok rows ->
+ assert (List.length rows = 2);
+ List.iter
+ (fun t ->
+ let v : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "n" t.Tuple.attributes)
+ .Attribute.value
+ in
+ assert (v > 3))
+ rows))
let%test_unit "algebra: project restricts schema" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "a" "integer" |> Schema.add "b" "integer" in
- let (db, rel) = match Manipulation.Memory.create_relation storage db ~name:"ab" ~schema with
- | Error _ -> assert false | Ok x -> x
- in
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "a" { Attribute.value = Obj.repr (1 : int) }
- |> Tuple.AttributeMap.add "b" { Attribute.value = Obj.repr (2 : int) } in
- let (_, rel, _) = match Manipulation.Memory.create_tuple storage db rel
- { Tuple.relation = "ab"; attributes = attrs } with
- | Error _ -> assert false | Ok x -> x
- in
- match Algebra.Memory.project storage ["a"] rel with
- | Error _ -> assert false
- | Ok projected ->
- match Algebra.Memory.materialize storage projected with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema =
+ Schema.empty |> Schema.add "a" "integer" |> Schema.add "b" "integer"
+ in
+ let db, rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"ab" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "a" { Attribute.value = Obj.repr (1 : int) }
+ |> Tuple.AttributeMap.add "b" { Attribute.value = Obj.repr (2 : int) }
+ in
+ let _, rel, _ =
+ match
+ Manipulation.Memory.create_tuple storage db rel
+ { Tuple.relation = "ab"; attributes = attrs }
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ match Algebra.Memory.project storage [ "a" ] rel with
| Error _ -> assert false
- | Ok [t] ->
- assert (Tuple.AttributeMap.mem "a" t.Tuple.attributes);
- assert (not (Tuple.AttributeMap.mem "b" t.Tuple.attributes))
- | Ok _ -> assert false)
+ | Ok projected -> (
+ match Algebra.Memory.materialize storage projected with
+ | Error _ -> assert false
+ | Ok [ t ] ->
+ assert (Tuple.AttributeMap.mem "a" t.Tuple.attributes);
+ assert (not (Tuple.AttributeMap.mem "b" t.Tuple.attributes))
+ | Ok _ -> assert false))
let%test_unit "algebra: rename changes attr names" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (_, rel) = stored_relation_with_ints storage db ~name:"r" ~attr:"x" [7] in
- match Algebra.Memory.rename storage [("x", "y")] rel with
- | Error _ -> assert false
- | Ok renamed ->
- match Algebra.Memory.materialize storage renamed with
- | Error _ -> assert false
- | Ok [t] ->
- assert (not (Tuple.AttributeMap.mem "x" t.Tuple.attributes));
- assert (Tuple.AttributeMap.mem "y" t.Tuple.attributes);
- let v: int = Obj.obj (Tuple.AttributeMap.find "y" t.Tuple.attributes).Attribute.value in
- assert (v = 7)
- | Ok _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let _, rel =
+ stored_relation_with_ints storage db ~name:"r" ~attr:"x" [ 7 ]
+ in
+ match Algebra.Memory.rename storage [ ("x", "y") ] rel with
+ | Error _ -> assert false
+ | Ok renamed -> (
+ match Algebra.Memory.materialize storage renamed with
+ | Error _ -> assert false
+ | Ok [ t ] ->
+ assert (not (Tuple.AttributeMap.mem "x" t.Tuple.attributes));
+ assert (Tuple.AttributeMap.mem "y" t.Tuple.attributes);
+ let v : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "y" t.Tuple.attributes)
+ .Attribute.value
+ in
+ assert (v = 7)
+ | Ok _ -> assert false))
let%test_unit "algebra: equijoin merges matching tuples" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- (* left: {id=1, name="Alice"}, {id=2, name="Bob"} *)
- let schema_l = Schema.empty |> Schema.add "id" "integer" |> Schema.add "name" "string" in
- let (db, left) = match Manipulation.Memory.create_relation storage db ~name:"L" ~schema:schema_l with
- | Error _ -> assert false | Ok x -> x
- in
- let make_left id name =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.repr (id : int) }
- |> Tuple.AttributeMap.add "name" { Attribute.value = Obj.repr (name : string) } in
- ({ Tuple.relation = "L"; attributes = attrs } : Tuple.materialized)
- in
- let (db, left, _) = match Manipulation.Memory.create_tuples storage db left
- [make_left 1 "Alice"; make_left 2 "Bob"] with
- | Error _ -> assert false | Ok x -> x
- in
- (* right: {id=1, score=100}, {id=3, score=999} *)
- let schema_r = Schema.empty |> Schema.add "id" "integer" |> Schema.add "score" "integer" in
- let (db, right) = match Manipulation.Memory.create_relation storage db ~name:"R" ~schema:schema_r with
- | Error _ -> assert false | Ok x -> x
- in
- let make_right id score =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "id" { Attribute.value = Obj.repr (id : int) }
- |> Tuple.AttributeMap.add "score" { Attribute.value = Obj.repr (score : int) } in
- ({ Tuple.relation = "R"; attributes = attrs } : Tuple.materialized)
- in
- let (_, right, _) = match Manipulation.Memory.create_tuples storage db right
- [make_right 1 100; make_right 3 999] with
- | Error _ -> assert false | Ok x -> x
- in
- match Algebra.Memory.equijoin storage ["id"] left right with
- | Error _ -> assert false
- | Ok joined ->
- match Algebra.Memory.materialize storage joined with
- | Error _ -> assert false
- | Ok rows ->
- (* Only id=1 matches *)
- assert (List.length rows = 1);
- let t = List.hd rows in
- let score: int = Obj.obj (Tuple.AttributeMap.find "score" t.Tuple.attributes).Attribute.value in
- assert (score = 100))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* left: {id=1, name="Alice"}, {id=2, name="Bob"} *)
+ let schema_l =
+ Schema.empty |> Schema.add "id" "integer" |> Schema.add "name" "string"
+ in
+ let db, left =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"L"
+ ~schema:schema_l
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let make_left id name =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "id"
+ { Attribute.value = Obj.repr (id : int) }
+ |> Tuple.AttributeMap.add "name"
+ { Attribute.value = Obj.repr (name : string) }
+ in
+ ({ Tuple.relation = "L"; attributes = attrs } : Tuple.materialized)
+ in
+ let db, left, _ =
+ match
+ Manipulation.Memory.create_tuples storage db left
+ [ make_left 1 "Alice"; make_left 2 "Bob" ]
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* right: {id=1, score=100}, {id=3, score=999} *)
+ let schema_r =
+ Schema.empty |> Schema.add "id" "integer"
+ |> Schema.add "score" "integer"
+ in
+ let db, right =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"R"
+ ~schema:schema_r
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let make_right id score =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "id"
+ { Attribute.value = Obj.repr (id : int) }
+ |> Tuple.AttributeMap.add "score"
+ { Attribute.value = Obj.repr (score : int) }
+ in
+ ({ Tuple.relation = "R"; attributes = attrs } : Tuple.materialized)
+ in
+ let _, right, _ =
+ match
+ Manipulation.Memory.create_tuples storage db right
+ [ make_right 1 100; make_right 3 999 ]
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ match Algebra.Memory.equijoin storage [ "id" ] left right with
+ | Error _ -> assert false
+ | Ok joined -> (
+ match Algebra.Memory.materialize storage joined with
+ | Error _ -> assert false
+ | Ok rows ->
+ (* Only id=1 matches *)
+ assert (List.length rows = 1);
+ let t = List.hd rows in
+ let score : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "score" t.Tuple.attributes)
+ .Attribute.value
+ in
+ assert (score = 100)))
let%test_unit "algebra: equijoin empty match" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (db, left) = stored_relation_with_ints storage db ~name:"L" ~attr:"id" [1;2] in
- let (_, right) = stored_relation_with_ints storage db ~name:"R" ~attr:"id" [9;8] in
- match Algebra.Memory.equijoin storage ["id"] left right with
- | Error _ -> assert false
- | Ok joined ->
- match Algebra.Memory.materialize storage joined with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db, left =
+ stored_relation_with_ints storage db ~name:"L" ~attr:"id" [ 1; 2 ]
+ in
+ let _, right =
+ stored_relation_with_ints storage db ~name:"R" ~attr:"id" [ 9; 8 ]
+ in
+ match Algebra.Memory.equijoin storage [ "id" ] left right with
| Error _ -> assert false
- | Ok rows -> assert (List.length rows = 0))
+ | Ok joined -> (
+ match Algebra.Memory.materialize storage joined with
+ | Error _ -> assert false
+ | Ok rows -> assert (List.length rows = 0)))
let%test_unit "algebra: union concatenates streams" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (db, r1) = stored_relation_with_ints storage db ~name:"A" ~attr:"n" [1;2] in
- let (_, r2) = stored_relation_with_ints storage db ~name:"B" ~attr:"n" [3;4] in
- match Algebra.Memory.union storage r1 r2 with
- | Error _ -> assert false
- | Ok unioned ->
- match Algebra.Memory.materialize storage unioned with
- | Error _ -> assert false
- | Ok rows ->
- assert (List.length rows = 4);
- let vals = List.map (fun t ->
- Obj.obj (Tuple.AttributeMap.find "n" t.Tuple.attributes).Attribute.value
- ) rows in
- assert (List.mem 1 vals);
- assert (List.mem 2 vals);
- assert (List.mem 3 vals);
- assert (List.mem 4 vals))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db, r1 =
+ stored_relation_with_ints storage db ~name:"A" ~attr:"n" [ 1; 2 ]
+ in
+ let _, r2 =
+ stored_relation_with_ints storage db ~name:"B" ~attr:"n" [ 3; 4 ]
+ in
+ match Algebra.Memory.union storage r1 r2 with
+ | Error _ -> assert false
+ | Ok unioned -> (
+ match Algebra.Memory.materialize storage unioned with
+ | Error _ -> assert false
+ | Ok rows ->
+ assert (List.length rows = 4);
+ let vals =
+ List.map
+ (fun t ->
+ Obj.obj
+ (Tuple.AttributeMap.find "n" t.Tuple.attributes)
+ .Attribute.value)
+ rows
+ in
+ assert (List.mem 1 vals);
+ assert (List.mem 2 vals);
+ assert (List.mem 3 vals);
+ assert (List.mem 4 vals)))
let%test_unit "algebra: diff removes right from left" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (db, r1) = stored_relation_with_ints storage db ~name:"A" ~attr:"n" [1;2;3] in
- let (_, r2) = stored_relation_with_ints storage db ~name:"B" ~attr:"n" [2;3] in
- match Algebra.Memory.diff storage r1 r2 with
- | Error _ -> assert false
- | Ok result ->
- match Algebra.Memory.materialize storage result with
- | Error _ -> assert false
- | Ok rows ->
- assert (List.length rows = 1);
- let v: int = Obj.obj (Tuple.AttributeMap.find "n" (List.hd rows).Tuple.attributes).Attribute.value in
- assert (v = 1))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db, r1 =
+ stored_relation_with_ints storage db ~name:"A" ~attr:"n" [ 1; 2; 3 ]
+ in
+ let _, r2 =
+ stored_relation_with_ints storage db ~name:"B" ~attr:"n" [ 2; 3 ]
+ in
+ match Algebra.Memory.diff storage r1 r2 with
+ | Error _ -> assert false
+ | Ok result -> (
+ match Algebra.Memory.materialize storage result with
+ | Error _ -> assert false
+ | Ok rows ->
+ assert (List.length rows = 1);
+ let v : int =
+ Obj.obj
+ (Tuple.AttributeMap.find "n" (List.hd rows).Tuple.attributes)
+ .Attribute.value
+ in
+ assert (v = 1)))
let%test_unit "algebra: take limits output" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (_, rel) = stored_relation_with_ints storage db ~name:"big" ~attr:"n" [10;20;30;40;50] in
- match Algebra.Memory.take storage 3 rel with
- | Error _ -> assert false
- | Ok result ->
- match Algebra.Memory.materialize storage result with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let _, rel =
+ stored_relation_with_ints storage db ~name:"big" ~attr:"n"
+ [ 10; 20; 30; 40; 50 ]
+ in
+ match Algebra.Memory.take storage 3 rel with
| Error _ -> assert false
- | Ok rows -> assert (List.length rows = 3))
+ | Ok result -> (
+ match Algebra.Memory.materialize storage result with
+ | Error _ -> assert false
+ | Ok rows -> assert (List.length rows = 3)))
let%test_unit "drl: parse Base" =
match Drl.Parser.of_string {|(Base "users")|} with
- | Error _ -> assert false
- | Ok (Drl.Ast.Base s) -> assert (s = "users")
- | Ok _ -> assert false
+ | Error _ -> assert false
+ | Ok (Drl.Ast.Base s) -> assert (s = "users")
+ | Ok _ -> assert false
let%test_unit "drl: parse Const" =
match Drl.Parser.of_string {|(Const (("age" (Int 18))))|} with
- | Error _ -> assert false
- | Ok (Drl.Ast.Const [("age", Drl.Ast.Int 18)]) -> ()
- | Ok _ -> assert false
+ | Error _ -> assert false
+ | Ok (Drl.Ast.Const [ ("age", Drl.Ast.Int 18) ]) -> ()
+ | Ok _ -> assert false
let%test_unit "drl: parse Join" =
match Drl.Parser.of_string {|(Join (id) (Base "L") (Base "R"))|} with
- | Error _ -> assert false
- | Ok (Drl.Ast.Join (["id"], Drl.Ast.Base "L", Drl.Ast.Base "R")) -> ()
- | Ok _ -> assert false
+ | Error _ -> assert false
+ | Ok (Drl.Ast.Join ([ "id" ], Drl.Ast.Base "L", Drl.Ast.Base "R")) -> ()
+ | Ok _ -> assert false
let%test_unit "drl: parse Select" =
let s = {|(Select (Const (("age" (Int 18)))) (Base "users"))|} in
match Drl.Parser.of_string s with
| Error _ -> assert false
| Ok (Drl.Ast.Select (Drl.Ast.Const _, Drl.Ast.Base "users")) -> ()
- | Ok _ -> assert false
+ | Ok _ -> assert false
let%test_unit "drl: execute Base" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- let (db, _) = stored_relation_with_ints storage db ~name:"items" ~attr:"v" [10; 20] in
- match Drl.Parser.of_string {|(Base "items")|} with
- | Error _ -> assert false
- | Ok query ->
- match Drl.Executor.Memory.execute storage db query with
- | Error _ -> assert false
- | Ok rel ->
- match Algebra.Memory.materialize storage rel with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
| Error _ -> assert false
- | Ok rows -> assert (List.length rows = 2))
+ | Ok db -> db
+ in
+ let db, _ =
+ stored_relation_with_ints storage db ~name:"items" ~attr:"v" [ 10; 20 ]
+ in
+ match Drl.Parser.of_string {|(Base "items")|} with
+ | Error _ -> assert false
+ | Ok query -> (
+ match Drl.Executor.Memory.execute storage db query with
+ | Error _ -> assert false
+ | Ok rel -> (
+ match Algebra.Memory.materialize storage rel with
+ | Error _ -> assert false
+ | Ok rows -> assert (List.length rows = 2))))
let%test_unit "drl: execute Select+Const" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"test_db" with
- | Error _ -> assert false | Ok db -> db
- in
- (* users: {age=18, name="Alice"}, {age=25, name="Bob"} *)
- let schema = Schema.empty |> Schema.add "age" "integer" |> Schema.add "name" "string" in
- let (db, users) = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok x -> x
- in
- let make_user age name =
- let attrs = Tuple.AttributeMap.empty
- |> Tuple.AttributeMap.add "age" { Attribute.value = Obj.repr (age : int) }
- |> Tuple.AttributeMap.add "name" { Attribute.value = Obj.repr (name : string) } in
- ({ Tuple.relation = "users"; attributes = attrs } : Tuple.materialized)
- in
- let (db, _, _) = match Manipulation.Memory.create_tuples storage db users
- [make_user 18 "Alice"; make_user 25 "Bob"] with
- | Error _ -> assert false | Ok x -> x
- in
- (* Query: select users where age = 18 *)
- let s = {|(Select (Const (("age" (Int 18)))) (Base "users"))|} in
- match Drl.Parser.of_string s with
- | Error _ -> assert false
- | Ok query ->
- match Drl.Executor.Memory.execute storage db query with
- | Error _ -> assert false
- | Ok rel ->
- match Algebra.Memory.materialize storage rel with
- | Error _ -> assert false
- | Ok rows ->
- assert (List.length rows = 1);
- let name_v: string =
- Obj.obj (Tuple.AttributeMap.find "name" (List.hd rows).Tuple.attributes).Attribute.value
- in
- assert (name_v = "Alice"))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test_db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* users: {age=18, name="Alice"}, {age=25, name="Bob"} *)
+ let schema =
+ Schema.empty |> Schema.add "age" "integer" |> Schema.add "name" "string"
+ in
+ let db, users =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let make_user age name =
+ let attrs =
+ Tuple.AttributeMap.empty
+ |> Tuple.AttributeMap.add "age"
+ { Attribute.value = Obj.repr (age : int) }
+ |> Tuple.AttributeMap.add "name"
+ { Attribute.value = Obj.repr (name : string) }
+ in
+ ({ Tuple.relation = "users"; attributes = attrs } : Tuple.materialized)
+ in
+ let db, _, _ =
+ match
+ Manipulation.Memory.create_tuples storage db users
+ [ make_user 18 "Alice"; make_user 25 "Bob" ]
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Query: select users where age = 18 *)
+ let s = {|(Select (Const (("age" (Int 18)))) (Base "users"))|} in
+ match Drl.Parser.of_string s with
+ | Error _ -> assert false
+ | Ok query -> (
+ match Drl.Executor.Memory.execute storage db query with
+ | Error _ -> assert false
+ | Ok rel -> (
+ match Algebra.Memory.materialize storage rel with
+ | Error _ -> assert false
+ | Ok rows ->
+ assert (List.length rows = 1);
+ let name_v : string =
+ Obj.obj
+ (Tuple.AttributeMap.find "name"
+ (List.hd rows).Tuple.attributes)
+ .Attribute.value
+ in
+ assert (name_v = "Alice"))))
(* ---------- Constraint construction ---------- *)
@@ -1322,12 +1695,8 @@ let%test_unit "constraint: vars_in MemberOf" =
assert (not (List.mem "right" vars))
let%test_unit "constraint: vars_in And" =
- let b1 =
- Constraint.BindingMap.singleton "left" (Constraint.Var "a")
- in
- let b2 =
- Constraint.BindingMap.singleton "left" (Constraint.Var "b")
- in
+ let b1 = Constraint.BindingMap.singleton "left" (Constraint.Var "a") in
+ let b2 = Constraint.BindingMap.singleton "left" (Constraint.Var "b") in
let c =
Constraint.And
[
@@ -1340,9 +1709,7 @@ let%test_unit "constraint: vars_in And" =
assert (List.mem "b" vars)
let%test_unit "constraint: rename_vars" =
- let b =
- Constraint.BindingMap.singleton "left" (Constraint.Var "old_name")
- in
+ let b = Constraint.BindingMap.singleton "left" (Constraint.Var "old_name") in
let c = Constraint.MemberOf { target = "t"; binding = b } in
let c' = Constraint.rename_vars [ ("old_name", "new_name") ] c in
let vars = Constraint.vars_in c' in
@@ -1350,9 +1717,7 @@ let%test_unit "constraint: rename_vars" =
assert (not (List.mem "old_name" vars))
let%test_unit "constraint: filter_by_attrs keeps relevant" =
- let b =
- Constraint.BindingMap.singleton "left" (Constraint.Var "x")
- in
+ let b = Constraint.BindingMap.singleton "left" (Constraint.Var "x") in
let c = Constraint.MemberOf { target = "t"; binding = b } in
assert (Constraint.filter_by_attrs [ "x" ] c <> None);
assert (Constraint.filter_by_attrs [ "y" ] c = None)
@@ -1399,7 +1764,8 @@ let%test_unit "constraint: lt shorthand" =
let%test_unit "constraint: between shorthand" =
match
- Constraint.between ~value:(Var "x") ~low:(Const (Obj.repr 0))
+ Constraint.between ~value:(Var "x")
+ ~low:(Const (Obj.repr 0))
~high:(Const (Obj.repr 100))
with
| Constraint.And [ _; _ ] -> ()
@@ -1444,26 +1810,16 @@ let make_eval_ctx ?(relations = []) () : Constraint.eval_context =
}
let%test_unit "constraint: evaluate MemberOf success" =
- let ctx =
- make_eval_ctx
- ~relations:[ ("my_rel", fun _pairs -> true) ]
- ()
- in
+ let ctx = make_eval_ctx ~relations:[ ("my_rel", fun _pairs -> true) ] () in
let b = Constraint.BindingMap.empty in
let c = Constraint.MemberOf { target = "my_rel"; binding = b } in
let tuple : Tuple.materialized =
{ relation = "test"; attributes = Tuple.AttributeMap.empty }
in
- match Constraint.evaluate ctx tuple c with
- | Ok true -> ()
- | _ -> assert false
+ match Constraint.evaluate ctx tuple c with Ok true -> () | _ -> assert false
let%test_unit "constraint: evaluate MemberOf failure" =
- let ctx =
- make_eval_ctx
- ~relations:[ ("my_rel", fun _pairs -> false) ]
- ()
- in
+ let ctx = make_eval_ctx ~relations:[ ("my_rel", fun _pairs -> false) ] () in
let b = Constraint.BindingMap.empty in
let c = Constraint.MemberOf { target = "my_rel"; binding = b } in
let tuple : Tuple.materialized =
@@ -1502,20 +1858,14 @@ let%test_unit "constraint: evaluate And short-circuits" =
{ relation = "test"; attributes = Tuple.AttributeMap.empty }
in
(match Constraint.evaluate ctx tuple c with
- | Error _ -> ()
- | _ -> assert false);
+ | Error _ -> ()
+ | _ -> assert false);
(* Second branch should not have been called *)
assert (!call_count = 1)
let%test_unit "constraint: evaluate Or succeeds on first match" =
let ctx =
- make_eval_ctx
- ~relations:
- [
- ("a", fun _ -> false);
- ("b", fun _ -> true);
- ]
- ()
+ make_eval_ctx ~relations:[ ("a", fun _ -> false); ("b", fun _ -> true) ] ()
in
let b = Constraint.BindingMap.empty in
let c =
@@ -1528,9 +1878,7 @@ let%test_unit "constraint: evaluate Or succeeds on first match" =
let tuple : Tuple.materialized =
{ relation = "test"; attributes = Tuple.AttributeMap.empty }
in
- match Constraint.evaluate ctx tuple c with
- | Ok true -> ()
- | _ -> assert false
+ match Constraint.evaluate ctx tuple c with Ok true -> () | _ -> assert false
let%test_unit "constraint: evaluate Not negates" =
let ctx : Constraint.eval_context =
@@ -1555,16 +1903,14 @@ let%test_unit "constraint: evaluate Not negates" =
let tuple : Tuple.materialized =
{ relation = "test"; attributes = Tuple.AttributeMap.empty }
in
- match Constraint.evaluate ctx tuple c with
- | Ok true -> ()
- | _ -> assert false
+ match Constraint.evaluate ctx tuple c with Ok true -> () | _ -> assert false
let%test_unit "constraint: evaluate Exists over finite relation" =
let ctx : Constraint.eval_context =
{
check_membership =
(fun _rel_name pairs ->
- match List.assoc_opt "left" pairs, List.assoc_opt "right" pairs with
+ match (List.assoc_opt "left" pairs, List.assoc_opt "right" pairs) with
| Some l, Some r -> (Obj.obj l : int) < (Obj.obj r : int)
| _ -> false);
iterate_finite =
@@ -1600,26 +1946,20 @@ let%test_unit "constraint: evaluate Exists over finite relation" =
Tuple.AttributeMap.singleton "x" { Attribute.value = Obj.repr 3 };
}
in
- match Constraint.evaluate ctx tuple c with
- | Ok true -> ()
- | _ -> assert false
+ match Constraint.evaluate ctx tuple c with Ok true -> () | _ -> assert false
let%test_unit "constraint: evaluate Forall fails when not all match" =
let ctx : Constraint.eval_context =
{
check_membership =
(fun _rel_name pairs ->
- match List.assoc_opt "left" pairs, List.assoc_opt "right" pairs with
+ match (List.assoc_opt "left" pairs, List.assoc_opt "right" pairs) with
| Some l, Some r -> (Obj.obj l : int) < (Obj.obj r : int)
| _ -> false);
iterate_finite =
(fun rel_name ->
if rel_name = "small_set" then
- Some
- [
- [ ("value", Obj.repr 1) ];
- [ ("value", Obj.repr 5) ];
- ]
+ Some [ [ ("value", Obj.repr 1) ]; [ ("value", Obj.repr 5) ] ]
else None);
}
in
@@ -1650,10 +1990,7 @@ let%test_unit "constraint: evaluate Forall fails when not all match" =
let%test_unit "constraint: Forall unbounded quantifier errors" =
let ctx : Constraint.eval_context =
- {
- check_membership = (fun _ _ -> true);
- iterate_finite = (fun _ -> None);
- }
+ { check_membership = (fun _ _ -> true); iterate_finite = (fun _ -> None) }
in
let b = Constraint.BindingMap.empty in
let c =
@@ -1675,667 +2012,659 @@ let%test_unit "constraint: Forall unbounded quantifier errors" =
let%test_unit "constraint: create_tuple with passing constraint" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "value" "natural" in
- let db, _rel =
- match
- Manipulation.Memory.create_relation storage db ~name:"positives"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Register a constraint: value must be member of natural (always true for naturals) *)
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"is_natural" ~relation_name:"positives"
- ~body:(Constraint.And [])
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- let rel =
- match Manipulation.Memory.get_relation db ~name:"positives" with
- | None -> assert false
- | Some r -> r
- in
- let tuple : Tuple.materialized =
- {
- relation = "positives";
- attributes =
- Tuple.AttributeMap.singleton "value"
- { Attribute.value = Obj.repr 5 };
- }
- in
- match Manipulation.Memory.create_tuple storage db rel tuple with
- | Ok _ -> ()
- | Error _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "value" "natural" in
+ let db, _rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"positives"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Register a constraint: value must be member of natural (always true for naturals) *)
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"is_natural" ~relation_name:"positives"
+ ~body:(Constraint.And [])
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Manipulation.Memory.get_relation db ~name:"positives" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let tuple : Tuple.materialized =
+ {
+ relation = "positives";
+ attributes =
+ Tuple.AttributeMap.singleton "value"
+ { Attribute.value = Obj.repr 5 };
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db rel tuple with
+ | Ok _ -> ()
+ | Error _ -> assert false)
let%test_unit "constraint: create_tuple with failing constraint" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "value" "natural" in
- let db, _rel =
- match
- Manipulation.Memory.create_relation storage db ~name:"checked"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Register a constraint that always fails: MemberOf a nonexistent relation *)
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"impossible" ~relation_name:"checked"
- ~body:
- (Constraint.MemberOf
- {
- target = "nonexistent_relation";
- binding =
- Constraint.BindingMap.singleton "x" (Constraint.Var "value");
- })
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- let rel =
- match Manipulation.Memory.get_relation db ~name:"checked" with
- | None -> assert false
- | Some r -> r
- in
- let tuple : Tuple.materialized =
- {
- relation = "checked";
- attributes =
- Tuple.AttributeMap.singleton "value"
- { Attribute.value = Obj.repr 5 };
- }
- in
- match Manipulation.Memory.create_tuple storage db rel tuple with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "value" "natural" in
+ let db, _rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"checked" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Register a constraint that always fails: MemberOf a nonexistent relation *)
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"impossible" ~relation_name:"checked"
+ ~body:
+ (Constraint.MemberOf
+ {
+ target = "nonexistent_relation";
+ binding =
+ Constraint.BindingMap.singleton "x"
+ (Constraint.Var "value");
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Manipulation.Memory.get_relation db ~name:"checked" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let tuple : Tuple.materialized =
+ {
+ relation = "checked";
+ attributes =
+ Tuple.AttributeMap.singleton "value"
+ { Attribute.value = Obj.repr 5 };
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db rel tuple with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* ---------- Scenario tests ported from Erlang ---------- *)
(* Scenario 1: Mutual exclusion between subtypes *)
let%test_unit "constraint scenario: mutual exclusion subtypes" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "id" "natural" in
- let db, _employee =
- match
- Manipulation.Memory.create_relation storage db ~name:"employee"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- let db, _manager =
- match
- Manipulation.Memory.create_relation storage db ~name:"manager"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* manager must NOT be in employee (mutual exclusion) *)
- let body =
- Constraint.Not
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "id" "natural" in
+ let db, _employee =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"employee"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let db, _manager =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"manager" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* manager must NOT be in employee (mutual exclusion) *)
+ let body =
+ Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ {
+ target = "employee";
+ binding =
+ Constraint.BindingMap.singleton "id" (Constraint.Var "id");
+ };
+ universe = "manager";
+ }
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"not_employee" ~relation_name:"manager" ~body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Insert into employee first *)
+ let employee =
+ match Manipulation.Memory.get_relation db ~name:"employee" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let emp_tuple : Tuple.materialized =
{
- body =
- Constraint.MemberOf
- {
- target = "employee";
- binding =
- Constraint.BindingMap.singleton "id" (Constraint.Var "id");
- };
- universe = "manager";
+ relation = "employee";
+ attributes =
+ Tuple.AttributeMap.singleton "id" { Attribute.value = Obj.repr 1 };
}
- in
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"not_employee" ~relation_name:"manager" ~body
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- (* Insert into employee first *)
- let employee =
- match Manipulation.Memory.get_relation db ~name:"employee" with
- | None -> assert false
- | Some r -> r
- in
- let emp_tuple : Tuple.materialized =
- {
- relation = "employee";
- attributes =
- Tuple.AttributeMap.singleton "id"
- { Attribute.value = Obj.repr 1 };
- }
- in
- let db, _employee, _ =
- match
- Manipulation.Memory.create_tuple storage db employee emp_tuple
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Now try to insert same id into manager, should fail *)
- let manager =
- match Manipulation.Memory.get_relation db ~name:"manager" with
- | None -> assert false
- | Some r -> r
- in
- let mgr_tuple : Tuple.materialized =
- {
- relation = "manager";
- attributes =
- Tuple.AttributeMap.singleton "id"
- { Attribute.value = Obj.repr 1 };
- }
- in
- match Manipulation.Memory.create_tuple storage db manager mgr_tuple with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ in
+ let db, _employee, _ =
+ match
+ Manipulation.Memory.create_tuple storage db employee emp_tuple
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Now try to insert same id into manager, should fail *)
+ let manager =
+ match Manipulation.Memory.get_relation db ~name:"manager" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let mgr_tuple : Tuple.materialized =
+ {
+ relation = "manager";
+ attributes =
+ Tuple.AttributeMap.singleton "id" { Attribute.value = Obj.repr 1 };
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db manager mgr_tuple with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* Scenario 2: Foreign key constraint *)
let%test_unit "constraint scenario: foreign key" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let order_schema = Schema.empty |> Schema.add "order_id" "natural" in
- let db, _orders =
- match
- Manipulation.Memory.create_relation storage db ~name:"orders"
- ~schema:order_schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- let item_schema =
- Schema.empty |> Schema.add "item_id" "natural"
- |> Schema.add "order_id" "natural"
- in
- let db, _items =
- match
- Manipulation.Memory.create_relation storage db ~name:"order_items"
- ~schema:item_schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* FK: order_items.order_id must exist in orders *)
- let fk_body =
- Constraint.MemberOf
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let order_schema = Schema.empty |> Schema.add "order_id" "natural" in
+ let db, _orders =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"orders"
+ ~schema:order_schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let item_schema =
+ Schema.empty
+ |> Schema.add "item_id" "natural"
+ |> Schema.add "order_id" "natural"
+ in
+ let db, _items =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"order_items"
+ ~schema:item_schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* FK: order_items.order_id must exist in orders *)
+ let fk_body =
+ Constraint.MemberOf
+ {
+ target = "orders";
+ binding =
+ Constraint.BindingMap.singleton "order_id"
+ (Constraint.Var "order_id");
+ }
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"fk_order" ~relation_name:"order_items"
+ ~body:fk_body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Insert an order *)
+ let orders =
+ match Manipulation.Memory.get_relation db ~name:"orders" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let order_tuple : Tuple.materialized =
{
- target = "orders";
- binding =
- Constraint.BindingMap.singleton "order_id"
- (Constraint.Var "order_id");
+ relation = "orders";
+ attributes =
+ Tuple.AttributeMap.singleton "order_id"
+ { Attribute.value = Obj.repr 100 };
}
- in
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"fk_order" ~relation_name:"order_items"
- ~body:fk_body
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- (* Insert an order *)
- let orders =
- match Manipulation.Memory.get_relation db ~name:"orders" with
- | None -> assert false
- | Some r -> r
- in
- let order_tuple : Tuple.materialized =
- {
- relation = "orders";
- attributes =
- Tuple.AttributeMap.singleton "order_id"
- { Attribute.value = Obj.repr 100 };
- }
- in
- let db, _orders, _ =
- match
- Manipulation.Memory.create_tuple storage db orders order_tuple
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Insert item referencing valid order, should succeed *)
- let items =
- match Manipulation.Memory.get_relation db ~name:"order_items" with
- | None -> assert false
- | Some r -> r
- in
- let valid_item : Tuple.materialized =
- {
- relation = "order_items";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("item_id", { Attribute.value = Obj.repr 1 });
- ("order_id", { Attribute.value = Obj.repr 100 });
- ];
- }
- in
- let db, items, _ =
- match
- Manipulation.Memory.create_tuple storage db items valid_item
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Insert item referencing nonexistent order, should fail *)
- let invalid_item : Tuple.materialized =
- {
- relation = "order_items";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("item_id", { Attribute.value = Obj.repr 2 });
- ("order_id", { Attribute.value = Obj.repr 999 });
- ];
- }
- in
- match Manipulation.Memory.create_tuple storage db items invalid_item with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ in
+ let db, _orders, _ =
+ match
+ Manipulation.Memory.create_tuple storage db orders order_tuple
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Insert item referencing valid order, should succeed *)
+ let items =
+ match Manipulation.Memory.get_relation db ~name:"order_items" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let valid_item : Tuple.materialized =
+ {
+ relation = "order_items";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("item_id", { Attribute.value = Obj.repr 1 });
+ ("order_id", { Attribute.value = Obj.repr 100 });
+ ];
+ }
+ in
+ let db, items, _ =
+ match Manipulation.Memory.create_tuple storage db items valid_item with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Insert item referencing nonexistent order, should fail *)
+ let invalid_item : Tuple.materialized =
+ {
+ relation = "order_items";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("item_id", { Attribute.value = Obj.repr 2 });
+ ("order_id", { Attribute.value = Obj.repr 999 });
+ ];
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db items invalid_item with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* Scenario 3: Self-reference (emp_id != mgr_id via not_equal comparison) *)
let%test_unit "constraint scenario: self-reference neq" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema =
- Schema.empty |> Schema.add "emp_id" "natural"
- |> Schema.add "mgr_id" "natural"
- in
- let db, _rel =
- match
- Manipulation.Memory.create_relation storage db ~name:"reports_to"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Register the not_equal comparison relation *)
- let db, _ =
- match
- Manipulation.Memory.create_immutable_relation storage db
- ~name:"not_equal"
- ~schema:
- (Schema.empty |> Schema.add "left" "natural"
- |> Schema.add "right" "natural")
- ~generator:(fun _ -> Generator.Error "not enumerable")
- ~membership_criteria:(fun t ->
- match t with
- | Tuple.Materialized m -> (
- match
- ( Tuple.AttributeMap.find_opt "left" m.attributes,
- Tuple.AttributeMap.find_opt "right" m.attributes )
- with
- | Some l, Some r ->
- (Obj.obj l.Attribute.value : int)
- <> (Obj.obj r.Attribute.value : int)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema =
+ Schema.empty
+ |> Schema.add "emp_id" "natural"
+ |> Schema.add "mgr_id" "natural"
+ in
+ let db, _rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"reports_to"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Register the not_equal comparison relation *)
+ let db, _ =
+ match
+ Manipulation.Memory.create_immutable_relation storage db
+ ~name:"not_equal"
+ ~schema:
+ (Schema.empty
+ |> Schema.add "left" "natural"
+ |> Schema.add "right" "natural")
+ ~generator:(fun _ -> Generator.Error "not enumerable")
+ ~membership_criteria:(fun t ->
+ match t with
+ | Tuple.Materialized m -> (
+ match
+ ( Tuple.AttributeMap.find_opt "left" m.attributes,
+ Tuple.AttributeMap.find_opt "right" m.attributes )
+ with
+ | Some l, Some r ->
+ (Obj.obj l.Attribute.value : int)
+ <> (Obj.obj r.Attribute.value : int)
+ | _ -> false)
| _ -> false)
- | _ -> false)
- ~cardinality:Conventions.Cardinality.AlephZero
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Constraint: emp_id != mgr_id via not_equal relation *)
- let neq_body =
- Constraint.neq ~left:(Var "emp_id") ~right:(Var "mgr_id")
- in
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"no_self_manage" ~relation_name:"reports_to"
- ~body:neq_body
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- let rel =
- match Manipulation.Memory.get_relation db ~name:"reports_to" with
- | None -> assert false
- | Some r -> r
- in
- (* Insert (emp_id=1, mgr_id=2), should succeed *)
- let valid : Tuple.materialized =
- {
- relation = "reports_to";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("emp_id", { Attribute.value = Obj.repr 1 });
- ("mgr_id", { Attribute.value = Obj.repr 2 });
- ];
- }
- in
- let db, rel, _ =
- match Manipulation.Memory.create_tuple storage db rel valid with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Insert (emp_id=3, mgr_id=3): same person, should fail *)
- let invalid : Tuple.materialized =
- {
- relation = "reports_to";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("emp_id", { Attribute.value = Obj.repr 3 });
- ("mgr_id", { Attribute.value = Obj.repr 3 });
- ];
- }
- in
- match Manipulation.Memory.create_tuple storage db rel invalid with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ ~cardinality:Conventions.Cardinality.AlephZero
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Constraint: emp_id != mgr_id via not_equal relation *)
+ let neq_body =
+ Constraint.neq ~left:(Var "emp_id") ~right:(Var "mgr_id")
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"no_self_manage" ~relation_name:"reports_to"
+ ~body:neq_body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Manipulation.Memory.get_relation db ~name:"reports_to" with
+ | None -> assert false
+ | Some r -> r
+ in
+ (* Insert (emp_id=1, mgr_id=2), should succeed *)
+ let valid : Tuple.materialized =
+ {
+ relation = "reports_to";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("emp_id", { Attribute.value = Obj.repr 1 });
+ ("mgr_id", { Attribute.value = Obj.repr 2 });
+ ];
+ }
+ in
+ let db, rel, _ =
+ match Manipulation.Memory.create_tuple storage db rel valid with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Insert (emp_id=3, mgr_id=3): same person, should fail *)
+ let invalid : Tuple.materialized =
+ {
+ relation = "reports_to";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("emp_id", { Attribute.value = Obj.repr 3 });
+ ("mgr_id", { Attribute.value = Obj.repr 3 });
+ ];
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db rel invalid with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* Scenario 4: Mutual exclusion between open_ticket and closed_ticket *)
let%test_unit "constraint scenario: open vs closed ticket" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "ticket_id" "natural" in
- let db, _ =
- match
- Manipulation.Memory.create_relation storage db ~name:"open_ticket"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- let db, _ =
- match
- Manipulation.Memory.create_relation storage db ~name:"closed_ticket"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* open_ticket must not be in closed_ticket *)
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"not_closed" ~relation_name:"open_ticket"
- ~body:
- (Constraint.Not
- {
- body =
- Constraint.MemberOf
- {
- target = "closed_ticket";
- binding =
- Constraint.BindingMap.singleton "ticket_id"
- (Constraint.Var "ticket_id");
- };
- universe = "open_ticket";
- })
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- (* Insert ticket 1 as closed *)
- let closed =
- match Manipulation.Memory.get_relation db ~name:"closed_ticket" with
- | None -> assert false
- | Some r -> r
- in
- let t1 : Tuple.materialized =
- {
- relation = "closed_ticket";
- attributes =
- Tuple.AttributeMap.singleton "ticket_id"
- { Attribute.value = Obj.repr 1 };
- }
- in
- let db, _, _ =
- match Manipulation.Memory.create_tuple storage db closed t1 with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Try to open same ticket, should fail *)
- let open_ =
- match Manipulation.Memory.get_relation db ~name:"open_ticket" with
- | None -> assert false
- | Some r -> r
- in
- let t1_open : Tuple.materialized =
- {
- relation = "open_ticket";
- attributes =
- Tuple.AttributeMap.singleton "ticket_id"
- { Attribute.value = Obj.repr 1 };
- }
- in
- match Manipulation.Memory.create_tuple storage db open_ t1_open with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "ticket_id" "natural" in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"open_ticket"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"closed_ticket"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* open_ticket must not be in closed_ticket *)
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"not_closed" ~relation_name:"open_ticket"
+ ~body:
+ (Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ {
+ target = "closed_ticket";
+ binding =
+ Constraint.BindingMap.singleton "ticket_id"
+ (Constraint.Var "ticket_id");
+ };
+ universe = "open_ticket";
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Insert ticket 1 as closed *)
+ let closed =
+ match Manipulation.Memory.get_relation db ~name:"closed_ticket" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let t1 : Tuple.materialized =
+ {
+ relation = "closed_ticket";
+ attributes =
+ Tuple.AttributeMap.singleton "ticket_id"
+ { Attribute.value = Obj.repr 1 };
+ }
+ in
+ let db, _, _ =
+ match Manipulation.Memory.create_tuple storage db closed t1 with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Try to open same ticket, should fail *)
+ let open_ =
+ match Manipulation.Memory.get_relation db ~name:"open_ticket" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let t1_open : Tuple.materialized =
+ {
+ relation = "open_ticket";
+ attributes =
+ Tuple.AttributeMap.singleton "ticket_id"
+ { Attribute.value = Obj.repr 1 };
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db open_ t1_open with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* Scenario 5: Weak entity dependency *)
let%test_unit "constraint scenario: weak entity dependency" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let parent_schema = Schema.empty |> Schema.add "parent_id" "natural" in
- let db, _ =
- match
- Manipulation.Memory.create_relation storage db ~name:"parent"
- ~schema:parent_schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- let dep_schema =
- Schema.empty |> Schema.add "dep_id" "natural"
- |> Schema.add "parent_id" "natural"
- in
- let db, _ =
- match
- Manipulation.Memory.create_relation storage db ~name:"dependent"
- ~schema:dep_schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* dependent.parent_id must exist in parent *)
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"parent_exists" ~relation_name:"dependent"
- ~body:
- (Constraint.MemberOf
- {
- target = "parent";
- binding =
- Constraint.BindingMap.singleton "parent_id"
- (Constraint.Var "parent_id");
- })
- with
- | Error _ -> assert false
- | Ok db -> db
- in
- (* Insert parent *)
- let parent =
- match Manipulation.Memory.get_relation db ~name:"parent" with
- | None -> assert false
- | Some r -> r
- in
- let pt : Tuple.materialized =
- {
- relation = "parent";
- attributes =
- Tuple.AttributeMap.singleton "parent_id"
- { Attribute.value = Obj.repr 10 };
- }
- in
- let db, _, _ =
- match Manipulation.Memory.create_tuple storage db parent pt with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Insert dependent with valid parent, should succeed *)
- let dep =
- match Manipulation.Memory.get_relation db ~name:"dependent" with
- | None -> assert false
- | Some r -> r
- in
- let valid_dep : Tuple.materialized =
- {
- relation = "dependent";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("dep_id", { Attribute.value = Obj.repr 1 });
- ("parent_id", { Attribute.value = Obj.repr 10 });
- ];
- }
- in
- let db, dep, _ =
- match
- Manipulation.Memory.create_tuple storage db dep valid_dep
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- (* Insert dependent with nonexistent parent, should fail *)
- let invalid_dep : Tuple.materialized =
- {
- relation = "dependent";
- attributes =
- Tuple.AttributeMap.of_list
- [
- ("dep_id", { Attribute.value = Obj.repr 2 });
- ("parent_id", { Attribute.value = Obj.repr 999 });
- ];
- }
- in
- match
- Manipulation.Memory.create_tuple storage db dep invalid_dep
- with
- | Error (Manipulation.ConstraintViolation _) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let parent_schema = Schema.empty |> Schema.add "parent_id" "natural" in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"parent"
+ ~schema:parent_schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let dep_schema =
+ Schema.empty
+ |> Schema.add "dep_id" "natural"
+ |> Schema.add "parent_id" "natural"
+ in
+ let db, _ =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"dependent"
+ ~schema:dep_schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* dependent.parent_id must exist in parent *)
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"parent_exists" ~relation_name:"dependent"
+ ~body:
+ (Constraint.MemberOf
+ {
+ target = "parent";
+ binding =
+ Constraint.BindingMap.singleton "parent_id"
+ (Constraint.Var "parent_id");
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Insert parent *)
+ let parent =
+ match Manipulation.Memory.get_relation db ~name:"parent" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let pt : Tuple.materialized =
+ {
+ relation = "parent";
+ attributes =
+ Tuple.AttributeMap.singleton "parent_id"
+ { Attribute.value = Obj.repr 10 };
+ }
+ in
+ let db, _, _ =
+ match Manipulation.Memory.create_tuple storage db parent pt with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Insert dependent with valid parent, should succeed *)
+ let dep =
+ match Manipulation.Memory.get_relation db ~name:"dependent" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let valid_dep : Tuple.materialized =
+ {
+ relation = "dependent";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("dep_id", { Attribute.value = Obj.repr 1 });
+ ("parent_id", { Attribute.value = Obj.repr 10 });
+ ];
+ }
+ in
+ let db, dep, _ =
+ match Manipulation.Memory.create_tuple storage db dep valid_dep with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Insert dependent with nonexistent parent, should fail *)
+ let invalid_dep : Tuple.materialized =
+ {
+ relation = "dependent";
+ attributes =
+ Tuple.AttributeMap.of_list
+ [
+ ("dep_id", { Attribute.value = Obj.repr 2 });
+ ("parent_id", { Attribute.value = Obj.repr 999 });
+ ];
+ }
+ in
+ match Manipulation.Memory.create_tuple storage db dep invalid_dep with
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | _ -> assert false)
(* Scenario 6: Algebra propagation, select preserves constraints *)
let%test_unit "constraint propagation: select preserves constraints" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "x" "natural" in
- let db, _rel =
- match
- Manipulation.Memory.create_relation storage db ~name:"constrained"
- ~schema
- with
- | Error _ -> assert false
- | Ok x -> x
- in
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"c1" ~relation_name:"constrained"
- ~body:(Constraint.And [])
- with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "x" "natural" in
+ let db, _rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"constrained"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"c1" ~relation_name:"constrained"
+ ~body:(Constraint.And [])
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Manipulation.Memory.get_relation db ~name:"constrained" with
+ | None -> assert false
+ | Some r -> r
+ in
+ match Algebra.Memory.select_fn storage (fun _ -> true) rel with
| Error _ -> assert false
- | Ok db -> db
- in
- let rel =
- match Manipulation.Memory.get_relation db ~name:"constrained" with
- | None -> assert false
- | Some r -> r
- in
- match
- Algebra.Memory.select_fn storage (fun _ -> true) rel
- with
- | Error _ -> assert false
- | Ok result ->
- assert (result.Relation.constraints <> None))
+ | Ok result -> assert (result.Relation.constraints <> None))
(* Scenario 7: Algebra propagation, project filters constraints *)
let%test_unit "constraint propagation: project filters constraints" =
with_storage (fun storage ->
- let db =
- match Manipulation.Memory.create_database storage ~name:"test" with
- | Error _ -> assert false
- | Ok db -> db
- in
- let schema =
- Schema.empty |> Schema.add "x" "natural" |> Schema.add "y" "natural"
- in
- let db, _rel =
- match
- Manipulation.Memory.create_relation storage db ~name:"xy" ~schema
- with
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"test" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema =
+ Schema.empty |> Schema.add "x" "natural" |> Schema.add "y" "natural"
+ in
+ let db, _rel =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"xy" ~schema
+ with
+ | Error _ -> assert false
+ | Ok x -> x
+ in
+ (* Constraint on x only *)
+ let c_on_x =
+ Constraint.MemberOf
+ {
+ target = "some_rel";
+ binding =
+ Constraint.BindingMap.singleton "left" (Constraint.Var "x");
+ }
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"x_only" ~relation_name:"xy" ~body:c_on_x
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Manipulation.Memory.get_relation db ~name:"xy" with
+ | None -> assert false
+ | Some r -> r
+ in
+ (* Project to only "x", constraint should survive *)
+ (match Algebra.Memory.project storage [ "x" ] rel with
| Error _ -> assert false
- | Ok x -> x
- in
- (* Constraint on x only *)
- let c_on_x =
- Constraint.MemberOf
- {
- target = "some_rel";
- binding =
- Constraint.BindingMap.singleton "left" (Constraint.Var "x");
- }
- in
- let db =
- match
- Manipulation.Memory.register_constraint storage db
- ~constraint_name:"x_only" ~relation_name:"xy" ~body:c_on_x
- with
+ | Ok result -> assert (result.Relation.constraints <> None));
+ (* Project to only "y", constraint referencing "x" should be dropped *)
+ match Algebra.Memory.project storage [ "y" ] rel with
| Error _ -> assert false
- | Ok db -> db
- in
- let rel =
- match Manipulation.Memory.get_relation db ~name:"xy" with
- | None -> assert false
- | Some r -> r
- in
- (* Project to only "x", constraint should survive *)
- (match Algebra.Memory.project storage [ "x" ] rel with
- | Error _ -> assert false
- | Ok result -> assert (result.Relation.constraints <> None));
- (* Project to only "y", constraint referencing "x" should be dropped *)
- match Algebra.Memory.project storage [ "y" ] rel with
- | Error _ -> assert false
- | Ok result -> assert (result.Relation.constraints = None))
+ | Ok result -> assert (result.Relation.constraints = None))
(* Parse tests use canonical flat sexp format: (Constructor (field val) ...) *)
@@ -2355,37 +2684,46 @@ let%test_unit "ddl: parse ClearRelation" =
| Ok stmt -> assert (stmt = Ddl.Ast.ClearRelation "users")
let%test_unit "dml: round-trip InsertTuple" =
- let src = Dml.Ast.InsertTuple {
- relation = "users";
- attributes = [("name", Drl.Ast.Str "Alice"); ("age", Drl.Ast.Int 30)]
- } in
+ let src =
+ Dml.Ast.InsertTuple
+ {
+ relation = "users";
+ attributes = [ ("name", Drl.Ast.Str "Alice"); ("age", Drl.Ast.Int 30) ];
+ }
+ in
match Dml.Parser.of_string (Dml.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "ddl: round-trip CreateRelation" =
- let src = Ddl.Ast.CreateRelation {
- name = "users";
- schema = [("name", "string"); ("age", "natural")]
- } in
+ let src =
+ Ddl.Ast.CreateRelation
+ { name = "users"; schema = [ ("name", "string"); ("age", "natural") ] }
+ in
match Ddl.Parser.of_string (Ddl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "dml: round-trip InsertTuples" =
- let src = Dml.Ast.InsertTuples {
- relation = "users";
- tuples = [
- [("name", Drl.Ast.Str "Alice"); ("age", Drl.Ast.Int 30)];
- [("name", Drl.Ast.Str "Bob"); ("age", Drl.Ast.Int 25)];
- ]
- } in
+ let src =
+ Dml.Ast.InsertTuples
+ {
+ relation = "users";
+ tuples =
+ [
+ [ ("name", Drl.Ast.Str "Alice"); ("age", Drl.Ast.Int 30) ];
+ [ ("name", Drl.Ast.Str "Bob"); ("age", Drl.Ast.Int 25) ];
+ ];
+ }
+ in
match Dml.Parser.of_string (Dml.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "ddl: round-trip RegisterDomain" =
- let src = Ddl.Ast.RegisterDomain { name = "money"; cardinality = Ddl.Ast.AlephZero } in
+ let src =
+ Ddl.Ast.RegisterDomain { name = "money"; cardinality = Ddl.Ast.AlephZero }
+ in
match Ddl.Parser.of_string (Ddl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
@@ -2394,550 +2732,867 @@ let%test_unit "ddl: round-trip RegisterDomain" =
let%test_unit "ddl: execute CreateDatabase" =
with_storage (fun storage ->
- let db = Management.Database.empty ~name:"" in
- let stmt = Ddl.Ast.CreateDatabase "shop" in
- match Ddl.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok (db, _msg) -> assert (db.name = "shop"))
+ let db = Management.Database.empty ~name:"" in
+ let stmt = Ddl.Ast.CreateDatabase "shop" in
+ match Ddl.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok (db, _msg) -> assert (db.name = "shop"))
let%test_unit "ddl: execute CreateRelation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let stmt = Ddl.Ast.CreateRelation { name = "users"; schema = [("name", "string"); ("age", "natural")] } in
- match Ddl.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok (db, _msg) -> assert (Management.Database.has_relation db "users"))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let stmt =
+ Ddl.Ast.CreateRelation
+ {
+ name = "users";
+ schema = [ ("name", "string"); ("age", "natural") ];
+ }
+ in
+ match Ddl.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok (db, _msg) -> assert (Management.Database.has_relation db "users"))
let%test_unit "dml: execute InsertTuple" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "name" "string" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let stmt = Dml.Ast.InsertTuple { relation = "users"; attributes = [("name", Drl.Ast.Str "Alice")] } in
- match Dml.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok db ->
- let rel = match Management.Database.get_relation db "users" with
- | None -> assert false | Some r -> r
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "name" "string" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
- assert (Manipulation.Memory.tuple_count rel = 1))
+ let stmt =
+ Dml.Ast.InsertTuple
+ { relation = "users"; attributes = [ ("name", Drl.Ast.Str "Alice") ] }
+ in
+ match Dml.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok db ->
+ let rel =
+ match Management.Database.get_relation db "users" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 1))
let%test_unit "dml: execute InsertTuples" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "name" "string" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let stmt = Dml.Ast.InsertTuples {
- relation = "users";
- tuples = [[("name", Drl.Ast.Str "Alice")]; [("name", Drl.Ast.Str "Bob")]]
- } in
- match Dml.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok db ->
- let rel = match Management.Database.get_relation db "users" with
- | None -> assert false | Some r -> r
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "name" "string" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let stmt =
+ Dml.Ast.InsertTuples
+ {
+ relation = "users";
+ tuples =
+ [
+ [ ("name", Drl.Ast.Str "Alice") ];
+ [ ("name", Drl.Ast.Str "Bob") ];
+ ];
+ }
in
- assert (Manipulation.Memory.tuple_count rel = 2))
+ match Dml.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok db ->
+ let rel =
+ match Management.Database.get_relation db "users" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 2))
let%test_unit "dml: execute DeleteTuple" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "name" "string" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "users"; attributes = [("name", Drl.Ast.Str "Alice")] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let stmt = Dml.Ast.DeleteTuple { relation = "users"; attributes = [("name", Drl.Ast.Str "Alice")] } in
- match Dml.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok db ->
- let rel = match Management.Database.get_relation db "users" with
- | None -> assert false | Some r -> r
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "name" "string" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "users";
+ attributes = [ ("name", Drl.Ast.Str "Alice") ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let stmt =
+ Dml.Ast.DeleteTuple
+ { relation = "users"; attributes = [ ("name", Drl.Ast.Str "Alice") ] }
in
- assert (Manipulation.Memory.tuple_count rel = 0))
+ match Dml.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok db ->
+ let rel =
+ match Management.Database.get_relation db "users" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 0))
let%test_unit "ddl: execute RetractRelation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty in
- let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- match Ddl.Executor.Memory.execute storage db (Ddl.Ast.RetractRelation "users") with
- | Error _ -> assert false
- | Ok (db, _msg) -> assert (not (Management.Database.has_relation db "users")))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ match
+ Ddl.Executor.Memory.execute storage db (Ddl.Ast.RetractRelation "users")
+ with
+ | Error _ -> assert false
+ | Ok (db, _msg) ->
+ assert (not (Management.Database.has_relation db "users")))
let%test_unit "ddl: execute ClearRelation" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "name" "string" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuples { relation = "users";
- tuples = [[("name", Drl.Ast.Str "Alice")]; [("name", Drl.Ast.Str "Bob")]] }) with
- | Error _ -> assert false | Ok db -> db
- in
- match Ddl.Executor.Memory.execute storage db (Ddl.Ast.ClearRelation "users") with
- | Error _ -> assert false
- | Ok (db, _msg) ->
- let rel = match Management.Database.get_relation db "users" with
- | None -> assert false | Some r -> r
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "name" "string" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"users" ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuples
+ {
+ relation = "users";
+ tuples =
+ [
+ [ ("name", Drl.Ast.Str "Alice") ];
+ [ ("name", Drl.Ast.Str "Bob") ];
+ ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
in
- assert (Manipulation.Memory.tuple_count rel = 0))
+ match
+ Ddl.Executor.Memory.execute storage db (Ddl.Ast.ClearRelation "users")
+ with
+ | Error _ -> assert false
+ | Ok (db, _msg) ->
+ let rel =
+ match Management.Database.get_relation db "users" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 0))
let%test_unit "ddl: execute RegisterDomain" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let stmt = Ddl.Ast.RegisterDomain { name = "money"; cardinality = Ddl.Ast.AlephZero } in
- match Ddl.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok (db, _msg) -> assert (Management.Database.has_domain db "money"))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let stmt =
+ Ddl.Ast.RegisterDomain
+ { name = "money"; cardinality = Ddl.Ast.AlephZero }
+ in
+ match Ddl.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok (db, _msg) -> assert (Management.Database.has_domain db "money"))
let%test_unit "dml: insert into nonexistent relation returns error" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let stmt = Dml.Ast.InsertTuple { relation = "ghost"; attributes = [("x", Drl.Ast.Int 1)] } in
- match Dml.Executor.Memory.execute storage db stmt with
- | Error (Dml.Executor.Memory.RelationNotFound "ghost") -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let stmt =
+ Dml.Ast.InsertTuple
+ { relation = "ghost"; attributes = [ ("x", Drl.Ast.Int 1) ] }
+ in
+ match Dml.Executor.Memory.execute storage db stmt with
+ | Error (Dml.Executor.Memory.RelationNotFound "ghost") -> ()
+ | _ -> assert false)
let%test_unit "dcl: round-trip RegisterConstraint MemberOf" =
- let src = Icl.Ast.RegisterConstraint {
- constraint_name = "fk_order";
- relation_name = "order_items";
- body = Icl.Ast.MemberOf { target = "orders"; binding = [("order_id", Icl.Ast.Var "order_id")] }
- } in
+ let src =
+ Icl.Ast.RegisterConstraint
+ {
+ constraint_name = "fk_order";
+ relation_name = "order_items";
+ body =
+ Icl.Ast.MemberOf
+ {
+ target = "orders";
+ binding = [ ("order_id", Icl.Ast.Var "order_id") ];
+ };
+ }
+ in
match Icl.Parser.of_string (Icl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "dcl: round-trip And constraint" =
- let src = Icl.Ast.RegisterConstraint {
- constraint_name = "valid_range";
- relation_name = "scores";
- body = Icl.Ast.And [
- Icl.Ast.MemberOf { target = "ge_zero";
- binding = [("left", Icl.Ast.Var "score"); ("right", Icl.Ast.Const (Drl.Ast.Int 0))] };
- Icl.Ast.MemberOf { target = "le_hundred";
- binding = [("left", Icl.Ast.Var "score"); ("right", Icl.Ast.Const (Drl.Ast.Int 100))] };
- ]
- } in
+ let src =
+ Icl.Ast.RegisterConstraint
+ {
+ constraint_name = "valid_range";
+ relation_name = "scores";
+ body =
+ Icl.Ast.And
+ [
+ Icl.Ast.MemberOf
+ {
+ target = "ge_zero";
+ binding =
+ [
+ ("left", Icl.Ast.Var "score");
+ ("right", Icl.Ast.Const (Drl.Ast.Int 0));
+ ];
+ };
+ Icl.Ast.MemberOf
+ {
+ target = "le_hundred";
+ binding =
+ [
+ ("left", Icl.Ast.Var "score");
+ ("right", Icl.Ast.Const (Drl.Ast.Int 100));
+ ];
+ };
+ ];
+ }
+ in
match Icl.Parser.of_string (Icl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "dcl: round-trip Not constraint" =
- let src = Icl.Ast.RegisterConstraint {
- constraint_name = "not_closed";
- relation_name = "open_ticket";
- body = Icl.Ast.Not {
- body = Icl.Ast.MemberOf { target = "closed_ticket";
- binding = [("ticket_id", Icl.Ast.Var "ticket_id")] };
- universe = "open_ticket"
- }
- } in
+ let src =
+ Icl.Ast.RegisterConstraint
+ {
+ constraint_name = "not_closed";
+ relation_name = "open_ticket";
+ body =
+ Icl.Ast.Not
+ {
+ body =
+ Icl.Ast.MemberOf
+ {
+ target = "closed_ticket";
+ binding = [ ("ticket_id", Icl.Ast.Var "ticket_id") ];
+ };
+ universe = "open_ticket";
+ };
+ }
+ in
match Icl.Parser.of_string (Icl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
let%test_unit "dcl: execute RegisterConstraint attaches constraint" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "order_id" "natural" in
- let db = match Manipulation.Memory.create_relation storage db ~name:"order_items" ~schema with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let stmt = Icl.Ast.RegisterConstraint {
- constraint_name = "fk_order";
- relation_name = "order_items";
- body = Icl.Ast.MemberOf { target = "orders"; binding = [("order_id", Icl.Ast.Var "order_id")] }
- } in
- match Icl.Executor.Memory.execute storage db stmt with
- | Error _ -> assert false
- | Ok (db, _) ->
- let rel = match Management.Database.get_relation db "order_items" with
- | None -> assert false | Some r -> r
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- assert (rel.Relation.constraints <> None))
+ let schema = Schema.empty |> Schema.add "order_id" "natural" in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"order_items"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let stmt =
+ Icl.Ast.RegisterConstraint
+ {
+ constraint_name = "fk_order";
+ relation_name = "order_items";
+ body =
+ Icl.Ast.MemberOf
+ {
+ target = "orders";
+ binding = [ ("order_id", Icl.Ast.Var "order_id") ];
+ };
+ }
+ in
+ match Icl.Executor.Memory.execute storage db stmt with
+ | Error _ -> assert false
+ | Ok (db, _) ->
+ let rel =
+ match Management.Database.get_relation db "order_items" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (rel.Relation.constraints <> None))
let%test_unit "dcl: FK constraint enforced on insert" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"shop" with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"orders"
- ~schema:(Schema.empty |> Schema.add "id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- (* Insert a valid order *)
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "orders"; attributes = [("id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"order_items"
- ~schema:(Schema.empty |> Schema.add "order_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- (* Register FK: order_id in order_items must match id in orders *)
- let db = match Icl.Executor.Memory.execute storage db
- (Icl.Ast.RegisterConstraint {
- constraint_name = "fk_order";
- relation_name = "order_items";
- body = Icl.Ast.MemberOf { target = "orders"; binding = [("id", Icl.Ast.Var "order_id")] }
- }) with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- (* Valid insert: order_id=1 exists in orders *)
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "order_items"; attributes = [("order_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let rel = match Management.Database.get_relation db "order_items" with
- | None -> assert false | Some r -> r
- in
- assert (Manipulation.Memory.tuple_count rel = 1);
- (* Invalid insert: order_id=99 does not exist in orders *)
- match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "order_items"; attributes = [("order_id", Drl.Ast.Int 99)] }) with
- | Error (Dml.Executor.Memory.ManipulationError (Manipulation.ConstraintViolation _)) -> ()
- | _ -> assert false)
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"shop" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"orders"
+ ~schema:(Schema.empty |> Schema.add "id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ (* Insert a valid order *)
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ { relation = "orders"; attributes = [ ("id", Drl.Ast.Int 1) ] })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"order_items"
+ ~schema:(Schema.empty |> Schema.add "order_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ (* Register FK: order_id in order_items must match id in orders *)
+ let db =
+ match
+ Icl.Executor.Memory.execute storage db
+ (Icl.Ast.RegisterConstraint
+ {
+ constraint_name = "fk_order";
+ relation_name = "order_items";
+ body =
+ Icl.Ast.MemberOf
+ {
+ target = "orders";
+ binding = [ ("id", Icl.Ast.Var "order_id") ];
+ };
+ })
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ (* Valid insert: order_id=1 exists in orders *)
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "order_items";
+ attributes = [ ("order_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let rel =
+ match Management.Database.get_relation db "order_items" with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count rel = 1);
+ (* Invalid insert: order_id=99 does not exist in orders *)
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "order_items";
+ attributes = [ ("order_id", Drl.Ast.Int 99) ];
+ })
+ with
+ | Error
+ (Dml.Executor.Memory.ManipulationError
+ (Manipulation.ConstraintViolation _)) ->
+ ()
+ | _ -> assert false)
-module BranchMemory = Management.Branch.Make(Management.Physical.Memory)
+module BranchMemory = Management.Branch.Make (Management.Physical.Memory)
let%test_unit "branch: create and get_tip" =
with_storage (fun storage ->
- let tip = "deadbeef" in
- (match BranchMemory.create storage ~name:"main" ~tip with
- | Error _ -> assert false
- | Ok () -> ());
- match BranchMemory.get_tip storage "main" with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some t) -> assert (t = tip))
+ let tip = "deadbeef" in
+ (match BranchMemory.create storage ~name:"main" ~tip with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match BranchMemory.get_tip storage "main" with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some t) -> assert (t = tip))
let%test_unit "branch: get_tip returns None for unknown branch" =
with_storage (fun storage ->
- match BranchMemory.get_tip storage "nonexistent" with
- | Error _ -> assert false
- | Ok None -> ()
- | Ok (Some _) -> assert false)
+ match BranchMemory.get_tip storage "nonexistent" with
+ | Error _ -> assert false
+ | Ok None -> ()
+ | Ok (Some _) -> assert false)
let%test_unit "branch: checkout and get_head" =
with_storage (fun storage ->
- (match BranchMemory.create storage ~name:"main" ~tip:"abc" with
- | Error _ -> assert false | Ok () -> ());
- (match BranchMemory.checkout storage "main" with
- | Error _ -> assert false | Ok () -> ());
- match BranchMemory.get_head storage with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some h) -> assert (h = "main"))
+ (match BranchMemory.create storage ~name:"main" ~tip:"abc" with
+ | Error _ -> assert false
+ | Ok () -> ());
+ (match BranchMemory.checkout storage "main" with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match BranchMemory.get_head storage with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some h) -> assert (h = "main"))
let%test_unit "branch: get_head returns None when not set" =
with_storage (fun storage ->
- match BranchMemory.get_head storage with
- | Error _ -> assert false
- | Ok None -> ()
- | Ok (Some _) -> assert false)
+ match BranchMemory.get_head storage with
+ | Error _ -> assert false
+ | Ok None -> ()
+ | Ok (Some _) -> assert false)
let%test_unit "branch: update_tip advances the branch" =
with_storage (fun storage ->
- (match BranchMemory.create storage ~name:"main" ~tip:"v1" with
- | Error _ -> assert false | Ok () -> ());
- (match BranchMemory.update_tip storage ~name:"main" ~tip:"v2" with
- | Error _ -> assert false | Ok () -> ());
- match BranchMemory.get_tip storage "main" with
- | Error _ -> assert false
- | Ok None -> assert false
- | Ok (Some t) -> assert (t = "v2"))
+ (match BranchMemory.create storage ~name:"main" ~tip:"v1" with
+ | Error _ -> assert false
+ | Ok () -> ());
+ (match BranchMemory.update_tip storage ~name:"main" ~tip:"v2" with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match BranchMemory.get_tip storage "main" with
+ | Error _ -> assert false
+ | Ok None -> assert false
+ | Ok (Some t) -> assert (t = "v2"))
let%test_unit "branch: update_tip fails on unknown branch" =
with_storage (fun storage ->
- match BranchMemory.update_tip storage ~name:"ghost" ~tip:"v1" with
- | Error _ -> ()
- | Ok () -> assert false)
+ match BranchMemory.update_tip storage ~name:"ghost" ~tip:"v1" with
+ | Error _ -> ()
+ | Ok () -> assert false)
let%test_unit "branch: multiple branches are independent" =
with_storage (fun storage ->
- (match BranchMemory.create storage ~name:"main" ~tip:"hash-main" with
- | Error _ -> assert false | Ok () -> ());
- (match BranchMemory.create storage ~name:"feature" ~tip:"hash-feature" with
- | Error _ -> assert false | Ok () -> ());
- let main_tip = match BranchMemory.get_tip storage "main" with
- | Ok (Some t) -> t | _ -> assert false
- in
- let feat_tip = match BranchMemory.get_tip storage "feature" with
- | Ok (Some t) -> t | _ -> assert false
- in
- assert (main_tip = "hash-main");
- assert (feat_tip = "hash-feature");
- assert (main_tip <> feat_tip))
+ (match BranchMemory.create storage ~name:"main" ~tip:"hash-main" with
+ | Error _ -> assert false
+ | Ok () -> ());
+ (match
+ BranchMemory.create storage ~name:"feature" ~tip:"hash-feature"
+ with
+ | Error _ -> assert false
+ | Ok () -> ());
+ let main_tip =
+ match BranchMemory.get_tip storage "main" with
+ | Ok (Some t) -> t
+ | _ -> assert false
+ in
+ let feat_tip =
+ match BranchMemory.get_tip storage "feature" with
+ | Ok (Some t) -> t
+ | _ -> assert false
+ in
+ assert (main_tip = "hash-main");
+ assert (feat_tip = "hash-feature");
+ assert (main_tip <> feat_tip))
let%test_unit "diff: identical databases produce empty diff" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let diffs = Management.Diff.diff ~ancestor:db ~target:db in
- assert (diffs = []))
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let diffs = Management.Diff.diff ~ancestor:db ~target:db in
+ assert (diffs = []))
let%test_unit "diff: added relation detected" =
with_storage (fun storage ->
- let ancestor = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let schema = Schema.empty |> Schema.add "x" "natural" in
- let target, _ = match Manipulation.Memory.create_relation storage ancestor
- ~name:"new_rel" ~schema with
- | Error _ -> assert false | Ok p -> p
- in
- let diffs = Management.Diff.diff ~ancestor ~target in
- let added = List.filter_map (function
- | Management.Diff.RelationAdded r -> Some r
- | _ -> None) diffs
- in
- assert (List.exists (fun r -> r.Relation.name = "new_rel") added))
+ let ancestor =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let schema = Schema.empty |> Schema.add "x" "natural" in
+ let target, _ =
+ match
+ Manipulation.Memory.create_relation storage ancestor ~name:"new_rel"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ let diffs = Management.Diff.diff ~ancestor ~target in
+ let added =
+ List.filter_map
+ (function Management.Diff.RelationAdded r -> Some r | _ -> None)
+ diffs
+ in
+ assert (List.exists (fun r -> r.Relation.name = "new_rel") added))
let%test_unit "diff: removed relation detected" =
with_storage (fun storage ->
- let schema = Schema.empty |> Schema.add "x" "natural" in
- let ancestor = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let ancestor, _ = match Manipulation.Memory.create_relation storage ancestor
- ~name:"gone" ~schema with
- | Error _ -> assert false | Ok p -> p
- in
- let target = match Manipulation.Memory.retract_relation storage ancestor ~name:"gone" with
- | Error _ -> assert false | Ok db -> db
- in
- let diffs = Management.Diff.diff ~ancestor ~target in
- let removed = List.filter_map (function
- | Management.Diff.RelationRemoved n -> Some n
- | _ -> None) diffs
- in
- assert (List.mem "gone" removed))
+ let schema = Schema.empty |> Schema.add "x" "natural" in
+ let ancestor =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let ancestor, _ =
+ match
+ Manipulation.Memory.create_relation storage ancestor ~name:"gone"
+ ~schema
+ with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ let target =
+ match
+ Manipulation.Memory.retract_relation storage ancestor ~name:"gone"
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let diffs = Management.Diff.diff ~ancestor ~target in
+ let removed =
+ List.filter_map
+ (function Management.Diff.RelationRemoved n -> Some n | _ -> None)
+ diffs
+ in
+ assert (List.mem "gone" removed))
let%test_unit "diff: modified relation detected with added tuple" =
with_storage (fun storage ->
- let schema = Schema.empty |> Schema.add "val" "natural" in
- let ancestor = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let ancestor, _ = match Manipulation.Memory.create_relation storage ancestor
- ~name:"r" ~schema with
- | Error _ -> assert false | Ok p -> p
- in
- let rel = Option.get (Management.Database.get_relation ancestor "r") in
- let t : Tuple.materialized = {
- Tuple.relation = "r";
- attributes = Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 42 };
- } in
- let (target, _, _) = match Manipulation.Memory.create_tuple storage ancestor rel t with
- | Error _ -> assert false | Ok p -> p
- in
- let diffs = Management.Diff.diff ~ancestor ~target in
- assert (List.length diffs = 1);
- match diffs with
- | [ Management.Diff.RelationModified { name; added_tuples; removed_tuples; _ } ] ->
- assert (name = "r");
- assert (List.length added_tuples = 1);
- assert (removed_tuples = [])
- | _ -> assert false)
-
-module MergeMemory = Management.Merge.Make
- (Management.Physical.Memory)
- (Manipulation.Memory)
+ let schema = Schema.empty |> Schema.add "val" "natural" in
+ let ancestor =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let ancestor, _ =
+ match
+ Manipulation.Memory.create_relation storage ancestor ~name:"r" ~schema
+ with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ let rel = Option.get (Management.Database.get_relation ancestor "r") in
+ let t : Tuple.materialized =
+ {
+ Tuple.relation = "r";
+ attributes =
+ Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 42 };
+ }
+ in
+ let target, _, _ =
+ match Manipulation.Memory.create_tuple storage ancestor rel t with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ let diffs = Management.Diff.diff ~ancestor ~target in
+ assert (List.length diffs = 1);
+ match diffs with
+ | [
+ Management.Diff.RelationModified
+ { name; added_tuples; removed_tuples; _ };
+ ] ->
+ assert (name = "r");
+ assert (List.length added_tuples = 1);
+ assert (removed_tuples = [])
+ | _ -> assert false)
+
+module MergeMemory =
+ Management.Merge.Make (Management.Physical.Memory) (Manipulation.Memory)
let%test_unit "merge: fast-forward when only one side changed" =
with_storage (fun storage ->
- let schema = Schema.empty |> Schema.add "val" "natural" in
- let base_db = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let base_db, _ = match Manipulation.Memory.create_relation storage base_db
- ~name:"r" ~schema with
- | Error _ -> assert false | Ok p -> p
- in
- (match Manipulation.Memory.store_database storage base_db with
- | Error _ -> assert false | Ok () -> ());
- let rel = Option.get (Management.Database.get_relation base_db "r") in
- let t1 : Tuple.materialized = {
- Tuple.relation = "r";
- attributes = Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 1 };
- } in
- let (left_db, _, _) = match Manipulation.Memory.create_tuple storage base_db rel t1 with
- | Error _ -> assert false | Ok p -> p
- in
- (match Manipulation.Memory.store_database storage left_db with
- | Error _ -> assert false | Ok () -> ());
- match MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
- ~left_tip:left_db.Management.Database.hash
- ~right_tip:base_db.Management.Database.hash with
- | Error _ -> assert false
- | Ok (Management.Merge.Failed _) -> assert false
- | Ok (Management.Merge.Clean merged) ->
- let merged_rel = Option.get (Management.Database.get_relation merged "r") in
- assert (Manipulation.Memory.tuple_count merged_rel = 1))
+ let schema = Schema.empty |> Schema.add "val" "natural" in
+ let base_db =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let base_db, _ =
+ match
+ Manipulation.Memory.create_relation storage base_db ~name:"r" ~schema
+ with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ (match Manipulation.Memory.store_database storage base_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ let rel = Option.get (Management.Database.get_relation base_db "r") in
+ let t1 : Tuple.materialized =
+ {
+ Tuple.relation = "r";
+ attributes =
+ Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 1 };
+ }
+ in
+ let left_db, _, _ =
+ match Manipulation.Memory.create_tuple storage base_db rel t1 with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ (match Manipulation.Memory.store_database storage left_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match
+ MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
+ ~left_tip:left_db.Management.Database.hash
+ ~right_tip:base_db.Management.Database.hash
+ with
+ | Error _ -> assert false
+ | Ok (Management.Merge.Failed _) -> assert false
+ | Ok (Management.Merge.Clean merged) ->
+ let merged_rel =
+ Option.get (Management.Database.get_relation merged "r")
+ in
+ assert (Manipulation.Memory.tuple_count merged_rel = 1))
let%test_unit "merge: independent tuple additions produce union" =
with_storage (fun storage ->
- let schema = Schema.empty |> Schema.add "val" "natural" in
- let base_db = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- let base_db, _ = match Manipulation.Memory.create_relation storage base_db
- ~name:"r" ~schema with
- | Error _ -> assert false | Ok p -> p
- in
- (match Manipulation.Memory.store_database storage base_db with
- | Error _ -> assert false | Ok () -> ());
- let rel = Option.get (Management.Database.get_relation base_db "r") in
- let t_left : Tuple.materialized = {
- Tuple.relation = "r";
- attributes = Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 10 };
- } in
- let (left_db, _, _) = match Manipulation.Memory.create_tuple storage base_db rel t_left with
- | Error _ -> assert false | Ok p -> p
- in
- (match Manipulation.Memory.store_database storage left_db with
- | Error _ -> assert false | Ok () -> ());
- let t_right : Tuple.materialized = {
- Tuple.relation = "r";
- attributes = Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 20 };
- } in
- let (right_db, _, _) = match Manipulation.Memory.create_tuple storage base_db rel t_right with
- | Error _ -> assert false | Ok p -> p
- in
- (match Manipulation.Memory.store_database storage right_db with
- | Error _ -> assert false | Ok () -> ());
- match MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
- ~left_tip:left_db.Management.Database.hash
- ~right_tip:right_db.Management.Database.hash with
- | Error _ -> assert false
- | Ok (Management.Merge.Failed _) -> assert false
- | Ok (Management.Merge.Clean merged) ->
- let merged_rel = Option.get (Management.Database.get_relation merged "r") in
- assert (Manipulation.Memory.tuple_count merged_rel = 2))
+ let schema = Schema.empty |> Schema.add "val" "natural" in
+ let base_db =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let base_db, _ =
+ match
+ Manipulation.Memory.create_relation storage base_db ~name:"r" ~schema
+ with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ (match Manipulation.Memory.store_database storage base_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ let rel = Option.get (Management.Database.get_relation base_db "r") in
+ let t_left : Tuple.materialized =
+ {
+ Tuple.relation = "r";
+ attributes =
+ Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 10 };
+ }
+ in
+ let left_db, _, _ =
+ match Manipulation.Memory.create_tuple storage base_db rel t_left with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ (match Manipulation.Memory.store_database storage left_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ let t_right : Tuple.materialized =
+ {
+ Tuple.relation = "r";
+ attributes =
+ Tuple.AttributeMap.singleton "val" { Attribute.value = Obj.repr 20 };
+ }
+ in
+ let right_db, _, _ =
+ match Manipulation.Memory.create_tuple storage base_db rel t_right with
+ | Error _ -> assert false
+ | Ok p -> p
+ in
+ (match Manipulation.Memory.store_database storage right_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match
+ MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
+ ~left_tip:left_db.Management.Database.hash
+ ~right_tip:right_db.Management.Database.hash
+ with
+ | Error _ -> assert false
+ | Ok (Management.Merge.Failed _) -> assert false
+ | Ok (Management.Merge.Clean merged) ->
+ let merged_rel =
+ Option.get (Management.Database.get_relation merged "r")
+ in
+ assert (Manipulation.Memory.tuple_count merged_rel = 2))
let%test_unit "merge: no-op when both sides are identical" =
with_storage (fun storage ->
- let base_db = match Manipulation.Memory.create_database storage ~name:"db" with
- | Error _ -> assert false | Ok db -> db
- in
- (match Manipulation.Memory.store_database storage base_db with
- | Error _ -> assert false | Ok () -> ());
- match MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
- ~left_tip:base_db.Management.Database.hash
- ~right_tip:base_db.Management.Database.hash with
- | Error _ -> assert false
- | Ok (Management.Merge.Failed _) -> assert false
- | Ok (Management.Merge.Clean merged) ->
- assert (Management.Database.get_relation_names merged =
- Management.Database.get_relation_names base_db))
+ let base_db =
+ match Manipulation.Memory.create_database storage ~name:"db" with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (match Manipulation.Memory.store_database storage base_db with
+ | Error _ -> assert false
+ | Ok () -> ());
+ match
+ MergeMemory.merge ~storage ~strategy:Management.Merge.PreferLeft
+ ~left_tip:base_db.Management.Database.hash
+ ~right_tip:base_db.Management.Database.hash
+ with
+ | Error _ -> assert false
+ | Ok (Management.Merge.Failed _) -> assert false
+ | Ok (Management.Merge.Clean merged) ->
+ assert (
+ Management.Database.get_relation_names merged
+ = Management.Database.get_relation_names base_db))
(* Helper: look up polarity for a relation name in the result map *)
-let find_polarity name pols =
- Constraint.PolarityMap.find_opt name pols
+let find_polarity name pols = Constraint.PolarityMap.find_opt name pols
let%test_unit "polarity: MemberOf target is Positive" =
- let c = Constraint.MemberOf {
- target = "R";
- binding = Constraint.BindingMap.empty;
- } in
- assert (find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Positive)
+ let c =
+ Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty }
+ in
+ assert (
+ find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Positive)
let%test_unit "polarity: Not MemberOf flips to Negative" =
- let c = Constraint.Not {
- body = Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty };
- universe = "U";
- } in
- assert (find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Negative)
+ let c =
+ Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ { target = "R"; binding = Constraint.BindingMap.empty };
+ universe = "U";
+ }
+ in
+ assert (
+ find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Negative)
let%test_unit "polarity: Exists quantifier is Positive" =
- let c = Constraint.Exists {
- variable = "x";
- quantifier = "Q";
- body = Constraint.MemberOf { target = "Q"; binding = Constraint.BindingMap.empty };
- } in
- assert (find_polarity "Q" (Constraint.polarity_of c) = Some Constraint.Positive)
+ let c =
+ Constraint.Exists
+ {
+ variable = "x";
+ quantifier = "Q";
+ body =
+ Constraint.MemberOf
+ { target = "Q"; binding = Constraint.BindingMap.empty };
+ }
+ in
+ assert (
+ find_polarity "Q" (Constraint.polarity_of c) = Some Constraint.Positive)
let%test_unit "polarity: Forall quantifier is Negative" =
- let c = Constraint.Forall {
- variable = "x";
- quantifier = "Q";
- body = Constraint.MemberOf { target = "T"; binding = Constraint.BindingMap.empty };
- } in
- assert (find_polarity "Q" (Constraint.polarity_of c) = Some Constraint.Negative);
- assert (find_polarity "T" (Constraint.polarity_of c) = Some Constraint.Positive)
+ let c =
+ Constraint.Forall
+ {
+ variable = "x";
+ quantifier = "Q";
+ body =
+ Constraint.MemberOf
+ { target = "T"; binding = Constraint.BindingMap.empty };
+ }
+ in
+ assert (
+ find_polarity "Q" (Constraint.polarity_of c) = Some Constraint.Negative);
+ assert (
+ find_polarity "T" (Constraint.polarity_of c) = Some Constraint.Positive)
let%test_unit "polarity: same relation with both polarities merges to Both" =
(* Not(MemberOf R) AND MemberOf R => R appears Negative and Positive => Both *)
- let c = Constraint.And [
- Constraint.Not {
- body = Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty };
- universe = "U";
- };
- Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty };
- ] in
+ let c =
+ Constraint.And
+ [
+ Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ { target = "R"; binding = Constraint.BindingMap.empty };
+ universe = "U";
+ };
+ Constraint.MemberOf
+ { target = "R"; binding = Constraint.BindingMap.empty };
+ ]
+ in
assert (find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Both)
let%test_unit "polarity: Forall body MemberOf keeps Positive" =
(* Forall x in Q, MemberOf(T): T still has positive polarity *)
- let c = Constraint.Forall {
- variable = "x";
- quantifier = "Q";
- body = Constraint.MemberOf { target = "T"; binding = Constraint.BindingMap.empty };
- } in
- assert (find_polarity "T" (Constraint.polarity_of c) = Some Constraint.Positive)
+ let c =
+ Constraint.Forall
+ {
+ variable = "x";
+ quantifier = "Q";
+ body =
+ Constraint.MemberOf
+ { target = "T"; binding = Constraint.BindingMap.empty };
+ }
+ in
+ assert (
+ find_polarity "T" (Constraint.polarity_of c) = Some Constraint.Positive)
let%test_unit "polarity: nested Not double-negation restores Positive" =
- let c = Constraint.Not {
- body = Constraint.Not {
- body = Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty };
- universe = "U";
- };
- universe = "V";
- } in
- assert (find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Positive)
+ let c =
+ Constraint.Not
+ {
+ body =
+ Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ { target = "R"; binding = Constraint.BindingMap.empty };
+ universe = "U";
+ };
+ universe = "V";
+ }
+ in
+ assert (
+ find_polarity "R" (Constraint.polarity_of c) = Some Constraint.Positive)
let%test_unit "polarity: unrelated relation absent from result" =
- let c = Constraint.MemberOf {
- target = "R";
- binding = Constraint.BindingMap.empty;
- } in
+ let c =
+ Constraint.MemberOf { target = "R"; binding = Constraint.BindingMap.empty }
+ in
assert (find_polarity "S" (Constraint.polarity_of c) = None)
-let abs i = Obj.magic (i : int) (* lift int to AbstractValue for tests *)
+let abs i = Obj.magic (i : int) (* lift int to AbstractValue for tests *)
-let%test_unit "focused_filter: Var binding maps deleted value to constrained attr" =
+let%test_unit
+ "focused_filter: Var binding maps deleted value to constrained attr" =
(* Constraint: MemberOf Dept (dept_id = Var "dept_id")
Deleted: Dept { dept_id = 99 }
Expected filter: [("dept_id", 99)]
@@ -2953,11 +3608,11 @@ let%test_unit "focused_filter: Var binding maps deleted value to constrained att
Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id")
in
let c = Constraint.MemberOf { target = "Dept"; binding } in
- let deleted = [("dept_id", abs 99)] in
+ let deleted = [ ("dept_id", abs 99) ] in
let filter = Constraint.focused_filter c "Dept" deleted in
assert (List.length filter = 1);
assert (fst (List.hd filter) = "dept_id");
- assert (Stdlib.(=) (snd (List.hd filter)) (abs 99))
+ assert (Stdlib.( = ) (snd (List.hd filter)) (abs 99))
let%test_unit "focused_filter: Const binding is ignored (no var link)" =
(* Constraint: MemberOf Dept (code = Const "eng")
@@ -2979,7 +3634,9 @@ let%test_unit "focused_filter: Const binding is ignored (no var link)" =
(Constraint.Const (Obj.magic ("eng" : string)))
in
let c = Constraint.MemberOf { target = "Dept"; binding } in
- let filter = Constraint.focused_filter c "Dept" [("code", Obj.magic ("eng" : string))] in
+ let filter =
+ Constraint.focused_filter c "Dept" [ ("code", Obj.magic ("eng" : string)) ]
+ in
assert (filter = [])
let%test_unit "focused_filter: Exists body MemberOf same relation is followed" =
@@ -2998,12 +3655,15 @@ let%test_unit "focused_filter: Exists body MemberOf same relation is followed" =
let binding =
Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id")
in
- let c = Constraint.Exists {
- variable = "d";
- quantifier = "Dept";
- body = Constraint.MemberOf { target = "Dept"; binding };
- } in
- let filter = Constraint.focused_filter c "Dept" [("dept_id", abs 7)] in
+ let c =
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Dept";
+ body = Constraint.MemberOf { target = "Dept"; binding };
+ }
+ in
+ let filter = Constraint.focused_filter c "Dept" [ ("dept_id", abs 7) ] in
assert (List.length filter = 1);
assert (fst (List.hd filter) = "dept_id")
@@ -3015,11 +3675,9 @@ let%test_unit "focused_filter: unrelated dep_rel yields empty filter" =
the deletion cannot possibly affect any constrained tuple since no join
variable links them. The cascade checker uses [] as a signal to skip
the re-check for this constraint entirely, not to re-check everything. *)
- let binding =
- Constraint.BindingMap.singleton "x" (Constraint.Var "x")
- in
+ let binding = Constraint.BindingMap.singleton "x" (Constraint.Var "x") in
let c = Constraint.MemberOf { target = "R"; binding } in
- let filter = Constraint.focused_filter c "S" [("x", abs 1)] in
+ let filter = Constraint.focused_filter c "S" [ ("x", abs 1) ] in
assert (filter = [])
let%test_unit "trigger_constants: Const value in binding is extracted" =
@@ -3051,9 +3709,7 @@ let%test_unit "trigger_constants: Var binding produces no constant" =
It is a join variable, not a filter. Any deleted tuple from R could
match a constrained tuple via the Var link, so no early-exit is possible.
[] means: always proceed to the cascade scan. *)
- let binding =
- Constraint.BindingMap.singleton "id" (Constraint.Var "id")
- in
+ let binding = Constraint.BindingMap.singleton "id" (Constraint.Var "id") in
let c = Constraint.MemberOf { target = "R"; binding } in
let consts = Constraint.trigger_constants c "R" in
assert (consts = [])
@@ -3074,72 +3730,99 @@ let%test_unit "trigger_constants: unrelated dep_rel yields empty" =
let consts = Constraint.trigger_constants c "S" in
assert (consts = [])
-let%test_unit "substitute_transition: Var \"variable.attr\" is replaced by Const from transition tuple" =
+let%test_unit
+ "substitute_transition: Var \"variable.attr\" is replaced by Const from \
+ transition tuple" =
(* Constraint: Exists d in Department, MemberOf Target (key = Var "d.dept_id")
Transition: Department row { dept_id = 99 }
After substitution: Exists d in Department, MemberOf Target (key = Const 99)
The namespaced Var "d.dept_id" (quantifier-scoped) is replaced;
a base-tuple Var "dept_id" would not be. *)
let c =
- Constraint.Exists {
- variable = "d"; quantifier = "Department";
- body = Constraint.MemberOf {
- target = "Target";
- binding = Constraint.BindingMap.singleton "key" (Constraint.Var "d.dept_id");
- };
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body =
+ Constraint.MemberOf
+ {
+ target = "Target";
+ binding =
+ Constraint.BindingMap.singleton "key"
+ (Constraint.Var "d.dept_id");
+ };
+ }
+ in
+ let result =
+ Constraint.substitute_transition c "Department" [ ("dept_id", abs 99) ]
in
- let result = Constraint.substitute_transition c "Department" [("dept_id", abs 99)] in
- (match result with
- | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } ->
- (match Constraint.BindingMap.find_opt "key" binding with
- | Some (Constraint.Const v) -> assert (Stdlib.(=) v (abs 99))
+ match result with
+ | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } -> (
+ match Constraint.BindingMap.find_opt "key" binding with
+ | Some (Constraint.Const v) -> assert (Stdlib.( = ) v (abs 99))
| _ -> assert false)
- | _ -> assert false)
+ | _ -> assert false
let%test_unit "substitute_transition: base-tuple Var is not substituted" =
(* Constraint: Exists d in Department, MemberOf Target (key = Var "dept_id")
Var "dept_id" is a base-tuple reference (no "d." prefix), so it must
survive substitution unchanged. Only "d.dept_id" would be replaced. *)
let c =
- Constraint.Exists {
- variable = "d"; quantifier = "Department";
- body = Constraint.MemberOf {
- target = "Target";
- binding = Constraint.BindingMap.singleton "key" (Constraint.Var "dept_id");
- };
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body =
+ Constraint.MemberOf
+ {
+ target = "Target";
+ binding =
+ Constraint.BindingMap.singleton "key" (Constraint.Var "dept_id");
+ };
+ }
in
- let result = Constraint.substitute_transition c "Department" [("dept_id", abs 99)] in
- (match result with
- | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } ->
- (match Constraint.BindingMap.find_opt "key" binding with
+ let result =
+ Constraint.substitute_transition c "Department" [ ("dept_id", abs 99) ]
+ in
+ match result with
+ | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } -> (
+ match Constraint.BindingMap.find_opt "key" binding with
| Some (Constraint.Var "dept_id") -> ()
| _ -> assert false)
- | _ -> assert false)
+ | _ -> assert false
-let%test_unit "substitute_transition: non-matching quantifier relation is unchanged" =
+let%test_unit
+ "substitute_transition: non-matching quantifier relation is unchanged" =
(* Constraint: Exists d in Department, MemberOf Target (key = Var "d.dept_id")
Transition is for relation "Other", not "Department".
The body must survive unmodified. *)
let c =
- Constraint.Exists {
- variable = "d"; quantifier = "Department";
- body = Constraint.MemberOf {
- target = "Target";
- binding = Constraint.BindingMap.singleton "key" (Constraint.Var "d.dept_id");
- };
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body =
+ Constraint.MemberOf
+ {
+ target = "Target";
+ binding =
+ Constraint.BindingMap.singleton "key"
+ (Constraint.Var "d.dept_id");
+ };
+ }
+ in
+ let result =
+ Constraint.substitute_transition c "Other" [ ("dept_id", abs 99) ]
in
- let result = Constraint.substitute_transition c "Other" [("dept_id", abs 99)] in
- (match result with
- | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } ->
- (match Constraint.BindingMap.find_opt "key" binding with
+ match result with
+ | Constraint.Exists { body = Constraint.MemberOf { binding; _ }; _ } -> (
+ match Constraint.BindingMap.find_opt "key" binding with
| Some (Constraint.Var "d.dept_id") -> ()
| _ -> assert false)
- | _ -> assert false)
+ | _ -> assert false
-let%test_unit "substitute_transition: substitution applies through And and Not" =
+let%test_unit "substitute_transition: substitution applies through And and Not"
+ =
(* Constraint:
Exists d in Department,
And [
@@ -3149,75 +3832,112 @@ let%test_unit "substitute_transition: substitution applies through And and Not"
Transition: { x = 1, y = 2 }
After substitution: Var "d.x" -> Const 1, Var "d.y" -> Const 2 *)
let c =
- Constraint.Exists {
- variable = "d"; quantifier = "Department";
- body = Constraint.And [
- Constraint.MemberOf {
- target = "A";
- binding = Constraint.BindingMap.singleton "x" (Constraint.Var "d.x");
- };
- Constraint.Not {
- universe = "B";
- body = Constraint.MemberOf {
- target = "B";
- binding = Constraint.BindingMap.singleton "y" (Constraint.Var "d.y");
- };
- };
- ];
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body =
+ Constraint.And
+ [
+ Constraint.MemberOf
+ {
+ target = "A";
+ binding =
+ Constraint.BindingMap.singleton "x" (Constraint.Var "d.x");
+ };
+ Constraint.Not
+ {
+ universe = "B";
+ body =
+ Constraint.MemberOf
+ {
+ target = "B";
+ binding =
+ Constraint.BindingMap.singleton "y"
+ (Constraint.Var "d.y");
+ };
+ };
+ ];
+ }
+ in
+ let result =
+ Constraint.substitute_transition c "Department"
+ [ ("x", abs 1); ("y", abs 2) ]
in
- let result = Constraint.substitute_transition c "Department" [("x", abs 1); ("y", abs 2)] in
- (match result with
- | Constraint.Exists {
- body = Constraint.And [
- Constraint.MemberOf { binding = b1; _ };
- Constraint.Not { body = Constraint.MemberOf { binding = b2; _ }; _ };
- ]; _
- } ->
- (match Constraint.BindingMap.find_opt "x" b1,
- Constraint.BindingMap.find_opt "y" b2 with
+ match result with
+ | Constraint.Exists
+ {
+ body =
+ Constraint.And
+ [
+ Constraint.MemberOf { binding = b1; _ };
+ Constraint.Not
+ { body = Constraint.MemberOf { binding = b2; _ }; _ };
+ ];
+ _;
+ } -> (
+ match
+ ( Constraint.BindingMap.find_opt "x" b1,
+ Constraint.BindingMap.find_opt "y" b2 )
+ with
| Some (Constraint.Const v1), Some (Constraint.Const v2) ->
- assert (Stdlib.(=) v1 (abs 1));
- assert (Stdlib.(=) v2 (abs 2))
+ assert (Stdlib.( = ) v1 (abs 1));
+ assert (Stdlib.( = ) v2 (abs 2))
| _ -> assert false)
- | _ -> assert false)
+ | _ -> assert false
(* Build a minimal db with two relations and an FK constraint:
Department { dept_id } <-- Employee { emp_id, dept_id }
Constraint on Employee: Exists d in Department, MemberOf Department (dept_id = Var "dept_id") *)
let setup_fk_db storage =
- let db = match Manipulation.Memory.create_database storage ~name:"hr" with
- | Error _ -> assert false | Ok db -> db
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"hr" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Department"
- ~schema:(Schema.empty |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Department"
+ ~schema:(Schema.empty |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty
- |> Schema.add "emp_id" "natural"
- |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Employee"
+ ~schema:
+ (Schema.empty
+ |> Schema.add "emp_id" "natural"
+ |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
let fk_body =
let binding =
Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id")
in
- Constraint.Exists {
- variable = "d";
- quantifier = "Department";
- body = Constraint.MemberOf { target = "Department"; binding };
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body = Constraint.MemberOf { target = "Department"; binding };
+ }
in
- let db = match Manipulation.Memory.register_constraint storage db
- ~constraint_name:"fk_dept"
- ~relation_name:"Employee"
- ~body:fk_body with
- | Error _ -> assert false | Ok db -> db
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"fk_dept" ~relation_name:"Employee" ~body:fk_body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
in
db
-let%test_unit "extend_tuple namespacing: insert Employee with non-existent dept_id is rejected" =
+let%test_unit
+ "extend_tuple namespacing: insert Employee with non-existent dept_id is \
+ rejected" =
(* Regression test for the extend_tuple namespacing bug.
Without namespacing, extend_tuple would overwrite the Employee's dept_id=99
with the Department row's dept_id=1 during Exists iteration. Var "dept_id"
@@ -3226,202 +3946,346 @@ let%test_unit "extend_tuple namespacing: insert Employee with non-existent dept_
With namespacing, Department attributes are stored as d.dept_id=1 and the
base Employee attribute dept_id=99 is preserved, so the check correctly fails. *)
with_storage (fun storage ->
- let db = setup_fk_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 1);
- ("dept_id", Drl.Ast.Int 99)] }) with
- | Ok _ -> assert false (* dept_id=99 does not exist in Department *)
- | Error _ -> ())
+ let db = setup_fk_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 1); ("dept_id", Drl.Ast.Int 99) ];
+ })
+ with
+ | Ok _ -> assert false (* dept_id=99 does not exist in Department *)
+ | Error _ -> ())
let%test_unit "cascade: delete referenced row violates FK and is rejected" =
with_storage (fun storage ->
- let db = setup_fk_db storage in
- (* Insert Dept 1 and Employee referencing it *)
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let dept_rel = match Manipulation.Memory.get_relation db ~name:"Department" with
- | None -> assert false | Some r -> r
- in
- let dept_hash = Hashing.hash_tuple
- { Tuple.relation = "Department";
- attributes = Tuple.AttributeMap.singleton "dept_id"
- { Attribute.value = Obj.magic (1 : int) } }
- in
- (* Deleting Dept 1 while Employee references it must fail *)
- match Manipulation.Memory.retract_tuple storage db dept_rel ~tuple_hash:dept_hash with
- | Ok _ -> assert false (* should have been rejected *)
- | Error (Manipulation.ConstraintViolation msg) ->
- assert (String.length msg > 0)
- | Error _ -> assert false)
+ let db = setup_fk_db storage in
+ (* Insert Dept 1 and Employee referencing it *)
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let dept_rel =
+ match Manipulation.Memory.get_relation db ~name:"Department" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let dept_hash =
+ Hashing.hash_tuple
+ {
+ Tuple.relation = "Department";
+ attributes =
+ Tuple.AttributeMap.singleton "dept_id"
+ { Attribute.value = Obj.magic (1 : int) };
+ }
+ in
+ (* Deleting Dept 1 while Employee references it must fail *)
+ match
+ Manipulation.Memory.retract_tuple storage db dept_rel
+ ~tuple_hash:dept_hash
+ with
+ | Ok _ -> assert false (* should have been rejected *)
+ | Error (Manipulation.ConstraintViolation msg) ->
+ assert (String.length msg > 0)
+ | Error _ -> assert false)
let%test_unit "cascade: delete unreferenced row succeeds" =
with_storage (fun storage ->
- let db = setup_fk_db storage in
- (* Insert two departments; employee only references dept 1 *)
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 2)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let dept_rel = match Manipulation.Memory.get_relation db ~name:"Department" with
- | None -> assert false | Some r -> r
- in
- let dept2_hash = Hashing.hash_tuple
- { Tuple.relation = "Department";
- attributes = Tuple.AttributeMap.singleton "dept_id"
- { Attribute.value = Obj.magic (2 : int) } }
- in
- (* Deleting unreferenced Dept 2 must succeed *)
- match Manipulation.Memory.retract_tuple storage db dept_rel ~tuple_hash:dept2_hash with
- | Error _ -> assert false
- | Ok (new_db, _) ->
- let dept = match Manipulation.Memory.get_relation new_db ~name:"Department" with
- | None -> assert false | Some r -> r
+ let db = setup_fk_db storage in
+ (* Insert two departments; employee only references dept 1 *)
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 2) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
in
- assert (Manipulation.Memory.tuple_count dept = 1))
+ let dept_rel =
+ match Manipulation.Memory.get_relation db ~name:"Department" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let dept2_hash =
+ Hashing.hash_tuple
+ {
+ Tuple.relation = "Department";
+ attributes =
+ Tuple.AttributeMap.singleton "dept_id"
+ { Attribute.value = Obj.magic (2 : int) };
+ }
+ in
+ (* Deleting unreferenced Dept 2 must succeed *)
+ match
+ Manipulation.Memory.retract_tuple storage db dept_rel
+ ~tuple_hash:dept2_hash
+ with
+ | Error _ -> assert false
+ | Ok (new_db, _) ->
+ let dept =
+ match
+ Manipulation.Memory.get_relation new_db ~name:"Department"
+ with
+ | None -> assert false
+ | Some r -> r
+ in
+ assert (Manipulation.Memory.tuple_count dept = 1))
let%test_unit "cascade: Negative-polarity relation deletion is not checked" =
(* Constraint on Employee: NOT (MemberOf Blacklist (emp_id = Var "emp_id"))
Blacklist has Negative polarity (INSERT into Blacklist could violate).
DELETE from Blacklist should never trigger a cascade check. *)
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"hr" with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Blacklist"
- ~schema:(Schema.empty |> Schema.add "emp_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty |> Schema.add "emp_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let not_body =
- let binding =
- Constraint.BindingMap.singleton "emp_id" (Constraint.Var "emp_id")
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"hr" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- Constraint.Not {
- body = Constraint.MemberOf { target = "Blacklist"; binding };
- universe = "Employee";
- }
- in
- let db = match Manipulation.Memory.register_constraint storage db
- ~constraint_name:"not_blacklisted"
- ~relation_name:"Employee"
- ~body:not_body with
- | Error _ -> assert false | Ok db -> db
- in
- (* Insert emp_id=5 into Blacklist, then insert emp_id=99 into Employee (unrelated) *)
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Blacklist";
- attributes = [("emp_id", Drl.Ast.Int 5)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 99)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let bl_rel = match Manipulation.Memory.get_relation db ~name:"Blacklist" with
- | None -> assert false | Some r -> r
- in
- let bl_hash = Hashing.hash_tuple
- { Tuple.relation = "Blacklist";
- attributes = Tuple.AttributeMap.singleton "emp_id"
- { Attribute.value = Obj.magic (5 : int) } }
- in
- (* Deleting from Blacklist (negative polarity) must not trigger cascade check *)
- match Manipulation.Memory.retract_tuple storage db bl_rel ~tuple_hash:bl_hash with
- | Error _ -> assert false
- | Ok _ -> ())
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Blacklist"
+ ~schema:(Schema.empty |> Schema.add "emp_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Employee"
+ ~schema:(Schema.empty |> Schema.add "emp_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let not_body =
+ let binding =
+ Constraint.BindingMap.singleton "emp_id" (Constraint.Var "emp_id")
+ in
+ Constraint.Not
+ {
+ body = Constraint.MemberOf { target = "Blacklist"; binding };
+ universe = "Employee";
+ }
+ in
+ let db =
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"not_blacklisted" ~relation_name:"Employee"
+ ~body:not_body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Insert emp_id=5 into Blacklist, then insert emp_id=99 into Employee (unrelated) *)
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Blacklist";
+ attributes = [ ("emp_id", Drl.Ast.Int 5) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes = [ ("emp_id", Drl.Ast.Int 99) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let bl_rel =
+ match Manipulation.Memory.get_relation db ~name:"Blacklist" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let bl_hash =
+ Hashing.hash_tuple
+ {
+ Tuple.relation = "Blacklist";
+ attributes =
+ Tuple.AttributeMap.singleton "emp_id"
+ { Attribute.value = Obj.magic (5 : int) };
+ }
+ in
+ (* Deleting from Blacklist (negative polarity) must not trigger cascade check *)
+ match
+ Manipulation.Memory.retract_tuple storage db bl_rel ~tuple_hash:bl_hash
+ with
+ | Error _ -> assert false
+ | Ok _ -> ())
let%test_unit "cascade: deferred constraint not checked during retract_tuple" =
with_storage (fun storage ->
- let db = match Manipulation.Memory.create_database storage ~name:"hr" with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Department"
- ~schema:(Schema.empty |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty
- |> Schema.add "emp_id" "natural"
- |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- let fk_body =
- let binding =
- Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id")
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"hr" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- Constraint.Exists {
- variable = "d";
- quantifier = "Department";
- body = Constraint.MemberOf { target = "Department"; binding };
- }
- in
- (* Register as DEFERRED *)
- let db = match Manipulation.Memory.attach_constraint storage db
- ~constraint_name:"fk_dept_deferred"
- ~relation_name:"Employee"
- ~body:fk_body
- ~timing:Constraint.Deferred with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let dept_rel = match Manipulation.Memory.get_relation db ~name:"Department" with
- | None -> assert false | Some r -> r
- in
- let dept_hash = Hashing.hash_tuple
- { Tuple.relation = "Department";
- attributes = Tuple.AttributeMap.singleton "dept_id"
- { Attribute.value = Obj.magic (1 : int) } }
- in
- (* Deferred: retract_tuple itself should NOT reject the deletion *)
- match Manipulation.Memory.retract_tuple storage db dept_rel ~tuple_hash:dept_hash with
- | Error _ -> assert false (* deferred: must pass here *)
- | Ok (new_db, _) ->
- (* But check_deferred_constraints must catch the violation *)
- match Manipulation.Memory.check_deferred_constraints storage new_db with
- | Ok () -> assert false (* should have caught the violation *)
- | Error (Manipulation.ConstraintViolation _) -> ()
- | Error _ -> assert false)
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Department"
+ ~schema:(Schema.empty |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Employee"
+ ~schema:
+ (Schema.empty
+ |> Schema.add "emp_id" "natural"
+ |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ let fk_body =
+ let binding =
+ Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id")
+ in
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body = Constraint.MemberOf { target = "Department"; binding };
+ }
+ in
+ (* Register as DEFERRED *)
+ let db =
+ match
+ Manipulation.Memory.attach_constraint storage db
+ ~constraint_name:"fk_dept_deferred" ~relation_name:"Employee"
+ ~body:fk_body ~timing:Constraint.Deferred
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let dept_rel =
+ match Manipulation.Memory.get_relation db ~name:"Department" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let dept_hash =
+ Hashing.hash_tuple
+ {
+ Tuple.relation = "Department";
+ attributes =
+ Tuple.AttributeMap.singleton "dept_id"
+ { Attribute.value = Obj.magic (1 : int) };
+ }
+ in
+ (* Deferred: retract_tuple itself should NOT reject the deletion *)
+ match
+ Manipulation.Memory.retract_tuple storage db dept_rel
+ ~tuple_hash:dept_hash
+ with
+ | Error _ -> assert false (* deferred: must pass here *)
+ | Ok (new_db, _) -> (
+ (* But check_deferred_constraints must catch the violation *)
+ match
+ Manipulation.Memory.check_deferred_constraints storage new_db
+ with
+ | Ok () -> assert false (* should have caught the violation *)
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | Error _ -> assert false))
(* INSERT cascade tests.
@@ -3435,64 +4299,109 @@ let%test_unit "cascade: deferred constraint not checked during retract_tuple" =
Blacklist has Negative polarity in this constraint, so inserting into Blacklist
must trigger a re-check of Employee tuples. *)
let setup_blacklist_db storage =
- let db = match Manipulation.Memory.create_database storage ~name:"hr" with
- | Error _ -> assert false | Ok db -> db
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"hr" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Blacklist"
- ~schema:(Schema.empty |> Schema.add "emp_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Blacklist"
+ ~schema:(Schema.empty |> Schema.add "emp_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty |> Schema.add "emp_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Employee"
+ ~schema:(Schema.empty |> Schema.add "emp_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
let not_body =
- Constraint.Not {
- body = Constraint.MemberOf {
- target = "Blacklist";
- binding = Constraint.BindingMap.singleton "emp_id" (Constraint.Var "emp_id");
- };
- universe = "Employee";
- }
+ Constraint.Not
+ {
+ body =
+ Constraint.MemberOf
+ {
+ target = "Blacklist";
+ binding =
+ Constraint.BindingMap.singleton "emp_id"
+ (Constraint.Var "emp_id");
+ };
+ universe = "Employee";
+ }
in
- match Manipulation.Memory.register_constraint storage db
- ~constraint_name:"not_blacklisted"
- ~relation_name:"Employee"
- ~body:not_body with
- | Error _ -> assert false | Ok db -> db
+ match
+ Manipulation.Memory.register_constraint storage db
+ ~constraint_name:"not_blacklisted" ~relation_name:"Employee"
+ ~body:not_body
+ with
+ | Error _ -> assert false
+ | Ok db -> db
-let%test_unit "cascade INSERT: inserting into Blacklist violates constraint on existing Employee" =
+let%test_unit
+ "cascade INSERT: inserting into Blacklist violates constraint on existing \
+ Employee" =
(* Employee emp_id=5 already exists. Inserting Blacklist{emp_id=5} must be
rejected because the Employee constraint (NOT member of Blacklist) would
be violated for the existing emp_id=5 row. *)
with_storage (fun storage ->
- let db = setup_blacklist_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 5)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Blacklist";
- attributes = [("emp_id", Drl.Ast.Int 5)] }) with
- | Ok _ -> assert false (* emp_id=5 exists in Employee, must be rejected *)
- | Error _ -> ())
+ let db = setup_blacklist_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes = [ ("emp_id", Drl.Ast.Int 5) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Blacklist";
+ attributes = [ ("emp_id", Drl.Ast.Int 5) ];
+ })
+ with
+ | Ok _ -> assert false (* emp_id=5 exists in Employee, must be rejected *)
+ | Error _ -> ())
-let%test_unit "cascade INSERT: inserting into Blacklist with no matching Employee succeeds" =
+let%test_unit
+ "cascade INSERT: inserting into Blacklist with no matching Employee \
+ succeeds" =
(* Employee emp_id=99 does not exist. Inserting Blacklist{emp_id=5} must
succeed because no Employee tuple is affected. *)
with_storage (fun storage ->
- let db = setup_blacklist_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 99)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Blacklist";
- attributes = [("emp_id", Drl.Ast.Int 5)] }) with
- | Error _ -> assert false (* emp_id=5 not in Employee, must succeed *)
- | Ok _ -> ())
+ let db = setup_blacklist_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes = [ ("emp_id", Drl.Ast.Int 99) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Blacklist";
+ attributes = [ ("emp_id", Drl.Ast.Int 5) ];
+ })
+ with
+ | Error _ -> assert false (* emp_id=5 not in Employee, must succeed *)
+ | Ok _ -> ())
(* commit tests.
@@ -3500,108 +4409,179 @@ let%test_unit "cascade INSERT: inserting into Blacklist with no matching Employe
Individual mutations succeed even when they temporarily violate the constraint;
commit is the boundary where deferred violations are caught. *)
let setup_deferred_fk_db storage =
- let db = match Manipulation.Memory.create_database storage ~name:"hr" with
- | Error _ -> assert false | Ok db -> db
+ let db =
+ match Manipulation.Memory.create_database storage ~name:"hr" with
+ | Error _ -> assert false
+ | Ok db -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Department"
- ~schema:(Schema.empty |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Department"
+ ~schema:(Schema.empty |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
- let db = match Manipulation.Memory.create_relation storage db ~name:"Employee"
- ~schema:(Schema.empty
- |> Schema.add "emp_id" "natural"
- |> Schema.add "dept_id" "natural") with
- | Error _ -> assert false | Ok (db, _) -> db
+ let db =
+ match
+ Manipulation.Memory.create_relation storage db ~name:"Employee"
+ ~schema:
+ (Schema.empty
+ |> Schema.add "emp_id" "natural"
+ |> Schema.add "dept_id" "natural")
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
in
let fk_body =
- Constraint.Exists {
- variable = "d"; quantifier = "Department";
- body = Constraint.MemberOf {
- target = "Department";
- binding = Constraint.BindingMap.singleton "dept_id" (Constraint.Var "dept_id");
- };
- }
+ Constraint.Exists
+ {
+ variable = "d";
+ quantifier = "Department";
+ body =
+ Constraint.MemberOf
+ {
+ target = "Department";
+ binding =
+ Constraint.BindingMap.singleton "dept_id"
+ (Constraint.Var "dept_id");
+ };
+ }
in
- match Manipulation.Memory.attach_constraint storage db
- ~constraint_name:"fk_dept"
- ~relation_name:"Employee"
- ~body:fk_body
- ~timing:Constraint.Deferred with
- | Error _ -> assert false | Ok db -> db
+ match
+ Manipulation.Memory.attach_constraint storage db ~constraint_name:"fk_dept"
+ ~relation_name:"Employee" ~body:fk_body ~timing:Constraint.Deferred
+ with
+ | Error _ -> assert false
+ | Ok db -> db
let%test_unit "commit: no deferred constraints, returns Ok with db unchanged" =
with_storage (fun storage ->
- let db = setup_deferred_fk_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- (* All constraints satisfied, commit must pass *)
- match Manipulation.Memory.commit storage db with
- | Error _ -> assert false
- | Ok _ -> ())
+ let db = setup_deferred_fk_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* All constraints satisfied, commit must pass *)
+ match Manipulation.Memory.commit storage db with
+ | Error _ -> assert false
+ | Ok _ -> ())
let%test_unit "commit: deferred violation is caught at commit boundary" =
with_storage (fun storage ->
- let db = setup_deferred_fk_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let dept_rel = match Manipulation.Memory.get_relation db ~name:"Department" with
- | None -> assert false | Some r -> r
- in
- let dept_hash = Hashing.hash_tuple
- { Tuple.relation = "Department";
- attributes = Tuple.AttributeMap.singleton "dept_id"
- { Attribute.value = Obj.magic (1 : int) } }
- in
- (* Delete the referenced department. Deferred, so retract succeeds *)
- let db = match Manipulation.Memory.retract_tuple storage db dept_rel
- ~tuple_hash:dept_hash with
- | Error _ -> assert false | Ok (db, _) -> db
- in
- (* commit must now catch the orphaned Employee *)
- match Manipulation.Memory.commit storage db with
- | Ok _ -> assert false
- | Error (Manipulation.ConstraintViolation _) -> ()
- | Error _ -> assert false)
+ let db = setup_deferred_fk_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let dept_rel =
+ match Manipulation.Memory.get_relation db ~name:"Department" with
+ | None -> assert false
+ | Some r -> r
+ in
+ let dept_hash =
+ Hashing.hash_tuple
+ {
+ Tuple.relation = "Department";
+ attributes =
+ Tuple.AttributeMap.singleton "dept_id"
+ { Attribute.value = Obj.magic (1 : int) };
+ }
+ in
+ (* Delete the referenced department. Deferred, so retract succeeds *)
+ let db =
+ match
+ Manipulation.Memory.retract_tuple storage db dept_rel
+ ~tuple_hash:dept_hash
+ with
+ | Error _ -> assert false
+ | Ok (db, _) -> db
+ in
+ (* commit must now catch the orphaned Employee *)
+ match Manipulation.Memory.commit storage db with
+ | Ok _ -> assert false
+ | Error (Manipulation.ConstraintViolation _) -> ()
+ | Error _ -> assert false)
let%test_unit "commit: clears deferred list, second commit passes" =
(* After a successful commit the deferred list is empty.
A follow-up mutation + commit on the returned db must also work correctly
rather than re-running stale entries from the previous window. *)
with_storage (fun storage ->
- let db = setup_deferred_fk_db storage in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Department";
- attributes = [("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Dml.Executor.Memory.execute storage db
- (Dml.Ast.InsertTuple { relation = "Employee";
- attributes = [("emp_id", Drl.Ast.Int 10);
- ("dept_id", Drl.Ast.Int 1)] }) with
- | Error _ -> assert false | Ok db -> db
- in
- let db = match Manipulation.Memory.commit storage db with
- | Error _ -> assert false | Ok db -> db
- in
- (* Second commit with no new mutations must also pass *)
- match Manipulation.Memory.commit storage db with
- | Error _ -> assert false
- | Ok _ -> ())
+ let db = setup_deferred_fk_db storage in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Department";
+ attributes = [ ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match
+ Dml.Executor.Memory.execute storage db
+ (Dml.Ast.InsertTuple
+ {
+ relation = "Employee";
+ attributes =
+ [ ("emp_id", Drl.Ast.Int 10); ("dept_id", Drl.Ast.Int 1) ];
+ })
+ with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ let db =
+ match Manipulation.Memory.commit storage db with
+ | Error _ -> assert false
+ | Ok db -> db
+ in
+ (* Second commit with no new mutations must also pass *)
+ match Manipulation.Memory.commit storage db with
+ | Error _ -> assert false
+ | Ok _ -> ())