aboutsummaryrefslogtreecommitdiffstats
path: root/test/drl_test.ml
blob: ca95a2b8b4e8b2dc95fd3c01c492e496b4305846 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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