Evaluate typed queries over persistent maps
This commit is contained in:
9 files changed
+464
-2
No files matched your search
@@ -133,7 +133,7 @@ $(LIB_ARCHIVE): $(LIB_CMX) $(BUILD)/order.mk
|
|||||||
$(OCAMLOPT) -a -o $@ $(LIB_ORDER)
|
$(OCAMLOPT) -a -o $@ $(LIB_ORDER)
|
||||||
|
|
||||||
deltac: $(BUILD)/main.cmx $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
|
deltac: $(BUILD)/main.cmx $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
|
||||||
$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE) $(BUILD)/main.cmx
|
$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(RUNTIME_ARCHIVE) $(LIB_ARCHIVE) $(BUILD)/main.cmx
|
||||||
|
|
||||||
$(RUNTIME_ARCHIVE) $(RUNTIME_NATIVE): $(RT_CMX)
|
$(RUNTIME_ARCHIVE) $(RUNTIME_NATIVE): $(RT_CMX)
|
||||||
ifeq ($(strip $(RT_ML)),)
|
ifeq ($(strip $(RT_ML)),)
|
||||||
@@ -146,7 +146,7 @@ ifeq ($(strip $(TEST_ML)),)
|
|||||||
TEST_EXE =
|
TEST_EXE =
|
||||||
else
|
else
|
||||||
$(TEST_EXE): $(TEST_CMX) $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
|
$(TEST_EXE): $(TEST_CMX) $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE)
|
||||||
$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(LIB_ARCHIVE) $(RUNTIME_ARCHIVE) $(TEST_ORDER)
|
$(OCAMLOPT) $(FLAGS) -o $@ $(LIBS) $(RUNTIME_ARCHIVE) $(LIB_ARCHIVE) $(TEST_ORDER)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
test: $(TEST_EXE)
|
test: $(TEST_EXE)
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
module Pure_map = struct
|
||||||
|
module Ordered = struct
|
||||||
|
type t = int
|
||||||
|
|
||||||
|
let compare left right = if left < right then -1 else if left > right then 1 else 0
|
||||||
|
end
|
||||||
|
|
||||||
|
module Map = Map.Make (Ordered)
|
||||||
|
|
||||||
|
type 'a t = 'a Map.t
|
||||||
|
|
||||||
|
let empty = Map.empty
|
||||||
|
|
||||||
|
let add key value map = Map.add key value map
|
||||||
|
|
||||||
|
let remove key map = Map.remove key map
|
||||||
|
|
||||||
|
let find key map = Map.find key map
|
||||||
|
|
||||||
|
let find_opt key map = try Some (Map.find key map) with Not_found -> None
|
||||||
|
|
||||||
|
let mem key map = Map.mem key map
|
||||||
|
|
||||||
|
let cardinal map = Map.cardinal map
|
||||||
|
|
||||||
|
let is_empty map = Map.is_empty map
|
||||||
|
|
||||||
|
let bindings map = Map.bindings map
|
||||||
|
|
||||||
|
let fold f map init = Map.fold f map init
|
||||||
|
|
||||||
|
let iter f map = Map.iter f map
|
||||||
|
|
||||||
|
let map f map = Map.map f map
|
||||||
|
|
||||||
|
let keys map = Map.fold (fun key _ acc -> key :: acc) map [] |> List.rev
|
||||||
|
|
||||||
|
let equal equal_value left right = Map.equal equal_value left right
|
||||||
|
end
|
||||||
|
|
||||||
|
type counters = {
|
||||||
|
mutable predicate_evaluations : int;
|
||||||
|
mutable mapping_evaluations : int;
|
||||||
|
mutable changed_key_visits : int;
|
||||||
|
mutable full_traversals : int;
|
||||||
|
mutable scalar_deltas : int;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_counters () =
|
||||||
|
{
|
||||||
|
predicate_evaluations = 0;
|
||||||
|
mapping_evaluations = 0;
|
||||||
|
changed_key_visits = 0;
|
||||||
|
full_traversals = 0;
|
||||||
|
scalar_deltas = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let copy_counters counters =
|
||||||
|
{
|
||||||
|
predicate_evaluations = counters.predicate_evaluations;
|
||||||
|
mapping_evaluations = counters.mapping_evaluations;
|
||||||
|
changed_key_visits = counters.changed_key_visits;
|
||||||
|
full_traversals = counters.full_traversals;
|
||||||
|
scalar_deltas = counters.scalar_deltas;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reset_counters counters =
|
||||||
|
counters.predicate_evaluations <- 0;
|
||||||
|
counters.mapping_evaluations <- 0;
|
||||||
|
counters.changed_key_visits <- 0;
|
||||||
|
counters.full_traversals <- 0;
|
||||||
|
counters.scalar_deltas <- 0
|
||||||
|
|
||||||
|
let count_predicate counters = counters.predicate_evaluations <- counters.predicate_evaluations + 1
|
||||||
|
|
||||||
|
let count_mapping counters = counters.mapping_evaluations <- counters.mapping_evaluations + 1
|
||||||
|
|
||||||
|
let count_changed_key counters = counters.changed_key_visits <- counters.changed_key_visits + 1
|
||||||
|
|
||||||
|
let count_full_traversal counters = counters.full_traversals <- counters.full_traversals + 1
|
||||||
|
|
||||||
|
let count_scalar_delta counters = counters.scalar_deltas <- counters.scalar_deltas + 1
|
||||||
|
|
||||||
|
let counters_to_string counters =
|
||||||
|
Printf.sprintf
|
||||||
|
"predicate_evaluations=%d mapping_evaluations=%d changed_key_visits=%d full_traversals=%d scalar_deltas=%d"
|
||||||
|
counters.predicate_evaluations counters.mapping_evaluations counters.changed_key_visits
|
||||||
|
counters.full_traversals counters.scalar_deltas
|
||||||
|
|
||||||
|
type 'a outcome = Success of 'a | Failure of string
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
module Pure_map : sig
|
||||||
|
type 'a t
|
||||||
|
|
||||||
|
val empty : 'a t
|
||||||
|
val add : int -> 'a -> 'a t -> 'a t
|
||||||
|
val remove : int -> 'a t -> 'a t
|
||||||
|
val find : int -> 'a t -> 'a
|
||||||
|
val find_opt : int -> 'a t -> 'a option
|
||||||
|
val mem : int -> 'a t -> bool
|
||||||
|
val cardinal : 'a t -> int
|
||||||
|
val is_empty : 'a t -> bool
|
||||||
|
val bindings : 'a t -> (int * 'a) list
|
||||||
|
val fold : (int -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b
|
||||||
|
val iter : (int -> 'a -> unit) -> 'a t -> unit
|
||||||
|
val map : ('a -> 'b) -> 'a t -> 'b t
|
||||||
|
val keys : 'a t -> int list
|
||||||
|
val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool
|
||||||
|
end
|
||||||
|
|
||||||
|
type counters = {
|
||||||
|
mutable predicate_evaluations : int;
|
||||||
|
mutable mapping_evaluations : int;
|
||||||
|
mutable changed_key_visits : int;
|
||||||
|
mutable full_traversals : int;
|
||||||
|
mutable scalar_deltas : int;
|
||||||
|
}
|
||||||
|
|
||||||
|
val new_counters : unit -> counters
|
||||||
|
val copy_counters : counters -> counters
|
||||||
|
val reset_counters : counters -> unit
|
||||||
|
val count_predicate : counters -> unit
|
||||||
|
val count_mapping : counters -> unit
|
||||||
|
val count_changed_key : counters -> unit
|
||||||
|
val count_full_traversal : counters -> unit
|
||||||
|
val count_scalar_delta : counters -> unit
|
||||||
|
val counters_to_string : counters -> string
|
||||||
|
|
||||||
|
type 'a outcome = Success of 'a | Failure of string
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
type env = {
|
||||||
|
helpers : (int * Typed.expr) list;
|
||||||
|
values : (int * Value.t) list;
|
||||||
|
}
|
||||||
|
|
||||||
|
let empty_env helpers = { helpers = helpers; values = [] }
|
||||||
|
|
||||||
|
let helpers_of_program program =
|
||||||
|
List.map
|
||||||
|
(fun helper -> (Ident.stamp helper.Typed.th_ident, helper.Typed.th_body))
|
||||||
|
program.Typed.tp_helpers
|
||||||
|
|
||||||
|
let bind env ident value = { env with values = (Ident.stamp ident, value) :: env.values }
|
||||||
|
|
||||||
|
let lookup env ident =
|
||||||
|
let rec search = function
|
||||||
|
| [] -> None
|
||||||
|
| (stamp, value) :: rest -> if stamp = Ident.stamp ident then Some value else search rest
|
||||||
|
in
|
||||||
|
search env.values
|
||||||
|
|
||||||
|
let int_of span value =
|
||||||
|
match value with
|
||||||
|
| Value.VInt number -> number
|
||||||
|
| other -> Diagnostic.error span "runtime error: expected an integer but got %s" (Value.to_string other)
|
||||||
|
|
||||||
|
let bool_of span value =
|
||||||
|
match value with
|
||||||
|
| Value.VBool truth -> truth
|
||||||
|
| other -> Diagnostic.error span "runtime error: expected a boolean but got %s" (Value.to_string other)
|
||||||
|
|
||||||
|
let collection_of span value =
|
||||||
|
match value with
|
||||||
|
| Value.VCollection map -> map
|
||||||
|
| other -> Diagnostic.error span "runtime error: expected a collection but got %s" (Value.to_string other)
|
||||||
|
|
||||||
|
let rec eval env expr =
|
||||||
|
let span = expr.Typed.tspan in
|
||||||
|
match expr.Typed.te with
|
||||||
|
| Typed.TInt value -> Value.VInt value
|
||||||
|
| Typed.TBool value -> Value.VBool value
|
||||||
|
| Typed.TString value -> Value.VString value
|
||||||
|
| Typed.TUnit -> Value.VUnit
|
||||||
|
| Typed.TVar ident -> (
|
||||||
|
match lookup env ident with
|
||||||
|
| Some value -> value
|
||||||
|
| None -> Diagnostic.error span "runtime error: `%s` has no value" (Ident.display ident))
|
||||||
|
| Typed.TSource ident -> (
|
||||||
|
match lookup env ident with
|
||||||
|
| Some value -> value
|
||||||
|
| None -> Diagnostic.error span "runtime error: the input collection has no value")
|
||||||
|
| Typed.TLet (ident, bound, body) -> eval (bind env ident (eval env bound)) body
|
||||||
|
| Typed.TLambda _ ->
|
||||||
|
Diagnostic.error span "runtime error: a function cannot be used as a value"
|
||||||
|
| Typed.TApp (fn, argument) -> apply env fn (eval env argument)
|
||||||
|
| Typed.TIf (condition, then_branch, else_branch) ->
|
||||||
|
if bool_of condition.Typed.tspan (eval env condition) then eval env then_branch
|
||||||
|
else eval env else_branch
|
||||||
|
| Typed.TBinop (Syntax.And, left, right) ->
|
||||||
|
if bool_of left.Typed.tspan (eval env left) then Value.VBool (bool_of right.Typed.tspan (eval env right))
|
||||||
|
else Value.VBool false
|
||||||
|
| Typed.TBinop (Syntax.Or, left, right) ->
|
||||||
|
if bool_of left.Typed.tspan (eval env left) then Value.VBool true
|
||||||
|
else Value.VBool (bool_of right.Typed.tspan (eval env right))
|
||||||
|
| Typed.TBinop (operator, left, right) ->
|
||||||
|
eval_binop span operator (eval env left) (eval env right)
|
||||||
|
| Typed.TTuple items -> Value.VTuple (List.map (eval env) items)
|
||||||
|
| Typed.TRecord (name, fields) ->
|
||||||
|
Value.VRecord (name, List.map (fun (label, value) -> (label, eval env value)) fields)
|
||||||
|
| Typed.TField (record, label) -> (
|
||||||
|
match Value.field (eval env record) label with
|
||||||
|
| Some value -> value
|
||||||
|
| None -> Diagnostic.error span "runtime error: the record has no field `%s`" label)
|
||||||
|
| Typed.TFilter (collection, predicate) ->
|
||||||
|
let map = collection_of collection.Typed.tspan (eval env collection) in
|
||||||
|
let kept =
|
||||||
|
Delta_runtime.Pure_map.fold
|
||||||
|
(fun key value acc ->
|
||||||
|
if bool_of predicate.Typed.tspan (apply env predicate value) then
|
||||||
|
Delta_runtime.Pure_map.add key value acc
|
||||||
|
else acc)
|
||||||
|
map Delta_runtime.Pure_map.empty
|
||||||
|
in
|
||||||
|
Value.VCollection kept
|
||||||
|
| Typed.TMap (collection, projection) ->
|
||||||
|
let map = collection_of collection.Typed.tspan (eval env collection) in
|
||||||
|
let mapped =
|
||||||
|
Delta_runtime.Pure_map.fold
|
||||||
|
(fun key value acc ->
|
||||||
|
Delta_runtime.Pure_map.add key (apply env projection value) acc)
|
||||||
|
map Delta_runtime.Pure_map.empty
|
||||||
|
in
|
||||||
|
Value.VCollection mapped
|
||||||
|
| Typed.TSum collection ->
|
||||||
|
let map = collection_of collection.Typed.tspan (eval env collection) in
|
||||||
|
let total =
|
||||||
|
Delta_runtime.Pure_map.fold
|
||||||
|
(fun _ value acc -> acc + int_of collection.Typed.tspan value)
|
||||||
|
map 0
|
||||||
|
in
|
||||||
|
Value.VInt total
|
||||||
|
| Typed.TCount collection ->
|
||||||
|
let map = collection_of collection.Typed.tspan (eval env collection) in
|
||||||
|
Value.VInt (Delta_runtime.Pure_map.cardinal map)
|
||||||
|
|
||||||
|
and apply env fn argument =
|
||||||
|
match fn.Typed.te with
|
||||||
|
| Typed.TLambda (ident, body) -> eval (bind env ident argument) body
|
||||||
|
| Typed.TVar ident -> (
|
||||||
|
match Util.assoc_opt (Ident.stamp ident) env.helpers with
|
||||||
|
| Some body -> apply env body argument
|
||||||
|
| None ->
|
||||||
|
Diagnostic.error fn.Typed.tspan "runtime error: `%s` is not a function" (Ident.display ident))
|
||||||
|
| _ -> Diagnostic.error fn.Typed.tspan "runtime error: this expression is not a function"
|
||||||
|
|
||||||
|
and eval_binop span operator left right =
|
||||||
|
match operator with
|
||||||
|
| Syntax.Add -> Value.VInt (int_of span left + int_of span right)
|
||||||
|
| Syntax.Sub -> Value.VInt (int_of span left - int_of span right)
|
||||||
|
| Syntax.Mul -> Value.VInt (int_of span left * int_of span right)
|
||||||
|
| Syntax.Div -> Value.VInt (int_of span left / int_of span right)
|
||||||
|
| Syntax.Eq -> Value.VBool (Value.equal left right)
|
||||||
|
| Syntax.Ne -> Value.VBool (not (Value.equal left right))
|
||||||
|
| Syntax.Lt -> Value.VBool (int_of span left < int_of span right)
|
||||||
|
| Syntax.Le -> Value.VBool (int_of span left <= int_of span right)
|
||||||
|
| Syntax.Gt -> Value.VBool (int_of span left > int_of span right)
|
||||||
|
| Syntax.Ge -> Value.VBool (int_of span left >= int_of span right)
|
||||||
|
| Syntax.And | Syntax.Or -> Diagnostic.error span "runtime error: boolean operator evaluated eagerly"
|
||||||
|
|
||||||
|
let call env fn argument = apply env fn argument
|
||||||
|
|
||||||
|
let program typed input =
|
||||||
|
let map = Value.collection_of_list input in
|
||||||
|
let env =
|
||||||
|
{
|
||||||
|
helpers = helpers_of_program typed;
|
||||||
|
values = [ (Ident.stamp typed.Typed.tp_input, Value.VCollection map) ];
|
||||||
|
}
|
||||||
|
in
|
||||||
|
eval env typed.Typed.tp_query_body
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
type env = {
|
||||||
|
helpers : (int * Typed.expr) list;
|
||||||
|
values : (int * Value.t) list;
|
||||||
|
}
|
||||||
|
|
||||||
|
val empty_env : (int * Typed.expr) list -> env
|
||||||
|
val helpers_of_program : Typed.program -> (int * Typed.expr) list
|
||||||
|
val bind : env -> Ident.t -> Value.t -> env
|
||||||
|
val eval : env -> Typed.expr -> Value.t
|
||||||
|
val call : env -> Typed.expr -> Value.t -> Value.t
|
||||||
|
val program : Typed.program -> (int * Value.t) list -> Value.t
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
type t =
|
||||||
|
| VUnit
|
||||||
|
| VInt of int
|
||||||
|
| VBool of bool
|
||||||
|
| VString of string
|
||||||
|
| VTuple of t list
|
||||||
|
| VRecord of string * (string * t) list
|
||||||
|
| VCollection of t Delta_runtime.Pure_map.t
|
||||||
|
|
||||||
|
let rec to_string value =
|
||||||
|
match value with
|
||||||
|
| VUnit -> "unit"
|
||||||
|
| VInt number -> string_of_int number
|
||||||
|
| VBool true -> "true"
|
||||||
|
| VBool false -> "false"
|
||||||
|
| VString text -> Printf.sprintf "%S" text
|
||||||
|
| VTuple items -> "(tuple " ^ Util.join " " (List.map to_string items) ^ ")"
|
||||||
|
| VRecord (name, fields) ->
|
||||||
|
Printf.sprintf "(record:%s %s)" name
|
||||||
|
(Util.join " " (List.map (fun (label, field) -> "(" ^ label ^ " " ^ to_string field ^ ")") fields))
|
||||||
|
| VCollection map ->
|
||||||
|
let entries =
|
||||||
|
List.map (fun (key, item) -> Printf.sprintf "(%d %s)" key (to_string item))
|
||||||
|
(Delta_runtime.Pure_map.bindings map)
|
||||||
|
in
|
||||||
|
"(collection " ^ Util.join " " entries ^ ")"
|
||||||
|
|
||||||
|
let collection_of_list entries =
|
||||||
|
List.fold_left
|
||||||
|
(fun map (key, value) ->
|
||||||
|
if Delta_runtime.Pure_map.mem key map then
|
||||||
|
Diagnostic.error Location.none "duplicate key %d in the initial input" key;
|
||||||
|
Delta_runtime.Pure_map.add key value map)
|
||||||
|
Delta_runtime.Pure_map.empty entries
|
||||||
|
|
||||||
|
let collection_to_list map = Delta_runtime.Pure_map.bindings map
|
||||||
|
|
||||||
|
let equal (left : t) (right : t) = left = right
|
||||||
|
|
||||||
|
let field record label =
|
||||||
|
match record with
|
||||||
|
| VRecord (_, fields) -> Util.assoc_opt label fields
|
||||||
|
| _ -> None
|
||||||
|
|
||||||
|
let is_collection = function VCollection _ -> true | _ -> false
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
type t =
|
||||||
|
| VUnit
|
||||||
|
| VInt of int
|
||||||
|
| VBool of bool
|
||||||
|
| VString of string
|
||||||
|
| VTuple of t list
|
||||||
|
| VRecord of string * (string * t) list
|
||||||
|
| VCollection of t Delta_runtime.Pure_map.t
|
||||||
|
|
||||||
|
val to_string : t -> string
|
||||||
|
val collection_of_list : (int * t) list -> t Delta_runtime.Pure_map.t
|
||||||
|
val collection_to_list : t Delta_runtime.Pure_map.t -> (int * t) list
|
||||||
|
val equal : t -> t -> bool
|
||||||
|
val field : t -> string -> t option
|
||||||
|
val is_collection : t -> bool
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
open Test_harness
|
||||||
|
|
||||||
|
let row customer total = Value.VRecord ("order", [ ("customer", Value.VString customer); ("total", Value.VInt total) ])
|
||||||
|
|
||||||
|
let input entries = List.mapi (fun index entry -> (index + 1, entry)) entries
|
||||||
|
|
||||||
|
let run fixture_name entries =
|
||||||
|
let program = infer (read_fixture fixture_name) in
|
||||||
|
Interpret.program program (input entries)
|
||||||
|
|
||||||
|
let run_text text entries =
|
||||||
|
let program = infer text in
|
||||||
|
Interpret.program program (input entries)
|
||||||
|
|
||||||
|
let cases =
|
||||||
|
[
|
||||||
|
( "the example query selects and projects rows",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run "expensive_order.delta"
|
||||||
|
[ row "Ada" 1500; row "Bo" 900; row "Lin" 2200 ]
|
||||||
|
in
|
||||||
|
check_equal_string "result" "(collection (1 (tuple \"Ada\" 300)) (3 (tuple \"Lin\" 440)))"
|
||||||
|
(Value.to_string result) );
|
||||||
|
( "a filter that keeps nothing yields an empty collection",
|
||||||
|
fun () ->
|
||||||
|
let result = run "expensive_order.delta" [ row "Bo" 10 ] in
|
||||||
|
check_equal_string "empty" "(collection )" (Value.to_string result) );
|
||||||
|
( "an empty input yields an empty collection and zero aggregates",
|
||||||
|
fun () ->
|
||||||
|
check_equal_string "no rows" "(collection )" (Value.to_string (run "expensive_order.delta" []));
|
||||||
|
check_equal_int "revenue" 0
|
||||||
|
(match run "revenue.delta" [] with Value.VInt total -> total | _ -> -1);
|
||||||
|
check_equal_int "count" 0
|
||||||
|
(match run "count_large.delta" [] with Value.VInt total -> total | _ -> -1) );
|
||||||
|
( "revenue sums the taxed totals",
|
||||||
|
fun () ->
|
||||||
|
let result = run "revenue.delta" [ row "Ada" 1000; row "Bo" 250 ] in
|
||||||
|
check_equal_string "revenue" "250" (Value.to_string result) );
|
||||||
|
( "count_large counts the retained rows",
|
||||||
|
fun () ->
|
||||||
|
let result = run "count_large.delta" [ row "Ada" 1000; row "Bo" 250; row "Lin" 501 ] in
|
||||||
|
check_equal_string "count" "2" (Value.to_string result) );
|
||||||
|
( "negative values are preserved",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> filter (fun r -> r < 0) |> sum\n"
|
||||||
|
[ Value.VInt (-5); Value.VInt 3; Value.VInt (-7) ]
|
||||||
|
in
|
||||||
|
check_equal_string "sum" "-12" (Value.to_string result) );
|
||||||
|
( "integer division truncates towards zero",
|
||||||
|
fun () ->
|
||||||
|
let result = run_text "input rows : collection int\nquery q = rows |> map (fun r -> r / 3) |> sum\n"
|
||||||
|
[ Value.VInt 10; Value.VInt (-10) ]
|
||||||
|
in
|
||||||
|
check_equal_string "sum" "0" (Value.to_string result) );
|
||||||
|
( "division by zero is a runtime error",
|
||||||
|
fun () ->
|
||||||
|
(try
|
||||||
|
ignore (run_text "input rows : collection int\nquery q = rows |> map (fun r -> 100 / r) |> sum\n"
|
||||||
|
[ Value.VInt 5; Value.VInt 0 ]);
|
||||||
|
fail "division" "expected a runtime error"
|
||||||
|
with Division_by_zero -> check "raised" true) );
|
||||||
|
( "the mapping expression runs only where the filter keeps rows",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> filter (fun r -> r > 10) |> map (fun r -> 1000 / r) |> sum\n"
|
||||||
|
[ Value.VInt 5; Value.VInt 20 ]
|
||||||
|
in
|
||||||
|
check_equal_string "sum" "50" (Value.to_string result) );
|
||||||
|
( "boolean operators short circuit",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> count\n"
|
||||||
|
[ Value.VInt 1 ]
|
||||||
|
in
|
||||||
|
check_equal_string "count" "1" (Value.to_string result);
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> filter (fun r -> false && 1 / r > 0) |> count\n"
|
||||||
|
[ Value.VInt 0 ]
|
||||||
|
in
|
||||||
|
check_equal_string "short circuit and" "0" (Value.to_string result);
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> filter (fun r -> true || 1 / r > 0) |> count\n"
|
||||||
|
[ Value.VInt 0 ]
|
||||||
|
in
|
||||||
|
check_equal_string "short circuit or" "1" (Value.to_string result) );
|
||||||
|
( "map preserves keys and filter keeps the original keys",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> filter (fun r -> r > 1) |> map (fun r -> r * 10)\n"
|
||||||
|
[ Value.VInt 1; Value.VInt 2; Value.VInt 3 ]
|
||||||
|
in
|
||||||
|
check_equal_string "keys" "(collection (2 20) (3 30))" (Value.to_string result) );
|
||||||
|
( "helpers are applied at their call sites",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text
|
||||||
|
"input rows : collection int\nlet scale n = n * 3\nlet offset n = scale n + 1\nquery q = rows |> map offset |> sum\n"
|
||||||
|
[ Value.VInt 1; Value.VInt 2 ]
|
||||||
|
in
|
||||||
|
check_equal_string "sum" "11" (Value.to_string result) );
|
||||||
|
( "records and tuples are compared structurally",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text
|
||||||
|
"type pair = { first : int; second : int }\ninput rows : collection pair\nquery q = rows |> filter (fun r -> r = { first = 1; second = 2 }) |> count\n"
|
||||||
|
[ Value.VRecord ("pair", [ ("first", Value.VInt 1); ("second", Value.VInt 2) ]); Value.VRecord ("pair", [ ("first", Value.VInt 2); ("second", Value.VInt 1) ]) ]
|
||||||
|
in
|
||||||
|
check_equal_string "count" "1" (Value.to_string result) );
|
||||||
|
( "a constant query ignores the input",
|
||||||
|
fun () ->
|
||||||
|
let result = run_text "input rows : collection int\nquery q = 6 * 7\n" [ Value.VInt 1 ] in
|
||||||
|
check_equal_string "constant" "42" (Value.to_string result) );
|
||||||
|
( "out of range arithmetic follows machine integers",
|
||||||
|
fun () ->
|
||||||
|
let result =
|
||||||
|
run_text "input rows : collection int\nquery q = rows |> sum\n"
|
||||||
|
[ Value.VInt max_int; Value.VInt 1 ]
|
||||||
|
in
|
||||||
|
check_equal_string "wrapped" (string_of_int min_int) (Value.to_string result) );
|
||||||
|
]
|
||||||
@@ -374,5 +374,6 @@ let () =
|
|||||||
Test_harness.run_suite "program" program_cases;
|
Test_harness.run_suite "program" program_cases;
|
||||||
Test_harness.run_suite "resolve" resolve_cases;
|
Test_harness.run_suite "resolve" resolve_cases;
|
||||||
Test_harness.run_suite "types" Test_type.cases;
|
Test_harness.run_suite "types" Test_type.cases;
|
||||||
|
Test_harness.run_suite "interpret" Test_incremental.cases;
|
||||||
Printf.printf "%d cases, %d failures\n" (Test_harness.case_count ()) (Test_harness.failure_count ());
|
Printf.printf "%d cases, %d failures\n" (Test_harness.case_count ()) (Test_harness.failure_count ());
|
||||||
exit (if Test_harness.failure_count () = 0 then 0 else 1)
|
exit (if Test_harness.failure_count () = 0 then 0 else 1)
|
||||||
Reference in new issue
Block a user