anchor.diff:    | String s1, String s2 -> reverse * compare s1 s2
     | _ -> failwith "Cannot compare values"
   
  -let evalCondition op actual expected =
  +let evalCondition op actual (expected:string) =
     match op, actual with 
  +  | _, Date _ -> failwith "Comparison on dates not supported"
  +  | Equals, Bool b -> (expected.ToLower() = "true" && b) || (expected.ToLower() = "false" && not b)
  +  | NotEquals, Bool b -> (expected.ToLower() = "true" && not b) || (expected.ToLower() = "false" && b)
     | Equals, String s -> expected = s
     | NotEquals, String s -> expected <> s
  -  | (Equals | NotEquals), Number _ -> failwith "Equals and not equals work only on strings"
  +  | (Equals | NotEquals), Number _ -> failwith "Equals and not equals work only on strings or booleans"
     | GreaterThan, Number n -> n > decimal expected
     | LessThan, Number n -> n < decimal expected
     | InRange, Number n -> let expected = expected.Split(',') in n > decimal expected.[0] && n < decimal expected.[1]
  -  | (GreaterThan | LessThan | InRange), String _ -> failwith "Relational operator work only on numbers"
  +  | (GreaterThan | LessThan | InRange), (Bool _ | String _) -> failwith "Relational operator work only on numbers"
   
   let transformData (objs:seq<(string * Value)[]>) = function
     | Empty -> objs
  
positive.code:       objs |> Seq.filter (fun o ->
          let f = match op with And -> Seq.forall | Or -> Seq.exists
          conds |> f (fun (op, fld, value) -> evalCondition op (pickField fld o) value))
    | GroupBy(flds, aggs) ->
        let aggs = List.rev aggs
        objs 
        |> Seq.groupBy (fun j -> List.map (fun f -> pickField f j) flds)
        |> Seq.map (fun (kvals, group) ->
          aggs 
          |> List.collect (applyAggregation (List.zip flds kvals) group)
          |> Array.ofSeq)
  
  // ----------------------------------------------------------------------------
  // Schema inference and CSV file parsing
  
negative.code: let inline pickField name obj = 
    Array.pick (fun (n, v) -> if n = name then Some v else None) obj
  
  let asString = function String s -> s | Number n -> string n | Date d -> d.ToString("g") | Bool b -> string b
  let asDecimal = function String s -> decimal s | Number n -> n | Date d -> decimal d.Ticks | Bool true -> 1M | Bool false -> 0M
  
  let applyGroupAggregation kvals group = function
   | GroupAggregation.GroupKey -> kvals
   | GroupAggregation.CountAll -> [ "count", Number(group |> Seq.length |> decimal) ]
   | GroupAggregation.CountDistinct(fld) -> [ fld, Number(group |> Seq.distinctBy (pickField fld) |> Seq.length |> decimal) ]
   | GroupAggregation.ConcatValues(fld) -> [ fld, group |> Seq.map(fun obj -> pickField fld obj |> asString) |> Seq.distinct |> String.concat ", " |> String ]
   | GroupAggregation.Sum(fld) -> [ fld, group |> Seq.sumBy (fun obj -> pickField fld obj |> asDecimal) |> Number ]
   | GroupAggregation.Mean(fld) -> [ fld, group |> Seq.averageBy (fun obj -> pickField fld obj |> asDecimal) |> Number ]
  
  let applyWinAggregation kname group agg = 
    let nums fld = group |> Seq.map (fun obj -> pickField fld obj |> asDecimal)
    let kvalues = Seq.map (pickField kname) group |> Array.ofSeq
    match agg with
    | WindowAggregation.Mean(fld) -> [ fld, nums fld |> Seq.average |> Number ]
    | WindowAggregation.Min(fld) -> [ fld, nums fld |> Seq.min |> Number ]
    | WindowAggregation.Max(fld) -> [ fld, nums fld |> Seq.max |> Number ]
    | WindowAggregation.FirstKey -> [ "first " + kname, kvalues.[0] ]
    | WindowAggregation.LastKey -> [ "last " + kname, kvalues.[kvalues.Length-1] ]
    | WindowAggregation.MiddleKey -> [ "middle " + kname, kvalues.[(kvalues.Length-1)/2] ]
  
  let compareFields o1 o2 (fld, order) = 
    let reverse = if order = Descending then -1 else 1
  