Atualmente, as restrições de chave estrangeira são implementadas com gatilhos internos especiais. Todos eles são executados FOR EACH ROW.
Observe que esses são detalhes de implementação que podem mudar, portanto, não confie neles. Mas o básico não mudou nas últimas duas versões principais, portanto, mudanças importantes são improváveis.
Fiz um teste rápido com uma restrição FK simples de tblpara tbltype. Um FK simples é implementado com quatro gatilhos internos simples FOR EACH ROWno meu teste na página 9.4.
Aqui está um rápido resumo de como investigar:
SELECT oid -- 74791
FROM pg_constraint
WHERE conrelid = 'tbl'::regclass
AND contype = 'f';
SELECT objid, classid::regclass -- 74792,74793,74794,74795 / 'pg_trigger'
FROM pg_depend
WHERE refobjid = 74791
AND deptype = 'i'
SELECT tgrelid::regclass, tgname, tgfoid, tgtype FROM pg_trigger
WHERE oid IN (74792,74793,74794,74795) ORDER BY tgfoid;
'tbl' ;'RI_ConstraintTrigger_c_74794';1644;5
'tbl' ;'RI_ConstraintTrigger_c_74795';1645;17
'tbltype';'RI_ConstraintTrigger_a_74792';1654;9
'tbltype';'RI_ConstraintTrigger_a_74793';1655;17
SELECT oid, proname FROM pg_proc
WHERE oid IN (1654,1655,1644,1645);
1644;'RI_FKey_check_ins'
1645;'RI_FKey_check_upd'
1654;'RI_FKey_noaction_del'
1655;'RI_FKey_noaction_upd'
Dois "noaction" interno são ativados tbltype.
Dois "cheques" internos são ativados tbl.
Todos eles são executados FOR EACH ROW, conforme indicado pelos números ímpares em tgtype.
Os 2 bytes do Postgres tgtype smallintrepresentam um int16código fonte em C onde o bit menos significativo é codificado TRIGGER_TYPE_ROW. Explicação detalhada aqui:
Você pode facilmente testar isso com um par de gatilhos idênticos, onde você só muda FOR ROW/ STATEMENT...