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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
|
module type LISTENER = functor
(T : Transport.TRANSPORT)
(S : Management.Physical.S with type error = string)
-> sig
val run : T.t -> S.t -> unit
end
module Make : LISTENER =
functor
(T : Transport.TRANSPORT)
(S : Management.Physical.S with type error = string)
->
struct
module type SubS = Sublanguage.S with type storage = S.t
module AlgebraOps = Algebra.Make (S)
(* Per-connection state: the client must issue (use "name") before
any sublanguage command is accepted. *)
type conn_state =
| NoMultigroup
| Active of Nt.handle * Management.Multigroup.multigroup
let read_command input =
try Ok (Sexplib.Sexp.input_sexp input)
with Sexplib.Sexp.Parse_error { err_msg; _ } ->
Error (Error.SyntaxError err_msg)
let sublanguages =
List.fold_right
(fun (module Language : SubS) ->
Utilities.StringMap.add Language.name (module Language : SubS))
[
(module Drl.Sublanguage.Make (S) : SubS);
(module Ddl.Sublanguage.Make (S) : SubS);
(module Dml.Sublanguage.Make (S) : SubS);
(module Icl.Sublanguage.Make (S) : SubS);
(* (module Dcl.Sublanguage.Make (S) : SubS); *)
(module Prl.Sublanguage.Make (S) : SubS);
(module Scl.Sublanguage.Make (S) : SubS);
]
Utilities.StringMap.empty
let fmap f m = Result.bind m f
let find_language tag =
Utilities.StringMap.find_opt tag sublanguages
|> Option.to_result ~none:(Error.UnrecognizedSublanguage tag)
let execute_sublanguage storage db expr (module Language : SubS) =
Language.parse_sexp expr
|> fmap (Language.execute storage db)
|> Result.map_error (fun e ->
Error.SublanguageError (Language.sexp_of_error e))
let execute_command storage db = function
| Sexplib.Sexp.(List [ Atom tag; expr ]) ->
find_language tag |> fmap (execute_sublanguage storage db expr)
| s -> Error (Error.MalformedExpression s)
(* Thread handle and db through the connection after a sublanguage result. *)
let perform handle db result =
match result with
| Sublanguage_types.Transition new_db -> Ok (handle, new_db, result)
| Sublanguage_types.SessionSwitch _ ->
Error (Error.SyntaxError "SessionSwitch not supported")
| Sublanguage_types.CreateMultigroup _ ->
Error (Error.SyntaxError "CreateMultigroup not supported")
| _ -> Ok (handle, db, result)
let current_limit = 16
let materialize_generator gen limit =
let rec go gen pos acc count =
if count >= limit then (List.rev acc, true)
else
match gen (Some pos) with
| Generator.Done -> (List.rev acc, false)
| Generator.Error _ -> (List.rev acc, false)
| Generator.Value (t, next) -> (
let mat =
match t with
| Tuple.Materialized m -> Some m
| Tuple.NonMaterialized _ -> None
in
match mat with
| None -> go next (pos + 1) acc count
| Some m -> go next (pos + 1) (m :: acc) (count + 1))
in
go gen 0 [] 0
let materialize_relation storage (rel : Relation.relation) limit =
let gen = AlgebraOps.to_generator storage rel in
materialize_generator gen limit
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 print_with_time str =
(* TODO: a proper logger *)
let now = Unix.gettimeofday () in
let tm = Unix.localtime now in
let orange = "\027[38;5;208m" in
let reset = "\027[0m" in
let formatted_time =
Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d" (tm.Unix.tm_year + 1900)
(tm.Unix.tm_mon + 1) tm.Unix.tm_mday tm.Unix.tm_hour tm.Unix.tm_min
tm.Unix.tm_sec
in
print_endline
@@ Printf.sprintf "%s[%s]%s %s" orange formatted_time reset str
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;
print_with_time response
let send_error out_ch e =
output_response out_ch
Sexplib.Sexp.(List [ Atom "error"; Error.sexp_of_error e ])
let send_ok_message out_ch msg =
output_response out_ch
Sexplib.Sexp.(List [ Atom "ok"; List [ Atom "message"; Atom msg ] ])
let serialize storage (db : Management.Multigroup.multigroup) =
let open Sexplib.Sexp in
function
| Error e -> List [ Atom "error"; Error.sexp_of_error e ]
| Ok (Sublanguage_types.Cursor { cursor_id; rows; has_more }) ->
let row_sexps = List.map tuple_to_sexp rows in
let rows_sexp = List row_sexps in
List
[
Atom "cursor";
List [ Atom "id"; Atom cursor_id ];
List [ Atom "rows"; rows_sexp ];
List
[
Atom "row_count"; Atom (string_of_int (List.length row_sexps));
];
List [ Atom "has_more"; Atom (string_of_bool has_more) ];
List [ Atom "db_hash"; Atom db#hash ];
List [ Atom "db_name"; Atom db#name ];
(* TODO: forsake the global cursor register in favour of per-connection bookkeeping *)
]
| Ok (Sublanguage_types.Query rel) ->
let tuples, truncated =
materialize_relation storage rel current_limit
in
let schema_sexp =
List
(List.map
(fun (a, d) -> List [ Atom a; Atom d ])
rel#schema)
in
let rows_sexp = List (List.map tuple_to_sexp tuples) in
List
[
Atom "relation";
List [ Atom "name"; Atom rel#name ];
List [ Atom "schema"; schema_sexp ];
List [ Atom "rows"; rows_sexp ];
List
[ Atom "row_count"; Atom (string_of_int (List.length tuples)) ];
List [ Atom "truncated"; Atom (string_of_bool truncated) ];
List [ Atom "db_hash"; Atom db#hash ];
List [ Atom "db_name"; Atom db#name ];
]
| Ok (Sublanguage_types.Transition new_db) ->
List
[
Atom "ok";
List [ Atom "db_hash"; Atom new_db#hash ];
List [ Atom "db_name"; Atom new_db#name ];
]
| Ok (Sublanguage_types.SessionSwitch multigroup) ->
List
[
Atom "ok";
List
[
Atom "message"; Atom ("Switched to multigroup " ^ multigroup);
];
]
| Ok (Sublanguage_types.CreateMultigroup name) ->
List
[
Atom "ok";
List [ Atom "message"; Atom ("Multigroup " ^ name ^ " created") ];
]
(* Open a handle to a multigroup by name via the NT object layer. The claims
from Firewall become the connection_context for access checks. *)
let open_multigroup (_storage : S.t) claims name =
match
Nt.open_handle () () ~path:[ "multigroups"; name ]
~connection_context:claims
with
| Error e -> Error (Error.SyntaxError e)
| Ok handle -> (
match Nt.deserialize () () Nt.Multigroup handle.Nt.obj with
| Error e -> Error (Error.SyntaxError e)
| Ok (Nt.Multigroup mg) -> Ok (handle, mg)
| Ok _ -> Error (Error.SyntaxError "unexpected object type at path"))
let handle_use storage claims output state name =
match open_multigroup storage claims name with
| Error e -> send_error output e
| Ok (handle, mg) ->
state := Active (handle, mg);
send_ok_message output ("using multigroup: " ^ name)
let handle_sublanguage storage output state sexp handle db =
match
Ok sexp
|> fmap (execute_command storage db)
|> fmap (perform handle db)
with
| Error e -> send_error output e
| Ok (new_handle, new_db, r) ->
state := Active (new_handle, new_db);
serialize storage new_db (Ok r) |> output_response output
let dispatch_command storage claims output state sexp =
match sexp with
| Sexplib.Sexp.(List [ Atom "use"; Atom name ]) ->
handle_use storage claims output state name
| _ -> (
match !state with
| NoMultigroup ->
send_error output
(Error.SyntaxError "no multigroup selected; send (use \"name\")")
| Active (handle, db) ->
handle_sublanguage storage output state sexp handle db)
let handle_client connection (storage : S.t) =
let input = T.input connection in
let output = T.output connection in
(* Authenticate via NT PermissionsManager::Firewall before anything else. *)
let claims =
match Nt.firewall () with
| Ok c -> c
| Error e -> Printf.eprintf "Auth failed: %s\n%!" e; ""
in
let state = ref NoMultigroup in
(try
while true do
match read_command input with
| Error e -> send_error output e
| Ok sexp -> dispatch_command storage claims output state sexp
done
with
| End_of_file -> ()
| e ->
Printf.eprintf "Error handling connection: %s" (Printexc.to_string e))
let spawn_handler storage connection =
Stdlib.Domain.spawn (fun () -> handle_client connection storage)
let run transport storage =
T.listen transport;
while true do
T.accept transport |> spawn_handler storage |> ignore
done
end
|