From 7c63eba7adcab8d8e3cc6e25ab234ff481f99cb5 Mon Sep 17 00:00:00 2001
From: Andrey Saranchin <Andrey22102001@gmail.com>
Date: Wed, 11 Oct 2023 15:37:32 +0300
Subject: [PATCH] space: drop space with its triggers set with old API

Attaching triggers to space id instead of space object is a significant
pitfall. The users who haven't discovered new triggers may not expect
the triggers of a dropped space will be fired by a new one. So let's
drop triggers that were set with old API along with the space.

All the tests, changed because of described above breaking change, are
restored.

Closes #9223

NO_DOC=later
---
 ...-8657-move-triggers-to-trigger-registry.md |  8 +-
 src/box/alter.cc                              |  1 +
 src/box/lua/space.cc                          |  9 +-
 src/box/lua/trigger.c                         | 22 +++--
 src/box/lua/trigger.h                         | 11 ++-
 src/box/space.c                               | 13 +++
 src/box/space.h                               |  6 ++
 .../gh_4264_recursive_trigger_test.lua        |  2 -
 ..._8027_txn_handle_abort_in_trigger_test.lua | 14 +--
 test/box/before_replace.result                | 13 +--
 test/box/before_replace.test.lua              |  7 +-
 test/box/on_replace.result                    | 34 +------
 test/box/on_replace.test.lua                  | 18 +---
 test/box/push.result                          |  8 +-
 test/box/push.test.lua                        |  4 +-
 test/engine-luatest/space_triggers_test.lua   | 98 +++++++++++++++++++
 test/engine/savepoint.result                  |  9 --
 test/engine/savepoint.test.lua                |  3 -
 test/sql/iproto.result                        |  3 -
 test/sql/iproto.test.lua                      |  1 -
 test/vinyl/on_replace.result                  | 33 -------
 test/vinyl/on_replace.test.lua                | 11 ---
 22 files changed, 170 insertions(+), 158 deletions(-)

diff --git a/changelogs/unreleased/gh-8657-move-triggers-to-trigger-registry.md b/changelogs/unreleased/gh-8657-move-triggers-to-trigger-registry.md
index 968ee0a0dc..d6165d86ec 100644
--- a/changelogs/unreleased/gh-8657-move-triggers-to-trigger-registry.md
+++ b/changelogs/unreleased/gh-8657-move-triggers-to-trigger-registry.md
@@ -1,8 +1,4 @@
 ## feature/lua
 
-* **[Breaking change]** Triggers from `space_object`, `box.session`, and
-  `box.ctl` were moved to the trigger registry (gh-8657). Now triggers set with
-  `space_object:on_replace()` or `space_object:before_replace()` are attached to
-  the space id, not to `space_object`. So, if you delete a space with registered
-  triggers and create another space with the same id, it will use the remaining
-  triggers.
+* Triggers from `space_object`, `box.session`, and `box.ctl` were moved to
+  the trigger registry (gh-8657).
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 51dac67e0c..966d407220 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1793,6 +1793,7 @@ on_drop_space_commit(struct trigger *trigger, void *event)
 {
 	(void) event;
 	struct space *space = (struct space *)trigger->data;
+	space_remove_temporary_triggers(space);
 	space_delete(space);
 	return 0;
 }
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index a6a290be5c..0706a783b3 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -59,9 +59,9 @@ extern "C" {
 #include "vclock/vclock.h"
 
 /**
- * Set/Reset/Get a trigger to an event associated with space by id. Argument
- * event_fmt is a format string for the event name - it must expect one uint32_t
- * value as an argument.
+ * Set/Reset/Get a temporary trigger to an event associated with space by id.
+ * Argument event_fmt is a format string for the event name - it must expect
+ * one uint32_t value as an argument.
  */
 static int
 lbox_space_reset_trigger(struct lua_State *L, uint32_t space_id,
@@ -70,7 +70,8 @@ lbox_space_reset_trigger(struct lua_State *L, uint32_t space_id,
 	const char *event_name = tt_sprintf(event_fmt, space_id);
 	struct event *event = event_get(event_name, true);
 	assert(event != NULL);
-	return luaT_event_reset_trigger(L, 2, event);
+	return luaT_event_reset_trigger_with_flags(
+		L, 2, event, EVENT_TRIGGER_IS_TEMPORARY);
 }
 
 /**
diff --git a/src/box/lua/trigger.c b/src/box/lua/trigger.c
index d62758ba75..0c51de0dc4 100644
--- a/src/box/lua/trigger.c
+++ b/src/box/lua/trigger.c
@@ -293,7 +293,7 @@ luaT_event_reset_trigger_check_positional_input(struct lua_State *L, int bottom)
  */
 static int
 luaT_event_reset_trigger_by_name(struct lua_State *L, struct event *event,
-				 int name_idx, int func_idx)
+				 int name_idx, int func_idx, uint8_t flags)
 {
 	if (lua_type(L, name_idx) != LUA_TSTRING)
 		luaL_error(L, "name must be a string");
@@ -301,7 +301,8 @@ luaT_event_reset_trigger_by_name(struct lua_State *L, struct event *event,
 	if (luaL_iscallable(L, func_idx)) {
 		struct func_adapter *func =
 			func_adapter_lua_create(L, func_idx);
-		event_reset_trigger(event, trigger_name, func);
+		event_reset_trigger_with_flags(event, trigger_name, func,
+					       flags);
 		lua_pushvalue(L, func_idx);
 		return 1;
 	} else if (lua_isnil(L, func_idx) || luaL_isnull(L, func_idx)) {
@@ -312,7 +313,8 @@ luaT_event_reset_trigger_by_name(struct lua_State *L, struct event *event,
 }
 
 int
-luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
+luaT_event_reset_trigger_with_flags(struct lua_State *L, int bottom,
+				    struct event *event, uint8_t flags)
 {
 	assert(L != NULL);
 	assert(bottom >= 1);
@@ -322,13 +324,15 @@ luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
 	    !luaL_iscallable(L, -1)) {
 		lua_getfield(L, bottom, "name");
 		lua_getfield(L, bottom, "func");
-		return luaT_event_reset_trigger_by_name(L, event, -2, -1);
+		return luaT_event_reset_trigger_by_name(L, event, -2, -1,
+							flags);
 	}
 	/* Old way with name support. */
 	luaT_event_reset_trigger_check_positional_input(L, bottom);
 	const int top = bottom + 2;
 	if (!lua_isnil(L, top) && !luaL_isnull(L, top))
-		return luaT_event_reset_trigger_by_name(L, event, top, bottom);
+		return luaT_event_reset_trigger_by_name(L, event, top, bottom,
+							flags);
 	/*
 	 * Name is not passed - old API support.
 	 * 1. If triggers are not passed, return table of triggers.
@@ -376,7 +380,8 @@ luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
 	}
 	if (new_handler != NULL && old_handler != NULL) {
 		if (old_handler == new_handler) {
-			event_reset_trigger(event, new_name, new_trg);
+			event_reset_trigger_with_flags(event, new_name,
+						       new_trg, flags);
 		} else {
 			/*
 			 * Need to reference the event because it can be
@@ -389,14 +394,15 @@ luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
 			 * new trigger at the beginning of the trigger list.
 			 */
 			event_reset_trigger(event, new_name, NULL);
-			event_reset_trigger(event, new_name, new_trg);
+			event_reset_trigger_with_flags(event, new_name, new_trg,
+						       flags);
 			event_unref(event);
 		}
 	} else if (old_handler != NULL) {
 		event_reset_trigger(event, old_name, NULL);
 	} else {
 		assert(new_handler != NULL);
-		event_reset_trigger(event, new_name, new_trg);
+		event_reset_trigger_with_flags(event, new_name, new_trg, flags);
 	}
 	return ret_count;
 }
diff --git a/src/box/lua/trigger.h b/src/box/lua/trigger.h
index e7eaf5bc41..e1e09d81aa 100644
--- a/src/box/lua/trigger.h
+++ b/src/box/lua/trigger.h
@@ -5,6 +5,8 @@
  */
 #pragma once
 
+#include <stdint.h>
+
 #if defined(__cplusplus)
 extern "C" {
 #endif /* defined(__cplusplus) */
@@ -56,7 +58,14 @@ box_lua_trigger_init(struct lua_State *L);
  * of the trigger list otherwise. The new trigger is returned.
  */
 int
-luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event);
+luaT_event_reset_trigger_with_flags(struct lua_State *L, int bottom,
+				    struct event *event, uint8_t flags);
+
+static inline int
+luaT_event_reset_trigger(struct lua_State *L, int bottom, struct event *event)
+{
+	return luaT_event_reset_trigger_with_flags(L, bottom, event, 0);
+}
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/space.c b/src/box/space.c
index 0f239bf48a..6e21f26100 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -423,6 +423,19 @@ space_reset_events(struct space *space)
 	space->run_recovery_triggers = false;
 }
 
+void
+space_remove_temporary_triggers(struct space *space)
+{
+	struct space_event *space_events[] = {
+		&space->on_replace_event,
+		&space->before_replace_event,
+	};
+	for (size_t i = 0; i < lengthof(space_events); ++i) {
+		event_remove_temporary_triggers(space_events[i]->by_id);
+		event_remove_temporary_triggers(space_events[i]->by_name);
+	}
+}
+
 int
 space_create(struct space *space, struct engine *engine,
 	     const struct space_vtab *vtab, struct space_def *def,
diff --git a/src/box/space.h b/src/box/space.h
index 7b3d316565..4c6d039eac 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -286,6 +286,12 @@ struct space_alter_stmt {
 	struct tuple *new_tuple;
 };
 
+/**
+ * Remove all temporary triggers from all associated events.
+ */
+void
+space_remove_temporary_triggers(struct space *space);
+
 /**
  * Detach constraints from space. They can be reattached or deleted then.
  */
diff --git a/test/box-luatest/gh_4264_recursive_trigger_test.lua b/test/box-luatest/gh_4264_recursive_trigger_test.lua
index 19c12e914c..36ab131fcb 100644
--- a/test/box-luatest/gh_4264_recursive_trigger_test.lua
+++ b/test/box-luatest/gh_4264_recursive_trigger_test.lua
@@ -37,8 +37,6 @@ g.test_recursive_trigger_invocation = function(cg)
         s:on_replace(f2)
         s:on_replace(f1)
         s:replace{1}
-        s:on_replace(nil, f2)
-        s:on_replace(nil, f1)
         return order
     end)
     t.assert_equals(order, {11, 21, 31, 32, 22, 12},
diff --git a/test/box-luatest/gh_8027_txn_handle_abort_in_trigger_test.lua b/test/box-luatest/gh_8027_txn_handle_abort_in_trigger_test.lua
index 336c6e3e05..0cdb91c293 100644
--- a/test/box-luatest/gh_8027_txn_handle_abort_in_trigger_test.lua
+++ b/test/box-luatest/gh_8027_txn_handle_abort_in_trigger_test.lua
@@ -17,13 +17,9 @@ end)
 
 g.after_each(function(cg)
     cg.server:exec(function()
-        local s = box.space.test
-        t.assert_not_equals(s, nil)
-        s:before_replace(nil, nil, 't1')
-        s:before_replace(nil, nil, 't2')
-        s:on_replace(nil, nil, 't1')
-        s:on_replace(nil, nil, 't2')
-        s:drop()
+        if box.space.test ~= nil then
+            box.space.test:drop()
+        end
     end)
 end)
 
@@ -37,13 +33,13 @@ g.test_yield_before_replace = function(cg)
                 fiber.yield()
             end
             return new
-        end, nil, 't1')
+        end)
         s:before_replace(function(_, new)
             if new[2] == 10 then
                 fiber.yield()
             end
             return new
-        end, nil, 't2')
+        end)
         local errmsg = 'Transaction has been aborted by a fiber yield'
         t.assert_error_msg_equals(errmsg, s.insert, s, {10, 1})
         t.assert_error_msg_equals(errmsg, s.insert, s, {1, 10})
diff --git a/test/box/before_replace.result b/test/box/before_replace.result
index 438b548bff..dad4fe3cc5 100644
--- a/test/box/before_replace.result
+++ b/test/box/before_replace.result
@@ -360,9 +360,6 @@ s2:select()
 ---
 - - [1, 1, 2, 2]
 ...
-s2:before_replace(nil, ret_update)
----
-...
 s2:drop()
 ---
 ...
@@ -833,9 +830,6 @@ save_type
 ---
 - UPSERT
 ...
-s:before_replace(nil, find_type)
----
-...
 s:drop()
 ---
 ...
@@ -851,7 +845,7 @@ _ = s:create_index('pk')
 save_type = ''
 ---
 ...
-tid = s:before_replace(function(old,new, name, type) save_type = type return new end)
+_ = s:before_replace(function(old,new, name, type) save_type = type return new end)
 ---
 ...
 _ = s:insert{1}
@@ -861,9 +855,6 @@ save_type
 ---
 - INSERT
 ...
-s:before_replace(nil, tid)
----
-...
 s:drop()
 ---
 ...
@@ -883,7 +874,7 @@ test_run:cmd('switch test')
 ---
 - true
 ...
-require('fiber').set_max_slice(100500)
+require('fiber').set_max_slice(15)
 ---
 ...
 s = box.schema.space.create('test_on_schema_init')
diff --git a/test/box/before_replace.test.lua b/test/box/before_replace.test.lua
index 3f7020028c..9f32f4a974 100644
--- a/test/box/before_replace.test.lua
+++ b/test/box/before_replace.test.lua
@@ -116,7 +116,6 @@ s2:insert{1, 1, 1, 1}
 s2:before_replace(ret_update) == ret_update
 s2.index.sk:update(1, {{'+', 4, 1}})
 s2:select()
-s2:before_replace(nil, ret_update)
 s2:drop()
 
 -- Stacking triggers.
@@ -291,7 +290,6 @@ save_type
 _ = s:upsert({3,4,5}, {{'+', 2, 1}})
 save_type
 
-s:before_replace(nil, find_type)
 s:drop()
 
 --
@@ -303,11 +301,10 @@ _ = s:create_index('pk')
 
 save_type = ''
 
-tid = s:before_replace(function(old,new, name, type) save_type = type return new end)
+_ = s:before_replace(function(old,new, name, type) save_type = type return new end)
 _ = s:insert{1}
 save_type
 
-s:before_replace(nil, tid)
 s:drop()
 
 --
@@ -317,7 +314,7 @@ s:drop()
 test_run:cmd('create server test with script="box/on_schema_init.lua"')
 test_run:cmd('start server test')
 test_run:cmd('switch test')
-require('fiber').set_max_slice(100500)
+require('fiber').set_max_slice(15)
 s = box.schema.space.create('test_on_schema_init')
 _ = s:create_index('pk')
 test_run:cmd('setopt delimiter ";"')
diff --git a/test/box/on_replace.result b/test/box/on_replace.result
index a02cdfb50a..8f25e8ab5b 100644
--- a/test/box/on_replace.result
+++ b/test/box/on_replace.result
@@ -115,10 +115,7 @@ n
 ---
 - [2, 'd', 'e', 'f']
 ...
-trigger_id = ts:on_replace(function() test = 1 end)
----
-...
-type(trigger_id)
+type(ts:on_replace(function() test = 1 end))
 ---
 - function
 ...
@@ -126,12 +123,6 @@ type(trigger_id)
 ---
 - 2
 ...
-ts:on_replace(nil, trigger_id)
----
-...
-ts:on_replace(nil, save_out)
----
-...
 ts:drop()
 ---
 ...
@@ -545,9 +536,6 @@ s:select()
 ---
 - []
 ...
-s:on_replace(nil, t)
----
-...
 s:drop() -- test_on_repl_ddl
 ---
 ...
@@ -582,7 +570,7 @@ x1 = 1;
 x2 = 1;
 ---
 ...
-s1_t = s1:on_replace(function(old, new)
+_ = s1:on_replace(function(old, new)
     for i = 1, 3 do
         s2:insert{x1}
         x1 = x1 + 1
@@ -594,7 +582,7 @@ s1_t = s1:on_replace(function(old, new)
 end);
 ---
 ...
-s2_t = s2:on_replace(function(old, new)
+_ = s2:on_replace(function(old, new)
     for i = 1, 3 do
         s3:insert{x2}
         x2 = x2 + 1
@@ -637,15 +625,9 @@ s3:select()
   - [8]
   - [9]
 ...
-s1:on_replace(nil, s1_t)
----
-...
 s1:drop()
 ---
 ...
-s2:on_replace(nil, s2_t)
----
-...
 s2:drop()
 ---
 ...
@@ -676,10 +658,10 @@ _ = s3:create_index('pk')
 x = 1
 ---
 ...
-t1 = s1:on_replace(function(old, new) s2:insert(new:update{{'!', 2, x}}) x = x + 1 end)
+_ = s1:on_replace(function(old, new) s2:insert(new:update{{'!', 2, x}}) x = x + 1 end)
 ---
 ...
-t2 = s1:on_replace(function(old, new) s3:insert(new:update{{'!', 2, x}}) x = x + 1 end)
+_ = s1:on_replace(function(old, new) s3:insert(new:update{{'!', 2, x}}) x = x + 1 end)
 ---
 ...
 box.begin() s1:insert{1} s1:insert{2} s1:insert{3} box.commit()
@@ -703,12 +685,6 @@ s3:select()
   - [2, 3]
   - [3, 5]
 ...
-s1:on_replace(nil, t1)
----
-...
-s1:on_replace(nil, t2)
----
-...
 s1:drop()
 ---
 ...
diff --git a/test/box/on_replace.test.lua b/test/box/on_replace.test.lua
index 6ec7a94edd..e0da1869c3 100644
--- a/test/box/on_replace.test.lua
+++ b/test/box/on_replace.test.lua
@@ -42,11 +42,8 @@ ts:replace{2, 'd', 'e', 'f'}
 o
 n
 
-trigger_id = ts:on_replace(function() test = 1 end)
-type(trigger_id)
+type(ts:on_replace(function() test = 1 end))
 #ts:on_replace()
-ts:on_replace(nil, trigger_id)
-ts:on_replace(nil, save_out)
 ts:drop()
 
 -- test garbage in lua stack
@@ -204,7 +201,6 @@ s:replace({8, 9})
 t = s:on_replace(function () s.index.pk:rename('newname') end, t)
 s:replace({9, 10})
 s:select()
-s:on_replace(nil, t)
 s:drop() -- test_on_repl_ddl
 
 --
@@ -222,7 +218,7 @@ test_run:cmd("setopt delimiter ';'");
 x1 = 1;
 x2 = 1;
 
-s1_t = s1:on_replace(function(old, new)
+_ = s1:on_replace(function(old, new)
     for i = 1, 3 do
         s2:insert{x1}
         x1 = x1 + 1
@@ -233,7 +229,7 @@ s1_t = s1:on_replace(function(old, new)
     pcall(s2.insert, s2, {123, 'fail'})
 end);
 
-s2_t = s2:on_replace(function(old, new)
+_ = s2:on_replace(function(old, new)
     for i = 1, 3 do
         s3:insert{x2}
         x2 = x2 + 1
@@ -254,9 +250,7 @@ s1:select()
 s2:select()
 s3:select()
 
-s1:on_replace(nil, s1_t)
 s1:drop()
-s2:on_replace(nil, s2_t)
 s2:drop()
 s3:drop()
 
@@ -272,8 +266,8 @@ _ = s3:create_index('pk')
 
 x = 1
 
-t1 = s1:on_replace(function(old, new) s2:insert(new:update{{'!', 2, x}}) x = x + 1 end)
-t2 = s1:on_replace(function(old, new) s3:insert(new:update{{'!', 2, x}}) x = x + 1 end)
+_ = s1:on_replace(function(old, new) s2:insert(new:update{{'!', 2, x}}) x = x + 1 end)
+_ = s1:on_replace(function(old, new) s3:insert(new:update{{'!', 2, x}}) x = x + 1 end)
 
 box.begin() s1:insert{1} s1:insert{2} s1:insert{3} box.commit()
 
@@ -281,8 +275,6 @@ s1:select()
 s2:select()
 s3:select()
 
-s1:on_replace(nil, t1)
-s1:on_replace(nil, t2)
 s1:drop()
 s2:drop()
 s3:drop()
diff --git a/test/box/push.result b/test/box/push.result
index dd85bedfc8..e3e4d1253e 100644
--- a/test/box/push.result
+++ b/test/box/push.result
@@ -321,10 +321,7 @@ box.schema.func.drop('do_push')
 s:truncate()
 ---
 ...
-function on_replace() box.session.push('replace') end
----
-...
-_ = s:on_replace(on_replace)
+_ = s:on_replace(function() box.session.push('replace') end)
 ---
 ...
 c:reload_schema()
@@ -345,9 +342,6 @@ s:select{}
 c:close()
 ---
 ...
-_ = s:on_replace(nil, on_replace)
----
-...
 s:drop()
 ---
 ...
diff --git a/test/box/push.test.lua b/test/box/push.test.lua
index b22b195c3a..a66d855a07 100644
--- a/test/box/push.test.lua
+++ b/test/box/push.test.lua
@@ -167,15 +167,13 @@ box.schema.func.drop('do_push')
 -- Test push from a non-call request.
 --
 s:truncate()
-function on_replace() box.session.push('replace') end
-_ = s:on_replace(on_replace)
+_ = s:on_replace(function() box.session.push('replace') end)
 c:reload_schema()
 c.space.test:replace({200}, {on_push = table.insert, on_push_ctx = messages})
 messages
 s:select{}
 
 c:close()
-_ = s:on_replace(nil, on_replace)
 s:drop()
 
 --
diff --git a/test/engine-luatest/space_triggers_test.lua b/test/engine-luatest/space_triggers_test.lua
index 87c1a71dad..e2a95a0aa8 100644
--- a/test/engine-luatest/space_triggers_test.lua
+++ b/test/engine-luatest/space_triggers_test.lua
@@ -152,6 +152,104 @@ g.test_before_replace_return_nil_or_none = function(cg)
     end, {cg.params.engine, cg.params.space_name})
 end
 
+g = t.group('Old API triggers', t.helpers.matrix{
+    engine = {'vinyl', 'memtx'},
+})
+
+g.before_all(function(cg)
+    cg.server = server:new({alias = 'master'})
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:stop()
+end)
+
+-- Checks if triggers set with old API are removed with the space.
+g.test_old_api_triggers_are_temporary = function(cg)
+    cg.server:exec(function(engine)
+        local trigger = require('trigger')
+        t.assert_equals(trigger.info(), {})
+        local space_id = 743
+        local on_replace_event = string.format('box.space[%d].on_replace',
+            space_id)
+        local before_replace_event =
+            string.format('box.space[%d].before_replace', space_id)
+        local s = box.schema.create_space('test1', {
+            id = space_id, engine = engine,
+            format = {{name = 'field1', type = 'unsigned'}}
+        })
+        s:create_index('pk')
+
+        local all_triggers = {}
+        local non_space_triggers = {}
+        local function space_set_trigger(new, old, name)
+            local h = s:on_replace(new, old, name)
+            s:before_replace(new, old, name)
+            table.insert(all_triggers, 1, {name, new})
+            return h
+        end
+        local function event_set_trigger(name, handler)
+            local h = trigger.set(on_replace_event, name, handler)
+            trigger.set(before_replace_event, name, handler)
+            table.insert(all_triggers, 1, {name, handler})
+            table.insert(non_space_triggers, 1, {name, handler})
+            return h
+        end
+        local function check_space_triggers(expected)
+            -- Space shows only handlers.
+            local expected_handlers = {}
+            for _, trigger_info in pairs(expected) do
+                table.insert(expected_handlers, trigger_info[2])
+            end
+            t.assert_equals(s:on_replace(), expected_handlers)
+            t.assert_equals(s:before_replace(), expected_handlers)
+        end
+        local function check_event_triggers(expected)
+            local info = trigger.info(on_replace_event)[on_replace_event]
+            t.assert_equals(info, expected)
+            info = trigger.info(before_replace_event)[before_replace_event]
+            t.assert_equals(info, expected)
+        end
+
+        -- Insert and remove trigger.
+        local h = space_set_trigger(function() end, nil)
+        space_set_trigger(nil, h)
+        all_triggers = {}
+
+        space_set_trigger(function() end, nil, "space_trigger1")
+        event_set_trigger("event_trigger1", function() end)
+        space_set_trigger(function() end, nil, "space_trigger2")
+        event_set_trigger("event_trigger2", function() end)
+
+        space_set_trigger(function() end, nil, "space_replaced_with_event")
+        event_set_trigger("space_replaced_with_event", function() end)
+        table.remove(all_triggers, 2)
+
+        event_set_trigger("event_replaced_with_space", function() end)
+        space_set_trigger(function() end, nil, "event_replaced_with_space")
+        -- The last trigger was replaced with an old space API.
+        table.remove(non_space_triggers, 1)
+        table.remove(all_triggers, 2)
+
+        space_set_trigger(function() end, nil, "space_trigger3")
+
+        check_space_triggers(all_triggers)
+        check_event_triggers(all_triggers)
+
+        s:alter({
+            name = 'test2',
+            format = {{name = 'field1', type = 'unsigned'}}
+        })
+        check_space_triggers(all_triggers)
+        check_event_triggers(all_triggers)
+
+        s:drop()
+        check_event_triggers(non_space_triggers)
+
+    end, {cg.params.engine})
+end
+
 g = t.group('Recovery triggers', {
     -- Vinyl does not recover from snapshot.
     {engine = 'vinyl', snapshot = false},
diff --git a/test/engine/savepoint.result b/test/engine/savepoint.result
index d2def78edf..554f1a7b8f 100644
--- a/test/engine/savepoint.result
+++ b/test/engine/savepoint.result
@@ -221,9 +221,6 @@ s:select{}
 - - [1]
   - [2]
 ...
-s:on_replace(nil, on_replace)
----
-...
 s:drop()
 ---
 ...
@@ -299,9 +296,6 @@ errmsg1
 ---
 - 'Can not rollback to savepoint: the savepoint does not exist'
 ...
-s:on_replace(nil, on_replace2)
----
-...
 s:drop()
 ---
 ...
@@ -508,9 +502,6 @@ s:select{}
 - - [1, 'create savepoint']
   - [5]
 ...
-s:on_replace(nil, on_replace3)
----
-...
 s:truncate()
 ---
 ...
diff --git a/test/engine/savepoint.test.lua b/test/engine/savepoint.test.lua
index bc5bbed126..47c0819dd9 100644
--- a/test/engine/savepoint.test.lua
+++ b/test/engine/savepoint.test.lua
@@ -110,7 +110,6 @@ select3
 select2
 select1
 s:select{}
-s:on_replace(nil, on_replace)
 s:drop()
 
 -- Test rollback to savepoint, created in trigger,
@@ -143,7 +142,6 @@ select3
 select4
 ok1
 errmsg1
-s:on_replace(nil, on_replace2)
 s:drop()
 
 -- Test incorrect savepoints usage inside a transaction.
@@ -248,7 +246,6 @@ s:replace{5}
 box.commit()
 test_run:cmd("setopt delimiter ''");
 s:select{}
-s:on_replace(nil, on_replace3)
 s:truncate()
 
 -- Several savepoints on a same statement.
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index a07f260c8a..08cabb7be4 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -574,9 +574,6 @@ cn:execute('insert into TEST3 values (null), (null), (null), (null)')
   - -29
   row_count: 4
 ...
-_ = box.space.TEST:on_replace(nil, push_id)
----
-...
 box.execute('drop table test')
 ---
 - row_count: 1
diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua
index 06a7f460a3..591f22238c 100644
--- a/test/sql/iproto.test.lua
+++ b/test/sql/iproto.test.lua
@@ -169,7 +169,6 @@ box.execute('create table test3 (id int primary key autoincrement)')
 box.schema.sequence.alter('TEST3', {min=-10000, step=-10})
 cn:execute('insert into TEST3 values (null), (null), (null), (null)')
 
-_ = box.space.TEST:on_replace(nil, push_id)
 box.execute('drop table test')
 s:drop()
 sq:drop()
diff --git a/test/vinyl/on_replace.result b/test/vinyl/on_replace.result
index b51fd94e97..b4367afe77 100644
--- a/test/vinyl/on_replace.result
+++ b/test/vinyl/on_replace.result
@@ -55,9 +55,6 @@ index:select{}
 ---
 - - [6, 'f']
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -114,9 +111,6 @@ index2:select{}
 ---
 - - [1, 2]
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -184,9 +178,6 @@ space:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -252,9 +243,6 @@ index2:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -354,9 +342,6 @@ index2:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -422,9 +407,6 @@ index:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -498,9 +480,6 @@ index2:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -587,9 +566,6 @@ index:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -696,9 +672,6 @@ index2:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -771,9 +744,6 @@ index:select{}
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
@@ -874,9 +844,6 @@ old_tuple, new_tuple
 fail = false
 ---
 ...
-space:on_replace(nil, on_replace)
----
-...
 space:drop()
 ---
 ...
diff --git a/test/vinyl/on_replace.test.lua b/test/vinyl/on_replace.test.lua
index b5c8c2d8d9..7dc57998de 100644
--- a/test/vinyl/on_replace.test.lua
+++ b/test/vinyl/on_replace.test.lua
@@ -18,7 +18,6 @@ fail = true
 space:insert({7, 'g'})
 old_tuple, new_tuple
 index:select{}
-space:on_replace(nil, on_replace)
 space:drop()
 fail = false
 
@@ -36,7 +35,6 @@ space:insert({2, 3})
 old_tuple, new_tuple
 index:select{}
 index2:select{}
-space:on_replace(nil, on_replace)
 space:drop()
 fail = false
 
@@ -56,7 +54,6 @@ space:replace({2, 100})
 old_tuple, new_tuple
 space:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- ensure trigger error causes rollback of only one statement
@@ -76,7 +73,6 @@ box.commit()
 index:select{}
 index2:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on replace in multiple indexes
@@ -102,7 +98,6 @@ old_tuple, new_tuple
 index:select{}
 index2:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on delete from one index
@@ -122,7 +117,6 @@ index:delete({1})
 old_tuple, new_tuple
 index:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on delete from multiple indexes
@@ -144,7 +138,6 @@ old_tuple, new_tuple
 index:select{}
 index2:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on update one index
@@ -168,7 +161,6 @@ index:update({1}, {{'=', 2, 'one'}})
 old_tuple, new_tuple
 index:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on update multiple indexes
@@ -196,7 +188,6 @@ old_tuple, new_tuple
 index:select{}
 index2:select{}
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on upsert one index
@@ -219,7 +210,6 @@ old_tuple, new_tuple
 index:select{}
 
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
 
 -- on upsert multiple indexes
@@ -246,5 +236,4 @@ old_tuple, new_tuple
 space:upsert({5, 5, 5}, {{'!', 4, 500}})
 old_tuple, new_tuple
 fail = false
-space:on_replace(nil, on_replace)
 space:drop()
-- 
GitLab