An error occurred while fetching folder content.
Andrey Saranchin
authored
After this patch, functions with non-empty option `trigger` will be inserted into the trigger registry. The option specifies event names in which the function will be inserted as a trigger. When a function is created, it is set as a trigger without any checks - it can replace an existing one. When it is deleted, it can delete a trigger it hasn't set - for example, it can happen when user manually replaces a persistent trigger. So, it is recommended to have different names (or even separate namespaces) for persistent and usual triggers. Note that both persistent and non-persistent funcs can be used as triggers. Also, when the triggers are called, access rights are not checked. Closes #8663 @TarantoolBot document Title: Document `trigger` option of `box.schema.func.create`. The new option `trigger` allows to create persistent triggers. The option can be a string or an array of strings - name (or names) of the event in which the trigger will be set. Each function created with `box.schema.func.create` has its own tuple in system space `_func`. When the tuple with non-empty field `trigger` is inserted, the function is set to the events listed by this option in the event registry, function name is used as a trigger name. When such tuple is deleted from `_func`, the triggers are deleted. When a function is created, it is set as a trigger without any checks - it can replace an existing one. When it is deleted, it can delete a trigger it hasn't set - for example, it can happen when user manually replaces a persistent trigger. So, it is recommended to have different names (or even separate namespaces) for persistent and usual triggers. When a function is called as a trigger, access rights are ignored, so, actually, every user that can trigger the event has access to your function, but only as a trigger. Since the space `_func` is not temporary, after Tarantool is restarted, the persistent triggers will be set. Also, since the space is not local, the persistent triggers will be replicated, so user has to manually control that triggers (for example, `before_replace` or `before_commit`) are run only on master node, if the application requires such logic. Example of a persistent trigger on a single node: ```lua box.cfg{} -- Create spaces box.schema.space.create('cdc') box.space.cdc:create_index('pk') box.schema.space.create('my_space1') box.space.my_space1:create_index('pk') box.schema.space.create('my_space2') box.space.my_space2:create_index('pk') -- Set triggers local body = 'function(old, new) box.space.cdc:auto_increment{old, new} end' local events = { 'box.space.my_space1.on_replace', 'box.space.my_space2.on_replace' } -- Set the function as a trigger for two events at the same time. box.schema.func.create('example.space_trigger', {body = body, trigger=events}) -- Some replaces box.space.my_space1:replace{0, 'v1'} box.space.my_space2:replace{0, 0} box.space.my_space1:replace{0, 'v2'} box.space.my_space2:replace{0, 1} print(box.space.cdc:fselect{}) ``` Here, restart Tarantool to check if the trigger will be restored. ```lua box.cfg{} box.space.my_space1:replace{1, 'v1'} box.space.my_space2:replace{1, 0} box.space.my_space1:replace{1, 'v2'} box.space.my_space2:replace{1, 1} print(box.space.cdc:fselect{}) ``` The output shows that all replaces were captured. Example of a persistent trigger in a cluster. In this scenario, before_replace trigger is not idempotent, so it must be applied only once - on actual replace, but not during replication. For this purpose, `box.session.type()` can be used. ```lua -- instance1.lua local fiber = require('fiber') box.cfg{} box.schema.user.grant('guest', 'super') local body = [[ function(old_tuple, new_tuple) -- Covert kilogramms into gramms if box.session.type() ~= 'applier' then return box.tuple.new{new_tuple[1], new_tuple[2] * 1000} end end ]] local event = 'box.space.weights.before_replace' box.schema.func.create('example.replicated_trigger', {body = body, trigger = event}) box.schema.space.create('weights') box.space.weights:format({ {name = 'name', type = 'string'}, {name = 'gramms', type = 'unsigned'}, }) box.space.weights:create_index('primary', {parts = {'name'}}) box.cfg{listen = 3301, replication = {3301, 3302}} box.ctl.wait_rw() box.space.weights:replace{'elephant', 4000} box.space.weights:replace{'crocodile', 600} -- Wait for another instance while box.space.weights:count() ~= 4 do fiber.sleep(0) end print(box.space.weights:fselect{}) ```lua Another instance: ```lua -- instance2.lua local fiber = require('fiber') box.cfg{listen = 3302, replication = {3301, 3302}} box.ctl.wait_rw() box.space.weights:replace{'cat', 6} box.space.weights:replace{'dog', 10} -- Wait for another instance while box.space.weights:count() ~= 4 do fiber.sleep(0) end print(box.space.weights:fselect{}) ``` Output of both instances: ``` +-----------+-------+ | name |gramms | +-----------+-------+ | "cat" | 6000 | |"crocodile"|600000 | | "dog" | 10000 | |"elephant" |4000000| +-----------+-------+ ``` We see that the trigger was applied exactly once for each tuple. I would also point out that when the trigger is fired, it pins the function, so it's better not to use persistent triggers for intensive events if the trigger yields (if the trigger doesn't yield, the problem won't be encountered at all). But if one faced such problem, he can manually drop the trigger from module `trigger`, wait for a while for the trigger to finish its execution and only then drop the function.
Name | Last commit | Last update |
---|