[Tarantool-patches] [PATCH v3 19/20] netbox: introduce prepared statements
Nikita Pettik
korablev at tarantool.org
Fri Dec 20 15:47:24 MSK 2019
This patch introduces support of prepared statements in IProto
protocol. To achieve this new IProto command is added - IPROTO_PREPARE
(key is 0x13). It is sent with one of two mandatory keys:
IPROTO_SQL_TEXT (0x40 and assumes string value) or IPROTO_STMT_ID (0x43
and assumes integer value). Depending on body it means to prepare or
unprepare SQL statement: IPROTO_SQL_TEXT implies prepare request,
meanwhile IPROTO_STMT_ID - unprepare. Also to reply on PREPARE request a
few response keys are added: IPROTO_BIND_METADATA (0x33 and contains
parameters metadata of type map) and IPROTO_BIND_COUNT (0x34 and
corresponds to the count of parameters to be bound).
Part of #2592
---
src/box/execute.c | 83 ++++++++++++++++++++++++++++++
src/box/iproto.cc | 68 +++++++++++++++++++++----
src/box/iproto_constants.c | 7 ++-
src/box/iproto_constants.h | 5 ++
src/box/lua/net_box.c | 98 +++++++++++++++++++++++++++++++++--
src/box/lua/net_box.lua | 27 ++++++++++
src/box/xrow.c | 23 +++++++--
src/box/xrow.h | 4 +-
test/box/misc.result | 1 +
test/sql/engine.cfg | 1 +
test/sql/iproto.result | 2 +-
test/sql/prepared.result | 124 ++++++++++++++++++++++++++-------------------
test/sql/prepared.test.lua | 76 +++++++++++++++++++--------
13 files changed, 420 insertions(+), 99 deletions(-)
diff --git a/src/box/execute.c b/src/box/execute.c
index 09224c23a..7174d0d41 100644
--- a/src/box/execute.c
+++ b/src/box/execute.c
@@ -328,6 +328,68 @@ sql_get_metadata(struct sql_stmt *stmt, struct obuf *out, int column_count)
return 0;
}
+static inline int
+sql_get_params_metadata(struct sql_stmt *stmt, struct obuf *out)
+{
+ int bind_count = sql_bind_parameter_count(stmt);
+ int size = mp_sizeof_uint(IPROTO_BIND_METADATA) +
+ mp_sizeof_array(bind_count);
+ char *pos = (char *) obuf_alloc(out, size);
+ if (pos == NULL) {
+ diag_set(OutOfMemory, size, "obuf_alloc", "pos");
+ return -1;
+ }
+ pos = mp_encode_uint(pos, IPROTO_BIND_METADATA);
+ pos = mp_encode_array(pos, bind_count);
+ for (int i = 0; i < bind_count; ++i) {
+ size_t size = mp_sizeof_map(2) +
+ mp_sizeof_uint(IPROTO_FIELD_NAME) +
+ mp_sizeof_uint(IPROTO_FIELD_TYPE);
+ const char *name = sql_bind_parameter_name(stmt, i);
+ if (name == NULL)
+ name = "?";
+ const char *type = "ANY";
+ size += mp_sizeof_str(strlen(name));
+ size += mp_sizeof_str(strlen(type));
+ char *pos = (char *) obuf_alloc(out, size);
+ if (pos == NULL) {
+ diag_set(OutOfMemory, size, "obuf_alloc", "pos");
+ return -1;
+ }
+ pos = mp_encode_map(pos, 2);
+ pos = mp_encode_uint(pos, IPROTO_FIELD_NAME);
+ pos = mp_encode_str(pos, name, strlen(name));
+ pos = mp_encode_uint(pos, IPROTO_FIELD_TYPE);
+ pos = mp_encode_str(pos, type, strlen(type));
+ }
+ return 0;
+}
+
+static int
+sql_get_prepare_common_keys(struct sql_stmt *stmt, struct obuf *out, int keys)
+{
+ const char *sql_str = sql_stmt_query_str(stmt);
+ uint32_t stmt_id = sql_stmt_calculate_id(sql_str, strlen(sql_str));
+ int size = mp_sizeof_map(keys) +
+ mp_sizeof_uint(IPROTO_STMT_ID) +
+ mp_sizeof_uint(stmt_id) +
+ mp_sizeof_uint(IPROTO_BIND_COUNT) +
+ mp_sizeof_uint(sql_bind_parameter_count(stmt));
+ char *pos = (char *) obuf_alloc(out, size);
+ if (pos == NULL) {
+ diag_set(OutOfMemory, size, "obuf_alloc", "pos");
+ return -1;
+ }
+ pos = mp_encode_map(pos, keys);
+ pos = mp_encode_uint(pos, IPROTO_STMT_ID);
+ pos = mp_encode_uint(pos, stmt_id);
+ pos = mp_encode_uint(pos, IPROTO_BIND_COUNT);
+ pos = mp_encode_uint(pos, sql_bind_parameter_count(stmt));
+ if (sql_get_params_metadata(stmt, out) != 0)
+ return -1;
+ return 0;
+}
+
static int
port_sql_dump_msgpack(struct port *port, struct obuf *out)
{
@@ -409,6 +471,27 @@ port_sql_dump_msgpack(struct port *port, struct obuf *out)
}
break;
}
+ case DQL_PREPARE: {
+ /* Format is following:
+ * query_id,
+ * param_count,
+ * params {name, type},
+ * metadata {name, type}
+ */
+ int keys = 4;
+ if (sql_get_prepare_common_keys(stmt, out, keys) != 0)
+ return -1;
+ return sql_get_metadata(stmt, out, sql_column_count(stmt));
+ }
+ case DML_PREPARE: {
+ /* Format is following:
+ * query_id,
+ * param_count,
+ * params {name, type},
+ */
+ int keys = 3;
+ return sql_get_prepare_common_keys(stmt, out, keys);
+ }
default: {
unreachable();
}
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index c39b8e7bf..fac94658a 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -178,7 +178,7 @@ struct iproto_msg
struct call_request call;
/** Authentication request. */
struct auth_request auth;
- /* SQL request, if this is the EXECUTE request. */
+ /* SQL request, if this is the EXECUTE/PREPARE request. */
struct sql_request sql;
/** In case of iproto parse error, saved diagnostics. */
struct diag diag;
@@ -1209,6 +1209,7 @@ static const struct cmsg_hop *dml_route[IPROTO_TYPE_STAT_MAX] = {
call_route, /* IPROTO_CALL */
sql_route, /* IPROTO_EXECUTE */
NULL, /* IPROTO_NOP */
+ sql_route, /* IPROTO_PREPARE */
};
static const struct cmsg_hop join_route[] = {
@@ -1264,6 +1265,7 @@ iproto_msg_decode(struct iproto_msg *msg, const char **pos, const char *reqend,
cmsg_init(&msg->base, call_route);
break;
case IPROTO_EXECUTE:
+ case IPROTO_PREPARE:
if (xrow_decode_sql(&msg->header, &msg->sql) != 0)
goto error;
cmsg_init(&msg->base, sql_route);
@@ -1710,23 +1712,64 @@ tx_process_sql(struct cmsg *m)
int bind_count = 0;
const char *sql;
uint32_t len;
+ bool is_unprepare = false;
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);
+ assert(msg->header.type == IPROTO_EXECUTE ||
+ msg->header.type == IPROTO_PREPARE);
tx_inject_delay();
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, &port,
- &fiber()->gc) != 0)
- goto error;
+ /*
+ * There are four options:
+ * 1. Prepare SQL query (IPROTO_PREPARE + SQL string);
+ * 2. Unprepare SQL query (IPROTO_PREPARE + stmt id);
+ * 3. Execute SQL query (IPROTO_EXECUTE + SQL string);
+ * 4. Execute prepared query (IPROTO_EXECUTE + stmt id).
+ */
+ if (msg->header.type == IPROTO_EXECUTE) {
+ if (msg->sql.sql_text != NULL) {
+ assert(msg->sql.stmt_id == NULL);
+ sql = msg->sql.sql_text;
+ sql = mp_decode_str(&sql, &len);
+ if (sql_prepare_and_execute(sql, len, bind, bind_count,
+ &port, &fiber()->gc) != 0)
+ goto error;
+ } else {
+ assert(msg->sql.sql_text == NULL);
+ assert(msg->sql.stmt_id != NULL);
+ sql = msg->sql.stmt_id;
+ uint32_t stmt_id = mp_decode_uint(&sql);
+ if (sql_execute_prepared(stmt_id, bind, bind_count,
+ &port, &fiber()->gc) != 0)
+ goto error;
+ }
+ } else {
+ /* IPROTO_PREPARE */
+ if (msg->sql.sql_text != NULL) {
+ assert(msg->sql.stmt_id == NULL);
+ sql = msg->sql.sql_text;
+ sql = mp_decode_str(&sql, &len);
+ if (sql_prepare(sql, len, &port) != 0)
+ goto error;
+ } else {
+ /* UNPREPARE */
+ assert(msg->sql.sql_text == NULL);
+ assert(msg->sql.stmt_id != NULL);
+ sql = msg->sql.stmt_id;
+ uint32_t stmt_id = mp_decode_uint(&sql);
+ if (sql_unprepare(stmt_id) != 0)
+ goto error;
+ is_unprepare = true;
+ }
+ }
+
/*
* Take an obuf only after execute(). Else the buffer can
* become out of date during yield.
@@ -1738,12 +1781,15 @@ tx_process_sql(struct cmsg *m)
port_destroy(&port);
goto error;
}
- if (port_dump_msgpack(&port, out) != 0) {
+ /* Nothing to dump in case of UNPREPARE request. */
+ if (! is_unprepare) {
+ if (port_dump_msgpack(&port, out) != 0) {
+ port_destroy(&port);
+ obuf_rollback_to_svp(out, &header_svp);
+ goto error;
+ }
port_destroy(&port);
- obuf_rollback_to_svp(out, &header_svp);
- goto error;
}
- port_destroy(&port);
iproto_reply_sql(out, &header_svp, msg->header.sync, schema_version);
iproto_wpos_create(&msg->wpos, out);
return;
diff --git a/src/box/iproto_constants.c b/src/box/iproto_constants.c
index 09ded1ecb..029d9888c 100644
--- a/src/box/iproto_constants.c
+++ b/src/box/iproto_constants.c
@@ -107,6 +107,7 @@ const char *iproto_type_strs[] =
"CALL",
"EXECUTE",
NULL, /* NOP */
+ "PREPARE",
};
#define bit(c) (1ULL<<IPROTO_##c)
@@ -124,6 +125,7 @@ const uint64_t iproto_body_key_map[IPROTO_TYPE_STAT_MAX] = {
0, /* CALL */
0, /* EXECUTE */
0, /* NOP */
+ 0, /* PREPARE */
};
#undef bit
@@ -179,8 +181,8 @@ const char *iproto_key_strs[IPROTO_KEY_MAX] = {
"data", /* 0x30 */
"error", /* 0x31 */
"metadata", /* 0x32 */
- NULL, /* 0x33 */
- NULL, /* 0x34 */
+ "bind meta", /* 0x33 */
+ "bind count", /* 0x34 */
NULL, /* 0x35 */
NULL, /* 0x36 */
NULL, /* 0x37 */
@@ -195,6 +197,7 @@ const char *iproto_key_strs[IPROTO_KEY_MAX] = {
"SQL text", /* 0x40 */
"SQL bind", /* 0x41 */
"SQL info", /* 0x42 */
+ "stmt id", /* 0x43 */
};
const char *vy_page_info_key_strs[VY_PAGE_INFO_KEY_MAX] = {
diff --git a/src/box/iproto_constants.h b/src/box/iproto_constants.h
index 5e8a7d483..34d0f49c6 100644
--- a/src/box/iproto_constants.h
+++ b/src/box/iproto_constants.h
@@ -110,6 +110,8 @@ enum iproto_key {
* ]
*/
IPROTO_METADATA = 0x32,
+ IPROTO_BIND_METADATA = 0x33,
+ IPROTO_BIND_COUNT = 0x34,
/* Leave a gap between response keys and SQL keys. */
IPROTO_SQL_TEXT = 0x40,
@@ -120,6 +122,7 @@ enum iproto_key {
* }
*/
IPROTO_SQL_INFO = 0x42,
+ IPROTO_STMT_ID = 0x43,
IPROTO_KEY_MAX
};
@@ -203,6 +206,8 @@ enum iproto_type {
IPROTO_EXECUTE = 11,
/** No operation. Treated as DML, used to bump LSN. */
IPROTO_NOP = 12,
+ /** Prepare SQL statement. */
+ IPROTO_PREPARE = 13,
/** The maximum typecode used for box.stat() */
IPROTO_TYPE_STAT_MAX,
diff --git a/src/box/lua/net_box.c b/src/box/lua/net_box.c
index 001af95dc..aa8a15e30 100644
--- a/src/box/lua/net_box.c
+++ b/src/box/lua/net_box.c
@@ -570,10 +570,16 @@ netbox_encode_execute(lua_State *L)
mpstream_encode_map(&stream, 3);
- size_t len;
- const char *query = lua_tolstring(L, 3, &len);
- mpstream_encode_uint(&stream, IPROTO_SQL_TEXT);
- mpstream_encode_strn(&stream, query, len);
+ if (lua_type(L, 3) == LUA_TNUMBER) {
+ uint32_t query_id = lua_tointeger(L, 3);
+ mpstream_encode_uint(&stream, IPROTO_STMT_ID);
+ mpstream_encode_uint(&stream, query_id);
+ } else {
+ size_t len;
+ const char *query = lua_tolstring(L, 3, &len);
+ mpstream_encode_uint(&stream, IPROTO_SQL_TEXT);
+ mpstream_encode_strn(&stream, query, len);
+ }
mpstream_encode_uint(&stream, IPROTO_SQL_BIND);
luamp_encode_tuple(L, cfg, &stream, 4);
@@ -585,6 +591,32 @@ netbox_encode_execute(lua_State *L)
return 0;
}
+static int
+netbox_encode_prepare(lua_State *L)
+{
+ if (lua_gettop(L) < 3)
+ return luaL_error(L, "Usage: netbox.encode_prepare(ibuf, "\
+ "sync, query)");
+ struct mpstream stream;
+ size_t svp = netbox_prepare_request(L, &stream, IPROTO_PREPARE);
+
+ mpstream_encode_map(&stream, 1);
+
+ if (lua_type(L, 3) == LUA_TNUMBER) {
+ uint32_t query_id = lua_tointeger(L, 3);
+ mpstream_encode_uint(&stream, IPROTO_STMT_ID);
+ mpstream_encode_uint(&stream, query_id);
+ } else {
+ size_t len;
+ const char *query = lua_tolstring(L, 3, &len);
+ mpstream_encode_uint(&stream, IPROTO_SQL_TEXT);
+ mpstream_encode_strn(&stream, query, len);
+ };
+
+ netbox_encode_request(&stream, svp);
+ return 0;
+}
+
/**
* Decode IPROTO_DATA into tuples array.
* @param L Lua stack to push result on.
@@ -752,6 +784,62 @@ netbox_decode_execute(struct lua_State *L)
return 2;
}
+static int
+netbox_decode_prepare(struct lua_State *L)
+{
+ uint32_t ctypeid;
+ const char *data = *(const char **)luaL_checkcdata(L, 1, &ctypeid);
+ assert(mp_typeof(*data) == MP_MAP);
+ uint32_t map_size = mp_decode_map(&data);
+ int stmt_id_idx = 0, meta_idx = 0, bind_meta_idx = 0,
+ bind_count_idx = 0;
+ uint32_t stmt_id = 0;
+ for (uint32_t i = 0; i < map_size; ++i) {
+ uint32_t key = mp_decode_uint(&data);
+ switch(key) {
+ case IPROTO_STMT_ID: {
+ stmt_id = mp_decode_uint(&data);
+ luaL_pushuint64(L, stmt_id);
+ stmt_id_idx = i - map_size;
+ break;
+ }
+ case IPROTO_METADATA: {
+ netbox_decode_metadata(L, &data);
+ meta_idx = i - map_size;
+ break;
+ }
+ case IPROTO_BIND_METADATA: {
+ netbox_decode_metadata(L, &data);
+ bind_meta_idx = i - map_size;
+ break;
+ }
+ default: {
+ assert(key == IPROTO_BIND_COUNT);
+ uint32_t bind_count = mp_decode_uint(&data);
+ luaL_pushuint64(L, bind_count);
+ bind_count_idx = i - map_size;
+ break;
+ }}
+ }
+ /* These fields must be present in response. */
+ assert(stmt_id_idx * bind_meta_idx * bind_count_idx != 0);
+ /* General meta is presented only in DQL responses. */
+ lua_createtable(L, 0, meta_idx != 0 ? 4 : 3);
+ lua_pushvalue(L, stmt_id_idx - 1);
+ lua_setfield(L, -2, "stmt_id");
+ lua_pushvalue(L, bind_count_idx - 1);
+ lua_setfield(L, -2, "param_count");
+ lua_pushvalue(L, bind_meta_idx - 1);
+ lua_setfield(L, -2, "params");
+ if (meta_idx != 0) {
+ lua_pushvalue(L, meta_idx - 1);
+ lua_setfield(L, -2, "metadata");
+ }
+
+ *(const char **)luaL_pushcdata(L, ctypeid) = data;
+ return 2;
+}
+
int
luaopen_net_box(struct lua_State *L)
{
@@ -767,11 +855,13 @@ luaopen_net_box(struct lua_State *L)
{ "encode_update", netbox_encode_update },
{ "encode_upsert", netbox_encode_upsert },
{ "encode_execute", netbox_encode_execute},
+ { "encode_prepare", netbox_encode_prepare},
{ "encode_auth", netbox_encode_auth },
{ "decode_greeting",netbox_decode_greeting },
{ "communicate", netbox_communicate },
{ "decode_select", netbox_decode_select },
{ "decode_execute", netbox_decode_execute },
+ { "decode_prepare", netbox_decode_prepare },
{ NULL, NULL}
};
/* luaL_register_module polutes _G */
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index c2e1bb9c4..b4811edfa 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -104,6 +104,8 @@ local method_encoder = {
upsert = internal.encode_upsert,
select = internal.encode_select,
execute = internal.encode_execute,
+ prepare = internal.encode_prepare,
+ unprepare = internal.encode_prepare,
get = internal.encode_select,
min = internal.encode_select,
max = internal.encode_select,
@@ -128,6 +130,8 @@ local method_decoder = {
upsert = decode_nil,
select = internal.decode_select,
execute = internal.decode_execute,
+ prepare = internal.decode_prepare,
+ unprepare = decode_nil,
get = decode_get,
min = decode_get,
max = decode_get,
@@ -1197,6 +1201,29 @@ function remote_methods:execute(query, parameters, sql_opts, netbox_opts)
sql_opts or {})
end
+function remote_methods:prepare(query, parameters, sql_opts, netbox_opts)
+ check_remote_arg(self, "prepare")
+ if type(query) ~= "string" then
+ box.error(box.error.SQL_PREPARE, "expected string as SQL statement")
+ end
+ if sql_opts ~= nil then
+ box.error(box.error.UNSUPPORTED, "prepare", "options")
+ end
+ return self:_request('prepare', netbox_opts, nil, query)
+end
+
+function remote_methods:unprepare(query, parameters, sql_opts, netbox_opts)
+ check_remote_arg(self, "unprepare")
+ if type(query) ~= "number" then
+ box.error("query id is expected to be numeric")
+ end
+ if sql_opts ~= nil then
+ box.error(box.error.UNSUPPORTED, "unprepare", "options")
+ end
+ return self:_request('unprepare', netbox_opts, nil, query, parameters or {},
+ sql_opts or {})
+end
+
function remote_methods:wait_state(state, timeout)
check_remote_arg(self, 'wait_state')
if timeout == nil then
diff --git a/src/box/xrow.c b/src/box/xrow.c
index 18bf08971..88f308be5 100644
--- a/src/box/xrow.c
+++ b/src/box/xrow.c
@@ -576,9 +576,11 @@ error:
uint32_t map_size = mp_decode_map(&data);
request->sql_text = NULL;
request->bind = NULL;
+ request->stmt_id = NULL;
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_STMT_ID) {
mp_check(&data, end); /* skip the key */
mp_check(&data, end); /* skip the value */
continue;
@@ -588,12 +590,23 @@ error:
goto error;
if (key == IPROTO_SQL_BIND)
request->bind = value;
- else
+ else if (key == IPROTO_SQL_TEXT)
request->sql_text = value;
+ else
+ request->stmt_id = value;
}
- if (request->sql_text == NULL) {
- xrow_on_decode_err(row->body[0].iov_base, end, ER_MISSING_REQUEST_FIELD,
- iproto_key_name(IPROTO_SQL_TEXT));
+ if (request->sql_text != NULL && request->stmt_id != NULL) {
+ xrow_on_decode_err(row->body[0].iov_base, end, ER_INVALID_MSGPACK,
+ "SQL text and statement id are incompatible "\
+ "options in one request: choose one");
+ return -1;
+ }
+ if (request->sql_text == NULL && request->stmt_id == NULL) {
+ xrow_on_decode_err(row->body[0].iov_base, end,
+ ER_MISSING_REQUEST_FIELD,
+ tt_sprintf("%s or %s",
+ iproto_key_name(IPROTO_SQL_TEXT),
+ iproto_key_name(IPROTO_STMT_ID)));
return -1;
}
if (data != end)
diff --git a/src/box/xrow.h b/src/box/xrow.h
index 60def2d3c..a4d8dc015 100644
--- a/src/box/xrow.h
+++ b/src/box/xrow.h
@@ -526,12 +526,14 @@ int
iproto_reply_error(struct obuf *out, const struct error *e, uint64_t sync,
uint32_t schema_version);
-/** EXECUTE request. */
+/** EXECUTE/PREPARE request. */
struct sql_request {
/** SQL statement text. */
const char *sql_text;
/** MessagePack array of parameters. */
const char *bind;
+ /** ID of prepared statement. In this case @sql_text == NULL. */
+ const char *stmt_id;
};
/**
diff --git a/test/box/misc.result b/test/box/misc.result
index 90923f28e..79fd49442 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -250,6 +250,7 @@ t;
- EVAL
- CALL
- ERROR
+ - PREPARE
- REPLACE
- UPSERT
- AUTH
diff --git a/test/sql/engine.cfg b/test/sql/engine.cfg
index a1b4b0fc5..e38bec24e 100644
--- a/test/sql/engine.cfg
+++ b/test/sql/engine.cfg
@@ -10,6 +10,7 @@
"local": {"remote": "false"}
},
"prepared.test.lua": {
+ "remote": {"remote": "true"},
"local": {"remote": "false"}
},
"*": {
diff --git a/test/sql/iproto.result b/test/sql/iproto.result
index 67acd0ac1..4dfbfce50 100644
--- a/test/sql/iproto.result
+++ b/test/sql/iproto.result
@@ -119,7 +119,7 @@ cn:execute('select id as identifier from test where a = 5;')
-- netbox API errors.
cn:execute(100)
---
-- error: Syntax error near '100'
+- error: Prepared statement with id 100 does not exist
...
cn:execute('select 1', nil, {dry_run = true})
---
diff --git a/test/sql/prepared.result b/test/sql/prepared.result
index bd37cfdd7..2f4983b00 100644
--- a/test/sql/prepared.result
+++ b/test/sql/prepared.result
@@ -12,34 +12,49 @@ fiber = require('fiber')
-- Wrappers to make remote and local execution interface return
-- same result pattern.
--
-test_run:cmd("setopt delimiter ';'")
+is_remote = test_run:get_cfg('remote') == 'true'
| ---
- | - true
| ...
-execute = function(...)
- local res, err = box.execute(...)
- if err ~= nil then
- error(err)
- end
- return res
-end;
+execute = nil
| ---
| ...
-prepare = function(...)
- local res, err = box.prepare(...)
- if err ~= nil then
- error(err)
- end
- return res
-end;
+prepare = nil
+ | ---
+ | ...
+
+test_run:cmd("setopt delimiter ';'")
| ---
+ | - true
| ...
-unprepare = function(...)
- local res, err = box.unprepare(...)
- if err ~= nil then
- error(err)
+if is_remote then
+ box.schema.user.grant('guest','read, write, execute', 'universe')
+ box.schema.user.grant('guest', 'create', 'space')
+ cn = remote.connect(box.cfg.listen)
+ execute = function(...) return cn:execute(...) end
+ prepare = function(...) return cn:prepare(...) end
+ unprepare = function(...) return cn:unprepare(...) end
+else
+ execute = function(...)
+ local res, err = box.execute(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
+ end
+ prepare = function(...)
+ local res, err = box.prepare(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
+ end
+ unprepare = function(...)
+ local res, err = box.unprepare(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
end
- return res
end;
| ---
| ...
@@ -128,31 +143,26 @@ execute(s.stmt_id, {1, 3})
| type: string
| rows: []
| ...
-s:execute({1, 2})
+
+test_run:cmd("setopt delimiter ';'")
| ---
- | - metadata:
- | - name: ID
- | type: integer
- | - name: A
- | type: number
- | - name: B
- | type: string
- | rows:
- | - [1, 2, '3']
+ | - true
| ...
-s:execute({1, 3})
+if not is_remote then
+ res = s:execute({1, 2})
+ assert(res ~= nil)
+ res = s:execute({1, 3})
+ assert(res ~= nil)
+end;
| ---
- | - metadata:
- | - name: ID
- | type: integer
- | - name: A
- | type: number
- | - name: B
- | type: string
- | rows: []
| ...
-s:unprepare()
+test_run:cmd("setopt delimiter ''");
| ---
+ | - true
+ | ...
+unprepare(s.stmt_id)
+ | ---
+ | - null
| ...
-- Test preparation of different types of queries.
@@ -338,6 +348,7 @@ _ = prepare("SELECT a FROM test WHERE b = '3';")
s = prepare("SELECT a FROM test WHERE b = '3';")
| ---
| ...
+
execute(s.stmt_id)
| ---
| - metadata:
@@ -354,21 +365,21 @@ execute(s.stmt_id)
| rows:
| - [2]
| ...
-s:execute()
+test_run:cmd("setopt delimiter ';'")
| ---
- | - metadata:
- | - name: A
- | type: number
- | rows:
- | - [2]
+ | - true
| ...
-s:execute()
+if not is_remote then
+ res = s:execute()
+ assert(res ~= nil)
+ res = s:execute()
+ assert(res ~= nil)
+end;
| ---
- | - metadata:
- | - name: A
- | type: number
- | rows:
- | - [2]
+ | ...
+test_run:cmd("setopt delimiter ''");
+ | ---
+ | - true
| ...
unprepare(s.stmt_id)
| ---
@@ -671,6 +682,13 @@ unprepare(s.stmt_id);
| - null
| ...
+if is_remote then
+ cn:close()
+ box.schema.user.revoke('guest', 'read, write, execute', 'universe')
+ box.schema.user.revoke('guest', 'create', 'space')
+end;
+ | ---
+ | ...
test_run:cmd("setopt delimiter ''");
| ---
| - true
diff --git a/test/sql/prepared.test.lua b/test/sql/prepared.test.lua
index 49d2fb3ae..c464cc21a 100644
--- a/test/sql/prepared.test.lua
+++ b/test/sql/prepared.test.lua
@@ -5,27 +5,40 @@ fiber = require('fiber')
-- Wrappers to make remote and local execution interface return
-- same result pattern.
--
+is_remote = test_run:get_cfg('remote') == 'true'
+execute = nil
+prepare = nil
+
test_run:cmd("setopt delimiter ';'")
-execute = function(...)
- local res, err = box.execute(...)
- if err ~= nil then
- error(err)
+if is_remote then
+ box.schema.user.grant('guest','read, write, execute', 'universe')
+ box.schema.user.grant('guest', 'create', 'space')
+ cn = remote.connect(box.cfg.listen)
+ execute = function(...) return cn:execute(...) end
+ prepare = function(...) return cn:prepare(...) end
+ unprepare = function(...) return cn:unprepare(...) end
+else
+ execute = function(...)
+ local res, err = box.execute(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
end
- return res
-end;
-prepare = function(...)
- local res, err = box.prepare(...)
- if err ~= nil then
- error(err)
+ prepare = function(...)
+ local res, err = box.prepare(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
end
- return res
-end;
-unprepare = function(...)
- local res, err = box.unprepare(...)
- if err ~= nil then
- error(err)
+ unprepare = function(...)
+ local res, err = box.unprepare(...)
+ if err ~= nil then
+ error(err)
+ end
+ return res
end
- return res
end;
test_run:cmd("setopt delimiter ''");
@@ -46,9 +59,16 @@ s.params
s.param_count
execute(s.stmt_id, {1, 2})
execute(s.stmt_id, {1, 3})
-s:execute({1, 2})
-s:execute({1, 3})
-s:unprepare()
+
+test_run:cmd("setopt delimiter ';'")
+if not is_remote then
+ res = s:execute({1, 2})
+ assert(res ~= nil)
+ res = s:execute({1, 3})
+ assert(res ~= nil)
+end;
+test_run:cmd("setopt delimiter ''");
+unprepare(s.stmt_id)
-- Test preparation of different types of queries.
-- Let's start from DDL. It doesn't make much sense since
@@ -111,10 +131,17 @@ space:replace{4, 5, '6'}
space:replace{7, 8.5, '9'}
_ = prepare("SELECT a FROM test WHERE b = '3';")
s = prepare("SELECT a FROM test WHERE b = '3';")
+
execute(s.stmt_id)
execute(s.stmt_id)
-s:execute()
-s:execute()
+test_run:cmd("setopt delimiter ';'")
+if not is_remote then
+ res = s:execute()
+ assert(res ~= nil)
+ res = s:execute()
+ assert(res ~= nil)
+end;
+test_run:cmd("setopt delimiter ''");
unprepare(s.stmt_id)
s = prepare("SELECT count(*), count(a - 3), max(b), abs(id) FROM test WHERE b = '3';")
@@ -233,6 +260,11 @@ f2:join();
unprepare(s.stmt_id);
+if is_remote then
+ cn:close()
+ box.schema.user.revoke('guest', 'read, write, execute', 'universe')
+ box.schema.user.revoke('guest', 'create', 'space')
+end;
test_run:cmd("setopt delimiter ''");
box.cfg{sql_cache_size = 5 * 1024 * 1024}
--
2.15.1
More information about the Tarantool-patches
mailing list