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.relation) : Generator.t = match rel#kind with | `Ephemeral | `Domain -> ((Obj.magic rel) : < generator : Generator.t >)#generator | `Pseudo -> ((Obj.magic rel) : < as_generator : (string * Conventions.AbstractValue.t) list -> Generator.t >)#as_generator [] | `Stored -> begin (* TODO: Avoid materializing all tuple hashes/tuples at once. Current implementation calls tuple_hashes rel which loads all hashes into memory, then load_tuples which loads all tuples. This defeats lazy evaluation and scales badly (fails for billions of tuples). Replace with paginated tuple_hashes or Merkle streaming to load hashes/tuples on demand in chunks. *) match Ops.load_tuples storage rel#hash with | Error _ -> fun _pos -> Generator.Error ("Failed to load: " ^ rel#name) | Ok ts -> list_generator (List.map (fun t -> Tuple.Materialized t) ts) end let of_generator ~name ~schema ?constraints ?cardinality generator membership_criteria: Relation.ephemeral = let cardinality = match cardinality with | Some c -> c | None -> Conventions.Cardinality.AlephZero in new Relation.ephemeral ~name ~schema ~constraints ~cardinality ~generator ~membership_criteria ~provenance:None ~lineage:None let const_relation (pairs : (string * Conventions.AbstractValue.t) list) : Relation.ephemeral = 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 (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in of_generator ~name:"const" ~schema (list_generator [ tuple ]) membership_criteria (* 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 (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name:("σ_" ^ rel#name) ~schema:rel#schema ?constraints:rel#constraints lazy_gen membership_criteria) 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#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 let filtered_constraints = match rel#constraints with | None | Some [] -> None | Some cs -> ( let kept = List.filter_map (fun (name, c) -> match Constraint.filter_by_attrs attrs c with | Some c' -> Some (name, c') | None -> None) cs in match kept with [] -> None | _ -> Some kept) in (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name:("π_" ^ rel#name) ~schema:new_schema ?constraints:filtered_constraints lazy_gen membership_criteria) 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#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 let renamed_constraints = match rel#constraints with | None | Some [] -> None | Some cs -> Some (List.map (fun (name, c) -> (name, Constraint.rename_vars renames c)) cs) in (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name:("ρ_" ^ rel#name) ~schema:new_schema ?constraints:renamed_constraints lazy_gen membership_criteria) let equijoin storage (attrs : string list) left right: (Relation.ephemeral, error) result = 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#schema @ List.filter (fun (n, _) -> (not (List.mem n attrs)) && not (List.exists (fun (m, _) -> m = n) left#schema)) right#schema in let merged_constraints = match (left#constraints, right#constraints) with | None, None -> None | Some cs, None | None, Some cs -> Some cs | Some cs1, Some cs2 -> Some (Constraint.merge cs1 cs2) in let name = "⋈_" ^ left#name ^ "_" ^ right#name in 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 make_join_gen left_gen right_tuples = 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 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 (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name ~schema:merged_schema ?constraints:merged_constraints (make_join_gen left_gen right_tuples) membership_criteria) 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#name ^ "_" ^ rel2#name in (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in (* Conservative: drop constraints since they only hold if both inputs agree *) Ok (of_generator ~name ~schema:rel1#schema chain_gen membership_criteria) 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#name ^ "_" ^ rel2#name in (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name ~schema:rel1#schema ?constraints:rel1#constraints lazy_gen membership_criteria) 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 (* TODO: Make it always true for now, gotta rework on this generator *) let membership_criteria _tuple = true in Ok (of_generator ~name:("τ_" ^ rel#name) ~schema:rel#schema ?constraints:rel#constraints ~cardinality:(Conventions.Cardinality.Finite n) lazy_gen membership_criteria) 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)