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

                           
             

        


                                    

                                             
 


                         
                                                               

                                        
                                                                                                 

                                     
                                                                                               

       








                                                                              
                                

                            
                                            
                                                                                      
 
                      
                     
                                        

                                                                          




                                                    
                                                     

                                 

                                                   
                                                                    
 
                                                               
                                                                              
 
                                      
                                               
                                                                                   
                                                 
 

                                                
          
                                                        
                                     
                                                                                       





                                                   
                                        

                             
                                                                                    
 


                                                                           

                              

                                                                    
                                                        
              






                                                                                    
                                            
                                                                              
                                                       
              


                                                      
                                                    
                                                                                      
                                                       
                                                                                           
 


                                              
                                      




                                                                          


                                                                                         


                                                                               
                                       
                                         

                                             

                                                  

















                                                                                           

                       

                         
                                                     

          
module Make =
functor
  (T : Transport.TRANSPORT)
  (NT : Nt.S)
  ->
  struct
    module type SubS = Sublanguage.S

    module Sess = Session.Make (NT)
    module B = Branch.Make (NT)
    module SC = Sublanguage_context.Make (NT)

    module Error = struct
      open Condition

      let syntax_error msg = condition "syntax-error" msg empty

      let unrecognized_sublanguage tag =
        condition "unrecognized-sublanguage" "Unrecognized sublanguage" ("tag" |=| of_string tag)

      let malformed_expression expr =
        condition "malformed-expression" "Malformed expression" ("expression" |=| of_sexp expr)
    end

    (** Per-connection state: session owns the active branch. The branch's
        [relation_path] becomes the context resolver, so all sublanguages
        address the full RNT namespace rather than a single multigroup. **)
    type conn_state = {claims: Nt.claims; session: Sess.session}

    (** Build a fresh execution context from the current session state. Called
        at the top of every handle_sublanguage so it always reflects the live
        branch after any prior VCL switch. **)
    let make_ctx ({session; claims} : conn_state) : Sublanguage_context.t =
      SC.make_ctx session claims

    let read_command input =
      try Ok (Sexplib.Sexp.input_sexp input)
      with Sexplib.Sexp.Parse_error {err_msg; _} -> Error (Error.syntax_error err_msg)

    let sublanguages =
      List.fold_right
        (fun (module Language : SubS) ->
          Utilities.StringMap.add Language.name (module Language : SubS) )
        [ (module Drl.Sublanguage.Make (NT) : SubS);
          (module Ddl.Sublanguage.Make (NT) : SubS);
          (module Dml.Sublanguage.Make (NT) : SubS);
          (module Icl.Sublanguage.Make (NT) : SubS);
          (module Prl.Sublanguage.Make (NT) : SubS);
          (module Scl.Sublanguage.Make (NT) : SubS);
          (module Vcl.Sublanguage.Make (NT) : SubS) ]
        Utilities.StringMap.empty

    let find_language tag =
      Utilities.StringMap.find_opt tag sublanguages
      |> Option.to_result ~none:(Error.unrecognized_sublanguage tag)

    let execute_sublanguage ctx expr (module Language : SubS) =
      Language.parse_sexp expr |> Utilities.Result.fmap (Language.execute ctx)

    let execute_command ctx = function
      | Sexplib.Sexp.(List [Atom tag; expr]) ->
          find_language tag |> Utilities.Result.fmap (execute_sublanguage ctx expr)
      | s -> Error (Error.malformed_expression s)

    let tuple_to_sexp (t : Tuple.materialized) =
      let open Sexplib.Sexp in
      List
        ( Tuple.AttributeMap.bindings t.Tuple.attributes
        |> List.map (fun (k, attr) ->
            List [Atom k; Conventions.AbstractValue.sexp_of_t attr.Attribute.value] ) )

    let output_response out_ch sexp =
      let response = Sexplib.Sexp.to_string sexp in
      output_string out_ch response;
      output_string out_ch "\n";
      flush out_ch;
      Utilities.print_with_time response

    let send_error out_ch e =
      output_response out_ch Sexplib.Sexp.(List [Atom "error"; Condition.to_sexp e])

    (* Serializes a sublanguage result.  [branch] is the write target whose
       tip and name surface in Cursor / Transition responses. *)
    let serialize (branch : B.branch) =
      let open Sexplib.Sexp in
      function
      | Error e -> List [Atom "error"; Condition.to_sexp e]
      | Ok (Sublanguage_types.Cursor {cursor_id; rows; has_more}) ->
          let row_sexps = List.map tuple_to_sexp rows in
          List
            [ Atom "cursor";
              List [Atom "id"; Atom cursor_id];
              List [Atom "rows"; List row_sexps];
              List [Atom "row_count"; Atom (string_of_int (List.length row_sexps))];
              List [Atom "has_more"; Atom (string_of_bool has_more)];
              List [Atom "snapshot"; Atom branch#tip];
              List [Atom "branch"; Atom branch#name] ]
      | Ok (Sublanguage_types.Query _rel) ->
          List [Atom "error"; Atom "unexpected Query result: use Cursor path"]
      | Ok (Sublanguage_types.Transition _new_cache) ->
          List
            [ Atom "ok";
              List [Atom "snapshot"; Atom branch#tip];
              List [Atom "branch"; Atom branch#name] ]
      | Ok (Sublanguage_types.SessionSwitch name) ->
          List [Atom "ok"; List [Atom "message"; Atom ("Switched to branch " ^ name)]]
      | Ok (Sublanguage_types.CreateMultigroup name) ->
          List [Atom "ok"; List [Atom "message"; Atom ("Multigroup " ^ name ^ " created")]]

    let handle_sublanguage output state sexp =
      let ctx = make_ctx !state in
      match execute_command ctx sexp with
      | Error e -> send_error output e
      | Ok result ->
          (* Sync updated schema_cache back into the branch mirror on any
             state-advancing result (DDL, ICL, VCL).  For VCL the session
             already holds the new branch; refresh_mg is a no-op on it but
             correct for DDL/ICL which stay on the same branch. *)
          ( match result with
          | Sublanguage_types.Transition delta -> !state.session#branch#apply_delta delta
          | _ -> () );
          serialize !state.session#branch (Ok result) |> output_response output

    let handle_client connection =
      let input = T.input connection in
      let output = T.output connection in
      match NT.authenticate Nt.PlainText with
      | Error e ->
          (* FIXME: why is this a syntax error? *)
          send_error output e
      | Ok claims -> (
        match Sess.open_session claims ~branch_name:"master" with
        | Error e -> send_error output e
        | Ok sess -> (
            let state = ref {claims; session= sess} in
            try
              while true do
                match read_command input with
                | Error e -> send_error output e
                | Ok sexp -> handle_sublanguage output state sexp
              done
            with
            | End_of_file -> ignore (!state.session#close ())
            | e ->
                ignore (!state.session#close ());
                Printf.eprintf "Error handling connection: %s" (Printexc.to_string e) ) )

    let spawn_handler connection = Stdlib.Domain.spawn (fun () -> handle_client connection)

    let run transport =
      T.listen transport;
      while true do
        T.accept transport |> spawn_handler |> ignore
      done
  end