From 62c49367a2eab6d8008da2d7201ffa03e002c31e Mon Sep 17 00:00:00 2001
From: Nikita Zheleztsov <n.zheleztsov@proton.me>
Date: Tue, 16 Jul 2024 18:56:56 +0300
Subject: [PATCH] iproto: introduce FETCH_SNAPSHOT_CURSOR feature

This commit introduces FETCH_SNAPSHOT_CURSOR feature, which is available
only in EE. The feature is not returned in response to IPROTO_ID and is
not shown in box.iproto.protocol_features in Community Edition. Its id
is shown only in box.iproto.feature, which is a list of all available
features in the current version.

Needed for tarantool/tarantool-ee#741

NO_CHANGELOG=minor

@TarantoolBot document
Title: Document iproto feature FETCH_SNAPSHOT_CURSOR

Root document: https://www.tarantool.io/en/doc/latest/reference/reference_lua/net_box/#net-box-connect

FETCH_SNAPSHOT_CURSOR feature requires cursor FETCH_SNAPSHOT on the
server. Its ID is IPROTO_FEATURE_FETCH_SNAPSHOT_CURSOR. IPROTO version
is 8 or more, Enterprise Edition is also required.
---
 src/box/box.cc                                |  1 +
 src/box/iproto.cc                             |  2 --
 src/box/iproto_features.c                     |  4 ++++
 src/box/iproto_features.h                     |  8 ++++++-
 src/box/lua/iproto.c                          |  6 +++--
 src/box/lua/net_box.c                         |  2 +-
 ...ort_iproto_constants_and_features_test.lua |  9 ++++++-
 test/box-py/iproto.result                     |  6 ++---
 test/box-py/iproto.test.py                    |  8 ++++++-
 test/box/net.box_iproto_id.result             | 24 ++++++++++++-------
 test/box/net.box_iproto_id.test.lua           | 16 +++++++++----
 11 files changed, 62 insertions(+), 24 deletions(-)

diff --git a/src/box/box.cc b/src/box/box.cc
index d2c5c18ab7..ff25901f88 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -6082,6 +6082,7 @@ void
 box_init(void)
 {
 	iproto_constants_init();
+	iproto_features_init();
 	port_init();
 	box_on_recovery_state_event =
 		event_get("box.ctl.on_recovery_state", true);
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 78c099a875..1cfac9e161 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -3851,8 +3851,6 @@ TRIGGER(trigger_on_change, trigger_on_change_iproto_notify);
 void
 iproto_init(int threads_count)
 {
-	iproto_features_init();
-
 	iproto_threads_count = 0;
 	struct session_vtab iproto_session_vtab = {
 		/* .push = */ iproto_session_push,
diff --git a/src/box/iproto_features.c b/src/box/iproto_features.c
index 5ee3ad1668..821faed7b6 100644
--- a/src/box/iproto_features.c
+++ b/src/box/iproto_features.c
@@ -85,4 +85,8 @@ iproto_features_init(void)
 			    IPROTO_FEATURE_CALL_RET_TUPLE_EXTENSION);
 	iproto_features_set(&IPROTO_CURRENT_FEATURES,
 			    IPROTO_FEATURE_CALL_ARG_TUPLE_EXTENSION);
+#if defined(ENABLE_FETCH_SNAPSHOT_CURSOR)
+	iproto_features_set(&IPROTO_CURRENT_FEATURES,
+			    IPROTO_FEATURE_FETCH_SNAPSHOT_CURSOR);
+#endif /* defined(ENABLE_FETCH_SNAPSHOT_CURSOR) */
 }
diff --git a/src/box/iproto_features.h b/src/box/iproto_features.h
index 8dcca26837..d8907d5f7b 100644
--- a/src/box/iproto_features.h
+++ b/src/box/iproto_features.h
@@ -77,6 +77,12 @@ extern "C" {
 	 * tuple formats are received in IPROTO_TUPLE_FORMATS field.
 	 */								\
 	_(CALL_ARG_TUPLE_EXTENSION, 9)					\
+	/**
+	 * Cursor (for checkpoint join) in FETCH_SNAPSHOT support:
+	 * IPROTO_IS_CHECKPOINT_JOIN, IPROTO_CHECKPOINT_VCLOCK and
+	 * IRPOTO_CHECKPOINT_LSN.
+	 */								\
+	 _(FETCH_SNAPSHOT_CURSOR, 10)					\
 
 #define IPROTO_FEATURE_MEMBER(s, v) IPROTO_FEATURE_ ## s = v,
 
@@ -101,7 +107,7 @@ struct iproto_features {
  * `box.iproto.protocol_version` needs to be updated correspondingly.
  */
 enum {
-	IPROTO_CURRENT_VERSION = 7,
+	IPROTO_CURRENT_VERSION = 8,
 };
 
 /**
diff --git a/src/box/lua/iproto.c b/src/box/lua/iproto.c
index f8c5e4c3e8..aa2e61f7de 100644
--- a/src/box/lua/iproto.c
+++ b/src/box/lua/iproto.c
@@ -188,8 +188,10 @@ push_iproto_protocol_features(struct lua_State *L)
 		lua_newtable(L);
 	for (int i = 0; i < iproto_feature_id_MAX; i++) {
 		char *name = strtolowerdup(iproto_feature_id_strs[i]);
-		lua_pushboolean(L, true);
-		lua_setfield(L, -2, name);
+		if (iproto_features_test(&IPROTO_CURRENT_FEATURES, i)) {
+			lua_pushboolean(L, true);
+			lua_setfield(L, -2, name);
+		}
 		lua_pushinteger(L, i);
 		lua_setfield(L, -3, name);
 		free(name);
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index b84b01c15b..8efa41d200 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -82,7 +82,7 @@ enum {
 	/**
 	 * IPROTO protocol version supported by the netbox connector.
 	 */
-	NETBOX_IPROTO_VERSION = 7,
+	NETBOX_IPROTO_VERSION = 8,
 };
 
 /**
diff --git a/test/box-luatest/gh_7894_export_iproto_constants_and_features_test.lua b/test/box-luatest/gh_7894_export_iproto_constants_and_features_test.lua
index b9c599258b..64c341ed2b 100644
--- a/test/box-luatest/gh_7894_export_iproto_constants_and_features_test.lua
+++ b/test/box-luatest/gh_7894_export_iproto_constants_and_features_test.lua
@@ -3,6 +3,8 @@ local t = require('luatest')
 
 local g = t.group()
 
+local is_enterprise = t.tarantool.is_enterprise_package()
+
 g.before_all(function(cg)
     cg.server = server:new{
         alias   = 'dflt',
@@ -86,6 +88,9 @@ local reference_table = {
         INDEX_NAME = 0x5f,
         TUPLE_FORMATS = 0x60,
         IS_SYNC = 0x61,
+        IS_CHECKPOINT_JOIN = 0x62,
+        CHECKPOINT_VCLOCK = 0x63,
+        CHECKPOINT_LSN = 0x64,
     },
 
     -- `iproto_metadata_key` enumeration.
@@ -166,7 +171,7 @@ local reference_table = {
     },
 
     -- `IPROTO_CURRENT_VERSION` constant
-    protocol_version = 7,
+    protocol_version = 8,
 
     -- `feature_id` enumeration
     protocol_features = {
@@ -180,6 +185,7 @@ local reference_table = {
         dml_tuple_extension = true,
         call_ret_tuple_extension = true,
         call_arg_tuple_extension = true,
+        fetch_snapshot_cursor = is_enterprise and true or nil,
     },
     feature = {
         streams = 0,
@@ -192,6 +198,7 @@ local reference_table = {
         dml_tuple_extension = 7,
         call_ret_tuple_extension = 8,
         call_arg_tuple_extension = 9,
+        fetch_snapshot_cursor = 10,
     },
 }
 
diff --git a/test/box-py/iproto.result b/test/box-py/iproto.result
index d8224563cd..edc6abc610 100644
--- a/test/box-py/iproto.result
+++ b/test/box-py/iproto.result
@@ -210,11 +210,11 @@ Invalid MsgPack - request body
 # Invalid auth_type
 Invalid MsgPack - request body
 # Empty request body
-version=7, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
+version=8, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
 # Unknown version and features
-version=7, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
+version=8, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
 # Unknown request key
-version=7, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
+version=8, features=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], auth_type=chap-sha1
 
 #
 # gh-6257 Watchers
diff --git a/test/box-py/iproto.test.py b/test/box-py/iproto.test.py
index 487e92c032..a661343970 100644
--- a/test/box-py/iproto.test.py
+++ b/test/box-py/iproto.test.py
@@ -490,8 +490,14 @@ print("""
 """)
 def print_id_response(resp):
     if resp["header"][IPROTO_CODE] == REQUEST_TYPE_OK:
+        features = resp["body"][IPROTO_FEATURES]
+        # Some features are available only in EE, so remove them
+        # from the list of features so that diff test passes.
+        # IPROTO_FEATURES_FETCH_SNAPSHOT_CURSOR - 10.
+        if 10 in features:
+            features.remove(10)
         print("version={}, features={}, auth_type={}".format(
-            resp["body"][IPROTO_VERSION], resp["body"][IPROTO_FEATURES],
+            resp["body"][IPROTO_VERSION], features,
             resp["body"].get(IPROTO_AUTH_TYPE, "").decode("utf-8")))
     else:
         print(str(resp["body"][IPROTO_ERROR].decode("utf-8")))
diff --git a/test/box/net.box_iproto_id.result b/test/box/net.box_iproto_id.result
index 1f613a5150..94ea756fdc 100644
--- a/test/box/net.box_iproto_id.result
+++ b/test/box/net.box_iproto_id.result
@@ -9,15 +9,23 @@ errinj = box.error.injection
  | ---
  | ...
 
+function print_features(conn)                                               \
+    local f = c.peer_protocol_features                                      \
+    f.fetch_snapshot_cursor = nil                                           \
+    return f                                                                \
+end
+ | ---
+ | ...
+
 -- actual version and feautures
 c = net.connect(box.cfg.listen)
  | ---
  | ...
 c.peer_protocol_version
  | ---
- | - 7
+ | - 8
  | ...
-c.peer_protocol_features
+print_features(c)
  | ---
  | - transactions: true
  |   watchers: true
@@ -50,7 +58,7 @@ c.peer_protocol_version
  | ---
  | - 0
  | ...
-c.peer_protocol_features
+print_features(c)
  | ---
  | - transactions: false
  |   watchers: false
@@ -104,7 +112,7 @@ c.peer_protocol_version
  | ---
  | - 9000
  | ...
-c.peer_protocol_features
+print_features(c)
  | ---
  | - transactions: true
  |   watchers: true
@@ -161,9 +169,9 @@ c.error -- error
  | ...
 c.peer_protocol_version
  | ---
- | - 7
+ | - 8
  | ...
-c.peer_protocol_features
+print_features(c)
  | ---
  | - transactions: false
  |   watchers: true
@@ -193,9 +201,9 @@ c.error -- error
  | ...
 c.peer_protocol_version
  | ---
- | - 7
+ | - 8
  | ...
-c.peer_protocol_features
+print_features(c)
  | ---
  | - transactions: true
  |   watchers: true
diff --git a/test/box/net.box_iproto_id.test.lua b/test/box/net.box_iproto_id.test.lua
index a69ffd1a37..dc936fd41f 100644
--- a/test/box/net.box_iproto_id.test.lua
+++ b/test/box/net.box_iproto_id.test.lua
@@ -2,10 +2,16 @@ test_run = require('test_run').new()
 net = require('net.box')
 errinj = box.error.injection
 
+function print_features(conn)                                               \
+    local f = c.peer_protocol_features                                      \
+    f.fetch_snapshot_cursor = nil                                           \
+    return f                                                                \
+end
+
 -- actual version and feautures
 c = net.connect(box.cfg.listen)
 c.peer_protocol_version
-c.peer_protocol_features
+print_features(c)
 c:close()
 
 -- no IPROTO_ID => assume no features
@@ -13,7 +19,7 @@ errinj.set('ERRINJ_IPROTO_DISABLE_ID', true)
 c = net.connect(box.cfg.listen)
 c.error -- none
 c.peer_protocol_version
-c.peer_protocol_features
+print_features(c)
 errinj.set('ERRINJ_IPROTO_DISABLE_ID', false)
 
 -- required version
@@ -27,7 +33,7 @@ c:close()
 c = net.connect(box.cfg.listen, {required_protocol_version = 9001})
 c.error -- error
 c.peer_protocol_version
-c.peer_protocol_features
+print_features(c)
 c:close()
 errinj.set('ERRINJ_IPROTO_SET_VERSION', -1)
 
@@ -44,14 +50,14 @@ c = net.connect(box.cfg.listen,                                             \
                 {required_protocol_features = {'streams', 'transactions'}})
 c.error -- error
 c.peer_protocol_version
-c.peer_protocol_features
+print_features(c)
 c:close()
 errinj.set('ERRINJ_IPROTO_FLIP_FEATURE', -1)
 c = net.connect(box.cfg.listen,                                             \
                 {required_protocol_features = {'foo', 'transactions', 'bar'}})
 c.error -- error
 c.peer_protocol_version
-c.peer_protocol_features
+print_features(c)
 c:close()
 
 -- required features and version are checked on reconnect
-- 
GitLab