mysql_protocol_tests.erl 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. %% MySQL/OTP – MySQL client library for Erlang/OTP
  2. %% Copyright (C) 2014 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 Eunit test cases for the mysql_protocol module.
  20. %% Most of the hexdump tests are from examples in the protocol documentation.
  21. %%
  22. %% TODO: Use ngrep -x -q -d lo '' 'port 3306' to dump traffic using various
  23. %% server versions.
  24. -module(mysql_protocol_tests).
  25. -include_lib("eunit/include/eunit.hrl").
  26. -include("protocol.hrl").
  27. -include("records.hrl").
  28. resultset_test() ->
  29. %% A query that returns a result set in the text protocol.
  30. Query = <<"SELECT @@version_comment">>,
  31. ExpectedReq = <<(size(Query) + 1):24/little, 0, ?COM_QUERY, Query/binary>>,
  32. ExpectedResponse = hexdump_to_bin(
  33. "01 00 00 01 01|27 00 00 02 03 64 65 66 00 00 00 .....'....def..."
  34. "11 40 40 76 65 72 73 69 6f 6e 5f 63 6f 6d 6d 65 .@@version_comme"
  35. "6e 74 00 0c 08 00 1c 00 00 00 fd 00 00 1f 00 00| nt.............."
  36. "05 00 00 03 fe 00 00 02 00|1d 00 00 04 1c 4d 79 ..............My"
  37. "53 51 4c 20 43 6f 6d 6d 75 6e 69 74 79 20 53 65 SQL Community Se"
  38. "72 76 65 72 20 28 47 50 4c 29|05 00 00 05 fe 00 rver (GPL)......"
  39. "00 02 00 ..."),
  40. ExpectedCommunication = [{send, ExpectedReq},
  41. {recv, ExpectedResponse}],
  42. Sock = mock_tcp:create(ExpectedCommunication),
  43. {ok, [ResultSet]} = mysql_protocol:query(Query, mock_tcp, Sock, [],
  44. no_filtermap_fun, infinity),
  45. mock_tcp:close(Sock),
  46. ?assertMatch(#resultset{cols = [#col{name = <<"@@version_comment">>}],
  47. rows = [[<<"MySQL Community Server (GPL)">>]]},
  48. ResultSet),
  49. ok.
  50. resultset_error_test() ->
  51. %% A query that returns a response starting as a result set but then
  52. %% interrupts itself and decides that it is an error.
  53. Query = <<"EXPLAIN SELECT * FROM dual;">>,
  54. ExpectedReq = <<(size(Query) + 1):24/little, 0, ?COM_QUERY, Query/binary>>,
  55. ExpectedResponse = hexdump_to_bin(
  56. "01 00 00 01 0a 18 00 00 02 03 64 65 66 00 00 00 ..........def..."
  57. "02 69 64 00 0c 3f 00 03 00 00 00 08 a1 00 00 00 .id..?.........."
  58. "00 21 00 00 03 03 64 65 66 00 00 00 0b 73 65 6c .!....def....sel"
  59. "65 63 74 5f 74 79 70 65 00 0c 08 00 13 00 00 00 ect_type........"
  60. "fd 01 00 1f 00 00 1b 00 00 04 03 64 65 66 00 00 ...........def.."
  61. "00 05 74 61 62 6c 65 00 0c 08 00 40 00 00 00 fd ..table....@...."
  62. "00 00 1f 00 00 1a 00 00 05 03 64 65 66 00 00 00 ..........def..."
  63. "04 74 79 70 65 00 0c 08 00 0a 00 00 00 fd 00 00 .type..........."
  64. "1f 00 00 23 00 00 06 03 64 65 66 00 00 00 0d 70 ...#....def....p"
  65. "6f 73 73 69 62 6c 65 5f 6b 65 79 73 00 0c 08 00 ossible_keys...."
  66. "00 10 00 00 fd 00 00 1f 00 00 19 00 00 07 03 64 ...............d"
  67. "65 66 00 00 00 03 6b 65 79 00 0c 08 00 40 00 00 ef....key....@.."
  68. "00 fd 00 00 1f 00 00 1d 00 00 08 03 64 65 66 00 ............def."
  69. "00 00 07 6b 65 79 5f 6c 65 6e 00 0c 08 00 00 10 ...key_len......"
  70. "00 00 fd 00 00 1f 00 00 19 00 00 09 03 64 65 66 .............def"
  71. "00 00 00 03 72 65 66 00 0c 08 00 00 04 00 00 fd ....ref........."
  72. "00 00 1f 00 00 1a 00 00 0a 03 64 65 66 00 00 00 ..........def..."
  73. "04 72 6f 77 73 00 0c 3f 00 0a 00 00 00 08 a0 00 .rows..?........"
  74. "00 00 00 1b 00 00 0b 03 64 65 66 00 00 00 05 45 ........def....E"
  75. "78 74 72 61 00 0c 08 00 ff 00 00 00 fd 01 00 1f xtra............"
  76. "00 00 05 00 00 0c fe 00 00 02 00 17 00 00 0d ff ................"
  77. "48 04 23 48 59 30 30 30 4e 6f 20 74 61 62 6c 65 H.#HY000No table"
  78. "73 20 75 73 65 64 s used"),
  79. Sock = mock_tcp:create([{send, ExpectedReq}, {recv, ExpectedResponse}]),
  80. {ok, [Result]} = mysql_protocol:query(Query, mock_tcp, Sock, [],
  81. no_filtermap_fun, infinity),
  82. ?assertMatch(#error{}, Result),
  83. mock_tcp:close(Sock),
  84. ok.
  85. prepare_test() ->
  86. %% Prepared statement. The example from "14.7.4 COM_STMT_PREPARE" in the
  87. %% "MySQL Internals" guide.
  88. Query = <<"SELECT CONCAT(?, ?) AS col1">>,
  89. ExpectedReq = hexdump_to_bin(
  90. "1c 00 00 00 16 53 45 4c 45 43 54 20 43 4f 4e 43 .....SELECT CONC"
  91. "41 54 28 3f 2c 20 3f 29 20 41 53 20 63 6f 6c 31 AT(?, ?) AS col1"
  92. ),
  93. ExpectedResp = hexdump_to_bin(
  94. "0c 00 00 01 00 01 00 00 00 01 00 02 00 00 00 00| ................"
  95. "17 00 00 02 03 64 65 66 00 00 00 01 3f 00 0c 3f .....def....?..?"
  96. "00 00 00 00 00 fd 80 00 00 00 00|17 00 00 03 03 ................"
  97. "64 65 66 00 00 00 01 3f 00 0c 3f 00 00 00 00 00 def....?..?....."
  98. "fd 80 00 00 00 00|05 00 00 04 fe 00 00 02 00|1a ................"
  99. "00 00 05 03 64 65 66 00 00 00 04 63 6f 6c 31 00 ....def....col1."
  100. "0c 3f 00 00 00 00 00 fd 80 00 1f 00 00|05 00 00 .?.............."
  101. "06 fe 00 00 02 00 ......"),
  102. Sock = mock_tcp:create([{send, ExpectedReq}, {recv, ExpectedResp}]),
  103. Result = mysql_protocol:prepare(Query, mock_tcp, Sock),
  104. mock_tcp:close(Sock),
  105. ?assertMatch(#prepared{statement_id = StmtId,
  106. param_count = 2,
  107. warning_count = 0} when is_integer(StmtId),
  108. Result),
  109. ok.
  110. bad_protocol_version_test() ->
  111. Sock = mock_tcp:create([{recv, <<2, 0, 0, 0, 9, 0>>}]),
  112. SSLOpts = undefined,
  113. ?assertError(unknown_protocol,
  114. mysql_protocol:handshake("foo", "bar", "baz", "db", mock_tcp,
  115. SSLOpts, Sock, false)),
  116. mock_tcp:close(Sock).
  117. error_as_initial_packet_test() ->
  118. %% This behaviour has been observed from MariaDB 10.1.21
  119. PacketBody = <<255,16,4,84,111,111,32,109,97,110,121,32,99,111,110,110,101,
  120. 99,116,105,111,110,115>>,
  121. Packet = <<(byte_size(PacketBody)):24/little-integer,
  122. (_SeqNum = 0):8/integer, PacketBody/binary>>,
  123. Sock = mock_tcp:create([{recv, Packet}]),
  124. SSLOpts = undefined,
  125. ?assertMatch(#error{code = 1040, msg = <<"Too many connections">>},
  126. mysql_protocol:handshake("foo", "bar", "baz", "db", mock_tcp,
  127. SSLOpts, Sock, false)),
  128. mock_tcp:close(Sock).
  129. %% --- Helper functions for the above tests ---
  130. %% Convert hex dumps to binaries. This is a helper function for the tests.
  131. %% This function is also tested below.
  132. hexdump_to_bin(HexDump) ->
  133. hexdump_to_bin(iolist_to_binary(HexDump), <<>>).
  134. hexdump_to_bin(<<Line:50/binary, _Junk:20/binary, Rest/binary>>, Acc) ->
  135. hexdump_to_bin(Line, Rest, Acc);
  136. hexdump_to_bin(<<Line:50/binary, _Junk/binary>>, Acc) ->
  137. %% last line (shorter than 70)
  138. hexdump_to_bin(Line, <<>>, Acc);
  139. hexdump_to_bin(<<>>, Acc) ->
  140. Acc.
  141. hexdump_to_bin(Line, Rest, Acc) ->
  142. HexNums = re:split(Line, <<"[ |]+">>, [{return, list}, trim]),
  143. Acc1 = lists:foldl(fun (HexNum, Acc0) ->
  144. {ok, [Byte], []} = io_lib:fread("~16u", HexNum),
  145. <<Acc0/binary, Byte:8>>
  146. end,
  147. Acc,
  148. HexNums),
  149. hexdump_to_bin(Rest, Acc1).
  150. hexdump_to_bin_test() ->
  151. HexDump =
  152. "0e 00 00 00 03 73 65 6c 65 63 74 20 55 53 45 52 .....select USER"
  153. "28 29 ()",
  154. Expect = <<16#0e, 16#00, 16#00, 16#00, 16#03, 16#73, 16#65, 16#6c,
  155. 16#65, 16#63, 16#74, 16#20, 16#55, 16#53, 16#45, 16#52,
  156. 16#28, 16#29>>,
  157. ?assertEqual(Expect, hexdump_to_bin(HexDump)).