aboutsummaryrefslogblamecommitdiffstats
path: root/lib/algebra.ml
blob: fffd4f329dd22fc79d6909e622223b2ab971f01f (plain) (tree)
1
2
3
4
5
6
7
8
9
10



                                                              
 




                                                                 

                                                                       



                               

                                                     
                                                                 

               



                                                                         

                                                                     




                                                                             




                                                                                
 





                                                                
                                                                               
                           
                                            
                                               
                                                   
 








                                                                                
      
                                                                  
                                                                 







                                         
                                          

                                                



                                                            
      




                                                                          




                                                 








                                                                           

                      


                                                                      
                  


                                          

                                                                                 

                           
      


                                         









                                                             
      



                                                                       

                                                             


                                                                   

                               





                                                                          


                                         


                                                                    
                  


                                          

                                                                                

                           
      



                                         



                                                                          
      



                                                                      

                                                         




















































                                                                            
                                                                              
























                                                                                      
                                                   
                                               
                                       
                      


                                                    
                                                  
                                                            
                                                          
                                                                                
                                      







                                                                           
                                      




                                                                
            
                          
          

                                                   

                                                     
            
                                                     
                                  









                                                                                    


                                                                        







                                                                   
                                           







                                                             






                                           
                                  

                                                                                 

                                 

                                                                        
                                                                                  

                                                                  
                                                                    

                                                          

                                                                   





                                                



























                                                                               
                                                             





                                         


                                            

                                                                                 

                             
      




                                                                          




                                                                           



                                                                  
   
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 -> (
        (* 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. *)
        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 ?constraints ?cardinality gen =
    let cardinality =
      match cardinality with
      | Some c -> c
      | None -> Conventions.Cardinality.AlephZero
    in
    Relation.make ~hash:None ~name ~schema ~tree:None ~constraints ~cardinality
      ~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 ?constraints:rel.Relation.constraints
         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
    let filtered_constraints =
      match rel.Relation.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
    Ok
      (of_generator
         ~name:("π_" ^ rel.Relation.name)
         ~schema:new_schema ?constraints:filtered_constraints 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
    let renamed_constraints =
      match rel.Relation.constraints with
      | None | Some [] -> None
      | Some cs ->
          Some
            (List.map
               (fun (name, c) -> (name, Constraint.rename_vars renames c))
               cs)
    in
    Ok
      (of_generator
         ~name:("ρ_" ^ rel.Relation.name)
         ~schema:new_schema ?constraints:renamed_constraints lazy_gen)

  let equijoin storage (attrs : string list) left right =
    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 merged_constraints =
      match (left.Relation.constraints, right.Relation.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.Relation.name ^ "_" ^ right.Relation.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 bindings_of_tuple attrs_list = function
      | Tuple.Materialized m ->
          List.filter_map
            (fun attr ->
              match Tuple.AttributeMap.find_opt attr m.Tuple.attributes with
              | Some a -> Some (attr, a.Attribute.value)
              | None -> None)
            attrs_list
      | _ -> []
    in
    match right.Relation.producer with
    | Some right_producer when attrs <> [] ->
        (* Right is a function predicate: for each left tuple, call the producer
           with that tuple's join-attribute bindings to obtain matching right rows. *)
        let left_gen = to_generator storage left in
        let make_right_producer_join_gen lgen =
          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 bindings = bindings_of_tuple attrs lt in
                match drain (right_producer bindings) with
                | Error (GeneratorError e | StorageError e) -> Generator.Error e
                | Ok right_tuples -> (
                    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 lgen 0
        in
        Ok
          (of_generator ~name ~schema:merged_schema
             ?constraints:merged_constraints
             (make_right_producer_join_gen left_gen))
    | _ -> (
        let right_gen = to_generator storage right in
        match drain right_gen with
        | Error e -> Error e
        | Ok right_tuples ->
            let left_gen_result =
              match left.Relation.producer with
              | Some left_producer when attrs <> [] ->
                  (* Left is a function predicate: collect all left tuples by
                     calling the producer once per unique binding set from right. *)
                  let unique_bindings =
                    right_tuples
                    |> List.filter_map (fun rt ->
                        let b = bindings_of_tuple attrs rt in
                        if List.length b = List.length attrs then Some b
                        else None)
                    |> List.sort_uniq compare
                  in
                  List.fold_left
                    (fun acc bindings ->
                      Result.bind acc (fun ts ->
                          match drain (left_producer bindings) with
                          | Ok new_ts -> Ok (ts @ new_ts)
                          | Error e -> Error e))
                    (Ok []) unique_bindings
                  |> Result.map list_generator
              | _ -> Ok (to_generator storage left)
            in
            Result.bind left_gen_result (fun left_gen ->
                Ok
                  (of_generator ~name ~schema:merged_schema
                     ?constraints:merged_constraints
                     (make_join_gen left_gen right_tuples))))

  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
    (* Conservative: drop constraints since they only hold if both inputs agree *)
    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
             ?constraints:rel1.Relation.constraints 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 ?constraints:rel.Relation.constraints
         ~cardinality:(Conventions.Cardinality.Finite n) 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