[patches] [PATCH 2/4] sql: introduce 'return_tuple' option for execute()
Vladislav Shpilevoy
v.shpilevoy at tarantool.org
Wed Mar 14 00:49:25 MSK 2018
If 'return_tuple' is specified and is set to true, then
last updated/inserted tuple is returned in IPROTO_DATA section.
Closes #2618
Signed-off-by: Vladislav Shpilevoy <v.shpilevoy at tarantool.org>
---
src/box/errcode.h | 2 +-
src/box/execute.c | 95 +++++++++++++++++++++++++++++++++++++++++++-----
src/box/execute.h | 20 +++++++++-
src/box/iproto.cc | 4 +-
src/box/lua/net_box.c | 28 +++++++++++++-
src/box/lua/net_box.lua | 31 +++++++++-------
test/box/misc.result | 33 +++++++++--------
test/sql/iproto.result | 67 +++++++++++++++++++++++++++++++++-
test/sql/iproto.test.lua | 22 +++++++++++
9 files changed, 255 insertions(+), 47 deletions(-)
diff --git a/src/box/errcode.h b/src/box/errcode.h
index beb37aa2f..0740acfb9 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -107,7 +107,7 @@ struct errcode_record {
/* 52 */_(ER_FUNCTION_EXISTS, "Function '%s' already exists") \
/* 53 */_(ER_BEFORE_REPLACE_RET, "Invalid return value of space:before_replace trigger: expected tuple or nil, got %s") \
/* 54 */_(ER_FUNCTION_MAX, "A limit on the total number of functions has been reached: %u") \
- /* 55 */_(ER_UNUSED4, "") \
+ /* 55 */_(ER_WRONG_SQL_OPTION, "Wrong SQL option: code = %llu, name = %s") \
/* 56 */_(ER_USER_MAX, "A limit on the total number of users has been reached: %u") \
/* 57 */_(ER_NO_SUCH_ENGINE, "Space engine '%s' does not exist") \
/* 58 */_(ER_RELOAD_CFG, "Can't set option '%s' dynamically") \
diff --git a/src/box/execute.c b/src/box/execute.c
index 756db5fd6..2c347e92c 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -42,6 +42,7 @@
#include "schema.h"
#include "port.h"
#include "memtx_tuple.h"
+#include "tuple_convert.h"
const char *sql_type_strs[] = {
NULL,
@@ -52,6 +53,10 @@ const char *sql_type_strs[] = {
"NULL",
};
+const char *sql_options_key_strs[] = {
+ "return tuple",
+};
+
/**
* Name and value of an SQL prepared statement parameter.
* @todo: merge with sqlite3_value.
@@ -241,6 +246,45 @@ sql_bind_list_decode(struct sql_request *request, const char *data,
return 0;
}
+/**
+ * Decode IPROTO_SQL_OPTIONS.
+ * @param[out] sql_request Request to decode to.
+ * @param data MessagePack encoded options.
+ *
+ * @retval 0 Success.
+ * @retval -1 Client error.
+ */
+static inline int
+sql_options_decode(struct sql_request *request, const char *data)
+{
+ assert(request != NULL);
+ assert(data != NULL);
+ if (mp_typeof(*data) != MP_MAP) {
+mp_error:
+ diag_set(ClientError, ER_INVALID_MSGPACK, "SQL options");
+ return -1;
+ }
+ uint32_t opts_count = mp_decode_map(&data);
+ for (uint32_t i = 0; i < opts_count; ++i) {
+ if (mp_typeof(*data) != MP_UINT)
+ goto mp_error;
+ uint64_t key = mp_decode_uint(&data);
+ if (key != SQL_OPTION_RETURN_TUPLE ||
+ mp_typeof(*data) != MP_BOOL) {
+ const char *name;
+ if (key >= sql_options_key_MAX)
+ name = "unknown";
+ else
+ name = sql_options_key_strs[key];
+ diag_set(ClientError, ER_WRONG_SQL_OPTION,
+ (unsigned long long)key, name);
+ return -1;
+ }
+ request->is_last_tuple_needed = mp_decode_bool(&data);
+ }
+ return 0;
+}
+
int
xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
struct region *region)
@@ -255,7 +299,7 @@ xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
assert((end - data) > 0);
if (mp_typeof(*data) != MP_MAP || mp_check_map(data, end) > 0) {
-error:
+mp_error:
diag_set(ClientError, ER_INVALID_MSGPACK, "packet body");
return -1;
}
@@ -267,19 +311,34 @@ error:
request->sync = row->sync;
for (uint32_t i = 0; i < map_size; ++i) {
uint8_t key = *data;
- if (key != IPROTO_SQL_BIND && key != IPROTO_SQL_TEXT) {
+ if (key != IPROTO_SQL_BIND && key != IPROTO_SQL_TEXT &&
+ key != IPROTO_SQL_OPTIONS) {
mp_check(&data, end); /* skip the key */
mp_check(&data, end); /* skip the value */
continue;
}
const char *value = ++data; /* skip the key */
if (mp_check(&data, end) != 0) /* check the value */
- goto error;
- if (key == IPROTO_SQL_BIND) {
+ goto mp_error;
+ switch(key) {
+ case IPROTO_SQL_BIND:
if (sql_bind_list_decode(request, value, region) != 0)
return -1;
- } else {
+ break;
+ case IPROTO_SQL_TEXT:
+ if (mp_typeof(*value) != MP_STR) {
+ diag_set(ClientError, ER_INVALID_MSGPACK,
+ "SQL text");
+ return -1;
+ }
request->sql_text = value;
+ break;
+ case IPROTO_SQL_OPTIONS:
+ if (sql_options_decode(request, value) != 0)
+ return -1;
+ break;
+ default:
+ unreachable();
}
}
if (request->sql_text == NULL) {
@@ -288,7 +347,7 @@ error:
return -1;
}
if (data != end)
- goto error;
+ goto mp_error;
return 0;
}
@@ -621,7 +680,6 @@ sql_execute_and_encode(sqlite3 *db, struct sqlite3_stmt *stmt, struct obuf *out,
goto err_body;
}
} else {
- keys = 1;
assert(port_tuple->size == 0);
if (iproto_reply_map_key(out, 1, IPROTO_SQL_INFO) != 0)
goto err_body;
@@ -635,6 +693,25 @@ sql_execute_and_encode(sqlite3 *db, struct sqlite3_stmt *stmt, struct obuf *out,
}
buf = mp_encode_uint(buf, IPROTO_SQL_ROW_COUNT);
buf = mp_encode_uint(buf, changes);
+
+ /* Reply DATA if needed. */
+ if (opts->last_tuple != NULL) {
+ keys = 2;
+ /*
+ * Even if a last tuple was requested but
+ * was not found, the empty IPROTO_DATA
+ * must be returned.
+ */
+ int tuples = *opts->last_tuple == NULL ? 0 : 1;
+ if (iproto_reply_array_key(out, tuples,
+ IPROTO_DATA) != 0)
+ goto err_body;
+ if (tuples == 1 &&
+ tuple_to_obuf(*opts->last_tuple, out) != 0)
+ goto err_body;
+ } else {
+ keys = 1;
+ }
}
port_destroy(&port);
iproto_reply_sql(out, &header_svp, sync, schema_version, keys);
@@ -649,7 +726,7 @@ err_execute:
int
sql_prepare_and_execute(const struct sql_request *request, struct obuf *out,
- struct region *region, bool is_last_tuple_needed)
+ struct region *region)
{
struct sql_options opts;
struct tuple *last_inserted_tuple = NULL;
@@ -671,7 +748,7 @@ sql_prepare_and_execute(const struct sql_request *request, struct obuf *out,
if (sql_bind(request, stmt) != 0)
goto err_stmt;
- if (is_last_tuple_needed)
+ if (request->is_last_tuple_needed)
sql_options_create(&opts, &last_inserted_tuple);
else
sql_options_create(&opts, NULL);
diff --git a/src/box/execute.h b/src/box/execute.h
index 0aef26702..a0d23a4ef 100644
--- a/src/box/execute.h
+++ b/src/box/execute.h
@@ -38,6 +38,13 @@
extern "C" {
#endif
+enum sql_options_key {
+ SQL_OPTION_RETURN_TUPLE = 0,
+ sql_options_key_MAX,
+};
+
+extern const char *sql_options_key_strs[];
+
struct obuf;
struct region;
struct sql_bind;
@@ -52,6 +59,8 @@ struct sql_request {
struct sql_bind *bind;
/** Length of the @bind. */
uint32_t bind_count;
+ /** True, if a last inserted tuple must be returned. */
+ bool is_last_tuple_needed;
};
/**
@@ -93,6 +102,15 @@ xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
* | IPROTO_SQL_ROW_COUNT: number |
* | } |
* | } |
+ * +-------------------- OR ----------------------+
+ * | IPROTO_BODY: { |
+ * | IPROTO_SQL_INFO: { |
+ * | IPROTO_SQL_ROW_COUNT: number |
+ * | }, |
+ * | IPROTO_DATA: [ |
+ * | tuple |
+ * | ] |
+ * | } |
* +----------------------------------------------+
*
* @param request IProto request.
@@ -105,7 +123,7 @@ xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
*/
int
sql_prepare_and_execute(const struct sql_request *request, struct obuf *out,
- struct region *region, bool is_last_tuple_needed);
+ struct region *region);
#if defined(__cplusplus)
} /* extern "C" { */
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 9ace0d082..f7784ef23 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -1382,15 +1382,13 @@ tx_process_sql(struct cmsg *m)
{
struct iproto_msg *msg = tx_accept_msg(m);
struct obuf *out = msg->connection->tx.p_obuf;
- bool is_last_tuple_needed = true;
tx_fiber_init(msg->connection->session, msg->header.sync);
if (tx_check_schema(msg->header.schema_version))
goto error;
assert(msg->header.type == IPROTO_EXECUTE);
- if (sql_prepare_and_execute(&msg->sql, out, &fiber()->gc,
- is_last_tuple_needed) != 0)
+ if (sql_prepare_and_execute(&msg->sql, out, &fiber()->gc) != 0)
goto error;
iproto_wpos_create(&msg->wpos, out);
return;
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index efb391379..40e585cfb 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -36,6 +36,7 @@
#include "scramble.h"
#include "box/iproto_constants.h"
+#include "box/execute.h"
#include "box/lua/tuple.h" /* luamp_convert_tuple() / luamp_convert_key() */
#include "box/xrow.h"
@@ -557,10 +558,31 @@ handle_error:
static int
netbox_encode_execute(lua_State *L)
{
- if (lua_gettop(L) < 6)
+ if (lua_gettop(L) < 6) {
+usage_error:
return luaL_error(L, "Usage: netbox.encode_execute(ibuf, "\
"sync, schema_version, query, parameters, "\
"options)");
+ }
+ /* Decode and validate options. */
+ struct luaL_field field;
+ luaL_tofield(L, cfg, 6, &field);
+ if (field.type != MP_MAP || field.size > 1)
+ goto usage_error;
+ bool opt_return_tuple;
+ if (field.size > 0) {
+ lua_getfield(L, 6, "return_tuple");
+ if (lua_isnil(L, -1) || !lua_isboolean(L, -1)) {
+ lua_pop(L, 1);
+ goto usage_error;
+ }
+ opt_return_tuple = lua_toboolean(L, -1);
+ lua_pop(L, 1);
+ } else {
+ opt_return_tuple = false;
+ }
+
+ /* Encode request. */
struct mpstream stream;
size_t svp = netbox_prepare_request(L, &stream, IPROTO_EXECUTE);
@@ -575,7 +597,9 @@ netbox_encode_execute(lua_State *L)
luamp_encode_tuple(L, cfg, &stream, 5);
luamp_encode_uint(cfg, &stream, IPROTO_SQL_OPTIONS);
- luamp_encode_tuple(L, cfg, &stream, 6);
+ luamp_encode_map(cfg, &stream, 1);
+ luamp_encode_uint(cfg, &stream, SQL_OPTION_RETURN_TUPLE);
+ luamp_encode_bool(cfg, &stream, opt_return_tuple);
netbox_encode_request(&stream, svp);
return 0;
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 9c660a9dd..0c6d30c24 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -73,6 +73,8 @@ local method_codec = {
end
}
+local function setmap(tab) return setmetatable(tab, { __serialize = 'map' }) end
+
local function next_id(id) return band(id + 1, 0x7FFFFFFF) end
-- function create_transport(host, port, user, password, callback)
@@ -853,35 +855,36 @@ end
function remote_methods:execute(query, parameters, sql_opts, netbox_opts)
check_remote_arg(self, "execute")
- if sql_opts ~= nil then
- box.error(box.error.UNSUPPORTED, "execute", "options")
- end
local timeout = self:request_timeout(netbox_opts)
local buffer = netbox_opts and netbox_opts.buffer
parameters = parameters or {}
- sql_opts = sql_opts or {}
- local err, res, metadata, info = self._transport.perform_request(timeout,
- buffer, 'execute', self.schema_version,
- query, parameters, sql_opts)
+ sql_opts = sql_opts or setmap({})
+ local err, data, metadata, info =
+ self._transport.perform_request(timeout, buffer, 'execute',
+ self.schema_version, query, parameters,
+ sql_opts)
if err then
- box.error({code = err, reason = res})
+ box.error({code = err, reason = data})
end
if buffer ~= nil then
- return res -- body length. Body is written to the buffer.
+ return data -- body length. Body is written to the buffer.
end
- assert((info == nil and metadata ~= nil and res ~= nil) or
- (info ~= nil and metadata == nil and res == nil))
if info ~= nil then
assert(info[IPROTO_SQL_ROW_COUNT_KEY] ~= nil)
- return {rowcount = info[IPROTO_SQL_ROW_COUNT_KEY]}
+ local ret = {rowcount = info[IPROTO_SQL_ROW_COUNT_KEY]}
+ if data ~= nil and #data > 0 then
+ assert(#data == 1)
+ ret.last_tuple = data[1]
+ end
+ return ret
end
-- Set readable names for the metadata fields.
for i, field_meta in pairs(metadata) do
field_meta["name"] = field_meta[IPROTO_FIELD_NAME_KEY]
field_meta[IPROTO_FIELD_NAME_KEY] = nil
end
- setmetatable(res, sequence_mt)
- return {metadata = metadata, rows = res}
+ setmetatable(data, sequence_mt)
+ return {metadata = metadata, rows = data}
end
function remote_methods:wait_state(state, timeout)
diff --git a/test/box/misc.result b/test/box/misc.result
index aeee42930..fdd43185f 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -314,6 +314,7 @@ t;
- 'box.error.NO_SUCH_TRIGGER : 34'
- 'box.error.SEQUENCE_EXISTS : 146'
- 'box.error.CHECKPOINT_IN_PROGRESS : 120'
+ - 'box.error.WRONG_SQL_OPTION : 55'
- 'box.error.FIELD_TYPE : 23'
- 'box.error.SQL_BIND_PARAMETER_MAX : 156'
- 'box.error.WRONG_SPACE_FORMAT : 141'
@@ -338,7 +339,7 @@ t;
- 'box.error.CANT_CREATE_COLLATION : 150'
- 'box.error.USER_EXISTS : 46'
- 'box.error.WAL_IO : 40'
- - 'box.error.PROC_RET : 21'
+ - 'box.error.RTREE_RECT : 101'
- 'box.error.PRIV_GRANTED : 89'
- 'box.error.CREATE_SPACE : 9'
- 'box.error.GRANT : 88'
@@ -359,8 +360,8 @@ t;
- 'box.error.DROP_FUNCTION : 71'
- 'box.error.CFG : 59'
- 'box.error.NO_SUCH_FIELD : 37'
- - 'box.error.CONNECTION_TO_SELF : 117'
- - 'box.error.FUNCTION_MAX : 54'
+ - 'box.error.MORE_THAN_ONE_TUPLE : 41'
+ - 'box.error.PROC_LUA : 32'
- 'box.error.ILLEGAL_PARAMS : 1'
- 'box.error.PARTIAL_KEY : 136'
- 'box.error.SAVEPOINT_NO_TRANSACTION : 114'
@@ -393,13 +394,13 @@ t;
- 'box.error.injection : table: <address>
- 'box.error.FUNCTION_TX_ACTIVE : 30'
- 'box.error.SQL_BIND_NOT_FOUND : 159'
- - 'box.error.RELOAD_CFG : 58'
+ - 'box.error.FUNCTION_MAX : 54'
- 'box.error.NO_SUCH_ENGINE : 57'
- 'box.error.COMMIT_IN_SUB_STMT : 122'
- 'box.error.SQL_EXECUTE : 157'
- 'box.error.NULLABLE_MISMATCH : 153'
- 'box.error.LAST_DROP : 15'
- - 'box.error.NO_SUCH_ROLE : 82'
+ - 'box.error.INJECTION : 8'
- 'box.error.DECOMPRESSION : 124'
- 'box.error.CREATE_SEQUENCE : 142'
- 'box.error.CREATE_USER : 43'
@@ -408,7 +409,7 @@ t;
- 'box.error.SEQUENCE_OVERFLOW : 147'
- 'box.error.SYSTEM : 115'
- 'box.error.KEY_PART_IS_TOO_LONG : 118'
- - 'box.error.TUPLE_FORMAT_LIMIT : 16'
+ - 'box.error.RELOAD_CFG : 58'
- 'box.error.BEFORE_REPLACE_RET : 53'
- 'box.error.NO_SUCH_SAVEPOINT : 61'
- 'box.error.TRUNCATE_SYSTEM_SPACE : 137'
@@ -418,7 +419,7 @@ t;
- 'box.error.INDEX_FIELD_COUNT_LIMIT : 127'
- 'box.error.READ_VIEW_ABORTED : 130'
- 'box.error.USER_MAX : 56'
- - 'box.error.PROTOCOL : 104'
+ - 'box.error.CONNECTION_TO_SELF : 117'
- 'box.error.TUPLE_NOT_ARRAY : 22'
- 'box.error.KEY_PART_COUNT : 31'
- 'box.error.ALTER_SPACE : 12'
@@ -426,22 +427,22 @@ t;
- 'box.error.EXACT_FIELD_COUNT : 38'
- 'box.error.DROP_SEQUENCE : 144'
- 'box.error.INVALID_MSGPACK : 20'
- - 'box.error.MORE_THAN_ONE_TUPLE : 41'
- - 'box.error.RTREE_RECT : 101'
- - 'box.error.SUB_STMT_MAX : 121'
+ - 'box.error.UPSERT_UNIQUE_SECONDARY_KEY : 105'
- 'box.error.UNKNOWN_REQUEST_TYPE : 48'
+ - 'box.error.SUB_STMT_MAX : 121'
+ - 'box.error.PROC_RET : 21'
- 'box.error.SPACE_EXISTS : 10'
- - 'box.error.PROC_LUA : 32'
- 'box.error.ROLE_NOT_GRANTED : 92'
+ - 'box.error.NO_SUCH_ROLE : 82'
- 'box.error.NO_SUCH_SPACE : 36'
- 'box.error.WRONG_INDEX_PARTS : 107'
- - 'box.error.DROP_SPACE : 11'
- 'box.error.MIN_FIELD_COUNT : 39'
- 'box.error.REPLICASET_UUID_MISMATCH : 63'
- 'box.error.UPDATE_FIELD : 29'
+ - 'box.error.INDEX_EXISTS : 85'
- 'box.error.COMPRESSION : 119'
- 'box.error.INVALID_ORDER : 68'
- - 'box.error.INDEX_EXISTS : 85'
+ - 'box.error.DROP_SPACE : 11'
- 'box.error.SPLICE : 25'
- 'box.error.UNKNOWN : 0'
- 'box.error.DROP_PRIMARY_KEY : 17'
@@ -449,7 +450,7 @@ t;
- 'box.error.NO_SUCH_SEQUENCE : 145'
- 'box.error.SQL : 158'
- 'box.error.INVALID_UUID : 64'
- - 'box.error.INJECTION : 8'
+ - 'box.error.TUPLE_FORMAT_LIMIT : 16'
- 'box.error.TIMEOUT : 78'
- 'box.error.IDENTIFIER : 70'
- 'box.error.ITERATOR_TYPE : 72'
@@ -462,10 +463,10 @@ t;
- 'box.error.UPDATE_INTEGER_OVERFLOW : 95'
- 'box.error.NO_CONNECTION : 77'
- 'box.error.INVALID_XLOG_ORDER : 76'
- - 'box.error.UPSERT_UNIQUE_SECONDARY_KEY : 105'
- - 'box.error.ROLLBACK_IN_SUB_STMT : 123'
- 'box.error.WRONG_SCHEMA_VERSION : 109'
+ - 'box.error.ROLLBACK_IN_SUB_STMT : 123'
- 'box.error.UNSUPPORTED_INDEX_FEATURE : 112'
+ - 'box.error.PROTOCOL : 104'
- 'box.error.INDEX_PART_TYPE_MISMATCH : 24'
- 'box.error.INVALID_XLOG_TYPE : 125'
...
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index f7749cb05..e17931496 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -86,7 +86,8 @@ cn:execute(100)
...
cn:execute('select 1', nil, {dry_run = true})
---
-- error: execute does not support options
+- error: 'builtin/box/net_box.lua:250: Usage: netbox.encode_execute(ibuf, sync, schema_version,
+ query, parameters, options)'
...
-- Empty request.
cn:execute('')
@@ -471,6 +472,70 @@ res.metadata
---
- [{'name': ID}, {'name': 'A'}, {'name': 'B'}]
...
+--
+-- gh-2618: add option 'return_tuple'.
+--
+space:truncate()
+---
+...
+cn:reload_schema()
+---
+...
+cn:execute("insert into test values (1, 2, '3')", nil, {return_tuple = true})
+---
+- last_tuple: [1, 2, '3']
+ rowcount: 1
+...
+cn:execute("insert into test values (2, 3, '4'), (3, 4, '5')", nil, {return_tuple = true})
+---
+- last_tuple: [3, 4, '5']
+ rowcount: 2
+...
+-- No last tuple in a case of error.
+cn:execute("insert into test values (1, 2, '3')", nil, {return_tuple = true})
+---
+- error: 'Failed to execute SQL statement: Duplicate key exists in unique index ''sqlite_autoindex_TEST_1''
+ in space ''TEST'''
+...
+-- Update.
+cn:execute('update test set a = 20 where id = 1', nil, {return_tuple = true})
+---
+- last_tuple: [1, 20, '3']
+ rowcount: 1
+...
+-- A last tuple is requested, but not found.
+cn:execute('update test set a = 200 where id = 100', nil, {return_tuple = true})
+---
+- rowcount: 0
+...
+-- Ignore requested tuple on delete, select.
+cn:execute('delete from test where id = 1', nil, {return_tuple = true})
+---
+- rowcount: 1
+...
+cn:execute('select * from test', nil, {return_tuple = true})
+---
+- metadata: [{'name': ID}, {'name': 'A'}, {'name': 'B'}]
+ rows:
+ - [2, 3, '4']
+ - [3, 4, '5']
+...
+-- Invalid options.
+cn:execute('select * from test', nil, {return_tuple = 1})
+---
+- error: 'builtin/box/net_box.lua:250: Usage: netbox.encode_execute(ibuf, sync, schema_version,
+ query, parameters, options)'
+...
+cn:execute('select * from test', nil, {[1] = {return_tuple = true}})
+---
+- error: 'builtin/box/net_box.lua:250: Usage: netbox.encode_execute(ibuf, sync, schema_version,
+ query, parameters, options)'
+...
+cn:execute('select * from test', nil, {bad_option = true})
+---
+- error: 'builtin/box/net_box.lua:250: Usage: netbox.encode_execute(ibuf, sync, schema_version,
+ query, parameters, options)'
+...
cn:close()
---
...
diff --git a/test/sql/iproto.test.lua b/test/sql/iproto.test.lua
index 64c0a56fe..c5bff8e76 100644
--- a/test/sql/iproto.test.lua
+++ b/test/sql/iproto.test.lua
@@ -179,6 +179,28 @@ _ = space:replace{1, 1, string.rep('a', 4 * 1024 * 1024)}
res = cn:execute('select * from test')
res.metadata
+--
+-- gh-2618: add option 'return_tuple'.
+--
+space:truncate()
+cn:reload_schema()
+cn:execute("insert into test values (1, 2, '3')", nil, {return_tuple = true})
+cn:execute("insert into test values (2, 3, '4'), (3, 4, '5')", nil, {return_tuple = true})
+-- No last tuple in a case of error.
+cn:execute("insert into test values (1, 2, '3')", nil, {return_tuple = true})
+-- Update.
+cn:execute('update test set a = 20 where id = 1', nil, {return_tuple = true})
+-- A last tuple is requested, but not found.
+cn:execute('update test set a = 200 where id = 100', nil, {return_tuple = true})
+-- Ignore requested tuple on delete, select.
+cn:execute('delete from test where id = 1', nil, {return_tuple = true})
+cn:execute('select * from test', nil, {return_tuple = true})
+
+-- Invalid options.
+cn:execute('select * from test', nil, {return_tuple = 1})
+cn:execute('select * from test', nil, {[1] = {return_tuple = true}})
+cn:execute('select * from test', nil, {bad_option = true})
+
cn:close()
box.schema.user.revoke('guest', 'read,write,execute', 'universe')
box.sql.execute('drop table test')
--
2.14.3 (Apple Git-98)
More information about the Tarantool-patches
mailing list