mysql_tests.erl 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  1. %% MySQL/OTP – MySQL client library for Erlang/OTP
  2. %% Copyright (C) 2014-2016 Viktor Söderqvist
  3. %% 2017 Piotr Nosek
  4. %%
  5. %% This file is part of MySQL/OTP.
  6. %%
  7. %% MySQL/OTP is free software: you can redistribute it and/or modify it under
  8. %% the terms of the GNU Lesser General Public License as published by the Free
  9. %% Software Foundation, either version 3 of the License, or (at your option)
  10. %% any later version.
  11. %%
  12. %% This program is distributed in the hope that it will be useful, but WITHOUT
  13. %% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14. %% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  15. %% more details.
  16. %%
  17. %% You should have received a copy of the GNU Lesser General Public License
  18. %% along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. %% @doc This module performs test to an actual database.
  20. -module(mysql_tests).
  21. -include_lib("eunit/include/eunit.hrl").
  22. -define(user, "otptest").
  23. -define(password, "otptest").
  24. -define(ssl_user, "otptestssl").
  25. -define(ssl_password, "otptestssl").
  26. %% We need to set a the SQL mode so it is consistent across MySQL versions
  27. %% and distributions.
  28. -define(SQL_MODE, <<"NO_ENGINE_SUBSTITUTION">>).
  29. -define(create_table_t, <<"CREATE TABLE t ("
  30. " id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,"
  31. " bl BLOB,"
  32. " tx TEXT NOT NULL," %% No default value
  33. " f FLOAT,"
  34. " d DOUBLE,"
  35. " dc DECIMAL(5,3),"
  36. " y YEAR,"
  37. " ti TIME,"
  38. " ts TIMESTAMP,"
  39. " da DATE,"
  40. " c CHAR(2)"
  41. ") ENGINE=InnoDB">>).
  42. connect_synchronous_test() ->
  43. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  44. {connect_mode, synchronous}]),
  45. ?assert(mysql:is_connected(Pid)),
  46. mysql:stop(Pid),
  47. ok.
  48. connect_asynchronous_successful_test() ->
  49. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  50. {connect_mode, asynchronous}]),
  51. ?assert(mysql:is_connected(Pid)),
  52. mysql:stop(Pid),
  53. ok.
  54. connect_asynchronous_failing_test() ->
  55. process_flag(trap_exit, true),
  56. {ok, Ret, _Logged} = error_logger_acc:capture(
  57. fun () ->
  58. {ok, Pid} = mysql:start_link([{user, "dummy"}, {password, "junk"},
  59. {connect_mode, asynchronous}]),
  60. receive
  61. {'EXIT', Pid, {error, {1045, <<"28000">>, _}}} -> ok
  62. after 1000 ->
  63. error(no_exit_message)
  64. end
  65. end
  66. ),
  67. ?assertEqual(ok, Ret),
  68. process_flag(trap_exit, false),
  69. ok.
  70. connect_lazy_test() ->
  71. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  72. {connect_mode, lazy}]),
  73. ?assertNot(mysql:is_connected(Pid)),
  74. {ok, [<<"1">>], [[1]]} = mysql:query(Pid, <<"SELECT 1">>),
  75. ?assert(mysql:is_connected(Pid)),
  76. mysql:stop(Pid),
  77. ok.
  78. failing_connect_test() ->
  79. process_flag(trap_exit, true),
  80. {ok, Ret, Logged} = error_logger_acc:capture(
  81. fun () ->
  82. mysql:start_link([{user, "dummy"}, {password, "junk"}])
  83. end),
  84. ?assertMatch([_|_], Logged), % some errors logged
  85. {error, Error} = Ret,
  86. case Error of
  87. {1045, <<"28000">>, <<"Access denie", _/binary>>} ->
  88. ok; % MySQL 5.x, etc.
  89. {1251, <<"08004">>, <<"Client does not support authentication "
  90. "protocol requested by server; consider "
  91. "upgrading MariaDB client">>} ->
  92. ok % MariaDB 10.3.13
  93. end,
  94. receive
  95. {'EXIT', _Pid, Error} -> ok
  96. after 1000 ->
  97. error(no_exit_message)
  98. end,
  99. process_flag(trap_exit, false).
  100. successful_connect_test() ->
  101. %% A connection with a registered name and execute initial queries and
  102. %% create prepared statements.
  103. Pid = common_basic_check([{user, ?user}, {password, ?password}]),
  104. %% Test some gen_server callbacks not tested elsewhere
  105. State = get_state(Pid),
  106. ?assertMatch({ok, State}, mysql_conn:code_change("0.1.0", State, [])),
  107. ?assertMatch({error, _}, mysql_conn:code_change("2.0.0", unknown_state, [])),
  108. common_conn_close().
  109. common_basic_check(ExtraOpts) ->
  110. Options = [{name, {local, tardis}},
  111. {queries, ["SET @foo = 'bar'", "SELECT 1",
  112. "SELECT 1; SELECT 2"]},
  113. {prepare, [{foo, "SELECT @foo"}]} | ExtraOpts],
  114. {ok, Pid} = mysql:start_link(Options),
  115. %% Check that queries and prepare has been done.
  116. ?assertEqual({ok, [<<"@foo">>], [[<<"bar">>]]},
  117. mysql:execute(Pid, foo, [])),
  118. Pid.
  119. common_conn_close() ->
  120. Pid = whereis(tardis),
  121. process_flag(trap_exit, true),
  122. mysql:stop(Pid),
  123. receive
  124. {'EXIT', Pid, normal} -> ok
  125. after
  126. 5000 -> error({cant_stop_connection, Pid})
  127. end,
  128. process_flag(trap_exit, false).
  129. exit_normal_test() ->
  130. Options = [{user, ?user}, {password, ?password}],
  131. {ok, Pid} = mysql:start_link(Options),
  132. {ok, ok, LoggedErrors} = error_logger_acc:capture(fun () ->
  133. %% Stop the connection without noise, errors or messages
  134. mysql:stop(Pid),
  135. receive
  136. UnexpectedExitMessage -> UnexpectedExitMessage
  137. after 0 ->
  138. ok
  139. end
  140. end),
  141. %% Check that we got nothing in the error log.
  142. ?assertEqual([], LoggedErrors).
  143. server_disconnect_test() ->
  144. process_flag(trap_exit, true),
  145. Options = [{user, ?user}, {password, ?password}],
  146. {ok, Pid} = mysql:start_link(Options),
  147. {ok, ok, _LoggedErrors} = error_logger_acc:capture(fun () ->
  148. %% Make the server close the connection after 1 second of inactivity.
  149. ok = mysql:query(Pid, <<"SET SESSION wait_timeout = 1">>),
  150. receive
  151. {'EXIT', Pid, normal} -> ok
  152. after 2000 ->
  153. no_exit_message
  154. end
  155. end),
  156. process_flag(trap_exit, false),
  157. ?assertExit(noproc, mysql:stop(Pid)).
  158. tcp_error_test() ->
  159. process_flag(trap_exit, true),
  160. Options = [{user, ?user}, {password, ?password}],
  161. {ok, Pid} = mysql:start_link(Options),
  162. {ok, ok, LoggedErrors} = error_logger_acc:capture(fun () ->
  163. %% Simulate a tcp error by sending a message. (Is there a better way?)
  164. Pid ! {tcp_error, dummy_socket, tcp_reason},
  165. receive
  166. {'EXIT', Pid, {tcp_error, tcp_reason}} -> ok
  167. after 1000 ->
  168. error(no_exit_message)
  169. end
  170. end),
  171. process_flag(trap_exit, false),
  172. %% Check that we got the expected crash report in the error log.
  173. [{error, Msg1}, {error, Msg2}, {error_report, CrashReport}] = LoggedErrors,
  174. %% "Connection Id 24 closing with reason: tcp_closed"
  175. ?assert(lists:prefix("Connection Id", Msg1)),
  176. ExpectedPrefix = io_lib:format("** Generic server ~p terminating", [Pid]),
  177. ?assert(lists:prefix(lists:flatten(ExpectedPrefix), Msg2)),
  178. ?assertMatch({crash_report, _}, CrashReport).
  179. keep_alive_test() ->
  180. %% Let the connection send a few pings.
  181. process_flag(trap_exit, true),
  182. Options = [{user, ?user}, {password, ?password}, {keep_alive, 20}],
  183. {ok, Pid} = mysql:start_link(Options),
  184. receive after 70 -> ok end,
  185. State = get_state(Pid),
  186. [state, _Version, _ConnectionId, Socket | _] = tuple_to_list(State),
  187. {ok, ExitMessage, _LoggedErrors} = error_logger_acc:capture(fun () ->
  188. gen_tcp:close(Socket),
  189. receive
  190. Message -> Message
  191. after 1000 ->
  192. ping_didnt_crash_connection
  193. end
  194. end),
  195. process_flag(trap_exit, false),
  196. ?assertMatch({'EXIT', Pid, _Reason}, ExitMessage),
  197. ?assertExit(noproc, mysql:stop(Pid)).
  198. reset_connection_test() ->
  199. %% Ignored test with MySQL earlier than 5.7
  200. Options = [{user, ?user}, {password, ?password}, {keep_alive, true}],
  201. {ok, Pid} = mysql:start_link(Options),
  202. ok = mysql:query(Pid, <<"CREATE DATABASE otptest">>),
  203. ok = mysql:query(Pid, <<"USE otptest">>),
  204. ok = mysql:query(Pid, <<"SET autocommit = 1">>),
  205. ok = mysql:query(Pid, ?create_table_t),
  206. ok = mysql:query(Pid, <<"INSERT INTO t (id, tx) VALUES (1, 'text 1')">>),
  207. ?assertEqual(1, mysql:insert_id(Pid)), %% auto_increment starts from 1
  208. case mysql:reset_connection(Pid) of
  209. ok ->
  210. ?assertEqual(0, mysql:insert_id(Pid)); %% insertid reset to 0;
  211. _Error ->
  212. ?assertEqual(1, mysql:insert_id(Pid)) %% reset failed
  213. end,
  214. mysql:stop(Pid),
  215. ok.
  216. unix_socket_test() ->
  217. try
  218. list_to_integer(erlang:system_info(otp_release))
  219. of
  220. %% Supported in OTP >= 19
  221. OtpRelease when OtpRelease >= 19 ->
  222. %% Get socket file to use
  223. {ok, Pid1} = mysql:start_link([{user, ?user},
  224. {password, ?password}]),
  225. {ok, [<<"@@socket">>], [SockFile]} = mysql:query(Pid1,
  226. "SELECT @@socket"),
  227. mysql:stop(Pid1),
  228. %% Connect through unix socket
  229. case mysql:start_link([{host, {local, SockFile}},
  230. {user, ?user}, {password, ?password}]) of
  231. {ok, Pid2} ->
  232. ?assertEqual({ok, [<<"1">>], [[1]]},
  233. mysql:query(Pid2, <<"SELECT 1">>)),
  234. mysql:stop(Pid2);
  235. {error, eafnosupport} ->
  236. error_logger:info_msg("Skipping unix socket test. "
  237. "Not supported on this OS.~n")
  238. end;
  239. OtpRelease ->
  240. error_logger:info_msg("Skipping unix socket test. Current OTP "
  241. "release is ~B. Required release is >= 19.~n",
  242. [OtpRelease])
  243. catch
  244. error:badarg ->
  245. error_logger:info_msg("Skipping unix socket tests. Current OTP "
  246. "release could not be determined.~n")
  247. end.
  248. connect_queries_failure_test() ->
  249. process_flag(trap_exit, true),
  250. {ok, Ret, Logged} = error_logger_acc:capture(
  251. fun () ->
  252. mysql:start_link([{user, ?user}, {password, ?password},
  253. {queries, ["foo"]}])
  254. end),
  255. ?assertMatch([{error_report, {crash_report, _}}], Logged),
  256. {error, Reason} = Ret,
  257. receive
  258. {'EXIT', _Pid, Reason} -> ok
  259. after 1000 ->
  260. exit(no_exit_message)
  261. end,
  262. process_flag(trap_exit, false).
  263. connect_prepare_failure_test() ->
  264. process_flag(trap_exit, true),
  265. {ok, Ret, Logged} = error_logger_acc:capture(
  266. fun () ->
  267. mysql:start_link([{user, ?user}, {password, ?password},
  268. {prepare, [{foo, "foo"}]}])
  269. end),
  270. ?assertMatch([{error_report, {crash_report, _}}], Logged),
  271. {error, Reason} = Ret,
  272. ?assertMatch({1064, <<"42000">>, <<"You have an erro", _/binary>>}, Reason),
  273. receive
  274. {'EXIT', _Pid, Reason} -> ok
  275. after 1000 ->
  276. exit(no_exit_message)
  277. end,
  278. process_flag(trap_exit, false).
  279. %% For R16B where sys:get_state/1 is not available.
  280. get_state(Process) ->
  281. {status,_,_,[_,_,_,_,Misc]} = sys:get_status(Process),
  282. hd([State || {data,[{"State", State}]} <- Misc]).
  283. query_test_() ->
  284. {setup,
  285. fun () ->
  286. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  287. {log_warnings, false},
  288. {keep_alive, true}]),
  289. ok = mysql:query(Pid, <<"DROP DATABASE IF EXISTS otptest">>),
  290. ok = mysql:query(Pid, <<"CREATE DATABASE otptest">>),
  291. ok = mysql:query(Pid, <<"USE otptest">>),
  292. ok = mysql:query(Pid, <<"SET autocommit = 1">>),
  293. ok = mysql:query(Pid, <<"SET SESSION sql_mode = ?">>, [?SQL_MODE]),
  294. Pid
  295. end,
  296. fun (Pid) ->
  297. ok = mysql:query(Pid, <<"DROP DATABASE otptest">>),
  298. mysql:stop(Pid)
  299. end,
  300. fun (Pid) ->
  301. [{"Select db on connect", fun () -> connect_with_db(Pid) end},
  302. {"Autocommit", fun () -> autocommit(Pid) end},
  303. {"Encode", fun () -> encode(Pid) end},
  304. {"Basic queries", fun () -> basic_queries(Pid) end},
  305. {"Filtermap queries", fun () -> filtermap_queries(Pid) end},
  306. {"FOUND_ROWS option", fun () -> found_rows(Pid) end},
  307. {"Multi statements", fun () -> multi_statements(Pid) end},
  308. {"Text protocol", fun () -> text_protocol(Pid) end},
  309. {"Binary protocol", fun () -> binary_protocol(Pid) end},
  310. {"FLOAT rounding", fun () -> float_rounding(Pid) end},
  311. {"DECIMAL", fun () -> decimal(Pid) end},
  312. {"INT", fun () -> int(Pid) end},
  313. {"BIT(N)", fun () -> bit(Pid) end},
  314. {"DATE", fun () -> date(Pid) end},
  315. {"TIME", fun () -> time(Pid) end},
  316. {"DATETIME", fun () -> datetime(Pid) end},
  317. {"JSON", fun () -> json(Pid) end},
  318. {"Microseconds", fun () -> microseconds(Pid) end},
  319. {"Invalid params", fun () -> invalid_params(Pid) end}]
  320. end}.
  321. connect_with_db(_Pid) ->
  322. %% Make another connection and set the db in the handshake phase
  323. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  324. {database, "otptest"}]),
  325. ?assertMatch({ok, _, [[<<"otptest">>]]},
  326. mysql:query(Pid, "SELECT DATABASE()")),
  327. mysql:stop(Pid).
  328. log_warnings_test() ->
  329. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password}]),
  330. ok = mysql:query(Pid, <<"CREATE DATABASE otptest">>),
  331. ok = mysql:query(Pid, <<"USE otptest">>),
  332. ok = mysql:query(Pid, <<"SET SESSION sql_mode = ?">>, [?SQL_MODE]),
  333. %% Capture error log to check that we get a warning logged
  334. ok = mysql:query(Pid, "CREATE TABLE foo (x INT NOT NULL)"),
  335. {ok, insrt} = mysql:prepare(Pid, insrt, "INSERT INTO foo () VALUES ()"),
  336. {ok, ok, LoggedErrors} = error_logger_acc:capture(fun () ->
  337. ok = mysql:query(Pid, "INSERT INTO foo () VALUES ()"),
  338. ok = mysql:query(Pid, "INSeRT INtO foo () VaLUeS ()", []),
  339. ok = mysql:execute(Pid, insrt, [])
  340. end),
  341. [{_, Log1}, {_, Log2}, {_, Log3}] = LoggedErrors,
  342. ?assertEqual("Warning 1364: Field 'x' doesn't have a default value\n"
  343. " in INSERT INTO foo () VALUES ()\n", Log1),
  344. ?assertEqual("Warning 1364: Field 'x' doesn't have a default value\n"
  345. " in INSeRT INtO foo () VaLUeS ()\n", Log2),
  346. ?assertEqual("Warning 1364: Field 'x' doesn't have a default value\n"
  347. " in INSERT INTO foo () VALUES ()\n", Log3),
  348. mysql:stop(Pid).
  349. log_slow_queries_test() ->
  350. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  351. {log_warnings, false}, {log_slow_queries, true}]),
  352. VersionStr = db_version_string(Pid),
  353. try
  354. Version = parse_db_version(VersionStr),
  355. case is_mariadb(VersionStr) of
  356. true when Version < [10, 0, 21] ->
  357. throw({mariadb, version_too_small});
  358. false when Version < [5, 5, 8] ->
  359. throw({mysql, version_too_small});
  360. _ ->
  361. ok
  362. end
  363. of _ ->
  364. ok = mysql:query(Pid, "SET long_query_time = 0.1"),
  365. %% single statement should not include query number
  366. SingleQuery = "SELECT SLEEP(0.2)",
  367. {ok, _, SingleLogged} = error_logger_acc:capture( fun () ->
  368. {ok, _, _} = mysql:query(Pid, SingleQuery)
  369. end),
  370. [{_, SingleLog}] = SingleLogged,
  371. ?assertEqual("MySQL query was slow: " ++ SingleQuery ++ "\n", SingleLog),
  372. %% multi statement should include number of slow query
  373. MultiQuery = "SELECT SLEEP(0.2); " %% #1 -> slow
  374. "SELECT 1; " %% #2 -> not slow
  375. "SET @foo = 1; " %% #3 -> not slow, no result set
  376. "SELECT SLEEP(0.2); " %% #4 -> slow
  377. "SELECT 1", %% #5 -> not slow
  378. {ok, _, MultiLogged} = error_logger_acc:capture(fun () ->
  379. {ok, _} = mysql:query(Pid, MultiQuery)
  380. end),
  381. [{_, MultiLog1}, {_, MultiLog2}] = MultiLogged,
  382. ?assertEqual("MySQL query #1 was slow: " ++ MultiQuery ++ "\n", MultiLog1),
  383. ?assertEqual("MySQL query #4 was slow: " ++ MultiQuery ++ "\n", MultiLog2)
  384. catch
  385. throw:{mysql, version_too_small} ->
  386. error_logger:info_msg("Skipping Log Slow Queries test. Current MySQL version"
  387. " is ~s. Required version is >= 5.5.8.~n",
  388. [VersionStr]);
  389. throw:{mariadb, version_too_small} ->
  390. error_logger:info_msg("Skipping Log Slow Queries test. Current MariaDB version"
  391. " is ~s. Required version is >= 10.0.21.~n",
  392. [VersionStr])
  393. end,
  394. mysql:stop(Pid).
  395. autocommit(Pid) ->
  396. ?assert(mysql:autocommit(Pid)),
  397. ok = mysql:query(Pid, <<"SET autocommit = 0">>),
  398. ?assertNot(mysql:autocommit(Pid)),
  399. ok = mysql:query(Pid, <<"SET autocommit = 1">>),
  400. ?assert(mysql:autocommit(Pid)).
  401. encode(Pid) ->
  402. %% Test with backslash escapes enabled and disabled.
  403. {ok, _, [[OldMode]]} = mysql:query(Pid, "SELECT @@sql_mode"),
  404. ok = mysql:query(Pid, "SET sql_mode = ''"),
  405. ?assertEqual(<<"'foo\\\\bar''baz'">>,
  406. iolist_to_binary(mysql:encode(Pid, "foo\\bar'baz"))),
  407. ok = mysql:query(Pid, "SET sql_mode = 'NO_BACKSLASH_ESCAPES'"),
  408. ?assertEqual(<<"'foo\\bar''baz'">>,
  409. iolist_to_binary(mysql:encode(Pid, "foo\\bar'baz"))),
  410. ok = mysql:query(Pid, "SET sql_mode = ?", [OldMode]).
  411. basic_queries(Pid) ->
  412. %% warning count
  413. ?assertEqual(ok, mysql:query(Pid, <<"DROP TABLE IF EXISTS foo">>)),
  414. ?assertEqual(1, mysql:warning_count(Pid)),
  415. %% SQL parse error
  416. ?assertMatch({error, {1064, <<"42000">>, <<"You have an erro", _/binary>>}},
  417. mysql:query(Pid, <<"FOO">>)),
  418. %% Simple resultset with various types
  419. ?assertEqual({ok, [<<"i">>, <<"s">>], [[42, <<"foo">>]]},
  420. mysql:query(Pid, <<"SELECT 42 AS i, 'foo' AS s;">>)),
  421. ok.
  422. filtermap_queries(Pid) ->
  423. ok = mysql:query(Pid, ?create_table_t),
  424. ok = mysql:query(Pid, <<"INSERT INTO t (id, tx) VALUES (1, 'text 1')">>),
  425. ok = mysql:query(Pid, <<"INSERT INTO t (id, tx) VALUES (2, 'text 2')">>),
  426. ok = mysql:query(Pid, <<"INSERT INTO t (id, tx) VALUES (3, 'text 3')">>),
  427. Query = <<"SELECT id, tx FROM t ORDER BY id">>,
  428. %% one-ary filtermap fun
  429. FilterMap1 = fun
  430. ([1|_]) ->
  431. true;
  432. ([2|_]) ->
  433. false;
  434. (Row1=[3|_]) ->
  435. {true, list_to_tuple(Row1)}
  436. end,
  437. %% two-ary filtermap fun
  438. FilterMap2 = fun
  439. (_, Row2) ->
  440. FilterMap1(Row2)
  441. end,
  442. Expected = [[1, <<"text 1">>], {3, <<"text 3">>}],
  443. %% test with plain query
  444. {ok, _, Rows1}=mysql:query(Pid, Query, FilterMap1),
  445. ?assertEqual(Expected, Rows1),
  446. {ok, _, Rows2}=mysql:query(Pid, Query, FilterMap2),
  447. ?assertEqual(Expected, Rows2),
  448. %% test with parameterized query
  449. {ok, _, Rows3}=mysql:query(Pid, Query, [], FilterMap1),
  450. ?assertEqual(Expected, Rows3),
  451. {ok, _, Rows4}=mysql:query(Pid, Query, [], FilterMap2),
  452. ?assertEqual(Expected, Rows4),
  453. %% test with prepared statement
  454. {ok, PrepStmt} = mysql:prepare(Pid, Query),
  455. {ok, _, Rows5}=mysql:execute(Pid, PrepStmt, [], FilterMap1),
  456. ?assertEqual(Expected, Rows5),
  457. {ok, _, Rows6}=mysql:execute(Pid, PrepStmt, [], FilterMap2),
  458. ?assertEqual(Expected, Rows6),
  459. ok = mysql:query(Pid, <<"DROP TABLE t">>).
  460. found_rows(Pid) ->
  461. Options = [{user, ?user}, {password, ?password}, {log_warnings, false},
  462. {keep_alive, true}, {found_rows, true}],
  463. {ok, FRPid} = mysql:start_link(Options),
  464. ok = mysql:query(FRPid, <<"USE otptest">>),
  465. ok = mysql:query(Pid, ?create_table_t),
  466. ok = mysql:query(Pid, <<"INSERT INTO t (id, tx) VALUES (1, 'text')">>),
  467. %% With no found_rows option, affected_rows for update returns 0
  468. ok = mysql:query(Pid, <<"UPDATE t SET tx = 'text' WHERE id = 1">>),
  469. ?assertEqual(0, mysql:affected_rows(Pid)),
  470. %% With found_rows, affected_rows returns the number of rows found
  471. ok = mysql:query(FRPid, <<"UPDATE t SET tx = 'text' WHERE id = 1">>),
  472. ?assertEqual(1, mysql:affected_rows(FRPid)),
  473. ok = mysql:query(Pid, <<"DROP TABLE t">>).
  474. multi_statements(Pid) ->
  475. %% Multiple statements, no result set
  476. ?assertEqual(ok, mysql:query(Pid, "CREATE TABLE foo (bar INT);"
  477. "DROP TABLE foo;")),
  478. %% Multiple statements, one result set
  479. ?assertEqual({ok, [<<"foo">>], [[42]]},
  480. mysql:query(Pid, "CREATE TABLE foo (bar INT);"
  481. "DROP TABLE foo;"
  482. "SELECT 42 AS foo;")),
  483. %% Multiple statements, multiple result sets
  484. ?assertEqual({ok, [{[<<"foo">>], [[42]]}, {[<<"bar">>], [[<<"baz">>]]}]},
  485. mysql:query(Pid, "SELECT 42 AS foo; SELECT 'baz' AS bar;")),
  486. %% Multiple results in a prepared statement.
  487. %% Preparing "SELECT ...; SELECT ...;" gives a syntax error although the
  488. %% docs say it should be possible.
  489. %% Instead, test executing a stored procedure that returns multiple result
  490. %% sets using a prepared statement.
  491. CreateProc = "CREATE PROCEDURE multifoo() BEGIN\n"
  492. " SELECT 42 AS foo;\n"
  493. " SELECT 'baz' AS bar;\n"
  494. "END;\n",
  495. ok = mysql:query(Pid, CreateProc),
  496. ?assertEqual({ok, multifoo},
  497. mysql:prepare(Pid, multifoo, "CALL multifoo();")),
  498. ?assertEqual({ok, [{[<<"foo">>], [[42]]}, {[<<"bar">>], [[<<"baz">>]]}]},
  499. mysql:execute(Pid, multifoo, [])),
  500. ?assertEqual(ok, mysql:unprepare(Pid, multifoo)),
  501. ?assertEqual(ok, mysql:query(Pid, "DROP PROCEDURE multifoo;")),
  502. ok.
  503. text_protocol(Pid) ->
  504. ok = mysql:query(Pid, ?create_table_t),
  505. ok = mysql:query(Pid, <<"INSERT INTO t (bl, f, d, dc, y, ti, ts, da, c)"
  506. " VALUES ('blob', 3.14, 3.14, 3.14, 2014,"
  507. "'00:22:11', '2014-11-03 00:22:24', '2014-11-03',"
  508. " NULL)">>),
  509. ?assertEqual(1, mysql:warning_count(Pid)), %% tx has no default value
  510. ?assertEqual(1, mysql:insert_id(Pid)), %% auto_increment starts from 1
  511. ?assertEqual(1, mysql:affected_rows(Pid)),
  512. %% select
  513. {ok, Columns, Rows} = mysql:query(Pid, <<"SELECT * FROM t">>),
  514. ?assertEqual([<<"id">>, <<"bl">>, <<"tx">>, <<"f">>, <<"d">>, <<"dc">>,
  515. <<"y">>, <<"ti">>, <<"ts">>, <<"da">>, <<"c">>], Columns),
  516. ?assertEqual([[1, <<"blob">>, <<>>, 3.14, 3.14, 3.14,
  517. 2014, {0, {0, 22, 11}},
  518. {{2014, 11, 03}, {00, 22, 24}}, {2014, 11, 03}, null]],
  519. Rows),
  520. ok = mysql:query(Pid, <<"DROP TABLE t">>).
  521. binary_protocol(Pid) ->
  522. ok = mysql:query(Pid, ?create_table_t),
  523. %% The same queries as in the text protocol. Expect the same results.
  524. {ok, Ins} = mysql:prepare(Pid, <<"INSERT INTO t (bl, tx, f, d, dc, y, ti,"
  525. " ts, da, c)"
  526. " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)">>),
  527. %% 16#161 is the codepoint for "s with caron"; <<197, 161>> in UTF-8.
  528. ok = mysql:execute(Pid, Ins, [<<"blob">>, [16#161], 3.14, 3.14, 3.14,
  529. 2014, {0, {0, 22, 11}},
  530. {{2014, 11, 03}, {0, 22, 24}},
  531. {2014, 11, 03}, null]),
  532. {ok, Stmt} = mysql:prepare(Pid, <<"SELECT * FROM t WHERE id=?">>),
  533. {ok, Columns, Rows} = mysql:execute(Pid, Stmt, [1]),
  534. ?assertEqual([<<"id">>, <<"bl">>, <<"tx">>, <<"f">>, <<"d">>, <<"dc">>,
  535. <<"y">>, <<"ti">>,
  536. <<"ts">>, <<"da">>, <<"c">>], Columns),
  537. ?assertEqual([[1, <<"blob">>, <<197, 161>>, 3.14, 3.14, 3.14,
  538. 2014, {0, {0, 22, 11}},
  539. {{2014, 11, 03}, {00, 22, 24}}, {2014, 11, 03}, null]],
  540. Rows),
  541. ok = mysql:query(Pid, <<"DROP TABLE t">>).
  542. float_rounding(Pid) ->
  543. %% This is to make sure we get the same values for 32-bit FLOATs in the text
  544. %% and binary protocols for ordinary queries and prepared statements
  545. %% respectively.
  546. %%
  547. %% MySQL rounds to 6 significant digits when "printing" floats over the
  548. %% text protocol. When we receive a float on the binary protocol, we round
  549. %% it in the same way to match what MySQL does on the text protocol. This
  550. %% way we should to get the same values regardless of which protocol is
  551. %% used.
  552. %% Table for testing floats
  553. ok = mysql:query(Pid, "CREATE TABLE f (f FLOAT)"),
  554. %% Prepared statements
  555. {ok, Insert} = mysql:prepare(Pid, "INSERT INTO f (f) VALUES (?)"),
  556. {ok, Select} = mysql:prepare(Pid, "SELECT f FROM f"),
  557. %% [{Input, Expected}]
  558. TestData = [{1.0, 1.0}, {0.0, 0.0}, {3.14, 3.14}, {0.2, 0.2},
  559. {0.20082111, 0.200821}, {0.000123456789, 0.000123457},
  560. {33.3333333, 33.3333}, {-33.2233443322, -33.2233},
  561. {400.0123, 400.012}, {1000.1234, 1000.12},
  562. {999.00009, 999.0},
  563. {1234.5678, 1234.57}, {68888.8888, 68888.9},
  564. {123456.789, 123457.0}, {7654321.0, 7654320.0},
  565. {80001111.1, 80001100.0}, {987654321.0, 987654000.0},
  566. {-123456789.0, -123457000.0},
  567. {2.12345111e-23, 2.12345e-23}, {-2.12345111e-23, -2.12345e-23},
  568. {2.12345111e23, 2.12345e23}, {-2.12345111e23, -2.12345e23}],
  569. lists:foreach(fun ({Input, Expected}) ->
  570. %% Insert using binary protocol (sending it as a double)
  571. ok = mysql:execute(Pid, Insert, [Input]),
  572. %% Text (plain query)
  573. {ok, _, [[Value]]} = mysql:query(Pid, "SELECT f FROM f"),
  574. ?assertEqual(Expected, Value),
  575. %% Binary (prepared statement)
  576. {ok, _, [[BinValue]]} = mysql:execute(Pid, Select, []),
  577. ?assertEqual(Expected, BinValue),
  578. %% cleanup before the next test
  579. ok = mysql:query(Pid, "DELETE FROM f")
  580. end,
  581. TestData),
  582. ok = mysql:query(Pid, "DROP TABLE f").
  583. decimal(Pid) ->
  584. %% As integer when S == 0
  585. ok = mysql:query(Pid, "CREATE TABLE dec0 (d DECIMAL(50, 0))"),
  586. write_read_text_binary(
  587. Pid, 14159265358979323846264338327950288419716939937510,
  588. <<"14159265358979323846264338327950288419716939937510">>,
  589. <<"dec0">>, <<"d">>
  590. ),
  591. write_read_text_binary(
  592. Pid, -14159265358979323846264338327950288419716939937510,
  593. <<"-14159265358979323846264338327950288419716939937510">>,
  594. <<"dec0">>, <<"d">>
  595. ),
  596. ok = mysql:query(Pid, "DROP TABLE dec0"),
  597. %% As float when P =< 15, S > 0
  598. ok = mysql:query(Pid, "CREATE TABLE dec15 (d DECIMAL(15, 14))"),
  599. write_read_text_binary(Pid, 3.14159265358979, <<"3.14159265358979">>,
  600. <<"dec15">>, <<"d">>),
  601. write_read_text_binary(Pid, -3.14159265358979, <<"-3.14159265358979">>,
  602. <<"dec15">>, <<"d">>),
  603. write_read_text_binary(Pid, 3.0, <<"3">>, <<"dec15">>, <<"d">>),
  604. ok = mysql:query(Pid, "DROP TABLE dec15"),
  605. %% As binary when P >= 16, S > 0
  606. ok = mysql:query(Pid, "CREATE TABLE dec16 (d DECIMAL(16, 15))"),
  607. write_read_text_binary(Pid, <<"3.141592653589793">>,
  608. <<"3.141592653589793">>, <<"dec16">>, <<"d">>),
  609. write_read_text_binary(Pid, <<"-3.141592653589793">>,
  610. <<"-3.141592653589793">>, <<"dec16">>, <<"d">>),
  611. write_read_text_binary(Pid, <<"3.000000000000000">>, <<"3">>,
  612. <<"dec16">>, <<"d">>),
  613. ok = mysql:query(Pid, "DROP TABLE dec16").
  614. int(Pid) ->
  615. ok = mysql:query(Pid, "CREATE TABLE ints (i INT)"),
  616. write_read_text_binary(Pid, 42, <<"42">>, <<"ints">>, <<"i">>),
  617. write_read_text_binary(Pid, -42, <<"-42">>, <<"ints">>, <<"i">>),
  618. write_read_text_binary(Pid, 987654321, <<"987654321">>, <<"ints">>,
  619. <<"i">>),
  620. write_read_text_binary(Pid, -987654321, <<"-987654321">>,
  621. <<"ints">>, <<"i">>),
  622. ok = mysql:query(Pid, "DROP TABLE ints"),
  623. %% Overflow with TINYINT
  624. ok = mysql:query(Pid, "CREATE TABLE tint (i TINYINT)"),
  625. write_read_text_binary(Pid, 127, <<"1000">>, <<"tint">>, <<"i">>),
  626. write_read_text_binary(Pid, -128, <<"-1000">>, <<"tint">>, <<"i">>),
  627. ok = mysql:query(Pid, "DROP TABLE tint"),
  628. %% TINYINT UNSIGNED
  629. ok = mysql:query(Pid, "CREATE TABLE tuint (i TINYINT UNSIGNED)"),
  630. write_read_text_binary(Pid, 240, <<"240">>, <<"tuint">>, <<"i">>),
  631. ok = mysql:query(Pid, "DROP TABLE tuint"),
  632. %% SMALLINT
  633. ok = mysql:query(Pid, "CREATE TABLE sint (i SMALLINT)"),
  634. write_read_text_binary(Pid, 32000, <<"32000">>, <<"sint">>, <<"i">>),
  635. write_read_text_binary(Pid, -32000, <<"-32000">>, <<"sint">>, <<"i">>),
  636. ok = mysql:query(Pid, "DROP TABLE sint"),
  637. %% SMALLINT UNSIGNED
  638. ok = mysql:query(Pid, "CREATE TABLE suint (i SMALLINT UNSIGNED)"),
  639. write_read_text_binary(Pid, 64000, <<"64000">>, <<"suint">>, <<"i">>),
  640. ok = mysql:query(Pid, "DROP TABLE suint"),
  641. %% MEDIUMINT
  642. ok = mysql:query(Pid, "CREATE TABLE mint (i MEDIUMINT)"),
  643. write_read_text_binary(Pid, 8388000, <<"8388000">>,
  644. <<"mint">>, <<"i">>),
  645. write_read_text_binary(Pid, -8388000, <<"-8388000">>,
  646. <<"mint">>, <<"i">>),
  647. ok = mysql:query(Pid, "DROP TABLE mint"),
  648. %% MEDIUMINT UNSIGNED
  649. ok = mysql:query(Pid, "CREATE TABLE muint (i MEDIUMINT UNSIGNED)"),
  650. write_read_text_binary(Pid, 16777000, <<"16777000">>,
  651. <<"muint">>, <<"i">>),
  652. ok = mysql:query(Pid, "DROP TABLE muint"),
  653. %% BIGINT
  654. ok = mysql:query(Pid, "CREATE TABLE bint (i BIGINT)"),
  655. write_read_text_binary(Pid, 123456789012, <<"123456789012">>,
  656. <<"bint">>, <<"i">>),
  657. write_read_text_binary(Pid, -123456789012, <<"-123456789012">>,
  658. <<"bint">>, <<"i">>),
  659. ok = mysql:query(Pid, "DROP TABLE bint"),
  660. %% BIGINT UNSIGNED
  661. ok = mysql:query(Pid, "CREATE TABLE buint (i BIGINT UNSIGNED)"),
  662. write_read_text_binary(Pid, 18446744073709551000,
  663. <<"18446744073709551000">>,
  664. <<"buint">>, <<"i">>),
  665. ok = mysql:query(Pid, "DROP TABLE buint").
  666. %% The BIT(N) datatype in MySQL 5.0.3 and later: the equivallent to bitstring()
  667. bit(Pid) ->
  668. ok = mysql:query(Pid, "CREATE TABLE bits (b BIT(11))"),
  669. write_read_text_binary(Pid, <<16#ff, 0:3>>, <<"b'11111111000'">>,
  670. <<"bits">>, <<"b">>),
  671. write_read_text_binary(Pid, <<16#7f, 6:3>>, <<"b'01111111110'">>,
  672. <<"bits">>, <<"b">>),
  673. ok = mysql:query(Pid, "DROP TABLE bits").
  674. date(Pid) ->
  675. ok = mysql:query(Pid, "CREATE TABLE d (d DATE)"),
  676. lists:foreach(
  677. fun ({Value, SqlLiteral}) ->
  678. write_read_text_binary(Pid, Value, SqlLiteral, <<"d">>, <<"d">>)
  679. end,
  680. [{{2014, 11, 03}, <<"'2014-11-03'">>},
  681. {{0, 0, 0}, <<"'0000-00-00'">>}]
  682. ),
  683. ok = mysql:query(Pid, "DROP TABLE d").
  684. %% Test TIME value representation. There are a few things to check.
  685. time(Pid) ->
  686. ok = mysql:query(Pid, "CREATE TABLE tm (tm TIME)"),
  687. lists:foreach(
  688. fun ({Value, SqlLiteral}) ->
  689. write_read_text_binary(Pid, Value, SqlLiteral, <<"tm">>, <<"tm">>)
  690. end,
  691. [{{0, {10, 11, 12}}, <<"'10:11:12'">>},
  692. {{5, {0, 0, 1}}, <<"'120:00:01'">>},
  693. {{-1, {23, 59, 59}}, <<"'-00:00:01'">>},
  694. {{-1, {23, 59, 0}}, <<"'-00:01:00'">>},
  695. {{-1, {23, 0, 0}}, <<"'-01:00:00'">>},
  696. {{-1, {0, 0, 0}}, <<"'-24:00:00'">>},
  697. {{-5, {10, 0, 0}}, <<"'-110:00:00'">>},
  698. {{0, {0, 0, 0}}, <<"'00:00:00'">>}]
  699. ),
  700. %% Zero seconds as a float.
  701. ok = mysql:query(Pid, "INSERT INTO tm (tm) VALUES (?)",
  702. [{-1, {1, 2, 0.0}}]),
  703. ?assertEqual({ok, [<<"tm">>], [[{-1, {1, 2, 0}}]]},
  704. mysql:query(Pid, "SELECT tm FROM tm")),
  705. ok = mysql:query(Pid, "DROP TABLE tm").
  706. datetime(Pid) ->
  707. ok = mysql:query(Pid, "CREATE TABLE dt (dt DATETIME)"),
  708. lists:foreach(
  709. fun ({Value, SqlLiteral}) ->
  710. write_read_text_binary(Pid, Value, SqlLiteral, <<"dt">>, <<"dt">>)
  711. end,
  712. [{{{2014, 12, 14}, {19, 39, 20}}, <<"'2014-12-14 19:39:20'">>},
  713. {{{2014, 12, 14}, {0, 0, 0}}, <<"'2014-12-14 00:00:00'">>},
  714. {{{0, 0, 0}, {0, 0, 0}}, <<"'0000-00-00 00:00:00'">>}]
  715. ),
  716. ok = mysql:query(Pid, "DROP TABLE dt").
  717. json(Pid) ->
  718. Version = db_version_string(Pid),
  719. try
  720. is_mariadb(Version) andalso throw(no_mariadb),
  721. Version1 = parse_db_version(Version),
  722. Version1 >= [5, 7, 8] orelse throw(version_too_small)
  723. of _ ->
  724. test_valid_json(Pid),
  725. test_invalid_json(Pid)
  726. catch
  727. throw:no_mariadb ->
  728. error_logger:info_msg("Skipping JSON test, not supported on"
  729. " MariaDB.~n");
  730. throw:version_too_small ->
  731. error_logger:info_msg("Skipping JSON test. Current MySQL version"
  732. " is ~s. Required version is >= 5.7.8.~n",
  733. [Version])
  734. end.
  735. test_valid_json(Pid) ->
  736. ok = mysql:query(Pid, "CREATE TABLE json_t (json_c JSON)"),
  737. Value = <<"'{\"a\": 1, \"b\": {\"c\": [1, 2, 3, 4]}}'">>,
  738. Expected = <<"{\"a\": 1, \"b\": {\"c\": [1, 2, 3, 4]}}">>,
  739. write_read_text_binary(Pid, Expected, Value,
  740. <<"json_t">>, <<"json_c">>),
  741. ok = mysql:query(Pid, "DROP TABLE json_t").
  742. test_invalid_json(Pid) ->
  743. ok = mysql:query(Pid, "CREATE TABLE json_t (json_c JSON)"),
  744. InvalidJson = <<"'{\"a\": \"c\": 2}'">>,
  745. ?assertMatch({error,{3140, <<"22032">>, _}},
  746. mysql:query(Pid, <<"INSERT INTO json_t (json_c)"
  747. " VALUES (", InvalidJson/binary,
  748. ")">>)),
  749. ok = mysql:query(Pid, "DROP TABLE json_t").
  750. microseconds(Pid) ->
  751. %% Check whether we have the required version for this testcase.
  752. Version = db_version_string(Pid),
  753. try
  754. Version1 = parse_db_version(Version),
  755. Version1 >= [5, 6, 4] orelse throw(nope)
  756. of _ ->
  757. test_time_microseconds(Pid),
  758. test_datetime_microseconds(Pid)
  759. catch _:_ ->
  760. error_logger:info_msg("Skipping microseconds test. Current MySQL"
  761. " version is ~s. Required version is >= 5.6.4.~n",
  762. [Version])
  763. end.
  764. test_time_microseconds(Pid) ->
  765. ok = mysql:query(Pid, "CREATE TABLE m (t TIME(6))"),
  766. %% Positive time
  767. write_read_text_binary(Pid, {0, {23, 59, 57.654321}},
  768. <<"'23:59:57.654321'">>, <<"m">>, <<"t">>),
  769. %% Negative time
  770. write_read_text_binary(Pid, {-1, {23, 59, 57.654321}},
  771. <<"'-00:00:02.345679'">>, <<"m">>, <<"t">>),
  772. ok = mysql:query(Pid, "DROP TABLE m").
  773. test_datetime_microseconds(Pid) ->
  774. ok = mysql:query(Pid, "CREATE TABLE dt (dt DATETIME(6))"),
  775. write_read_text_binary(Pid, {{2014, 11, 23}, {23, 59, 57.654321}},
  776. <<"'2014-11-23 23:59:57.654321'">>, <<"dt">>,
  777. <<"dt">>),
  778. ok = mysql:query(Pid, "DROP TABLE dt").
  779. invalid_params(Pid) ->
  780. {ok, StmtId} = mysql:prepare(Pid, "SELECT ?"),
  781. ?assertError(badarg, mysql:execute(Pid, StmtId, [x])),
  782. ?assertError(badarg, mysql:query(Pid, "SELECT ?", [x])),
  783. ok = mysql:unprepare(Pid, StmtId).
  784. %% @doc Tests write and read in text and the binary protocol, all combinations.
  785. %% This helper function assumes an empty table with a single column.
  786. write_read_text_binary(Conn, Term, SqlLiteral, Table, Column) ->
  787. SelectQuery = <<"SELECT ", Column/binary, " FROM ", Table/binary>>,
  788. {ok, SelectStmt} = mysql:prepare(Conn, SelectQuery),
  789. %% Insert as text, read text and binary, delete
  790. InsertQuery = <<"INSERT INTO ", Table/binary, " (", Column/binary, ")"
  791. " VALUES (", SqlLiteral/binary, ")">>,
  792. ok = mysql:query(Conn, InsertQuery),
  793. R = mysql:query(Conn, SelectQuery),
  794. ?assertEqual({ok, [Column], [[Term]]}, R),
  795. ?assertEqual({ok, [Column], [[Term]]}, mysql:execute(Conn, SelectStmt, [])),
  796. mysql:query(Conn, <<"DELETE FROM ", Table/binary>>),
  797. %% Insert as binary, read text and binary, delete
  798. InsertQ = <<"INSERT INTO ", Table/binary, " (", Column/binary, ")",
  799. " VALUES (?)">>,
  800. {ok, InsertStmt} = mysql:prepare(Conn, InsertQ),
  801. ok = mysql:execute(Conn, InsertStmt, [Term]),
  802. ok = mysql:unprepare(Conn, InsertStmt),
  803. ?assertEqual({ok, [Column], [[Term]]}, mysql:query(Conn, SelectQuery)),
  804. ?assertEqual({ok, [Column], [[Term]]}, mysql:execute(Conn, SelectStmt, [])),
  805. mysql:query(Conn, <<"DELETE FROM ", Table/binary>>),
  806. %% Cleanup
  807. ok = mysql:unprepare(Conn, SelectStmt).
  808. %% --------------------------------------------------------------------------
  809. timeout_test_() ->
  810. {setup,
  811. fun () ->
  812. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  813. {log_warnings, false}]),
  814. Pid
  815. end,
  816. fun (Pid) ->
  817. mysql:stop(Pid)
  818. end,
  819. {with, [fun (Pid) ->
  820. %% SLEEP was added in MySQL 5.0.12
  821. ?assertEqual({ok, [<<"SLEEP(5)">>], [[1]]},
  822. mysql:query(Pid, <<"SELECT SLEEP(5)">>, 40)),
  823. %% A query after an interrupted query shouldn't get a timeout.
  824. ?assertMatch({ok,[<<"42">>], [[42]]},
  825. mysql:query(Pid, <<"SELECT 42">>)),
  826. %% Parametrized query
  827. ?assertEqual({ok, [<<"SLEEP(?)">>], [[1]]},
  828. mysql:query(Pid, <<"SELECT SLEEP(?)">>, [5], 40)),
  829. %% Prepared statement
  830. {ok, Stmt} = mysql:prepare(Pid, <<"SELECT SLEEP(?)">>),
  831. ?assertEqual({ok, [<<"SLEEP(?)">>], [[1]]},
  832. mysql:execute(Pid, Stmt, [5], 40)),
  833. ok = mysql:unprepare(Pid, Stmt)
  834. end]}}.
  835. %% --------------------------------------------------------------------------
  836. %% Prepared statements
  837. with_table_foo_test_() ->
  838. {setup,
  839. fun () ->
  840. {ok, Pid} = mysql:start_link([{user, ?user}, {password, ?password},
  841. {query_cache_time, 50},
  842. {log_warnings, false}]),
  843. ok = mysql:query(Pid, <<"DROP DATABASE IF EXISTS otptest">>),
  844. ok = mysql:query(Pid, <<"CREATE DATABASE otptest">>),
  845. ok = mysql:query(Pid, <<"USE otptest">>),
  846. ok = mysql:query(Pid, <<"CREATE TABLE foo (bar INT) engine=InnoDB">>),
  847. Pid
  848. end,
  849. fun (Pid) ->
  850. ok = mysql:query(Pid, <<"DROP DATABASE otptest">>),
  851. mysql:stop(Pid)
  852. end,
  853. fun (Pid) ->
  854. [{"Prepared statements", fun () -> prepared_statements(Pid) end},
  855. {"Parametrized queries", fun () -> parameterized_query(Pid) end}]
  856. end}.
  857. prepared_statements(Pid) ->
  858. %% Unnamed
  859. ?assertEqual({error,{1146, <<"42S02">>,
  860. <<"Table 'otptest.tab' doesn't exist">>}},
  861. mysql:prepare(Pid, "SELECT * FROM tab WHERE id = ?")),
  862. {ok, StmtId} = mysql:prepare(Pid, "SELECT * FROM foo WHERE bar = ?"),
  863. ?assert(is_integer(StmtId)),
  864. ?assertEqual(ok, mysql:unprepare(Pid, StmtId)),
  865. ?assertEqual({error, not_prepared}, mysql:unprepare(Pid, StmtId)),
  866. %% Named
  867. ?assertEqual({error,{1146, <<"42S02">>,
  868. <<"Table 'otptest.tab' doesn't exist">>}},
  869. mysql:prepare(Pid, tab, "SELECT * FROM tab WHERE id = ?")),
  870. ?assertEqual({ok, foo},
  871. mysql:prepare(Pid, foo, "SELECT * FROM foo WHERE bar = ?")),
  872. %% Prepare again unprepares the old stmt associated with this name.
  873. ?assertEqual({ok, foo},
  874. mysql:prepare(Pid, foo, "SELECT bar FROM foo WHERE bar = ?")),
  875. ?assertEqual(ok, mysql:unprepare(Pid, foo)),
  876. ?assertEqual({error, not_prepared}, mysql:unprepare(Pid, foo)),
  877. %% Execute when not prepared
  878. ?assertEqual({error, not_prepared}, mysql:execute(Pid, not_a_stmt, [])),
  879. ok.
  880. parameterized_query(Conn) ->
  881. %% To see that cache eviction works as expected, look at the code coverage.
  882. {ok, _, []} = mysql:query(Conn, "SELECT * FROM foo WHERE bar = ?", [1]),
  883. {ok, _, []} = mysql:query(Conn, "SELECT * FROM foo WHERE bar = ?", [2]),
  884. receive after 150 -> ok end, %% Now the query cache should emptied
  885. {ok, _, []} = mysql:query(Conn, "SELECT * FROM foo WHERE bar = ?", [3]),
  886. {error, {_, _, _}} = mysql:query(Conn, "Lorem ipsum dolor sit amet", [4]).
  887. %% --- simple gen_server callbacks ---
  888. gen_server_coverage_test() ->
  889. {noreply, state} = mysql_conn:handle_cast(foo, state),
  890. {noreply, state} = mysql_conn:handle_info(foo, state),
  891. ok = mysql_conn:terminate(kill, state).
  892. %% --- Utility functions
  893. db_version_string(Pid) ->
  894. {ok, _, [[Version]]} = mysql:query(Pid, <<"SELECT @@version">>),
  895. Version.
  896. is_mariadb(Version) ->
  897. binary:match(Version, <<"MariaDB">>) =/= nomatch.
  898. parse_db_version(Version) ->
  899. %% Remove stuff after dash for e.g. "5.5.40-0ubuntu0.12.04.1-log"
  900. [Version1 | _] = binary:split(Version, <<"-">>),
  901. lists:map(fun binary_to_integer/1,
  902. binary:split(Version1, <<".">>, [global])).