From 032e26591f7ae4839fe9401a63dd94d6f955738b Mon Sep 17 00:00:00 2001 From: Arseniy Volynets <vol0ncar@yandex.ru> Date: Mon, 3 Jul 2023 01:42:40 +0300 Subject: [PATCH] feat: add limit for max executed vdbe opcodes - Add a configurable non-negative session parameter "sql_vdbe_max_steps" -- max number of opcodes that Vdbe is allowed to execute for sql query. - Default value can be specified in box.cfg. If not set via box.cfg, default value is 45000. Value 0 means that no checks for number of executed Vdbe opcodes will be made. - Add the third argument to box.execute function, that allows to specify options for query execution. The only option supported: sql_vdbe_max_steps. Usage example: ``` box.execute([[select * from t]], {}, {{sql_vdbe_max_steps = 1000}}) ``` part of picodata/picodata/sbroad!461 NO_DOC=picodata internal patch NO_CHANGELOG=picodata internal patch --- src/box/box.cc | 35 +++++- src/box/box.h | 1 + src/box/errcode.h | 6 +- src/box/execute.c | 18 ++-- src/box/execute.h | 6 +- src/box/iproto.cc | 6 +- src/box/lua/execute.c | 85 +++++++++++---- src/box/lua/execute.h | 2 +- src/box/lua/load_cfg.lua | 2 + src/box/lua/session.c | 12 +++ src/box/session.c | 3 + src/box/session.h | 2 + src/box/session_settings.c | 1 + src/box/session_settings.h | 1 + src/box/sql/build.c | 32 +++++- src/box/sql/main.c | 4 - src/box/sql/sqlInt.h | 17 +-- src/box/sql/sqlLimit.h | 8 +- src/box/sql/vdbe.c | 21 +++- src/box/sql/vdbeInt.h | 6 +- src/box/sql/vdbeapi.c | 8 ++ src/box/sql/vdbeaux.c | 1 + test/box-tap/cfg.test.lua | 3 +- test/box/admin.result | 2 + test/box/cfg.result | 4 + test/box/error.result | 3 + test/box/session_settings.result | 1 + test/sql-luatest/vdbe_max_steps_test.lua | 129 +++++++++++++++++++++++ test/sql-tap/autoindex1.test.lua | 9 ++ test/sql-tap/count.test.lua | 3 + test/sql-tap/delete3.test.lua | 3 + test/sql-tap/index4.test.lua | 3 + test/sql-tap/limit.test.lua | 6 +- test/sql-tap/select2.test.lua | 4 + test/sql-tap/selectG.test.lua | 3 + test/sql-tap/sort.test.lua | 5 + test/sql-tap/trigger8.test.lua | 3 + test/sql-tap/with1.test.lua | 1 + test/sql/prepared.result | 8 ++ test/sql/prepared.test.lua | 2 + 40 files changed, 410 insertions(+), 59 deletions(-) create mode 100644 test/sql-luatest/vdbe_max_steps_test.lua diff --git a/src/box/box.cc b/src/box/box.cc index 5edc4cb5c9..59ce162b4d 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -96,6 +96,7 @@ #include "mp_util.h" #include "small/static.h" #include "memory.h" +#include "sqlLimit.h" static char status[64] = "unconfigured"; @@ -253,6 +254,9 @@ static char box_feedback_host[BOX_FEEDBACK_HOST_MAX]; /** Whether sending crash info to feedback URL is enabled. */ static bool box_feedback_crash_enabled; +/** Default limit for the number of executed VDBE opcodes. */ +uint64_t default_vdbe_max_steps = 45000; + static int box_run_on_recovery_state(enum box_recovery_state state) { @@ -1638,6 +1642,20 @@ box_check_sql_cache_size(int size) return 0; } +/** + * Check sql_vdbe_max_steps cfg option value + */ +static int +box_check_sql_vdbe_max_steps(int steps) +{ + if (steps < 0) { + diag_set(ClientError, ER_CFG, "sql_vdbe_max_steps", + "must be non-negative"); + return -1; + } + return 0; +} + static int box_check_allocator(void) { @@ -1645,7 +1663,7 @@ box_check_allocator(void) if (strcmp(allocator, "small") && strcmp(allocator, "system")) { diag_set(ClientError, ER_CFG, "memtx_allocator", tt_sprintf("must be small or system, " - "but was set to %s", allocator)); + "but was set to %s", allocator)); return -1; } return 0; @@ -1817,6 +1835,8 @@ box_check_config(void) diag_raise(); if (box_check_sql_cache_size(cfg_geti("sql_cache_size")) != 0) diag_raise(); + if (box_check_sql_vdbe_max_steps(cfg_geti("sql_vdbe_max_steps")) != 0) + diag_raise(); if (box_check_txn_timeout() < 0) diag_raise(); if (box_check_txn_isolation() == txn_isolation_level_MAX) @@ -3001,6 +3021,17 @@ box_set_prepared_stmt_cache_size(void) return 0; } +int +box_set_vdbe_max_steps(void) +{ + int new_limit = cfg_geti("sql_vdbe_max_steps"); + if (box_check_sql_vdbe_max_steps(new_limit) != 0) + return -1; + current_session()->vdbe_max_steps = new_limit; + default_vdbe_max_steps = new_limit; + return 0; +} + /** * Report crash information to the feedback daemon * (ie send it to feedback daemon). @@ -5054,6 +5085,8 @@ box_cfg_xc(void) if (box_set_prepared_stmt_cache_size() != 0) diag_raise(); + if (box_set_vdbe_max_steps() != 0) + diag_raise(); box_set_net_msg_max(); box_set_readahead(); box_set_too_long_threshold(); diff --git a/src/box/box.h b/src/box/box.h index fa90835724..af95eedc0c 100644 --- a/src/box/box.h +++ b/src/box/box.h @@ -353,6 +353,7 @@ void box_set_replication_skip_conflict(void); void box_set_replication_anon(void); void box_set_net_msg_max(void); int box_set_prepared_stmt_cache_size(void); +int box_set_vdbe_max_steps(void); int box_set_feedback(void); int box_set_txn_timeout(void); int box_set_txn_isolation(void); diff --git a/src/box/errcode.h b/src/box/errcode.h index ceafd3c308..bde19049ea 100644 --- a/src/box/errcode.h +++ b/src/box/errcode.h @@ -319,9 +319,9 @@ struct errcode_record { /*264 */_(ER_NIL_UUID, "Nil UUID is reserved and can't be used in replication") \ /*265 */_(ER_WRONG_FUNCTION_OPTIONS, "Wrong function options: %s") \ /*266 */_(ER_MISSING_SYSTEM_SPACES, "Snapshot has no system spaces") \ - /*267 */_(ER_UNUSED1, "") \ - /*268 */_(ER_UNUSED2, "") \ - /*269 */_(ER_UNUSED3, "") \ + /*267 */_(ER_EXCEEDED_VDBE_MAX_STEPS, "Reached a limit on max executed vdbe opcodes. Limit: %u") \ + /*268 */_(ER_ILLEGAL_OPTIONS, "Illegal options: %s") \ + /*269 */_(ER_ILLEGAL_OPTIONS_FORMAT, "Each option in third argument must be a table containing only one key value pair") \ /*270 */_(ER_UNUSED4, "") \ /*271 */_(ER_UNUSED5, "") \ /*272 */_(ER_SCHEMA_UPGRADE_IN_PROGRESS, "Schema upgrade is already in progress") \ diff --git a/src/box/execute.c b/src/box/execute.c index ce4aa87a78..72cfa12136 100644 --- a/src/box/execute.c +++ b/src/box/execute.c @@ -43,7 +43,6 @@ #include "schema.h" #include "port.h" #include "tuple.h" -#include "sql/vdbe.h" #include "box/lua/execute.h" #include "box/sql_stmt_cache.h" #include "session.h" @@ -176,10 +175,12 @@ sql_unprepare(uint32_t stmt_id) * @retval -1 Error. */ static inline int -sql_execute(struct sql_stmt *stmt, struct port *port, struct region *region) +sql_execute(struct sql_stmt *stmt, struct port *port, struct region *region, + uint64_t vdbe_max_steps) { int rc, column_count = sql_column_count(stmt); rmean_collect(rmean_box, IPROTO_EXECUTE, 1); + sql_set_vdbe_max_steps(stmt, vdbe_max_steps); if (column_count > 0) { /* Either ROW or DONE or ERROR. */ while ((rc = sql_step(stmt)) == SQL_ROW) { @@ -200,7 +201,8 @@ sql_execute(struct sql_stmt *stmt, struct port *port, struct region *region) int sql_execute_prepared(uint32_t stmt_id, const struct sql_bind *bind, uint32_t bind_count, struct port *port, - struct region *region) + struct region *region, + uint64_t vdbe_max_steps) { if (!session_check_stmt_id(current_session(), stmt_id)) { @@ -219,7 +221,8 @@ sql_execute_prepared(uint32_t stmt_id, const struct sql_bind *bind, if (sql_stmt_busy(stmt)) { const char *sql_str = sql_stmt_query_str(stmt); return sql_prepare_and_execute(sql_str, strlen(sql_str), bind, - bind_count, port, region); + bind_count, port, region, + vdbe_max_steps); } /* * Clear all set from previous execution cycle values to be bound and @@ -232,7 +235,7 @@ sql_execute_prepared(uint32_t stmt_id, const struct sql_bind *bind, enum sql_serialization_format format = sql_column_count(stmt) > 0 ? DQL_EXECUTE : DML_EXECUTE; port_sql_create(port, stmt, format, false); - if (sql_execute(stmt, port, region) != 0) { + if (sql_execute(stmt, port, region, vdbe_max_steps) != 0) { port_destroy(port); sql_stmt_reset(stmt); return -1; @@ -245,7 +248,8 @@ sql_execute_prepared(uint32_t stmt_id, const struct sql_bind *bind, int sql_prepare_and_execute(const char *sql, int len, const struct sql_bind *bind, uint32_t bind_count, struct port *port, - struct region *region) + struct region *region, + uint64_t vdbe_max_steps) { struct sql_stmt *stmt; if (sql_stmt_compile(sql, len, NULL, &stmt, NULL) != 0) @@ -255,7 +259,7 @@ sql_prepare_and_execute(const char *sql, int len, const struct sql_bind *bind, DQL_EXECUTE : DML_EXECUTE; port_sql_create(port, stmt, format, true); if (sql_bind(stmt, bind, bind_count) == 0 && - sql_execute(stmt, port, region) == 0) + sql_execute(stmt, port, region, vdbe_max_steps) == 0) return 0; port_destroy(port); return -1; diff --git a/src/box/execute.h b/src/box/execute.h index c15e9d67ba..ebf061731c 100644 --- a/src/box/execute.h +++ b/src/box/execute.h @@ -72,7 +72,8 @@ sql_execute_prepared_ext(uint32_t query_id, const struct sql_bind *bind, int sql_execute_prepared(uint32_t query_id, const struct sql_bind *bind, uint32_t bind_count, struct port *port, - struct region *region); + struct region *region, + uint64_t vdbe_max_steps); /** * Prepare and execute an SQL statement. @@ -90,7 +91,8 @@ sql_execute_prepared(uint32_t query_id, const struct sql_bind *bind, int sql_prepare_and_execute(const char *sql, int len, const struct sql_bind *bind, uint32_t bind_count, struct port *port, - struct region *region); + struct region *region, + uint64_t vdbe_max_steps); int sql_stmt_finalize(struct sql_stmt *stmt); diff --git a/src/box/iproto.cc b/src/box/iproto.cc index 4217151eff..54ef40356d 100644 --- a/src/box/iproto.cc +++ b/src/box/iproto.cc @@ -2506,7 +2506,8 @@ tx_process_sql(struct cmsg *m) 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) + &port, &fiber()->gc, + current_session()->vdbe_max_steps) != 0) goto error; } else { assert(msg->sql.sql_text == NULL); @@ -2514,7 +2515,8 @@ tx_process_sql(struct cmsg *m) 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) + &port, &fiber()->gc, + current_session()->vdbe_max_steps) != 0) goto error; } } else { diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c index 768001b303..fa249f8890 100644 --- a/src/box/lua/execute.c +++ b/src/box/lua/execute.c @@ -11,6 +11,7 @@ #include "mpstream/mpstream.h" #include "box/sql/vdbeInt.h" #include "box/sql/port.h" +#include "box/session.h" /** * Serialize a description of the prepared statement. @@ -103,18 +104,20 @@ lbox_execute_prepared(struct lua_State *L) { int top = lua_gettop(L); - if ((top != 1 && top != 2) || ! lua_istable(L, 1)) - return luaL_error(L, "Usage: statement:execute([, params])"); + if ((top != 1 && top != 2 && top != 3) || !lua_istable(L, 1)) + return luaL_error(L, "Usage: statement:execute([, " + "params[, options]])"); lua_getfield(L, 1, "stmt_id"); if (!lua_isnumber(L, -1)) return luaL_error(L, "Query id is expected to be numeric"); lua_remove(L, 1); - if (top == 2) { + if (top >= 2) { /* * Stack state (before remove operation): * 1 Prepared statement object (Lua table) * 2 Bindings (Lua table) - * 3 Statement ID(fetched from PS table) - top of stack + * 3 Options (Lua table) + * 4 Statement ID(fetched from PS table) - top of stack * * We should make it suitable to pass arguments to * lbox_execute(), i.e. after manipulations stack @@ -124,10 +127,14 @@ lbox_execute_prepared(struct lua_State *L) * Since there's no swap operation, we firstly remove * PS object, then copy table of values to be bound to * the top of stack (push), and finally remove original - * bindings from stack. + * bindings from stack. The goes if Options are present */ lua_pushvalue(L, 1); lua_remove(L, 1); + if (top == 3) { + lua_pushvalue(L, 1); + lua_remove(L, 1); + } } return lbox_execute(L); } @@ -278,26 +285,29 @@ sql_execute_prepared_ext(uint32_t stmt_id, const struct sql_bind *bind, uint32_t bind_count, struct port *port) { return sql_execute_prepared(stmt_id, bind, bind_count, port, - &fiber()->gc); + &fiber()->gc, + current_session()->vdbe_max_steps); } /** - * Decode a single bind column from Lua stack. + * Decode a single bind column or option from Lua stack. * * @param L Lua stack. * @param[out] bind Bind to decode to. * @param idx Position of table with bind columns on Lua stack. * @param i Ordinal bind number. + * @param is_option whether the option is being decoded. * * @retval 0 Success. * @retval -1 Memory or client error. */ static inline int -lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) +lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i, bool is_option) { struct luaL_field field; struct region *region = &fiber()->gc; char *buf; + int old_stack_sz = lua_gettop(L); lua_rawgeti(L, idx, i + 1); bind->pos = i + 1; if (lua_istable(L, -1)) { @@ -330,6 +340,9 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) memcpy(buf, bind->name, name_len + 1); bind->name = buf; bind->name_len = name_len; + } else if (is_option) { + diag_set(ClientError, ER_ILLEGAL_OPTIONS_FORMAT); + return -1; } else { bind->name = NULL; bind->name_len = 0; @@ -418,13 +431,13 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i) default: unreachable(); } - lua_pop(L, lua_gettop(L) - idx); + lua_pop(L, lua_gettop(L) - old_stack_sz); return 0; } int lua_sql_bind_list_decode(struct lua_State *L, struct sql_bind **out_bind, - int idx) + int idx, bool is_option) { assert(out_bind != NULL); uint32_t bind_count = lua_objlen(L, idx); @@ -445,7 +458,7 @@ lua_sql_bind_list_decode(struct lua_State *L, struct sql_bind **out_bind, struct sql_bind *bind = xregion_alloc_array(region, typeof(bind[0]), bind_count); for (uint32_t i = 0; i < bind_count; ++i) { - if (lua_sql_bind_decode(L, &bind[i], idx, i) != 0) { + if (lua_sql_bind_decode(L, &bind[i], idx, i, is_option) != 0) { region_truncate(region, used); return -1; } @@ -457,27 +470,57 @@ lua_sql_bind_list_decode(struct lua_State *L, struct sql_bind **out_bind, static int lbox_execute(struct lua_State *L) { - struct sql_bind *bind = NULL; + struct sql_bind *bind = NULL, *options = NULL; int bind_count = 0; + uint64_t vdbe_max_steps = current_session()->vdbe_max_steps; size_t length; struct port port; int top = lua_gettop(L); - if ((top != 1 && top != 2) || ! lua_isstring(L, 1)) - return luaL_error(L, "Usage: box.execute(sqlstring[, params]) " - "or box.execute(stmt_id[, params])"); + if ((top != 1 && top != 2 && top != 3) || !lua_isstring(L, 1)) + return luaL_error(L, "Usage: box.execute(sqlstring" + "[, params[, options]]) " + "or box.execute(stmt_id[, params[, options]])"); if (lua_type(L, 1) != LUA_TSTRING && lua_tointeger(L, 1) < 0) return luaL_error(L, "Statement id can't be negative"); + if (top >= 2 && !lua_istable(L, 2)) + return luaL_error(L, "Second argument must be a table"); + if (top == 3 && !lua_istable(L, 3)) + return luaL_error(L, "Third argument must be a table"); size_t region_svp = region_used(&fiber()->gc); - if (top == 2) { - if (! lua_istable(L, 2)) - return luaL_error(L, "Second argument must be a table"); - bind_count = lua_sql_bind_list_decode(L, &bind, 2); + if (top >= 2) { + bind_count = lua_sql_bind_list_decode(L, &bind, 2, false); if (bind_count < 0) return luaT_push_nil_and_error(L); } + if (top == 3) { + int option_count = lua_sql_bind_list_decode(L, + &options, 3, true); + if (option_count < 0) + goto error; + const char *option_name = "sql_vdbe_max_steps"; + for (int i = 0; i < option_count; i++) { + /* Currently there exists only one option */ + if (strcmp(options[i].name, option_name) == 0) { + if (options[i].type != MP_UINT) { + diag_set(ClientError, + ER_ILLEGAL_OPTIONS, + tt_sprintf("value of the " + "%s option " + "should be a non-negative integer.", + option_name)); + goto error; + } + vdbe_max_steps = options[i].u64; + } else { + diag_set(ClientError, ER_ILLEGAL_OPTIONS, + options[i].name); + goto error; + } + } + } /* * lua_isstring() returns true for numeric values as well, * so test explicit type instead. @@ -485,13 +528,13 @@ lbox_execute(struct lua_State *L) if (lua_type(L, 1) == LUA_TSTRING) { const char *sql = lua_tolstring(L, 1, &length); if (sql_prepare_and_execute(sql, length, bind, bind_count, &port, - &fiber()->gc) != 0) + &fiber()->gc, vdbe_max_steps) != 0) goto error; } else { assert(lua_type(L, 1) == LUA_TNUMBER); lua_Integer query_id = lua_tointeger(L, 1); if (sql_execute_prepared(query_id, bind, bind_count, &port, - &fiber()->gc) != 0) + &fiber()->gc, vdbe_max_steps) != 0) goto error; } port_dump_lua(&port, L, false); diff --git a/src/box/lua/execute.h b/src/box/lua/execute.h index bafd676156..ad8eaab49d 100644 --- a/src/box/lua/execute.h +++ b/src/box/lua/execute.h @@ -63,7 +63,7 @@ port_sql_dump_lua(struct port *port, struct lua_State *L, bool is_flat); */ int lua_sql_bind_list_decode(struct lua_State *L, struct sql_bind **out_bind, - int idx); + int idx, bool is_option); void box_lua_sql_init(struct lua_State *L); diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index 4465065c71..672031cf89 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -198,6 +198,7 @@ local default_cfg = { feedback_metrics_limit = ifdef_feedback(1024 * 1024), net_msg_max = 768, sql_cache_size = 5 * 1024 * 1024, + sql_vdbe_max_steps = 45000, txn_timeout = 365 * 100 * 86400, txn_isolation = "best-effort", @@ -390,6 +391,7 @@ local template_cfg = { feedback_metrics_limit = ifdef_feedback('number'), net_msg_max = 'number', sql_cache_size = 'number', + sql_vdbe_max_steps = 'number', txn_timeout = 'number', metrics = 'table', diff --git a/src/box/lua/session.c b/src/box/lua/session.c index 4db8dcd4a4..9c53999c3d 100644 --- a/src/box/lua/session.c +++ b/src/box/lua/session.c @@ -406,6 +406,9 @@ lbox_session_setting_get_by_id(struct lua_State *L, int sid) if (field_type == FIELD_TYPE_BOOLEAN) { bool value = mp_decode_bool(&mp_pair); lua_pushboolean(L, value); + } else if (field_type == FIELD_TYPE_UNSIGNED) { + uint64_t value = mp_decode_uint(&mp_pair); + luaL_pushuint64(L, value); } else { assert(field_type == FIELD_TYPE_STRING); const char *str = mp_decode_str(&mp_pair, &len); @@ -465,6 +468,15 @@ lbox_session_setting_set(struct lua_State *L) return luaT_error(L); break; } + case LUA_TNUMBER: { + const uint64_t value = lua_tonumber(L, -1); + size_t size = mp_sizeof_uint(value); + char *mp_value = (char *)static_alloc(size); + mp_encode_uint(mp_value, value); + if (setting->set(sid, mp_value) != 0) + return luaT_error(L); + break; + } default: diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, session_setting_strs[sid], diff --git a/src/box/session.c b/src/box/session.c index de4222efe2..60384c0b03 100644 --- a/src/box/session.c +++ b/src/box/session.c @@ -42,6 +42,8 @@ #include "watcher.h" #include "on_shutdown.h" #include "sql.h" +#include "sqlInt.h" +#include "cfg.h" const char *session_type_strs[] = { "background", @@ -258,6 +260,7 @@ session_new(enum session_type type) session_set_type(session, type); session->sql_flags = sql_default_session_flags(); session->sql_default_engine = SQL_STORAGE_ENGINE_MEMTX; + session->vdbe_max_steps = default_vdbe_max_steps; session->sql_stmts = NULL; session->watchers = NULL; rlist_create(&session->in_shutdown_list); diff --git a/src/box/session.h b/src/box/session.h index 0d9ffcde61..cef5f46422 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -114,6 +114,8 @@ struct session { /** SQL Connection flag for current user session */ uint32_t sql_flags; enum session_type type; + /** Max number of Vdbe commands for query. */ + uint64_t vdbe_max_steps; /** Session virtual methods. */ const struct session_vtab *vtab; /** Session metadata. */ diff --git a/src/box/session_settings.c b/src/box/session_settings.c index 9c38cd3f96..969a5c87ab 100644 --- a/src/box/session_settings.c +++ b/src/box/session_settings.c @@ -51,6 +51,7 @@ const char *session_setting_strs[SESSION_SETTING_COUNT] = { "sql_select_debug", "sql_seq_scan", "sql_vdbe_debug", + "sql_vdbe_max_steps", }; struct session_settings_index { diff --git a/src/box/session_settings.h b/src/box/session_settings.h index 1eb6fa522a..9bbc20f704 100644 --- a/src/box/session_settings.h +++ b/src/box/session_settings.h @@ -52,6 +52,7 @@ enum { SESSION_SETTING_SQL_SELECT_DEBUG, SESSION_SETTING_SQL_SEQ_SCAN, SESSION_SETTING_SQL_VDBE_DEBUG, + SESSION_SETTING_SQL_VDBE_MAX_STEPS, SESSION_SETTING_SQL_END, /** * Follow the pattern for groups of settings: diff --git a/src/box/sql/build.c b/src/box/sql/build.c index f0a49f1ef8..86811595bc 100644 --- a/src/box/sql/build.c +++ b/src/box/sql/build.c @@ -3222,6 +3222,8 @@ static struct sql_option_metadata sql_session_opts[] = { /** SESSION_SETTING_SQL_VDBE_DEBUG */ {FIELD_TYPE_BOOLEAN, SQL_SqlTrace | SQL_VdbeListing | SQL_VdbeTrace}, + /** SESSION_SETTING_SQL_VDBE_MAX_STEPS */ + {FIELD_TYPE_UNSIGNED, 0}, }; static void @@ -3236,15 +3238,18 @@ sql_session_setting_get(int id, const char **mp_pair, const char **mp_pair_end) const char *name = session_setting_strs[id]; size_t name_len = strlen(name); size_t engine_len; + uint64_t vdbe_max_steps = session->vdbe_max_steps; const char *engine; size_t size = mp_sizeof_array(2) + mp_sizeof_str(name_len); /* - * Currently, SQL session settings are of a boolean or + * Currently, SQL session settings are of a boolean, unsigned or * string type. */ bool is_bool = opt->field_type == FIELD_TYPE_BOOLEAN; if (is_bool) { size += mp_sizeof_bool(true); + } else if (opt->field_type == FIELD_TYPE_UNSIGNED) { + size += mp_sizeof_uint(vdbe_max_steps); } else { assert(id == SESSION_SETTING_SQL_DEFAULT_ENGINE); engine = sql_storage_engine_strs[session->sql_default_engine]; @@ -3258,6 +3263,8 @@ sql_session_setting_get(int id, const char **mp_pair, const char **mp_pair_end) pos_end = mp_encode_str(pos_end, name, name_len); if (is_bool) pos_end = mp_encode_bool(pos_end, (flags & mask) == mask); + else if (opt->field_type == FIELD_TYPE_UNSIGNED) + pos_end = mp_encode_uint(pos_end, vdbe_max_steps); else pos_end = mp_encode_str(pos_end, engine, engine_len); *mp_pair = pos; @@ -3309,6 +3316,24 @@ sql_set_string_option(int id, const char *value) return 0; } +/** + * Set given value for option that has + * type uint64_t. + * + * Currently it is only sql_vdbe_max_steps + * option. + */ +static int +sql_set_unsigned_option(int id, uint64_t value) +{ + assert(sql_session_opts[id - SESSION_SETTING_SQL_BEGIN].field_type = + FIELD_TYPE_UNSIGNED); + assert(id == SESSION_SETTING_SQL_VDBE_MAX_STEPS); + (void)id; + current_session()->vdbe_max_steps = value; + return 0; +} + static int sql_session_setting_set(int id, const char *mp_value) { @@ -3329,6 +3354,11 @@ sql_session_setting_set(int id, const char *mp_value) tmp = mp_decode_str(&mp_value, &len); tmp = tt_cstr(tmp, len); return sql_set_string_option(id, tmp); + case FIELD_TYPE_UNSIGNED: + if (mtype != MP_UINT) + break; + return sql_set_unsigned_option(id, + mp_decode_uint(&mp_value)); default: unreachable(); } diff --git a/src/box/sql/main.c b/src/box/sql/main.c index 402576bcfc..b84fd226c5 100644 --- a/src/box/sql/main.c +++ b/src/box/sql/main.c @@ -258,7 +258,6 @@ static const int aHardLimit[] = { SQL_MAX_COLUMN, SQL_MAX_EXPR_DEPTH, SQL_MAX_COMPOUND_SELECT, - SQL_MAX_VDBE_OP, SQL_MAX_FUNCTION_ARG, SQL_MAX_ATTACHED, SQL_MAX_LIKE_PATTERN_LENGTH, @@ -280,9 +279,6 @@ static const int aHardLimit[] = { #if SQL_MAX_COMPOUND_SELECT<2 #error SQL_MAX_COMPOUND_SELECT must be at least 2 #endif -#if SQL_MAX_VDBE_OP<40 -#error SQL_MAX_VDBE_OP must be at least 40 -#endif #if SQL_MAX_FUNCTION_ARG<0 || SQL_MAX_FUNCTION_ARG>127 #error SQL_MAX_FUNCTION_ARG must be between 0 and 127 #endif diff --git a/src/box/sql/sqlInt.h b/src/box/sql/sqlInt.h index a5008fbc17..6d90453396 100644 --- a/src/box/sql/sqlInt.h +++ b/src/box/sql/sqlInt.h @@ -296,11 +296,10 @@ struct sql_vfs { #define SQL_LIMIT_COLUMN 2 #define SQL_LIMIT_EXPR_DEPTH 3 #define SQL_LIMIT_COMPOUND_SELECT 4 -#define SQL_LIMIT_VDBE_OP 5 -#define SQL_LIMIT_FUNCTION_ARG 6 -#define SQL_LIMIT_ATTACHED 7 -#define SQL_LIMIT_LIKE_PATTERN_LENGTH 8 -#define SQL_LIMIT_TRIGGER_DEPTH 9 +#define SQL_LIMIT_FUNCTION_ARG 5 +#define SQL_LIMIT_ATTACHED 6 +#define SQL_LIMIT_LIKE_PATTERN_LENGTH 7 +#define SQL_LIMIT_TRIGGER_DEPTH 8 struct tt_uuid; struct sql_key_info; @@ -350,6 +349,13 @@ sql_stmt_compile(const char *sql, int bytes_count, struct Vdbe *re_prepared, int sql_step(sql_stmt *); +/** + * Set vdbe_max_steps limit before execution of + * Vdbe program. + */ +void +sql_set_vdbe_max_steps(sql_stmt *, uint64_t); + int sql_column_bytes16(sql_stmt *, int iCol); @@ -479,7 +485,6 @@ sql_vfs_register(sql_vfs *, int makeDflt); #define SQL_STMTSTATUS_FULLSCAN_STEP 1 #define SQL_STMTSTATUS_SORT 2 #define SQL_STMTSTATUS_AUTOINDEX 3 -#define SQL_STMTSTATUS_VM_STEP 4 /** Unbind all parameters of given prepared statement. */ void diff --git a/src/box/sql/sqlLimit.h b/src/box/sql/sqlLimit.h index 53dbe15059..f56ec3d960 100644 --- a/src/box/sql/sqlLimit.h +++ b/src/box/sql/sqlLimit.h @@ -122,12 +122,10 @@ enum { #endif /* - * The maximum number of opcodes in a VDBE program. - * Not currently enforced. + * The default number of opcodes Vdbe is allowed + * to execute. */ -#ifndef SQL_MAX_VDBE_OP -#define SQL_MAX_VDBE_OP 25000 -#endif +extern uint64_t default_vdbe_max_steps; /* * The maximum number of arguments to an SQL function. diff --git a/src/box/sql/vdbe.c b/src/box/sql/vdbe.c index fb4f4bd839..927affbcf9 100644 --- a/src/box/sql/vdbe.c +++ b/src/box/sql/vdbe.c @@ -370,7 +370,6 @@ int sqlVdbeExec(Vdbe *p) /* The database */ struct sql *db = sql_get(); int iCompare = 0; /* Result of last comparison */ - unsigned nVmStep = 0; /* Number of virtual machine steps */ Mem *aMem = p->aMem; /* Copy of p->aMem */ Mem *pIn1 = 0; /* 1st input operand */ Mem *pIn2 = 0; /* 2nd input operand */ @@ -416,7 +415,13 @@ int sqlVdbeExec(Vdbe *p) assert(rc == 0); assert(pOp>=aOp && pOp<&aOp[p->nOp]); - nVmStep++; + p->step_count++; + if (p->vdbe_max_steps > 0 && + p->vdbe_max_steps < p->step_count) { + diag_set(ClientError, ER_EXCEEDED_VDBE_MAX_STEPS, + p->vdbe_max_steps); + goto abort_due_to_error; + } /* Only allow tracing if SQL_DEBUG is defined. */ @@ -4379,6 +4384,17 @@ case OP_SetSession: { goto abort_due_to_error; break; } + case FIELD_TYPE_UNSIGNED: { + if (!mem_is_uint(pIn1)) + goto invalid_type; + uint64_t value = pIn1->u.u; + size_t size = mp_sizeof_uint(value); + char *mp_value = (char *)static_alloc(size); + mp_encode_uint(mp_value, value); + if (setting->set(sid, mp_value) != 0) + goto abort_due_to_error; + break; + } default: invalid_type: diag_set(ClientError, ER_SESSION_SETTING_INVALID_VALUE, @@ -4448,7 +4464,6 @@ default: { /* This is really OP_Noop and OP_Explain */ /* This is the only way out of this procedure. */ vdbe_return: - p->aCounter[SQL_STMTSTATUS_VM_STEP] += (int)nVmStep; assert(rc == 0 || rc == -1 || rc == SQL_ROW || rc == SQL_DONE); return rc; diff --git a/src/box/sql/vdbeInt.h b/src/box/sql/vdbeInt.h index ab98dae1b5..0604e2602a 100644 --- a/src/box/sql/vdbeInt.h +++ b/src/box/sql/vdbeInt.h @@ -294,7 +294,7 @@ struct Vdbe { * does not use external resources other than bind variables. */ bft is_sandboxed : 1; - u32 aCounter[5]; /* Counters used by sql_stmt_status() */ + u32 aCounter[4]; /* Counters used by sql_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ void *pFree; /* Free this when deleting the vdbe */ VdbeFrame *pFrame; /* Parent frame */ @@ -303,6 +303,10 @@ struct Vdbe { SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ /** Parser flags with which this object was built. */ uint32_t sql_flags; + /** Limit for maximum vdbe opcodes to execute. */ + uint64_t vdbe_max_steps; + /** The number of opcodes that were executed for current program */ + uint64_t step_count; /* Anonymous savepoint for aborts only */ struct txn_savepoint *anonymous_savepoint; }; diff --git a/src/box/sql/vdbeapi.c b/src/box/sql/vdbeapi.c index 113f5d5f7e..5bb5a3089f 100644 --- a/src/box/sql/vdbeapi.c +++ b/src/box/sql/vdbeapi.c @@ -170,6 +170,14 @@ sql_step(sql_stmt * pStmt) return sqlStep(v); } +void +sql_set_vdbe_max_steps(sql_stmt *pStmt, uint64_t vdbe_max_steps) +{ + Vdbe *v = (Vdbe *) pStmt; /* the prepared statement */ + assert(v != NULL); + v->vdbe_max_steps = vdbe_max_steps; +} + /* * Return the number of columns in the result set for the statement pStmt. */ diff --git a/src/box/sql/vdbeaux.c b/src/box/sql/vdbeaux.c index f6edc96f39..ccc98ea038 100644 --- a/src/box/sql/vdbeaux.c +++ b/src/box/sql/vdbeaux.c @@ -1358,6 +1358,7 @@ sqlVdbeRewind(Vdbe * p) /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */ p->magic = VDBE_MAGIC_RUN; + p->step_count = 0; p->pc = -1; p->is_aborted = false; p->ignoreRaised = 0; diff --git a/test/box-tap/cfg.test.lua b/test/box-tap/cfg.test.lua index c6f511ec90..2d3e3a5222 100755 --- a/test/box-tap/cfg.test.lua +++ b/test/box-tap/cfg.test.lua @@ -6,7 +6,7 @@ local socket = require('socket') local fio = require('fio') local uuid = require('uuid') local msgpack = require('msgpack') -test:plan(107) +test:plan(108) -------------------------------------------------------------------------------- -- Invalid values @@ -49,6 +49,7 @@ invalid('vinyl_run_size_ratio', 1) invalid('vinyl_bloom_fpr', 0) invalid('vinyl_bloom_fpr', 1.1) invalid('wal_queue_max_size', -1) +invalid('sql_vdbe_max_steps', -1) local function invalid_combinations(name, val) local status, result = pcall(box.cfg, val) diff --git a/test/box/admin.result b/test/box/admin.result index 53effd27f0..e5e2e9e2da 100644 --- a/test/box/admin.result +++ b/test/box/admin.result @@ -112,6 +112,8 @@ cfg_filter(box.cfg) - 8 - - sql_cache_size - 5242880 + - - sql_vdbe_max_steps + - 45000 - - strip_core - true - - too_long_threshold diff --git a/test/box/cfg.result b/test/box/cfg.result index 733461143e..7fc65dd1e7 100644 --- a/test/box/cfg.result +++ b/test/box/cfg.result @@ -100,6 +100,8 @@ cfg_filter(box.cfg) | - 8 | - - sql_cache_size | - 5242880 + | - - sql_vdbe_max_steps + | - 45000 | - - strip_core | - true | - - too_long_threshold @@ -238,6 +240,8 @@ cfg_filter(box.cfg) | - 8 | - - sql_cache_size | - 5242880 + | - - sql_vdbe_max_steps + | - 45000 | - - strip_core | - true | - - too_long_threshold diff --git a/test/box/error.result b/test/box/error.result index 56bf2886a2..c0973dd006 100644 --- a/test/box/error.result +++ b/test/box/error.result @@ -484,6 +484,9 @@ t; | 264: box.error.NIL_UUID | 265: box.error.WRONG_FUNCTION_OPTIONS | 266: box.error.MISSING_SYSTEM_SPACES + | 267: box.error.EXCEEDED_VDBE_MAX_STEPS + | 268: box.error.ILLEGAL_OPTIONS + | 269: box.error.ILLEGAL_OPTIONS_FORMAT | 272: box.error.SCHEMA_UPGRADE_IN_PROGRESS | 274: box.error.UNCONFIGURED | 278: box.error.IN_ANOTHER_PROMOTE diff --git a/test/box/session_settings.result b/test/box/session_settings.result index 6d74be6a00..f79de5aba7 100644 --- a/test/box/session_settings.result +++ b/test/box/session_settings.result @@ -61,6 +61,7 @@ s:select() | - ['sql_select_debug', false] | - ['sql_seq_scan', true] | - ['sql_vdbe_debug', false] + | - ['sql_vdbe_max_steps', 45000] | ... t = box.schema.space.create('settings', {format = s:format()}) diff --git a/test/sql-luatest/vdbe_max_steps_test.lua b/test/sql-luatest/vdbe_max_steps_test.lua new file mode 100644 index 0000000000..4cd7aeee1a --- /dev/null +++ b/test/sql-luatest/vdbe_max_steps_test.lua @@ -0,0 +1,129 @@ +local server = require('luatest.server') +local t = require('luatest') + +local g = t.group() + +g.before_each(function() + g.server = server:new({alias = 'vdbe_max_steps'}) + g.server:start() + g.server:exec(function() + box.execute([[SET SESSION "sql_vdbe_max_steps" = 0;]]) + box.execute([[CREATE TABLE t (i INT PRIMARY KEY, a INT);]]) + box.execute([[INSERT INTO t VALUES (1, 1), (2, 2), (3, 3);]]) + end) +end) + +g.after_each(function() + g.server:exec(function() + box.execute([[SET SESSION "sql_vdbe_max_steps" = 0;]]) + box.execute([[DROP TABLE t;]]) + end) + g.server:stop() +end) + +g.test_above_limit = function() + g.server:exec(function() + local _, err = box.execute([[SET SESSION "sql_vdbe_max_steps" = 5;]]) + t.assert_equals(err, nil) + + _, err = box.execute([[SELECT * FROM t;]]) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 5") + + _, err = box.execute([[SELECT * FROM t WHERE i + 1 > 2;]]) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 5") + + _, err = box.execute([[SELECT * FROM t WHERE a > 2;]], {}, + {{sql_vdbe_max_steps = 4}}) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 4") + + _, err = box.execute([[SELECT a, sum(i) FROM t + WHERE a > ? GROUP BY a;]], + {2}, {{sql_vdbe_max_steps = 20}}) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 20") + end) +end + +g.test_signature = function() + g.server:exec(function() + local _, err = box.execute([[select * from t;]], + {{sql_vdbe_max_steps = 1}}) + t.assert_equals(err.message, "Parameter 'sql_vdbe_max_steps' " .. + "was not found in the statement") + + _, err = box.execute([[select i + ? from t;]], {2}, {2, 3, 4}) + t.assert_equals(err.message, "Each option in third argument must " .. + "be a table containing only one key value pair") + + _, err = box.execute([[select i + ? from t;]], {2}, + {{sql_vdbe_max_steps = -1}}) + t.assert_equals(err.message, "Illegal options: value of the " .. + "sql_vdbe_max_steps option should be a non-negative integer.") + + _, err = box.execute([[select i + ? from t;]], {2}, + {{vdbe_max_steps = -1}}) + t.assert_equals(err.message, "Illegal options: vdbe_max_steps") + + _, err = box.execute([[select i + ? from t;]], {2}, {}) + t.assert_equals(err, nil) + + -- test prepared statement + local r + r, err = box.prepare([[SELECT * FROM t ORDER BY a;]]) + t.assert(r ~= nil and err == nil) + _, err = r:execute({}, {{sql_vdbe_max_steps = 10}}) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 10") + end) +end + +g.test_sql_trigger = function() + g.server:exec(function() + local _, err = box.execute([[SET SESSION "sql_vdbe_max_steps" = 12;]]) + t.assert_equals(err, nil) + -- such insert without triggers must take 12 opcodes + _, err = box.execute([[insert into t values (4, 4)]]) + t.assert_equals(err, nil) + + -- create trigger + _, err = box.execute([[create trigger before insert on t + for each row begin select * from t; end]], {}, + {{sql_vdbe_max_steps = 0}}) + t.assert_equals(err, nil) + + -- check that now insert will fail for 12 opcodes limit + _, err = box.execute([[SET SESSION "sql_vdbe_max_steps" = 12;]]) + t.assert_equals(err, nil) + _, err = box.execute([[insert into t values (5, 5)]]) + t.assert_equals(err.message, + "Reached a limit on max executed vdbe opcodes. Limit: 12") + end) +end + +g.test_below_limit = function() + g.server:exec(function() + local _, err = box.execute([[SET SESSION "sql_vdbe_max_steps" = 20;]]) + t.assert(err == nil) + + local res + res, err = box.execute([[SELECT max(i) FROM t;]]) + t.assert(res ~= nil and err == nil) + + res, err = box.execute([[SELECT min(i) FROM t;]]) + t.assert(res ~= nil and err == nil) + + res, err = box.execute([[SELECT * FROM t WHERE i = 2;]]) + t.assert(res ~= nil and err == nil) + + res, err = box.execute([[SELECT * FROM t WHERE i > 2;]]) + t.assert(res ~= nil and err == nil) + + box.execute([[SET SESSION "sql_vdbe_max_steps" = 0;]]) + res, err = box.execute([[SELECT * FROM t INNER JOIN + (select i as c, a as b from t) on true;]]) + t.assert(res ~= nil and err == nil) + end) +end diff --git a/test/sql-tap/autoindex1.test.lua b/test/sql-tap/autoindex1.test.lua index 99d44a2bc2..63a61a4391 100755 --- a/test/sql-tap/autoindex1.test.lua +++ b/test/sql-tap/autoindex1.test.lua @@ -36,6 +36,7 @@ test:do_eqp_test( {1,0,0,"SEARCH TABLE T2 USING EPHEMERAL INDEX (C=?) (~20 rows)"} }) +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) local result = test:execsql([[SELECT b, (SELECT d FROM t2 WHERE c = a) FROM t1;]]) test:do_eqp_test( @@ -50,6 +51,8 @@ test:do_execsql_test( "autoindex-1.3", [[ SELECT b, d FROM t1 JOIN t2 ON a = c ORDER BY b; ]], result) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) test:do_eqp_test( "autoindex-1.4", [[ @@ -59,10 +62,13 @@ test:do_eqp_test( {0,1,1,"SEARCH TABLE T2 USING EPHEMERAL INDEX (C=?) (~20 rows)"} }) +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( "autoindex-1.5", [[ SELECT b, d FROM t1 CROSS JOIN t2 ON (c = a); ]], result) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) test:execsql([[ CREATE TABLE t3(i INT PRIMARY KEY, a INT, b INT); @@ -76,6 +82,7 @@ test:execsql([[ -- for i = 1, 10240 do test:execsql("INSERT INTO t3 VALUES ("..i..", "..i..", "..(i + 1)..");") end +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( "autoindex-1.6", [[ SELECT count(*) @@ -92,6 +99,8 @@ test:do_execsql_test( ]], { 10231 }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) test:do_eqp_test( "autoindex-1.7", [[ diff --git a/test/sql-tap/count.test.lua b/test/sql-tap/count.test.lua index 9e75fb20b1..66fb15be60 100755 --- a/test/sql-tap/count.test.lua +++ b/test/sql-tap/count.test.lua @@ -90,6 +90,7 @@ for _, zIndex in ipairs(queries) do 4096 }) + test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( "count-1."..iTest..".5", [[ @@ -103,6 +104,8 @@ for _, zIndex in ipairs(queries) do ]], { 65536 }) + -- restore exec limit to default value + test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) end local function uses_op_count(sql) diff --git a/test/sql-tap/delete3.test.lua b/test/sql-tap/delete3.test.lua index eb20ebd528..6c995b67a1 100755 --- a/test/sql-tap/delete3.test.lua +++ b/test/sql-tap/delete3.test.lua @@ -21,6 +21,7 @@ test:plan(2) -- ["source",[["testdir"],"\/tester.tcl"]] -- Create a table that contains a large number of rows. -- +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( "delete3-1.1", [[ @@ -64,6 +65,8 @@ test:do_execsql_test( 262144 -- </delete3-1.2> }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) --integrity_check delete3-1.3 test:finish_test() diff --git a/test/sql-tap/index4.test.lua b/test/sql-tap/index4.test.lua index 262aee5b10..53bfaa8bfa 100755 --- a/test/sql-tap/index4.test.lua +++ b/test/sql-tap/index4.test.lua @@ -18,6 +18,7 @@ test:plan(7) -- -- ["set","testdir",[["file","dirname",["argv0"]]]] -- ["source",[["testdir"],"\/tester.tcl"]] +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( 1.1, [[ @@ -42,6 +43,8 @@ test:do_execsql_test( INSERT INTO t1 SELECT randomblob(102) FROM t1; -- 65536 COMMIT; ]]) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) test:do_execsql_test( 1.2, diff --git a/test/sql-tap/limit.test.lua b/test/sql-tap/limit.test.lua index 7173df55f0..d5d1a753c3 100755 --- a/test/sql-tap/limit.test.lua +++ b/test/sql-tap/limit.test.lua @@ -248,6 +248,8 @@ test:do_execsql_test( +-- disable sql exec limit +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_test( "limit-4.1", function() @@ -276,6 +278,9 @@ test:do_test( -- </limit-4.1> }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) + test:do_execsql_test( "limit-4.2", [[ @@ -358,7 +363,6 @@ test:do_execsql_test( 1000, 1528204, 593161, 0, 3107, 505, 1005 -- </limit-5.5> }) - -- There is some contraversy about whether LIMIT 0 should be the same as -- no limit at all or if LIMIT 0 should result in zero output rows. -- diff --git a/test/sql-tap/select2.test.lua b/test/sql-tap/select2.test.lua index ab679b0cf1..d2036e38f4 100755 --- a/test/sql-tap/select2.test.lua +++ b/test/sql-tap/select2.test.lua @@ -95,6 +95,7 @@ test:do_execsql_test( -- </select2-2.1> }) +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test( "select2-2.2", [[ @@ -114,6 +115,9 @@ test:do_execsql_test( 500 -- </select2-3.1> }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], + box.cfg.sql_vdbe_max_steps)) test:do_execsql_test( "select2-3.2a", diff --git a/test/sql-tap/selectG.test.lua b/test/sql-tap/selectG.test.lua index 63777d7b0d..3e24d080a4 100755 --- a/test/sql-tap/selectG.test.lua +++ b/test/sql-tap/selectG.test.lua @@ -36,6 +36,7 @@ else time_quota = engine == 'memtx' and 25 or ( engine == 'vinyl' and 50 or 0) -- seconds end +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_test( 100, function() @@ -57,6 +58,8 @@ test:do_test( 100000, 5000050000, 50000, true -- </100> }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) test:finish_test() diff --git a/test/sql-tap/sort.test.lua b/test/sql-tap/sort.test.lua index d3f32bfb65..2fd474d026 100755 --- a/test/sql-tap/sort.test.lua +++ b/test/sql-tap/sort.test.lua @@ -729,6 +729,9 @@ test:do_execsql_test( CREATE TABLE t10(id INT primary key, a INT , b INT ); ]]) +-- disable sql execution limit +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) + test:do_test( "sort-13.1", function() @@ -791,6 +794,8 @@ box.internal.sql_create_function("cksum", cksum) UPDATE t11 SET b = cksum(a); ]]) + -- restore exec limit to default value + test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) -- Legacy from the original code. Must be replaced with analogue -- functions from box. local tn = nil diff --git a/test/sql-tap/trigger8.test.lua b/test/sql-tap/trigger8.test.lua index adb0c86ab2..c99f5764f2 100755 --- a/test/sql-tap/trigger8.test.lua +++ b/test/sql-tap/trigger8.test.lua @@ -47,6 +47,7 @@ test:do_test( end sql = sql .. "END;" test:execsql(sql) + test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) return test:execsql [[ INSERT INTO t1 VALUES(5); SELECT COUNT(*) FROM t2; @@ -56,6 +57,8 @@ test:do_test( nStatement -- </trigger8-1.1> }) +-- restore exec limit to default value +test:execsql(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) -- MUST_WORK_TEST test:finish_test() diff --git a/test/sql-tap/with1.test.lua b/test/sql-tap/with1.test.lua index 2aa9f0aca4..e30d671416 100755 --- a/test/sql-tap/with1.test.lua +++ b/test/sql-tap/with1.test.lua @@ -23,6 +23,7 @@ test:plan(64) -- return -- end +test:execsql([[SET SESSION "sql_vdbe_max_steps" = 0;]]) test:do_execsql_test(1.0, [[ CREATE TABLE t1(x INTEGER UNIQUE, y INTEGER, z INTEGER PRIMARY KEY); WITH x(a) AS ( SELECT * FROM t1) SELECT 10 diff --git a/test/sql/prepared.result b/test/sql/prepared.result index d96cc72ce2..a356520712 100644 --- a/test/sql/prepared.result +++ b/test/sql/prepared.result @@ -479,9 +479,17 @@ s = prepare([[WITH RECURSIVE \ | --- | ... +execute([[SET SESSION "sql_vdbe_max_steps" = 0;]]) + | --- + | - row_count: 1 + | ... res = execute(s.stmt_id) | --- | ... +execute(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) + | --- + | - row_count: 1 + | ... res.metadata | --- | - - name: COLUMN_13 diff --git a/test/sql/prepared.test.lua b/test/sql/prepared.test.lua index eedcf0a370..1aea026d41 100644 --- a/test/sql/prepared.test.lua +++ b/test/sql/prepared.test.lua @@ -179,7 +179,9 @@ s = prepare([[WITH RECURSIVE \ FROM m2 GROUP BY cy) \ SELECT group_concat(CAST(TRIM(TRAILING FROM t) AS VARBINARY), x'0a') FROM a;]]) +execute([[SET SESSION "sql_vdbe_max_steps" = 0;]]) res = execute(s.stmt_id) +execute(string.format([[SET SESSION "sql_vdbe_max_steps" = %d;]], box.cfg.sql_vdbe_max_steps)) res.metadata unprepare(s.stmt_id) -- GitLab