# # # add_file "mlsqlite/config.h" # content [5c92e8154522056c910c08d08d1f853d28c4d47c] # # add_file "mlsqlite/ocaml-sqlite3.h" # content [eddda40cfa9cc8d5bcc12793587a01bdf71a3387] # # patch "Makefile" # from [a08a3b88c66ad02fa99b951e989780617ce2a4da] # to [87427535ef0907137382b73f16abdb166ef65cc7] # # patch "database.ml" # from [2575ef442d509845d4de8bd77fd4dac357edf3fd] # to [6675f62b995a0380b329bdd5c9134023df4416ec] # # patch "mlsqlite/ocaml-sqlite3.c" # from [7c98e2fdeb9663865485a3f21374dae11f74f46f] # to [aa91300e6fe9da56753089dc45ebb061c5a9f05f] # # patch "mlsqlite/sqlite3.ml" # from [53295e5f3023a4343a9708c102aa6622e12633fb] # to [fecc3f3795b4baeb75e1816275cfcd7c96c4b8c6] # # patch "mlsqlite/sqlite3.mli" # from [7675ce305667d22ed95e6040e58b94769a4b66d5] # to [61a3daf169c5cfa3c693dcce707a3489b69c420b] # ============================================================ --- mlsqlite/config.h 5c92e8154522056c910c08d08d1f853d28c4d47c +++ mlsqlite/config.h 5c92e8154522056c910c08d08d1f853d28c4d47c @@ -0,0 +1,17 @@ +/* Define to 1 if you have the `sqlite3_bind_value' function. */ +/* #undef HAVE_SQLITE3_BIND_VALUE */ + +/* Define to 1 if you have the `sqlite3_clear_bindings' function. */ +/* #undef HAVE_SQLITE3_CLEAR_BINDINGS */ + +/* Define to 1 if you have the `sqlite3_complete' function. */ +#define HAVE_SQLITE3_COMPLETE 1 + +/* Define to 1 if you have the `sqlite3_get_autocommit' function. */ +#define HAVE_SQLITE3_GET_AUTOCOMMIT 1 + +/* Define to 1 if you have the `sqlite3_progress_handler' function. */ +#define HAVE_SQLITE3_PROGRESS_HANDLER 1 + +/* Define to 1 if you have the `sqlite3_sleep' function. */ +/* #undef HAVE_SQLITE3_SLEEP */ ============================================================ --- mlsqlite/ocaml-sqlite3.h eddda40cfa9cc8d5bcc12793587a01bdf71a3387 +++ mlsqlite/ocaml-sqlite3.h eddda40cfa9cc8d5bcc12793587a01bdf71a3387 @@ -0,0 +1,60 @@ + +#include "config.h" + +#define TRUE 1 +#define FALSE 0 + +void ml_sqlite3_raise_exn (int, const char *, int) Noreturn; +#define raise_sqlite3_exn(db) ml_sqlite3_raise_exn (sqlite3_errcode (Sqlite3_val(db)), sqlite3_errmsg (Sqlite3_val(db)), TRUE) + + +#if defined(__GNUC__) && (__GNUC__ >= 3) +# define Pure __attribute__ ((pure)) +#else +# define Pure +#endif + +struct user_function { + value fun; + struct user_function *next; +}; + +struct ml_sqlite3_data { + sqlite3 *db; + value callbacks; + value stmt_store; + struct user_function *user_functions; +}; + +#define Sqlite3_data_val(v) (* ((struct ml_sqlite3_data **) Data_custom_val(v))) + +static sqlite3 * Sqlite3_val (value) Pure; +static sqlite3_stmt * Sqlite3_stmt_val (value) Pure; +static sqlite3_value * Sqlite3_value_val (value) Pure; + +static inline sqlite3 * +Sqlite3_val (value v) +{ + struct ml_sqlite3_data *data = Sqlite3_data_val (v); + if (data->db == NULL) + ml_sqlite3_raise_exn (SQLITE_MISUSE, "closed db", TRUE); + return data->db; +} + +static inline sqlite3_stmt * +Sqlite3_stmt_val (value v) +{ + sqlite3_stmt *stmt = * ((sqlite3_stmt **) Field (v, 0)); + if (stmt == NULL) + ml_sqlite3_raise_exn (SQLITE_MISUSE, "invalid statement", TRUE); + return stmt; +} + +static inline sqlite3_value * +Sqlite3_value_val (value v) +{ + sqlite3_value *val = * ((sqlite3_value **) v); + if (val == NULL) + ml_sqlite3_raise_exn (SQLITE_MISUSE, "invalid value", TRUE); + return val; +} ============================================================ --- Makefile a08a3b88c66ad02fa99b951e989780617ce2a4da +++ Makefile 87427535ef0907137382b73f16abdb166ef65cc7 @@ -46,7 +46,7 @@ DISTSRC = Makefile configure.ac config.m components.ml schema_lexer.mll database.ml database.mli agraph.ml agraph.mli \ unidiff.ml unidiff.mli gnomecanvas_hack.c view.ml view.mli \ query.ml query.mli app.mli app.ml main.ml \ - mlsqlite/sqlite3.ml mlsqlite/sqlite3.mli mlsqlite/ocaml-sqlite3.c \ + mlsqlite/sqlite3.ml mlsqlite/sqlite3.mli mlsqlite/ocaml-sqlite3.h mlsqlite/ocaml-sqlite3.c \ ocamlnet-0.97.1/base64.ml ocamlnet-0.97.1/base64.mli ocamlnet-0.97.1/LICENSE \ extlib-1.3/IO.ml extlib-1.3/IO.mli extlib-1.3/unzip.ml extlib-1.3/unzip.mli \ glib/gspawn.ml glib/gspawn.mli glib/giochannel.ml glib/giochannel.mli \ ============================================================ --- database.ml 2575ef442d509845d4de8bd77fd4dac357edf3fd +++ database.ml 6675f62b995a0380b329bdd5c9134023df4416ec @@ -9,8 +9,10 @@ let may_decode base64 v = let may_decode base64 v = if base64 then monot_decode v else v -let acc_one_col base64 acc row = - may_decode base64 row.(0) :: acc +let blob_col base64 stmt n = + may_decode base64 (Sqlite3.column_blob stmt n) +let acc_one_col base64 acc stmt = + blob_col base64 stmt 0 :: acc @@ -19,7 +21,7 @@ let setup_sqlite ?busy_handler db = then Sqlite3.trace_set db (fun s -> prerr_string "### sql: " ; prerr_endline s) ; - Sqlite3.exec db "PRAGMA temp_store = MEMORY" ; + Sqlite3.exec db "PRAGMA temp_store = MEMORY" [] ; may (Sqlite3.busy_set db) busy_handler @@ -32,7 +34,7 @@ let schema_id db = WHERE (type = 'table' OR type = 'index') \ AND sql IS NOT NULL \ AND name NOT LIKE 'sqlite_stat%' \ - ORDER BY name" + ORDER BY name" [] (acc_one_col false) [] in let schema_data = String.concat "\n" (List.rev lines) in let schema = Schema_lexer.massage_sql_tokens schema_data in @@ -40,7 +42,7 @@ let has_rosters db = let has_rosters db = Sqlite3.fetch db - "SELECT name FROM sqlite_master WHERE name = 'rosters'" + "SELECT name FROM sqlite_master WHERE name = 'rosters'" [] (fun _ _ -> true) false @@ -49,23 +51,21 @@ let fetch_pubkeys db base64 tbl = let fetch_pubkeys db base64 tbl = Sqlite3.fetch db - "SELECT id, keydata, ROWID FROM public_keys" - (fun () -> function - | [| id; data; rowid |] -> - begin - try - let data = may_decode base64 data in - let key = Crypto.decode_rsa_pubkey data in - Hashtbl.add tbl id (key, int_of_string rowid - 1) - with Failure _ -> () - end - | _ -> ()) + "SELECT id, keydata, ROWID FROM public_keys" [] + (fun () stmt -> + let id = Sqlite3.column_text stmt 0 in + let data = blob_col base64 stmt 1 in + let rowid = Sqlite3.column_int stmt 2 in + try + let key = Crypto.decode_rsa_pubkey data in + Hashtbl.add tbl id (key, rowid - 1) + with Failure _ -> ()) () let fetch_branches base64 db = List.sort compare (Sqlite3.fetch db - "SELECT DISTINCT value FROM revision_certs WHERE name = 'branch'" + "SELECT DISTINCT value FROM revision_certs WHERE name = 'branch'" [] (acc_one_col base64) []) @@ -94,16 +94,18 @@ let find_merge_nodes g = then node.kind <- MERGE) g.nodes +let grab_one_int stmt id = + Sqlite3.bind_fetch stmt + [ `TEXT id ] + (fun _ stmt -> Sqlite3.column_int stmt 0) + 0 + let count_all_parents db = let stmt = Sqlite3.prepare_one db "SELECT COUNT(parent) FROM revision_ancestry WHERE parent != '' AND child = ?" in fun id -> - Sqlite3.reset stmt ; - Sqlite3.bind stmt 1 (`TEXT id) ; - Sqlite3.fold_rows - (fun _ stmt -> Sqlite3.column_int stmt 0) - 0 stmt + grab_one_int stmt id let count_regular_children db = let stmt = @@ -112,11 +114,7 @@ let count_regular_children db = "SELECT COUNT(child) FROM revision_ancestry, %s WHERE parent = ? AND child = id" view_name_domain) in fun id -> - Sqlite3.reset stmt ; - Sqlite3.bind stmt 1 (`TEXT id) ; - Sqlite3.fold_rows - (fun _ stmt -> Sqlite3.column_int stmt 0) - 0 stmt + grab_one_int stmt id let is_interesting_neighbour_out db = let count_p = count_all_parents db in @@ -129,28 +127,25 @@ let fetch_children db = start_of_branch id || end_of_branch id_parent let fetch_children db = - let stmt = lazy - (Sqlite3.prepare_one - db "SELECT child FROM revision_ancestry WHERE parent = ?") in + let stmt = + Sqlite3.prepare_one db + "SELECT child FROM revision_ancestry WHERE parent = ?" in fun id f init -> - let stmt = Lazy.force stmt in - Sqlite3.reset stmt ; - Sqlite3.bind stmt 1 (`TEXT id) ; - Sqlite3.fold_rows + Sqlite3.bind_fetch + stmt [ `TEXT id] (fun acc stmt -> f acc (Sqlite3.column_text stmt 0)) init - stmt let collect_tags db base64 view g = Sqlite3.fetch_f db - (fun () row -> - let id = row.(0) in + "SELECT C.id, C.value FROM revision_certs AS C, %s AS D WHERE name = 'tag' AND C.id = D.id" + view [] + (fun () stmt -> + let id = Sqlite3.column_text stmt 0 in let n = NodeMap.find id g.nodes in - let tag = may_decode base64 row.(1) in + let tag = blob_col base64 stmt 1 in n.kind <- TAGGED tag) () - "SELECT C.id, C.value FROM revision_certs AS C, %s AS D WHERE name = 'tag' AND C.id = D.id" - view let ensure_node g id k = @@ -159,11 +154,10 @@ let ensure_node g id k = let n = { id = id ; kind = k ; family = [] } in n, { g with nodes = NodeMap.add id n g.nodes } -let process_regular_node g = function - | [| id |] -> - let _, g = ensure_node g id REGULAR in - g - | _ -> g +let process_regular_node g s = + let id = Sqlite3.column_text s 0 in + let _, g = ensure_node g id REGULAR in + g let add_edge g id1 k1 id2 k2 ek = let n1, g = ensure_node g id1 k1 in @@ -173,27 +167,29 @@ let add_edge g id1 k1 id2 k2 ek = { g with ancestry = EdgeMap.add (id1, id2) ek g.ancestry } -let process_neighb_in g = function - | [| id ; child |] -> - add_edge g id NEIGHBOUR_IN child REGULAR BRANCHING_NEIGH - | _ -> g +let process_neighb_in g s = + let id = Sqlite3.column_text s 0 in + let child = Sqlite3.column_text s 1 in + add_edge g id NEIGHBOUR_IN child REGULAR BRANCHING_NEIGH let process_neighb_out db = let is_interesting = is_interesting_neighbour_out db in - fun g -> function - | [| parent ; id |] when is_interesting parent id -> - add_edge g parent REGULAR id NEIGHBOUR_OUT BRANCHING_NEIGH - | _ -> g + fun g s -> + let parent = Sqlite3.column_text s 0 in + let id = Sqlite3.column_text s 1 in + if is_interesting parent id + then add_edge g parent REGULAR id NEIGHBOUR_OUT BRANCHING_NEIGH + else g -let process_ancestry g = function - | [| parent ; child |] -> - add_edge g parent REGULAR child REGULAR SAME_BRANCH - | _ -> g +let process_ancestry g s = + let parent = Sqlite3.column_text s 0 in + let child = Sqlite3.column_text s 1 in + add_edge g parent REGULAR child REGULAR SAME_BRANCH -let process_branching_edge g = function - | [| parent ; child |] -> - add_edge g parent REGULAR child REGULAR BRANCHING - | _ -> g +let process_branching_edge g s = + let parent = Sqlite3.column_text s 0 in + let child = Sqlite3.column_text s 1 in + add_edge g parent REGULAR child REGULAR BRANCHING let fetch_agraph_with_view db base64 (query, query_limit) = @@ -206,32 +202,36 @@ let fetch_agraph_with_view db base64 (qu (* grab all our main nodes *) let agraph = - Sqlite3.fetch_f db process_regular_node agraph - "SELECT id FROM %s" view_name_limit in + Sqlite3.fetch_f db + "SELECT id FROM %s" view_name_limit [] + process_regular_node agraph in (* neighbor IN *) let agraph = - Sqlite3.fetch_f db process_neighb_in agraph + Sqlite3.fetch_f db "SELECT parent, child \ FROM %s AS D, revision_ancestry AS A \ WHERE D.id = A.child AND A.parent != '' AND A.parent NOT IN %s" - view_name_limit view_name_domain in + view_name_limit view_name_domain [] + process_neighb_in agraph in (* neighbor OUT *) let agraph = - Sqlite3.fetch_f db (process_neighb_out db) agraph + Sqlite3.fetch_f db "SELECT parent, child \ FROM %s AS D, revision_ancestry AS A \ WHERE D.id = A.parent AND A.child NOT IN %s" - view_name_limit view_name_domain in + view_name_limit view_name_domain [] + (process_neighb_out db) agraph in (* ancestry *) let agraph = - Sqlite3.fetch_f db process_ancestry agraph + Sqlite3.fetch_f db "SELECT parent, child \ FROM %s AS D1, revision_ancestry AS A, %s AS D2 \ WHERE D1.id = A.parent AND A.child = D2.id" - view_name_limit view_name_limit in + view_name_limit view_name_limit [] + process_ancestry agraph in (* find merge/propagate nodes (they have more than one parent) *) find_merge_nodes agraph ; @@ -249,7 +249,6 @@ let fetch_agraph_with_view db base64 (qu | _ -> (* we need another database query *) Sqlite3.fetch_f db - process_branching_edge agraph "SELECT parent, child \ FROM %s, revision_ancestry \ WHERE id = child \ @@ -259,7 +258,8 @@ let fetch_agraph_with_view db base64 (qu WHERE C.id = A.child AND P.id = A.parent \ AND C.name = 'branch' AND P.name = 'branch' \ AND C.value = P.value)" - view_name_limit + view_name_limit [] + process_branching_edge agraph end in (* reconnect disconnected components *) @@ -315,24 +315,24 @@ let fetch_with_view query base64 db f = ~before:(fun () -> (* We fetch the ids matching the query (ie those on certain branches) *) (* and store them in a view. *) - Sqlite3.exec db view_query_domain ; + Sqlite3.exec db view_query_domain [] ; Sqlite3.exec_f db - "CREATE INDEX %s__id ON %s (id)" view_name_domain view_name_domain ; + "CREATE INDEX %s__id ON %s (id)" view_name_domain view_name_domain [] ; if query_limit <> QUERY_NO_LIMIT then begin register_date_p () ; - Sqlite3.exec db (view_query_date_limit ()) ; + Sqlite3.exec db (view_query_date_limit ()) [] ; Sqlite3.exec_f db - "CREATE INDEX %s__id ON %s (id)" view_name_limit view_name_limit + "CREATE INDEX %s__id ON %s (id)" view_name_limit view_name_limit [] end) ~action:(fun () -> f db base64 query) ~after:(fun () -> if query_limit <> QUERY_NO_LIMIT then begin Sqlite3.delete_function db "date_p" ; - Sqlite3.exec_f db "DROP TABLE %s" view_name_limit + Sqlite3.exec_f db "DROP TABLE %s" view_name_limit [] end ; - Sqlite3.exec_f db "DROP TABLE %s" view_name_domain) + Sqlite3.exec_f db "DROP TABLE %s" view_name_domain []) () let fetch_agraph query base64 db = @@ -356,8 +356,9 @@ let fetch_revision_set rostered b64 db i decode_and_parse_revision rostered (List.hd - (Sqlite3.fetch_f db (acc_one_col b64) [] - "SELECT data FROM revisions WHERE id = '%s'" id)) + (Sqlite3.fetch db + "SELECT data FROM revisions WHERE id = ?" [`TEXT id] + (acc_one_col b64) [])) let verify_cert_sig pubkeys keypair name id v signature = try @@ -369,22 +370,23 @@ let verify_cert_sig pubkeys keypair name else SIG_BAD with Not_found -> SIG_UNKNOWN -let process_certs pubkeys b64 acc = function - | [| id; name; v; keypair; signature |] -> - let dec_v = may_decode b64 v in - let dec_sig = may_decode b64 signature in - { c_id = id ; - c_name = name ; - c_value = dec_v ; - c_signer_id = keypair ; - c_signature = verify_cert_sig pubkeys keypair name id dec_v dec_sig } :: acc - | _ -> acc +let process_certs pubkeys b64 acc s = + let id = Sqlite3.column_text s 0 in + let name = Sqlite3.column_text s 1 in + let dec_v = blob_col b64 s 2 in + let keypair = Sqlite3.column_text s 3 in + let dec_sig = blob_col b64 s 4 in + { c_id = id ; + c_name = name ; + c_value = dec_v ; + c_signer_id = keypair ; + c_signature = verify_cert_sig pubkeys keypair name id dec_v dec_sig } :: acc let fetch_certs db pubkeys b64 id = - Sqlite3.fetch_f db (process_certs pubkeys b64) [] + Sqlite3.fetch db "SELECT id, name, value, keypair, signature \ - FROM revision_certs WHERE id = '%s'" id - + FROM revision_certs WHERE id = ?" [`TEXT id] + (process_certs pubkeys b64) [] let prepare_fetch_one_cert_signer db = Sqlite3.prepare_one db "SELECT keypair FROM revision_certs WHERE id = ? AND name = ?" @@ -393,10 +395,9 @@ let fetch_one_cert_field stmt id name ki "SELECT value FROM revision_certs WHERE id = ? AND name = ?" let fetch_one_cert_field stmt id name kind = - Sqlite3.reset stmt ; - Sqlite3.bind stmt 1 (`TEXT id) ; - Sqlite3.bind stmt 2 (`TEXT name) ; - Sqlite3.fold_rows + Sqlite3.bind_fetch + stmt + [ `TEXT id ; `TEXT name ] (fun acc stmt -> let v = Sqlite3.column_text stmt 0 in match kind with @@ -404,20 +405,21 @@ let fetch_one_cert_field stmt id name ki | `VALUE -> v :: acc | `VALUE_B64 -> monot_decode v :: acc) [] - stmt let get_matching_cert db b64 name p = List.rev - (Sqlite3.fetch_f db - (fun acc -> function - | [| id; v |] -> - let dv = may_decode b64 v in - if p dv - then (id, dv) :: acc - else acc - | _ -> acc) - [] - "SELECT id, value FROM revision_certs WHERE name = '%s'" name) + (Sqlite3.fetch db + "SELECT id, value FROM revision_certs WHERE name = ?" + [`TEXT name] + (fun acc s -> + let v = blob_col b64 s 1 in + if p v + then begin + let id = Sqlite3.column_text s 0 in + (id, v) :: acc + end + else acc) + []) ============================================================ --- mlsqlite/ocaml-sqlite3.c 7c98e2fdeb9663865485a3f21374dae11f74f46f +++ mlsqlite/ocaml-sqlite3.c aa91300e6fe9da56753089dc45ebb061c5a9f05f @@ -11,8 +11,7 @@ #include -#define TRUE 1 -#define FALSE 0 +#include "ocaml-sqlite3.h" /* Not wrapped : - user-defined aggregate functions @@ -27,10 +26,7 @@ /* Error handling */ -static void ml_sqlite3_raise_exn (int, const char *, int) Noreturn; -#define raise_sqlite3_exn(db) ml_sqlite3_raise_exn (sqlite3_errcode (Sqlite3_val(db)), sqlite3_errmsg (Sqlite3_val(db)), TRUE) - -static void ml_sqlite3_raise_exn (int status, const char *errmsg, int static_errmsg) +void ml_sqlite3_raise_exn (int status, const char *errmsg, int static_errmsg) { static value *sqlite3_exn; @@ -59,60 +55,6 @@ static void ml_sqlite3_raise_exn (int st -/* Memory management of sqlite3* values */ -struct user_function { - value fun; - struct user_function *next; -}; - -struct ml_sqlite3_data { - sqlite3 *db; - value callbacks; - value stmt_store; - struct user_function *user_functions; -}; - -#define Sqlite3_data_val(v) (* ((struct ml_sqlite3_data **) Data_custom_val(v))) - -#if defined(__GNUC__) && (__GNUC__ >= 3) -# define Pure __attribute__ ((pure)) -#else -# define Pure -#endif - -static sqlite3 * Sqlite3_val (value) Pure; -static sqlite3_stmt * Sqlite3_stmt_val (value) Pure; -static sqlite3_value * Sqlite3_value_val (value) Pure; - -static inline sqlite3 * -Sqlite3_val (value v) -{ - struct ml_sqlite3_data *data = Sqlite3_data_val (v); - if (data->db == NULL) - ml_sqlite3_raise_exn (SQLITE_MISUSE, "closed db", TRUE); - return data->db; -} - -static inline sqlite3_stmt * -Sqlite3_stmt_val (value v) -{ - sqlite3_stmt *stmt = * ((sqlite3_stmt **) Field (v, 0)); - if (stmt == NULL) - ml_sqlite3_raise_exn (SQLITE_MISUSE, "invalid statement", TRUE); - return stmt; -} - -static inline sqlite3_value * -Sqlite3_value_val (value v) -{ - sqlite3_value *val = * ((sqlite3_value **) v); - if (val == NULL) - ml_sqlite3_raise_exn (SQLITE_MISUSE, "invalid value", TRUE); - return val; -} - - - /* 0 -> trace * 1 -> busy * 2 -> progress @@ -231,7 +173,11 @@ ml_sqlite3_complete (value sql) CAMLprim value ml_sqlite3_complete (value sql) { +#ifdef HAVE_SQLITE3_COMPLETE return Val_bool (sqlite3_complete (String_val (sql))); +#else + caml_failwith ("sqlite3_complete unavailable"); +#endif } CAMLprim value @@ -258,18 +204,20 @@ ml_sqlite3_total_changes (value db) return Val_long (sqlite3_total_changes (Sqlite3_val (db))); } -#if 0 CAMLprim value ml_sqlite3_get_autocommit (value db) { +#ifdef HAVE_SQLITE3_GET_AUTOCOMMIT return Val_bool (sqlite3_get_autocommit (Sqlite3_val (db))); -} +#else + caml_failwith ("sqlite3_get_autocommit unavailable"); #endif +} CAMLprim value ml_sqlite3_sleep (value ms) { -#if 0 +#if HAVE_SQLITE3_SLEEP return Val_int (sqlite3_sleep (Int_val (ms))); #else caml_failwith ("sqlite3_sleep unavailable"); @@ -345,6 +293,7 @@ ml_sqlite3_trace_unset (value db) return Val_unit; } +#ifdef HAVE_SQLITE3_PROGRESS_HANDLER static int ml_sqlite3_progress_handler_cb (void *data) { @@ -353,23 +302,28 @@ ml_sqlite3_progress_handler_cb (void *da res = caml_callback_exn (Field (db->callbacks, 2), Val_unit); return Is_exception_result(res); } +#endif CAMLprim value ml_sqlite3_progress_handler (value db, value delay, value cb) { +#ifdef HAVE_SQLITE3_PROGRESS_HANDLER struct ml_sqlite3_data *db_data = Sqlite3_data_val(db); sqlite3_progress_handler (Sqlite3_val (db), Int_val (delay), ml_sqlite3_progress_handler_cb, db_data); Store_field (db_data->callbacks, 2, cb); +#endif return Val_unit; } CAMLprim value ml_sqlite3_progress_handler_unset (value db) { +#ifdef HAVE_SQLITE3_PROGRESS_HANDLER struct ml_sqlite3_data *db_data = Sqlite3_data_val(db); sqlite3_progress_handler (Sqlite3_val(db), 0, NULL, NULL); Store_field (db_data->callbacks, 2, Val_unit); +#endif return Val_unit; } @@ -508,11 +462,8 @@ ml_sqlite3_reset (value stmt) CAMLprim value ml_sqlite3_reset (value stmt) { - sqlite3_stmt *s = * ((sqlite3_stmt **) Field (stmt, 0)); - if (s == NULL) - ml_sqlite3_recompile (stmt, NULL); - else - sqlite3_reset (s); + sqlite3_stmt *s = Sqlite3_stmt_val (stmt); + sqlite3_reset (s); return Val_unit; } @@ -520,7 +471,7 @@ ml_sqlite3_expired (value stmt) ml_sqlite3_expired (value stmt) { sqlite3_stmt *s = * ((sqlite3_stmt **) Field (stmt, 0)); - return Val_bool (s ? sqlite3_expired (s) : TRUE); + return Val_bool (s == NULL); } #define MLTAG_ROW 0x007cfbf5L @@ -545,12 +496,13 @@ ml_sqlite3_step (value stmt) default: /* either BUSY, ERROR or MISUSE */ { sqlite3 *db; - if (sqlite3_expired (s)) + if (status == SQLITE_ERROR) + status = sqlite3_reset (s); + if (status == SQLITE_SCHEMA) { s = ml_sqlite3_recompile (stmt, s); goto again; } - status = ml_sqlite3_finalize_stmt (stmt, &db); ml_sqlite3_raise_exn (status, sqlite3_errmsg (db), TRUE); } } @@ -594,7 +546,7 @@ ml_sqlite3_bind (value s, value idx, val SQLITE_TRANSIENT); break; case MLTAG_VALUE: -#if 0 +#if HAVE_SQLITE3_BIND_VALUE status = sqlite3_bind_value (stmt, i, Sqlite3_value_val (val)); break; #else caml_failwith ("sqlite3_bind_value unavailable"); @@ -631,14 +583,23 @@ ml_sqlite3_clear_bindings (value s) CAMLprim value ml_sqlite3_clear_bindings (value s) { -#if 0 +#if HAVE_SQLITE3_CLEAR_BINDINGS int status; status = sqlite3_clear_bindings (Sqlite3_stmt_val (s)); if (status != SQLITE_OK) ml_sqlite3_raise_exn (status, "clear_bindings failed", TRUE); return Val_unit; #else - caml_failwith ("sqlite3_clear_bindings unavailable"); + sqlite3_stmt *stmt = Sqlite3_stmt_val (s); + int i, n, status; + n = sqlite3_bind_parameter_count(stmt); + for (i = 1; i <= n; i++) + { + status = sqlite3_bind_null(stmt, i); + if (status != SQLITE_OK) + ml_sqlite3_raise_exn (status, "clear_bindings failed", TRUE); + } + return Val_unit; #endif } ============================================================ --- mlsqlite/sqlite3.ml 53295e5f3023a4343a9708c102aa6622e12633fb +++ mlsqlite/sqlite3.ml fecc3f3795b4baeb75e1816275cfcd7c96c4b8c6 @@ -1,11 +1,14 @@ module Weak_store = struct module Weak_store = struct type 'a t = - { mutable w : 'a Weak.t ; mutable free : int } + { mutable w : 'a Weak.t ; + mutable free : int ; + finalise : 'a -> unit } - let create () = - { w = Weak.create 8 ; free = 0 } + 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 @@ -30,6 +33,13 @@ module Weak_store = struct 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 @@ -91,23 +101,15 @@ let stmt_store db = let stmt_store db = try get_stmt_store db with Not_found -> - let s = Weak_store.create () in + let s = Weak_store.create finalize_stmt in set_stmt_store db (Some s) ; s let register_stmt db stmt = - Gc.finalise finalize_stmt stmt ; Weak_store.register (stmt_store db) stmt let close_db db = begin - try - let store = (get_stmt_store db).Weak_store.w in - for i = 0 to Weak.length store - 1 do - match Weak.get store i with - | Some stmt -> finalize_stmt stmt - | None -> () - done ; - set_stmt_store db None ; + try Weak_store.clear (get_stmt_store db) with Not_found -> () end ; _close_db db @@ -122,7 +124,7 @@ external total_changes : db -> int = "ml 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 get_autocommit : db -> bool = "ml_sqlite3_get_autocommit" external sleep : int -> unit = "ml_sqlite3_sleep" external busy_set : db -> (int -> [`FAIL|`RETRY]) -> unit @@ -146,20 +148,6 @@ external prepare : db -> string -> int - (* } *) external prepare : db -> string -> int -> stmt option * int = "ml_sqlite3_prepare" - -let _prepare_one db sql = - match prepare db sql 0 with - | Some stmt, _ -> - register_stmt db stmt ; - stmt - | None, _ -> failwith "Sqlite3.prepare_one: empty statement" - -let prepare_one db sql = - _prepare_one db (String.copy sql) - -let prepare_one_f db fmt = - Printf.kprintf (_prepare_one db) fmt - external reset : stmt -> unit = "ml_sqlite3_reset" external expired : stmt -> bool = "ml_sqlite3_expired" external step : stmt -> [`DONE|`ROW] = "ml_sqlite3_step" @@ -210,34 +198,97 @@ external delete_function : db -> string external delete_function : db -> string -> unit = "ml_sqlite3_delete_function" -let fold_prepare db sql f init = + + +(* 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 -> finalize_stmt stmt ; raise exn in - finalize_stmt stmt ; + with exn -> + if final then 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 rec fold_rows f acc stmt = - match step stmt with - | `DONE -> acc - | `ROW -> - fold_rows f (f acc stmt) stmt +let fold_prepare db sql = + _fold_prepare db (String.copy sql) +let fold_prepare_f db fmt = + Printf.kprintf (_fold_prepare db) fmt -let _exec db sql = - fold_prepare - db sql - (fold_rows - (fun () _ -> ())) + +(* 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 data = + _fold_prepare_bind + ~final:true + db sql data + (fun () stmt -> do_step stmt) () let exec db sql = @@ -247,31 +298,32 @@ let exec_f db fmt = Printf.kprintf (_exec db) fmt -let fetch_one ?column_names f init stmt = - begin - match column_names with - | None -> () - | Some n -> - n := Array.init (column_count stmt) (column_name stmt) - end ; - fold_rows - (fun acc stmt -> - let row = - Array.init - (data_count stmt) - (column_blob stmt) in - f acc row) - init - stmt +(* Execute statements and get some results back *) +let rec fold_step f acc stmt = + match step stmt with + | `DONE -> acc + | `ROW -> + fold_step f (f acc stmt) stmt -let _fetch db sql ?column_names f init = - fold_prepare - db sql - (fetch_one ?column_names f) - init +let _fetch db sql data f init = + _fold_prepare_bind + db sql data + (fold_step f) init -let fetch db sql ?column_names f init = - _fetch db (String.copy sql) ?column_names f init +let fetch db sql = + _fetch db (String.copy sql) +let fetch_f db fmt = + Printf.kprintf (_fetch 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 fetch_f db ?column_names f init fmt = - Printf.kprintf (fun q -> _fetch db q ?column_names f init) fmt ============================================================ --- mlsqlite/sqlite3.mli 7675ce305667d22ed95e6040e58b94769a4b66d5 +++ mlsqlite/sqlite3.mli 61a3daf169c5cfa3c693dcce707a3489b69c420b @@ -1,3 +1,7 @@ +(** Sqlite3 bindings for OCaml *) + +(** {2 Types and library initialization} *) + type db type stmt type argument @@ -12,121 +16,156 @@ type sql_value = | `TEXT of string | `VALUE of argument ] +type ('a, 'b) fmt = ('b, unit, string, 'a) format4 -> 'b + 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 + ERROR (** SQL error or missing database *) + | INTERNAL (** An internal logic error in SQLite *) + | PERM (** Access permission denied *) + | ABORT (** Callback routine requested an abort *) + | BUSY (** The database file is locked *) + | LOCKED (** A table in the database is locked *) + | NOMEM (** A [malloc()] failed *) + | READONLY (** Attempt to write a readonly database *) + | INTERRUPT (** Operation terminated by [sqlite_interrupt()] *) + | IOERR (** Some kind of disk I/O error occurred *) + | CORRUPT (** The database disk image is malformed *) + | NOTFOUND (** (Internal Only) Table or record not found *) + | FULL (** Insertion failed because database is full *) + | CANTOPEN (** Unable to open the database file *) + | PROTOCOL (** Database lock protocol error *) + | EMPTY (** (Internal Only) Database table is empty *) + | SCHEMA (** The database schema changed *) + | TOOBIG (** Too much data for one row of a table *) + | CONSTRAINT (** Abort due to constraint violation *) + | MISMATCH (** Data type mismatch *) + | MISUSE (** Library used incorrectly *) + | NOLFS (** Uses OS features not supported on host *) + | AUTH (** Authorization denied *) + | FORMAT (** Auxiliary database format error *) + | RANGE (** 2nd parameter to [sqlite3_bind] out of range *) + | NOTADB (** File opened that is not a database file *) exception Error of error_code * string +val version : string +(** The [sqlite3] library version number. *) + val init : unit +(** Reference this value to ensure that the [Sqlite3] module is linked in. *) + +(** {2 Open/Close databases} *) + external open_db : string -> db = "ml_sqlite3_open" val close_db : db -> unit external interrupt : db -> unit = "ml_sqlite3_interrupt" external is_complete : string -> bool = "ml_sqlite3_complete" -val version : string 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 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" +(** {2 Callbacks} *) + +(** The [busy] callback *) + +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" +(** The [trace] callback *) + +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" +(** The [progress] callback *) +external progress_handler_set : db -> int -> (unit -> unit) -> unit = "ml_sqlite3_progress_handler" +external progress_handler_unset : db -> unit = "ml_sqlite3_progress_handler_unset" +(** {2 Compiled SQL statements } *) + external finalize_stmt : stmt -> unit = "ml_sqlite3_finalize_noerr" -val prepare_one : db -> string -> stmt +(** Finalize a statement. + Statements are collected by the GC so it's not necessary to manually finalize them. *) + +val prepare_one : db -> string -> stmt +(** Prepare the first statement in a string. The rest of the string is ignored *) +val prepare_one_f : db -> (stmt, 'a) fmt +(** Same as [prepare_one] but uses a format string.*) + external reset : stmt -> unit = "ml_sqlite3_reset" + external expired : stmt -> bool = "ml_sqlite3_expired" +(** This function is actually not a binding to the [sqlite3_expired] function. + [expired] return [true] if the [stmt] was finalized by calling {!finalize_stmt} + or if its [db] was closed with {!close_db} *) + external step : stmt -> [ `DONE | `ROW ] = "ml_sqlite3_step" +(** {3 SQL parameter binding} *) + 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 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" +(** {3 Results} *) + +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 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 value_blob : argument -> string = "ml_sqlite3_value_blob" +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" + +(** {2 User-defined SQL functions } *) + +(** {3 Arguments access} *) + +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 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" +(** {3 Registration} *) + val create_fun_N : db -> string -> (argument array -> sql_value) -> unit val create_fun_0 : db -> string -> (unit -> sql_value) -> unit val create_fun_1 : db -> string -> (argument -> sql_value) -> unit -val create_fun_2 : - db -> string -> (argument -> argument -> sql_value) -> unit -val create_fun_3 : - db -> string -> (argument -> argument -> argument -> sql_value) -> unit +val create_fun_2 : db -> string -> (argument -> argument -> sql_value) -> unit +val create_fun_3 : db -> string -> (argument -> argument -> argument -> sql_value) -> unit -external delete_function : db -> string -> unit - = "ml_sqlite3_delete_function" +external delete_function : db -> string -> unit = "ml_sqlite3_delete_function" -val fold_rows : ('a -> stmt -> 'a) -> 'a -> stmt -> 'a -val exec : db -> string -> unit -val exec_f : db -> ('a, unit, string, unit) format4 -> 'a +(** {2 High-level functions} *) +val do_step : stmt -> unit +val fold_step : ('a -> stmt -> 'a) -> 'a -> stmt -> 'a + +val bind_and_exec : stmt -> sql_value list -> unit +val bind_fetch : stmt -> sql_value list -> ('a -> stmt -> 'a) -> 'a -> 'a + +val fetch : db -> string -> sql_value list -> ('a -> stmt -> 'a) -> 'a -> 'a +val exec : db -> string -> sql_value list -> unit +val fold_prepare_bind : db -> string -> sql_value list -> ('a -> stmt -> 'a) -> 'a -> 'a +val fold_prepare : db -> string -> ('a -> stmt -> 'a) -> 'a -> 'a + +val fetch_f : db -> (sql_value list -> ('a -> stmt -> 'a) -> 'a -> 'a, 'b) fmt +val exec_f : db -> (sql_value list -> unit, 'b) fmt +val fold_prepare_bind_f : db -> (sql_value list -> ('a -> stmt -> 'a) -> 'a -> 'a, 'b) fmt +val fold_prepare_f : db -> (('a -> stmt -> 'a) -> 'a -> 'a, 'b) fmt + -val fetch : - db -> - string -> - ?column_names:string array ref -> ('a -> string array -> 'a) -> 'a -> 'a -val fetch_f : - db -> - ?column_names:string array ref -> - ('a -> string array -> 'a) -> 'a -> ('b, unit, string, 'a) format4 -> 'b