From 1399d9aa18f58bb0b4c3a638ec30c15f2273e476 Mon Sep 17 00:00:00 2001
From: Alexandr Lyapunov <a.lyapunov@corp.mail.ru>
Date: Tue, 25 Aug 2015 19:01:12 +0300
Subject: [PATCH] fixed gh-970, fixed gh-971 : added UPSERT to iproto, to
 net.box and added tests for them

---
 src/box/iproto.cc         |  3 ++-
 src/lua/net_box.cc        | 46 +++++++++++++++++++++++++++++++++++++++
 src/lua/net_box.lua       | 21 +++++++++++++++++-
 test/box/net.box.result   | 45 +++++++++++++++++++++++++++++++++++++-
 test/box/net.box.test.lua | 14 ++++++++++++
 5 files changed, 126 insertions(+), 3 deletions(-)

diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 0cdb34e22f..77573d5080 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -448,7 +448,7 @@ iproto_enqueue_batch(struct iproto_connection *con, struct ibuf *in)
 		 * in->rpos.
 		 */
 		if (msg->header.type >= IPROTO_SELECT &&
-		    msg->header.type <= IPROTO_EVAL) {
+		    msg->header.type <= IPROTO_UPSERT) {
 			/* Pre-parse request before putting it into the queue */
 			if (msg->header.bodycnt == 0) {
 				tnt_raise(ClientError, ER_INVALID_MSGPACK,
@@ -630,6 +630,7 @@ tx_process_msg(struct cmsg *m)
 		case IPROTO_REPLACE:
 		case IPROTO_UPDATE:
 		case IPROTO_DELETE:
+		case IPROTO_UPSERT:
 			assert(msg->request.type == msg->header.type);
 			struct iproto_port port;
 			iproto_port_init(&port, out, msg->header.sync);
diff --git a/src/lua/net_box.cc b/src/lua/net_box.cc
index 9e6fc68e0c..0a1303106f 100644
--- a/src/lua/net_box.cc
+++ b/src/lua/net_box.cc
@@ -358,6 +358,51 @@ netbox_encode_update(lua_State *L)
 	return 0;
 }
 
+static int
+netbox_encode_upsert(lua_State *L)
+{
+	if (lua_gettop(L) < 7)
+		return luaL_error(L, "Usage: netbox.encode_update(ibuf, sync, "
+			"space_id, index_id, key, ops, tuple)");
+
+	struct mpstream stream;
+	size_t svp = netbox_prepare_request(L, &stream, IPROTO_UPSERT);
+
+	luamp_encode_map(cfg, &stream, 6);
+
+	/* encode space_id */
+	uint32_t space_id = lua_tointeger(L, 3);
+	luamp_encode_uint(cfg, &stream, IPROTO_SPACE_ID);
+	luamp_encode_uint(cfg, &stream, space_id);
+
+	/* encode index_id */
+	uint32_t index_id = lua_tointeger(L, 4);
+	luamp_encode_uint(cfg, &stream, IPROTO_INDEX_ID);
+	luamp_encode_uint(cfg, &stream, index_id);
+
+	/* encode index_id */
+	luamp_encode_uint(cfg, &stream, IPROTO_INDEX_BASE);
+	luamp_encode_uint(cfg, &stream, 1);
+
+	/* encode in reverse order for speedup - see luamp_encode() code */
+	/* encode tuple */
+	luamp_encode_uint(cfg, &stream, IPROTO_TUPLE);
+	luamp_encode_tuple(L, cfg, &stream, 7);
+	lua_pop(L, 1); /* tuple */
+
+	/* encode ops */
+	luamp_encode_uint(cfg, &stream, IPROTO_OPS);
+	luamp_encode_tuple(L, cfg, &stream, 6);
+	lua_pop(L, 1); /* ops */
+
+	/* encode key */
+	luamp_encode_uint(cfg, &stream, IPROTO_KEY);
+	luamp_convert_key(L, cfg, &stream, 5);
+
+	netbox_encode_request(&stream, svp);
+	return 0;
+}
+
 int
 luaopen_net_box(struct lua_State *L)
 {
@@ -370,6 +415,7 @@ luaopen_net_box(struct lua_State *L)
 		{ "encode_replace", netbox_encode_replace },
 		{ "encode_delete",  netbox_encode_delete },
 		{ "encode_update",  netbox_encode_update },
+		{ "encode_upsert",  netbox_encode_upsert },
 		{ "encode_auth",    netbox_encode_auth },
 		{ NULL, NULL}
 	};
diff --git a/src/lua/net_box.lua b/src/lua/net_box.lua
index 76cbfa1371..f6b09505c1 100644
--- a/src/lua/net_box.lua
+++ b/src/lua/net_box.lua
@@ -21,6 +21,7 @@ local DELETE            = 5
 local CALL              = 6
 local AUTH              = 7
 local EVAL              = 8
+local UPSERT            = 9
 local PING              = 64
 local ERROR_TYPE        = 65536
 
@@ -38,6 +39,7 @@ local TUPLE             = 0x21
 local FUNCTION_NAME     = 0x22
 local USER              = 0x23
 local EXPR              = 0x27
+local OPS               = 0x28
 local DATA              = 0x30
 local ERROR             = 0x31
 local GREETING_SIZE     = 128
@@ -92,6 +94,7 @@ local requests = {
     [REPLACE] = internal.encode_replace;
     [DELETE] = internal.encode_delete;
     [UPDATE]  = internal.encode_update;
+    [UPSERT]  = internal.encode_upsert;
     [SELECT]  = function(wbuf, sync, spaceno, indexno, key, opts)
         if opts == nil then
             opts = {}
@@ -158,6 +161,11 @@ local function space_metatable(self)
                 return self:_update(space.id, key, oplist, 0)
             end,
 
+            upsert = function(space, key, oplist, tuple)
+                check_if_space(space)
+                return self:_upsert(space.id, key, oplist, tuple, 0)
+            end,
+
             get = function(space, key)
                 check_if_space(space)
                 local res = self:_select(space.id, 0, key,
@@ -240,6 +248,11 @@ local function index_metatable(self)
                 return self:_update(idx.space.id, key, oplist, idx.id)
             end,
 
+            upsert = function(idx, key, oplist, tuple)
+                check_if_index(idx)
+                return self:_upsert(idx.space.id, key, oplist, tuple, idx.id)
+            end,
+
         }
     }
 end
@@ -966,7 +979,13 @@ local remote_methods = {
     _update = function(self, spaceno, key, oplist, index_id)
         local res = self:_request(UPDATE, true, spaceno, index_id, key, oplist)
         return one_tuple(res.body[DATA])
-    end
+    end,
+
+    _upsert = function(self, spaceno, key, oplist, tuple, index_id)
+        local res = self:_request(UPSERT, true, spaceno,
+                                  index_id, key, oplist, tuple)
+        return one_tuple(res.body[DATA])
+    end,
 }
 
 setmetatable(remote, { __index = remote_methods })
diff --git a/test/box/net.box.result b/test/box/net.box.result
index 2f38086ed4..94ed39a94b 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -288,7 +288,7 @@ cn.space.net_box_test_space:insert{234, 1,2,3}
 ...
 cn.space.net_box_test_space.insert{234, 1,2,3}
 ---
-- error: 'builtin/net.box.lua:130: Use space:method(...) instead space.method(...)'
+- error: 'builtin/net.box.lua:133: Use space:method(...) instead space.method(...)'
 ...
 cn.space.net_box_test_space:replace{354, 1,2,3}
 ---
@@ -905,6 +905,49 @@ r = c.space.test:select(nil, {limit=5000})
 box.space.test:drop()
 ---
 ...
+-- gh-970 gh-971 UPSERT over network
+_ = box.schema.space.create('test')
+---
+...
+_ = box.space.test:create_index('primary', {type = 'TREE', parts = {1,'NUM'}})
+---
+...
+_ = box.space.test:insert{1, 2, "string"}
+---
+...
+c = net:new(box.cfg.listen)
+---
+...
+c.space.test:select{}
+---
+- - [1, 2, 'string']
+...
+c.space.test:upsert({1}, {{'+', 2, 1}}, {10, 20, 'nothing'}) -- common update
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+...
+c.space.test:upsert({2}, {{'+', 2, 1}}, {2, 4, 'something'}) -- insert
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+  - [2, 4, 'something']
+...
+c.space.test:upsert({2}, {{'+', 3, 100500}}, {2, 4, 'nothing'}) -- wrong operation
+---
+...
+c.space.test:select{}
+---
+- - [1, 3, 'string']
+  - [2, 4, 'something']
+...
+box.space.test:drop()
+---
+...
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
 ---
 ...
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 0ea5f5b40e..5942779adb 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -379,4 +379,18 @@ c = net:new(box.cfg.listen)
 r = c.space.test:select(nil, {limit=5000})
 box.space.test:drop()
 
+-- gh-970 gh-971 UPSERT over network
+_ = box.schema.space.create('test')
+_ = box.space.test:create_index('primary', {type = 'TREE', parts = {1,'NUM'}})
+_ = box.space.test:insert{1, 2, "string"}
+c = net:new(box.cfg.listen)
+c.space.test:select{}
+c.space.test:upsert({1}, {{'+', 2, 1}}, {10, 20, 'nothing'}) -- common update
+c.space.test:select{}
+c.space.test:upsert({2}, {{'+', 2, 1}}, {2, 4, 'something'}) -- insert
+c.space.test:select{}
+c.space.test:upsert({2}, {{'+', 3, 100500}}, {2, 4, 'nothing'}) -- wrong operation
+c.space.test:select{}
+box.space.test:drop()
+
 box.schema.user.revoke('guest', 'read,write,execute', 'universe')
-- 
GitLab