Триггеры

В этой главе содержится общая информация о написании триггерных функций. Триггерные функции можно написать на большинстве доступных процедурных языков, включая PL/pgSQL.

Прочитав текущую главу, вы можете обратиться к главе PL/pgSQL, чтобы узнать подробности о написании на триггера на этом языке.

Также можно написать триггерную функцию на C/RUST, хотя большинству людей проще использовать один из процедурных языков. В настоящее время написать триггерную функцию на простом языке функций SQL невозможно.


Обзор поведения триггеров

Триггер — это указание, согласно которому база данных должна автоматически выполнять определенную функцию всякий раз, когда осуществляется операция определенного типа. Триггеры могут быть прикреплены к таблицам (партиционированным или обычным), представлениям и сторонним таблицам.

В таблицах и сторонних таблицах можно определить триггеры, которые будут выполняться до или после любой операции INSERT, UPDATE или DELETE либо один раз для каждой модифицированной строки, либо один раз для каждого оператора SQL. Более того, триггеры UPDATE можно установить на срабатывание только в том случае, если в предложении SET команды UPDATE упоминаются определенные столбцы. Также триггеры могут срабатывать для операторов TRUNCATE. Если происходит событие триггера, функция триггера вызывается в соответствующее время для его обработки.

В представлениях можно определить триггеры, которые будут выполняться вместо операций INSERT, UPDATE или DELETE. Такие триггеры INSTEAD OF срабатывают один раз для каждой строки, которую необходимо модифицировать. Функция триггера отвечает за выполнение необходимых модификаций нижележащей базовой таблицы (или таблиц) представления и при необходимости возвращает модифицированную строку такой, какой она будет отображаться в представлении. Кроме того, для представлений можно определить триггеры, которые будут выполняться один раз для каждого оператора SQL, до или после операций INSERT, UPDATE или DELETE. Однако такие триггеры срабатывают, только если в представлении также имеется триггер INSTEAD OF. В противном случае любой оператор, направленный на представление, должен быть переписан в оператор, затрагивающий его нижележащую базовую таблицу (или таблицы), и тогда будут срабатывать триггеры, прикрепленные к этой таблице (или таблицам).

Триггерная функция должна быть определена до создания триггера и объявлена как функция, не имеющая аргументов и возвращающая тип trigger. (Триггерная функция получает входные данные через специально переданную структуру TriggerData, а не в форме аргументов обычной функции.)

После создания подходящей триггерной функции триггер устанавливается с помощью команды CREATE TRIGGER. Одна триггерная функция может использоваться для нескольких триггеров.

QHB предлагает как построчные, так и пооператорные триггеры. В случае построчного триггера триггерная функция вызывается один раз для каждой строки, которую затрагивает оператор, вызвавший триггер. Напротив, пооператорный триггер вызывается только один раз при выполнении соответствующего оператора, независимо от количества строк, затронутых этим оператором. В частности, оператор, который влияет на ноль строк, все равно будет провоцировать выполнение любых применимых пооператорных триггеров для каждого оператора. Эти два типа триггеров иногда называют триггерами уровня строки и уровня оператора соответственно. Триггеры для TRUNCATE можно определить только на уровне оператора, но не для каждой строки.

Кроме того, триггеры классифицируются в зависимости от того, срабатывают они до, после или вместо операции. Они называются триггерами BEFORE, AFTER и INSTEAD OF соответственно. Триггеры BEFORE уровня оператора естественным образом срабатывают до того, как оператор начинает что-либо делать, тогда как триггеры AFTER того же уровня срабатывают в самом конце оператора. Эти типы триггеров можно определить в таблицах, представлениях или сторонних таблицах. Триггеры BEFORE уровня строки срабатывают непосредственно перед тем, как будет обработана определенная строка, тогда как триггеры AFTER того же уровня срабатывают в конце оператора (но до триггеров AFTER уровня оператора). Эти типы триггеров можно определить только для партиционированных и сторонних таблиц, но не для представлений. Триггеры INSTEAD OF можно определить только для представлений и только на уровне строки; они запускаются сразу после того, как каждая строка в представлении определяется как требующая обработки.

Если триггер AFTER определен как триггер ограничения, его выполнение может быть отложено до конца транзакции, а не оператора. В любом случае триггер выполняется как часть той же транзакции, которой принадлежит запустивший его оператор, поэтому если оператор или триггер вызывают ошибку, отменятся действия обоих.

Оператор, нацеленный на родительскую таблицу в иерархии наследования или партиционирования, не вызывает срабатывания триггеров уровня операторов затрагиваемых дочерних таблиц; срабатывают только триггеры уровня операторов для родительской таблицы. Однако триггеры этих дочерних таблиц на уровне строки срабатывать будут.

Если INSERT содержит предложение ON CONFLICT DO UPDATE, возможно, что эффекты триггеров BEFORE INSERT и BEFORE UPDATE уровня строки можно применить совместно таким образом, что это проявится в конечном состоянии изменяемой строки, если в запросе было обращение к столбцу EXCLUDED. Однако для выполнения обоих наборов триггеров BEFORE уровня строки необязательно указывать ссылку на столбец EXCLUDED. Когда имеются триггеры BEFORE INSERT и BEFORE UPDATE уровня строки, которые совместно изменяют добавляемую/ обновляемую строку, следует рассмотреть возможность получения неожиданного результата (это может стать проблемой, даже если изменения более или менее равнозначны, если только они не идемпотентны). Обратите внимание, что триггеры UPDATE уровня оператора выполняются, когда указано ON CONFLICT DO UPDATE, независимо от того, затронул ли UPDATE какие-либо строки или нет (и независимо от того, был ли выбран альтернативный путь UPDATE). INSERT с предложением ON CONFLICT DO UPDATE сначала выполнит триггеры BEFORE INSERT, затем триггеры BEFORE UPDATE, затем триггеры AFTER UPDATE и наконец триггеры AFTER INSERT (все триггеры уровня оператора).

Если команда UPDATE в партиционированной таблице приводит к перемещению строки в другую партицию, она будет проведена как выполнение сначала DELETE в исходной партиции, а затем INSERT в новой партиции. В этом случае все триггеры BEFORE UPDATE и BEFORE DELETE уровня строки срабатывают в исходной партиции. Затем в целевой партиции срабатывают все триггеры BEFORE INSERT уровня строки. Когда все эти триггеры затрагивают перемещение строки, следует рассмотреть возможность неожиданных результатов. Если речь идет о триггерах AFTER ROW, то применяются триггеры AFTER DELETE и AFTER INSERT, но не AFTER UPDATE, потому что команда UPDATE была преобразована в DELETE и INSERT. Если же говорить о триггерах уровня оператора, то ни один из триггеров DELETE или INSERT не срабатывает, даже если происходит перемещение строки; сработают только триггеры UPDATE, определенные в целевой таблице, используемой в операторе UPDATE.

Триггерные функции, вызываемые пооператорными триггерами, должны всегда возвращать NULL. Триггерные функции, вызываемые построчными триггерами, могут возвращать строку таблицы (значение типа HeapTuple) вызывающему исполнителю, если они того пожелают. Построчный триггер, срабатывающий до операции, имеет следующие варианты:

  • Он может вернуть NULL, чтобы пропустить операцию для текущей строки. Это указывает исполнителю не выполнять операцию на уровне строки, вызвавшей триггер (добавление, изменение или удаление определенной строки таблицы).

  • Только для триггеров INSERT и UPDATE уровня строки возвращаемая строка становится строкой, которая будет добавлена или заменит изменяемую строку. Это позволяет триггерной функции модифицировать добавляемую или изменяемую строку.

Триггер BEFORE уровня строки, который не планирует вызвать какое-либо из этих поведений, должен аккуратно вернуть в качестве своего результата ту же строку, что была передана на вход (то есть строку NEW для триггеров INSERT и UPDATE, строку OLD для триггеров DELETE).

Триггер INSTEAD OF уровня строки должен возвращать либо NULL, чтобы показать, что он не изменил никаких данных из нижележащих базовых таблиц представления, либо строку представления, которая была передана на вход (строку NEW для операций INSERT и UPDATE, строку OLD для операций DELETE). Возвращаемое значение, отличное от NULL, сигнализирует, что триггер выполнил необходимые модификации данных в представлении. Это приведет к увеличению счетчика количества строк, затрагиваемых командой. Только для операций INSERT и UPDATE триггер может модифицировать строку NEW перед тем, как вернуть ее. Это изменит данные, возвращаемые INSERT RETURNING или UPDATE RETURNING, и будет полезно, когда представление не будет отображать ровно те же данные, что были предоставлены.

Возвращаемое значение игнорируется для триггеров уровня строки, срабатывающих после операции, и поэтому они могут возвращать NULL.

Некоторые соображения применимы к генерируемым столбцам. Сохраняемые генерируемые столбцы вычисляются после триггеров BEFORE и до триггеров AFTER. Поэтому в триггерах AFTER можно проверить сгенерированное значение. В триггерах BEFORE строка OLD, как и следовало ожидать, содержит старое сгенерированное значение, но строка NEW еще не содержит нового сгенерированного значения и не должна быть доступна. В интерфейсе языка C содержимое столбца на этом этапе не определено; язык программирования более высокого уровня должен предотвращать доступ к сохраняемому генерируемому столбцу в строке NEW в триггере BEFORE. Изменения значения генерируемого столбца в триггере BEFORE игнорируются и будут перезаписаны.

Если для одного и того же события в одном и том же отношении определено несколько триггеров, они сработают в алфавитном порядке по имени триггера. В случае триггеров BEFORE и INSTEAD OF потенциально модифицированная строка, возвращаемая каждым триггером, становится входящей строкой для следующего триггера. Если какой-либо триггер BEFORE или INSTEAD OF возвращает NULL, для этой строки операция прекращается, и последующие триггеры не срабатывают (для этой строки).

В определении триггера также можно указать логическое условие WHEN, которое будет проверяться на предмет того, должен ли триггер срабатывать. В триггерах уровня строки условие WHEN может проверять старые и/или новые значения столбцов строки. (Триггеры уровня оператора также могут иметь условия WHEN, хотя для них эта возможность не так полезна.) В триггере BEFORE условие WHEN вычисляется непосредственно перед выполнением функции, поэтому использование WHEN не слишком отличается от проверки того же условия в начале триггерной функции. Однако в триггере AFTER условие WHEN вычисляется сразу после изменения строки и определяет, стоит ли в очереди событие для запуска триггера в конце оператора. Таким образом, когда условие WHEN триггера AFTER не возвращает true, нет необходимости ставить в очередь событие или повторно извлекать строку в конце оператора. Триггер, который нужно запустить только для нескольких строк, значительному ускорить операторы, изменяющие много строк. Триггеры INSTEAD OF не поддерживают условия WHEN.

Как правило, триггеры BEFORE уровня строки используются для проверки или модификации данных, которые будут добавлены или изменены. Например, триггер BEFORE можно использовать для добавления текущего времени в столбец timestamp или для проверки согласованности двух элементов строки. Триггеры AFTER наиболее разумно использовать для распространения изменений на другие таблицы или для проверки согласованности с другими таблицами. Причина такого разделения работы заключается в том, что триггер AFTER может быть уверен, что видит окончательное значение строки, а триггер BEFORE — нет, так как после него могут сработать другие триггеры BEFORE. Если у вас нет особой причины делать триггер именно BEFORE или AFTER, вариант с BEFORE более эффективен, поскольку информацию об операции не нужно сохранять до конца выполнения оператора.

Если триггерная функция выполняет команды SQL, эти команды могут снова запускать триггеры. Они называются каскадными триггерами. Прямого ограничения на количество каскадных уровней нет. Каскады могут приводить к рекурсивному вызову одного и того же триггера; например, триггер INSERT может выполнить команду, которая добавляет дополнительную строку в ту же таблицу, вызывая повторное срабатывание триггера INSERT. Программист триггера обязан избегать бесконечной рекурсии в таких сценариях.

При определении триггера для него можно указать аргументы. Цель этого заключается в том, чтобы разрешить различным триггерам с одинаковыми требованиями вызывать одну и ту же функцию. Например, это может быть обобщенная триггерная функция, принимающая в качестве аргументов два имени столбца и помещающая текущего пользователя в один из них, а текущую метку времени — в другой. При правильном написании эта триггерная функция не зависит от конкретной таблицы, для которой она запускается. Таким образом, эту же функцию можно использовать для событий INSERT в любой таблице с подходящими столбцами, например, для автоматического отслеживания создания записей в таблице транзакций. Кроме того, если она определена как триггер UPDATE, ее можно использовать для отслеживания событий последних изменений.

Каждый язык программирования, который поддерживает триггеры, имеет свой собственный метод, позволяющий сделать входные данные триггера доступными для триггерной функции. Эти входные данные включают тип события триггера (например INSERT или UPDATE), а также любые аргументы, которые были перечислены в команде CREATE TRIGGER. Для триггера уровня строки входные данные дополнительно выключают строку NEW для триггеров INSERT и UPDATE и/или строку OLD для триггеров UPDATE и DELETE.

По умолчанию триггеры уровня оператора не имеют никакой возможности проверить отдельные строки, измененные этим оператором. Но триггер AFTER STATEMENT может запросить создание переходных таблиц, чтобы наборы затронутых строк стали для него доступны. Триггеры AFTER ROW также могут запрашивать переходные таблицы, чтобы иметь возможность видеть общие изменения в таблице, а также изменения в отдельной строке, для которой они в данный момент запускаются. Способ проверки переходных таблиц опять же зависит от используемого языка программирования, но типичный подход заключается в том, чтобы заставить переходные таблицы работать как временные таблицы только для чтения, к которым могут обращаться команды SQL, выполненные в триггерной функции.


Видимость изменений данных

Если в своей триггерной функции вы выполняете команды SQL, и эти команды обращаются к таблице, для которой создан этот триггер, вам необходимо знать правила видимости данных, поскольку они определяют, будут ли эти команды SQL видеть изменение данных, для которых срабатывает триггер. Кратко:

  • Триггеры уровня оператора следуют простым правилам видимости: ни одно из изменений, внесенных оператором, не видно триггерам BEFORE, тогда как триггерам AFTER видны все модификации.

  • Изменение данных (добавление, изменение или удаление), вызывающее срабатывание триггера, естественно, не видно командам SQL, выполняемым в триггере BEFORE уровня строки, поскольку это еще не произошло.

  • Однако команды SQL, выполняемые в триггере BEFORE уровня строки, увидят последствия изменений данных для строк, ранее обработанных в той же внешней команде. Это требует осторожности, поскольку порядок этих событий изменений в целом непредсказуем; команда SQL, затрагивающая несколько строк, может посещать их в любом порядке.

  • Аналогичным образом триггер INSTEAD OF уровня строки будет видеть последствия изменений данных, выполненных при предыдущих срабатываниях триггеров INSTEAD OF в той же внешней команде.

  • При срабатывании триггера AFTER уровня строки все изменения данных, внесенные внешней командой, уже завершены и видны для вызываемой триггерной функции.

Если ваша триггерная функция написана на любом из стандартных процедурных языков, то приведенные выше операторы применимы, только если функция объявлена как VOLATILE. Функции, которые объявлены как STABLE или IMMUTABLE, в любом случае не увидят изменений, внесенных вызывающей командой.

Дополнительную информацию о правилах видимости данных можно найти в разделе Видимость изменений данных SPI. В примере в разделе Полный пример триггера содержится демонстрация этих правил.


Написание триггерных функций на C/RUST

В этом разделе описываются низкоуровневые детали интерфейса для триггерной функции. Эта информация необходима только при написании триггерных функций на языке C/RUST. Если вы используете язык более высокого уровня, то эти детали обрабатываются незаметно для вас. В большинстве случаев вам следует рассмотреть возможность использования процедурного языка, прежде чем писать свои триггеры на C/RUST. В документации каждого процедурного языка объясняется, как написать на нем триггер.

Триггерные функции должны использовать интерфейс менеджера функций «версии 1».

Когда функция вызывается менеджером триггеров, ей не передаются никакие обычные аргументы, но передается указатель «context», указывающий на структуру TriggerData. Функции на C/RUST могут проверить, были ли они вызваны из менеджера триггеров или нет, выполнив макрос:

CALLED_AS_TRIGGER(fcinfo)

который разворачивается в:

((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))

Если эта запись возвращает true, то можно безопасно привести fcinfo->context к типу TriggerData * и использовать структуру TriggerData. Функция не должна изменять структуру TriggerData или любые данные, на которые она указывает.

Структура TriggerData определена в commands/trigger.h:

typedef struct TriggerData
{
    NodeTag          type;
    TriggerEvent     tg_event;
    Relation         tg_relation;
    HeapTuple        tg_trigtuple;
    HeapTuple        tg_newtuple;
    Trigger         *tg_trigger;
    TupleTableSlot  *tg_trigslot;
    TupleTableSlot  *tg_newslot;
    Tuplestorestate *tg_oldtable;
    Tuplestorestate *tg_newtable;
    const Bitmapset *tg_updatedcols;
} TriggerData;

где члены определены следующим образом:

type
Всегда T_TriggerData.

tg_event
Описывает событие, для которого вызывается функция. Для проверки tg_event можно воспользоваться следующими макросами:

  • TRIGGER_FIRED_BEFORE(tg_event) — возвращает true, если триггер сработал до операции.

  • TRIGGER_FIRED_AFTER(tg_event) — возвращает true, если триггер сработал после операции.

  • TRIGGER_FIRED_INSTEAD(tg_event) — возвращает true, если триггер сработал вместо операции.

  • TRIGGER_FIRED_FOR_ROW(tg_event) — возвращает true, если триггер сработал для события уровня строки.

  • TRIGGER_FIRED_FOR_STATEMENT(tg_event) — возвращает true, если триггер сработал для события уровня оператора.

  • TRIGGER_FIRED_BY_INSERT(tg_event) — возвращает true, если триггер был запущен командой INSERT.

  • TRIGGER_FIRED_BY_UPDATE(tg_event) — возвращает true, если триггер был запущен командой UPDATE.

  • TRIGGER_FIRED_BY_DELETE(tg_event) — возвращает true, если триггер был запущен командой DELETE.

  • TRIGGER_FIRED_BY_TRUNCATE(tg_event) — возвращает true, если триггер был запущен командой TRUNCATE.

tg_relation
Указатель на структуру, описывающую отношение, для которого сработал триггер. Подробную информацию об этой структуре см. в utils/rel.h. Наиболее интересными элементами являются tg_relation->rd_att (дескриптор кортежей отношения) и tg_relation->rd_rel->relname (имя отношения; имеет тип не char*, а NameData; используйте SPI_getrelname(tg_relation) для получения char*, если вам нужна копия этого имени).

tg_trigtuple
Указатель на строку, для которой сработал триггер. Это строка, которая добавляется, изменяется или удаляется. Если этот триггер сработал для INSERT или DELETE, то это значение следует вернуть из функции, если вы не планируете заменять эту строку другой (в случае INSERT) или пропустить данную операцию. Для триггеров во внешних таблицах значения системных столбцов здесь не указываются.

tg_newtuple
Указатель на новую версию строки, если триггер сработал для UPDATE, и NULL, если он сработал для INSERT или DELETE. Это значение следует вернуть из функции, если событие является UPDATE и вы не планируете заменять эту строку другой или пропустить данную операцию. Для триггеров во внешних таблицах значения системных столбцов здесь не указываются.

tg_trigger
Указатель на структуру типа Trigger, определенную в utils/reltrigger.h:

typedef struct Trigger
{
    Oid         tgoid;
    char       *tgname;
    Oid         tgfoid;
    int16       tgtype;
    char        tgenabled;
    bool        tgisinternal;
    Oid         tgconstrrelid;
    Oid         tgconstrindid;
    Oid         tgconstraint;
    bool        tgdeferrable;
    bool        tginitdeferred;
    int16       tgnargs;
    int16       tgnattr;
    int16      *tgattr;
    char      **tgargs;
    char       *tgqual;
    char       *tgoldtable;
    char       *tgnewtable;
} Trigger;

где tgname — имя триггера, tgnargs — количество аргументов в tgargs, tgargs — массив указателей на аргументы, заданные в операторе CREATE TRIGGER. Остальные члены предназначены только для внутреннего использования.

tg_trigslot
Слот, содержащий tg_trigtuple, или указатель NULL, если такого кортежа нет.

tg_newslot
Слот, содержащий tg_newtuple, или указатель NULL, если такого кортежа нет.

tg_oldtable
Указатель на структуру типа Tuplestorestate, содержащую ноль или более строк в формате, заданном параметром tg_relation, или указатель NULL если переходного отношения OLD TABLE нет.

tg_newtable
Указатель на структуру типа Tuplestorestate, содержащую ноль или более строк в формате, заданном параметром tg_relation, или указатель NULL, если переходного отношения NEW TABLE нет.

tg_updatedcols
Для триггеров UPDATE это битовая карта, указывающая на столбец, измененный командой, которая вызвала срабатывание триггера. Универсальные триггерные функции могут использовать ее для оптимизации своих действий, чтобы не иметь дела со столбцами, которые не были изменены.

Например, чтобы определить, является ли столбец с атрибутом номер attnum (считая с 1) членом этой битовой карты, вызовите bms_is_member(attnum - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols)).

Для триггеров, отличных от UPDATE, здесь будет значение NULL.

Чтобы позволить запросам, отправленным через SPI, обращаться к переходным таблицам, см. SPI_register_trigger_data.

Триггерная функция должна возвращать либо указатель HeapTuple, либо указатель NULL (но не SQL-значение NULL, т. е. не устанавливать isNull равным true). Обязательно возвращайте tg_trigtuple или tg_newtuple (в зависимости от ситуации), если не хотите модифицировать строку, с которой работаете.


Полный пример триггера

Вот очень простой пример триггерной функции, написанной на C/RUST. (Примеры триггеров, написанных на процедурных языках, можно найти в документации этих процедурных языков.)

Функция trigf выводит количество строк в таблице ttest и пропускает фактическую операцию, если команда пытается вставить значение NULL в столбец x. (Таким образом, триггер действует как ограничение NOT NULL, но не прерывает транзакцию.)

Для начала определяем таблицу:

CREATE TABLE ttest (
    x integer
);

Это исходный код триггерной функции:

#include "postgres.h"
#include "fmgr.h"
#include "executor/spi.h"       /* это то, что вам нужно для работы с SPI */
#include "commands/trigger.h"   /* ... триггеры ... */
#include "utils/rel.h"          /* ... и отношения */

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;

    /* убедимся, что она вообще вызывается как триггер */
    if (!CALLED_AS_TRIGGER(fcinfo))
        elog(ERROR, "trigf: not called by trigger manager");
        /* ОШИБКА: "trigf: не вызывается менеджером триггеров" */

    /* кортеж, возвращаемый исполнителю */
    if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        rettuple = trigdata->tg_newtuple;
    else
        rettuple = trigdata->tg_trigtuple;

    /* проверяем на значения NULL */
    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;

    /* подключаемся к менеджеру SPI */
    if ((ret = SPI_connect()) < 0)
        elog(ERROR, "trigf (fired %s): SPI_connect returned %d", when, ret);

    /* получаем количество строк в таблице */
    ret = SPI_exec("SELECT count(*) FROM ttest", 0);

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

    /* count(*) возвращает int8, так что не забудьте преобразовать его */
    i = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
                                    SPI_tuptable->tupdesc,
                                    1,
                                    &isnull));

    elog (INFO, "trigf (fired %s): there are %d rows in ttest", when, i);

    SPI_finish();

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

    return PointerGetDatum(rettuple);
}

После компиляции исходного кода (см. подраздел Компиляция и связывание динамически загружаемых функций), объявляем функцию и триггеры:

CREATE FUNCTION trigf() RETURNS trigger
    AS 'имя_файла'
    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();

Теперь можно проверить работу триггера:

=> INSERT INTO ttest VALUES (NULL);
INFO:  trigf (fired before): there are 0 rows in ttest
INSERT 0 0

-- Добавление пропущено, и триггер AFTER не сработал

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

=> INSERT INTO ttest VALUES (1);
INFO:  trigf (fired before): there are 0 rows in ttest
INFO:  trigf (fired after ): there are 1 rows in ttest
                                       ^^^^^^^^
                             вспомните, что мы говорили про видимость.
INSERT 167793 1
vac=> SELECT * FROM ttest;
 x
---
 1
(1 row)

=> INSERT INTO ttest SELECT x * 2 FROM ttest;
INFO:  trigf (fired before): there are 1 rows in ttest
INFO:  trigf (fired after ): there are 2 rows in ttest
                                       ^^^^^^
                             вспомните, что мы говорили про видимость.
INSERT 167794 1
=> SELECT * FROM ttest;
 x
---
 1
 2
(2 rows)

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

=> DELETE FROM ttest;
INFO:  trigf (fired before): there are 2 rows in ttest
INFO:  trigf (fired before): there are 1 rows in ttest
INFO:  trigf (fired after ): there are 0 rows in ttest
INFO:  trigf (fired after ): there are 0 rows in ttest
                                       ^^^^^^
                             вспомните, что мы говорили про видимость.
DELETE 2
=> SELECT * FROM ttest;
 x
---
(0 rows)

Более сложные примеры можно найти в SPI.