diff --git a/apps/db/src/db_data.erl b/apps/db/src/db_data.erl index 658ce4e..35b59d4 100644 --- a/apps/db/src/db_data.erl +++ b/apps/db/src/db_data.erl @@ -1,27 +1,21 @@ -module(db_data). -export([obtem_cliente/1]). --export([obtem_saldo/1]). --export([salva_transacao/2]). -export([obtem_transacoes/1]). +-export([salva_transacao/2]). obtem_cliente(Id) -> - pgo:query("select * from clientes where id=$1", [Id]). - -obtem_saldo(Id) -> - pgo:query("select valor from saldos where cliente_id=$1", [Id]). + pgo:query("select id, saldo, limite from clientes where id=$1", [Id]). obtem_transacoes(Id) -> pgo:query("select valor, tipo, descricao, realizada_em from transacoes where cliente_id=$1 order by id desc limit 10", [Id]). - + salva_transacao(Id, {Valor, <<"c">>, Descricao}) -> - pgo:transaction(fun () -> - pgo:query("insert into transacoes (id, cliente_id, valor, tipo, descricao, realizada_em) values (default, $1, $2, 'c', $3, now())", [Id, Valor, Descricao]), - pgo:query("update saldos set valor = valor + $2 where cliente_id=$1 returning valor", [Id, Valor]) + pgo:transaction(fun() -> + pgo:query("select transacao_credito($1, 'c', $2, $3)", [Id, Valor, Descricao]) end); salva_transacao(Id, {Valor, <<"d">>, Descricao}) -> - pgo:transaction(fun () -> - pgo:query("insert into transacoes (id, cliente_id, valor, tipo, descricao, realizada_em) values (default, $1, $2, 'd', $3, now())", [Id, Valor, Descricao]), - pgo:query("update saldos set valor = valor + $2 where cliente_id=$1 returning valor", [Id, -1*Valor]) + pgo:transaction(fun() -> + pgo:query("select transacao_debito($1, 'd', $2, $3)", [Id, Valor, Descricao]) end). diff --git a/apps/rinha/src/extrato.erl b/apps/rinha/src/extrato.erl index e2155eb..6555268 100644 --- a/apps/rinha/src/extrato.erl +++ b/apps/rinha/src/extrato.erl @@ -35,25 +35,15 @@ manda_extrato(Req, Saldo, Transacoes) -> #{<<"content-type">> => <<"application/json">>}, formt_resp(Saldo, Transacoes), Req)}. -obtem_saldo(Id) -> - case db_data:obtem_saldo(Id) of - #{rows := [{Saldo}]} -> {saldo, Saldo}; - _ -> banco - end. - init(Req0=#{method := <<"GET">>}, State) -> case db_data:obtem_cliente(cowboy_req:binding(id, Req0)) of - #{rows := []} -> {ok, cowboy_req:reply(404, Req0), State}; - #{rows := [{Id, _, Limite}]} -> - case obtem_saldo(Id) of - {saldo, Saldo} -> - case db_data:obtem_transacoes(Id) of - #{rows := Transacoes} -> manda_extrato(Req0, {Limite, Saldo}, Transacoes); - _ -> {ok, cowboy_req:reply(500, Req0), State} - end; - banco -> {ok, cowboy_req:reply(500, Req0), State} + #{rows := [{Id, Saldo, Limite}]} -> + case db_data:obtem_transacoes(Id) of + #{rows := Transacoes} -> manda_extrato(Req0, {Limite, Saldo}, Transacoes); + _ -> {ok, cowboy_req:reply(500, Req0), State} end; - _ -> {ok, cowboy_req:reply(404, Req0), State} + #{rows := []} -> {ok, cowboy_req:reply(404, Req0), State}; + _ -> {ok, cowboy_req:reply(500, Req0), State} end; diff --git a/apps/rinha/src/rinha_app.erl b/apps/rinha/src/rinha_app.erl index 68e516f..bd418dd 100644 --- a/apps/rinha/src/rinha_app.erl +++ b/apps/rinha/src/rinha_app.erl @@ -10,7 +10,9 @@ -export([start/2, stop/1]). start(_Type, _Args) -> - application:ensure_all_started(pgo), + application:ensure_all_started(pgo, db), + {ok, Porta} = application:get_env(rinha, porta), + io:format("~p~n", [Porta]), Dispatch = cowboy_router:compile([ %% {HostMatchm list({PatchMatch, Constraints, Handler, InitialState})} {'_', [ @@ -22,7 +24,7 @@ start(_Type, _Args) -> ]), persistent_term:put(erlang_rinher_dispatch, Dispatch), - {ok, _} = cowboy:start_clear(erlang_rinher, [{port, 6969}], + {ok, _} = cowboy:start_clear(erlang_rinher, [Porta], #{env => #{dispatch => {persistent_term, erlang_rinher_dispatch}}} ), diff --git a/apps/rinha/src/transacoes.erl b/apps/rinha/src/transacoes.erl index 43d14b9..801008d 100644 --- a/apps/rinha/src/transacoes.erl +++ b/apps/rinha/src/transacoes.erl @@ -43,56 +43,33 @@ valida_transacao(Info) -> Transacao -> valida_transacao(Transacao) end. -salva_transacao(Id, Limite, {_, <<"c">>, _} = T) -> - case db_data:salva_transacao(Id, T) of - #{rows := [{Saldo}]} -> {salvo, {Saldo, Limite}}; - _ -> banco - end; - -salva_transacao(Id, Limite, {Valor, <<"d">>, _} = T) -> - case db_data:obtem_saldo(Id) of - #{rows := [{Saldo}]} -> - if (Saldo - Valor) >= (-1 * Limite) -> - case db_data:salva_transacao(Id, T) of - #{rows := [{NovoSaldo}]} -> {salvo, {NovoSaldo, Limite}}; - _ -> banco - end; - true -> inconsistente - end; - _ -> banco +salva_transacao(Id, Transacao) -> + case catch db_data:salva_transacao(Id, Transacao) of + #{rows := [{{Id, <<"nao_encontrado">>}}]} -> nao_encontrou; + #{rows := [{{Id, <<"inconsistente">>}}]} -> inconsistente; + #{rows := [{{Saldo, Limite}}]} -> {salvo, {Saldo, Limite}}; + {error, none_available} -> banco end. -faz_transacao({Id, _, Limite}, [Body]) -> +faz_transacao(Id, [Body]) -> case Body of {Info, true} -> case valida_transacao(Info) of - {transacao, T} -> salva_transacao(Id, Limite, T); + {transacao, T} -> salva_transacao(Id, T); invalido -> invalido end; {_, false} -> vazio end. init(Req0=#{method := <<"POST">>, headers := #{<<"content-type">> := <<"application/json">>}}, State) -> - case db_data:obtem_cliente(cowboy_req:binding(id, Req0)) of - #{rows := [Cliente]} -> - {ok, Body, Req} = cowboy_req:read_urlencoded_body(Req0), - case faz_transacao(Cliente, Body) of - {salvo, Json} -> - {ok, cowboy_req:reply(200, #{<<"content-type">> => <<"application/json">>}, formt_resp(Json), Req)}; - vazio -> - %%io:format("req vazio~n"), - {ok, cowboy_req:reply(422, Req), State}; - invalido -> - io:format("transacao invalida~n"), - {ok, cowboy_req:reply(422, Req), State}; - inconsistente -> - %%io:format("transacao inconsistente~n"), - {ok, cowboy_req:reply(422, Req), State}; - banco -> - {ok, cowboy_req:reply(500, Req), State} - end; - #{rows := []} -> {ok, cowboy_req:reply(404, Req0), State}; - _ -> {ok, cowboy_req:reply(404, Req0), State} + {ok, Body, Req} = cowboy_req:read_urlencoded_body(Req0), + case faz_transacao(cowboy_req:binding(id, Req), Body) of + {salvo, Json} -> {ok, cowboy_req:reply(200, #{<<"content-type">> => <<"application/json">>}, formt_resp(Json), Req)}; + nao_encontrou -> {ok, cowboy_req:reply(404, Req0), State}; + inconsistente -> {ok, cowboy_req:reply(422, Req0), State}; + invalido -> {ok, cowboy_req:reply(422, Req0), State}; + vazio -> {ok, cowboy_req:reply(422, Req0), State}; + banco -> {ok, cowboy_req:reply(500, Req0), State} end; init(Req, State) -> diff --git a/config/sys.config b/config/sys.config index c02c4da..0799c15 100644 --- a/config/sys.config +++ b/config/sys.config @@ -1,7 +1,9 @@ [ - {rinha, []}, + {rinha, [ + {porta, {port, 6969}} + ]}, {db, [{pools, [{default, #{pool_size => 8, - host => "db", + host => "db", database => "rinha", user => "admin", password => "123"}}]}]} diff --git a/init.sql b/init.sql index 660c86f..43c3572 100644 --- a/init.sql +++ b/init.sql @@ -1,6 +1,6 @@ CREATE TABLE clientes ( id SERIAL PRIMARY KEY, - nome VARCHAR(50) NOT NULL, + saldo INTEGER NOT NULL, limite INTEGER NOT NULL ); @@ -15,24 +15,76 @@ CREATE TABLE transacoes ( FOREIGN KEY (cliente_id) REFERENCES clientes(id) ); -CREATE TABLE saldos ( - id SERIAL PRIMARY KEY, - cliente_id INTEGER NOT NULL, - valor INTEGER NOT NULL, - CONSTRAINT fk_clientes_saldos_id - FOREIGN KEY (cliente_id) REFERENCES clientes(id) -); - DO $$ BEGIN - INSERT INTO clientes (nome, limite) - VALUES ('Cleiton Rasta', 1000 * 100), - ('Alonzo Church', 800 * 100), - ('Marcos Valle', 10000 * 100), - ('Vinicius de Moraes', 100000 * 100), - ('Jose Raul Capablanca', 5000 * 100); - - INSERT INTO saldos (cliente_id, valor) - SELECT id, 0 FROM clientes; + INSERT INTO clientes (saldo, limite) + VALUES (0, 1000 * 100), + (0, 800 * 100), + (0, 10000 * 100), + (0, 100000 * 100), + (0, 5000 * 100); END; $$; + +CREATE FUNCTION transacao_credito( + id_cliente integer, + tipo_salva char(1), + valor_salva integer, + descricao_salva varchar(10) +) RETURNS RECORD AS $$ +DECLARE + cliente clientes%ROWTYPE; + retorno RECORD; +BEGIN + SELECT * INTO cliente FROM clientes WHERE id = id_cliente; + + IF NOT FOUND THEN + RETORNO := (id_cliente, 'nao_encontrado'); + RETURN retorno; + END IF; + + UPDATE clientes + SET saldo = saldo + valor_salva + WHERE id = id_cliente + RETURNING saldo, limite INTO retorno; + + INSERT INTO transacoes (id, cliente_id, valor, tipo, descricao, realizada_em) + VALUES (DEFAULT, id_cliente, valor_salva, tipo_salva, descricao_salva, NOW()); + + RETURN retorno; +END; +$$ LANGUAGE plpgsql; + +CREATE FUNCTION transacao_debito( + id_cliente integer, + tipo_salva char(1), + valor_salva integer, + descricao_salva varchar(10) +) RETURNS RECORD AS $$ +DECLARE + cliente clientes%ROWTYPE; + retorno RECORD; +BEGIN + SELECT * INTO cliente FROM clientes WHERE id = id_cliente; + + IF NOT FOUND THEN + RETORNO := (id_cliente, 'nao_encontrado'); + RETURN retorno; + END IF; + + IF (cliente.saldo - valor_salva) >= (-1 * cliente.limite) THEN + UPDATE clientes + SET saldo = saldo + valor_salva + WHERE id = id_cliente + RETURNING saldo, limite INTO retorno; + ELSE + RETORNO := (id_cliente, 'inconsistente'); + RETURN retorno; + END IF; + + INSERT INTO transacoes (id, cliente_id, valor, tipo, descricao, realizada_em) + VALUES (DEFAULT, id_cliente, valor_salva, tipo_salva, descricao_salva, NOW()); + + RETURN retorno; +END; +$$ LANGUAGE plpgsql;