Browse Source

Improve typing

* Improve typing

* Tighten transaction function spec
Jan Uhlig 4 years ago
parent
commit
5f8d3c8fbf
1 changed files with 122 additions and 85 deletions
  1. 122 85
      src/mysql.erl

+ 122 - 85
src/mysql.erl

@@ -35,7 +35,9 @@
          transaction/2, transaction/3, transaction/4,
          change_user/3, change_user/4, reset_connection/1]).
 
--export_type([connection/0, server_reason/0, query_result/0]).
+-export_type([option/0, connection/0, query/0, statement_name/0,
+              statement_ref/0, query_param/0, query_filtermap_fun/0,
+              query_result/0, transaction_result/1, server_reason/0]).
 
 %% A connection is a ServerRef as in gen_server:call/2,3.
 -type connection() :: Name :: atom() |
@@ -48,20 +50,49 @@
 -type server_reason() :: {Code :: integer(), SQLState :: binary() | undefined,
                           Message :: binary()}.
 
--type column_names() :: [binary()].
+-type column_name() :: binary().
+-type query() :: iodata().
 -type row() :: [term()].
--type rows() :: [row()].
+
+-type query_param() :: term().
 
 -type query_filtermap_fun() :: fun((row()) -> query_filtermap_res())
-                             | fun((column_names(), row()) -> query_filtermap_res()).
+                             | fun(([column_name()], row()) -> query_filtermap_res()).
 -type query_filtermap_res() :: boolean()
                              | {true, term()}.
 
+-type statement_id() :: integer().
+-type statement_name() :: atom().
+-type statement_ref() :: statement_id() | statement_name().
+
 -type query_result() :: ok
-                      | {ok, column_names(), rows()}
-                      | {ok, [{column_names(), rows()}, ...]}
+                      | {ok, [column_name()], [row()]}
+                      | {ok, [{[column_name()], [row()]}, ...]}
                       | {error, server_reason()}.
 
+-type transaction_result(Result) :: {atomic, Result} | {aborted, Reason :: term()}.
+
+-type server_name() :: {local, Name :: atom()}
+                     | {global, GlobalName :: term()}
+                     | {via, Via :: module(), ViaName :: term()}.
+
+-type option() :: {name, ServerName :: server_name()}
+                | {host, inet:socket_address() | inet:hostname()} | {port, integer()}
+                | {user, iodata()} | {password, iodata()}
+                | {database, iodata()}
+                | {connect_mode, synchronous | asynchronous | lazy}
+                | {connect_timeout, timeout()}
+                | {log_warnings, boolean()}
+                | {log_slow_queries, boolean()}
+                | {keep_alive, boolean() | timeout()}
+                | {prepare, [{StatementName :: statement_name(), Statement :: query()}]}
+                | {queries, [query()]}
+                | {query_timeout, timeout()}
+                | {found_rows, boolean()}
+                | {query_cache_time, non_neg_integer()}
+                | {tcp_options, [gen_tcp:connect_option()]}
+                | {ssl, term()}.
+
 -include("exception.hrl").
 
 %% @doc Starts a connection gen_server process and connects to a database. To
@@ -154,28 +185,7 @@
 %%       of the `host' option if it is a hostname string, otherwise no default
 %%       value is set.</dd>
 %% </dl>
--spec start_link(Options) -> {ok, pid()} | ignore | {error, term()}
-    when Options :: [Option],
-         Option :: {name, ServerName} |
-                   {host, inet:socket_address() | inet:hostname()} | {port, integer()} |
-                   {user, iodata()} | {password, iodata()} |
-                   {database, iodata()} |
-                   {connect_mode, synchronous | asynchronous | lazy} |
-                   {connect_timeout, timeout()} |
-                   {log_warnings, boolean()} |
-                   {log_slow_queries, boolean()} |
-                   {keep_alive, boolean() | timeout()} |
-                   {prepare, NamedStatements} |
-                   {queries, [iodata()]} |
-                   {query_timeout, timeout()} |
-                   {found_rows, boolean()} |
-                   {query_cache_time, non_neg_integer()} |
-                   {tcp_options, [gen_tcp:connect_option()]} |
-                   {ssl, term()},
-         ServerName :: {local, Name :: atom()} |
-                       {global, GlobalName :: term()} |
-                       {via, Module :: atom(), ViaName :: term()},
-         NamedStatements :: [{StatementName :: atom(), Statement :: iodata()}].
+-spec start_link(Options :: [option()]) -> {ok, pid()} | ignore | {error, term()}.
 start_link(Options) ->
     case proplists:get_value(name, Options) of
         undefined ->
@@ -241,68 +251,68 @@ is_connected(Conn) ->
          Query :: iodata(),
          Result :: query_result().
 query(Conn, Query) ->
-    query(Conn, Query, no_params, no_filtermap_fun, default_timeout).
+    query_helper(Conn, Query, no_params, no_filtermap_fun, default_timeout).
 
 %% @doc Executes a query.
 %% @see query/5.
 -spec query(Conn, Query, Params | FilterMap | Timeout) -> Result
     when Conn :: connection(),
-         Query :: iodata(),
-         Timeout :: default_timeout | timeout(),
-         Params :: no_params | [term()],
-         FilterMap :: no_filtermap_fun | query_filtermap_fun(),
+         Query :: query(),
+         Timeout :: timeout(),
+         Params :: [query_param()],
+         FilterMap :: query_filtermap_fun(),
          Result :: query_result().
 query(Conn, Query, Params) when Params == no_params;
                                 is_list(Params) ->
-    query(Conn, Query, Params, no_filtermap_fun, default_timeout);
+    query_helper(Conn, Query, Params, no_filtermap_fun, default_timeout);
 query(Conn, Query, FilterMap) when FilterMap == no_filtermap_fun;
                                    is_function(FilterMap, 1);
                                    is_function(FilterMap, 2) ->
-    query(Conn, Query, no_params, FilterMap, default_timeout);
+    query_helper(Conn, Query, no_params, FilterMap, default_timeout);
 query(Conn, Query, Timeout) when Timeout == default_timeout;
                                  is_integer(Timeout);
                                  Timeout == infinity ->
-    query(Conn, Query, no_params, no_filtermap_fun, Timeout).
+    query_helper(Conn, Query, no_params, no_filtermap_fun, Timeout).
 
 %% @doc Executes a query.
 %% @see query/5.
 -spec query(Conn, Query, Params, Timeout) -> Result
         when Conn :: connection(),
-             Query :: iodata(),
-             Timeout :: default_timeout | timeout(),
-             Params :: no_params | [term()],
+             Query :: query(),
+             Timeout :: timeout(),
+             Params :: [query_param()],
              Result :: query_result();
     (Conn, Query, FilterMap, Timeout) -> Result
         when Conn :: connection(),
-             Query :: iodata(),
-             Timeout :: default_timeout | timeout(),
-             FilterMap :: no_filtermap_fun | query_filtermap_fun(),
+             Query :: query(),
+             Timeout :: timeout(),
+             FilterMap :: query_filtermap_fun(),
              Result :: query_result();
     (Conn, Query, Params, FilterMap) -> Result
         when Conn :: connection(),
-             Query :: iodata(),
-             Params :: no_params | [term()],
-             FilterMap :: no_filtermap_fun | query_filtermap_fun(),
+             Query :: query(),
+             Params :: [query_param()],
+             FilterMap :: query_filtermap_fun(),
              Result :: query_result().
 query(Conn, Query, Params, Timeout) when (Params == no_params orelse
                                           is_list(Params)) andalso
                                          (Timeout == default_timeout orelse
                                           is_integer(Timeout) orelse
                                           Timeout == infinity) ->
-    query(Conn, Query, Params, no_filtermap_fun, Timeout);
+    query_helper(Conn, Query, Params, no_filtermap_fun, Timeout);
 query(Conn, Query, FilterMap, Timeout) when (FilterMap == no_filtermap_fun orelse
                                              is_function(FilterMap, 1) orelse
                                              is_function(FilterMap, 2)) andalso
                                             (Timeout == default_timeout orelse
                                              is_integer(Timeout) orelse
                                              Timeout=:=infinity) ->
-    query(Conn, Query, no_params, FilterMap, Timeout);
+    query_helper(Conn, Query, no_params, FilterMap, Timeout);
 query(Conn, Query, Params, FilterMap) when (Params == no_params orelse
                                             is_list(Params)) andalso
                                            (FilterMap == no_filtermap_fun orelse
                                             is_function(FilterMap, 1) orelse
                                             is_function(FilterMap, 2)) ->
-    query(Conn, Query, Params, FilterMap, default_timeout).
+    query_helper(Conn, Query, Params, FilterMap, default_timeout).
 
 %% @doc Executes a query.
 %%
@@ -319,10 +329,9 @@ query(Conn, Query, Params, FilterMap) when (Params == no_params orelse
 %% statement. A prepared statement is created, executed and then cached for a
 %% certain time (specified using the option `{query_cache_time, Milliseconds}'
 %% to `start_link/1'). If the same query is executed again during this time,
-%% it does not need to be prepared again. If `Params' is omitted (or specified
-%% as `no_params'), the query is executed as a plain query. To force a query
-%% without parameters to be executed as a prepared statement, an empty list can
-%% be used for `Params'.
+%% it does not need to be prepared again. If `Params' is omitted, the query
+%% is executed as a plain query. To force a query without parameters to be
+%% executed as a prepared statement, an empty list can be used for `Params'.
 %%
 %% If `FilterMap' (a fun) is specified, the function is applied to each row to
 %% filter or perform other actions on the rows, in a way similar to how
@@ -330,8 +339,7 @@ query(Conn, Query, Params, FilterMap) when (Params == no_params orelse
 %% below for details.
 %%
 %% `Timeout' specifies the time to wait for a response from the database. If
-%% omitted (or specified as `default_timeout'), the timeout given in
-%% `start_link/1' is used.
+%% omitted, the timeout given in `start_link/1' is used.
 %%
 %% === Return value ===
 %%
@@ -396,18 +404,28 @@ query(Conn, Query, Params, FilterMap) when (Params == no_params orelse
 %%     (_) ->
 %%         false
 %% end,
-%% query(Conn, Query, no_params, FilterMap, default_timeout).
+%% query(Conn, Query, FilterMap).
 %% '''
 -spec query(Conn, Query, Params, FilterMap, Timeout) -> Result
     when Conn :: connection(),
-         Query :: iodata(),
+         Query :: query(),
+         Timeout :: timeout(),
+         Params :: [query_param()],
+         FilterMap :: query_filtermap_fun(),
+         Result :: query_result().
+query(Conn, Query, Params, FilterMap, Timeout) ->
+    query_helper(Conn, Query, Params, FilterMap, Timeout).
+
+-spec query_helper(Conn, Query, Params, FilterMap, Timeout) -> Result
+    when Conn :: connection(),
+         Query :: query(),
          Timeout :: default_timeout | timeout(),
-         Params :: no_params | [term()],
+         Params :: no_params | [query_param()],
          FilterMap :: no_filtermap_fun | query_filtermap_fun(),
          Result :: query_result().
-query(Conn, Query, no_params, FilterMap, Timeout) ->
+query_helper(Conn, Query, no_params, FilterMap, Timeout) ->
     query_call(Conn, {query, Query, FilterMap, Timeout});
-query(Conn, Query, Params, FilterMap, Timeout) ->
+query_helper(Conn, Query, Params, FilterMap, Timeout) ->
     case mysql_protocol:valid_params(Params) of
         true ->
             query_call(Conn,
@@ -424,11 +442,11 @@ query(Conn, Query, Params, FilterMap, Timeout) ->
 %% @see execute/5
 -spec execute(Conn, StatementRef, Params) -> Result | {error, not_prepared}
   when Conn :: connection(),
-       StatementRef :: atom() | integer(),
-       Params :: [term()],
+       StatementRef :: statement_ref(),
+       Params :: [query_param()],
        Result :: query_result().
 execute(Conn, StatementRef, Params) ->
-    execute(Conn, StatementRef, Params, no_filtermap_fun, default_timeout).
+    execute_helper(Conn, StatementRef, Params, no_filtermap_fun, default_timeout).
 
 %% @doc Executes a prepared statement.
 %% @see prepare/2
@@ -438,19 +456,19 @@ execute(Conn, StatementRef, Params) ->
 -spec execute(Conn, StatementRef, Params, FilterMap | Timeout) ->
     Result | {error, not_prepared}
   when Conn :: connection(),
-       StatementRef :: atom() | integer(),
-       Params :: [term()],
-       FilterMap :: no_filtermap_fun | query_filtermap_fun(),
-       Timeout :: default_timeout | timeout(),
+       StatementRef :: statement_ref(),
+       Params :: [query_param()],
+       FilterMap :: query_filtermap_fun(),
+       Timeout :: timeout(),
        Result :: query_result().
 execute(Conn, StatementRef, Params, Timeout) when Timeout == default_timeout;
                                                   is_integer(Timeout);
                                                   Timeout=:=infinity ->
-    execute(Conn, StatementRef, Params, no_filtermap_fun, Timeout);
+    execute_helper(Conn, StatementRef, Params, no_filtermap_fun, Timeout);
 execute(Conn, StatementRef, Params, FilterMap) when FilterMap == no_filtermap_fun;
                                                     is_function(FilterMap, 1);
                                                     is_function(FilterMap, 2) ->
-    execute(Conn, StatementRef, Params, FilterMap, default_timeout).
+    execute_helper(Conn, StatementRef, Params, FilterMap, default_timeout).
 
 %% @doc Executes a prepared statement.
 %%
@@ -472,12 +490,23 @@ execute(Conn, StatementRef, Params, FilterMap) when FilterMap == no_filtermap_fu
 -spec execute(Conn, StatementRef, Params, FilterMap, Timeout) ->
     Result | {error, not_prepared}
   when Conn :: connection(),
-       StatementRef :: atom() | integer(),
-       Params :: [term()],
+       StatementRef :: statement_ref(),
+       Params :: [query_param()],
+       FilterMap :: query_filtermap_fun(),
+       Timeout :: timeout(),
+       Result :: query_result().
+execute(Conn, StatementRef, Params, FilterMap, Timeout) ->
+    execute_helper(Conn, StatementRef, Params, FilterMap, Timeout).
+
+-spec execute_helper(Conn, StatementRef, Params, FilterMap, Timeout) ->
+    Result | {error, not_prepared}
+  when Conn :: connection(),
+       StatementRef :: statement_ref(),
+       Params :: [query_param()],
        FilterMap :: no_filtermap_fun | query_filtermap_fun(),
        Timeout :: default_timeout | timeout(),
        Result :: query_result().
-execute(Conn, StatementRef, Params, FilterMap, Timeout) ->
+execute_helper(Conn, StatementRef, Params, FilterMap, Timeout) ->
     case mysql_protocol:valid_params(Params) of
         true ->
             query_call(Conn,
@@ -490,8 +519,8 @@ execute(Conn, StatementRef, Params, FilterMap, Timeout) ->
 %% @see prepare/3
 -spec prepare(Conn, Query) -> {ok, StatementId} | {error, Reason}
   when Conn :: connection(),
-       Query :: iodata(),
-       StatementId :: integer(),
+       Query :: query(),
+       StatementId :: statement_id(),
        Reason :: server_reason().
 prepare(Conn, Query) ->
     gen_server:call(Conn, {prepare, Query}).
@@ -501,8 +530,8 @@ prepare(Conn, Query) ->
 %% @see prepare/2
 -spec prepare(Conn, Name, Query) -> {ok, Name} | {error, Reason}
   when Conn :: connection(),
-       Name :: atom(),
-       Query :: iodata(),
+       Name :: statement_name(),
+       Query :: query(),
        Reason :: server_reason().
 prepare(Conn, Name, Query) ->
     gen_server:call(Conn, {prepare, Name, Query}).
@@ -510,7 +539,7 @@ prepare(Conn, Name, Query) ->
 %% @doc Deallocates a prepared statement.
 -spec unprepare(Conn, StatementRef) -> ok | {error, Reason}
   when Conn :: connection(),
-       StatementRef :: atom() | integer(),
+       StatementRef :: statement_ref(),
        Reason :: server_reason() | not_prepared.
 unprepare(Conn, StatementRef) ->
     gen_server:call(Conn, {unprepare, StatementRef}).
@@ -550,15 +579,20 @@ in_transaction(Conn) ->
 
 %% @doc This function executes the functional object Fun as a transaction.
 %% @see transaction/4
--spec transaction(connection(), fun()) -> {atomic, term()} | {aborted, term()}.
+-spec transaction(Conn, TransactionFun) -> TransactionResult
+    when Conn :: connection(),
+         TransactionFun :: fun(() -> Result),
+         TransactionResult :: transaction_result(Result).
 transaction(Conn, Fun) ->
     transaction(Conn, Fun, [], infinity).
 
 %% @doc This function executes the functional object Fun as a transaction.
 %% @see transaction/4
--spec transaction(connection(), fun(), Retries) -> {atomic, term()} |
-                                                   {aborted, term()}
-    when Retries :: non_neg_integer() | infinity.
+-spec transaction(Conn, TransactionFun, Retries) -> TransactionResult
+    when Conn :: connection(),
+         TransactionFun :: fun(() -> Result),
+         Retries :: non_neg_integer() | infinity,
+         TransactionResult :: transaction_result(Result).
 transaction(Conn, Fun, Retries) ->
     transaction(Conn, Fun, [], Retries).
 
@@ -606,9 +640,12 @@ transaction(Conn, Fun, Retries) ->
 %%     <tr><td>`throw(Term)'</td><td>`{aborted, {throw, Term}}'</td></tr>
 %%   </tbody>
 %% </table>
--spec transaction(connection(), fun(), list(), Retries) -> {atomic, term()} |
-                                                           {aborted, term()}
-    when Retries :: non_neg_integer() | infinity.
+-spec transaction(Conn, TransactionFun, Args, Retries) -> TransactionResult
+    when Conn :: connection(),
+         TransactionFun :: fun((...) -> Result),
+         Args :: list(),
+         Retries :: non_neg_integer() | infinity,
+         TransactionResult :: transaction_result(Result).
 transaction(Conn, Fun, Args, Retries) when is_list(Args),
                                            is_function(Fun, length(Args)) ->
     %% The guard makes sure that we can apply Fun to Args. Any error we catch
@@ -743,9 +780,9 @@ change_user(Conn, Username, Password) ->
          Options :: [Option],
          Result :: ok,
          Option :: {database, iodata()}
-                 | {queries, [iodata()]}
+                 | {queries, [query()]}
                  | {prepare, [NamedStatement]},
-         NamedStatement :: {StatementName :: atom(), Statement :: iodata()}.
+         NamedStatement :: {StatementName :: statement_name(), Statement :: query()}.
 change_user(Conn, Username, Password, Options) ->
     case in_transaction(Conn) of
         true -> error(change_user_in_transaction);