aboutsummaryrefslogblamecommitdiffstats
path: root/test/drl_test.ml
blob: ca95a2b8b4e8b2dc95fd3c01c492e496b4305846 (plain) (tree)
1
2
3
4
5
6
7
8
9
10









                                                    

                                       





















                                                                                              
                                               

                                                  


                                                                         




                             
                                               

                                                


                                                                             

































                                                                                           
 



                                                                             




                                                                                           










                                                                               
                           

                                      
                   
                
                       

                                                                                  
 


                                                                                      





                                                                           
                       
                                 
                                                                
                      


                                                                                     


                        


                                                                             
                       
                                 
                                                                                     





                                                     












                                                                                                                  
open Relational_engine

module Test_Nt = Nt.Memory
module Test_Drl = Drl.Executor.Make (Test_Nt)
module Test_Branch = Branch.Make (Test_Nt)
module Test_Session = Session.Make (Test_Nt)
module Test_Ctx = Sublanguage_context.Make (Test_Nt)

let ( let* ) = Result.bind

(* TODO: move fixture somewhere else *)

let with_proper_attrs tuples =
  List.map (fun (name, value) -> (name, { Attribute.value = value })) tuples

let tuple_from_attrs name attrs =
  { Tuple.relation = name; attributes = Tuple.AttributeMap.of_list (with_proper_attrs attrs) }

let seed_fixture
      (branch : Test_Branch.branch)
      (multigroup : Management.Multigroup.multigroup)
    : (unit, Condition.t) result =
  let branch_handle = branch#branch_handle in
  let branch_name = branch#name in
  let mg_name = multigroup#name in
  let relation ~name ~schema ~tuples =
    (name, schema,
     List.map (tuple_from_attrs name) tuples)
  in
  let fixture =
    [
      relation
        ~name:"fruit"
        ~schema:(Schema.empty
                 |> Schema.add "fruit" "string"
                 |> Schema.add "flavour" "string")
        ~tuples:[
          [("fruit", Obj.magic "peach"); ("flavour", Obj.magic "sweet")];
          [("fruit", Obj.magic "grape"); ("flavour", Obj.magic "sweet")];
          [("fruit", Obj.magic "lemon"); ("flavour", Obj.magic "sour")]
        ];

      relation
        ~name:"drink"
        ~schema:(Schema.empty
                 |> Schema.add "drink" "string"
                 |> Schema.add "fruit" "string")
        ~tuples:[
          [("drink", Obj.magic "lemonade");    ("fruit", Obj.magic "lemon")];
          [("drink", Obj.magic "wine");        ("fruit", Obj.magic "grape")];
          [("drink", Obj.magic "grape juice"); ("fruit", Obj.magic "grape")]
        ]
    ]
  in
  let create (name, schema, tuples) =
    let* _ = Test_Nt.create_relation branch_handle multigroup ~branch_name ~name ~schema in
    let* _ = Test_Nt.create_tuples ~branch_name ~mg_name ~rel_name:name tuples in
    Ok ()
  in
  let sequencing a b = Result.bind a (fun _ -> b) in
  fixture
  |> List.map create
  |> List.fold_left sequencing (Ok ())
  |> Result.map ignore

let make_fixture () =
  let* _ = Test_Nt.initialize () in (* FIXME: this *leaks* on the C++ side! *)
  let branch_name = "fixture" in
  let multigroup_name = "fixture" in
  let claims = "fixture" in
  let* session = Test_Session.open_session claims ~branch_name in
  let branch = session#branch in
  let multigroup = begin
      branch#add_multigroup ~name:multigroup_name;
      branch#mg_of multigroup_name
      |> Option.get
    end in
  let* _ = seed_fixture branch multigroup in
  let ctx = Test_Ctx.make_ctx session claims in
  Ok ctx

module Error = struct
  open Condition

  let invalid_argument msg = condition "invalid-argument" msg empty

  let expected_tuple tuple_expr relation_expr =
    condition "expected-tuple" "An expected tuple is missing from the result"
      ("tuple" |=| (of_sexp tuple_expr) &
       "relation" |=| (of_sexp relation_expr))

  let unexpected_tuple tuple_expr relation_expr =
    condition "unexpected-tuple" "A tuple expected to be missing was present on the result"
      ("tuple" |=| (of_sexp tuple_expr) &
       "relation" |=| (of_sexp relation_expr))
end

let assert_ok = function
  | Ok x -> x
  | Error condition -> raise (Invalid_argument (Condition.to_string condition))

let drain { Sublanguage_types.rows; _ } = rows         (* TODO *)

let contains tuple ts =
  List.find_opt (Tuple.materialized_equal tuple) ts |> Option.is_some

let ensuring f error t ts =
  let tuple = tuple_from_attrs "" t in
  Result.bind ts (fun ts ->
      if f tuple ts
      then Ok ts
      else Error (error
                    (Tuple.sexp_of_materialized tuple)
                    (Sexplib.Sexp.List (List.map Tuple.sexp_of_materialized ts))))

let ensure_contains = ensuring contains Error.expected_tuple
let ensure_missing = ensuring (fun t ts -> not (contains t ts)) Error.unexpected_tuple

let with_cursor = function
  | Sublanguage_types.Cursor cursor -> Ok (drain cursor)
  | _ -> Error (Error.invalid_argument "Expected cursor")

let%test_unit "Ensure that `base` translates to a scan over its argument" =
  begin
    let open Drl.Ast in
    let* ctx = make_fixture () in
    let* result = Test_Drl.execute ctx (Base "fixture:fruit") in
    with_cursor result
    |> ensure_contains [("fruit", Obj.magic "peach"); ("flavour", Obj.magic "sweet")]
    |> ensure_contains [("fruit", Obj.magic "grape"); ("flavour", Obj.magic "sweet")]
    |> ensure_contains [("fruit", Obj.magic "lemon"); ("flavour", Obj.magic "sour")]
    |> Result.map ignore
  end
  |> assert_ok

let%test_unit "Ensure that `project` returns a projection of it's argument" =
  begin
    let open Drl.Ast in
    let* ctx = make_fixture () in
    let* result = Test_Drl.execute ctx (Project (["fruit"], Base "fixture:drink")) in
    with_cursor result
    |> ensure_contains [("fruit", Obj.magic "grape")]
    |> ensure_contains [("fruit", Obj.magic "lemon")]
    |> Result.map ignore
  end
  |> assert_ok

let%test_unit "Ensure that `join` returns an inner join of it's arguments" =
  begin
    let open Drl.Ast in
    let* ctx = make_fixture () in
    let* result = Test_Drl.execute ctx (Join (["fruit"], Base "fixture:drink", Base "fixture:fruit")) in
    with_cursor result
    |> ensure_contains [("drink", Obj.magic "wine"); ("fruit", Obj.magic "grape"); ("flavour", Obj.magic "sweet")]
    |> ensure_missing [("drink", Obj.magic "wine"); ("fruit", Obj.magic "lemon"); ("flavour", Obj.magic "sour")]
    |> ensure_missing [("drink", Obj.magic "wine"); ("fruit", Obj.magic "grape"); ("flavour", Obj.magic "sour")]
    |> Result.map ignore
  end
  |> assert_ok