123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- %% 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/>.
- %% @doc API for communicating with MySQL databases.
- %%
- %% Most of the functions are wrappers for `gen_server' calls. The
- %% `connection()' type is the same as returned by `gen_server:start_link/2,3'.
- -module(mysql).
- -export([start_link/1, query/2, execute/3, prepare/2, prepare/3, unprepare/2,
- warning_count/1, affected_rows/1, autocommit/1, insert_id/1,
- in_transaction/1,
- transaction/2, transaction/3]).
- -export_type([connection/0]).
- %% A connection is a ServerRef as in gen_server:call/2,3.
- -type connection() :: Name :: atom() |
- {Name :: atom(), Node :: atom()} |
- {global, GlobalName :: term()} |
- {via, Module :: atom(), ViaName :: term()} |
- pid().
- %% MySQL error with the codes and message returned from the server.
- -type reason() :: {Code :: integer(), SQLState :: binary(),
- Message :: binary()}.
- %% @doc Starts a connection gen_server process and connects to a database. To
- %% disconnect just do `exit(Pid, normal)'.
- %%
- %% 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.
- -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}
- when Conn :: connection(),
- Query :: iodata(),
- ColumnNames :: [binary()],
- Rows :: [[term()]],
- Reason :: reason().
- query(Conn, Query) ->
- gen_server:call(Conn, {query, Query}).
- %% @doc Executes a prepared statement.
- %% @see prepare/2
- execute(Conn, StatementId, Args) ->
- gen_server:call(Conn, {execute, StatementId, Args}).
- %% @doc Creates a prepared statement from the passed query.
- %% @see execute/3
- -spec prepare(Conn :: connection(), Query :: iodata()) ->
- {ok, StatementId :: integer()} | {error, Reason :: reason()}.
- prepare(Conn, Query) ->
- gen_server:call(Conn, {prepare, Query}).
- %% @doc Creates a prepared statement from the passed query and associates it
- %% with the given name.
- %% @see execute/3
- -spec prepare(Conn :: connection(), Name :: term(), Query :: iodata()) ->
- {ok, Name :: term()} | {error, Reason :: reason()}.
- prepare(Conn, Name, Query) ->
- gen_server:call(Conn, {prepare, Name, Query}).
- %% @doc Deallocates a prepared statement.
- %% @see prepare/3
- -spec unprepare(Conn :: connection(), StatementRef :: term()) ->
- ok | {error, not_prepared} | {error, Reason :: reason()}.
- unprepare(Conn, StatementRef) ->
- gen_server:call(Conn, {unprepare, StatementRef}).
- %% @doc Returns the number of warnings generated by the last query/2 or
- %% execute/3 calls.
- -spec warning_count(connection()) -> integer().
- warning_count(Conn) ->
- gen_server:call(Conn, warning_count).
- %% @doc Returns the number of inserted, updated and deleted rows of the last
- %% executed query or prepared statement.
- -spec affected_rows(connection()) -> integer().
- affected_rows(Conn) ->
- gen_server:call(Conn, affected_rows).
- %% @doc Returns true if auto-commit is enabled and false otherwise.
- -spec autocommit(connection()) -> boolean().
- autocommit(Conn) ->
- gen_server:call(Conn, autocommit).
- %% @doc Returns the last insert-id.
- -spec insert_id(connection()) -> integer().
- 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, []).
- %% @doc This function executes the functional object Fun with arguments Args as
- %% a transaction.
- %%
- %% The semantics are the same as for mnesia's transactions.
- %%
- %% The Fun must be a function and Args must be a list with the same length
- %% as the arity of Fun.
- %%
- %% Current limitations:
- %%
- %% <ul>
- %% <li>Transactions cannot be nested</li>
- %% <li>They are not automatically restarted when deadlocks are detected.</li>
- %% </ul>
- %%
- %% If an exception occurs within Fun, the exception is caught and `{aborted,
- %% Reason}' is returned. The value of `Reason' depends on the class of the
- %% exception.
- %%
- %% <table>
- %% <thead>
- %% <tr><th>Class of exception</th><th>Return value</th></tr>
- %% </thead>
- %% <tbody>
- %% <tr>
- %% <td>`error' with reason `ErrorReason'</td>
- %% <td>`{aborted, {ErrorReason, Stack}}'</td>
- %% </tr>
- %% <tr><td>`exit(Term)'</td><td>`{aborted, Term}'</td></tr>
- %% <tr><td>`throw(Term)'</td><td>`{aborted, {throw, Term}}'</td></tr>
- %% </tbody>
- %% </table>
- %%
- %% 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),
- is_function(Fun, length(Args)) ->
- %% The guard makes sure that we can apply Fun to Args. Any error we catch
- %% in the try-catch are actual errors that occurred in Fun.
- ok = query(Conn, <<"BEGIN">>),
- try apply(Fun, Args) of
- ResultOfFun ->
- %% We must be able to rollback. Otherwise let's go mad.
- ok = query(Conn, <<"COMMIT">>),
- {atomic, ResultOfFun}
- catch
- Class:Reason ->
- %% We must be able to rollback. Otherwise let's go mad.
- ok = query(Conn, <<"ROLLBACK">>),
- %% These forms for throw, error and exit mirror Mnesia's behaviour.
- Aborted = case Class of
- throw -> {throw, Reason};
- error -> {Reason, erlang:get_stacktrace()};
- exit -> Reason
- end,
- {aborted, Aborted}
- end.
|