%% 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 . %% @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: %% %% %% %% 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. %% %% %% %% %% %% %% %% %% %% %% %% %% %%
Class of exceptionReturn value
`error' with reason `ErrorReason'`{aborted, {ErrorReason, Stack}}'
`exit(Term)'`{aborted, Term}'
`throw(Term)'`{aborted, {throw, Term}}'
%% %% 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.