Support empty sets in (x IN @foo) exprs (#109)
[sqlgg.git] / src / gen_csharp.ml
blobd2c8484e9718239bc21b4cb1d1ca0e4dc754776f
1 (* C# code generation *)
3 open Printf
4 open ExtLib
5 open Sqlgg
6 open Prelude
8 open Gen
9 open Sql
11 module G = Gen_cxx
12 module J = Gen_java
13 module Values = G.Values
15 let comment = G.comment
16 let empty_line = G.empty_line
18 let comment_doc sl =
19 output "/**";
20 output_l (List.map (fun str -> Gen_caml.replace_all ~sub:"*/" ~by:"* /" ~str) sl);
21 output "*/"
23 let comment_xml summary params =
24 let summary = String.nsplit summary "\n" in
25 let params = List.map (fun (n,s) -> sprintf "<param name=\"%s\">%s</param>" n s) params in
26 comment_doc ("<summary>" :: summary @ ("</summary>" :: params))
28 let (start_class,end_class) = J.start_class,J.end_class
29 let (start_ns,end_ns) = J.start_ "namespace"
31 let quote = J.quote
33 module L = struct
35 let as_api_type = function
36 | Type.Int -> "Int64"
37 | Text -> "String"
38 | Float -> "Float"
39 | Blob -> "String"
40 | Bool -> "Boolean"
41 | Decimal -> "Decimal"
42 | Datetime -> "Datetime"
43 | Any -> "String"
44 | Unit _ -> assert false
46 let as_lang_type = as_api_type
48 end
50 module T = Translate(L)
52 open L
53 open T
55 let get_column attr index =
56 sprintf "reader.Get%s(%u)"
57 (attr.domain |> as_api_type)
58 index
60 let schema_to_string = G.Values.to_string $ G.Values.inject $ schema_to_values
62 let output_schema_binder _ schema =
63 let name = "callback" in
64 output "public delegate void %s(%s);" name (schema_to_string schema);
65 empty_line ();
66 name
68 let output_schema_binder index schema =
69 match schema with
70 | [] -> None
71 | _ -> Some (output_schema_binder index schema)
73 let set_param index param =
74 let (id,t) = param in
75 let name = default_name "param" index in
76 output "IDbDataParameter %s = _cmd.CreateParameter();" name;
77 output "%s.ParameterName = \"@%s\";" name id;
78 output "%s.DbType = DbType.%s;" name t; (* FIXME? this is ok because api_type = lang_type *)
79 output "_cmd.Parameters.Add(%s);" name
81 let output_params_binder params =
82 List.iteri set_param params;
83 output "_cmd.Prepare();"
85 type t = unit
87 let start () = ()
89 let func_execute index stmt =
90 let params = params_only stmt.vars in
91 let values = G.Values.inject @@ values_of_params params in
92 let schema_binder_name = output_schema_binder index stmt.schema in
93 let is_select = Option.is_some schema_binder_name in
94 let doc = if is_select then ["result", schema_to_string stmt.schema] else [] in
95 comment_xml "execute query" doc;
96 let func_name = if is_select then "execute_reader" else "execute" in
97 let result = "public " ^ if is_select then "IEnumerable<IDataReader>" else "int" in
98 G.func result func_name values (fun () ->
99 output "if (null == _cmd)";
100 G.open_curly ();
101 output "_cmd = _conn.CreateCommand();";
102 output "_cmd.CommandText = sql;";
103 output_params_binder values;
104 G.close_curly "";
105 output "if (null != CommandTimeout) _cmd.CommandTimeout = CommandTimeout.Value;";
106 List.iteri
107 (fun i (name,_) -> output "((IDbDataParameter)_cmd.Parameters[%u]).Value = %s;" i name)
108 values;
109 begin match schema_binder_name with
110 | None -> output "return _cmd.ExecuteNonQuery();"
111 | Some _ ->
112 output "IDataReader reader = _cmd.ExecuteReader();";
113 output "while (reader.Read())";
114 G.open_curly ();
115 output "yield return reader;";
116 G.close_curly "";
117 output "reader.Close();";
118 end);
119 if is_select then
120 begin
121 empty_line ();
122 let result = match schema_binder_name with None -> [] | Some name -> ["result",name] in
123 let all_params = values @ result in
124 G.func "public int" "execute" all_params (fun () ->
125 let args = List.mapi (fun index attr -> get_column attr index) stmt.schema in
126 output "int count = 0;";
127 output "foreach (var reader in execute_reader(%s))" (Values.inline values);
128 G.open_curly ();
129 output "result(%s);" (Values.join args);
130 output "count++;";
131 G.close_curly "";
132 output "return count;"
134 empty_line ();
135 match stmt.schema with
136 | [attr] ->
137 let t = as_lang_type attr.domain in
138 G.func ("public IEnumerable<" ^ t ^ ">") "rows" values (fun () ->
139 output "foreach (var reader in execute_reader(%s))" (Values.inline values);
140 G.open_curly ();
141 output "yield return %s;" (get_column attr 0);
142 G.close_curly ""
144 | _ ->
145 start_class "row";
146 List.iteri (fun index attr ->
147 output "public readonly %s %s;"
148 (as_lang_type attr.domain)
149 (name_of attr index)
150 ) stmt.schema;
151 empty_line ();
152 G.func "public" "row" ["reader","IDataReader"] (fun () ->
153 List.iteri (fun i attr -> output "%s = %s;" (name_of attr i) (get_column attr i)) stmt.schema;
155 end_class "row";
156 G.func "public IEnumerable<row>" "rows" values (fun () ->
157 output "foreach (var reader in execute_reader(%s))" (Values.inline values);
158 G.open_curly ();
159 output "yield return new row(reader);";
160 G.close_curly ""
164 else (* not is_select *)
165 begin
166 match stmt.kind with
167 | Insert _ when List.length values > 1 ->
168 G.func "public int" "execute<T>" ["v","T"] (fun () ->
169 output "return execute(%s);" (values |> Values.names |> List.map ((^) "v.") |> Values.join)
171 | _ -> ()
175 let generate_code index stmt =
176 let name = choose_name stmt.props stmt.kind index in
177 let sql = quote (get_sql_string_only stmt) in
178 start_class name;
179 output "IDbCommand _cmd;";
180 output "IDbConnection _conn;";
181 output "public int? CommandTimeout;";
182 output "static string sql = %s;" sql;
183 empty_line ();
184 G.func "public" name ["db","IDbConnection"] (fun () ->
185 output "_cmd = null;";
186 output "_conn = db;";
188 empty_line ();
189 func_execute index stmt;
190 end_class name;
191 name
193 let generate_all names =
194 start_class "all";
195 output "public readonly IDbConnection db;";
196 List.iter (fun s -> output "public %s %s;" s s) names;
197 empty_line ();
198 G.func "public" "all" ["db","IDbConnection"] (fun () ->
199 output "this.db = db;";
200 List.iter (fun name -> output "%s = new %s(db);" name name) names
202 end_class "all"
204 let generate () name stmts =
205 params_mode := Some Named; (* only named params allowed *)
206 let using = ["System";"System.Data";"System.Collections.Generic"] in
207 List.iter (fun s -> output "using %s;" s) using;
208 empty_line ();
209 start_ns name;
210 let names = List.mapi generate_code stmts in
211 generate_all names;
212 end_ns name