37.4. Un ejemplo completo de disparador #

Aquí tienes un ejemplo muy simple de una función de disparo escrita en C. (Se pueden encontrar ejemplos de disparadores escritos en lenguajes procedimentales en la documentación de los mismos).

La función trigf informa el número de filas en la tabla ttest y omite la operación real si el comando intenta insertar un valor nulo en la columna x. (De modo que el disparador actúa como una restricción de no nulo pero no aborta la transacción).

Primero, la definición de la tabla:

CREATE TABLE ttest (
    x integer
);

Este es el código fuente de la función del disparador:

#include "postgres.h"
#include "fmgr.h"
#include "executor/spi.h"       /* esto es lo que necesitas para trabajar con SPI */
#include "commands/trigger.h"   /* ... disparadores ... */
#include "utils/rel.h"          /* ... y relaciones */

PG_MODULE_MAGIC;

PG_FUNCTION_INFO_V1(trigf);

Datum
trigf(PG_FUNCTION_ARGS)
{
    TriggerData *trigdata = (TriggerData *) fcinfo->context;
    TupleDesc   tupdesc;
    HeapTuple   rettuple;
    char       *when;
    bool        checknull = false;
    bool        isnull;
    int         ret, i;

    /* asegurarse de que sea llamado como un disparador */
    if (!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "trigf: no llamado por el administrador de disparadores");

    /* tupla a devolver al ejecutor */
    if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        rettuple = trigdata->tg_newtuple;
    else
        rettuple = trigdata->tg_trigtuple;

    /* comprobar si hay valores nulos */
    if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)
        && TRIGGER_FIRED_BEFORE(trigdata->tg_event))
        checknull = true;

    if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
        when = "before";
    else
        when = "after ";

    tupdesc = trigdata->tg_relation->rd_att;

    /* conectarse al gestor SPI */
    SPI_connect();

    /* obtener el número de filas en la tabla */
    ret = SPI_exec("SELECT count(*) FROM ttest", 0);

    if (ret < 0)
        elog(ERROR, "trigf (fired %s): SPI_exec devolvió %d", when, ret);

    /* count(*) devuelve int8, así que ten cuidado de convertirlo */
    i = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
                                    SPI_tuptable->tupdesc,
                                    1,
                                    &isnull));

    elog (INFO, "trigf (fired %s): hay %d filas en ttest", when, i);

    SPI_finish();

    if (checknull)
    {
        SPI_getbinval(rettuple, tupdesc, 1, &isnull);
        if (isnull)
            rettuple = NULL;
    }

    return PointerGetDatum(rettuple);
}

Después de compilar el código fuente (consulta Section 36.10.5), declara la función y los disparadores:

CREATE FUNCTION trigf() RETURNS trigger
    AS 'filename'
    LANGUAGE C;

CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
    FOR EACH ROW EXECUTE FUNCTION trigf();

CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
    FOR EACH ROW EXECUTE FUNCTION trigf();

Ahora puedes probar el funcionamiento del disparador:

=> INSERT INTO ttest VALUES (NULL);
INFO:  trigf (fired before): hay 0 filas en ttest
INSERT 0 0

-- Inserción omitida y el disparador AFTER no se activa

=> SELECT * FROM ttest;
 x
---
(0 rows)

=> INSERT INTO ttest VALUES (1);
INFO:  trigf (fired before): hay 0 filas en ttest
INFO:  trigf (fired after ): hay 1 filas en ttest
                                       ^^^^^^^^
                             recuerda lo que dijimos sobre la visibilidad.
INSERT 167793 1
vac=> SELECT * FROM ttest;
 x
---
 1
(1 row)

=> INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO:  trigf (fired before): hay 1 filas en ttest
INFO:  trigf (fired after ): hay 2 filas en ttest
                                       ^^^^^^
                             recuerda lo que dijimos sobre la visibilidad.
INSERT 167794 1
=> SELECT * FROM ttest;
 x
---
 1
 2
(2 rows)

=> UPDATE ttest SET x = NULL WHERE x = 2;
INFO:  trigf (fired before): hay 2 filas en ttest
UPDATE 0
=> UPDATE ttest SET x = 4 WHERE x = 2;
INFO:  trigf (fired before): hay 2 filas en ttest
INFO:  trigf (fired after ): hay 2 filas en ttest
UPDATE 1
vac=> SELECT * FROM ttest;
 x
---
 1
 4
(2 rows)

=> DELETE FROM ttest;
INFO:  trigf (fired before): hay 2 filas en ttest
INFO:  trigf (fired before): hay 1 filas en ttest
INFO:  trigf (fired after ): hay 0 filas en ttest
INFO:  trigf (fired after ): hay 0 filas en ttest
                                       ^^^^^^
                             recuerda lo que dijimos sobre la visibilidad.
DELETE 2
=> SELECT * FROM ttest;
 x
---
(0 rows)

Hay ejemplos más complejos en src/test/regress/regress.c y en spi.