Alle code-oefeningen
SQLPostgreSQLExpert8 vragen

Audit-trigger — vijf manieren waarop je logtabel liegt

Een PL/pgSQL-trigger die elke wijziging als JSONB wegschrijft. Hij crasht op DELETE, mist precies de gebeurtenissen die je wilde vastleggen, en zijn tijdstempels staan allemaal gelijk.

"We willen kunnen zien wie wat wanneer gewijzigd heeft." Vrijwel elk systeem krijgt vroeg of laat zo'n trigger. Deze ziet er compleet uit: hij slaat oude en nieuwe waarde op als JSONB, berekent welke velden veranderd zijn, en registreert de gebruiker.

Hij zit vol gaten. Eén ervan laat de trigger hard crashen; de andere zorgen ervoor dat de logtabel er gevuld uitziet terwijl hij het verkeerde verhaal vertelt. Dat tweede is erger — een crash merk je.

CREATE OR REPLACE FUNCTION audit.log_wijziging()
RETURNS trigger
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
DECLARE
    v_oud       jsonb;
    v_nieuw     jsonb;
    v_gewijzigd text[];
BEGIN
    IF TG_OP = 'DELETE' THEN
        v_oud   := to_jsonb(OLD);
        v_nieuw := NULL;
    ELSIF TG_OP = 'INSERT' THEN
        v_oud   := NULL;
        v_nieuw := to_jsonb(NEW);
    ELSE
        v_oud   := to_jsonb(OLD);
        v_nieuw := to_jsonb(NEW);

        SELECT array_agg(n.sleutel)
        INTO   v_gewijzigd
        FROM   jsonb_each(v_nieuw) AS n(sleutel, waarde)
        WHERE  v_oud -> n.sleutel IS DISTINCT FROM n.waarde;

        IF v_gewijzigd IS NULL THEN
            RETURN NEW;
        END IF;
    END IF;

    INSERT INTO audit.wijzigingen
           (tabel, record_id, actie, oude_waarde, nieuwe_waarde,
            gewijzigde_velden, gebruiker, tijdstip)
    VALUES (TG_TABLE_NAME,
            COALESCE(NEW.id, OLD.id),
            TG_OP,
            v_oud,
            v_nieuw,
            v_gewijzigd,
            current_setting('app.gebruiker_id', true),
            now());

    RETURN COALESCE(NEW, OLD);
END;
$$;

CREATE TRIGGER klanten_audit
    AFTER INSERT OR UPDATE OR DELETE ON crm.klanten
    FOR EACH ROW EXECUTE FUNCTION audit.log_wijziging();
Vraag 1Opwarmer

TG_OP, OLD en NEW zijn automatisch beschikbaar in een triggerfunctie. Wat bevat elk van de drie bij een INSERT, een UPDATE en een DELETE?

Vraag 2Crash

Er staat COALESCE(NEW.id, OLD.id). De bedoeling is duidelijk: pak NEW.id, en als die er niet is, OLD.id. Werkt dat bij een DELETE?

Vraag 3Dode code

De trigger is AFTER. Wat doen RETURN NEW en RETURN COALESCE(NEW, OLD) dan eigenlijk?

Vraag 4Beveiliging

De functie is SECURITY DEFINER. Wat betekent dat hier, en wat ontbreekt er?

Vraag 5Volledigheid

De trigger vergelijkt de velden via jsonb_each(v_nieuw). Welk soort wijziging ontsnapt daaraan?

Vraag 6Stille fout

current_setting('app.gebruiker_id', true) — wat doet die tweede parameter, en wat is het gevolg?

Vraag 7Tijd

now() staat in de tijdstempel. Waarom zien alle auditrijen van één bulkwijziging er daardoor identiek uit?

Vraag 8Fundamenteel

De grootste beperking staat niet in de functie zelf. Welke gebeurtenissen komen er nóóit in deze audittabel terecht?

Volgende oefening