blob: 5a7560f03093ed72c7c300e554b0abcb6ba9af14 (
plain) (
blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
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
|