36.3. Триггерные функции на языке C
В этом разделе описываются низкоуровневые детали интерфейса для триггерных функций. Эта информация необходима только при разработке триггерных функций на языке C. При использовании языка более высокого уровня эти детали обрабатываются не видны. В большинстве случаев стоит рассмотреть возможность использования процедурного языка, прежде чем начать разрабатывать триггеры на C. В документации по каждому процедурному языку объясняется, как создавать триггеры на этом языке.
Триггерные функции должны использовать интерфейс функций «версии 1».
Когда функция вызывается диспетчером триггеров, ей не передаются обычные аргументы, но передаётся указатель «context», ссылающийся на структуру TriggerData. Функции на C могут проверить, вызваны ли они диспетчером триггеров или нет, выполнив макрос:
CALLED_AS_TRIGGER(fcinfo)
который разворачивается в:
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
Если возвращается истина, то fcinfo->context можно безопасно привести к типу TriggerData * и использовать указатель на структуру TriggerData. Функция не должна изменять структуру TriggerData или любые данные, которые на неё указывают.
struct TriggerData определяется в commands/trigger.h:
typedef struct TriggerData
{
NodeTag type;
TriggerEvent tg_event;
Relation tg_relation;
HeapTuple tg_trigtuple;
HeapTuple tg_newtuple;
Trigger *tg_trigger;
Buffer tg_trigtuplebuf;
Buffer tg_newtuplebuf;
Tuplestorestate *tg_oldtable;
Tuplestorestate *tg_newtable;
} TriggerData;где элементы определяются следующим образом:
typeВсегда
T_TriggerData.tg_eventОписывает событие, для которого вызывается функция. Можно использовать следующие макросы для получения информации о
tg_event:TRIGGER_FIRED_BEFORE(tg_event)Возвращает истину, если триггер сработал до операции.
TRIGGER_FIRED_AFTER(tg_event)Возвращает истину, если триггер сработал после операции.
TRIGGER_FIRED_INSTEAD(tg_event)Возвращает истину, если триггер сработал вместо операции.
TRIGGER_FIRED_FOR_ROW(tg_event)Возвращает истину, если триггер сработал на уровне строки.
TRIGGER_FIRED_FOR_STATEMENT(tg_event)Возвращает истину, если триггер сработал на уровне оператора.
TRIGGER_FIRED_BY_INSERT(tg_event)Возвращает истину, если триггер сработал для операции
INSERT.TRIGGER_FIRED_BY_UPDATE(tg_event)Возвращает истину, если триггер сработал для операции
UPDATE.TRIGGER_FIRED_BY_DELETE(tg_event)Возвращает истину, если триггер сработал для операции
DELETE.TRIGGER_FIRED_BY_TRUNCATE(tg_event)Возвращает истину, если триггер сработал для операции
TRUNCATE.
tg_relationУказатель на структуру, описывающую таблицу, для которой сработал триггер. Подробнее об этой структуре в
utils/rel.h. Самое интересное здесь этоtg_relation->rd_att(дескриптор записей таблицы) иtg_relation->rd_rel->relname(имя таблицы; имеет типNameData, а неchar*; используйте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_trigtuplebufБуфер, содержащий
tg_trigtuple, или содержащийInvalidBuffer— если нет такой строки или она не хранится в дисковом буфере.tg_newtuplebufБуфер, содержащий
tg_newtuple, или содержащийInvalidBuffer— если нет такой строки или она не хранится в дисковом буфере.tg_oldtableУказатель на структуру типа
Tuplestorestate, содержащую ноль или несколько строк в формате, определяемом содержимымtg_relation, или указательNULL, если переходное отношениеOLD TABLEотсутствует.tg_newtableУказатель на структуру типа
Tuplestorestate, содержащую ноль или несколько строк в формате, определяемом содержимымtg_relation, или указательNULL, если переходное отношениеNEW TABLEотсутствует.
Чтобы обращаться к переходным таблицам в запросах, выполняемых через SPI, используйте SPI_register_trigger_data.
Триггерная функция должна возвращать указатель HeapTuple или указатель NULL (но не SQL значение null, то есть не нужно устанавливать isNull в истину). Не забудьте, что если не планируете менять обрабатываемую триггером строку, то нужно вернуть либо tg_trigtuple, либо tg_newtuple.
36.3. Writing Trigger Functions in C
This section describes the low-level details of the interface to a trigger function. This information is only needed when writing trigger functions in C. If you are using a higher-level language then these details are handled for you. In most cases you should consider using a procedural language before writing your triggers in C. The documentation of each procedural language explains how to write a trigger in that language.
Trigger functions must use the “version 1” function manager interface.
When a function is called by the trigger manager, it is not passed any normal arguments, but it is passed a “context” pointer pointing to a TriggerData structure. C functions can check whether they were called from the trigger manager or not by executing the macro:
CALLED_AS_TRIGGER(fcinfo)
which expands to:
((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
If this returns true, then it is safe to cast fcinfo->context to type TriggerData * and make use of the pointed-to TriggerData structure. The function must not alter the TriggerData structure or any of the data it points to.
struct TriggerData is defined in commands/trigger.h:
typedef struct TriggerData
{
NodeTag type;
TriggerEvent tg_event;
Relation tg_relation;
HeapTuple tg_trigtuple;
HeapTuple tg_newtuple;
Trigger *tg_trigger;
Buffer tg_trigtuplebuf;
Buffer tg_newtuplebuf;
Tuplestorestate *tg_oldtable;
Tuplestorestate *tg_newtable;
} TriggerData;
where the members are defined as follows:
typeAlways
T_TriggerData.tg_eventDescribes the event for which the function is called. You can use the following macros to examine
tg_event:TRIGGER_FIRED_BEFORE(tg_event)Returns true if the trigger fired before the operation.
TRIGGER_FIRED_AFTER(tg_event)Returns true if the trigger fired after the operation.
TRIGGER_FIRED_INSTEAD(tg_event)Returns true if the trigger fired instead of the operation.
TRIGGER_FIRED_FOR_ROW(tg_event)Returns true if the trigger fired for a row-level event.
TRIGGER_FIRED_FOR_STATEMENT(tg_event)Returns true if the trigger fired for a statement-level event.
TRIGGER_FIRED_BY_INSERT(tg_event)Returns true if the trigger was fired by an
INSERTcommand.TRIGGER_FIRED_BY_UPDATE(tg_event)Returns true if the trigger was fired by an
UPDATEcommand.TRIGGER_FIRED_BY_DELETE(tg_event)Returns true if the trigger was fired by a
DELETEcommand.TRIGGER_FIRED_BY_TRUNCATE(tg_event)Returns true if the trigger was fired by a
TRUNCATEcommand.
tg_relationA pointer to a structure describing the relation that the trigger fired for. Look at
utils/rel.hfor details about this structure. The most interesting things aretg_relation->rd_att(descriptor of the relation tuples) andtg_relation->rd_rel->relname(relation name; the type is notchar*butNameData; useSPI_getrelname(tg_relation)to get achar*if you need a copy of the name).tg_trigtupleA pointer to the row for which the trigger was fired. This is the row being inserted, updated, or deleted. If this trigger was fired for an
INSERTorDELETEthen this is what you should return from the function if you don't want to replace the row with a different one (in the case ofINSERT) or skip the operation. For triggers on foreign tables, values of system columns herein are unspecified.tg_newtupleA pointer to the new version of the row, if the trigger was fired for an
UPDATE, andNULLif it is for anINSERTor aDELETE. This is what you have to return from the function if the event is anUPDATEand you don't want to replace this row by a different one or skip the operation. For triggers on foreign tables, values of system columns herein are unspecified.tg_triggerA pointer to a structure of type
Trigger, defined inutils/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;where
tgnameis the trigger's name,tgnargsis the number of arguments intgargs, andtgargsis an array of pointers to the arguments specified in theCREATE TRIGGERstatement. The other members are for internal use only.tg_trigtuplebufThe buffer containing
tg_trigtuple, orInvalidBufferif there is no such tuple or it is not stored in a disk buffer.tg_newtuplebufThe buffer containing
tg_newtuple, orInvalidBufferif there is no such tuple or it is not stored in a disk buffer.tg_oldtableA pointer to a structure of type
Tuplestorestatecontaining zero or more rows in the format specified bytg_relation, or aNULLpointer if there is noOLD TABLEtransition relation.tg_newtableA pointer to a structure of type
Tuplestorestatecontaining zero or more rows in the format specified bytg_relation, or aNULLpointer if there is noNEW TABLEtransition relation.
To allow queries issued through SPI to reference transition tables, see SPI_register_trigger_data.
A trigger function must return either a HeapTuple pointer or a NULL pointer (not an SQL null value, that is, do not set isNull true). Be careful to return either tg_trigtuple or tg_newtuple, as appropriate, if you don't want to modify the row being operated on.