drop schema if exists l0 cascade;
create schema l0;

-- tabela de contas
create table l0.account (

	-- identificadores não são criados aqui
	aid bigint primary key,

	-- saldo da conta
	balance numeric not null default 0,

	-- outros campos
	"..." text --,

	-- saldo positivo para contas de correntistas
--	constraint non_negative_balance check (balance >= 0 or aid = 0)
);

-- conta do banco com aid=0 pode ter saldo negativo
insert into l0.account(aid) values (0);

-- tabela de transações financeiras
create table l0.transaction (

	-- identificador artificial
	tid bigint primary key generated always as identity,

	-- momento da transação
	ts timestamptz default now() not null,

	-- outros campos
	"..." text
);

-- tabela de mudanças de saldo causadas pela transação
create table l0.entry (

	-- identificador da conta
	aid bigint not null references l0.account(aid),

	-- identificador da transação
	tid bigint not null references l0.transaction(tid),

	-- valor da mudança na conta feito por essa transação
	value numeric not null,

	primary key (aid,tid)
);


CREATE OR REPLACE FUNCTION l0.cria_conta(bigint, numeric, text DEFAULT NULL::text)
RETURNS void
LANGUAGE sql
AS $function$
	insert into l0.account(aid, balance, "...")
		values ($1, $2, $3)
$function$;


CREATE OR REPLACE FUNCTION l0.consulta_saldo(bigint)
RETURNS numeric
LANGUAGE sql
AS $function$
	select balance
		from l0.account
		where aid = $1
	union all
		select 0
	limit 1
$function$;


CREATE OR REPLACE FUNCTION l0.transfere_valor(de_aid bigint, para_aid bigint, valor numeric, outros text DEFAULT NULL::text)
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
	_tid bigint;
begin

	-- transação iniciando com locks de longa duração
	-- insert on conflict do update + deadlock
	insert into l0.account (aid, balance)
		values
			(de_id, -valor)
		on conflict do
		update set balance = balance + excluded.balance;
	insert into l0.account (aid, balance)
		values
			(para_id, valor)
		on conflict do
		update set balance = balance + excluded.balance;

	-- transação financeira
	insert into l0.transaction ("...")
		values
			(outros)
		returning tid
		into _tid;

	-- entradas na transação
	insert into l0.entry (aid, tid, value)
		values
			(de_aid, _tid, -valor),
			(para_aid, _tid, valor);

end;
$function$;

