mysql_connection.erl 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. %% MySQL/OTP – a MySQL driver for Erlang/OTP
  2. %% Copyright (C) 2014 Viktor Söderqvist
  3. %%
  4. %% This program is free software: you can redistribute it and/or modify
  5. %% it under the terms of the GNU General Public License as published by
  6. %% the Free Software Foundation, either version 3 of the License, or
  7. %% (at your option) any later version.
  8. %%
  9. %% This program is distributed in the hope that it will be useful,
  10. %% but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. %% GNU General Public License for more details.
  13. %%
  14. %% You should have received a copy of the GNU General Public License
  15. %% along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. %% A mysql connection implemented as a gen_server. This is a gen_server callback
  17. %% module only. The API functions are located in the mysql module.
  18. -module(mysql_connection).
  19. -behaviour(gen_server).
  20. -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
  21. code_change/3]).
  22. %% Some defaults
  23. -define(default_host, "localhost").
  24. -define(default_port, 3306).
  25. -define(default_user, <<>>).
  26. -define(default_password, <<>>).
  27. -define(default_timeout, infinity).
  28. -include("records.hrl").
  29. %% Gen_server state
  30. -record(state, {socket, timeout = infinity, affected_rows = 0, status = 0,
  31. warning_count = 0, insert_id = 0}).
  32. %% A tuple representing a MySQL server error, typically returned in the form
  33. %% {error, reason()}.
  34. -type reason() :: {Code :: integer(), SQLState :: binary(), Msg :: binary()}.
  35. init(Opts) ->
  36. %% Connect
  37. Host = proplists:get_value(host, Opts, ?default_host),
  38. Port = proplists:get_value(port, Opts, ?default_port),
  39. User = proplists:get_value(user, Opts, ?default_user),
  40. Password = proplists:get_value(password, Opts, ?default_password),
  41. Database = proplists:get_value(database, Opts, undefined),
  42. Timeout = proplists:get_value(timeout, Opts, ?default_timeout),
  43. %% Connect socket
  44. SockOpts = [{active, false}, binary, {packet, raw}],
  45. {ok, Socket} = gen_tcp:connect(Host, Port, SockOpts),
  46. %% Exchange handshake communication.
  47. SendFun = fun (Data) -> gen_tcp:send(Socket, Data) end,
  48. RecvFun = fun (Size) -> gen_tcp:recv(Socket, Size, Timeout) end,
  49. Result = mysql_protocol:handshake(User, Password, Database, SendFun,
  50. RecvFun),
  51. case Result of
  52. #ok{} = OK ->
  53. State = #state{socket = Socket, timeout = Timeout},
  54. State1 = update_state(State, OK),
  55. {ok, State1};
  56. #error{} = E ->
  57. {stop, error_to_reason(E)}
  58. end.
  59. handle_call({query, Query}, _From, State) when is_binary(Query);
  60. is_list(Query) ->
  61. #state{socket = Socket, timeout = Timeout} = State,
  62. SendFun = fun (Data) -> gen_tcp:send(Socket, Data) end,
  63. RecvFun = fun (Size) -> gen_tcp:recv(Socket, Size, Timeout) end,
  64. Rec = mysql_protocol:query(Query, SendFun, RecvFun),
  65. State1 = update_state(State, Rec),
  66. case Rec of
  67. #ok{} ->
  68. {reply, ok, State1};
  69. #error{} = E ->
  70. {reply, {error, error_to_reason(E)}, State1};
  71. #text_resultset{column_definitions = ColDefs, rows = Rows} ->
  72. Names = [Def#column_definition.name || Def <- ColDefs],
  73. Rows1 = decode_text_rows(ColDefs, Rows),
  74. {reply, {ok, Names, Rows1}, State1}
  75. end;
  76. handle_call(warning_count, _From, State) ->
  77. {reply, State#state.warning_count, State};
  78. handle_call(insert_id, _From, State) ->
  79. {reply, State#state.insert_id, State};
  80. handle_call(status_flags, _From, State) ->
  81. %% Bitmask of status flags from the last ok packet, etc.
  82. {reply, State#state.status, State}.
  83. handle_cast(_, _) -> todo.
  84. handle_info(_, _) -> todo.
  85. terminate(_, _) -> todo.
  86. code_change(_, _, _) -> todo.
  87. %% --- Helpers ---
  88. %% @doc Produces a tuple to return when an error needs to be returned to in the
  89. %% public API.
  90. -spec error_to_reason(#error{}) -> reason().
  91. error_to_reason(#error{code = Code, state = State, msg = Msg}) ->
  92. {Code, State, Msg}.
  93. %% @doc Updates a state with information from a response.
  94. -spec update_state(#state{}, #ok{} | #eof{} | any()) -> #state{}.
  95. update_state(State, #ok{status = S, affected_rows = R,
  96. insert_id = Id, warning_count = W}) ->
  97. State#state{status = S, affected_rows = R, insert_id = Id,
  98. warning_count = W};
  99. update_state(State, #eof{status = S, warning_count = W}) ->
  100. State#state{status = S, warning_count = W, insert_id = 0,
  101. affected_rows = 0};
  102. update_state(State, _Other) ->
  103. %% This includes errors, resultsets, etc.
  104. %% Reset warnings, etc. (Note: We don't reset 'status'.)
  105. State#state{warning_count = 0, insert_id = 0, affected_rows = 0}.
  106. %% @doc Uses a list of column definitions to decode rows returned in the text
  107. %% protocol. Returns the rows with values as for their type their appropriate
  108. %% Erlang terms.
  109. decode_text_rows(ColDefs, Rows) ->
  110. [decode_text_row_acc(ColDefs, Row, []) || Row <- Rows].
  111. decode_text_row_acc([#column_definition{type = T} | Defs], [V | Vs], Acc) ->
  112. Term = mysql_text_protocol:text_to_term(T, V),
  113. decode_text_row_acc(Defs, Vs, [Term | Acc]);
  114. decode_text_row_acc([], [], Acc) ->
  115. lists:reverse(Acc).