From 67319f3e8dae545c12dcf9ac09a6289eddcc6f67 Mon Sep 17 00:00:00 2001 From: Oleg Smirnov Date: Wed, 7 Aug 2024 22:10:50 +0700 Subject: [PATCH] MDEV-34860 Implement MAX_EXECUTION_TIME hint It places a limit N (a timeout value in milliseconds) on how long a statement is permitted to execute before the server terminates it. Syntax: SELECT /*+ MAX_EXECUTION_TIME(milliseconds) */ ... Only top-level SELECT statements support the hint. --- mysql-test/main/opt_hint_timeout.result | 113 ++++++++++++++ mysql-test/main/opt_hint_timeout.test | 95 ++++++++++++ mysql-test/main/opt_hints.result | 10 +- mysql-test/main/opt_hints.test | 5 + sql/log_event_server.cc | 2 +- sql/opt_hints.cc | 188 +++++++++++++++++++++--- sql/opt_hints.h | 37 ++++- sql/opt_hints_parser.cc | 19 +++ sql/opt_hints_parser.h | 59 +++++++- sql/simple_parser.h | 47 +++++- sql/sql_base.cc | 18 ++- sql/sql_base.h | 5 +- sql/sql_class.h | 8 +- sql/sql_delete.cc | 4 +- sql/sql_help.cc | 2 +- sql/sql_insert.cc | 2 +- sql/sql_load.cc | 2 +- sql/sql_parse.cc | 2 +- sql/sql_select.cc | 2 +- sql/sql_update.cc | 4 +- 20 files changed, 573 insertions(+), 51 deletions(-) create mode 100644 mysql-test/main/opt_hint_timeout.result create mode 100644 mysql-test/main/opt_hint_timeout.test diff --git a/mysql-test/main/opt_hint_timeout.result b/mysql-test/main/opt_hint_timeout.result new file mode 100644 index 00000000000..daad9bea30a --- /dev/null +++ b/mysql-test/main/opt_hint_timeout.result @@ -0,0 +1,113 @@ +# +# MAX_EXECUTION_TIME hint testing +# +CREATE TABLE t1 (a INT, b VARCHAR(300)); +INSERT INTO t1 VALUES (1, 'string'); +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +# Correct hint usage +SELECT /*+ MAX_EXECUTION_TIME(10) */* FROM t1 a, t1 b; +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +EXPLAIN EXTENDED SELECT /*+ MAX_EXECUTION_TIME(000149) */* FROM t1; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 100.00 +Warnings: +Note 1003 select /*+ MAX_EXECUTION_TIME(000149) */ `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1` +SELECT /*+ MAX_EXECUTION_TIME(20) */ *, SLEEP(1) FROM t1 UNION SELECT 1, 2, 3; +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +(SELECT /*+ MAX_EXECUTION_TIME(30) */ *, SLEEP(1) FROM t1) UNION (SELECT 1, 2, 3); +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +((SELECT /*+ MAX_EXECUTION_TIME(50) */ *, SLEEP(1) FROM t1)); +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +# Check that prepared statements process the hint correctly +PREPARE s FROM 'SELECT /*+ MAX_EXECUTION_TIME(20) */ seq, SLEEP(1) FROM seq_1_to_10'; +EXECUTE s; +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +EXECUTE s; +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +# Hint duplication +SELECT /*+ MAX_EXECUTION_TIME(10) MAX_EXECUTION_TIME(100) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 4202 Hint MAX_EXECUTION_TIME(100) is ignored as conflicting/duplicated +# Wrong values +SELECT /*+ MAX_EXECUTION_TIME(0) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 1912 Incorrect value '0' for option 'MAX_EXECUTION_TIME' +SELECT /*+ MAX_EXECUTION_TIME(-1) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 1064 Optimizer hint syntax error near '-1) */ count(*) FROM t1' at line 1 +SELECT /*+ MAX_EXECUTION_TIME(4294967296) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 1912 Incorrect value '4294967296' for option 'MAX_EXECUTION_TIME' +# Conflicting max_statement_time and hint (must issue a warning) +SET STATEMENT max_statement_time=1 FOR +SELECT /*+ MAX_EXECUTION_TIME(500) */ count(*) FROM t1 a; +count(*) +512 +Warnings: +Warning 4202 Hint MAX_EXECUTION_TIME(500) is ignored as conflicting/duplicated + +# only SELECT statements supports the MAX_EXECUTION_TIME hint (warning): + +CREATE TABLE t2 (i INT); +INSERT /*+ MAX_EXECUTION_TIME(10) */ INTO t2 SELECT 1; +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context +REPLACE /*+ MAX_EXECUTION_TIME(15) */ INTO t2 SELECT 1; +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(15)' is not allowed in this context +UPDATE /*+ MAX_EXECUTION_TIME(23) */ t2 SET i = 1; +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(23)' is not allowed in this context +DELETE /*+ MAX_EXECUTION_TIME(5000) */ FROM t2 WHERE i = 1; +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(5000)' is not allowed in this context +# Not supported inside stored procedures/functions +CREATE PROCEDURE p1() BEGIN SELECT /*+ MAX_EXECUTION_TIME(10) */ count(*) FROM t1 a, t1 b +INTO @a; END| +CALL p1(); +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context +DROP PROCEDURE p1; +# Hint in a subquery is not allowed (warning): +SELECT 1 FROM (SELECT /*+ MAX_EXECUTION_TIME(10) */ 1) a; +1 +1 +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context +# Hint is allowed only for the first select of UNION (warning): +SELECT /*+ MAX_EXECUTION_TIME(20) */ count(*) FROM t1 +UNION +SELECT /*+ MAX_EXECUTION_TIME(30) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 4202 Hint MAX_EXECUTION_TIME(30) is ignored as conflicting/duplicated +SELECT count(*) FROM t1 +UNION +SELECT /*+ MAX_EXECUTION_TIME(30) */ count(*) FROM t1; +count(*) +512 +Warnings: +Warning 4172 'MAX_EXECUTION_TIME(30)' is not allowed in this context +# Check that hint actually works: +SELECT /*+ MAX_EXECUTION_TIME(20) */ count(*), SLEEP(1) FROM t1 +UNION +SELECT count(*), SLEEP(1) FROM t1; +ERROR 70100: Query execution was interrupted (max_statement_time exceeded) +DROP TABLE t1, t2; diff --git a/mysql-test/main/opt_hint_timeout.test b/mysql-test/main/opt_hint_timeout.test new file mode 100644 index 00000000000..68500edfc59 --- /dev/null +++ b/mysql-test/main/opt_hint_timeout.test @@ -0,0 +1,95 @@ +--source include/have_sequence.inc +--echo # +--echo # MAX_EXECUTION_TIME hint testing +--echo # +--enable_prepare_warnings + +CREATE TABLE t1 (a INT, b VARCHAR(300)); +INSERT INTO t1 VALUES (1, 'string'); +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; +INSERT INTO t1 SELECT * FROM t1; + +-- disable_query_log +-- disable_result_log +analyze table t1; +-- enable_result_log +-- enable_query_log + +--echo # Correct hint usage +--error ER_STATEMENT_TIMEOUT +SELECT /*+ MAX_EXECUTION_TIME(10) */* FROM t1 a, t1 b; + +EXPLAIN EXTENDED SELECT /*+ MAX_EXECUTION_TIME(000149) */* FROM t1; + +--error ER_STATEMENT_TIMEOUT +SELECT /*+ MAX_EXECUTION_TIME(20) */ *, SLEEP(1) FROM t1 UNION SELECT 1, 2, 3; +--error ER_STATEMENT_TIMEOUT +(SELECT /*+ MAX_EXECUTION_TIME(30) */ *, SLEEP(1) FROM t1) UNION (SELECT 1, 2, 3); +--error ER_STATEMENT_TIMEOUT +((SELECT /*+ MAX_EXECUTION_TIME(50) */ *, SLEEP(1) FROM t1)); + +--echo # Check that prepared statements process the hint correctly +PREPARE s FROM 'SELECT /*+ MAX_EXECUTION_TIME(20) */ seq, SLEEP(1) FROM seq_1_to_10'; +--error ER_STATEMENT_TIMEOUT +EXECUTE s; +--error ER_STATEMENT_TIMEOUT +EXECUTE s; + +--echo # Hint duplication + +SELECT /*+ MAX_EXECUTION_TIME(10) MAX_EXECUTION_TIME(100) */ count(*) FROM t1; + +--echo # Wrong values +SELECT /*+ MAX_EXECUTION_TIME(0) */ count(*) FROM t1; +SELECT /*+ MAX_EXECUTION_TIME(-1) */ count(*) FROM t1; +SELECT /*+ MAX_EXECUTION_TIME(4294967296) */ count(*) FROM t1; + +--echo # Conflicting max_statement_time and hint (must issue a warning) +SET STATEMENT max_statement_time=1 FOR + SELECT /*+ MAX_EXECUTION_TIME(500) */ count(*) FROM t1 a; + +--echo +--echo # only SELECT statements supports the MAX_EXECUTION_TIME hint (warning): +--echo +CREATE TABLE t2 (i INT); +INSERT /*+ MAX_EXECUTION_TIME(10) */ INTO t2 SELECT 1; +REPLACE /*+ MAX_EXECUTION_TIME(15) */ INTO t2 SELECT 1; +UPDATE /*+ MAX_EXECUTION_TIME(23) */ t2 SET i = 1; +DELETE /*+ MAX_EXECUTION_TIME(5000) */ FROM t2 WHERE i = 1; + +--echo # Not supported inside stored procedures/functions +DELIMITER |; +CREATE PROCEDURE p1() BEGIN SELECT /*+ MAX_EXECUTION_TIME(10) */ count(*) FROM t1 a, t1 b +INTO @a; END| +DELIMITER ;| + +CALL p1(); +DROP PROCEDURE p1; + +--echo # Hint in a subquery is not allowed (warning): +SELECT 1 FROM (SELECT /*+ MAX_EXECUTION_TIME(10) */ 1) a; + +--echo # Hint is allowed only for the first select of UNION (warning): +SELECT /*+ MAX_EXECUTION_TIME(20) */ count(*) FROM t1 +UNION +SELECT /*+ MAX_EXECUTION_TIME(30) */ count(*) FROM t1; + +SELECT count(*) FROM t1 +UNION +SELECT /*+ MAX_EXECUTION_TIME(30) */ count(*) FROM t1; + +--echo # Check that hint actually works: +--error ER_STATEMENT_TIMEOUT +SELECT /*+ MAX_EXECUTION_TIME(20) */ count(*), SLEEP(1) FROM t1 +UNION +SELECT count(*), SLEEP(1) FROM t1; + +DROP TABLE t1, t2; + diff --git a/mysql-test/main/opt_hints.result b/mysql-test/main/opt_hints.result index 65d0c71af9f..2249e70567f 100644 --- a/mysql-test/main/opt_hints.result +++ b/mysql-test/main/opt_hints.result @@ -1799,6 +1799,14 @@ id select_type table type possible_keys key key_len ref rows filtered Extra Warnings: Note 1003 select /*+ QB_NAME(`a b`) */ 1 AS `1` +# Identifiers starting with digits must be supported: +CREATE OR REPLACE TABLE 0a (8a INT, KEY 6a(8a)); +EXPLAIN EXTENDED SELECT /*+ NO_MRR(0a 6a) BKA(0a)*/ 8a FROM 0a; +id select_type table type possible_keys key key_len ref rows filtered Extra +1 SIMPLE 0a system NULL NULL NULL NULL 0 0.00 Const row not found +Warnings: +Note 1003 select /*+ BKA(`0a`@`select#1`) NO_MRR(`0a`@`select#1` `6a`) */ NULL AS `8a` from `test`.`0a` +DROP TABLE 0a; # hint syntax error: empty quoted identifier EXPLAIN EXTENDED SELECT /*+ QB_NAME(``) */ 1; id select_type table type possible_keys key key_len ref rows filtered Extra @@ -1876,7 +1884,7 @@ SELECT /*+ NO_ICP(10) */ 1; 1 1 Warnings: -Warning 4204 Unresolved table name `10`@`select#1` for NO_ICP hint +Warning 1064 Optimizer hint syntax error near '10) */ 1' at line 1 SELECT /*+ NO_ICP( */ 1; 1 1 diff --git a/mysql-test/main/opt_hints.test b/mysql-test/main/opt_hints.test index de605656be9..755f5bce23b 100644 --- a/mysql-test/main/opt_hints.test +++ b/mysql-test/main/opt_hints.test @@ -914,6 +914,11 @@ EXPLAIN EXTENDED SELECT /*+ QB_NAME(`*b`) */ 1; EXPLAIN EXTENDED SELECT /*+ QB_NAME(`a b`) */ 1; +--echo # Identifiers starting with digits must be supported: +CREATE OR REPLACE TABLE 0a (8a INT, KEY 6a(8a)); +EXPLAIN EXTENDED SELECT /*+ NO_MRR(0a 6a) BKA(0a)*/ 8a FROM 0a; +DROP TABLE 0a; + --echo # hint syntax error: empty quoted identifier EXPLAIN EXTENDED SELECT /*+ QB_NAME(``) */ 1; diff --git a/sql/log_event_server.cc b/sql/log_event_server.cc index bf42ea382c7..e7e3ece2717 100644 --- a/sql/log_event_server.cc +++ b/sql/log_event_server.cc @@ -4857,7 +4857,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) set/reset the slave thread's timer; a Rows_log_event update needs to set the timer itself */ - thd->set_query_timer(); + thd->set_query_timer_if_needed(); /* If there are no tables open, this must be the first row event seen diff --git a/sql/opt_hints.cc b/sql/opt_hints.cc index b50fb3130af..727fc7aa242 100644 --- a/sql/opt_hints.cc +++ b/sql/opt_hints.cc @@ -19,7 +19,6 @@ #include "sql_lex.h" #include "sql_select.h" #include "opt_hints.h" -#include "opt_hints_parser.h" /** Information about hints. Sould be @@ -34,12 +33,13 @@ struct st_opt_hint_info opt_hint_info[]= { - {{STRING_WITH_LEN("BKA")}, true, true}, - {{STRING_WITH_LEN("BNL")}, true, true}, - {{STRING_WITH_LEN("ICP")}, true, true}, - {{STRING_WITH_LEN("MRR")}, true, true}, - {{STRING_WITH_LEN("NO_RANGE_OPTIMIZATION")}, true, true}, + {{STRING_WITH_LEN("BKA")}, true, false}, + {{STRING_WITH_LEN("BNL")}, true, false}, + {{STRING_WITH_LEN("ICP")}, true, false}, + {{STRING_WITH_LEN("MRR")}, true, false}, + {{STRING_WITH_LEN("NO_RANGE_OPTIMIZATION")}, true, false}, {{STRING_WITH_LEN("QB_NAME")}, false, false}, + {{STRING_WITH_LEN("MAX_EXECUTION_TIME")}, false, true}, {null_clex_str, 0, 0} }; @@ -52,12 +52,14 @@ const LEX_CSTRING sys_qb_prefix= {"select#", 7}; static const Lex_ident_sys null_ident_sys; +template static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, bool hint_state, const Lex_ident_sys *qb_name_arg, const Lex_ident_sys *table_name_arg, - const Lex_ident_sys *key_name_arg) + const Lex_ident_sys *key_name_arg, + Hint_type *hint) { String str; @@ -77,6 +79,18 @@ void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, return; } + /* ER_BAD_OPTION_VALUE with two arguments. hint argument is required here */ + if (err_code == ER_BAD_OPTION_VALUE) + { + DBUG_ASSERT(hint); + String args; + hint->append_args(thd, &args); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + err_code, ER_THD(thd, err_code), + args.c_ptr_safe(), str.c_ptr_safe()); + return; + } + /* ER_WARN_CONFLICTING_HINT with one argument */ str.append('('); @@ -105,6 +119,15 @@ void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, append_identifier(thd, &str, key_name_arg->str, key_name_arg->length); } + /* Append additional hint arguments if they exist */ + if (hint) + { + if (qb_name_arg || table_name_arg || key_name_arg) + str.append(' '); + + hint->append_args(thd, &str); + } + str.append(')'); push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, @@ -131,8 +154,6 @@ static Opt_hints_global *get_global_hints(Parse_context *pc) lex->opt_hints_global= new (pc->thd->mem_root) Opt_hints_global(pc->thd->mem_root); } - if (lex->opt_hints_global) - lex->opt_hints_global->set_resolved(); return lex->opt_hints_global; } @@ -184,7 +205,7 @@ static Opt_hints_qb *find_qb_hints(Parse_context *pc, if (qb == NULL) { print_warn(pc->thd, ER_WARN_UNKNOWN_QB_NAME, hint_type, hint_state, - &qb_name, NULL, NULL); + &qb_name, NULL, NULL, (Parser::Hint*) NULL); } return qb; } @@ -252,7 +273,21 @@ void Opt_hints::print(THD *thd, String *str) { append_hint_type(str, static_cast(i)); str->append(STRING_WITH_LEN("(")); + uint32 len_before_name= str->length(); append_name(thd, str); + uint32 len_after_name= str->length(); + if (len_after_name > len_before_name) + str->append(' '); + if (opt_hint_info[i].has_arguments) + { + std::function args_printer= get_args_printer(); + args_printer(thd, str); + } + if (str->length() == len_after_name + 1) + { + // No additional arguments were printed, trim the space added before + str->length(len_after_name); + } str->append(STRING_WITH_LEN(") ")); } } @@ -396,7 +431,7 @@ static bool get_hint_state(Opt_hints *hint, { DBUG_ASSERT(parent_hint); - if (opt_hint_info[type_arg].switch_hint) + if (!opt_hint_info[type_arg].has_arguments) { if (hint && hint->is_specified(type_arg)) { @@ -412,7 +447,7 @@ static bool get_hint_state(Opt_hints *hint, } else { - /* Complex hint, not implemented atm */ + /* Complex hint with arguments, not implemented atm */ DBUG_ASSERT(0); } return false; @@ -524,8 +559,10 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const { // e.g. BKA(@qb1) if (qb->set_switch(hint_state, hint_type, false)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &qb_name_sys, NULL, NULL); + &qb_name_sys, NULL, NULL, (Parser::Hint*) NULL); + } return false; } else @@ -539,8 +576,10 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const if (!tab) return true; if (tab->set_switch(hint_state, hint_type, true)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &qb_name_sys, &table_name_sys, NULL); + &qb_name_sys, &table_name_sys, NULL, (Parser::Hint*) NULL); + } } } } @@ -556,8 +595,10 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const { // e.g. BKA() if (qb->set_switch(hint_state, hint_type, false)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &null_ident_sys, NULL, NULL); + &null_ident_sys, NULL, NULL, (Parser::Hint*) NULL); + } return false; } for (const Table_name &table : table_name_list) @@ -568,8 +609,11 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const if (!tab) return true; if (tab->set_switch(hint_state, hint_type, true)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &null_ident_sys, &table_name_sys, NULL); + &null_ident_sys, &table_name_sys, + NULL, (Parser::Hint*) NULL); + } } for (const Hint_param_table &table : opt_hint_param_table_list) @@ -586,15 +630,17 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const if (!tab) return true; if (tab->set_switch(hint_state, hint_type, true)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &qb_name_sys, &table_name_sys, NULL); + &qb_name_sys, &table_name_sys, NULL, (Parser::Hint*) NULL); + } } } return false; } -bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const +bool Parser::Index_level_hint::resolve(Parse_context *pc) const { const Index_level_hint_type &index_level_hint_type= *this; opt_hints_enum hint_type; @@ -639,8 +685,10 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const if (is_empty()) // Table level hint { if (tab->set_switch(hint_state, hint_type, false)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &qb_name_sys, &table_name_sys, NULL); + &qb_name_sys, &table_name_sys, NULL, (Parser::Hint*) NULL); + } return false; } @@ -656,15 +704,18 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const } if (idx->set_switch(hint_state, hint_type, true)) + { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, - &qb_name_sys, &table_name_sys, &index_name_sys); + &qb_name_sys, &table_name_sys, &index_name_sys, + (Parser::Hint*) NULL); + } } return false; } -bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const +bool Parser::Qb_name_hint::resolve(Parse_context *pc) const { Opt_hints_qb *qb= pc->select->opt_hints_qb; @@ -676,7 +727,7 @@ bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const qb->get_parent()->find_by_name(qb_name_sys)) // Name is already used { print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, QB_NAME_HINT_ENUM, true, - &qb_name_sys, NULL, NULL); + &qb_name_sys, NULL, NULL, (Parser::Hint*) NULL); return false; } @@ -684,6 +735,94 @@ bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const return false; } +/* + This is the first step of MAX_EXECUTION_TIME() hint resolution. It is invoked + during the parsing phase, but at this stage some essential information is + not yet available, preventing a full validation of the hint. + Particularly, the type of SQL command, mark of a stored procedure execution + or whether SELECT_LEX is not top-level (i.e., a subquery) are not yet set. + However, some basic checks like the numeric argument validation or hint + duplication check can still be performed. + The second step of hint validation is performed during the JOIN preparation + phase, within Opt_hints_global::resolve(). By this point, all necessary + information is up-to-date, allowing the hint to be fully resolved +*/ +bool Parser::Max_execution_time_hint::resolve(Parse_context *pc) const +{ + const Unsigned_Number& hint_arg= *this; + const ULonglong_null time_ms= hint_arg.get_ulonglong(); + + if (time_ms.is_null() || time_ms.value() == 0 || time_ms.value() > INT_MAX32) + { + print_warn(pc->thd, ER_BAD_OPTION_VALUE, MAX_EXEC_TIME_HINT_ENUM, + true, NULL, NULL, NULL, this); + return false; + } + + Opt_hints_global *global_hint= get_global_hints(pc); + if (global_hint->is_specified(MAX_EXEC_TIME_HINT_ENUM)) + { + // Hint duplication: /*+ MAX_EXECUTION_TIME ... MAX_EXECUTION_TIME */ + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, MAX_EXEC_TIME_HINT_ENUM, true, + NULL, NULL, NULL, this); + return false; + } + + global_hint->set_switch(true, MAX_EXEC_TIME_HINT_ENUM, false); + global_hint->max_exec_time_hint= this; + global_hint->max_exec_time_select_lex= pc->select; + return false; +} + + +void Parser::Max_execution_time_hint::append_args(THD *thd, String *str) const +{ + const Unsigned_Number& hint_arg= *this; + str->append(ErrConvString(hint_arg.str, hint_arg.length, + &my_charset_latin1).lex_cstring()); +} + + +ulong Parser::Max_execution_time_hint::get_milliseconds() const +{ + const Unsigned_Number& hint_arg= *this; + return hint_arg.get_ulonglong().value(); +} + + +bool Opt_hints_global::resolve(THD *thd) +{ + if (!max_exec_time_hint || thd->lex->is_ps_or_view_context_analysis()) + return false; + + /* + 2nd step of MAX_EXECUTION_TIME() hint validation. Some checks were already + performed during the parsing stage (Max_execution_time_hint::resolve()), + but the following checks can only be performed during the JOIN preparation + because thd->lex variables are not available during parsing + */ + if (thd->lex->sql_command != SQLCOM_SELECT || // not a SELECT statement + thd->lex->sphead || thd->in_sub_stmt != 0 || // or a SP/trigger/event + max_exec_time_select_lex->master_unit() != &thd->lex->unit || // or a subquery + max_exec_time_select_lex->select_number != 1) // not a top-level select + { + print_warn(thd, ER_NOT_ALLOWED_IN_THIS_CONTEXT, MAX_EXEC_TIME_HINT_ENUM, + true, NULL, NULL, NULL, max_exec_time_hint); + } + else if (thd->variables.max_statement_time != 0 || + thd->query_timer.expired == 0) + { + print_warn(thd, ER_WARN_CONFLICTING_HINT, MAX_EXEC_TIME_HINT_ENUM, true, + NULL, NULL, NULL, max_exec_time_hint); + } + else + { + thd->set_query_timer_force(max_exec_time_hint->get_milliseconds() * 1000); + } + set_resolved(); + return false; +} + bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc) const { @@ -717,6 +856,11 @@ bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc) const if (qb_hint.resolve(pc)) return true; } + else if (const Max_execution_time_hint &max_hint= hint) + { + if (max_hint.resolve(pc)) + return true; + } } return false; } diff --git a/sql/opt_hints.h b/sql/opt_hints.h index cc87feaf605..1b95b5a7269 100644 --- a/sql/opt_hints.h +++ b/sql/opt_hints.h @@ -65,6 +65,7 @@ #ifndef OPT_HINTS_INCLUDED #define OPT_HINTS_INCLUDED +#include #include "my_config.h" #include "sql_alloc.h" #include "sql_list.h" @@ -73,7 +74,7 @@ #include "sql_bitmap.h" #include "sql_show.h" #include "mysqld_error.h" - +#include "opt_hints_parser.h" struct LEX; struct TABLE; @@ -91,6 +92,7 @@ enum opt_hints_enum MRR_HINT_ENUM, NO_RANGE_HINT_ENUM, QB_NAME_HINT_ENUM, + MAX_EXEC_TIME_HINT_ENUM, MAX_HINT_ENUM }; @@ -100,9 +102,10 @@ struct st_opt_hint_info LEX_CSTRING hint_name; // Hint name. bool check_upper_lvl; // true if upper level hint check is needed (for hints // which can be specified on more than one level). - bool switch_hint; // true if hint is not complex. + bool has_arguments; // true if hint has additional arguments. }; +typedef Optimizer_hint_parser Parser; /** Opt_hints_map contains information @@ -304,6 +307,16 @@ public: void check_unresolved(THD *thd); virtual void append_name(THD *thd, String *str)= 0; + /** + Get the function appending additional hint arguments to the printed string, + if the arguments exist. For example, SEMIJOIN and SUBQUERY hints may have + a list of strategies as additional arguments + */ + virtual std::function get_args_printer() const + { + return [](THD*, String*) {}; + } + virtual ~Opt_hints() {} private: @@ -336,13 +349,31 @@ protected: class Opt_hints_global : public Opt_hints { - public: + const Parser::Max_execution_time_hint *max_exec_time_hint= nullptr; + + /* + If MAX_EXECUTION_TIME() hint was provided, this pointer is set to + the SELECT_LEX which the hint is attached to. + NULL if MAX_EXECUTION_TIME() hint is missing. + */ + st_select_lex *max_exec_time_select_lex= nullptr; + Opt_hints_global(MEM_ROOT *mem_root_arg) : Opt_hints(Lex_ident_sys(), NULL, mem_root_arg) {} virtual void append_name(THD *thd, String *str) override {} + + virtual std::function get_args_printer() const override + { + using std::placeholders::_1; + using std::placeholders::_2; + return std::bind(&Parser::Max_execution_time_hint::append_args, + max_exec_time_hint, _1, _2); + } + + bool resolve(THD *thd); }; diff --git a/sql/opt_hints_parser.cc b/sql/opt_hints_parser.cc index c86b54effad..8e8c18eba53 100644 --- a/sql/opt_hints_parser.cc +++ b/sql/opt_hints_parser.cc @@ -57,11 +57,30 @@ Optimizer_hint_tokenizer::find_keyword(const LEX_CSTRING &str) return TokenID::keyword_QB_NAME; break; + case 18: + if ("MAX_EXECUTION_TIME"_Lex_ident_column.streq(str)) + return TokenID::keyword_MAX_EXECUTION_TIME; + break; + case 21: if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_RANGE_OPTIMIZATION; break; } + + if (str.length > 0 && (str.str[0] >= '0' && str.str[0] <= '9')) + { + /* + If all characters are digits, qualify the token as a number, + otherwise as an identifier + */ + for(size_t i = 1; i < str.length; i++) + { + if (str.str[i] < '0' || str.str[i] > '9') + return TokenID::tIDENT; + } + return TokenID::tUNSIGNED_NUMBER; + } return TokenID::tIDENT; } diff --git a/sql/opt_hints_parser.h b/sql/opt_hints_parser.h index 368a5b26d75..d37779966d5 100644 --- a/sql/opt_hints_parser.h +++ b/sql/opt_hints_parser.h @@ -22,6 +22,7 @@ #include "simple_tokenizer.h" #include "sql_list.h" #include "sql_string.h" +#include "sql_type_int.h" #include "simple_parser.h" class st_select_lex; @@ -72,9 +73,11 @@ public: keyword_NO_RANGE_OPTIMIZATION, keyword_MRR, keyword_QB_NAME, + keyword_MAX_EXECUTION_TIME, // Other token types - tIDENT + tIDENT, + tUNSIGNED_NUMBER }; class Token: public Lex_cstring @@ -240,6 +243,13 @@ private: using TOKEN::TOKEN; }; + class Keyword_MAX_EXECUTION_TIME: + public TOKEN + { + public: + using TOKEN::TOKEN; + }; + class Identifier: public TOKEN { public: @@ -258,6 +268,28 @@ private: } }; + class Unsigned_Number: public TOKEN + { + public: + using TOKEN::TOKEN; + + /* + Converts token string to a non-negative number ( >=0 ). + Returns the converted number if the conversion succeeds. + Returns non-NULL ULonglong_null value on successful string conversion and + NULL ULonglong_null if the conversion failed or the number is negative + */ + ULonglong_null get_ulonglong() const + { + int error; + char *end= const_cast(str + length); + longlong n= my_strtoll10(str, &end, &error); + if (error != 0 || end != str + length || n < 0) + return ULonglong_null(0, true); + return ULonglong_null(n, false); + } + }; + class LParen: public TOKEN { public: @@ -554,21 +586,40 @@ private: }; +public: + // max_execution_time_hint ::= MAX_EXECUTION_TIME ( milliseconds ) + class Max_execution_time_hint: public AND4 + { + public: + using AND4::AND4; + + bool resolve(Parse_context *pc) const; + void append_args(THD *thd, String *str) const; + ulong get_milliseconds() const; + }; + /* hint ::= index_level_hint | table_level_hint | qb_name_hint + | statement_level_hint */ - class Hint: public OR3 + Qb_name_hint, + Max_execution_time_hint> { public: - using OR3::OR3; + using OR4::OR4; }; +private: // hint_list ::= hint [ hint... ] class Hint_list_container: public List { diff --git a/sql/simple_parser.h b/sql/simple_parser.h index a49b716182c..f51b535059e 100644 --- a/sql/simple_parser.h +++ b/sql/simple_parser.h @@ -380,7 +380,7 @@ protected: /* - A rule consisting of a choice of thee rules: + A rule consisting of a choice of three rules: rule ::= rule1 | rule2 | rule3 For the case when the three branches have incompatible storage @@ -478,7 +478,50 @@ protected: /* - A list with at least MIN_COUNT elements (typlically 0 or 1), + A rule consisting of a choice of four rules: + rule ::= rule1 | rule2 | rule3 | rule4 + + For the case when the three branches have incompatible storage + */ + template + class OR4: public A, public B, public C, public D + { + public: + OR4() + { } + OR4(OR4 &&rhs) + :A(std::move(static_cast(rhs))), + B(std::move(static_cast(rhs))), + C(std::move(static_cast(rhs))), + D(std::move(static_cast(rhs))) + { } + OR4 & operator=(OR4 &&rhs) + { + A::operator=(std::move(static_cast(rhs))); + B::operator=(std::move(static_cast(rhs))); + C::operator=(std::move(static_cast(rhs))); + D::operator=(std::move(static_cast(rhs))); + return *this; + } + OR4(PARSER *p) + :A(p), + B(A::operator bool() ? B() : B(p)), + C(A::operator bool() || B::operator bool() ? C() : C(p)), + D(A::operator bool() || B::operator bool() || C::operator bool() ? + D() : D(p)) + { + DBUG_ASSERT(!operator bool() || !p->is_error()); + } + operator bool() const + { + return A::operator bool() || B::operator bool() || C::operator bool() || + D::operator bool(); + } + }; + + + /* + A list with at least MIN_COUNT elements (typically 0 or 1), with or without a token separator between elements: list ::= element [ {, element }... ] // with a separator diff --git a/sql/sql_base.cc b/sql/sql_base.cc index f7a0be34c89..6c58a14128f 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8229,6 +8229,7 @@ void make_leaves_list(THD *thd, List &list, TABLE_LIST *tables, refresh It is only refresh for subquery select_insert It is SELECT ... INSERT command full_table_list a parameter to pass to the make_leaves_list function + resolve_opt_hints Whether optimizer hints must be resolved here NOTE Check also that the 'used keys' and 'ignored keys' exists and set up the @@ -8248,7 +8249,7 @@ void make_leaves_list(THD *thd, List &list, TABLE_LIST *tables, bool setup_tables(THD *thd, Name_resolution_context *context, List *from_clause, TABLE_LIST *tables, List &leaves, bool select_insert, - bool full_table_list) + bool full_table_list, bool resolve_opt_hints) { uint tablenr= 0; List_iterator ti(leaves); @@ -8393,8 +8394,13 @@ bool setup_tables(THD *thd, Name_resolution_context *context, if (setup_natural_join_row_types(thd, from_clause, context)) DBUG_RETURN(1); - if (qb_hints) - qb_hints->check_unresolved(thd); + if (resolve_opt_hints) + { + if (thd->lex->opt_hints_global && select_lex->select_number == 1) + thd->lex->opt_hints_global->resolve(thd); + if (qb_hints) + qb_hints->check_unresolved(thd); + } DBUG_RETURN(0); } @@ -8414,6 +8420,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context, select_insert It is SELECT ... INSERT command want_access what access is needed full_table_list a parameter to pass to the make_leaves_list function + resolve_opt_hints Whether optimizer hints must be resolved here NOTE a wrapper for check_tables that will also check the resulting @@ -8430,12 +8437,13 @@ bool setup_tables_and_check_access(THD *thd, Name_resolution_context *context, bool select_insert, privilege_t want_access_first, privilege_t want_access, - bool full_table_list) + bool full_table_list, + bool resolve_opt_hints) { DBUG_ENTER("setup_tables_and_check_access"); if (setup_tables(thd, context, from_clause, tables, - leaves, select_insert, full_table_list)) + leaves, select_insert, full_table_list, resolve_opt_hints)) DBUG_RETURN(TRUE); List_iterator ti(leaves); diff --git a/sql/sql_base.h b/sql/sql_base.h index a37e57852ed..0c1947f1c83 100644 --- a/sql/sql_base.h +++ b/sql/sql_base.h @@ -228,7 +228,7 @@ Item ** find_item_in_list(Item *item, List &items, uint *counter, bool setup_tables(THD *thd, Name_resolution_context *context, List *from_clause, TABLE_LIST *tables, List &leaves, bool select_insert, - bool full_table_list); + bool full_table_list, bool resolve_opt_hints); bool setup_tables_and_check_access(THD *thd, Name_resolution_context *context, List *from_clause, @@ -237,7 +237,8 @@ bool setup_tables_and_check_access(THD *thd, bool select_insert, privilege_t want_access_first, privilege_t want_access, - bool full_table_list); + bool full_table_list, + bool resolve_opt_hints); bool wait_while_table_is_used(THD *thd, TABLE *table, enum ha_extra_function function); diff --git a/sql/sql_class.h b/sql/sql_class.h index b9dd2ebe8a8..915533a83e3 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -6040,7 +6040,7 @@ public: ulonglong num_of_strings_sorted_on_truncated_length; public: - void set_query_timer() + void set_query_timer_if_needed() { #ifndef EMBEDDED_LIBRARY /* @@ -6059,9 +6059,13 @@ public: */ if (!timeout_val || spcont || in_sub_stmt || query_timer.expired == 0) return; - thr_timer_settime(&query_timer, timeout_val); + set_query_timer_force(timeout_val); #endif } + void set_query_timer_force(ulonglong timeout_val) + { + thr_timer_settime(&query_timer, timeout_val); + } void reset_query_timer() { #ifndef EMBEDDED_LIBRARY diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index f644c0c659e..b74ca7e44f6 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -1880,11 +1880,11 @@ bool Sql_cmd_delete::prepare_inner(THD *thd) if (setup_tables_and_check_access(thd, &select_lex->context, &select_lex->top_join_list, table_list, select_lex->leaf_tables, - false, DELETE_ACL, SELECT_ACL, true)) + false, DELETE_ACL, SELECT_ACL, true, false)) DBUG_RETURN(TRUE); if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list, - table_list, select_lex->leaf_tables, false, false)) + table_list, select_lex->leaf_tables, false, false, true)) DBUG_RETURN(TRUE); if (!multitable) diff --git a/sql/sql_help.cc b/sql/sql_help.cc index a028c219936..e079e8b793a 100644 --- a/sql/sql_help.cc +++ b/sql/sql_help.cc @@ -801,7 +801,7 @@ static bool init_items_for_help_command(THD *thd, if (setup_tables(thd, &first_select_lex->context, &first_select_lex->top_join_list, - &tables[0], leaves, false, false)) + &tables[0], leaves, false, false, true)) return true; memcpy((char*) used_fields, (char*) init_used_fields, diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 1756489b430..4290a74d43f 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1647,7 +1647,7 @@ static bool mysql_prepare_insert_check_table(THD *thd, TABLE_LIST *table_list, table_list, thd->lex->first_select_lex()->leaf_tables, select_insert, INSERT_ACL, SELECT_ACL, - TRUE)) + true, true)) DBUG_RETURN(TRUE); if (insert_into_view && !fields.elements) diff --git a/sql/sql_load.cc b/sql/sql_load.cc index f4a11449abf..96d00a2d198 100644 --- a/sql/sql_load.cc +++ b/sql/sql_load.cc @@ -419,7 +419,7 @@ int mysql_load(THD *thd, const sql_exchange *ex, TABLE_LIST *table_list, thd->lex->first_select_lex()->leaf_tables, FALSE, INSERT_ACL | UPDATE_ACL, - INSERT_ACL | UPDATE_ACL, FALSE)) + INSERT_ACL | UPDATE_ACL, false, true)) DBUG_RETURN(-1); if (!table_list->table || // do not support join view !table_list->single_table_updatable() || // and derived tables diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index d8de3368f22..eeb52493de4 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3822,7 +3822,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) thd->query_plan_flags|= QPLAN_ADMIN; /* Start timeouts */ - thd->set_query_timer(); + thd->set_query_timer_if_needed(); #ifdef WITH_WSREP /* Check wsrep_mode rules before command execution. */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7cc55a1e782..6e1168368aa 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1468,7 +1468,7 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, if (!(select_options & OPTION_SETUP_TABLES_DONE) && setup_tables_and_check_access(thd, &select_lex->context, join_list, tables_list, select_lex->leaf_tables, - FALSE, SELECT_ACL, SELECT_ACL, FALSE)) + false, SELECT_ACL, SELECT_ACL, false, true)) DBUG_RETURN(-1); /* System Versioning: handle FOR SYSTEM_TIME clause. */ diff --git a/sql/sql_update.cc b/sql/sql_update.cc index ceba72915b3..9d5289169bf 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1672,7 +1672,7 @@ bool Multiupdate_prelocking_strategy::handle_end(THD *thd) if (setup_tables_and_check_access(thd, &select_lex->context, &select_lex->top_join_list, table_list, select_lex->leaf_tables, - FALSE, UPDATE_ACL, SELECT_ACL, TRUE)) + false, UPDATE_ACL, SELECT_ACL, true, false)) DBUG_RETURN(1); if (table_list->has_period() && @@ -3118,7 +3118,7 @@ bool Sql_cmd_update::prepare_inner(THD *thd) DBUG_RETURN(TRUE); if (setup_tables(thd, &select_lex->context, &select_lex->top_join_list, - table_list, select_lex->leaf_tables, false, false)) + table_list, select_lex->leaf_tables, false, false, true)) DBUG_RETURN(TRUE); if (select_lex->vers_setup_conds(thd, table_list))