adam@1656: (* Copyright (c) 2009-2010, Adam Chlipala adamc@885: * All rights reserved. adamc@885: * adamc@885: * Redistribution and use in source and binary forms, with or without adamc@885: * modification, are permitted provided that the following conditions are met: adamc@885: * adamc@885: * - Redistributions of source code must retain the above copyright notice, adamc@885: * this list of conditions and the following disclaimer. adamc@885: * - Redistributions in binary form must reproduce the above copyright notice, adamc@885: * this list of conditions and the following disclaimer in the documentation adamc@885: * and/or other materials provided with the distribution. adamc@885: * - The names of contributors may not be used to endorse or promote products adamc@885: * derived from this software without specific prior written permission. adamc@885: * adamc@885: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" adamc@885: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE adamc@885: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE adamc@885: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE adam@1682: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR adamc@885: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF adamc@885: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS adamc@885: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN adamc@885: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) adamc@885: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE adamc@885: * POSSIBILITY OF SUCH DAMAGE. adamc@885: *) adamc@885: adamc@885: structure SQLite :> SQLITE = struct adamc@885: adamc@885: open Settings adamc@885: open Print.PD adamc@885: open Print adamc@885: adamc@885: fun p_sql_type t = adamc@885: case t of adamc@885: Int => "integer" adamc@885: | Float => "real" adamc@885: | String => "text" adamc@1014: | Char => "text" adamc@885: | Bool => "integer" adamc@887: | Time => "text" adamc@885: | Blob => "blob" adamc@885: | Channel => "integer" adamc@885: | Client => "integer" adamc@885: | Nullable t => p_sql_type t adamc@885: adamc@885: val ident = String.translate (fn #"'" => "PRIME" adamc@885: | ch => str ch) adamc@885: adamc@885: fun checkRel (table, checkNullable) (s, xts) = adamc@885: let adamc@885: val q = "SELECT COUNT(*) FROM sqlite_master WHERE type = '" ^ table ^ "' AND name = '" adamc@885: ^ s ^ "'" adamc@885: in adamc@885: box [string "if (sqlite3_prepare_v2(conn->conn, \"", adamc@885: string q, adamc@885: string "\", -1, &stmt, NULL) != SQLITE_OK) {", adamc@885: newline, adamc@885: box [string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@1266: string "uw_error(ctx, FATAL, \"Query preparation failed:
", adamc@885: string q, adamc@885: string "\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "while ((res = sqlite3_step(stmt)) == SQLITE_BUSY)", adamc@885: newline, adamc@885: box [string "sleep(1);", adamc@885: newline], adamc@885: newline, adamc@885: string "if (res == SQLITE_DONE) {", adamc@885: newline, adamc@885: box [string "sqlite3_finalize(stmt);", adamc@885: newline, adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@1266: string "uw_error(ctx, FATAL, \"No row returned:
", adamc@885: string q, adamc@885: string "\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: string "if (res != SQLITE_ROW) {", adamc@885: newline, adamc@885: box [string "sqlite3_finalize(stmt);", adamc@885: newline, adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@1266: string "uw_error(ctx, FATAL, \"Error getting row:
", adamc@885: string q, adamc@885: string "\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (sqlite3_column_count(stmt) != 1) {", adamc@885: newline, adamc@885: box [string "sqlite3_finalize(stmt);", adamc@885: newline, adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@1266: string "uw_error(ctx, FATAL, \"Bad column count:
", adamc@885: string q, adamc@885: string "\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (sqlite3_column_int(stmt, 0) != 1) {", adamc@885: newline, adamc@885: box [string "sqlite3_finalize(stmt);", adamc@885: newline, adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@885: string "uw_error(ctx, FATAL, \"Table '", adamc@885: string s, adamc@885: string "' does not exist.\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: string "sqlite3_finalize(stmt);", adamc@885: newline] adamc@885: end adamc@885: adamc@885: fun init {dbstring, prepared = ss, tables, views, sequences} = adamc@885: let adamc@885: val db = ref dbstring adamc@885: in adamc@885: app (fn s => adamc@885: case String.fields (fn ch => ch = #"=") s of adamc@885: [name, value] => adamc@885: (case name of adamc@885: "dbname" => db := value adamc@885: | _ => ()) adamc@885: | _ => ()) (String.tokens Char.isSpace dbstring); adamc@885: adamc@885: box [string "typedef struct {", adamc@885: newline, adamc@885: box [string "sqlite3 *conn;", adamc@885: newline, adamc@885: p_list_sepi (box []) adamc@885: (fn i => fn _ => adamc@885: box [string "sqlite3_stmt *p", adamc@885: string (Int.toString i), adamc@885: string ";", adamc@885: newline]) adamc@885: ss], adamc@885: string "} uw_conn;", adamc@885: newline, adamc@885: newline, adamc@885: adamc@1094: string "static void uw_client_init(void) {", adamc@885: newline, adamc@885: box [string "uw_sqlfmtInt = \"%lld%n\";", adamc@885: newline, adam@1920: string "uw_sqlfmtFloat = \"%.16g%n\";", adamc@885: newline, adamc@885: string "uw_Estrings = 0;", adamc@885: newline, adam@1834: string "uw_sql_type_annotations = 0;", adam@1834: newline, adamc@885: string "uw_sqlsuffixString = \"\";", adamc@885: newline, adamc@1011: string "uw_sqlsuffixChar = \"\";", adamc@1011: newline, adamc@885: string "uw_sqlsuffixBlob = \"\";", adamc@885: newline, adamc@885: string "uw_sqlfmtUint4 = \"%u%n\";", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: if #persistent (currentProtocol ()) then adamc@885: box [string "static void uw_db_validate(uw_context ctx) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: string "sqlite3_stmt *stmt;", adamc@885: newline, adamc@885: string "int res;", adamc@885: newline, adamc@885: newline, adamc@885: p_list_sep newline (checkRel ("table", true)) tables, adamc@885: p_list_sep newline (fn name => checkRel ("table", true) adamc@885: (name, [("id", Settings.Client)])) sequences, adamc@885: p_list_sep newline (checkRel ("view", false)) views, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "static void uw_db_prepare(uw_context ctx) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: newline, adamc@885: adam@2103: p_list_sepi newline (fn i => fn (s, _) => adamc@885: let adamc@885: fun uhoh this s args = adamc@885: box [p_list_sepi (box []) adamc@885: (fn j => fn () => adamc@885: box [string adamc@885: "sqlite3_finalize(conn->p", adamc@885: string (Int.toString j), adamc@885: string ");", adamc@885: newline]) adamc@885: (List.tabulate (i, fn _ => ())), adamc@885: box (if this then adamc@885: [string adamc@885: "sqlite3_finalize(conn->p", adamc@885: string (Int.toString i), adamc@885: string ");", adamc@885: newline] adamc@885: else adamc@885: []), adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@885: string "uw_error(ctx, FATAL, \"", adamc@885: string s, adamc@885: string "\"", adamc@885: p_list_sep (box []) (fn s => box [string ", ", adamc@885: string s]) args, adamc@885: string ");", adamc@885: newline] adamc@885: in adamc@885: box [string "if (sqlite3_prepare_v2(conn->conn, \"", adam@1656: string (Prim.toCString s), adamc@885: string "\", -1, &conn->p", adamc@885: string (Int.toString i), adamc@885: string ", NULL) != SQLITE_OK) {", adamc@885: newline, adamc@886: box [string "char msg[1024];", adamc@886: newline, adamc@886: string "strncpy(msg, sqlite3_errmsg(conn->conn), 1024);", adamc@886: newline, adamc@886: string "msg[1023] = 0;", adamc@886: newline, adamc@886: uhoh false ("Error preparing statement: " adam@1656: ^ Prim.toCString s ^ "
%s") ["msg"]], adamc@885: string "}", adamc@885: newline] adamc@885: end) adamc@885: ss, adamc@885: adamc@885: string "}"] adamc@885: else adamc@885: box [string "static void uw_db_prepare(uw_context ctx) { }", adamc@885: newline, adamc@885: string "static void uw_db_validate(uw_context ctx) { }"], adamc@885: newline, adamc@885: newline, adam@1682: adamc@1094: string "static void uw_db_init(uw_context ctx) {", adamc@885: newline, adamc@885: string "sqlite3 *sqlite;", adamc@885: newline, adamc@1115: string "sqlite3_stmt *stmt;", adamc@1115: newline, adamc@885: string "uw_conn *conn;", adamc@885: newline, adamc@885: newline, adamc@885: string "if (sqlite3_open(\"", adamc@885: string (!db), adamc@885: string "\", &sqlite) != SQLITE_OK) uw_error(ctx, FATAL, ", adamc@885: string "\"Can't open SQLite database.\");", adamc@885: newline, adamc@885: newline, adamc@1115: string "if (uw_database_max < SIZE_MAX) {", adamc@1115: newline, adamc@1115: box [string "char buf[100];", adamc@1115: newline, adamc@1115: newline, adamc@1115: adamc@1115: string "sprintf(buf, \"PRAGMA max_page_count = %llu\", (unsigned long long)(uw_database_max / 1024));", adamc@1115: newline, adamc@1115: newline, adamc@1115: adamc@1115: string "if (sqlite3_prepare_v2(sqlite, buf, -1, &stmt, NULL) != SQLITE_OK) {", adamc@1115: newline, adamc@1115: box [string "sqlite3_close(sqlite);", adamc@1115: newline, adamc@1115: string "uw_error(ctx, FATAL, \"Can't prepare max_page_count query for SQLite database\");", adamc@1115: newline], adamc@1115: string "}", adamc@1115: newline, adamc@1115: newline, adamc@1115: adamc@1115: string "if (sqlite3_step(stmt) != SQLITE_ROW) {", adamc@1115: newline, adamc@1115: box [string "sqlite3_finalize(stmt);", adamc@1115: newline, adamc@1115: string "sqlite3_close(sqlite);", adamc@1115: newline, adamc@1115: string "uw_error(ctx, FATAL, \"Can't set max_page_count parameter for SQLite database\");", adamc@1115: newline], adamc@1115: string "}", adamc@1115: newline, adamc@1115: newline, adamc@1115: adamc@1115: string "sqlite3_finalize(stmt);", adamc@1115: newline], adamc@1115: string "}", adamc@1115: newline, adamc@1115: newline, adam@1682: adamc@885: string "conn = calloc(1, sizeof(uw_conn));", adamc@885: newline, adamc@885: string "conn->conn = sqlite;", adamc@885: newline, adamc@885: string "uw_set_db(ctx, conn);", adamc@885: newline, adamc@885: string "uw_db_validate(ctx);", adamc@885: newline, adamc@885: string "uw_db_prepare(ctx);", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@1094: string "static void uw_db_close(uw_context ctx) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: p_list_sepi (box []) adamc@885: (fn i => fn _ => adamc@885: box [string "if (conn->p", adamc@885: string (Int.toString i), adamc@885: string ") sqlite3_finalize(conn->p", adamc@885: string (Int.toString i), adamc@885: string ");", adamc@885: newline]) adamc@885: ss, adamc@885: string "sqlite3_close(conn->conn);", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adam@1936: string "static int uw_db_begin(uw_context ctx, int could_write) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: newline, adamc@885: string "if (sqlite3_exec(conn->conn, \"BEGIN\", NULL, NULL, NULL) == SQLITE_OK)", adamc@885: newline, adamc@885: box [string "return 0;", adamc@885: newline], adamc@885: string "else {", adamc@885: newline, adamc@1266: box [string "fprintf(stderr, \"Begin error: %s
\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: string "return 1;", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@1094: string "static int uw_db_commit(uw_context ctx) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: string "if (sqlite3_exec(conn->conn, \"COMMIT\", NULL, NULL, NULL) == SQLITE_OK)", adamc@885: newline, adamc@885: box [string "return 0;", adamc@885: newline], adamc@885: string "else {", adamc@885: newline, adamc@1266: box [string "fprintf(stderr, \"Commit error: %s
\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: string "return 1;", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@1094: string "static int uw_db_rollback(uw_context ctx) {", adamc@885: newline, adamc@885: string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: string "if (sqlite3_exec(conn->conn, \"ROLLBACK\", NULL, NULL, NULL) == SQLITE_OK)", adamc@885: newline, adamc@885: box [string "return 0;", adamc@885: newline], adamc@885: string "else {", adamc@885: newline, adamc@1266: box [string "fprintf(stderr, \"Rollback error: %s
\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: string "return 1;", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@885: newline] adamc@885: end adamc@885: adam@1352: val fmt = "\"%Y-%m-%d %H:%M:%S\"" adam@1352: adamc@885: fun p_getcol {loc, wontLeakStrings, col = i, typ = t} = adamc@885: let adamc@885: fun p_unsql t = adamc@885: case t of adamc@885: Int => box [string "sqlite3_column_int64(stmt, ", string (Int.toString i), string ")"] adamc@885: | Float => box [string "sqlite3_column_double(stmt, ", string (Int.toString i), string ")"] adamc@885: | String => adamc@885: if wontLeakStrings then adamc@1014: box [string "(uw_Basis_string)sqlite3_column_text(stmt, ", string (Int.toString i), string ")"] adamc@885: else adamc@1014: box [string "uw_strdup(ctx, (uw_Basis_string)sqlite3_column_text(stmt, ", string (Int.toString i), string "))"] adamc@1014: | Char => box [string "sqlite3_column_text(stmt, ", string (Int.toString i), string ")[0]"] adamc@885: | Bool => box [string "(uw_Basis_bool)sqlite3_column_int(stmt, ", string (Int.toString i), string ")"] adam@1352: | Time => box [string "uw_Basis_stringToTimef_error(ctx, ", adam@1352: string fmt, adam@1352: string ", (uw_Basis_string)sqlite3_column_text(stmt, ", adam@1352: string (Int.toString i), adam@1352: string "))"] adamc@885: | Blob => box [string "({", adamc@885: newline, adamc@890: string "char *data = (char *)sqlite3_column_blob(stmt, ", adamc@885: string (Int.toString i), adamc@885: string ");", adamc@885: newline, adamc@890: string "int len = sqlite3_column_bytes(stmt, ", adamc@885: string (Int.toString i), adamc@890: string ");", adamc@890: newline, adamc@890: string "uw_Basis_blob b = {len, uw_memdup(ctx, data, len)};", adamc@885: newline, adamc@885: string "b;", adamc@885: newline, adamc@885: string "})"] adamc@886: | Channel => box [string "({", adamc@886: newline, adamc@886: string "sqlite3_int64 n = sqlite3_column_int64(stmt, ", adamc@886: string (Int.toString i), adamc@886: string ");", adamc@886: newline, adamc@886: string "uw_Basis_channel ch = {n >> 32, n & 0xFFFFFFFF};", adamc@886: newline, adamc@886: string "ch;", adamc@886: newline, adamc@886: string "})"] adamc@885: | Client => box [string "sqlite3_column_int(stmt, ", string (Int.toString i), string ")"] adamc@885: adamc@885: | Nullable _ => raise Fail "Postgres: Recursive Nullable" adamc@885: adamc@885: fun getter t = adamc@885: case t of adamc@885: Nullable t => adamc@885: box [string "(sqlite3_column_type(stmt, ", adamc@885: string (Int.toString i), adamc@885: string ") == SQLITE_NULL ? NULL : ", adamc@885: case t of adamc@885: String => getter t adamc@885: | _ => box [string "({", adamc@885: newline, adamc@885: string (p_sql_ctype t), adamc@885: space, adamc@885: string "*tmp = uw_malloc(ctx, sizeof(", adamc@885: string (p_sql_ctype t), adamc@885: string "));", adamc@885: newline, adamc@885: string "*tmp = ", adamc@885: getter t, adamc@885: string ";", adamc@885: newline, adamc@885: string "tmp;", adamc@885: newline, adamc@885: string "})"], adamc@885: string ")"] adamc@885: | _ => adamc@885: box [string "(sqlite3_column_type(stmt, ", adamc@885: string (Int.toString i), adamc@885: string ") == SQLITE_NULL ? ", adamc@885: box [string "({", adamc@885: string (p_sql_ctype t), adamc@885: space, adamc@885: string "tmp;", adamc@885: newline, adamc@885: string "uw_error(ctx, FATAL, \"", adamc@885: string (ErrorMsg.spanToString loc), adamc@885: string ": Unexpectedly NULL field #", adamc@885: string (Int.toString i), adamc@885: string "\");", adamc@885: newline, adamc@885: string "tmp;", adamc@885: newline, adamc@885: string "})"], adamc@885: string " : ", adamc@885: p_unsql t, adamc@885: string ")"] adamc@885: in adamc@885: getter t adamc@885: end adamc@885: adamc@885: fun queryCommon {loc, query, cols, doCols} = adamc@885: box [string "int r;", adamc@885: newline, adamc@885: adamc@885: string "sqlite3_reset(stmt);", adamc@885: newline, adamc@885: adamc@885: string "uw_end_region(ctx);", adamc@885: newline, adamc@885: string "while ((r = sqlite3_step(stmt)) == SQLITE_ROW) {", adamc@885: newline, adamc@885: doCols p_getcol, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (r == SQLITE_BUSY) {", adamc@885: box [string "sleep(1);", adamc@885: newline, adamc@885: string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (r != SQLITE_DONE) uw_error(ctx, FATAL, \"", adamc@885: string (ErrorMsg.spanToString loc), adamc@1266: string ": query step failed: %s
%s\", ", adamc@885: query, adamc@885: string ", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: newline] adamc@885: adamc@885: fun query {loc, cols, doCols} = adamc@885: box [string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@886: string "sqlite3_stmt *stmt;", adamc@885: newline, adamc@885: newline, adamc@1266: string "if (sqlite3_prepare_v2(conn->conn, query, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s
%s\", sqlite3_errmsg(conn->conn), query);", adamc@885: newline, adamc@885: newline, adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: queryCommon {loc = loc, cols = cols, doCols = doCols, query = string "query"}, adamc@885: adamc@885: string "uw_pop_cleanup(ctx);", adamc@885: newline] adamc@885: adamc@1014: val p_pre_inputs = adamc@1014: p_list_sepi (box []) adamc@1014: (fn i => fn t => adamc@1014: case t of adamc@1014: Char => box [string "char arg", adamc@1014: string (Int.toString (i + 1)), adamc@1014: string "s = {arg", adamc@1014: string (Int.toString (i + 1)), adamc@1014: string ", 0};", adamc@1014: newline] adamc@1014: | _ => box []) adamc@1014: adamc@885: fun p_inputs loc = adamc@885: p_list_sepi (box []) adamc@885: (fn i => fn t => adamc@885: let adamc@885: fun bind (t, arg) = adamc@885: case t of adamc@885: Int => box [string "sqlite3_bind_int64(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@885: string ")"] adamc@885: | Float => box [string "sqlite3_bind_double(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@1014: string ")"] adamc@885: | String => box [string "sqlite3_bind_text(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@885: string ", -1, SQLITE_TRANSIENT)"] adamc@1014: | Char => box [string "sqlite3_bind_text(stmt, ", adamc@1014: string (Int.toString (i + 1)), adamc@1014: string ", ", adamc@1014: arg, adamc@1014: string "s, -1, SQLITE_TRANSIENT)"] adamc@885: | Bool => box [string "sqlite3_bind_int(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@885: string ")"] adamc@887: | Time => box [string "sqlite3_bind_text(stmt, ", adamc@885: string (Int.toString (i + 1)), adam@1359: string ", uw_Basis_timef(ctx, ", adam@1352: string fmt, adam@1352: string ", ", adamc@885: arg, adamc@887: string "), -1, SQLITE_TRANSIENT)"] adamc@885: | Blob => box [string "sqlite3_bind_blob(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@885: string ".data, ", adamc@885: arg, adamc@890: string ".size, SQLITE_TRANSIENT)"] adamc@886: | Channel => box [string "sqlite3_bind_int64(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@886: string ", ((sqlite3_int64)", adamc@885: arg, adamc@886: string ".cli << 32) | ", adamc@886: arg, adamc@886: string ".chn)"] adamc@885: | Client => box [string "sqlite3_bind_int(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ", ", adamc@885: arg, adamc@885: string ")"] adamc@885: | Nullable t => box [string "(", adamc@885: arg, adamc@885: string " == NULL ? sqlite3_bind_null(stmt, ", adamc@885: string (Int.toString (i + 1)), adamc@885: string ") : ", adamc@885: bind (t, case t of adamc@885: String => arg adamc@885: | _ => box [string "(*", arg, string ")"]), adamc@885: string ")"] adamc@885: in adamc@885: box [string "if (", adamc@885: bind (t, box [string "arg", string (Int.toString (i + 1))]), adamc@885: string " != SQLITE_OK) uw_error(ctx, FATAL, \"", adamc@885: string (ErrorMsg.spanToString loc), adamc@885: string ": Error binding parameter #", adamc@885: string (Int.toString (i + 1)), adamc@885: string ": %s\", sqlite3_errmsg(conn->conn));", adamc@885: newline] adamc@885: end) adamc@885: adamc@885: fun queryPrepared {loc, id, query, inputs, cols, doCols, nested} = adamc@885: box [string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@1014: p_pre_inputs inputs, adamc@885: if nested then adamc@885: box [string "sqlite3_stmt *stmt;", adamc@885: newline] adamc@885: else adamc@885: box [string "sqlite3_stmt *stmt = conn->p", adamc@885: string (Int.toString id), adamc@885: string ";", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (stmt == NULL) {", adamc@885: newline], adamc@885: adamc@885: string "if (sqlite3_prepare_v2(conn->conn, \"", adam@1656: string (Prim.toCString query), adamc@885: string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ", adam@1656: string (Prim.toCString query), adamc@1266: string "
%s\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: if nested then adamc@885: box [string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", adamc@885: newline] adamc@885: else adamc@885: box [string "conn->p", adamc@885: string (Int.toString id), adamc@885: string " = stmt;", adamc@885: newline, adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);", adamc@885: newline, adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);", adamc@885: newline], adamc@885: newline, adamc@885: adamc@885: p_inputs loc inputs, adamc@885: newline, adamc@885: adamc@885: queryCommon {loc = loc, cols = cols, doCols = doCols, query = box [string "\"", adam@1656: string (Prim.toCString query), adamc@885: string "\""]}, adamc@885: adamc@885: string "uw_pop_cleanup(ctx);", adamc@885: newline, adamc@885: if nested then adamc@885: box [] adamc@885: else adamc@885: box [string "uw_pop_cleanup(ctx);", adamc@885: newline]] adamc@885: adam@1293: fun dmlCommon {loc, dml, mode} = adamc@885: box [string "int r;", adamc@885: newline, adamc@885: adamc@885: string "if ((r = sqlite3_step(stmt)) == SQLITE_BUSY) {", adamc@885: box [string "sleep(1);", adamc@885: newline, adamc@885: string "uw_error(ctx, UNLIMITED_RETRY, \"Database is busy\");", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: newline, adamc@885: adam@1293: string "if (r != SQLITE_DONE) ", adam@1293: case mode of adam@1293: Settings.Error => box [string "uw_error(ctx, FATAL, \"", adam@1293: string (ErrorMsg.spanToString loc), adam@1293: string ": DML step failed: %s
%s\", ", adam@1293: dml, adam@1293: string ", sqlite3_errmsg(conn->conn));"] adam@1295: | Settings.None => string "uw_set_error_message(ctx, sqlite3_errmsg(conn->conn));", adamc@885: newline] adamc@885: adam@1293: fun dml (loc, mode) = adamc@885: box [string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@886: string "sqlite3_stmt *stmt;", adamc@885: newline, adamc@885: newline, adamc@1266: string "if (sqlite3_prepare_v2(conn->conn, dml, -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: %s
%s\", dml, sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: newline, adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_finalize, stmt);", adamc@885: newline, adamc@885: newline, adamc@885: adam@1293: dmlCommon {loc = loc, dml = string "dml", mode = mode}, adamc@885: adamc@885: string "uw_pop_cleanup(ctx);", adamc@885: newline] adamc@885: adam@1293: fun dmlPrepared {loc, id, dml, inputs, mode = mode} = adamc@885: box [string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@1014: p_pre_inputs inputs, adamc@885: string "sqlite3_stmt *stmt = conn->p", adamc@885: string (Int.toString id), adamc@885: string ";", adamc@885: newline, adamc@885: newline, adamc@885: adamc@885: string "if (stmt == NULL) {", adamc@885: newline, adamc@885: box [string "if (sqlite3_prepare_v2(conn->conn, \"", adam@1656: string (Prim.toCString dml), adamc@885: string "\", -1, &stmt, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"Error preparing statement: ", adam@1656: string (Prim.toCString dml), adamc@1266: string "
%s\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: string "conn->p", adamc@885: string (Int.toString id), adamc@885: string " = stmt;", adamc@885: newline], adamc@885: string "}", adamc@885: newline, adamc@885: adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_clear_bindings, stmt);", adamc@885: newline, adamc@885: string "uw_push_cleanup(ctx, (void (*)(void *))sqlite3_reset, stmt);", adamc@885: newline, adamc@885: adamc@885: p_inputs loc inputs, adamc@885: newline, adamc@885: adamc@885: dmlCommon {loc = loc, dml = box [string "\"", adam@1656: string (Prim.toCString dml), adam@1293: string "\""], mode = mode}, adamc@885: adamc@885: string "uw_pop_cleanup(ctx);", adamc@885: newline, adamc@885: string "uw_pop_cleanup(ctx);", adamc@885: newline] adamc@885: adamc@885: fun nextval {loc, seqE, seqName} = adamc@885: box [string "uw_conn *conn = uw_get_db(ctx);", adamc@885: newline, adamc@885: string "char *insert = ", adamc@885: case seqName of adamc@886: SOME s => string ("\"INSERT INTO " ^ s ^ " VALUES (NULL)\"") adamc@885: | NONE => box [string "uw_Basis_strcat(ctx, \"INSERT INTO \", uw_Basis_strcat(ctx, ", adamc@885: seqE, adamc@885: string ", \" VALUES ()\"))"], adamc@885: string ";", adamc@885: newline, adamc@885: string "char *delete = ", adamc@885: case seqName of adamc@885: SOME s => string ("\"DELETE FROM " ^ s ^ "\"") adamc@885: | NONE => box [string "uw_Basis_strcat(ctx, \"DELETE FROM \", ", adamc@885: seqE, adamc@885: string ")"], adamc@885: string ";", adamc@885: newline, adamc@885: newline, adamc@885: adamc@886: string "if (sqlite3_exec(conn->conn, insert, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' INSERT failed: %s\", sqlite3_errmsg(conn->conn));", adamc@885: newline, adamc@885: string "n = sqlite3_last_insert_rowid(conn->conn);", adamc@885: newline, adamc@886: string "if (sqlite3_exec(conn->conn, delete, NULL, NULL, NULL) != SQLITE_OK) uw_error(ctx, FATAL, \"'nextval' DELETE failed: %s\", sqlite3_errmsg(conn->conn));", adamc@885: newline] adamc@885: adamc@885: fun nextvalPrepared _ = raise Fail "SQLite.nextvalPrepared called" adamc@1073: fun setval _ = raise Fail "SQLite.setval called" adamc@885: adamc@885: fun sqlifyString s = "'" ^ String.translate (fn #"'" => "''" adam@1285: | #"\000" => "" adam@1285: | ch => str ch) adam@1285: s ^ "'" adamc@885: adamc@885: fun p_cast (s, _) = s adamc@885: adamc@885: fun p_blank _ = "?" adamc@885: adamc@885: val () = addDbms {name = "sqlite", adam@1682: randomFunction = "RANDOM", adam@1464: header = Config.sqheader, adamc@885: link = "-lsqlite3", adamc@885: init = init, adamc@885: p_sql_type = p_sql_type, adamc@885: query = query, adamc@885: queryPrepared = queryPrepared, adamc@885: dml = dml, adamc@885: dmlPrepared = dmlPrepared, adamc@885: nextval = nextval, adamc@885: nextvalPrepared = nextvalPrepared, adamc@1073: setval = setval, adamc@885: sqlifyString = sqlifyString, adamc@885: p_cast = p_cast, adamc@885: p_blank = p_blank, adamc@885: supportsDeleteAs = false, adamc@886: supportsUpdateAs = false, adamc@886: createSequence = fn s => "CREATE TABLE " ^ s ^ " (id INTEGER PRIMARY KEY AUTOINCREMENT)", adamc@885: textKeysNeedLengths = false, adamc@885: supportsNextval = false, adamc@885: supportsNestedPrepared = false, adamc@890: sqlPrefix = "", adamc@1014: supportsOctetLength = false, adamc@1014: trueString = "1", adamc@1196: falseString = "0", adamc@1196: onlyUnion = false, adam@1777: nestedRelops = false, adam@1777: windowFunctions = false} adamc@885: adamc@885: end