Triggers e Integridade
Triggers e Integridade
Introdução
A utilização de triggers em bancos de dados relacionais representa uma abordagem avançada para automatizar ações e reforçar regras de negócio diretamente no nível do banco. Segundo Elmasri e Navathe, triggers são procedimentos armazenados que são automaticamente executados em resposta a determinados eventos, como inserções, atualizações ou exclusões de dados em tabelas específicas.
Este documento apresenta a aplicação de triggers no sistema de gerenciamento de partidas com cartas, demonstrando como elas contribuem para a manutenção da integridade, automação de processos e resposta a eventos críticos, garantindo que o sistema opere de acordo com os padrões e restrições previamente definidos.
1. Proteção de inserção em partidas e cartas
Este trigger serve para impedir inserções diretas nas tabelas partida e carta_partida, garantindo que apenas procedures seguras sejam utilizadas.
Comandos
-- Impede inserção direta na tabela 'partida'
CREATE OR REPLACE FUNCTION bloquear_insert_partida()
RETURNS TRIGGER AS $$
BEGIN
IF current_setting('app.criacao_partida_autorizada', true) = 'true' THEN
RETURN NEW;
END IF;
RAISE EXCEPTION 'Inserção direta em "partida" não permitida! Use: SELECT * FROM iniciar_partida_segura(id_jogador)';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_bloquear_insert_partida
BEFORE INSERT ON partida
FOR EACH ROW
EXECUTE FUNCTION bloquear_insert_partida();
-- Impede inserção direta na tabela 'carta_partida'
CREATE OR REPLACE FUNCTION bloquear_insert_carta_partida()
RETURNS TRIGGER AS $$
BEGIN
IF current_setting('app.criacao_partida_autorizada', true) = 'true' OR
current_setting('app.exclusao_autorizada', true) = 'true' THEN
RETURN NEW;
END IF;
RAISE EXCEPTION 'Inserção direta em "carta_partida" não permitida! Use functions seguras.';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_bloquear_insert_carta_partida
BEFORE INSERT ON carta_partida
FOR EACH ROW
EXECUTE FUNCTION bloquear_insert_carta_partida();
2. Validação de integridade da partida
Este trigger serve para verificar se uma partida foi criada corretamente com 14 cartas (7 porta + 7 tesouro) na mão do jogador.
Comandos
-- Valida quantidade e tipo de cartas na mão ao criar uma partida
CREATE OR REPLACE FUNCTION validar_integridade_partida()
RETURNS TRIGGER AS $$
DECLARE
qtd_cartas INTEGER;
qtd_porta INTEGER;
qtd_tesouro INTEGER;
BEGIN
IF current_setting('app.criacao_partida_autorizada', true) = 'true' THEN
RETURN NEW;
END IF;
PERFORM pg_sleep(0.05);
SELECT COUNT(*) INTO qtd_cartas
FROM carta_partida WHERE id_partida = NEW.id_partida AND zona = 'mao';
SELECT COUNT(*) INTO qtd_porta
FROM carta_partida cp
JOIN carta c ON c.id_carta = cp.id_carta
WHERE cp.id_partida = NEW.id_partida AND cp.zona = 'mao' AND c.tipo_carta = 'porta';
SELECT COUNT(*) INTO qtd_tesouro
FROM carta_partida cp
JOIN carta c ON c.id_carta = cp.id_carta
WHERE cp.id_partida = NEW.id_partida AND cp.zona = 'mao' AND c.tipo_carta = 'tesouro';
IF qtd_cartas < 14 THEN
RAISE EXCEPTION 'Partida % tem apenas % cartas na mão (mínimo: 14)!', NEW.id_partida, qtd_cartas;
END IF;
IF qtd_porta < 7 OR qtd_tesouro < 7 THEN
RAISE EXCEPTION 'Distribuição inválida: % porta, % tesouro (mínimo: 7 de cada)', NEW.id_partida, qtd_porta, qtd_tesouro;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE CONSTRAINT TRIGGER trigger_validar_integridade_partida
AFTER INSERT ON partida
DEFERRABLE INITIALLY DEFERRED
FOR EACH ROW
EXECUTE FUNCTION validar_integridade_partida();
3. Bloqueio de exclusões diretas
Este trigger serve para impedir exclusões diretas de jogadores e partidas, garantindo que apenas procedures seguras sejam utilizadas.
Comandos
-- Impede exclusão direta de jogador
CREATE OR REPLACE FUNCTION bloquear_delete_jogador()
RETURNS TRIGGER AS $$
BEGIN
IF current_setting('app.exclusao_autorizada', true) = 'true' THEN
RETURN OLD;
END IF;
RAISE EXCEPTION 'Exclusão direta de jogador não permitida! Use: CALL excluir_jogador_completo(%)', OLD.id_jogador;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_bloquear_delete_jogador
BEFORE DELETE ON jogador
FOR EACH ROW
EXECUTE FUNCTION bloquear_delete_jogador();
-- Impede exclusão direta de partida
CREATE OR REPLACE FUNCTION bloquear_delete_partida()
RETURNS TRIGGER AS $$
BEGIN
IF current_setting('app.exclusao_autorizada', true) = 'true' THEN
RETURN OLD;
END IF;
RAISE EXCEPTION 'Exclusão direta de partida não permitida!';
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_bloquear_delete_partida
BEFORE DELETE ON partida
FOR EACH ROW
EXECUTE FUNCTION bloquear_delete_partida();
4. Bloqueio de atualização direta de zona
Este trigger serve para impedir alterações diretas na zona das cartas, garantindo que movimentações sejam feitas apenas via procedures seguras.
Comandos
-- Impede alteração direta da zona de uma carta
CREATE OR REPLACE FUNCTION bloquear_update_zona()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.zona IS DISTINCT FROM OLD.zona THEN
IF current_setting('app.mudanca_zona_autorizada', true) = 'true' THEN
RETURN NEW;
END IF;
RAISE EXCEPTION 'Atualização da zona não permitida diretamente! Use procedure segura.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_bloquear_update_zona
BEFORE UPDATE ON carta_partida
FOR EACH ROW
EXECUTE FUNCTION bloquear_update_zona();
5. Validação de slots equipados
Este trigger serve para validar se múltiplos itens podem ser equipados no mesmo slot, impedindo conflitos de equipamento.
Comandos
-- Impede múltiplos itens equipados no mesmo slot
CREATE OR REPLACE FUNCTION validar_limite_slot_equipado()
RETURNS TRIGGER AS $$
DECLARE
v_slot VARCHAR(20);
v_ocupacao_dupla BOOLEAN;
v_subtipo carta.subtipo%TYPE;
v_conflito INTEGER;
BEGIN
IF NEW.zona != 'equipado' THEN
RETURN NEW;
END IF;
SELECT ci.slot, ci.ocupacao_dupla, c.subtipo
INTO v_slot, v_ocupacao_dupla, v_subtipo
FROM carta_item ci
JOIN carta c ON c.id_carta = ci.id_carta
WHERE ci.id_carta = NEW.id_carta;
IF v_subtipo != 'item' THEN
RETURN NEW;
END IF;
SELECT COUNT(*) INTO v_conflito
FROM carta_partida cp
JOIN carta_item ci ON ci.id_carta = cp.id_carta
JOIN carta c ON c.id_carta = cp.id_carta
WHERE cp.id_partida = NEW.id_partida
AND cp.zona = 'equipado'
AND ci.slot = v_slot
AND cp.id_carta != NEW.id_carta;
IF v_conflito > 0 AND NOT v_ocupacao_dupla THEN
RAISE EXCEPTION '❌ Slot "%": já existe item equipado.', v_slot;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_validar_limites_equipados
AFTER UPDATE ON carta_partida
FOR EACH ROW
WHEN (OLD.zona IS DISTINCT FROM NEW.zona AND NEW.zona = 'equipado')
EXECUTE FUNCTION validar_limite_slot_equipado();
Referência Bibliográfica
[1] ELMASRI, Ramez; NAVATHE, Shamkant B. Sistemas de banco de dados. 6. ed. São Paulo: Pearson Addison Wesley, 2011.
Versionamento
Versão | Data | Modificação | Autor(es) |
---|---|---|---|
0.1 | 06/07/2025 | Criação do Documento e triggers | Mylena Mendonça |
1.0 | 06/07/2025 | Continuação do desenvolvimento | Maria Clara Sena |
1.1 | 07/07/2025 | Padronização e ajustes de formatação | Breno Soares Fernandes |