diff --git a/bindings/ffi_bindings.ml b/bindings/ffi_bindings.ml index e55c5ff..1520c85 100644 --- a/bindings/ffi_bindings.ml +++ b/bindings/ffi_bindings.ml @@ -308,6 +308,9 @@ module Functions (F : Ctypes.FOREIGN) = struct let mysql_ping = foreign "mysql_ping" (mysql @-> returning int) + let mysql_sqlstate = foreign "mysql_sqlstate" + (mysql @-> returning string) + let mysql_stmt_prepare = foreign "mysql_stmt_prepare" (stmt @-> ptr char @-> ulong @-> returning int) @@ -320,6 +323,9 @@ module Functions (F : Ctypes.FOREIGN) = struct let mysql_stmt_fetch = foreign "mysql_stmt_fetch" (stmt @-> returning int) + let mysql_stmt_sqlstate = foreign "mysql_stmt_sqlstate" + (stmt @-> returning string) + let mysql_stmt_close = foreign "mysql_stmt_close" (stmt @-> returning my_bool) diff --git a/examples/blocking/blocking_example.ml b/examples/blocking/blocking_example.ml index 81de5dd..41298f4 100644 --- a/examples/blocking/blocking_example.ml +++ b/examples/blocking/blocking_example.ml @@ -47,6 +47,37 @@ let stream res = | Error e -> raise (F.E e) in next +let test_sqlstate mariadb = + assert (M.sqlstate mariadb = "00000"); + (match M.prepare mariadb "SELECT * FROM inexistent_table" with + | Error _ -> assert (M.sqlstate mariadb <> "00000") (* actually "42S02" *) + | Ok _ -> assert false); + begin + let stmt = + M.prepare mariadb + "CREATE TEMPORARY TABLE test_sqlstate (i integer PRIMARY KEY)" + |> or_die "prepare CREATE TABLE test_sqlstate" + in + let _ = + M.Stmt.execute stmt [||] + |> or_die "exec CREATE TABLE test_sqlstate" + in + M.Stmt.close stmt |> or_die "stmt close CREATE TABLE test_sqlstate" + end; + for i = 0 to 1 do + let stmt = + M.prepare mariadb "INSERT INTO test_sqlstate VALUES (?)" + |> or_die "prepare in test_sqlstate" + in + (match M.Stmt.execute stmt [|`Int 1|] with + | Error (_, _) -> + assert (i = 1); + assert (M.Stmt.sqlstate stmt <> "00000") (* actually "23000" *) + | Ok _ -> assert (i = 0)); + + M.Stmt.close stmt |> or_die "stmt close in test_sqlstate" + done + let main () = let mariadb = connect () |> or_die "connect" in let query = env "OCAML_MARIADB_QUERY" @@ -58,6 +89,7 @@ let main () = let s = stream res in Seq.iter print_row s; M.Stmt.close stmt |> or_die "stmt close"; + test_sqlstate mariadb; M.close mariadb; M.library_end (); printf "done\n%!" diff --git a/lib/blocking.ml b/lib/blocking.ml index 11904af..d75db29 100644 --- a/lib/blocking.ml +++ b/lib/blocking.ml @@ -153,6 +153,8 @@ let prepare mariadb query = let start_txn mariadb = wrap_unit mariadb (B.mysql_real_query mariadb.Common.raw "START TRANSACTION") +let sqlstate = Common.sqlstate + module Res = struct type t = [`Blocking] Common.Res.t @@ -203,6 +205,8 @@ module Stmt = struct else Error (Common.Stmt.error stmt) + let sqlstate = Common.Stmt.sqlstate + let close stmt = Common.Stmt.free_meta stmt; let raw = stmt.Common.Stmt.raw in diff --git a/lib/common.ml b/lib/common.ml index 397df26..b5a8542 100644 --- a/lib/common.ml +++ b/lib/common.ml @@ -85,6 +85,9 @@ type error = int * string let error mariadb = (B.mysql_errno mariadb.raw, B.mysql_error mariadb.raw) +let sqlstate mariadb = + B.mysql_sqlstate mariadb.raw + let int_of_server_option = function | Multi_statements true -> T.Server_options.multi_statements_on | Multi_statements false -> T.Server_options.multi_statements_off @@ -283,6 +286,9 @@ module Stmt = struct let error stmt = (B.mysql_stmt_errno stmt.raw, B.mysql_stmt_error stmt.raw) + let sqlstate stmt = + B.mysql_stmt_sqlstate stmt.raw + let fetch_field res i = coerce (ptr void) (ptr T.Field.t) (B.mysql_fetch_field_direct res i) diff --git a/lib/mariadb.ml b/lib/mariadb.ml index 85b7be9..9d68ceb 100644 --- a/lib/mariadb.ml +++ b/lib/mariadb.ml @@ -80,6 +80,7 @@ module type S = sig val execute : t -> Field.value array -> Res.t result val reset : t -> unit result + val sqlstate : t -> string val close : t -> unit result end @@ -164,6 +165,7 @@ module type S = sig val commit : t -> unit result val rollback : t -> unit result val prepare : t -> string -> Stmt.t result + val sqlstate : t -> string end module B = Binding_wrappers diff --git a/lib/mariadb.mli b/lib/mariadb.mli index 0e39c8d..dc56bad 100644 --- a/lib/mariadb.mli +++ b/lib/mariadb.mli @@ -160,6 +160,12 @@ module type S = sig were after [stmt] was prepared, and frees up any {!Res.t} produced by [stmt]. *) + val sqlstate : t -> string + (** [sqlstate stmt] is the SQLSTATE with MariaDB extensions indicating the + status of the previous execution of the statement. The string + ["00000"] is returned if no error occurred or if the statement has not + been executed. *) + val close : t -> unit result (** [close stmt] closes the prepapred statement [stmt] and frees any allocated memory associated with it and its result. *) @@ -282,6 +288,10 @@ module type S = sig (** [prepare mariadb query] creates a prepared statement for [query]. The query may contain [?] as placeholders for parameters that can be bound by calling [Stmt.execute]. *) + + val sqlstate : t -> string + (* [sqlstate mariadb] is the SQLSTATE with MariaDB extensions of the last + * operation on [mariadb]. Returns ["00000"] if no error occurred. *) end (** The module for blocking MariaDB API calls. It should be possible to call diff --git a/lib/nonblocking.ml b/lib/nonblocking.ml index 0184ff2..857673f 100644 --- a/lib/nonblocking.ml +++ b/lib/nonblocking.ml @@ -218,6 +218,8 @@ let prepare mariadb query = `Ok (prepare_start mariadb stmt, prepare_cont mariadb stmt) | None -> `Error (Common.error mariadb) +let sqlstate = Common.sqlstate + module Res = struct type t = [`Nonblocking] Common.Res.t @@ -354,6 +356,8 @@ module Stmt = struct let next_result_cont stmt status = handle_next stmt (B.mysql_stmt_next_result_cont stmt.Common.Stmt.raw status) + + let sqlstate = Common.Stmt.sqlstate end module type Wait = sig