From 18818962d66f9bcbe3d1db778657868cfd00c199 Mon Sep 17 00:00:00 2001 From: "Petter A. Urkedal" Date: Sun, 25 Jul 2021 16:26:00 +0200 Subject: [PATCH] Expose sqlstate in the connection and stmt APIs. --- bindings/ffi_bindings.ml | 6 +++++ examples/blocking/blocking_example.ml | 32 +++++++++++++++++++++++++++ lib/blocking.ml | 4 ++++ lib/common.ml | 6 +++++ lib/mariadb.ml | 2 ++ lib/mariadb.mli | 10 +++++++++ lib/nonblocking.ml | 4 ++++ 7 files changed, 64 insertions(+) diff --git a/bindings/ffi_bindings.ml b/bindings/ffi_bindings.ml index d881dba..30f0554 100644 --- a/bindings/ffi_bindings.ml +++ b/bindings/ffi_bindings.ml @@ -305,6 +305,9 @@ module Bindings (F : Cstubs.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) @@ -317,6 +320,9 @@ module Bindings (F : Cstubs.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 915e8f2..b08c17c 100644 --- a/examples/blocking/blocking_example.ml +++ b/examples/blocking/blocking_example.ml @@ -48,6 +48,37 @@ let stream res = try Ok (Stream.from next) with F.E e -> Error e +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 (_, msg) -> + 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" @@ -59,6 +90,7 @@ let main () = let s = stream res |> or_die "stream" in Stream.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 cf5d9e5..c3e9230 100644 --- a/lib/blocking.ml +++ b/lib/blocking.ml @@ -152,6 +152,8 @@ let prepare mariadb query = | Some raw -> build_stmt raw | None -> Error (2008, "out of memory") +let sqlstate = Common.sqlstate + module Res = struct type t = [`Blocking] Common.Res.t @@ -198,6 +200,8 @@ module Stmt = struct else Error (Common.Stmt.error stmt) + let sqlstate = Common.Stmt.sqlstate + let close stmt = let raw = stmt.Common.Stmt.raw in if B.mysql_stmt_free_result raw && B.mysql_stmt_close raw then diff --git a/lib/common.ml b/lib/common.ml index 0fc95c9..042509a 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 @@ -280,6 +283,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 90f5774..5a999da 100644 --- a/lib/mariadb.ml +++ b/lib/mariadb.ml @@ -79,6 +79,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 @@ -162,6 +163,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 c1c3392..772082c 100644 --- a/lib/mariadb.mli +++ b/lib/mariadb.mli @@ -154,6 +154,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. *) @@ -275,6 +281,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 71e9d53..20ee5e9 100644 --- a/lib/nonblocking.ml +++ b/lib/nonblocking.ml @@ -211,6 +211,8 @@ let prepare mariadb query = `Ok (prepare_start mariadb stmt query, prepare_cont mariadb stmt) | None -> `Error (Common.error mariadb) +let sqlstate = Common.sqlstate + module Res = struct type t = [`Nonblocking] Common.Res.t @@ -344,6 +346,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