Mercurial > urweb
comparison src/sqlite.sml @ 885:e6070333d8a8
demo/sql works with SQLite
author | Adam Chlipala <adamc@hcoop.net> |
---|---|
date | Fri, 17 Jul 2009 16:29:36 -0400 |
parents | |
children | 5805fa825fe8 |
comparison
equal
deleted
inserted
replaced
884:ced093080e17 | 885:e6070333d8a8 |
---|---|
1 (* Copyright (c) 2009, Adam Chlipala | |
2 * All rights reserved. | |
3 * | |
4 * Redistribution and use in source and binary forms, with or without | |
5 * modification, are permitted provided that the following conditions are met: | |
6 * | |
7 * - Redistributions of source code must retain the above copyright notice, | |
8 * this list of conditions and the following disclaimer. | |
9 * - Redistributions in binary form must reproduce the above copyright notice, | |
10 * this list of conditions and the following disclaimer in the documentation | |
11 * and/or other materials provided with the distribution. | |
12 * - The names of contributors may not be used to endorse or promote products | |
13 * derived from this software without specific prior written permission. | |
14 * | |
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
25 * POSSIBILITY OF SUCH DAMAGE. | |
26 *) | |
27 | |
28 structure SQLite :> SQLITE = struct | |
29 | |
30 open Settings | |
31 open Print.PD | |
32 open Print | |
33 | |
34 fun p_sql_type t = | |
35 case t of | |
36 Int => "integer" | |
37 | Float => "real" | |
38 | String => "text" | |
39 | Bool => "integer" | |
40 | Time => "integer" | |
41 | Blob => "blob" | |
42 | Channel => "integer" | |
43 | Client => "integer" | |
44 | Nullable t => p_sql_type t | |
45 | |
46 val ident = String.translate (fn #"'" => "PRIME" | |
47 | ch => str ch) | |
48 | |
49 fun checkRel (table, checkNullable) (s, xts) = | |
50 let | |
51 val q = "SELECT COUNT(*) FROM sqlite_master WHERE type = '" ^ table ^ "' AND name = '" | |
52 ^ s ^ "'" | |
53 in | |
54 box [string "if (sqlite3_prepare_v2(conn->conn, \"", | |
55 string q, | |
56 string "\", -1, &stmt, NULL) != SQLITE_OK) {", | |
57 newline, | |
58 box [string "sqlite3_close(conn->conn);", | |
59 newline, | |
60 string "uw_error(ctx, FATAL, \"Query preparation failed:\\n", | |
61 string q, | |
62 string "\");", | |
63 newline], | |
64 string "}", | |
65 newline, | |
66 newline, | |
67 | |
68 string "while ((res = sqlite3_step(stmt)) == SQLITE_BUSY)", | |
69 newline, | |
70 box [string "sleep(1);", | |
71 newline], | |
72 newline, | |
73 string "if (res == SQLITE_DONE) {", | |
74 newline, | |
75 box [string "sqlite3_finalize(stmt);", | |
76 newline, | |
77 string "sqlite3_close(conn->conn);", | |
78 newline, | |
79 string "uw_error(ctx, FATAL, \"No row returned:\\n", | |
80 string q, | |
81 string "\");", | |
82 newline], | |
83 string "}", | |
84 newline, | |
85 newline, | |
86 string "if (res != SQLITE_ROW) {", | |
87 newline, | |
88 box [string "sqlite3_finalize(stmt);", | |
89 newline, | |
90 string "sqlite3_close(conn->conn);", | |
91 newline, | |
92 string "uw_error(ctx, FATAL, \"Error getting row:\\n", | |
93 string q, | |
94 string "\");", | |
95 newline], | |
96 string "}", | |
97 newline, | |
98 newline, | |
99 | |
100 string "if (sqlite3_column_count(stmt) != 1) {", | |
101 newline, | |
102 box [string "sqlite3_finalize(stmt);", | |
103 newline, | |
104 string "sqlite3_close(conn->conn);", | |
105 newline, | |
106 string "uw_error(ctx, FATAL, \"Bad column count:\\n", | |
107 string q, | |
108 string "\");", | |
109 newline], | |
110 string "}", | |
111 newline, | |
112 newline, | |
113 | |
114 string "if (sqlite3_column_int(stmt, 0) != 1) {", | |
115 newline, | |
116 box [string "sqlite3_finalize(stmt);", | |
117 newline, | |
118 string "sqlite3_close(conn->conn);", | |
119 newline, | |
120 string "uw_error(ctx, FATAL, \"Table '", | |
121 string s, | |
122 string "' does not exist.\");", | |
123 newline], | |
124 string "}", | |
125 newline, | |
126 newline, | |
127 string "sqlite3_finalize(stmt);", | |
128 newline] | |
129 end | |
130 | |
131 fun init {dbstring, prepared = ss, tables, views, sequences} = | |
132 let | |
133 val db = ref dbstring | |
134 in | |
135 app (fn s => | |
136 case String.fields (fn ch => ch = #"=") s of | |
137 [name, value] => | |
138 (case name of | |
139 "dbname" => db := value | |
140 | _ => ()) | |
141 | _ => ()) (String.tokens Char.isSpace dbstring); | |
142 | |
143 box [string "typedef struct {", | |
144 newline, | |
145 box [string "sqlite3 *conn;", | |
146 newline, | |
147 p_list_sepi (box []) | |
148 (fn i => fn _ => | |
149 box [string "sqlite3_stmt *p", | |
150 string (Int.toString i), | |
151 string ";", | |
152 newline]) | |
153 ss], | |
154 string "} uw_conn;", | |
155 newline, | |
156 newline, | |
157 | |
158 string "void uw_client_init(void) {", | |
159 newline, | |
160 box [string "uw_sqlfmtInt = \"%lld%n\";", | |
161 newline, | |
162 string "uw_sqlfmtFloat = \"%g%n\";", | |
163 newline, | |
164 string "uw_Estrings = 0;", | |
165 newline, | |
166 string "uw_sqlsuffixString = \"\";", | |
167 newline, | |
168 string "uw_sqlsuffixBlob = \"\";", | |
169 newline, | |
170 string "uw_sqlfmtUint4 = \"%u%n\";", | |
171 newline], | |
172 string "}", | |
173 newline, | |
174 newline, | |
175 | |
176 if #persistent (currentProtocol ()) then | |
177 box [string "static void uw_db_validate(uw_context ctx) {", | |
178 newline, | |
179 string "uw_conn *conn = uw_get_db(ctx);", | |
180 newline, | |
181 string "sqlite3_stmt *stmt;", | |
182 newline, | |
183 string "int res;", | |
184 newline, | |
185 newline, | |
186 p_list_sep newline (checkRel ("table", true)) tables, | |
187 p_list_sep newline (fn name => checkRel ("table", true) | |
188 (name, [("id", Settings.Client)])) sequences, | |
189 p_list_sep newline (checkRel ("view", false)) views, | |
190 string "}", | |
191 newline, | |
192 newline, | |
193 | |
194 string "static void uw_db_prepare(uw_context ctx) {", | |
195 newline, | |
196 string "uw_conn *conn = uw_get_db(ctx);", | |
197 newline, | |
198 newline, | |
199 | |
200 p_list_sepi newline (fn i => fn (s, n) => | |
201 let | |
202 fun uhoh this s args = | |
203 box [p_list_sepi (box []) | |
204 (fn j => fn () => | |
205 box [string | |
206 "sqlite3_finalize(conn->p", | |
207 string (Int.toString j), | |
208 string ");", | |
209 newline]) | |
210 (List.tabulate (i, fn _ => ())), | |
211 box (if this then | |
212 [string | |
213 "sqlite3_finalize(conn->p", | |
214 string (Int.toString i), | |
215 string ");", | |
216 newline] | |
217 else | |
218 []), | |
219 string "sqlite3_close(conn->conn);", | |
220 newline, | |
221 string "uw_error(ctx, FATAL, \"", | |
222 string s, | |
223 string "\"", | |
224 p_list_sep (box []) (fn s => box [string ", ", | |
225 string s]) args, | |
226 string ");", | |
227 newline] | |
228 in | |
229 box [string "if (sqlite3_prepare_v2(conn->conn, \"", | |
230 string (String.toString s), | |
231 string "\", -1, &conn->p", | |
232 string (Int.toString i), | |
233 string ", NULL) != SQLITE_OK) {", | |
234 newline, | |
235 uhoh false ("Error preparing statement: " | |
236 ^ String.toString s) [], | |
237 string "}", | |
238 newline] | |
239 end) | |
240 ss, | |
241 | |
242 string "}"] | |
243 else | |
244 box [string "static void uw_db_prepare(uw_context ctx) { }", | |
245 newline, | |
246 string "static void uw_db_validate(uw_context ctx) { }"], | |
247 newline, | |
248 newline, | |
249 | |
250 string "void uw_db_init(uw_context ctx) {", | |
251 newline, | |
252 string "sqlite3 *sqlite;", | |
253 newline, | |
254 string "uw_conn *conn;", | |
255 newline, | |
256 newline, | |
257 string "if (sqlite3_open(\"", | |
258 string (!db), | |
259 string "\", &sqlite) != SQLITE_OK) uw_error(ctx, FATAL, ", | |
260 string "\"Can't open SQLite database.\");", | |
261 newline, | |
262 newline, | |
263 string "conn = calloc(1, sizeof(uw_conn));", | |
264 newline, | |
265 string "conn->conn = sqlite;", | |
266 newline, | |
267 string "uw_set_db(ctx, conn);", | |
268 newline, | |
269 string "uw_db_validate(ctx);", | |
270 newline, | |
271 string "uw_db_prepare(ctx);", | |
272 newline, | |
273 string "}", | |
274 newline, | |
275 newline, | |
276 | |
277 string "void uw_db_close(uw_context ctx) {", | |
278 newline, | |
279 string "uw_conn *conn = uw_get_db(ctx);", | |
280 newline, | |
281 p_list_sepi (box []) | |
282 (fn i => fn _ => | |
283 box [string "if (conn->p", | |
284 string (Int.toString i), | |
285 string ") sqlite3_finalize(conn->p", | |
286 string (Int.toString i), | |
287 string ");", | |
288 newline]) | |
289 ss, | |
290 string "sqlite3_close(conn->conn);", | |
291 newline, | |
292 string "}", | |
293 newline, | |
294 newline, | |
295 | |
296 string "int uw_db_begin(uw_context ctx) {", | |
297 newline, | |
298 string "uw_conn *conn = uw_get_db(ctx);", | |
299 newline, | |
300 newline, | |
301 string "if (sqlite3_exec(conn->conn, \"BEGIN\", NULL, NULL, NULL) == SQLITE_OK)", | |
302 newline, | |
303 box [string "return 0;", | |
304 newline], | |
305 string "else {", | |
306 newline, | |
307 box [string "fprintf(stderr, \"Begin error: %s\\n\", sqlite3_errmsg(conn->conn));", | |
308 newline, | |
309 string "return 1;", | |
310 newline], | |
311 string "}", | |
312 newline, | |
313 string "}", | |
314 newline, | |
315 string "int uw_db_commit(uw_context ctx) {", | |
316 newline, | |
317 string "uw_conn *conn = uw_get_db(ctx);", | |
318 newline, | |
319 string "if (sqlite3_exec(conn->conn, \"COMMIT\", NULL, NULL, NULL) == SQLITE_OK)", | |
320 newline, | |
321 box [string "return 0;", | |
322 newline], | |
323 string "else {", | |
324 newline, | |
325 box [string "fprintf(stderr, \"Commit error: %s\\n\", sqlite3_errmsg(conn->conn));", | |
326 newline, | |
327 string "return 1;", | |
328 newline], | |
329 string "}", | |
330 newline, | |
331 string "}", | |
332 newline, | |
333 newline, | |
334 | |
335 string "int uw_db_rollback(uw_context ctx) {", | |
336 newline, | |
337 string "uw_conn *conn = uw_get_db(ctx);", | |
338 newline, | |
339 string "if (sqlite3_exec(conn->conn, \"ROLLBACK\", NULL, NULL, NULL) == SQLITE_OK)", | |
340 newline, | |
341 box [string "return 0;", | |
342 newline], | |
343 string "else {", | |
344 newline, | |
345 box [string "fprintf(stderr, \"Rollback error: %s\\n\", sqlite3_errmsg(conn->conn));", | |
346 newline, | |
347 string "return 1;", | |
348 newline], | |
349 string "}", | |
350 newline, | |
351 string "}", | |
352 newline, | |
353 newline] | |
354 end | |
355 | |
356 fun p_getcol {loc, wontLeakStrings, col = i, typ = t} = | |
357 let | |
358 fun p_unsql t = | |
359 case t of | |
360 Int => box [string "sqlite3_column_int64(stmt, ", string (Int.toString i), string ")"] | |
361 | Float => box [string "sqlite3_column_double(stmt, ", string (Int.toString i), string ")"] | |
362 | String => | |
363 if wontLeakStrings then | |
364 box [string "sqlite3_column_text(stmt, ", string (Int.toString i), string ")"] | |
365 else | |
366 box [string "uw_strdup(ctx, sqlite3_column_text(stmt, ", string (Int.toString i), string "))"] | |
367 | Bool => box [string "(uw_Basis_bool)sqlite3_column_int(stmt, ", string (Int.toString i), string ")"] | |
368 | Time => box [string "(uw_Basis_time)sqlite3_column_int64(stmt, ", string (Int.toString i), string ")"] | |
369 | Blob => box [string "({", | |
370 newline, | |
371 string "char *data = sqlite3_column_blob(stmt, ", | |
372 string (Int.toString i), | |
373 string ");", | |
374 newline, | |
375 string "uw_Basis_blob b = {sqlite3_column_bytes(stmt, ", | |
376 string (Int.toString i), | |
377 string "), data};", | |
378 newline, | |
379 string "b;", | |
380 newline, | |
381 string "})"] | |
382 | Channel => box [string "sqlite3_column_int64(stmt, ", string (Int.toString i), string ")"] | |
383 | Client => box [string "sqlite3_column_int(stmt, ", string (Int.toString i), string ")"] | |
384 | |
385 | Nullable _ => raise Fail "Postgres: Recursive Nullable" | |
386 | |
387 fun getter t = | |
388 case t of | |
389 Nullable t => | |
390 box [string "(sqlite3_column_type(stmt, ", | |
391 string (Int.toString i), | |
392 string ") == SQLITE_NULL ? NULL : ", | |
393 case t of | |
394 String => getter t | |
395 | _ => box [string "({", | |
396 newline, | |
397 string (p_sql_ctype t), | |
398 space, | |
399 string "*tmp = uw_malloc(ctx, sizeof(", | |
400 string (p_sql_ctype t), | |
401 string "));", | |
402 newline, | |
403 string "*tmp = ", | |
404 getter t, | |
405 string ";", | |
406 newline, | |
407 string "tmp;", | |
408 newline, | |
409 string "})"], | |
410 string ")"] | |
411 | _ => | |
412 box [string "(sqlite3_column_type(stmt, ", | |
413 string (Int.toString i), | |
414 string ") == SQLITE_NULL ? ", | |
415 box [string "({", | |
416 string (p_sql_ctype t), | |
417 space, | |
418 string "tmp;", | |
419 newline, | |
420 string "uw_error(ctx, FATAL, \"", | |
421 string (ErrorMsg.spanToString loc), | |
422 string ": Unexpectedly NULL field #", | |
423 string (Int.toString i), | |
424 string "\");", | |
425 newline, | |
426 string "tmp;", | |
427 newline, | |
428 string "})"], | |
429 string " : ", | |
430 p_unsql t, | |
431 string ")"] | |
432 in | |
433 getter t | |
434 end | |
435 | |
436 fun queryCommon {loc, query, cols, doCols} = | |
437 box [string "int r;", | |
438 newline, | |
439 | |
440 string "sqlite3_reset(stmt);", | |
441 newline, | |
442 | |
443 string "uw_end_region(ctx);", | |
444 newline, | |
445 string "while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {", | |
446 newline, | |
447 doCols p_getcol, | |
448 string "}", | |
449 newline, | |
450 newline, | |
451 | |
452 string "if (r == SQLITE_BUSY) {", | |
453 box [string "sleep(1);", | |
454 newline, | |
455 string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");", | |
456 newline], | |
457 string "}", | |
458 newline, | |
459 newline, | |
460 | |
461 string "if (r != SQLITE_DONE) uw_error(ctx, FATAL, \"", | |
462 string (ErrorMsg.spanToString loc), | |
463 string ": query step failed: %s\\n%s\", ", | |
464 query, | |
465 string ", sqlite3_errmsg(conn->conn));", | |
466 newline, | |
467 newline] | |
468 | |
469 fun query {loc, cols, doCols} = | |
470 box [string "uw_conn *conn = uw_get_db(ctx);", | |
471 newline, | |
472 string "sqlite3 *stmt;", | |
473 newline, | |
474 newline, | |
475 string "if (sqlite3_prepare_v2(conn->conn, query, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s\\n%s\", sqlite3_errmsg(conn->conn));", | |
476 newline, | |
477 newline, | |
478 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", | |
479 newline, | |
480 newline, | |
481 | |
482 queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}, | |
483 | |
484 string "uw_pop_cleanup(ctx);", | |
485 newline] | |
486 | |
487 fun p_inputs loc = | |
488 p_list_sepi (box []) | |
489 (fn i => fn t => | |
490 let | |
491 fun bind (t, arg) = | |
492 case t of | |
493 Int => box [string "sqlite3_bind_int64(stmt, ", | |
494 string (Int.toString (i + 1)), | |
495 string ", ", | |
496 arg, | |
497 string ")"] | |
498 | Float => box [string "sqlite3_bind_double(stmt, ", | |
499 string (Int.toString (i + 1)), | |
500 string ", ", | |
501 arg, | |
502 string ")"] | |
503 | String => box [string "sqlite3_bind_text(stmt, ", | |
504 string (Int.toString (i + 1)), | |
505 string ", ", | |
506 arg, | |
507 string ", -1, SQLITE_TRANSIENT)"] | |
508 | Bool => box [string "sqlite3_bind_int(stmt, ", | |
509 string (Int.toString (i + 1)), | |
510 string ", ", | |
511 arg, | |
512 string ")"] | |
513 | Time => box [string "sqlite3_bind_int64(stmt, ", | |
514 string (Int.toString (i + 1)), | |
515 string ", ", | |
516 arg, | |
517 string ")"] | |
518 | Blob => box [string "sqlite3_bind_blob(stmt, ", | |
519 string (Int.toString (i + 1)), | |
520 string ", ", | |
521 arg, | |
522 string ".data, ", | |
523 arg, | |
524 string ".size, SQLITE_TRANSIENT"] | |
525 | Channel => box [string "sqlite_bind_int64(stmt, ", | |
526 string (Int.toString (i + 1)), | |
527 string ", ", | |
528 arg, | |
529 string ")"] | |
530 | Client => box [string "sqlite3_bind_int(stmt, ", | |
531 string (Int.toString (i + 1)), | |
532 string ", ", | |
533 arg, | |
534 string ")"] | |
535 | Nullable t => box [string "(", | |
536 arg, | |
537 string " == NULL ? sqlite3_bind_null(stmt, ", | |
538 string (Int.toString (i + 1)), | |
539 string ") : ", | |
540 bind (t, case t of | |
541 String => arg | |
542 | _ => box [string "(*", arg, string ")"]), | |
543 string ")"] | |
544 in | |
545 box [string "if (", | |
546 bind (t, box [string "arg", string (Int.toString (i + 1))]), | |
547 string " != SQLITE_OK) uw_error(ctx, FATAL, \"", | |
548 string (ErrorMsg.spanToString loc), | |
549 string ": Error binding parameter #", | |
550 string (Int.toString (i + 1)), | |
551 string ": %s\", sqlite3_errmsg(conn->conn));", | |
552 newline] | |
553 end) | |
554 | |
555 fun queryPrepared {loc, id, query, inputs, cols, doCols, nested} = | |
556 box [string "uw_conn *conn = uw_get_db(ctx);", | |
557 newline, | |
558 if nested then | |
559 box [string "sqlite3_stmt *stmt;", | |
560 newline] | |
561 else | |
562 box [string "sqlite3_stmt *stmt = conn->p", | |
563 string (Int.toString id), | |
564 string ";", | |
565 newline, | |
566 newline, | |
567 | |
568 string "if (stmt == NULL) {", | |
569 newline], | |
570 | |
571 string "if (sqlite3_prepare_v2(conn->conn, \"", | |
572 string (String.toString query), | |
573 string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ", | |
574 string (String.toString query), | |
575 string "\\n%s\", sqlite3_errmsg(conn->conn));", | |
576 newline, | |
577 if nested then | |
578 box [string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", | |
579 newline] | |
580 else | |
581 box [string "conn->p", | |
582 string (Int.toString id), | |
583 string " = stmt;", | |
584 newline, | |
585 string "}", | |
586 newline, | |
587 newline, | |
588 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);", | |
589 newline, | |
590 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);", | |
591 newline], | |
592 newline, | |
593 | |
594 p_inputs loc inputs, | |
595 newline, | |
596 | |
597 queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"", | |
598 string (String.toString query), | |
599 string "\""]}, | |
600 | |
601 string "uw_pop_cleanup(ctx);", | |
602 newline, | |
603 if nested then | |
604 box [] | |
605 else | |
606 box [string "uw_pop_cleanup(ctx);", | |
607 newline]] | |
608 | |
609 fun dmlCommon {loc, dml} = | |
610 box [string "int r;", | |
611 newline, | |
612 | |
613 string "if ((r = sqlite3_step(stmt)) == SQLITE_BUSY) {", | |
614 box [string "sleep(1);", | |
615 newline, | |
616 string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");", | |
617 newline], | |
618 string "}", | |
619 newline, | |
620 newline, | |
621 | |
622 string "if (r != SQLITE_DONE) uw_error(ctx, FATAL, \"", | |
623 string (ErrorMsg.spanToString loc), | |
624 string ": DML step failed: %s\\n%s\", ", | |
625 dml, | |
626 string ", sqlite3_errmsg(conn->conn));", | |
627 newline] | |
628 | |
629 fun dml loc = | |
630 box [string "uw_conn *conn = uw_get_db(ctx);", | |
631 newline, | |
632 string "sqlite3 *stmt;", | |
633 newline, | |
634 newline, | |
635 string "if (sqlite3_prepare_v2(conn->conn, dml, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s\\n%s\", dml, sqlite3_errmsg(conn->conn));", | |
636 newline, | |
637 newline, | |
638 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", | |
639 newline, | |
640 newline, | |
641 | |
642 dmlCommon {loc = loc, dml = string "dml"}, | |
643 | |
644 string "uw_pop_cleanup(ctx);", | |
645 newline] | |
646 | |
647 fun dmlPrepared {loc, id, dml, inputs} = | |
648 box [string "uw_conn *conn = uw_get_db(ctx);", | |
649 newline, | |
650 string "sqlite3_stmt *stmt = conn->p", | |
651 string (Int.toString id), | |
652 string ";", | |
653 newline, | |
654 newline, | |
655 | |
656 string "if (stmt == NULL) {", | |
657 newline, | |
658 box [string "if (sqlite3_prepare_v2(conn->conn, \"", | |
659 string (String.toString dml), | |
660 string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ", | |
661 string (String.toString dml), | |
662 string "\\n%s\", sqlite3_errmsg(conn->conn));", | |
663 newline, | |
664 string "conn->p", | |
665 string (Int.toString id), | |
666 string " = stmt;", | |
667 newline], | |
668 string "}", | |
669 newline, | |
670 | |
671 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);", | |
672 newline, | |
673 string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);", | |
674 newline, | |
675 | |
676 p_inputs loc inputs, | |
677 newline, | |
678 | |
679 dmlCommon {loc = loc, dml = box [string "\"", | |
680 string (String.toString dml), | |
681 string "\""]}, | |
682 | |
683 string "uw_pop_cleanup(ctx);", | |
684 newline, | |
685 string "uw_pop_cleanup(ctx);", | |
686 newline] | |
687 | |
688 fun nextval {loc, seqE, seqName} = | |
689 box [string "uw_conn *conn = uw_get_db(ctx);", | |
690 newline, | |
691 string "char *insert = ", | |
692 case seqName of | |
693 SOME s => string ("\"INSERT INTO " ^ s ^ " VALUES ()\"") | |
694 | NONE => box [string "uw_Basis_strcat(ctx, \"INSERT INTO \", uw_Basis_strcat(ctx, ", | |
695 seqE, | |
696 string ", \" VALUES ()\"))"], | |
697 string ";", | |
698 newline, | |
699 string "char *delete = ", | |
700 case seqName of | |
701 SOME s => string ("\"DELETE FROM " ^ s ^ "\"") | |
702 | NONE => box [string "uw_Basis_strcat(ctx, \"DELETE FROM \", ", | |
703 seqE, | |
704 string ")"], | |
705 string ";", | |
706 newline, | |
707 newline, | |
708 | |
709 string "if (sqlite3_exec(conn->conn, insert, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' INSERT failed\");", | |
710 newline, | |
711 string "n = sqlite3_last_insert_rowid(conn->conn);", | |
712 newline, | |
713 string "if (sqlite3_exec(conn->conn, delete, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' DELETE failed\");", | |
714 newline] | |
715 | |
716 fun nextvalPrepared _ = raise Fail "SQLite.nextvalPrepared called" | |
717 | |
718 fun sqlifyString s = "'" ^ String.translate (fn #"'" => "''" | |
719 | ch => | |
720 if Char.isPrint ch then | |
721 str ch | |
722 else | |
723 (ErrorMsg.error | |
724 "Non-printing character found in SQL string literal"; | |
725 "")) | |
726 (String.toString s) ^ "'" | |
727 | |
728 fun p_cast (s, _) = s | |
729 | |
730 fun p_blank _ = "?" | |
731 | |
732 val () = addDbms {name = "sqlite", | |
733 header = "sqlite3.h", | |
734 link = "-lsqlite3", | |
735 init = init, | |
736 p_sql_type = p_sql_type, | |
737 query = query, | |
738 queryPrepared = queryPrepared, | |
739 dml = dml, | |
740 dmlPrepared = dmlPrepared, | |
741 nextval = nextval, | |
742 nextvalPrepared = nextvalPrepared, | |
743 sqlifyString = sqlifyString, | |
744 p_cast = p_cast, | |
745 p_blank = p_blank, | |
746 supportsDeleteAs = false, | |
747 createSequence = fn s => "CREATE TABLE " ^ s ^ " (id INTEGER PRIMARY KEY AUTO INCREMENT)", | |
748 textKeysNeedLengths = false, | |
749 supportsNextval = false, | |
750 supportsNestedPrepared = false, | |
751 sqlPrefix = ""} | |
752 | |
753 end |