Description
Related dev. issues: tarantool/tarantool#8656
Product: Tarantool
Since: 3.0
Root document: new page - https://www.tarantool.io/en/doc/latest/reference/reference_lua/trigger/
SME (except for transactional triggers): @ drewdzzz
SME (transactional triggers): @ Gumix
https://www.tarantool.io/en/doc/latest/release/3.0.0/#triggers
https://habr.com/ru/companies/vk/articles/782318/
Universal trigger registry
The new Lua module trigger
was introduced. It is storage of event
s - each event
has its own unique name and a list of named triggers. Each trigger has unique name within the event in which it is registered.
The module has following methods:
trigger.set(event_name, trigger_name, handler_f)
- insert the named handler to the beginning of the trigger list or update a trigger by name (if the name is already taken). Handler can be any callable Lua object.trigger.del(event_name, trigger_name)
- delete a trigger by name from the event. No-op if there is no such trigger.trigger.call(event_name, arg1, arg2, ...)
- call all the triggers registered on the event, from the beginning of the list to its end. The execution is stopped after the first exception. Returned values are ignored, all the arguments are passed without copying and any other preprocessing. For advanced scenarios,pairs
method can be used (for example, if you want to process returned values of each trigger).trigger.pairs(event_name)
- an iterator over triggers, registered on event, iterates from the beginning to the end. Iterator yields two values - name of the trigger (string) and its handler (callable object).trigger.info([event_name])
- return key-value map{event: {{trigger_name1, handler1}, {trigger_name2, handler2}, ...}}
, all the triggers inside one event are ordered in call order. If argumentevent_name
is passed, map contains only one event with nameevent_name
, if it has any registered triggers.
The idea of this module - any developer, including us, can create an event. He documents how registered triggers are called and provides this behavior using methods call
and pairs
. Example: for a trigger like space:on_replace
, method call
can be used - trigger.call("box.space.space_name.on_replace", old_tuple, new_tuple)
. But one cannot implement space:before_replace
trigger using call
method - this trigger passes returned value from current trigger as new_tuple
to the next one. For such "advanced" semantics developer needs to use method pairs
.
On the other side, users of events just set and delete triggers. Every event has its own behavior, so the user must thoroughly read the documentation of a particular event to set his triggers on it.
Naming
Every name (event_name
or trigger_name
) is a string, virtually divided into namespaces. Each namespace or name can be a string or a number. To address a sub namespace with a string identifier, one must use dot ("namespace.subnamespace"), in the case of a number identifier, square brackets must be used ("namespace[512]").
Example:
local trigger = require('trigger')
trigger.set('box.space[512].on_replace', 'my_name.on_replace_trigger', function(...) ... end)
Reserved namespaces
Root namespaces tarantool
and box
(names like tarantool.*
and box.*
) are reserved for tarantool triggers and internal purposes - creating user triggers or events with such names can lead to negative consequences.
Tarantool triggers
Almost all the tarantool triggers were moved to the trigger registry, old way to set triggers still works.
List of tarantool triggers in the trigger registry:
box.session.on_connect
- the new place for this trigger.box.session.on_disconnect
the new place for this trigger.box.session.on_auth
- the new place for this trigger.box.session.on_access_denied
- the new place for this trigger.box.ctl.on_election
- the new place for this trigger.box.ctl.on_recovery_state
- the new place for this trigger.box.ctl.on_schema_init
- the new place for this trigger.box.ctl.on_shutdown
- the new place for this trigger.- Space triggers - described below in a separate section.
- New transactional triggers - described below in a separate section.
tarantool.trigger.on_change
- described below in a separate section.
Space triggers
NB: behavior of triggers, set with old space trigger API, is not changed. Only the new triggers has new behavior.
In the new trigger system, each space has four types of triggers:
box.space.test.on_replace
- works in the same way as oldon_replace
triggers, but isn't fired on recovery.box.space.test.before_replace
- works in the same way as oldbefore_replace
triggers, but isn't fired on recovery.box.space.test.on_recovery_replace
- works in the same way asbox.space.test.on_replace
, but fired only on recovery and has two additional arguments - xrow header and xrow body of type MsgPack object, both MsgPacks are maps with integer keys.box.space.test.before_recovery_replace
- works in the same way asbox.space.test.before_replace
, but fired only on recovery and has two additional arguments - xrow header and xrow body of type MsgPack object, both MsgPacks are maps with integer keys.
Also, each trigger can be set by id and by name. For example, we have a space named test
with id = 512
. We can set an on_replace trigger in two ways:
trigger.set('box.space[512].on_replace', 'my_name.my_on_replace_trigger', function(...) ... end)
trigger.set('box.space.test.on_replace', 'my_name.my_on_replace_trigger', function(...) ... end)
All the triggers, set by id, are fired before the triggers, set by name.
All the restriction are the same as for old space triggers.
New transactional triggers
NB: old transactional triggers are not moved to the trigger registry and still have old behavior.
The new transactional triggers:
box.before_commit
- triggered when a transaction is ready to commit;box.on_commit
- triggered when a transaction is committed;box.on_rollback
- triggered when a transaction is rolled back.
Each of them have 3 versions, e.g. `box.on_commit' event has:
box.on_commit
- global version, called for all transactions;box.on_commit.space.test
- called for transactions that write to
space "test";box.on_commit.space[512]
- called for transactions that write to
space with id 512.
One of the main advantages of the new triggers is that they can be set for all transactions, rather than setting them within each transaction.
Space-specific triggers are called prior to global ones. If a trigger-function fails, the remaining triggers for this event (including global) are not executed.
If a space-specific trigger is added to the registry within an active transaction, it may or may not be called on commit/rollback of this transaction (the behavior is unspecified).
The trigger-function for each event may take an iterator parameter. Similar to old transactional triggers, the iterator goes through the effects of every request that changed a space during the transaction.
box.before_commit
trigger-function restrictions:
- Yield is allowed (on memtx without mvcc it will abort the txn);
- Allowed to write into database spaces;
- If the function raises an error, the transaction is rolled back.
box.on_commit
and box.on_rollback
trigger-function restrictions:
- Yield is forbidden (it will crash Tarantool);
- The function should not access any database spaces;
- If the function raises an error, the error is simply logged.
tarantool.trigger.on_change
In the trigger registry, there is event named 'tarantool.trigger.on_change' which is called when any event is modified (trigger.set
or trigger.del
is called). All the handlers are called with one argument - name of the modified event. Returned value of each handler is ignored. Handlers are fired after the event is changed (the event contains inserted trigger, if any, and does not contain deleted one, if any). All thrown errors are logged with error level and do not stop execution of triggers.