diff options
| -rw-r--r-- | lib/algebra.ml | 229 | ||||
| -rw-r--r-- | lib/drl/ast.ml | 25 | ||||
| -rw-r--r-- | lib/drl/executor.ml | 70 | ||||
| -rw-r--r-- | lib/drl/parser.ml | 9 | ||||
| -rw-r--r-- | test/test_relational_engine.ml | 308 |
5 files changed, 641 insertions, 0 deletions
diff --git a/lib/algebra.ml b/lib/algebra.ml new file mode 100644 index 0000000..8701dcb --- /dev/null +++ b/lib/algebra.ml @@ -0,0 +1,229 @@ +type error = + | StorageError of string + | GeneratorError of string + +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)) + + 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.Value (t, next) -> go next (pos + 1) (t :: acc) + in go gen 0 [] + + let to_generator (storage : storage) (rel : Relation.t) : Generator.t = + match rel.Relation.generator with + | Some gen -> gen + | None -> + 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 gen = + Relation.make + ~hash:None ~name ~schema ~tree:None ~constraints:None + ~cardinality:Conventions.Cardinality.AlephZero + ~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 + 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]) + + (* Operators *) + + let select_fn storage predicate rel = + let gen = to_generator storage rel 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 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 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' } + | other -> other + 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 + | Generator.Error e -> Generator.Error e + | Generator.Value (t, next) -> Generator.Value (project_tuple t, go next) + in fun pos -> go gen pos + in + Ok (of_generator ~name:("π_" ^ rel.Relation.name) ~schema:new_schema 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_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' } + | 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 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) -> Generator.Value (rename_tuple t, go next) + in fun pos -> go gen pos + in + Ok (of_generator ~name:("ρ_" ^ rel.Relation.name) ~schema:new_schema 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 + 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 name = "⋈_" ^ left.Relation.name ^ "_" ^ right.Relation.name in + Ok (of_generator ~name ~schema:merged_schema join_gen) + + let union storage rel1 rel2 = + let gen1 = to_generator storage rel1 in + let gen2 = to_generator storage rel2 in + let chain_gen = + let rec go g1 g2 pos = + match g1 pos with + | 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 + let name = "∪_" ^ rel1.Relation.name ^ "_" ^ rel2.Relation.name in + Ok (of_generator ~name ~schema:rel1.Relation.schema chain_gen) + + 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 + + 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 lazy_gen) + + let take storage n rel = + let gen = to_generator storage rel in + let lazy_gen = + let rec go g count pos = + if count = 0 then 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 + Ok (of_generator ~name:("τ_" ^ rel.Relation.name) ~schema:rel.Relation.schema 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) + +end + +module Memory = Make(Management.Physical.Memory) diff --git a/lib/drl/ast.ml b/lib/drl/ast.ml new file mode 100644 index 0000000..f94965d --- /dev/null +++ b/lib/drl/ast.ml @@ -0,0 +1,25 @@ +open Sexplib.Std + +(** Literal values for Const nodes *) +type value = + | Int of int + | Float of float + | Str of string + | Bool of bool +[@@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 (** τ *) +[@@deriving sexp] diff --git a/lib/drl/executor.ml b/lib/drl/executor.ml new file mode 100644 index 0000000..7588b33 --- /dev/null +++ b/lib/drl/executor.ml @@ -0,0 +1,70 @@ +module Make(Storage : Management.Physical.S) = struct + module Alg = Algebra.Make(Storage) + module Ops = Manipulation.Make(Storage) + + type error = + | RelationNotFound of string + | AlgebraError of Algebra.error + + let wrap = Result.map_error (fun e -> AlgebraError e) + + (* Convert Ast.value → AbstractValue.t (Obj.t) via Obj.repr *) + let ast_value_to_abstract : Ast.value -> Conventions.AbstractValue.t = function + | Ast.Int n -> Obj.repr n + | Ast.Float f -> Obj.repr f + | Ast.Str s -> Obj.repr s + | Ast.Bool b -> Obj.repr b + + 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.Const 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) + | Ast.Join (attrs, q1, q2) -> + 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) + | Ast.Rename (renames, q) -> + 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) + | Ast.Union (q1, q2) -> + 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) + | Ast.Take (n, q) -> + execute storage db q >>= fun rel -> + wrap (Alg.take storage n rel) +end + +module Memory = Make(Management.Physical.Memory) diff --git a/lib/drl/parser.ml b/lib/drl/parser.ml new file mode 100644 index 0000000..71d694b --- /dev/null +++ b/lib/drl/parser.ml @@ -0,0 +1,9 @@ +type error = ParseError of string + +let of_string s = + match Sexplib.Sexp.of_string s |> Ast.query_of_sexp with + | 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 diff --git a/test/test_relational_engine.ml b/test/test_relational_engine.ml index 737970b..8ba8226 100644 --- a/test/test_relational_engine.ml +++ b/test/test_relational_engine.ml @@ -1042,3 +1042,311 @@ let%test_unit "branching: full reconstruction from hash" = assert (List.mem "Apple" names); assert (List.mem "Banana" names); assert (List.mem "Cherry" names)) + +(* ============================================================================ + Algebra Tests + ============================================================================ *) + +(* 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) + Tuple.AttributeMap.empty pairs + in + (Tuple.Materialized { Tuple.relation; attributes } : Tuple.t) + +(* 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 + 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 + 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%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 + | 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 + | 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%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%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 + | 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%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%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 + | Error _ -> assert false + | Ok rows -> assert (List.length rows = 3)) + +(* ============================================================================ + DRL Parser Tests + ============================================================================ *) + +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 + +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 + +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 + +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 + +(* ============================================================================ + DRL Executor end-to-end Tests + ============================================================================ *) + +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 + | 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")) |
