aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--bin/server.ml336
-rw-r--r--flake.nix4
-rw-r--r--lib/dbms_language.ml39
-rw-r--r--lib/dcl/executor.ml20
-rw-r--r--lib/dcl/parser.ml7
-rw-r--r--lib/dcl/sublanguage.ml23
-rw-r--r--lib/ddl/ast.ml16
-rw-r--r--lib/ddl/executor.ml68
-rw-r--r--lib/ddl/parser.ml14
-rw-r--r--lib/ddl/sublanguage.ml23
-rw-r--r--lib/dml/ast.ml15
-rw-r--r--lib/dml/executor.ml150
-rw-r--r--lib/dml/parser.ml5
-rw-r--r--lib/dml/sublanguage.ml23
-rw-r--r--lib/drl/executor.ml11
-rw-r--r--lib/drl/gate.ml82
-rw-r--r--lib/drl/parser.ml5
-rw-r--r--lib/drl/sublanguage.ml26
-rw-r--r--lib/drl_verified/drl_verified.ml76
-rw-r--r--lib/drl_verified/drl_verified.mli29
-rw-r--r--lib/envelope.ml18
-rw-r--r--lib/icl/executor.ml10
-rw-r--r--lib/icl/parser.ml5
-rw-r--r--lib/icl/sublanguage.ml23
-rw-r--r--lib/manipulation.ml18
-rw-r--r--lib/sublanguage.ml29
-rw-r--r--proof/.gitignore11
-rw-r--r--proof/Drl.v67
-rw-r--r--proof/Extraction.v17
-rw-r--r--proof/Makefile14
-rw-r--r--proof/Relation.v26
-rw-r--r--proof/Schema.v5
-rw-r--r--proof/_CoqProject6
-rw-r--r--proof/drl_verified.ml410
-rw-r--r--test/test_relational_engine.ml66
36 files changed, 1330 insertions, 371 deletions
diff --git a/.gitignore b/.gitignore
index fd81ad4..8f56c28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,5 @@
-_build/
-.direnv/
+_build
+.direnv
# Nix build/link artifacts
result
diff --git a/bin/server.ml b/bin/server.ml
index 2c84468..80f392b 100644
--- a/bin/server.ml
+++ b/bin/server.ml
@@ -35,6 +35,7 @@ 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
let get_branch storage =
match BranchOps.get_head storage with
@@ -76,96 +77,45 @@ let ok_response storage db_name db_hash msg =
List [Atom "branch"; Atom (get_branch storage)];
]
-let error_response storage db_name db_hash msg =
+let error_response storage db_name db_hash err_sexp =
let open Sexplib.Sexp in
to_string @@ List [
Atom "error";
- List [Atom "message"; Atom msg];
+ 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 manip_err = function
- | Manipulation.RelationNotFound s -> "RelationNotFound: " ^ s
- | Manipulation.RelationAlreadyExists s -> "RelationAlreadyExists: " ^ s
- | Manipulation.TupleNotFound h -> "TupleNotFound: " ^ h
- | Manipulation.DuplicateTuple h -> "DuplicateTuple: " ^ h
- | Manipulation.ConstraintViolation s -> "ConstraintViolation: " ^ s
- | Manipulation.StorageError s -> "StorageError: " ^ s
-
-(* TODO: language dispatch by trying parsers in sequence is a code smell —
- a valid DRL expression that happens to also parse as DML would silently
- be treated as DRL. The three sublanguages should share a common envelope,
- e.g. a top-level tag distinguishing (Query ...) from (Exec ...). *)
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)
| _ -> ()
+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);
+]
+
let execute_command storage db_ref cmd =
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
- (* TODO: (schema) is a stopgap. Introspection should be expressible in
- DRL itself via the catalog relations, not special-cased here. *)
- let cmd = match String.trim cmd with
- | "(schema)" -> {|(Base sakura:attribute)|}
- | s -> s
- in
- match Drl.Parser.of_string cmd with
- | Ok query ->
- (match Drl.Executor.Memory.execute storage db query with
- | Ok rel -> relation_to_sexp storage name h rel default_limit
- | Error (Drl.Executor.Memory.RelationNotFound s) ->
- err h ("RelationNotFound: " ^ s)
- | Error (Drl.Executor.Memory.AlgebraError (Algebra.StorageError s)) ->
- err h ("StorageError: " ^ s)
- | Error (Drl.Executor.Memory.AlgebraError (Algebra.GeneratorError s)) ->
- err h ("GeneratorError: " ^ s))
- | Error _ ->
- match Dml.Parser.of_string cmd with
- | Ok stmt ->
- (match Dml.Executor.Memory.execute storage db stmt with
- | Ok new_db ->
- db_ref := new_db;
- advance_head_branch storage new_db.Management.Database.hash;
- ok new_db.Management.Database.hash "updated"
- | Error (Dml.Executor.Memory.ManipulationError me) ->
- err h (manip_err me)
- | Error (Dml.Executor.Memory.RelationNotFound s) ->
- err h ("RelationNotFound: " ^ s)
- | Error (Dml.Executor.Memory.ParseError s) ->
- err h ("ParseError: " ^ s))
- | Error _ ->
- match Icl.Parser.of_string cmd with
- | Ok stmt ->
- (match Icl.Executor.Memory.execute storage db stmt with
- | Ok (new_db, msg) ->
- db_ref := new_db;
- advance_head_branch storage new_db.Management.Database.hash;
- ok new_db.Management.Database.hash msg
- | Error (Icl.Executor.Memory.ManipulationError me) ->
- err h (manip_err me)
- | Error (Icl.Executor.Memory.ConversionError s) ->
- err h ("ConversionError: " ^ s))
- | Error _ ->
- match Dcl.Parser.of_string cmd with
- | Ok stmt ->
- (match Dcl.Executor.Memory.execute storage db stmt with
- | Ok (new_db, msg) ->
- db_ref := new_db;
- advance_head_branch storage new_db.Management.Database.hash;
- ok new_db.Management.Database.hash msg
- | Error (Dcl.Executor.Memory.BranchError s) ->
- err h ("BranchError: " ^ s)
- | Error (Dcl.Executor.Memory.MergeError s) ->
- err h ("MergeError: " ^ s))
- | Error _ ->
- err h "ParseError: not valid DRL, DML, ICL, or DCL"
+ match Dispatch.execute dbms_dispatch storage db cmd with
+ | Ok (Sublanguage.Query rel) ->
+ 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
+ | 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
@@ -184,7 +134,7 @@ let register_prelude_relations storage db =
| Ok (new_db, _) -> new_db
| Error e ->
Printf.eprintf "Warning: failed to register prelude relation %s: %s\n%!"
- rel.name (manip_err e);
+ rel.name (Manipulation.string_of_error e);
db)
db
[ less_than_natural
@@ -199,6 +149,18 @@ let register_prelude_relations storage db =
; divide_natural
]
+let print_with_time str =
+ let now = Unix.gettimeofday () in
+ let tm = Unix.localtime now in
+ 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
+ in
+ print_endline @@ Printf.sprintf "%s[%s]%s %s" orange formatted_time reset str
+
(* TODO: single-threaded accept loop — one slow or hung client blocks all
others. Should use threads or non-blocking I/O. *)
let handle_client storage db_ref fd =
@@ -215,183 +177,75 @@ let handle_client storage db_ref fd =
let db = !db_ref in
error_response storage db.Management.Database.name
db.Management.Database.hash
- ("Uncaught error: " ^ Printexc.to_string e)
+ 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 depart_emp = [
- "(CreateRelation (name \"Department\") (schema ((dept_id \"integer\") (dept_name \"string\") (location \"string\"))))";
- "(CreateRelation (name \"Employee\") (schema ((emp_id \"integer\") (emp_name \"string\") (salary \"float\") (dept_id \"integer\") (hire_date \"string\"))))";
- "(RegisterConstraint(constraint_name \"salary_positive\")(relation_name \"Employee\")(body (MemberOf(target \"positive_reals\")(binding ((salary (Var \"salary\")))))))";
- "(RegisterConstraint
- (constraint_name \"fk_employee_dept\")
- (relation_name \"Employee\")
- (body (MemberOf
- (target \"Department\")
- (binding ((dept_id (Var \"dept_id\")))))))";
- "(InsertTuple (relation \"Department\") (attributes ((dept_id (Int 1)) (location (Str \"Gifu\")) (dept_name (Str \"Nippon Ichi\")))))";
- "(InsertTuples (relation \"Employee\") (tuples (((emp_id (Int 101)) (emp_name (Str \"Alice Johnson\")) (salary (Float 95000.0)) (dept_id (Int 1)) (hire_date (Str \"2020-01-15\"))))))"
- ]
-
-let _depart_emp () =
- match Management.Physical.Memory.create () with
- | Error _ -> failwith "Failed to create storage"
- | Ok storage ->
- match Manipulation.Memory.create_database storage ~name:"sakura" with
- | Error _ -> failwith "Failed to create initial database"
- | Ok db ->
- let db = register_prelude_relations storage db in
- let db_ref = ref db in
- Printf.printf "Database hash: %s\n%!" (short_hash db.Management.Database.hash);
- List.iter (fun cmd -> print_endline @@ execute_command storage db_ref cmd)
- depart_emp;
- ()
-
-let n_way = [
- "(CreateRelation
- (name \"Building\")
- (schema
- ((building_id \"integer\")
- (building_name \"string\")
- (floors \"integer\"))))";
- "(CreateRelation
- (name \"Room\")
- (schema
- ((room_id \"integer\")
- (building_id \"integer\")
- (floor \"integer\")
- (room_number \"string\"))))";
-"(CreateRelation
- (name \"Suite\")
- (schema
- ((suite_id \"integer\")
- (room_id \"integer\")
- (suite_name \"string\")
- (capacity \"integer\"))))";
-(* FK: every Room must belong to an existing Building *)
-"(RegisterConstraint
- (constraint_name \"fk_room_building\")
- (relation_name \"Room\")
- (body (MemberOf
- (target \"Building\")
- (binding ((building_id (Var \"building_id\")))))))";
-(* FK: every Suite must belong to an existing Room *)
-"(RegisterConstraint
- (constraint_name \"fk_suite_room\")
- (relation_name \"Suite\")
- (body (MemberOf
- (target \"Room\")
- (binding ((room_id (Var \"room_id\")))))))";
-(* A suite is only valid if its room belongs to a building with more than 3 floors *)
-"(RegisterConstraint
- (constraint_name \"suite_in_tall_building\")
- (relation_name \"Suite\")
- (body
- (Exists (variable \"r\") (quantifier \"Room\")
- (body
- (Exists (variable \"b\") (quantifier \"Building\")
- (body
- (And
- ((MemberOf (target \"Room\")
- (binding ((room_id (Var \"room_id\")))))
- (MemberOf (target \"Building\")
- (binding ((building_id (Var \"r.building_id\")))))
- (MemberOf (target \"greater_than_natural\")
- (binding ((left (Var \"b.floors\")) (right (Const (Int 3))))))))))))))";
-"(InsertTuple
- (relation \"Building\")
- (attributes
- ((building_id (Int 2))
- (building_name (Str \"Tower B\"))
- (floors (Int 2)))))";
- "(InsertTuple
- (relation \"Building\")
- (attributes
- ((building_id (Int 1))
- (building_name (Str \"Tower A\"))
- (floors (Int 10)))))";
-"(InsertTuples
- (relation \"Room\")
- (tuples
- (((room_id (Int 101)) (building_id (Int 1)) (floor (Int 1)) (room_number (Str \"1A\")))
- ((room_id (Int 102)) (building_id (Int 1)) (floor (Int 2)) (room_number (Str \"2A\")))
- ((room_id (Int 201)) (building_id (Int 2)) (floor (Int 1)) (room_number (Str \"1B\"))))))";
-"(InsertTuples
- (relation \"Suite\")
- (tuples
- (((suite_id (Int 1001)) (room_id (Int 101)) (suite_name (Str \"Presidential\")) (capacity (Int 4)))
- ((suite_id (Int 1002)) (room_id (Int 101)) (suite_name (Str \"Standard\")) (capacity (Int 2)))
- ((suite_id (Int 1003)) (room_id (Int 102)) (suite_name (Str \"Deluxe\")) (capacity (Int 3))))))";
- ]
+let setup () =
+ let ( let* ) = Result.bind in
+ let* storage =
+ Management.Physical.Memory.create ()
+ |> Result.map_error (Fun.const "Failed to create storage")
+ in
+ let* db =
+ Manipulation.Memory.create_database storage ~name:"sakura"
+ |> Result.map_error (Fun.const "Failed to create initial database")
+ in
+ Ok (storage, db)
+
+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
+ with
+ | Ok (db, _) -> db
+ | Error e ->
+ 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 _n_way () =
- match Management.Physical.Memory.create () with
- | Error _ -> failwith "Failed to create storage"
- | Ok storage ->
- match Manipulation.Memory.create_database storage ~name:"sakura" with
- | Error _ -> failwith "Failed to create initial database"
- | Ok db ->
- let db = register_prelude_relations storage db in
- let db_ref = ref db in
- Printf.printf "Database hash: %s\n%!" (short_hash db.Management.Database.hash);
- List.iter (fun cmd -> print_endline @@ execute_command storage db_ref cmd)
- n_way;
- ()
+let warn_on_error fmt = function
+ | Ok () -> ()
+ | Error e -> Printf.eprintf fmt e
let () =
- let port = default_port in
- match Management.Physical.Memory.create () with
- | Error _ -> failwith "Failed to create storage"
- | Ok storage ->
- match Manipulation.Memory.create_database storage ~name:"sakura" with
- | Error _ -> failwith "Failed to create initial database"
- | Ok db ->
- let db = register_prelude_relations storage db in
- (* Register ephemeral sakura:branch and sakura:head relations backed by
- the raw branch registry — no stored tuples, no circularity. *)
- let db =
- 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
- with
- | Ok (db, _) -> db
- | Error e ->
- Printf.eprintf "Warning: failed to register %s: %s\n%!"
- rel.Relation.name (match e with
- | Manipulation.RelationAlreadyExists s -> "already exists: " ^ s
- | Manipulation.StorageError s -> s | _ -> "error");
- db
- in
- let db = register db (BranchOps.branch_relation storage) in
- let db = register db (BranchOps.head_relation storage) in
- db
- in
- (* Create default master branch in raw registry and set HEAD *)
- (match BranchOps.create storage ~name:"master"
- ~tip:db.Management.Database.hash with
- | Error e -> Printf.eprintf "Warning: failed to create master branch: %s\n%!" e
- | Ok () -> ());
- (match BranchOps.checkout storage "master" with
- | Error e -> Printf.eprintf "Warning: failed to checkout master: %s\n%!" e
- | Ok () -> ());
- let db_ref = ref db 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, port));
- Unix.listen sock 5;
- Printf.printf "Sakura server listening on 127.0.0.1:%d\n" 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 _ -> ())
- done
+ match setup () with
+ | Error msg -> failwith msg
+ | Ok (storage, db) ->
+ let db = db
+ |> register_prelude_relations storage
+ |> register_branch_relations storage
+ in
+ (* 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 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 _ -> ())
+ done
diff --git a/flake.nix b/flake.nix
index 1976555..6a1e1a2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -275,6 +275,10 @@
ppx_protocol_conv
ppx_protocol_conv_xml_light
ocamlPackages.earlybird
+ # Formal verification
+ legacyPackages.coq
+ legacyPackages.coqPackages.stdlib
+ legacyPackages.z3
];
diff --git a/lib/dbms_language.ml b/lib/dbms_language.ml
new file mode 100644
index 0000000..8d61aa3
--- /dev/null
+++ b/lib/dbms_language.ml
@@ -0,0 +1,39 @@
+(** 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]. *)
+
+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
+ 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))
+
+ let sexp_of_dispatch_error (NoMatch sexp) = sexp
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/dcl/executor.ml b/lib/dcl/executor.ml
index bf549ec..cf2cfed 100644
--- a/lib/dcl/executor.ml
+++ b/lib/dcl/executor.ml
@@ -4,22 +4,22 @@ module Make (Storage : Management.Physical.S with type error = string) = struct
module MergeOps = Management.Merge.Make(Storage)(Ops)
type error =
+ | ParseError of string
| BranchError 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]
+
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 manip_err = function
- | Manipulation.RelationNotFound s -> "RelationNotFound: " ^ s
- | Manipulation.RelationAlreadyExists s -> "RelationAlreadyExists: " ^ s
- | Manipulation.TupleNotFound h -> "TupleNotFound: " ^ h
- | Manipulation.DuplicateTuple h -> "DuplicateTuple: " ^ h
- | Manipulation.ConstraintViolation s -> "ConstraintViolation: " ^ s
- | Manipulation.StorageError s -> "StorageError: " ^ s
-
let execute
(storage : Storage.t)
(db : Management.Database.t)
@@ -45,7 +45,7 @@ module Make (Storage : Management.Physical.S with type error = string) = struct
| Error e -> Error (BranchError e)
| Ok () ->
(match Ops.load_database storage tip with
- | Error e -> Error (BranchError (manip_err e))
+ | 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))))
@@ -75,7 +75,7 @@ module Make (Storage : Management.Physical.S with type error = string) = struct
| 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 (manip_err e))
+ | Error e -> Error (MergeError (Manipulation.string_of_error e))
| Ok (Management.Merge.Failed conflicts) ->
let msgs = List.map (function
| Management.Merge.TupleConflict { relation; hash } ->
diff --git a/lib/dcl/parser.ml b/lib/dcl/parser.ml
index 3caf748..ef64924 100644
--- a/lib/dcl/parser.ml
+++ b/lib/dcl/parser.ml
@@ -1,9 +1,9 @@
type error = ParseError of string
-let of_string s =
+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 Sexplib.Sexp.of_string s with
+ let sexp = match sexp with
| Sexplib.Sexp.List [atom] -> atom
| other -> other
in
@@ -11,5 +11,8 @@ let of_string s =
| 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
diff --git a/lib/dcl/sublanguage.ml b/lib/dcl/sublanguage.ml
new file mode 100644
index 0000000..112b6ed
--- /dev/null
+++ b/lib/dcl/sublanguage.ml
@@ -0,0 +1,23 @@
+module Make (Storage : Management.Physical.S with type error = string) = struct
+ module Exec = Executor.Make(Storage)
+
+ type storage = Storage.t
+ type ast = Ast.statement
+ type error = Exec.error
+
+ let name = "dcl"
+
+ let parse_sexp sexp =
+ match Parser.of_sexp sexp with
+ | Ok r -> Ok r
+ | Error (Parser.ParseError s) -> Error (Exec.ParseError s)
+
+ let execute storage db ast =
+ match Exec.execute storage db ast with
+ | Ok (db, msg) -> Ok (Sublanguage.Transition (db, msg))
+ | Error e -> Error e
+
+ let sexp_of_error = Exec.sexp_of_error
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/ddl/ast.ml b/lib/ddl/ast.ml
new file mode 100644
index 0000000..7faee40
--- /dev/null
+++ b/lib/ddl/ast.ml
@@ -0,0 +1,16 @@
+open Sexplib.Std
+
+type cardinality_spec =
+ | Finite of int
+ | AlephZero
+ | Continuum
+ | ConstrainedFinite
+[@@deriving sexp]
+
+type statement =
+ | CreateDatabase of string
+ | CreateRelation of { name: string; schema: (string * string) list }
+ | RetractRelation of string
+ | ClearRelation of string
+ | RegisterDomain of { name: string; cardinality: cardinality_spec }
+[@@deriving sexp]
diff --git a/lib/ddl/executor.ml b/lib/ddl/executor.ml
new file mode 100644
index 0000000..fbb2f62
--- /dev/null
+++ b/lib/ddl/executor.ml
@@ -0,0 +1,68 @@
+module Make (Storage : Management.Physical.S) = struct
+ module Ops = Manipulation.Make(Storage)
+
+ type error =
+ | ParseError of string
+ | ManipulationError of Manipulation.error
+ | RelationNotFound of string
+
+ 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]
+
+ 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)
+
+ 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 =
+ match stmt with
+ | Ast.CreateDatabase 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)
+
+ | Ast.RetractRelation 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)
+
+ | 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)
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/ddl/parser.ml b/lib/ddl/parser.ml
new file mode 100644
index 0000000..28f8b6c
--- /dev/null
+++ b/lib/ddl/parser.ml
@@ -0,0 +1,14 @@
+type error = ParseError of string
+
+let of_sexp sexp =
+ match Ast.statement_of_sexp sexp with
+ | 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
+ | exception exn -> Error (ParseError (Printexc.to_string exn))
+
+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
new file mode 100644
index 0000000..26a012f
--- /dev/null
+++ b/lib/ddl/sublanguage.ml
@@ -0,0 +1,23 @@
+module Make (Storage : Management.Physical.S) = struct
+ module Exec = Executor.Make(Storage)
+
+ type storage = Storage.t
+ type ast = Ast.statement
+ type error = Exec.error
+
+ let name = "ddl"
+
+ let parse_sexp sexp =
+ match Parser.of_sexp sexp with
+ | Ok r -> Ok r
+ | Error (Parser.ParseError s) -> Error (Exec.ParseError s)
+
+ let execute storage db ast =
+ match Exec.execute storage db ast with
+ | Ok (db, msg) -> Ok (Sublanguage.Transition (db, msg))
+ | Error e -> Error e
+
+ let sexp_of_error = Exec.sexp_of_error
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/dml/ast.ml b/lib/dml/ast.ml
index 4a903e7..479437a 100644
--- a/lib/dml/ast.ml
+++ b/lib/dml/ast.ml
@@ -3,20 +3,11 @@ open Sexplib.Std
type attr_value = string * Drl.Ast.value
[@@deriving sexp]
-type cardinality_spec =
- | Finite of int
- | AlephZero
- | Continuum
- | ConstrainedFinite
-[@@deriving sexp]
-
type statement =
- | CreateDatabase of string
- | CreateRelation of { name: string; schema: (string * string) list }
- | RetractRelation of string
- | ClearRelation of string
- | RegisterDomain of { name: string; cardinality: cardinality_spec }
| 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 3a8b05a..7c08a24 100644
--- a/lib/dml/executor.ml
+++ b/lib/dml/executor.ml
@@ -1,19 +1,35 @@
module Make (Storage : Management.Physical.S) = struct
module Ops = Manipulation.Make(Storage)
+ module Alg = Algebra.Make(Storage)
+ module DrlExec = Drl.Executor.Make(Storage)
type error =
+ | ParseError of string
| ManipulationError of Manipulation.error
- | RelationNotFound of string
- | ParseError of string
+ | 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]
+
+ let ( let* ) = Result.bind
let wrap_manip r = Result.map_error (fun e -> ManipulationError e) r
- (** Convert a cardinality_spec AST node to a Conventions.Cardinality.t *)
- 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 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)
+
+ 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 =
@@ -28,78 +44,74 @@ module Make (Storage : Management.Physical.S) = struct
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.RelationNotFound s) -> Error (RelationNotFound s)
+ | 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 =
match stmt with
- | Ast.CreateDatabase name ->
- Ops.create_database storage ~name |> wrap_manip
-
- | 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
- (match Ops.create_relation storage db ~name ~schema |> wrap_manip with
- | Ok (db, _rel) -> Ok db
- | Error e -> Error e)
-
- | Ast.RetractRelation name ->
- Ops.retract_relation storage db ~name |> wrap_manip
-
- | Ast.ClearRelation name ->
- (match Ops.get_relation db ~name with
- | None -> Error (RelationNotFound name)
- | Some rel ->
- match Ops.clear_relation storage db rel |> wrap_manip with
- | Ok (db, _rel) -> Ok db
- | Error e -> Error e)
-
- | Ast.RegisterDomain { name; cardinality } ->
- (* TODO: Custom generators, membership_criteria, and comparators
- are not supported via DML. Use the OCaml API for full-featured
- domain definitions (e.g., custom enumeration, type-specific
- comparison, or domain-specific validation). *)
- let domain = Domain.make
- ~name
- ~generator:(fun _ -> Generator.Error "not enumerable via DML")
- ~membership_criteria:(fun _ -> true)
- ~cardinality:(convert_cardinality cardinality)
- ~compare:Stdlib.compare
- in
- Ops.register_domain storage db domain |> wrap_manip
-
| Ast.InsertTuple { relation; attributes } ->
- (match Ops.get_relation db ~name:relation with
- | None -> Error (RelationNotFound relation)
- | Some rel ->
- let tuple = build_tuple ~relation attributes in
- match Ops.create_tuple storage db rel tuple |> wrap_manip with
- | Ok (db, _rel, _hash) -> Ok db
- | Error e -> Error e)
+ 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 } ->
- (match Ops.get_relation db ~name:relation with
- | None -> Error (RelationNotFound relation)
- | Some rel ->
- let tuple_list = List.map (build_tuple ~relation) tuples in
- match Ops.create_tuples storage db rel tuple_list |> wrap_manip with
- | Ok (db, _rel, _hashes) -> Ok db
- | Error e -> Error e)
+ 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 } ->
- (match Ops.get_relation db ~name:relation with
- | None -> Error (RelationNotFound relation)
- | Some rel ->
- let tuple = build_tuple ~relation attributes in
- let tuple_hash = Hashing.hash_tuple tuple in
- match Ops.retract_tuple storage db rel ~tuple_hash |> wrap_manip with
- | Ok (db, _rel) -> Ok db
- | Error e -> Error e)
+ 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
+
+ | 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
+
+ | 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
end
module Memory = Make(Management.Physical.Memory)
diff --git a/lib/dml/parser.ml b/lib/dml/parser.ml
index a7981c0..28f8b6c 100644
--- a/lib/dml/parser.ml
+++ b/lib/dml/parser.ml
@@ -1,5 +1,10 @@
type error = ParseError of string
+let of_sexp sexp =
+ match Ast.statement_of_sexp sexp with
+ | 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
diff --git a/lib/dml/sublanguage.ml b/lib/dml/sublanguage.ml
new file mode 100644
index 0000000..29f951e
--- /dev/null
+++ b/lib/dml/sublanguage.ml
@@ -0,0 +1,23 @@
+module Make (Storage : Management.Physical.S) = struct
+ module Exec = Executor.Make(Storage)
+
+ type storage = Storage.t
+ type ast = Ast.statement
+ type error = Exec.error
+
+ let name = "dml"
+
+ let parse_sexp sexp =
+ match Parser.of_sexp sexp with
+ | Ok r -> Ok r
+ | Error (Parser.ParseError s) -> Error (Exec.ParseError s)
+
+ let execute storage db ast =
+ match Exec.execute storage db ast with
+ | Ok db -> Ok (Sublanguage.Transition (db, "updated"))
+ | Error e -> Error e
+
+ let sexp_of_error = Exec.sexp_of_error
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/drl/executor.ml b/lib/drl/executor.ml
index 27f52c2..322fada 100644
--- a/lib/drl/executor.ml
+++ b/lib/drl/executor.ml
@@ -3,8 +3,17 @@ module Make(Storage : Management.Physical.S) = struct
module Ops = Manipulation.Make(Storage)
type error =
+ | 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]
let wrap = Result.map_error (fun e -> AlgebraError e)
diff --git a/lib/drl/gate.ml b/lib/drl/gate.ml
new file mode 100644
index 0000000..222e935
--- /dev/null
+++ b/lib/drl/gate.ml
@@ -0,0 +1,82 @@
+(** 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. *)
+
+(* -- string -> char list impedance --------------------------------- *)
+
+let string_to_chars (s : string) : char list =
+ List.init (String.length s) (String.get s)
+
+(* -- 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.Const 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)
+ | Ast.Join (attrs, q1, 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)
+ | Ast.Project (attrs, 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)
+
+and value_of_ast_value : Ast.value -> Drl_verified.literal = function
+ | 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
+
+(* -- 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
+
+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
+ []
+
+(* -- public interface ---------------------------------------------- *)
+
+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"
diff --git a/lib/drl/parser.ml b/lib/drl/parser.ml
index 71d694b..36ce15a 100644
--- a/lib/drl/parser.ml
+++ b/lib/drl/parser.ml
@@ -1,5 +1,10 @@
type error = ParseError of string
+let of_sexp sexp =
+ match Ast.query_of_sexp sexp with
+ | 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
diff --git a/lib/drl/sublanguage.ml b/lib/drl/sublanguage.ml
new file mode 100644
index 0000000..f57af95
--- /dev/null
+++ b/lib/drl/sublanguage.ml
@@ -0,0 +1,26 @@
+module Make (Storage : Management.Physical.S) = struct
+ module Exec = Executor.Make(Storage)
+
+ type storage = Storage.t
+ type ast = Ast.query
+ type error = Exec.error
+
+ let name = "drl"
+
+ let parse_sexp sexp =
+ match Parser.of_sexp sexp with
+ | Ok r -> Ok r
+ | Error (Parser.ParseError s) -> Error (Exec.ParseError s)
+
+ 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
+
+ let sexp_of_error = Exec.sexp_of_error
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/drl_verified/drl_verified.ml b/lib/drl_verified/drl_verified.ml
new file mode 100644
index 0000000..b5aeaa7
--- /dev/null
+++ b/lib/drl_verified/drl_verified.ml
@@ -0,0 +1,76 @@
+
+(** 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)
+
+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
+
+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
+
+(** 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)
+ | 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)
+ | 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)
+ | None -> None)
+ | None -> None)
+| Diff (q1, _) -> predicted_finite db q1
+| _ -> Some true
diff --git a/lib/drl_verified/drl_verified.mli b/lib/drl_verified/drl_verified.mli
new file mode 100644
index 0000000..31acc7a
--- /dev/null
+++ b/lib/drl_verified/drl_verified.mli
@@ -0,0 +1,29 @@
+
+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 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/envelope.ml b/lib/envelope.ml
new file mode 100644
index 0000000..d8404d9
--- /dev/null
+++ b/lib/envelope.ml
@@ -0,0 +1,18 @@
+(** 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. *)
+
+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 }
+ | _ -> Error "expected (tag payload...)"
+ | exception exn -> Error (Printexc.to_string exn)
diff --git a/lib/icl/executor.ml b/lib/icl/executor.ml
index a895135..591f839 100644
--- a/lib/icl/executor.ml
+++ b/lib/icl/executor.ml
@@ -2,8 +2,16 @@ module Make (Storage : Management.Physical.S) = struct
module Ops = Manipulation.Make(Storage)
type error =
+ | ParseError of string
| ManipulationError of Manipulation.error
- | ConversionError of string
+ | ConversionError of string
+
+ 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
+ | ConversionError s -> List [Atom "conversion-error"; Atom s]
let wrap_manip = Result.map_error (fun e -> ManipulationError e)
diff --git a/lib/icl/parser.ml b/lib/icl/parser.ml
index a7981c0..28f8b6c 100644
--- a/lib/icl/parser.ml
+++ b/lib/icl/parser.ml
@@ -1,5 +1,10 @@
type error = ParseError of string
+let of_sexp sexp =
+ match Ast.statement_of_sexp sexp with
+ | 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
diff --git a/lib/icl/sublanguage.ml b/lib/icl/sublanguage.ml
new file mode 100644
index 0000000..4e74082
--- /dev/null
+++ b/lib/icl/sublanguage.ml
@@ -0,0 +1,23 @@
+module Make (Storage : Management.Physical.S) = struct
+ module Exec = Executor.Make(Storage)
+
+ type storage = Storage.t
+ type ast = Ast.statement
+ type error = Exec.error
+
+ let name = "icl"
+
+ let parse_sexp sexp =
+ match Parser.of_sexp sexp with
+ | Ok r -> Ok r
+ | Error (Parser.ParseError s) -> Error (Exec.ParseError s)
+
+ let execute storage db ast =
+ match Exec.execute storage db ast with
+ | Ok (db, msg) -> Ok (Sublanguage.Transition (db, msg))
+ | Error e -> Error e
+
+ let sexp_of_error = Exec.sexp_of_error
+end
+
+module Memory = Make(Management.Physical.Memory)
diff --git a/lib/manipulation.ml b/lib/manipulation.ml
index 73607fa..be77c02 100644
--- a/lib/manipulation.ml
+++ b/lib/manipulation.ml
@@ -23,6 +23,24 @@ type error =
| ConstraintViolation of string
| StorageError of string
+let string_of_error = function
+ | RelationNotFound s -> "RelationNotFound: " ^ s
+ | RelationAlreadyExists s -> "RelationAlreadyExists: " ^ s
+ | TupleNotFound h -> "TupleNotFound: " ^ h
+ | DuplicateTuple h -> "DuplicateTuple: " ^ h
+ | ConstraintViolation s -> "ConstraintViolation: " ^ s
+ | StorageError s -> "StorageError: " ^ s
+
+let sexp_of_error e =
+ let open Sexplib.Sexp in
+ match e with
+ | RelationNotFound s -> List [Atom "relation-not-found"; Atom s]
+ | RelationAlreadyExists s -> List [Atom "relation-already-exists"; Atom s]
+ | TupleNotFound h -> List [Atom "tuple-not-found"; Atom h]
+ | DuplicateTuple h -> List [Atom "duplicate-tuple"; Atom h]
+ | ConstraintViolation s -> List [Atom "constraint-violation"; Atom s]
+ | StorageError s -> List [Atom "storage-error"; Atom s]
+
(** Result type for operations *)
type 'a result = ('a, error) Result.t
diff --git a/lib/sublanguage.ml b/lib/sublanguage.ml
new file mode 100644
index 0000000..3df91f1
--- /dev/null
+++ b/lib/sublanguage.ml
@@ -0,0 +1,29 @@
+(** Effect classification for sublanguage operations.
+
+ Categorically:
+ - Query: a natural transformation between instance functors (DRL).
+ Side-effect-free; the database state is unchanged.
+ - Transition: a morphism in the category of database states (DDL,
+ DML, ICL, DCL, and future TCL). Produces a new state via
+ substitution (Harper, PFPL ch. 3-4).
+
+ Future sublanguages:
+ - TCL -> Transition (transaction boundaries are state morphisms
+ with atomicity constraints)
+ - PPL -> new effect class: potentially non-terminating computation
+ (Turing-complete; breaks the termination guarantee of DRL)
+ - ACL -> a functor restriction: narrows the admissible natural
+ transformations based on authorization context *)
+type result =
+ | Query of Relation.t
+ | Transition of Management.Database.t * string
+
+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 sexp_of_error : error -> Sexplib.Sexp.t
+end
diff --git a/proof/.gitignore b/proof/.gitignore
new file mode 100644
index 0000000..5ff7fe7
--- /dev/null
+++ b/proof/.gitignore
@@ -0,0 +1,11 @@
+# Coq build artifacts
+*.vo
+*.vos
+*.vok
+*.glob
+*.aux
+.*.aux
+Makefile.coq
+Makefile.coq.conf
+.Makefile.coq.d
+.coqdeps.d
diff --git a/proof/Drl.v b/proof/Drl.v
new file mode 100644
index 0000000..fd179a1
--- /dev/null
+++ b/proof/Drl.v
@@ -0,0 +1,67 @@
+(** * DRL: Data Retrieval Language — Finiteness Gate
+
+ This file defines:
+ - The DRL query AST (mirroring [lib/drl/ast.ml])
+ - [predicted_finite]: statically decides whether a query can
+ produce an infinite result based on the cardinality flags of
+ the relations it references.
+
+ [predicted_finite] is extracted to OCaml and called by
+ [lib/drl/gate.ml] before every query execution. Queries whose
+ result cannot be statically proven finite are rejected unless
+ wrapped in a [Take]. *)
+
+From Stdlib Require Import List.
+From Stdlib Require Import String.
+From Stdlib Require Import Bool.
+Require Import Schema.
+Require Import Relation.
+Import ListNotations.
+
+(* ================================================================== *)
+(** ** AST *)
+(* ================================================================== *)
+
+Inductive literal :=
+ | LitInt : nat -> literal
+ | LitStr : string -> literal
+ | LitBool : bool -> literal.
+
+Inductive query : Type :=
+ | Base : string -> query
+ | Const : list (attr_name * literal) -> query
+ | Select : query -> query -> query
+ | Join : list attr_name -> query -> query -> query
+ | Cartesian : query -> query -> query
+ | Project : list attr_name -> query -> query
+ | Rename : list (attr_name * attr_name) -> query -> query
+ | Union : query -> query -> query
+ | Diff : query -> query -> query
+ | Take : nat -> query -> query.
+
+(* ================================================================== *)
+(** ** Finiteness Prediction *)
+(* ================================================================== *)
+
+(** Conservatively predict whether a query produces a finite result.
+ Returns [Some true] if guaranteed finite, [Some false] if not,
+ [None] if a referenced relation is not found in the database. *)
+Fixpoint predicted_finite (db : database) (q : query) : option bool :=
+ match q with
+ | Base name =>
+ match db_lookup name db with
+ | Some r => Some (rel_finite r)
+ | None => None
+ end
+ | Const _ => Some true
+ | Take _ _ => Some true
+ | Select _ source =>
+ predicted_finite db source
+ | Join _ q1 q2 | Cartesian q1 q2 | Union q1 q2 =>
+ match predicted_finite db q1, predicted_finite db q2 with
+ | Some f1, Some f2 => Some (f1 && f2)
+ | _, _ => None
+ end
+ | Diff q1 _ => predicted_finite db q1
+ | Project _ q' | Rename _ q' => predicted_finite db q'
+ end.
diff --git a/proof/Extraction.v b/proof/Extraction.v
new file mode 100644
index 0000000..dc93c21
--- /dev/null
+++ b/proof/Extraction.v
@@ -0,0 +1,17 @@
+(** * Extraction to OCaml — finiteness gate only
+
+ Extracts [predicted_finite] to [lib/drl_verified/drl_verified.ml].
+ To regenerate: cd proof && make *)
+
+From Stdlib Require Import Extraction.
+From Stdlib Require Import ExtrOcamlBasic.
+From Stdlib Require Import ExtrOcamlString.
+From Stdlib Require Import ExtrOcamlNatInt.
+
+Require Import Schema.
+Require Import Relation.
+Require Import Drl.
+
+Set Extraction Output Directory "../lib/drl_verified".
+
+Extraction "drl_verified" predicted_finite.
diff --git a/proof/Makefile b/proof/Makefile
new file mode 100644
index 0000000..a9e1f72
--- /dev/null
+++ b/proof/Makefile
@@ -0,0 +1,14 @@
+COQMAKEFILE ?= Makefile.coq
+
+all: $(COQMAKEFILE)
+ mkdir -p ../lib/drl_verified
+ $(MAKE) -f $(COQMAKEFILE)
+
+$(COQMAKEFILE): _CoqProject
+ coq_makefile -f _CoqProject -o $(COQMAKEFILE)
+
+clean: $(COQMAKEFILE)
+ $(MAKE) -f $(COQMAKEFILE) clean
+ rm -f $(COQMAKEFILE) $(COQMAKEFILE).conf
+
+.PHONY: all clean
diff --git a/proof/Relation.v b/proof/Relation.v
new file mode 100644
index 0000000..f7c087d
--- /dev/null
+++ b/proof/Relation.v
@@ -0,0 +1,26 @@
+(** * Relation: minimal model for finiteness checking
+
+ A relation is reduced to the two fields that [predicted_finite]
+ actually inspects: its name (for database lookup) and its
+ finiteness flag (for the gate decision). *)
+
+From Stdlib Require Import List.
+From Stdlib Require Import String.
+Require Import Schema.
+Import ListNotations.
+
+Record relation := mkRelation {
+ rel_name : string;
+ rel_finite : bool;
+}.
+
+(** A database is a named collection of relations. *)
+Definition database := list (string * relation).
+
+(** Look up a relation by name. *)
+Fixpoint db_lookup (name : string) (db : database) : option relation :=
+ match db with
+ | [] => None
+ | (n, r) :: rest =>
+ if String.eqb name n then Some r else db_lookup name rest
+ end.
diff --git a/proof/Schema.v b/proof/Schema.v
new file mode 100644
index 0000000..eb8f4c5
--- /dev/null
+++ b/proof/Schema.v
@@ -0,0 +1,5 @@
+(** * Schema: attribute name type *)
+
+From Stdlib Require Import String.
+
+Definition attr_name := string.
diff --git a/proof/_CoqProject b/proof/_CoqProject
new file mode 100644
index 0000000..0e6123e
--- /dev/null
+++ b/proof/_CoqProject
@@ -0,0 +1,6 @@
+-R . Sakura
+
+Schema.v
+Relation.v
+Drl.v
+Extraction.v
diff --git a/proof/drl_verified.ml b/proof/drl_verified.ml
new file mode 100644
index 0000000..f4c282a
--- /dev/null
+++ b/proof/drl_verified.ml
@@ -0,0 +1,410 @@
+
+(** val negb : bool -> bool **)
+
+let negb = function
+| true -> false
+| false -> true
+
+(** val fst : ('a1 * 'a2) -> 'a1 **)
+
+let fst = function
+| (x, _) -> x
+
+(** val snd : ('a1 * 'a2) -> 'a2 **)
+
+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)
+
+(** val map : ('a1 -> 'a2) -> 'a1 list -> 'a2 list **)
+
+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 _ -> [])
+ (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)
+
+(** val existsb : ('a1 -> bool) -> 'a1 list -> bool **)
+
+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)
+
+(** 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
+
+(** 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
+
+(** 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)
+
+(** val append : char list -> char list -> char list **)
+
+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
+
+(** val attr_names : schema -> attr_name list **)
+
+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
+
+(** val merge_schema : schema -> schema -> schema **)
+
+let merge_schema s1 s2 =
+ app s1 (filter (fun p -> negb (has_attr (fst p) s1)) s2)
+
+(** val remove_attrs : attr_name list -> schema -> schema **)
+
+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 **)
+
+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)))
+ 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)"
+
+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
+
+(** val project_tuple : attr_name list -> tuple -> tuple **)
+
+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)))
+ t
+
+(** val merge_tuples : tuple -> tuple -> tuple **)
+
+let merge_tuples t1 t2 =
+ app t1 (filter (fun p -> negb (existsb (eqb (fst p)) (map fst 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))
+ attrs
+
+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
+
+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
+
+(** 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)))
+ | 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)
+| 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'
+
+(** val literal_to_value : literal -> value **)
+
+let literal_to_value =
+ failwith "AXIOM TO BE REALIZED (Sakura.Drl.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 sch =
+ 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 :: []) }
+
+(** val semijoin : relation -> relation -> relation **)
+
+let semijoin filter_rel source =
+ let common =
+ filter (fun a -> has_attr a filter_rel.rel_schema)
+ (attr_names source.rel_schema)
+ in
+ 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) }
+
+(** 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)
+ 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))
+ in
+ { 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)
+ 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 }
+
+(** 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) }
+
+(** 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) }
+
+(** 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) }
+
+(** 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)
+ 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) }
+
+(** 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) }
+
+(** 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)
+ | 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)
+ | 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)
+ | 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)
+ | 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)
+ | None -> None)
+ | 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 2b902e1..963337f 100644
--- a/test/test_relational_engine.ml
+++ b/test/test_relational_engine.ml
@@ -2391,20 +2391,20 @@ let%test_unit "constraint propagation: project filters constraints" =
(* Parse tests use canonical flat sexp format: (Constructor (field val) ...) *)
-let%test_unit "dml: parse CreateDatabase" =
- match Dml.Parser.of_string {|(CreateDatabase "shop")|} with
+let%test_unit "ddl: parse CreateDatabase" =
+ match Ddl.Parser.of_string {|(CreateDatabase "shop")|} with
| Error _ -> assert false
- | Ok stmt -> assert (stmt = Dml.Ast.CreateDatabase "shop")
+ | Ok stmt -> assert (stmt = Ddl.Ast.CreateDatabase "shop")
-let%test_unit "dml: parse RetractRelation" =
- match Dml.Parser.of_string {|(RetractRelation "users")|} with
+let%test_unit "ddl: parse RetractRelation" =
+ match Ddl.Parser.of_string {|(RetractRelation "users")|} with
| Error _ -> assert false
- | Ok stmt -> assert (stmt = Dml.Ast.RetractRelation "users")
+ | Ok stmt -> assert (stmt = Ddl.Ast.RetractRelation "users")
-let%test_unit "dml: parse ClearRelation" =
- match Dml.Parser.of_string {|(ClearRelation "users")|} with
+let%test_unit "ddl: parse ClearRelation" =
+ match Ddl.Parser.of_string {|(ClearRelation "users")|} with
| Error _ -> assert false
- | Ok stmt -> assert (stmt = Dml.Ast.ClearRelation "users")
+ | Ok stmt -> assert (stmt = Ddl.Ast.ClearRelation "users")
let%test_unit "dml: round-trip InsertTuple" =
let src = Dml.Ast.InsertTuple {
@@ -2415,12 +2415,12 @@ let%test_unit "dml: round-trip InsertTuple" =
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
-let%test_unit "dml: round-trip CreateRelation" =
- let src = Dml.Ast.CreateRelation {
+let%test_unit "ddl: round-trip CreateRelation" =
+ let src = Ddl.Ast.CreateRelation {
name = "users";
schema = [("name", "string"); ("age", "natural")]
} in
- match Dml.Parser.of_string (Dml.Parser.to_string src) with
+ match Ddl.Parser.of_string (Ddl.Parser.to_string src) with
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
@@ -2436,31 +2436,31 @@ let%test_unit "dml: round-trip InsertTuples" =
| Error _ -> assert false
| Ok parsed -> assert (parsed = src)
-let%test_unit "dml: round-trip RegisterDomain" =
- let src = Dml.Ast.RegisterDomain { name = "money"; cardinality = Dml.Ast.AlephZero } in
- match Dml.Parser.of_string (Dml.Parser.to_string src) with
+let%test_unit "ddl: round-trip RegisterDomain" =
+ 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)
(* Executor tests construct AST directly — no dependency on sexp format *)
-let%test_unit "dml: execute CreateDatabase" =
+let%test_unit "ddl: execute CreateDatabase" =
with_storage (fun storage ->
let db = Management.Database.empty ~name:"" in
- let stmt = Dml.Ast.CreateDatabase "shop" in
- match Dml.Executor.Memory.execute storage db stmt with
+ let stmt = Ddl.Ast.CreateDatabase "shop" in
+ match Ddl.Executor.Memory.execute storage db stmt with
| Error _ -> assert false
- | Ok db -> assert (db.name = "shop"))
+ | Ok (db, _msg) -> assert (db.name = "shop"))
-let%test_unit "dml: execute CreateRelation" =
+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 = Dml.Ast.CreateRelation { name = "users"; schema = [("name", "string"); ("age", "natural")] } in
- match Dml.Executor.Memory.execute storage db stmt with
+ 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 -> assert (Management.Database.has_relation db "users"))
+ | Ok (db, _msg) -> assert (Management.Database.has_relation db "users"))
let%test_unit "dml: execute InsertTuple" =
with_storage (fun storage ->
@@ -2523,7 +2523,7 @@ let%test_unit "dml: execute DeleteTuple" =
in
assert (Manipulation.Memory.tuple_count rel = 0))
-let%test_unit "dml: execute RetractRelation" =
+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
@@ -2532,11 +2532,11 @@ let%test_unit "dml: execute RetractRelation" =
let db = match Manipulation.Memory.create_relation storage db ~name:"users" ~schema with
| Error _ -> assert false | Ok (db, _) -> db
in
- match Dml.Executor.Memory.execute storage db (Dml.Ast.RetractRelation "users") with
+ match Ddl.Executor.Memory.execute storage db (Ddl.Ast.RetractRelation "users") with
| Error _ -> assert false
- | Ok db -> assert (not (Management.Database.has_relation db "users")))
+ | Ok (db, _msg) -> assert (not (Management.Database.has_relation db "users")))
-let%test_unit "dml: execute ClearRelation" =
+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
@@ -2550,23 +2550,23 @@ let%test_unit "dml: execute ClearRelation" =
tuples = [[("name", Drl.Ast.Str "Alice")]; [("name", Drl.Ast.Str "Bob")]] }) with
| Error _ -> assert false | Ok db -> db
in
- match Dml.Executor.Memory.execute storage db (Dml.Ast.ClearRelation "users") with
+ match Ddl.Executor.Memory.execute storage db (Ddl.Ast.ClearRelation "users") with
| Error _ -> assert false
- | Ok db ->
+ | 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 "dml: execute RegisterDomain" =
+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 = Dml.Ast.RegisterDomain { name = "money"; cardinality = Dml.Ast.AlephZero } in
- match Dml.Executor.Memory.execute storage db stmt with
+ 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 -> assert (Management.Database.has_domain db "money"))
+ | Ok (db, _msg) -> assert (Management.Database.has_domain db "money"))
let%test_unit "dml: insert into nonexistent relation returns error" =
with_storage (fun storage ->