diff options
Diffstat (limited to '')
| -rw-r--r-- | lib/algebra.ml | 229 | ||||
| -rw-r--r-- | lib/drl/ast.ml | 25 | ||||
| -rw-r--r-- | lib/drl/executor.ml | 70 | ||||
| -rw-r--r-- | lib/drl/parser.ml | 9 |
4 files changed, 333 insertions, 0 deletions
diff --git a/lib/algebra.ml b/lib/algebra.ml new file mode 100644 index 0000000..8701dcb --- /dev/null +++ b/lib/algebra.ml @@ -0,0 +1,229 @@ +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.t) : Generator.t = + match rel.Relation.generator with + | Some gen -> gen + | None -> + let hashes = Ops.tuple_hashes rel in + (match Ops.load_tuples storage hashes with + | Error _ -> (fun _pos -> Generator.Error ("Failed to load: " ^ rel.Relation.name)) + | Ok ts -> list_generator (List.map (fun t -> Tuple.Materialized t) ts)) + + let of_generator ~name ~schema gen = + Relation.make + ~hash:None ~name ~schema ~tree:None ~constraints:None + ~cardinality:Conventions.Cardinality.AlephZero + ~generator:(Some gen) + ~membership_criteria:(fun _ -> true) + ~provenance:Relation.Provenance.Undefined + ~lineage:(Relation.Lineage.Base "derived") + + let const_relation (pairs : (string * Conventions.AbstractValue.t) list) : Relation.t = + 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 + of_generator ~name:"const" ~schema (list_generator [tuple]) + + (* 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 + Ok (of_generator ~name:("σ_" ^ rel.Relation.name) ~schema:rel.Relation.schema lazy_gen) + + 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.Relation.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 + Ok (of_generator ~name:("π_" ^ rel.Relation.name) ~schema:new_schema lazy_gen) + + 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.Relation.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 + Ok (of_generator ~name:("ρ_" ^ rel.Relation.name) ~schema:new_schema lazy_gen) + + let equijoin storage (attrs : string list) left right = + 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 + 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.Relation.schema @ + List.filter (fun (n, _) -> + not (List.mem n attrs) && + not (List.exists (fun (m, _) -> m = n) left.Relation.schema)) + right.Relation.schema + in + let join_gen = + 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 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 name = "⋈_" ^ left.Relation.name ^ "_" ^ right.Relation.name in + Ok (of_generator ~name ~schema:merged_schema join_gen) + + 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.Relation.name ^ "_" ^ rel2.Relation.name in + Ok (of_generator ~name ~schema:rel1.Relation.schema chain_gen) + + 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.Relation.name ^ "_" ^ rel2.Relation.name in + Ok (of_generator ~name ~schema:rel1.Relation.schema lazy_gen) + + 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 + Ok (of_generator ~name:("τ_" ^ rel.Relation.name) ~schema:rel.Relation.schema lazy_gen) + + 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) diff --git a/lib/drl/ast.ml b/lib/drl/ast.ml new file mode 100644 index 0000000..f94965d --- /dev/null +++ b/lib/drl/ast.ml @@ -0,0 +1,25 @@ +open Sexplib.Std + +(** Literal values for Const nodes *) +type value = + | Int of int + | Float of float + | Str of string + | Bool of bool +[@@deriving sexp] + +type direction = Asc | Desc +[@@deriving sexp] + +type query = + | Base of string (** base relation by name *) + | Const of (string * value) list (** constant single-tuple relation *) + | Select of query * query (** σ semijoin: (filter, source) *) + | Join of string list * query * query (** ⋈ natural equijoin on named attrs *) + | Cartesian of query * query (** × Cartesian product *) + | Project of string list * query (** π restrict columns *) + | Rename of (string * string) list * query (** ρ rename (old,new) pairs *) + | Union of query * query (** ∪ — compatible schemas assumed *) + | Diff of query * query (** − *) + | Take of int * query (** τ *) +[@@deriving sexp] diff --git a/lib/drl/executor.ml b/lib/drl/executor.ml new file mode 100644 index 0000000..7588b33 --- /dev/null +++ b/lib/drl/executor.ml @@ -0,0 +1,70 @@ +module Make(Storage : Management.Physical.S) = struct + module Alg = Algebra.Make(Storage) + module Ops = Manipulation.Make(Storage) + + type error = + | RelationNotFound of string + | AlgebraError of Algebra.error + + let wrap = Result.map_error (fun e -> AlgebraError e) + + (* Convert Ast.value → AbstractValue.t (Obj.t) via Obj.repr *) + let ast_value_to_abstract : Ast.value -> Conventions.AbstractValue.t = function + | Ast.Int n -> Obj.repr n + | Ast.Float f -> Obj.repr f + | Ast.Str s -> Obj.repr s + | Ast.Bool b -> Obj.repr b + + let rec execute + (storage : Storage.t) + (db : Management.Database.t) + (q : Ast.query) + : (Relation.t, error) Result.t = + let ( >>= ) = Result.bind in + match q with + | Ast.Base name -> + (match Ops.get_relation db ~name with + | None -> Error (RelationNotFound name) + | Some rel -> Ok rel) + | Ast.Const pairs -> + Ok (Alg.const_relation + (List.map (fun (k, v) -> (k, ast_value_to_abstract v)) pairs)) + | Ast.Select (filter_q, source_q) -> + execute storage db filter_q >>= fun filter -> + execute storage db source_q >>= fun source -> + (* semijoin = project(source attrs, equijoin on common attrs) *) + let common = List.filter_map (fun (n, _) -> + if List.exists (fun (m, _) -> m = n) filter.Relation.schema + then Some n else None) source.Relation.schema in + let source_attrs = List.map fst source.Relation.schema in + wrap (Alg.equijoin storage common source filter) >>= fun joined -> + wrap (Alg.project storage source_attrs joined) + | Ast.Join (attrs, q1, q2) -> + execute storage db q1 >>= fun r1 -> + execute storage db q2 >>= fun r2 -> + wrap (Alg.equijoin storage attrs r1 r2) + | Ast.Project (attrs, q) -> + execute storage db q >>= fun rel -> + wrap (Alg.project storage attrs rel) + | Ast.Rename (renames, q) -> + execute storage db q >>= fun rel -> + wrap (Alg.rename storage renames rel) + | Ast.Cartesian (q1, q2) -> + execute storage db q1 >>= fun r1 -> + execute storage db q2 >>= fun r2 -> + (* Cartesian product = join on no attrs *) + wrap (Alg.equijoin storage [] r1 r2) + | Ast.Union (q1, q2) -> + execute storage db q1 >>= fun r1 -> + execute storage db q2 >>= fun r2 -> + wrap (Alg.union storage r1 r2) + | Ast.Diff (q1, q2) -> + execute storage db q1 >>= fun r1 -> + execute storage db q2 >>= fun r2 -> + wrap (Alg.diff storage r1 r2) + | Ast.Take (n, q) -> + execute storage db q >>= fun rel -> + wrap (Alg.take storage n rel) +end + +module Memory = Make(Management.Physical.Memory) diff --git a/lib/drl/parser.ml b/lib/drl/parser.ml new file mode 100644 index 0000000..71d694b --- /dev/null +++ b/lib/drl/parser.ml @@ -0,0 +1,9 @@ +type error = ParseError of string + +let of_string s = + match Sexplib.Sexp.of_string s |> Ast.query_of_sexp with + | q -> Ok q + | exception exn -> Error (ParseError (Printexc.to_string exn)) + +let to_string q = + Ast.sexp_of_query q |> Sexplib.Sexp.to_string_hum |
