Below is the file 'mlsqlite/sqlite3.ml' from this revision. You can also download the file.
module Weak_store = struct type 'a t = { mutable w : 'a Weak.t ; mutable free : int ; finalise : 'a -> unit } let create f = { w = Weak.create 8 ; free = 0 ; finalise = f } let register s v = Gc.finalise s.finalise v ; let len = Weak.length s.w in assert (len > 0) ; if s.free < len then begin Weak.set s.w s.free (Some v) ; s.free <- s.free + 1 end else begin let i = ref 0 in let full = ref true in while !full && !i < Weak.length s.w do full := Weak.check s.w !i ; if !full then incr i done ; if !full then begin let n_s = Weak.create (2 * len) in Weak.blit s.w 0 n_s 0 len ; s.w <- n_s ; s.free <- len + 1 ; i := len end ; Weak.set s.w !i (Some v) end let clear s = for i = 0 to Weak.length s.w - 1 do match Weak.get s.w i with | Some v -> s.finalise v | None -> () done end type db type stmt type argument type sql_type = [`INTEGER|`FLOAT|`TEXT|`BLOB|`NULL] type sql_value = [ | `INT of int | `INT64 of int64 | `FLOAT of float | `TEXT of string | `BLOB of string | `VALUE of argument | `NULL ] type error_code = | ERROR | INTERNAL | PERM | ABORT | BUSY | LOCKED | NOMEM | READONLY | INTERRUPT | IOERR | CORRUPT | NOTFOUND | FULL | CANTOPEN | PROTOCOL | EMPTY | SCHEMA | TOOBIG | CONSTRAINT | MISMATCH | MISUSE | NOLFS | AUTH | FORMAT | RANGE | NOTADB exception Error of error_code * string let init = Callback.register_exception "mlsqlite3_exn" (Error (ERROR, "")) external open_db : string -> db = "ml_sqlite3_open" external _close_db : db -> unit = "ml_sqlite3_close" external set_stmt_store : db -> stmt Weak_store.t option -> unit = "ml_sqlite3_set_stmt_store" external get_stmt_store : db -> stmt Weak_store.t = "ml_sqlite3_get_stmt_store" external finalize_stmt : stmt -> unit = "ml_sqlite3_finalize_noerr" let stmt_store db = try get_stmt_store db with Not_found -> let s = Weak_store.create finalize_stmt in set_stmt_store db (Some s) ; s let register_stmt db stmt = Weak_store.register (stmt_store db) stmt let close_db db = begin try Weak_store.clear (get_stmt_store db) with Not_found -> () end ; _close_db db external interrupt : db -> unit = "ml_sqlite3_interrupt" external is_complete : string -> bool = "ml_sqlite3_complete" external _version : unit -> string = "ml_sqlite3_version" let version = _version () external last_insert_rowid : db -> int64 = "ml_sqlite3_last_insert_rowid" external changes : db -> int = "ml_sqlite3_changes" external total_changes : db -> int = "ml_sqlite3_total_changes" external get_autocommit : db -> bool = "ml_sqlite3_get_autocommit" external sleep : int -> unit = "ml_sqlite3_sleep" external busy_set : db -> (int -> [`FAIL|`RETRY]) -> unit = "ml_sqlite3_busy_handler" external busy_unset : db -> unit = "ml_sqlite3_busy_handler_unset" external busy_timeout : db -> int -> unit = "ml_sqlite3_busy_timeout" external trace_set : db -> (string -> unit) -> unit = "ml_sqlite3_trace" external trace_unset : db -> unit = "ml_sqlite3_trace_unset" external progress_handler_set : db -> int -> (unit -> unit) -> unit = "ml_sqlite3_progress_handler" external progress_handler_unset : db -> unit = "ml_sqlite3_progress_handler_unset" (* type vm *) (* type stmt = { *) (* mutable vm : vm ; *) (* db : db ; *) (* sql : string ; *) (* sql_off : int *) (* } *) external prepare : db -> string -> int -> stmt option * int = "ml_sqlite3_prepare" external reset : stmt -> unit = "ml_sqlite3_reset" external expired : stmt -> bool = "ml_sqlite3_expired" external step : stmt -> [`DONE|`ROW] = "ml_sqlite3_step" external bind : stmt -> int -> sql_value -> unit = "ml_sqlite3_bind" external bind_parameter_count : stmt -> int = "ml_sqlite3_bind_parameter_count" external bind_parameter_index : stmt -> string -> int = "ml_sqlite3_bind_parameter_index" external bind_parameter_name : stmt -> int -> string = "ml_sqlite3_bind_parameter_name" external clear_bindings : stmt -> unit = "ml_sqlite3_clear_bindings" external transfer_bindings : stmt -> stmt -> unit = "ml_sqlite3_transfer_bindings" external column_blob : stmt -> int -> string = "ml_sqlite3_column_blob" external column_double : stmt -> int -> float = "ml_sqlite3_column_double" external column_int : stmt -> int -> int = "ml_sqlite3_column_int" external column_int64 : stmt -> int -> int64 = "ml_sqlite3_column_int64" external column_text : stmt -> int -> string = "ml_sqlite3_column_text" external column_type : stmt -> int -> sql_type = "ml_sqlite3_column_type" external data_count : stmt -> int = "ml_sqlite3_data_count" external column_count : stmt -> int = "ml_sqlite3_column_count" external column_name : stmt -> int -> string = "ml_sqlite3_column_name" external column_decltype : stmt -> int -> string = "ml_sqlite3_column_decltype" external value_blob : argument -> string = "ml_sqlite3_value_blob" external value_double : argument -> float = "ml_sqlite3_value_double" external value_int : argument -> int = "ml_sqlite3_value_int" external value_int64 : argument -> int64 = "ml_sqlite3_value_int64" external value_text : argument -> string = "ml_sqlite3_value_text" external value_type : argument -> sql_type = "ml_sqlite3_value_type" external _create_function : db -> string -> int -> (argument array -> sql_value) -> unit = "ml_sqlite3_create_function" let create_fun_N db name f = _create_function db name (-1) f let create_fun_0 db name f = _create_function db name 0 (fun _ -> f ()) let create_fun_1 db name f = _create_function db name 1 (fun args -> f args.(0)) let create_fun_2 db name f = _create_function db name 2 (fun args -> f args.(0) args.(1)) let create_fun_3 db name f = _create_function db name 3 (fun args -> f args.(0) args.(1) args.(2)) external delete_function : db -> string -> unit = "ml_sqlite3_delete_function" (* Higher-level functions manipulating statements *) type ('a, 'b) fmt = ('b, unit, string, 'a) format4 -> 'b (* Prepare only the first statement of the SQL string *) let rec _prepare_one db off sql = if off >= String.length sql then failwith "Sqlite3.prepare_one: empty statement" ; match prepare db sql off with | Some stmt, _ -> register_stmt db stmt ; stmt | None, nxt -> _prepare_one db nxt sql let prepare_one db sql = _prepare_one db 0 (String.copy sql) let prepare_one_f db fmt = Printf.kprintf (_prepare_one db 0) fmt (* Loop over all the statements in a SQL string *) let _fold_prepare ?(final=false) db sql f init = let rec loop acc off = if off >= String.length sql then acc else match prepare db sql off with | Some stmt, nxt -> register_stmt db stmt ; let acc = try f acc stmt with exn when final -> finalize_stmt stmt ; raise exn in if final then finalize_stmt stmt ; loop acc nxt | None, nxt -> loop acc nxt in loop init 0 let fold_prepare db sql = _fold_prepare db (String.copy sql) let fold_prepare_f db fmt = Printf.kprintf (_fold_prepare db) fmt (* Bind SQL values to statements *) let do_bind stmt = function | [] -> [] | l -> let n = bind_parameter_count stmt in let rec proc i = function | v :: tl when i <= n -> bind stmt i v ; proc (i+1) tl | l -> l in proc 1 l let _fold_prepare_bind ?final db sql bindings f init = let bindings = ref bindings in _fold_prepare ?final db sql (fun acc stmt -> bindings := do_bind stmt !bindings ; f acc stmt) init let fold_prepare_bind db sql = _fold_prepare_bind db (String.copy sql) let fold_prepare_bind_f db fmt = Printf.kprintf (_fold_prepare_bind db) fmt (* Execute statements *) let rec do_step stmt = match step stmt with | `DONE -> () | `ROW -> do_step stmt let _exec db sql = _fold_prepare ~final:true db sql (fun () stmt -> do_step stmt) () let exec db sql = _exec db (String.copy sql) let exec_f db fmt = Printf.kprintf (_exec db) fmt let _exec_v db sql data = _fold_prepare_bind ~final:true db sql data (fun () stmt -> do_step stmt) () let exec_v db sql = _exec_v db (String.copy sql) let exec_fv db fmt = Printf.kprintf (_exec_v db) fmt (* Execute statements and get some results back *) let rec fold_step f acc stmt = match step stmt with | `DONE -> acc | `ROW -> let acc = try f acc stmt with exn -> reset stmt ; raise exn in fold_step f acc stmt let _fetch db sql f init = _fold_prepare db sql (fold_step f) init let fetch db sql = _fetch db (String.copy sql) let fetch_f db fmt = Printf.kprintf (_fetch db) fmt let _fetch_v db sql data f init = _fold_prepare_bind db sql data (fold_step f) init let fetch_v db sql = _fetch_v db (String.copy sql) let fetch_fv db fmt = Printf.kprintf (_fetch_v db) fmt (* Reset-Bind-Step *) let bind_and_exec stmt bindings = reset stmt ; ignore (do_bind stmt bindings) ; do_step stmt let bind_fetch stmt bindings f init = reset stmt ; ignore (do_bind stmt bindings) ; fold_step f init stmt let sql_escape s = let n = ref 0 in let len = String.length s in for i = 0 to len - 1 do let c = String.unsafe_get s i in if c = '\'' then incr n done ; if !n = 0 then s else begin let n_len = len + !n in let o = String.create n_len in let j = ref 0 in for i = 0 to len - 1 do let c = String.unsafe_get s i in if c = '\'' then begin String.unsafe_set o !j '\'' ; incr j end ; String.unsafe_set o !j c ; incr j done ; assert (!j = n_len) ; o end let char_of_hex v = if v < 0xa then Char.chr (v + Char.code '0') else Char.chr (v - 0xa + Char.code 'a') let hex_enc s = let len = String.length s in let o = String.create (2 * len) in for i = 0 to len - 1 do let c = int_of_char s.[i] in let hi = c lsr 4 in o.[2*i] <- char_of_hex hi ; let lo = c land 0xf in o.[2*i + 1] <- char_of_hex lo done ; o let blob_escape = hex_enc let string_of_transaction = function | `DEFERRED -> "DEFERRED" | `IMMEDIATE -> "IMMEDIATE" | `EXCLUSIVE -> "EXCLUSIVE" let transaction ?(kind=`DEFERRED) db f = exec db ("BEGIN " ^ string_of_transaction kind) ; try let r = f db in exec db "COMMIT" ; r with exn -> exec db "ROLLBACK" ; raise exn