epgsql_pool_SUITE.erl 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. -module(epgsql_pool_SUITE).
  2. %% test needs connection to database
  3. %% and database should be inited with ./testdb_schema.sql
  4. -include("epgsql_pool.hrl").
  5. -include_lib("epgsql/include/epgsql.hrl").
  6. -include_lib("common_test/include/ct.hrl").
  7. -include_lib("eunit/include/eunit.hrl").
  8. -export([all/0,
  9. init_per_suite/1, end_per_suite/1, init_per_testcase/2, end_per_testcase/2,
  10. query_test/1, squery_test/1, transaction_test/1, reconnect_test/1, timeout_test/1,
  11. validate_connection_params_test/1
  12. ]).
  13. -define(SELECT_ITEMS_QUERY, "SELECT id, category_id, title, num FROM item ORDER by id ASC").
  14. -type config() :: proplists:proplist().
  15. -spec all() -> [atom()].
  16. all() ->
  17. [query_test,
  18. squery_test,
  19. transaction_test,
  20. reconnect_test,
  21. timeout_test,
  22. validate_connection_params_test
  23. ].
  24. -spec init_per_suite(config()) -> config().
  25. init_per_suite(Config) ->
  26. application:ensure_all_started(epgsql_pool),
  27. Config.
  28. -spec end_per_suite(config()) -> config().
  29. end_per_suite(Config) ->
  30. application:stop(epgsql_pool),
  31. Config.
  32. -spec init_per_testcase(atom(), config()) -> config().
  33. init_per_testcase(_TestCase, Config) ->
  34. Params = #epgsql_connection_params{host = "localhost", port = 5432, username = "test", password = "test", database = "testdb"},
  35. {ok, _} = epgsql_pool:start(my_pool, 5, 10, Params),
  36. epgsql_pool:query(my_pool, "TRUNCATE TABLE item"),
  37. epgsql_pool:query(my_pool, "TRUNCATE TABLE category CASCADE"),
  38. Config.
  39. -spec end_per_testcase(atom(), config()) -> config().
  40. end_per_testcase(_TestCase, Config) ->
  41. ok = epgsql_pool:stop(my_pool),
  42. Config.
  43. -spec query_test(config()) -> ok.
  44. query_test(_Config) ->
  45. {ok, 3, _, Ids} = epgsql_pool:query(my_pool,
  46. "INSERT INTO category (title) "
  47. "VALUES ('cat 1'), ('cat 2'), ('cat 3') "
  48. "RETURNING id"),
  49. WaitForRows = lists:map(fun({{Id}, Title}) -> {Id, Title} end,
  50. lists:zip(Ids, [<<"cat 1">>, <<"cat 2">>, <<"cat 3">>])),
  51. {ok, _, Rows} = epgsql_pool:query(my_pool, "SELECT id, title FROM category ORDER by id ASC"),
  52. ct:log("Rows ~p", [Rows]),
  53. ?assertEqual(WaitForRows, Rows),
  54. [{Id1}, {Id2} | _] = Ids,
  55. {ok, _, Rows2} = epgsql_pool:query(my_pool, "SELECT id, title FROM category WHERE id = $1 OR id = $2 ORDER BY id ASC", [Id1, Id2]),
  56. ct:log("Rows2 ~p", [Rows2]),
  57. ?assertEqual([{Id1, <<"cat 1">>}, {Id2, <<"cat 2">>}], Rows2),
  58. {error, Error} = epgsql_pool:query(my_pool, "SELECT id, title FROM some_table"),
  59. ct:log("Error:~p", [Error]),
  60. ?assertMatch(#error{severity = error, message = <<"relation \"some_table\" does not exist">>}, Error),
  61. ok.
  62. -spec squery_test(config()) -> ok.
  63. squery_test(Config) ->
  64. {ok, 3, _, Ids} = epgsql_pool:squery(my_pool,
  65. "INSERT INTO category (title) "
  66. "VALUES ('cat 1'), ('cat 2'), ('cat 3') "
  67. "RETURNING id"),
  68. WaitForRows = lists:map(fun({{Id}, Title}) -> {Id, Title} end,
  69. lists:zip(Ids, [<<"cat 1">>, <<"cat 2">>, <<"cat 3">>])),
  70. {ok, _, Rows} = epgsql_pool:squery(my_pool, "SELECT id, title FROM category ORDER by id ASC"),
  71. ct:log("Rows ~p", [Rows]),
  72. ?assertEqual(WaitForRows, Rows),
  73. ?assertMatch([{ok, _, [{<<"1">>}]}, {ok, _, [{<<"2">>}]}],
  74. epgsql_pool:squery(my_pool, "SELECT 1; SELECT 2;")),
  75. ?assertMatch({error, #error{severity = error, message = <<"relation \"some_table\" does not exist">>}},
  76. epgsql_pool:squery(my_pool, "SELECT * FROM some_table")),
  77. Config.
  78. -spec transaction_test(config()) -> ok.
  79. transaction_test(_Config) ->
  80. {FirstCatId, CatIds2, ItemIds2} =
  81. epgsql_pool:transaction(my_pool,
  82. fun(Worker) ->
  83. ct:log("worker:~p", [Worker]),
  84. {ok, 3, _, CatIds0} =
  85. epgsql_pool:query(Worker,
  86. "INSERT INTO category (title) "
  87. "VALUES ('cat 4'), ('cat 5'), ('cat 6') "
  88. "RETURNING id"),
  89. CatIds1 = lists:map(fun({Cid}) -> Cid end, CatIds0),
  90. CatId = hd(CatIds1),
  91. {ok, 2, _, ItemIds0} =
  92. epgsql_pool:query(Worker,
  93. "INSERT INTO item (category_id, title, num) "
  94. "VALUES ($1, 'item 1', 5), ($1, 'item 2', 7) "
  95. "RETURNING id", [CatId]),
  96. ItemIds1 = lists:map(fun({Iid}) -> Iid end, ItemIds0),
  97. {CatId, CatIds1, ItemIds1}
  98. end),
  99. WaitForCats = lists:zip(CatIds2, [<<"cat 4">>, <<"cat 5">>, <<"cat 6">>]),
  100. {ok, _, CatRows} = epgsql_pool:query(my_pool, "SELECT id, title FROM category ORDER by id ASC"),
  101. ct:log("CatRows ~p", [CatRows]),
  102. ?assertEqual(WaitForCats, CatRows),
  103. WaitForItems = lists:map(fun({ItemId, {Title, Num}}) -> {ItemId, FirstCatId, Title, Num} end,
  104. lists:zip(ItemIds2, [{<<"item 1">>, 5}, {<<"item 2">>, 7}])),
  105. {ok, _, ItemRows} = epgsql_pool:query(my_pool, ?SELECT_ITEMS_QUERY),
  106. ct:log("ItemRows ~p", [ItemRows]),
  107. ?assertEqual(WaitForItems, ItemRows),
  108. try
  109. epgsql_pool:transaction(my_pool,
  110. fun(Worker) ->
  111. ct:log("worker:~p", [Worker]),
  112. {ok, 2} =
  113. epgsql_pool:query(Worker,
  114. "INSERT INTO item (category_id, title, num) "
  115. "VALUES ($1, 'item 3', 55), ($1, 'item 4', 77) ",
  116. [FirstCatId]),
  117. {ok, _, ItemRows2} = epgsql_pool:query(Worker, ?SELECT_ITEMS_QUERY),
  118. ct:log("ItemRows2 ~p", [ItemRows2]),
  119. ?assertMatch([{_, FirstCatId, <<"item 1">>, 5},
  120. {_, FirstCatId, <<"item 2">>, 7},
  121. {_, FirstCatId, <<"item 3">>, 55},
  122. {_, FirstCatId, <<"item 4">>, 77}],
  123. ItemRows2),
  124. throw(cancel_transaction)
  125. end)
  126. catch
  127. throw:cancel_transaction -> ok
  128. end,
  129. %% items not changes after calcelled transaction
  130. {ok, _, ItemRows} = epgsql_pool:query(my_pool, ?SELECT_ITEMS_QUERY),
  131. ok.
  132. -spec reconnect_test(config()) -> ok.
  133. reconnect_test(_Config) ->
  134. Worker = pooler:take_member(my_pool, 1000),
  135. [state, my_pool, #epgsql_connection{sock = Sock1} | _]= tuple_to_list(sys:get_state(Worker)),
  136. ct:log("Worker: ~p, sock: ~p", [Worker, Sock1]),
  137. R1 = epgsql_pool:query(Worker, ?SELECT_ITEMS_QUERY),
  138. ct:log("first query ~p", [R1]),
  139. {ok, _, []} = R1,
  140. ct:log("~p close_connection", [Sock1]),
  141. exit(Sock1, close_connection),
  142. R2 = epgsql_pool:query(Worker, "select * from item"),
  143. ct:log("second query goes immediatelly ~p", [R2]),
  144. {error, no_connection} = R2,
  145. timer:sleep(50),
  146. R3 = epgsql_pool:query(Worker, "select * from item"),
  147. ct:log("third query goes after 50 ms ~p", [R3]),
  148. {error, no_connection} = R3,
  149. timer:sleep(150),
  150. R4 = epgsql_pool:query(Worker, "select * from item"),
  151. ct:log("fouth query goes after 200 ms ~p", [R4]),
  152. {ok, _, []} = R4,
  153. [state, my_pool, #epgsql_connection{sock = Sock2} | _]= tuple_to_list(sys:get_state(Worker)),
  154. ct:log("Worker: ~p, sock: ~p", [Worker, Sock2]),
  155. ?assertNotEqual(Sock1, Sock2),
  156. ok.
  157. -spec timeout_test(config()) -> ok.
  158. timeout_test(_Config) ->
  159. Res1 = epgsql_pool:query(my_pool, "SELECT pg_sleep(1)", [], [{timeout, 2000}]),
  160. ct:log("Res1:~p", [Res1]),
  161. ?assertMatch({ok, _, _}, Res1),
  162. Res2 = epgsql_pool:query(my_pool, "SELECT pg_sleep(1)", [], [{timeout, 500}]),
  163. ct:log("Res2:~p", [Res2]),
  164. ?assertEqual({error, timeout}, Res2),
  165. Worker = pooler:take_member(my_pool, 1000),
  166. Res3 = epgsql_pool:query(Worker, "SELECT pg_sleep(100)", [], [{timeout, 500}]),
  167. ct:log("Res3:~p", [Res3]),
  168. ?assertEqual({error, timeout}, Res3),
  169. %% check worker and connection able to perform query
  170. Res4 = epgsql_pool:query(Worker, "SELECT * FROM item", [], [{timeout, 500}]),
  171. ct:log("Res4:~p", [Res4]),
  172. ?assertMatch({ok, _, _}, Res4),
  173. ok.
  174. -spec validate_connection_params_test(config()) -> ok.
  175. validate_connection_params_test(_Config) ->
  176. Params1 = #epgsql_connection_params{host = "localhost", port = 5432,
  177. username = "test", password = "test", database = "testdb"},
  178. Res1 = epgsql_pool:validate_connection_params(Params1),
  179. ct:log("Res1: ~p", [Res1]),
  180. ?assertEqual(ok, Res1),
  181. Params2 = #epgsql_connection_params{host = "localhost", port = 5432,
  182. username = "test", password = "some", database = "testdb"},
  183. Res2 = epgsql_pool:validate_connection_params(Params2),
  184. ct:log("Res2: ~p", [Res2]),
  185. ?assertEqual({error,invalid_password}, Res2),
  186. Params3 = #epgsql_connection_params{host = "localhost", port = 5432,
  187. username = "test", password = "test", database = "some"},
  188. {error, Res3} = epgsql_pool:validate_connection_params(Params3),
  189. ct:log("Res3: ~p", [Res3]),
  190. ?assertMatch(#error{
  191. severity = fatal,
  192. code = <<"3D000">>,
  193. codename = invalid_catalog_name,
  194. message = <<"database \"some\" does not exist">>
  195. }, Res3),
  196. ok.