Browse Source

in_transaction/1 (related to #7)

Viktor Söderqvist 10 years ago
parent
commit
ad019aa3d6
6 changed files with 65 additions and 29 deletions
  1. 0 21
      include/protocol.hrl
  2. 38 0
      include/server_status.hrl
  3. 17 4
      src/mysql.erl
  4. 6 4
      src/mysql_connection.erl
  5. 1 0
      src/mysql_protocol.erl
  6. 3 0
      test/mysql_tests.erl

+ 0 - 21
include/protocol.hrl

@@ -16,27 +16,6 @@
 %% You should have received a copy of the GNU Lesser General Public License
 %% along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-%% --- Status flags (bits) ---
-
--define(SERVER_STATUS_IN_TRANS, 16#0001).       %% a transaction is active
--define(SERVER_STATUS_AUTOCOMMIT, 16#0002).     %% auto-commit is enabled
--define(SERVER_MORE_RESULTS_EXISTS, 16#0008).
--define(SERVER_STATUS_NO_GOOD_INDEX_USED, 16#0010).
--define(SERVER_STATUS_NO_INDEX_USED, 16#0020).
--define(SERVER_STATUS_CURSOR_EXISTS, 16#0040).  %% Used by Binary Protocol
-                                                %% Resultset to signal that
-                                                %% COM_STMT_FETCH has to be used
-                                                %% to fetch the row-data.
--define(SERVER_STATUS_LAST_ROW_SENT, 16#0080).
--define(SERVER_STATUS_DB_DROPPED, 16#0100).
--define(SERVER_STATUS_NO_BACKSLASH_ESCAPES, 16#0200).
--define(SERVER_STATUS_METADATA_CHANGED, 16#0400).
--define(SERVER_QUERY_WAS_SLOW, 16#0800).
--define(SERVER_PS_OUT_PARAMS, 16#1000).
--define(SERVER_STATUS_IN_TRANS_READONLY, 16#2000). %% in a read-only transaction
--define(SERVER_SESSION_STATE_CHANGED, 16#4000). %% connection state information
-                                                %% has changed
-
 %% Response packet tag (first byte)
 -define(OK, 0).
 -define(EOF, 16#fe).

+ 38 - 0
include/server_status.hrl

@@ -0,0 +1,38 @@
+%% MySQL/OTP – MySQL client library for Erlang/OTP
+%% Copyright (C) 2014 Viktor Söderqvist
+%%
+%% This file is part of MySQL/OTP.
+%%
+%% MySQL/OTP is free software: you can redistribute it and/or modify it under
+%% the terms of the GNU Lesser General Public License as published by the Free
+%% Software Foundation, either version 3 of the License, or (at your option)
+%% any later version.
+%%
+%% This program is distributed in the hope that it will be useful, but WITHOUT
+%% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+%% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+%% more details.
+%%
+%% You should have received a copy of the GNU Lesser General Public License
+%% along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+%% --- Status flags (bits) ---
+
+-define(SERVER_STATUS_IN_TRANS, 16#0001).       %% a transaction is active
+-define(SERVER_STATUS_AUTOCOMMIT, 16#0002).     %% auto-commit is enabled
+-define(SERVER_MORE_RESULTS_EXISTS, 16#0008).
+-define(SERVER_STATUS_NO_GOOD_INDEX_USED, 16#0010).
+-define(SERVER_STATUS_NO_INDEX_USED, 16#0020).
+-define(SERVER_STATUS_CURSOR_EXISTS, 16#0040).  %% Used by Binary Protocol
+                                                %% Resultset to signal that
+                                                %% COM_STMT_FETCH has to be used
+                                                %% to fetch the row-data.
+-define(SERVER_STATUS_LAST_ROW_SENT, 16#0080).
+-define(SERVER_STATUS_DB_DROPPED, 16#0100).
+-define(SERVER_STATUS_NO_BACKSLASH_ESCAPES, 16#0200).
+-define(SERVER_STATUS_METADATA_CHANGED, 16#0400).
+-define(SERVER_QUERY_WAS_SLOW, 16#0800).
+-define(SERVER_PS_OUT_PARAMS, 16#1000).
+-define(SERVER_STATUS_IN_TRANS_READONLY, 16#2000). %% in a read-only transaction
+-define(SERVER_SESSION_STATE_CHANGED, 16#4000). %% connection state information
+                                                %% has changed

+ 17 - 4
src/mysql.erl

@@ -23,7 +23,8 @@
 -module(mysql).
 
 -export([start_link/1, query/2, execute/3, prepare/2, warning_count/1,
-         affected_rows/1, insert_id/1, transaction/2, transaction/3]).
+         affected_rows/1, insert_id/1, in_transaction/1,
+         transaction/2, transaction/3]).
 
 -export_type([connection/0]).
 
@@ -44,12 +45,12 @@
 %% This is just a wrapper for `gen_server:start_link(mysql_connection, Options,
 %% [])'. If you need to specify gen_server options, use gen_server:start_link/3
 %% directly.
-start_link(Opts) ->
-    gen_server:start_link(mysql_connection, Opts, []).
 -spec start_link(Options) -> {ok, pid()} | ignore | {error, term()}
     when Options :: [Option],
          Option :: {host, iodata()} | {port, integer()} | {user, iodata()} |
                    {password, iodata()} | {database, iodata()}.
+start_link(Opts) ->
+    gen_server:start_link(mysql_connection, Opts, []).
 
 %% @doc Executes a query.
 -spec query(Conn, Query) -> ok | {ok, ColumnNames, Rows} | {error, Reason}
@@ -90,8 +91,19 @@ affected_rows(Conn) ->
 insert_id(Conn) ->
     gen_server:call(Conn, insert_id).
 
+%% @doc Returns true if the connection is in a transaction and false otherwise.
+%% This works regardless of whether the transaction has been started using
+%% transaction/2,3 or using a plain `mysql:query(Connection, "START
+%% TRANSACTION")'.
+%% @see transaction/2
+%% @see transaction/3
+-spec in_transaction(connection()) -> boolean().
+in_transaction(Conn) ->
+    gen_server:call(Conn, in_transaction).
+
 %% @doc This function executes the functional object Fun as a transaction.
 %% @see transaction/3
+%% @see in_transaction/1
 -spec transaction(connection(), fun()) -> {atomic, term()} | {aborted, term()}.
 transaction(Conn, Fun) ->
     transaction(Conn, Fun, []).
@@ -121,7 +133,7 @@ transaction(Conn, Fun) ->
 %%   </thead>
 %%   <tbody>
 %%     <tr>
-%%       <td>`error' with ErrorReason</td>
+%%       <td>`error' with reason `ErrorReason'</td>
 %%       <td>`{aborted, {ErrorReason, Stack}}'</td>
 %%     </tr>
 %%     <tr><td>`exit(Term)'</td><td>`{aborted, Term}'</td></tr>
@@ -131,6 +143,7 @@ transaction(Conn, Fun) ->
 %%
 %% TODO: Implement nested transactions
 %% TODO: Automatic restart on deadlocks
+%% @see in_transaction/1
 -spec transaction(connection(), fun(), list()) -> {atomic, term()} |
                                                   {aborted, term()}.
 transaction(Conn, Fun, Args) when is_list(Args),

+ 6 - 4
src/mysql_connection.erl

@@ -32,6 +32,7 @@
 -define(default_timeout, infinity).
 
 -include("records.hrl").
+-include("server_status.hrl").
 
 %% Gen_server state
 -record(state, {socket, timeout = infinity, affected_rows = 0, status = 0,
@@ -122,6 +123,8 @@ handle_call(insert_id, _From, State) ->
     {reply, State#state.insert_id, State};
 handle_call(affected_rows, _From, State) ->
     {reply, State#state.affected_rows, State};
+handle_call(in_transaction, _From, State) ->
+    {reply, State#state.status band ?SERVER_STATUS_IN_TRANS /= 0, State};
 handle_call(get_state, _From, State) ->
     %% *** FOR DEBUGGING ***
     %% TODO: Delete this.
@@ -150,9 +153,8 @@ update_state(State, #ok{status = S, affected_rows = R,
     State#state{status = S, affected_rows = R, insert_id = Id,
                 warning_count = W};
 update_state(State, #eof{status = S, warning_count = W}) ->
-    State#state{status = S, warning_count = W, insert_id = 0,
-                affected_rows = 0};
+    State#state{status = S, warning_count = W, affected_rows = 0};
 update_state(State, _Other) ->
     %% This includes errors, resultsets, etc.
-    %% Reset warnings, etc. (Note: We don't reset 'status'.)
-    State#state{warning_count = 0, insert_id = 0, affected_rows = 0}.
+    %% Reset warnings, etc. (Note: We don't reset status and insert_id.)
+    State#state{warning_count = 0, affected_rows = 0}.

+ 1 - 0
src/mysql_protocol.erl

@@ -879,6 +879,7 @@ nulterm_str(Bin) ->
 
 -ifdef(TEST).
 -include_lib("eunit/include/eunit.hrl").
+-include("server_status.hrl").
 
 %% Testing some of the internal functions, mostly the cases we don't cover in
 %% other tests.

+ 3 - 0
test/mysql_tests.erl

@@ -297,11 +297,14 @@ transaction_single_connection_test_() ->
              fun transaction_simple_aborted/1]}}.
 
 transaction_simple_success(Pid) ->
+    ?assertNot(mysql:in_transaction(Pid)),
     Result = mysql:transaction(Pid, fun () ->
                  ok = mysql:query(Pid, "INSERT INTO foo VALUES (42)"),
+                 ?assert(mysql:in_transaction(Pid)),
                  hello
              end),
     ?assertEqual({atomic, hello}, Result),
+    ?assertNot(mysql:in_transaction(Pid)),
     ok = mysql:query(Pid, "DELETE FROM foo").
 
 transaction_simple_aborted(Pid) ->