Знайомство з Erlang [16]

epgsql_pool transactions
розглянемо роботу з postgresql-транзакціями при користуванні
epgsql та epgsql_pool на прикладі https://github.com/221V/eauc

обгортки звичайних запитів виглядають так:

select(Q,A) ->
  case epgsql_pool:query(my_main_pool, Q, A) of
    {ok,_,R} ->
      R;
    {error,E} ->
      io:format("~p~n", [E]),
      {error,E}
end.

in_up_del(Q,A) ->
  case epgsql_pool:query(my_main_pool, Q, A) of
    {ok,R} ->
      R;
    {error,E} ->
      io:format("~p~n", [E]),
      {error,E}
end.

returning(Q,A) ->
  case epgsql_pool:query(my_main_pool, Q, A) of
    {ok,1,_,R} ->
      R;
    {error,E} ->
      io:format("~p~n", [E]),
      {error,E}
end.

при підключенні epgsql_pool ми створюємо нову обгортку для транзакцій,
і відповідно, обгортку для запитів, виконуваних в транзакції
( транзакція вимагає явно вказувати процес пула )

transaction(Fun) ->
  case epgsql_pool:transaction(my_main_pool, Fun) of
    {ok, _} ->
      ok;
    Error ->
      io:format("transaction error: ~p~n in tr fun: ~p~n", [Error, Fun]),
      Error
end.

transaction_q(Worker, Q, A) ->
  epgsql_pool:query(Worker, Q, A).

запит виглядає так:

make_new_bet(Worker, Lot_Id, Nickname, User_Id, Bet_Add, Bet_Total) ->
  pg:transaction_q(Worker, "INSERT INTO eauc_bets (lot_id, nickname, uid, bet_add, bet_total) VALUES ($1, $2, $3, $4, $5)", [Lot_Id, Nickname, User_Id, Bet_Add, Bet_Total]).

транзакція виглядає так:
( як приклад, можна ще перевіряти результати окремих запитів всередині транзації )

ResultN = pg:transaction(fun(Worker) ->
  %% transaction
  
  pq:make_new_bet(Worker, Lot_Id, Nickname, User_Id, Bet, Bet),
  pq:update_user_money(Worker, User_Id, Money_New),
  pq:make_money_log(Worker, User_Id, User_Money, Bet, 1),
  pq:update_lot_info(Worker, Lot_Id, Bet, Nickname, User_Id)
  
end),

case ResultN of
  ok ->
    %% transaction ok
    ...
    ;
  _ ->
    %% transaction err
    ...
end;

виключення і помилки всередині транзакції відміняють всі запити транзакції:
( приклад з readme https://github.com/wgnet/epgsql_pool )

epgsql_pool:transaction(my_pool,
  fun(Worker) ->
    Res1 = epgsql_pool:query(Worker, Query1),
    ...
    case SomeData of
      GoodResult -> do_something;
      BadResult -> throw(cancel_transaction)
    end,
    ResN = epgsql_pool:query(Worker, QueryN)
  end).

окремо замітка про налаштування - sys.config

{epgsql_pool,
  [{connection_timeout, 5000},
  {query_timeout, 3000},
  {transaction_timeout, 5000} ]}

це різні таймаути, рекомендується залишати значення по-замовчуванню

і ще один момент -- транзакція повинна виконуватись якомога скоріше,
і бажано не виконувати http-запитів всередині транзакції :)

Продовження

Посилання

https://github.com/wgnet/epgsql_pool
https://github.com/epgsql/epgsql