From 7676b2b1a6371f3df16fa017fa03aab57ec066d2 Mon Sep 17 00:00:00 2001
From: Alexander Turenko <alexander.turenko@tarantool.org>
Date: Tue, 26 Mar 2019 00:25:12 +0300
Subject: [PATCH] sql: make SQL_BIND optional in an iproto request

The documentation [1] says this field is optional. I don't know which
commit lead to the regression, only that 2.1.1-7-gd381a45b6 is good.

[1]: https://tarantool.io/en/doc/2.1/dev_guide/internals/sql_protocol/

Fixes #4077.
---
 src/box/iproto.cc                             | 12 ++--
 .../gh-4077-iproto-execute-no-bind.test.lua   | 69 +++++++++++++++++++
 2 files changed, 76 insertions(+), 5 deletions(-)
 create mode 100755 test/sql-tap/gh-4077-iproto-execute-no-bind.test.lua

diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 3b0ba62346..8934b7488f 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -1622,8 +1622,8 @@ tx_process_sql(struct cmsg *m)
 	struct iproto_msg *msg = tx_accept_msg(m);
 	struct obuf *out;
 	struct sql_response response;
-	struct sql_bind *bind;
-	int bind_count;
+	struct sql_bind *bind = NULL;
+	int bind_count = 0;
 	const char *sql;
 	uint32_t len;
 
@@ -1633,9 +1633,11 @@ tx_process_sql(struct cmsg *m)
 		goto error;
 	assert(msg->header.type == IPROTO_EXECUTE);
 	tx_inject_delay();
-	bind_count = sql_bind_list_decode(msg->sql.bind, &bind);
-	if (bind_count < 0)
-		goto error;
+	if (msg->sql.bind != NULL) {
+		bind_count = sql_bind_list_decode(msg->sql.bind, &bind);
+		if (bind_count < 0)
+			goto error;
+	}
 	sql = msg->sql.sql_text;
 	sql = mp_decode_str(&sql, &len);
 	if (sql_prepare_and_execute(sql, len, bind, bind_count, &response,
diff --git a/test/sql-tap/gh-4077-iproto-execute-no-bind.test.lua b/test/sql-tap/gh-4077-iproto-execute-no-bind.test.lua
new file mode 100755
index 0000000000..55804768c3
--- /dev/null
+++ b/test/sql-tap/gh-4077-iproto-execute-no-bind.test.lua
@@ -0,0 +1,69 @@
+#!/usr/bin/env tarantool
+
+local tap = require('tap')
+local net_box = require('net.box')
+local urilib = require('uri')
+local msgpack = require('msgpack')
+
+local IPROTO_REQUEST_TYPE       = 0x00
+local IPROTO_EXECUTE            = 0x0b
+local IPROTO_SYNC               = 0x01
+local IPROTO_SQL_TEXT           = 0x40
+local IPROTO_SQL_INFO           = 0x42
+local IPROTO_SQL_INFO_ROW_COUNT = 0x00
+local IPROTO_OK                 = 0x00
+local IPROTO_SCHEMA_VERSION     = 0x05
+local IPROTO_STATUS_KEY         = 0x00
+
+box.cfg({
+    listen = os.getenv('LISTEN') or 'localhost:3301',
+})
+
+box.schema.user.grant('guest', 'read,write,execute', 'universe')
+box.sql.execute('create table T(ID int primary key)')
+
+local test = tap.test('gh-4077-iproto-execute-no-bind')
+test:plan(3)
+
+local uri = urilib.parse(box.cfg.listen)
+local sock = net_box.establish_connection(uri.host, uri.service)
+
+-- Send request w/o SQL_BIND field in body.
+local next_request_id = 16
+local header = msgpack.encode({
+    [IPROTO_REQUEST_TYPE] = IPROTO_EXECUTE,
+    [IPROTO_SYNC] = next_request_id,
+})
+local body = msgpack.encode({
+    [IPROTO_SQL_TEXT] = 'insert into T values (1)',
+})
+local size = msgpack.encode(header:len() + body:len())
+sock:write(size .. header .. body)
+
+-- Read response.
+local size = msgpack.decode(sock:read(5))
+local header_body = sock:read(size)
+local header, header_len = msgpack.decode(header_body)
+local body = msgpack.decode(header_body:sub(header_len))
+sock:close()
+
+-- Verify response.
+header[IPROTO_SCHEMA_VERSION] = nil -- expect any
+local exp_header = {
+    [IPROTO_STATUS_KEY] = IPROTO_OK,
+    [IPROTO_SYNC] = next_request_id,
+}
+local exp_body = {
+    [IPROTO_SQL_INFO] = {
+        [IPROTO_SQL_INFO_ROW_COUNT] = 1,
+    }
+}
+test:is_deeply(exp_header, header, 'verify response header')
+test:is_deeply(exp_body, body, 'verify response body')
+
+-- Verify space data.
+local exp_res = {{1}}
+local res = box.space.T:pairs():map(box.tuple.totable):totable()
+test:is_deeply(res, exp_res, 'verify inserted data')
+
+os.exit(test:check() == true and 0 or 1)
-- 
GitLab