PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/src/mysql/mysql_conn.erl

http://github.com/evanmiller/ChicagoBoss
Erlang | 918 lines | 589 code | 67 blank | 262 comment | 18 complexity | 20800c4ac17ec49a4e8f6f47884c3e2f MD5 | raw file
  1. %%%-------------------------------------------------------------------
  2. %%% File : mysql_conn.erl
  3. %%% Author : Fredrik Thulin <ft@it.su.se>
  4. %%% Descrip.: MySQL connection handler, handles de-framing of messages
  5. %%% received by the MySQL receiver process.
  6. %%% Created : 5 Aug 2005 by Fredrik Thulin <ft@it.su.se>
  7. %%% Modified: 11 Jan 2006 by Mickael Remond <mickael.remond@process-one.net>
  8. %%%
  9. %%% Note : All MySQL code was written by Magnus Ahltorp, originally
  10. %%% in the file mysql.erl - I just moved it here.
  11. %%%
  12. %%% Modified: 12 Sep 2006 by Yariv Sadan <yarivvv@gmail.com>
  13. %%% Added automatic type conversion between MySQL types and Erlang types
  14. %%% and different logging style.
  15. %%%
  16. %%% Modified: 23 Sep 2006 by Yariv Sadan <yarivvv@gmail.com>
  17. %%% Added transaction handling and prepared statement execution.
  18. %%%
  19. %%% Copyright (c) 2001-2004 Kungliga Tekniska Högskolan
  20. %%% See the file COPYING
  21. %%%
  22. %%%
  23. %%% This module handles a single connection to a single MySQL server.
  24. %%% You can use it stand-alone, or through the 'mysql' module if you
  25. %%% want to have more than one connection to the server, or
  26. %%% connections to different servers.
  27. %%%
  28. %%% To use it stand-alone, set up the connection with
  29. %%%
  30. %%% {ok, Pid} = mysql_conn:start(Host, Port, User, Password,
  31. %%% Database, LogFun)
  32. %%%
  33. %%% Host = string()
  34. %%% Port = integer()
  35. %%% User = string()
  36. %%% Password = string()
  37. %%% Database = string()
  38. %%% LogFun = undefined | (gives logging to console)
  39. %%% function() of arity 3 (Level, Fmt, Args)
  40. %%%
  41. %%% Note: In stand-alone mode you have to start Erlang crypto application by
  42. %%% yourself with crypto:start()
  43. %%%
  44. %%% and then make MySQL querys with
  45. %%%
  46. %%% Result = mysql_conn:fetch(Pid, Query, self())
  47. %%%
  48. %%% Result = {data, MySQLRes} |
  49. %%% {updated, MySQLRes} |
  50. %%% {error, MySQLRes}
  51. %%% Where: MySQLRes = #mysql_result
  52. %%%
  53. %%% Actual data can be extracted from MySQLRes by calling the following API
  54. %%% functions:
  55. %%% - on data received:
  56. %%% FieldInfo = mysql:get_result_field_info(MysqlRes)
  57. %%% AllRows = mysql:get_result_rows(MysqlRes)
  58. %%% with FieldInfo = list() of {Table, Field, Length, Name}
  59. %%% and AllRows = list() of list() representing records
  60. %%% - on update:
  61. %%% Affected= mysql:get_result_affected_rows(MysqlRes)
  62. %%% with Affected = integer()
  63. %%% - on error:
  64. %%% Reason = mysql:get_result_reason(MysqlRes)
  65. %%% with Reason = string()
  66. %%%-------------------------------------------------------------------
  67. -module(mysql_conn).
  68. %%--------------------------------------------------------------------
  69. %% External exports
  70. %%--------------------------------------------------------------------
  71. -export([start/8,
  72. start_link/8,
  73. fetch/3,
  74. fetch/4,
  75. execute/5,
  76. execute/6,
  77. transaction/3,
  78. transaction/4
  79. ]).
  80. %% private exports to be called only from the 'mysql' module
  81. -export([fetch_local/2,
  82. execute_local/3,
  83. get_pool_id/1
  84. ]).
  85. %%--------------------------------------------------------------------
  86. %% External exports (should only be used by the 'mysql_auth' module)
  87. %%--------------------------------------------------------------------
  88. -export([do_recv/3
  89. ]).
  90. -include("mysql.hrl").
  91. -record(state, {
  92. mysql_version,
  93. log_fun,
  94. recv_pid,
  95. socket,
  96. data,
  97. %% maps statement names to their versions
  98. prepares = gb_trees:empty(),
  99. %% the id of the connection pool to which this connection belongs
  100. pool_id
  101. }).
  102. -define(SECURE_CONNECTION, 32768).
  103. -define(MYSQL_QUERY_OP, 3).
  104. -define(DEFAULT_STANDALONE_TIMEOUT, 5000).
  105. -define(MYSQL_4_0, 40). %% Support for MySQL 4.0.x
  106. -define(MYSQL_4_1, 41). %% Support for MySQL 4.1.x et 5.0.x
  107. %% Used by transactions to get the state variable for this connection
  108. %% when bypassing the dispatcher.
  109. -define(STATE_VAR, mysql_connection_state).
  110. -define(Log(LogFun,Level,Msg),
  111. LogFun(?MODULE, ?LINE,Level,fun()-> {Msg,[]} end)).
  112. -define(Log2(LogFun,Level,Msg,Params),
  113. LogFun(?MODULE, ?LINE,Level,fun()-> {Msg,Params} end)).
  114. -define(L(Msg), io:format("~p:~b ~p ~n", [?MODULE, ?LINE, Msg])).
  115. %%====================================================================
  116. %% External functions
  117. %%====================================================================
  118. %%--------------------------------------------------------------------
  119. %% Function: start(Host, Port, User, Password, Database, LogFun)
  120. %% Function: start_link(Host, Port, User, Password, Database, LogFun)
  121. %% Host = string()
  122. %% Port = integer()
  123. %% User = string()
  124. %% Password = string()
  125. %% Database = string()
  126. %% LogFun = undefined | function() of arity 3
  127. %% Descrip.: Starts a mysql_conn process that connects to a MySQL
  128. %% server, logs in and chooses a database.
  129. %% Returns : {ok, Pid} | {error, Reason}
  130. %% Pid = pid()
  131. %% Reason = string()
  132. %%--------------------------------------------------------------------
  133. start(Host, Port, User, Password, Database, LogFun, Encoding, PoolId) ->
  134. ConnPid = self(),
  135. Pid = spawn(fun () ->
  136. init(Host, Port, User, Password, Database,
  137. LogFun, Encoding, PoolId, ConnPid)
  138. end),
  139. post_start(Pid, LogFun).
  140. start_link(Host, Port, User, Password, Database, LogFun, Encoding, PoolId) ->
  141. ConnPid = self(),
  142. Pid = spawn_link(fun () ->
  143. init(Host, Port, User, Password, Database,
  144. LogFun, Encoding, PoolId, ConnPid)
  145. end),
  146. post_start(Pid, LogFun).
  147. %% part of start/6 or start_link/6:
  148. post_start(Pid, LogFun) ->
  149. receive
  150. {mysql_conn, Pid, ok} ->
  151. {ok, Pid};
  152. {mysql_conn, Pid, {error, Reason}} ->
  153. {error, Reason};
  154. {mysql_conn, OtherPid, {error, Reason}} ->
  155. % Ignore error message from other processes. This handles the case
  156. % when mysql is shutdown and takes more than 5 secs to close the
  157. % listener socket.
  158. ?Log2(LogFun, debug, "Ignoring message from process ~p | Reason: ~p",
  159. [OtherPid, Reason]),
  160. post_start(Pid, LogFun);
  161. Unknown ->
  162. ?Log2(LogFun, error,
  163. "received unknown signal, exiting: ~p", [Unknown]),
  164. {error, "unknown signal received"}
  165. after 5000 ->
  166. {error, "timed out"}
  167. end.
  168. %%--------------------------------------------------------------------
  169. %% Function: fetch(Pid, Query, From)
  170. %% fetch(Pid, Query, From, Timeout)
  171. %% Pid = pid(), mysql_conn to send fetch-request to
  172. %% Queries = A single binary() query or a list of binary() queries.
  173. %% If a list is provided, the return value is the return
  174. %% of the last query, or the first query that has
  175. %% returned an error. If an error occurs, execution of
  176. %% the following queries is aborted.
  177. %% From = pid() or term(), use a From of self() when
  178. %% using this module for a single connection,
  179. %% or pass the gen_server:call/3 From argument if
  180. %% using a gen_server to do the querys (e.g. the
  181. %% mysql_dispatcher)
  182. %% Timeout = integer() | infinity, gen_server timeout value
  183. %% Descrip.: Send a query or a list of queries and wait for the result
  184. %% if running stand-alone (From = self()), but don't block
  185. %% the caller if we are not running stand-alone
  186. %% (From = gen_server From).
  187. %% Returns : ok | (non-stand-alone mode)
  188. %% {data, #mysql_result} | (stand-alone mode)
  189. %% {updated, #mysql_result} | (stand-alone mode)
  190. %% {error, #mysql_result} (stand-alone mode)
  191. %% FieldInfo = term()
  192. %% Rows = list() of [string()]
  193. %% Reason = term()
  194. %%--------------------------------------------------------------------
  195. fetch(Pid, Queries, From) ->
  196. fetch(Pid, Queries, From, ?DEFAULT_STANDALONE_TIMEOUT).
  197. fetch(Pid, Queries, From, Timeout) ->
  198. do_fetch(Pid, Queries, From, Timeout).
  199. execute(Pid, Name, Version, Params, From) ->
  200. execute(Pid, Name, Version, Params, From, ?DEFAULT_STANDALONE_TIMEOUT).
  201. execute(Pid, Name, Version, Params, From, Timeout) ->
  202. send_msg(Pid, {execute, Name, Version, Params, From}, From, Timeout).
  203. transaction(Pid, Fun, From) ->
  204. transaction(Pid, Fun, From, ?DEFAULT_STANDALONE_TIMEOUT).
  205. transaction(Pid, Fun, From, Timeout) ->
  206. send_msg(Pid, {transaction, Fun, From}, From, Timeout).
  207. get_pool_id(State) ->
  208. State#state.pool_id.
  209. %%====================================================================
  210. %% Internal functions
  211. %%====================================================================
  212. fetch_local(State, Query) ->
  213. do_query(State, Query).
  214. execute_local(State, Name, Params) ->
  215. case do_execute(State, Name, Params, undefined) of
  216. {ok, Res, State1} ->
  217. put(?STATE_VAR, State1),
  218. Res;
  219. Err ->
  220. Err
  221. end.
  222. %%--------------------------------------------------------------------
  223. %% Function: do_recv(LogFun, RecvPid, SeqNum)
  224. %% LogFun = undefined | function() with arity 3
  225. %% RecvPid = pid(), mysql_recv process
  226. %% SeqNum = undefined | integer()
  227. %% Descrip.: Wait for a frame decoded and sent to us by RecvPid.
  228. %% Either wait for a specific frame if SeqNum is an integer,
  229. %% or just any frame if SeqNum is undefined.
  230. %% Returns : {ok, Packet, Num} |
  231. %% {error, Reason}
  232. %% Reason = term()
  233. %%
  234. %% Note : Only to be used externally by the 'mysql_auth' module.
  235. %%--------------------------------------------------------------------
  236. do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun);
  237. LogFun == undefined,
  238. SeqNum == undefined ->
  239. receive
  240. {mysql_recv, RecvPid, data, Packet, Num} ->
  241. {ok, Packet, Num};
  242. {mysql_recv, RecvPid, closed, _E} ->
  243. {error, "mysql_recv: socket was closed"}
  244. end;
  245. do_recv(LogFun, RecvPid, SeqNum) when is_function(LogFun);
  246. LogFun == undefined,
  247. is_integer(SeqNum) ->
  248. ResponseNum = SeqNum + 1,
  249. receive
  250. {mysql_recv, RecvPid, data, Packet, ResponseNum} ->
  251. {ok, Packet, ResponseNum};
  252. {mysql_recv, RecvPid, closed, _E} ->
  253. {error, "mysql_recv: socket was closed"}
  254. end.
  255. do_fetch(Pid, Queries, From, Timeout) ->
  256. send_msg(Pid, {fetch, Queries, From}, From, Timeout).
  257. send_msg(Pid, Msg, From, Timeout) ->
  258. Self = self(),
  259. Pid ! Msg,
  260. case From of
  261. Self ->
  262. %% We are not using a mysql_dispatcher, await the response
  263. receive
  264. {fetch_result, Pid, Result} ->
  265. Result
  266. after Timeout ->
  267. {error, "message timed out"}
  268. end;
  269. _ ->
  270. %% From is gen_server From,
  271. %% Pid will do gen_server:reply() when it has an answer
  272. ok
  273. end.
  274. %%--------------------------------------------------------------------
  275. %% Function: init(Host, Port, User, Password, Database, LogFun,
  276. %% Parent)
  277. %% Host = string()
  278. %% Port = integer()
  279. %% User = string()
  280. %% Password = string()
  281. %% Database = string()
  282. %% LogFun = function() of arity 4
  283. %% Parent = pid() of process starting this mysql_conn
  284. %% Descrip.: Connect to a MySQL server, log in and chooses a database.
  285. %% Report result of this to Parent, and then enter loop() if
  286. %% we were successfull.
  287. %% Returns : void() | does not return
  288. %%--------------------------------------------------------------------
  289. init(Host, Port, User, Password, Database, LogFun, Encoding, PoolId, Parent) ->
  290. case mysql_recv:start_link(Host, Port, LogFun, self()) of
  291. {ok, RecvPid, Sock} ->
  292. case mysql_init(Sock, RecvPid, User, Password, LogFun) of
  293. {ok, Version} ->
  294. Db = iolist_to_binary(Database),
  295. case do_query(Sock, RecvPid, LogFun,
  296. <<"use ", Db/binary>>,
  297. Version) of
  298. {error, MySQLRes} ->
  299. ?Log2(LogFun, error,
  300. "mysql_conn: Failed changing to database "
  301. "~p : ~p",
  302. [Database,
  303. mysql:get_result_reason(MySQLRes)]),
  304. Parent ! {mysql_conn, self(),
  305. {error, failed_changing_database}};
  306. %% ResultType: data | updated
  307. {_ResultType, _MySQLRes} ->
  308. Parent ! {mysql_conn, self(), ok},
  309. case Encoding of
  310. undefined -> undefined;
  311. _ ->
  312. EncodingBinary = list_to_binary(atom_to_list(Encoding)),
  313. do_query(Sock, RecvPid, LogFun,
  314. <<"set names '", EncodingBinary/binary, "'">>,
  315. Version)
  316. end,
  317. State = #state{mysql_version=Version,
  318. recv_pid = RecvPid,
  319. socket = Sock,
  320. log_fun = LogFun,
  321. pool_id = PoolId,
  322. data = <<>>
  323. },
  324. loop(State)
  325. end;
  326. {error, _Reason} ->
  327. Parent ! {mysql_conn, self(), {error, login_failed}}
  328. end;
  329. E ->
  330. ?Log2(LogFun, error,
  331. "failed connecting to ~p:~p : ~p",
  332. [Host, Port, E]),
  333. Parent ! {mysql_conn, self(), {error, connect_failed}}
  334. end.
  335. %%--------------------------------------------------------------------
  336. %% Function: loop(State)
  337. %% State = state record()
  338. %% Descrip.: Wait for signals asking us to perform a MySQL query, or
  339. %% signals that the socket was closed.
  340. %% Returns : error | does not return
  341. %%--------------------------------------------------------------------
  342. loop(State) ->
  343. RecvPid = State#state.recv_pid,
  344. LogFun = State#state.log_fun,
  345. receive
  346. {fetch, Queries, From} ->
  347. send_reply(From, do_queries(State, Queries)),
  348. loop(State);
  349. {transaction, Fun, From} ->
  350. put(?STATE_VAR, State),
  351. Res = do_transaction(State, Fun),
  352. %% The transaction may have changed the state of this process
  353. %% if it has executed prepared statements. This would happen in
  354. %% mysql:execute.
  355. State1 = get(?STATE_VAR),
  356. send_reply(From, Res),
  357. loop(State1);
  358. {execute, Name, Version, Params, From} ->
  359. State1 =
  360. case do_execute(State, Name, Params, Version) of
  361. {error, _} = Err ->
  362. send_reply(From, Err),
  363. State;
  364. {ok, Result, NewState} ->
  365. send_reply(From, Result),
  366. NewState
  367. end,
  368. loop(State1);
  369. {mysql_recv, RecvPid, data, Packet, Num} ->
  370. ?Log2(LogFun, error,
  371. "received data when not expecting any -- "
  372. "ignoring it: {~p, ~p}", [Num, Packet]),
  373. loop(State);
  374. Unknown ->
  375. ?Log2(LogFun, error,
  376. "received unknown signal, exiting: ~p", [Unknown]),
  377. error
  378. end.
  379. %% GenSrvFrom is either a gen_server:call/3 From term(),
  380. %% or a pid if no gen_server was used to make the query
  381. send_reply(GenSrvFrom, Res) when is_pid(GenSrvFrom) ->
  382. %% The query was not sent using gen_server mechanisms
  383. GenSrvFrom ! {fetch_result, self(), Res};
  384. send_reply(GenSrvFrom, Res) ->
  385. gen_server:reply(GenSrvFrom, Res).
  386. do_query(State, Query) ->
  387. do_query(State#state.socket,
  388. State#state.recv_pid,
  389. State#state.log_fun,
  390. Query,
  391. State#state.mysql_version
  392. ).
  393. do_query(Sock, RecvPid, LogFun, Query, Version) ->
  394. Query1 = iolist_to_binary(Query),
  395. ?Log2(LogFun, debug, "fetch ~p (id ~p)", [Query1,RecvPid]),
  396. Packet = <<?MYSQL_QUERY_OP, Query1/binary>>,
  397. case do_send(Sock, Packet, 0, LogFun) of
  398. ok ->
  399. get_query_response(LogFun,RecvPid,
  400. Version);
  401. {error, Reason} ->
  402. Msg = io_lib:format("Failed sending data "
  403. "on socket : ~p",
  404. [Reason]),
  405. {error, Msg}
  406. end.
  407. do_queries(State, Queries) when not is_list(Queries) ->
  408. do_query(State, Queries);
  409. do_queries(State, Queries) ->
  410. do_queries(State#state.socket,
  411. State#state.recv_pid,
  412. State#state.log_fun,
  413. Queries,
  414. State#state.mysql_version
  415. ).
  416. %% Execute a list of queries, returning the response for the last query.
  417. %% If a query returns an error before the last query is executed, the
  418. %% loop is aborted and the error is returned.
  419. do_queries(Sock, RecvPid, LogFun, Queries, Version) ->
  420. catch
  421. lists:foldl(
  422. fun(Query, _LastResponse) ->
  423. case do_query(Sock, RecvPid, LogFun, Query, Version) of
  424. {error, _} = Err -> throw(Err);
  425. Res -> Res
  426. end
  427. end, ok, Queries).
  428. do_transaction(State, Fun) ->
  429. case do_query(State, <<"BEGIN">>) of
  430. {error, _} = Err ->
  431. {aborted, Err};
  432. _ ->
  433. case catch Fun() of
  434. error = Err -> rollback(State, Err);
  435. {error, _} = Err -> rollback(State, Err);
  436. {'EXIT', _} = Err -> rollback(State, Err);
  437. Res ->
  438. case do_query(State, <<"COMMIT">>) of
  439. {error, _} = Err ->
  440. rollback(State, {commit_error, Err});
  441. _ ->
  442. case Res of
  443. {atomic, _} -> Res;
  444. _ -> {atomic, Res}
  445. end
  446. end
  447. end
  448. end.
  449. rollback(State, Err) ->
  450. Res = do_query(State, <<"ROLLBACK">>),
  451. {aborted, {Err, {rollback_result, Res}}}.
  452. do_execute(State, Name, Params, ExpectedVersion) ->
  453. Res = case gb_trees:lookup(Name, State#state.prepares) of
  454. {value, Version} when Version == ExpectedVersion ->
  455. {ok, latest};
  456. {value, Version} ->
  457. mysql:get_prepared(Name, Version);
  458. none ->
  459. mysql:get_prepared(Name)
  460. end,
  461. case Res of
  462. {ok, latest} ->
  463. {ok, do_execute1(State, Name, Params), State};
  464. {ok, {Stmt, NewVersion}} ->
  465. prepare_and_exec(State, Name, NewVersion, Stmt, Params);
  466. {error, _} = Err ->
  467. Err
  468. end.
  469. prepare_and_exec(State, Name, Version, Stmt, Params) ->
  470. NameBin = atom_to_binary(Name),
  471. StmtBin = <<"PREPARE ", NameBin/binary, " FROM '",
  472. Stmt/binary, "'">>,
  473. case do_query(State, StmtBin) of
  474. {updated, _} ->
  475. State1 =
  476. State#state{
  477. prepares = gb_trees:enter(Name, Version,
  478. State#state.prepares)},
  479. {ok, do_execute1(State1, Name, Params), State1};
  480. {error, _} = Err ->
  481. Err;
  482. Other ->
  483. {error, {unexpected_result, Other}}
  484. end.
  485. do_execute1(State, Name, Params) ->
  486. Stmts = make_statements_for_execute(Name, Params),
  487. do_queries(State, Stmts).
  488. make_statements_for_execute(Name, []) ->
  489. NameBin = atom_to_binary(Name),
  490. [<<"EXECUTE ", NameBin/binary>>];
  491. make_statements_for_execute(Name, Params) ->
  492. NumParams = length(Params),
  493. ParamNums = lists:seq(1, NumParams),
  494. NameBin = atom_to_binary(Name),
  495. ParamNames =
  496. lists:foldl(
  497. fun(Num, Acc) ->
  498. ParamName = [$@ | integer_to_list(Num)],
  499. if Num == 1 ->
  500. ParamName ++ Acc;
  501. true ->
  502. [$, | ParamName] ++ Acc
  503. end
  504. end, [], lists:reverse(ParamNums)),
  505. ParamNamesBin = list_to_binary(ParamNames),
  506. ExecStmt = <<"EXECUTE ", NameBin/binary, " USING ",
  507. ParamNamesBin/binary>>,
  508. ParamVals = lists:zip(ParamNums, Params),
  509. Stmts = lists:foldl(
  510. fun({Num, Val}, Acc) ->
  511. NumBin = mysql:encode(Num, true),
  512. ValBin = mysql:encode(Val, true),
  513. [<<"SET @", NumBin/binary, "=", ValBin/binary>> | Acc]
  514. end, [ExecStmt], lists:reverse(ParamVals)),
  515. Stmts.
  516. atom_to_binary(Val) ->
  517. <<_:4/binary, Bin/binary>> = term_to_binary(Val),
  518. Bin.
  519. %%--------------------------------------------------------------------
  520. %% Function: mysql_init(Sock, RecvPid, User, Password, LogFun)
  521. %% Sock = term(), gen_tcp socket
  522. %% RecvPid = pid(), mysql_recv process
  523. %% User = string()
  524. %% Password = string()
  525. %% LogFun = undefined | function() with arity 3
  526. %% Descrip.: Try to authenticate on our new socket.
  527. %% Returns : ok | {error, Reason}
  528. %% Reason = string()
  529. %%--------------------------------------------------------------------
  530. mysql_init(Sock, RecvPid, User, Password, LogFun) ->
  531. case do_recv(LogFun, RecvPid, undefined) of
  532. {ok, Packet, InitSeqNum} ->
  533. {Version, Salt1, Salt2, Caps} = greeting(Packet, LogFun),
  534. AuthRes =
  535. case Caps band ?SECURE_CONNECTION of
  536. ?SECURE_CONNECTION ->
  537. mysql_auth:do_new_auth(
  538. Sock, RecvPid, InitSeqNum + 1,
  539. User, Password, Salt1, Salt2, LogFun);
  540. _ ->
  541. mysql_auth:do_old_auth(
  542. Sock, RecvPid, InitSeqNum + 1, User, Password,
  543. Salt1, LogFun)
  544. end,
  545. case AuthRes of
  546. {ok, <<0:8, _Rest/binary>>, _RecvNum} ->
  547. {ok,Version};
  548. {ok, <<255:8, Code:16/little, Message/binary>>, _RecvNum} ->
  549. ?Log2(LogFun, error, "init error ~p: ~p",
  550. [Code, binary_to_list(Message)]),
  551. {error, binary_to_list(Message)};
  552. {ok, RecvPacket, _RecvNum} ->
  553. ?Log2(LogFun, error,
  554. "init unknown error ~p",
  555. [binary_to_list(RecvPacket)]),
  556. {error, binary_to_list(RecvPacket)};
  557. {error, Reason} ->
  558. ?Log2(LogFun, error,
  559. "init failed receiving data : ~p", [Reason]),
  560. {error, Reason}
  561. end;
  562. {error, Reason} ->
  563. {error, Reason}
  564. end.
  565. %% part of mysql_init/4
  566. greeting(Packet, LogFun) ->
  567. <<Protocol:8, Rest/binary>> = Packet,
  568. {Version, Rest2} = asciz(Rest),
  569. <<_TreadID:32/little, Rest3/binary>> = Rest2,
  570. {Salt, Rest4} = asciz(Rest3),
  571. <<Caps:16/little, Rest5/binary>> = Rest4,
  572. <<ServerChar:16/binary-unit:8, Rest6/binary>> = Rest5,
  573. {Salt2, _Rest7} = asciz(Rest6),
  574. ?Log2(LogFun, debug,
  575. "greeting version ~p (protocol ~p) salt ~p caps ~p serverchar ~p"
  576. "salt2 ~p",
  577. [Version, Protocol, Salt, Caps, ServerChar, Salt2]),
  578. {normalize_version(Version, LogFun), Salt, Salt2, Caps}.
  579. %% part of greeting/2
  580. asciz(Data) when is_binary(Data) ->
  581. mysql:asciz_binary(Data, []);
  582. asciz(Data) when is_list(Data) ->
  583. {String, [0 | Rest]} = lists:splitwith(fun (C) ->
  584. C /= 0
  585. end, Data),
  586. {String, Rest}.
  587. %%--------------------------------------------------------------------
  588. %% Function: get_query_response(LogFun, RecvPid)
  589. %% LogFun = undefined | function() with arity 3
  590. %% RecvPid = pid(), mysql_recv process
  591. %% Version = integer(), Representing MySQL version used
  592. %% Descrip.: Wait for frames until we have a complete query response.
  593. %% Returns : {data, #mysql_result}
  594. %% {updated, #mysql_result}
  595. %% {error, #mysql_result}
  596. %% FieldInfo = list() of term()
  597. %% Rows = list() of [string()]
  598. %% AffectedRows = int()
  599. %% Reason = term()
  600. %%--------------------------------------------------------------------
  601. get_query_response(LogFun, RecvPid, Version) ->
  602. case do_recv(LogFun, RecvPid, undefined) of
  603. {ok, <<Fieldcount:8, Rest/binary>>, _} ->
  604. case Fieldcount of
  605. 0 ->
  606. %% No Tabular data
  607. <<AffectedRows:8, _Rest2/binary>> = Rest,
  608. {updated, #mysql_result{affectedrows=AffectedRows}};
  609. 255 ->
  610. <<_Code:16/little, Message/binary>> = Rest,
  611. {error, #mysql_result{error=Message}};
  612. _ ->
  613. %% Tabular data received
  614. case get_fields(LogFun, RecvPid, [], Version) of
  615. {ok, Fields} ->
  616. case get_rows(Fields, LogFun, RecvPid, []) of
  617. {ok, Rows} ->
  618. {data, #mysql_result{fieldinfo=Fields,
  619. rows=Rows}};
  620. {error, Reason} ->
  621. {error, #mysql_result{error=Reason}}
  622. end;
  623. {error, Reason} ->
  624. {error, #mysql_result{error=Reason}}
  625. end
  626. end;
  627. {error, Reason} ->
  628. {error, #mysql_result{error=Reason}}
  629. end.
  630. %%--------------------------------------------------------------------
  631. %% Function: get_fields(LogFun, RecvPid, [], Version)
  632. %% LogFun = undefined | function() with arity 3
  633. %% RecvPid = pid(), mysql_recv process
  634. %% Version = integer(), Representing MySQL version used
  635. %% Descrip.: Received and decode field information.
  636. %% Returns : {ok, FieldInfo} |
  637. %% {error, Reason}
  638. %% FieldInfo = list() of term()
  639. %% Reason = term()
  640. %%--------------------------------------------------------------------
  641. %% Support for MySQL 4.0.x:
  642. get_fields(LogFun, RecvPid, Res, ?MYSQL_4_0) ->
  643. case do_recv(LogFun, RecvPid, undefined) of
  644. {ok, Packet, _Num} ->
  645. case Packet of
  646. <<254:8>> ->
  647. {ok, lists:reverse(Res)};
  648. <<254:8, Rest/binary>> when size(Rest) < 8 ->
  649. {ok, lists:reverse(Res)};
  650. _ ->
  651. {Table, Rest} = get_with_length(Packet),
  652. {Field, Rest2} = get_with_length(Rest),
  653. {LengthB, Rest3} = get_with_length(Rest2),
  654. LengthL = size(LengthB) * 8,
  655. <<Length:LengthL/little>> = LengthB,
  656. {Type, Rest4} = get_with_length(Rest3),
  657. {_Flags, _Rest5} = get_with_length(Rest4),
  658. This = {Table,
  659. Field,
  660. Length,
  661. %% TODO: Check on MySQL 4.0 if types are specified
  662. %% using the same 4.1 formalism and could
  663. %% be expanded to atoms:
  664. Type},
  665. get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_0)
  666. end;
  667. {error, Reason} ->
  668. {error, Reason}
  669. end;
  670. %% Support for MySQL 4.1.x and 5.x:
  671. get_fields(LogFun, RecvPid, Res, ?MYSQL_4_1) ->
  672. case do_recv(LogFun, RecvPid, undefined) of
  673. {ok, Packet, _Num} ->
  674. case Packet of
  675. <<254:8>> ->
  676. {ok, lists:reverse(Res)};
  677. <<254:8, Rest/binary>> when size(Rest) < 8 ->
  678. {ok, lists:reverse(Res)};
  679. _ ->
  680. {_Catalog, Rest} = get_with_length(Packet),
  681. {_Database, Rest2} = get_with_length(Rest),
  682. {Table, Rest3} = get_with_length(Rest2),
  683. %% OrgTable is the real table name if Table is an alias
  684. {_OrgTable, Rest4} = get_with_length(Rest3),
  685. {Field, Rest5} = get_with_length(Rest4),
  686. %% OrgField is the real field name if Field is an alias
  687. {_OrgField, Rest6} = get_with_length(Rest5),
  688. <<_Metadata:8/little, _Charset:16/little,
  689. Length:32/little, Type:8/little,
  690. _Flags:16/little, _Decimals:8/little,
  691. _Rest7/binary>> = Rest6,
  692. This = {Table,
  693. Field,
  694. Length,
  695. get_field_datatype(Type)},
  696. get_fields(LogFun, RecvPid, [This | Res], ?MYSQL_4_1)
  697. end;
  698. {error, Reason} ->
  699. {error, Reason}
  700. end.
  701. %%--------------------------------------------------------------------
  702. %% Function: get_rows(N, LogFun, RecvPid, [])
  703. %% N = integer(), number of rows to get
  704. %% LogFun = undefined | function() with arity 3
  705. %% RecvPid = pid(), mysql_recv process
  706. %% Descrip.: Receive and decode a number of rows.
  707. %% Returns : {ok, Rows} |
  708. %% {error, Reason}
  709. %% Rows = list() of [string()]
  710. %%--------------------------------------------------------------------
  711. get_rows(Fields, LogFun, RecvPid, Res) ->
  712. case do_recv(LogFun, RecvPid, undefined) of
  713. {ok, Packet, _Num} ->
  714. case Packet of
  715. <<254:8, Rest/binary>> when size(Rest) < 8 ->
  716. {ok, lists:reverse(Res)};
  717. _ ->
  718. {ok, This} = get_row(Fields, Packet, []),
  719. get_rows(Fields, LogFun, RecvPid, [This | Res])
  720. end;
  721. {error, Reason} ->
  722. {error, Reason}
  723. end.
  724. %% part of get_rows/4
  725. get_row([], _Data, Res) ->
  726. {ok, lists:reverse(Res)};
  727. get_row([Field | OtherFields], Data, Res) ->
  728. {Col, Rest} = get_with_length(Data),
  729. This = case Col of
  730. null ->
  731. undefined;
  732. _ ->
  733. convert_type(Col, element(4, Field), element(3, Field))
  734. end,
  735. get_row(OtherFields, Rest, [This | Res]).
  736. get_with_length(<<251:8, Rest/binary>>) ->
  737. {null, Rest};
  738. get_with_length(<<252:8, Length:16/little, Rest/binary>>) ->
  739. split_binary(Rest, Length);
  740. get_with_length(<<253:8, Length:24/little, Rest/binary>>) ->
  741. split_binary(Rest, Length);
  742. get_with_length(<<254:8, Length:64/little, Rest/binary>>) ->
  743. split_binary(Rest, Length);
  744. get_with_length(<<Length:8, Rest/binary>>) when Length < 251 ->
  745. split_binary(Rest, Length).
  746. %%--------------------------------------------------------------------
  747. %% Function: do_send(Sock, Packet, SeqNum, LogFun)
  748. %% Sock = term(), gen_tcp socket
  749. %% Packet = binary()
  750. %% SeqNum = integer(), packet sequence number
  751. %% LogFun = undefined | function() with arity 3
  752. %% Descrip.: Send a packet to the MySQL server.
  753. %% Returns : result of gen_tcp:send/2
  754. %%--------------------------------------------------------------------
  755. do_send(Sock, Packet, SeqNum, _LogFun) when is_binary(Packet), is_integer(SeqNum) ->
  756. Data = <<(size(Packet)):24/little, SeqNum:8, Packet/binary>>,
  757. gen_tcp:send(Sock, Data).
  758. %%--------------------------------------------------------------------
  759. %% Function: normalize_version(Version, LogFun)
  760. %% Version = string()
  761. %% LogFun = undefined | function() with arity 3
  762. %% Descrip.: Return a flag corresponding to the MySQL version used.
  763. %% The protocol used depends on this flag.
  764. %% Returns : Version = string()
  765. %%--------------------------------------------------------------------
  766. normalize_version([$4,$.,$0|_T], LogFun) ->
  767. ?Log(LogFun, debug, "switching to MySQL 4.0.x protocol."),
  768. ?MYSQL_4_0;
  769. normalize_version([$4,$.,$1|_T], _LogFun) ->
  770. ?MYSQL_4_1;
  771. normalize_version([$5|_T], _LogFun) ->
  772. %% MySQL version 5.x protocol is compliant with MySQL 4.1.x:
  773. ?MYSQL_4_1;
  774. normalize_version(_Other, LogFun) ->
  775. ?Log(LogFun, error, "MySQL version not supported: MySQL Erlang module "
  776. "might not work correctly."),
  777. %% Error, but trying the oldest protocol anyway:
  778. ?MYSQL_4_0.
  779. %%--------------------------------------------------------------------
  780. %% Function: get_field_datatype(DataType)
  781. %% DataType = integer(), MySQL datatype
  782. %% Descrip.: Return MySQL field datatype as description string
  783. %% Returns : String, MySQL datatype
  784. %%--------------------------------------------------------------------
  785. get_field_datatype(0) -> 'DECIMAL';
  786. get_field_datatype(1) -> 'TINY';
  787. get_field_datatype(2) -> 'SHORT';
  788. get_field_datatype(3) -> 'LONG';
  789. get_field_datatype(4) -> 'FLOAT';
  790. get_field_datatype(5) -> 'DOUBLE';
  791. get_field_datatype(6) -> 'NULL';
  792. get_field_datatype(7) -> 'TIMESTAMP';
  793. get_field_datatype(8) -> 'LONGLONG';
  794. get_field_datatype(9) -> 'INT24';
  795. get_field_datatype(10) -> 'DATE';
  796. get_field_datatype(11) -> 'TIME';
  797. get_field_datatype(12) -> 'DATETIME';
  798. get_field_datatype(13) -> 'YEAR';
  799. get_field_datatype(14) -> 'NEWDATE';
  800. get_field_datatype(246) -> 'NEWDECIMAL';
  801. get_field_datatype(247) -> 'ENUM';
  802. get_field_datatype(248) -> 'SET';
  803. get_field_datatype(249) -> 'TINYBLOB';
  804. get_field_datatype(250) -> 'MEDIUM_BLOG';
  805. get_field_datatype(251) -> 'LONG_BLOG';
  806. get_field_datatype(252) -> 'BLOB';
  807. get_field_datatype(253) -> 'VAR_STRING';
  808. get_field_datatype(254) -> 'STRING';
  809. get_field_datatype(255) -> 'GEOMETRY'.
  810. % HACK EMM
  811. convert_type(0, 'TINY', 1) ->
  812. false;
  813. convert_type(1, 'TINY', 1) ->
  814. true;
  815. convert_type(Val, ColType, _Length) ->
  816. case ColType of
  817. T when T == 'TINY';
  818. T == 'SHORT';
  819. T == 'LONG';
  820. T == 'LONGLONG';
  821. T == 'INT24';
  822. T == 'YEAR' ->
  823. list_to_integer(binary_to_list(Val));
  824. T when T == 'TIMESTAMP';
  825. T == 'DATETIME' ->
  826. {ok, [Year, Month, Day, Hour, Minute, Second], _Leftovers} =
  827. io_lib:fread("~d-~d-~d ~d:~d:~d", binary_to_list(Val)),
  828. {datetime, {{Year, Month, Day}, {Hour, Minute, Second}}};
  829. 'TIME' ->
  830. {ok, [Hour, Minute, Second], _Leftovers} =
  831. io_lib:fread("~d:~d:~d", binary_to_list(Val)),
  832. {time, {Hour, Minute, Second}};
  833. 'DATE' ->
  834. {ok, [Year, Month, Day], _Leftovers} =
  835. io_lib:fread("~d-~d-~d", binary_to_list(Val)),
  836. {date, {Year, Month, Day}};
  837. T when T == 'DECIMAL';
  838. T == 'NEWDECIMAL';
  839. T == 'FLOAT';
  840. T == 'DOUBLE' ->
  841. {ok, [Num], _Leftovers} =
  842. case io_lib:fread("~f", binary_to_list(Val)) of
  843. {error, _} ->
  844. io_lib:fread("~d", binary_to_list(Val));
  845. Res ->
  846. Res
  847. end,
  848. Num;
  849. _Other ->
  850. Val
  851. end.