1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

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.
This commit is contained in:
Oleg Smirnov
2024-08-07 22:10:50 +07:00
parent 1e2774d829
commit 67319f3e8d
20 changed files with 573 additions and 51 deletions

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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<typename Hint_type>
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<opt_hints_enum>(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<void(THD*, String*)> 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;
}

View File

@ -65,6 +65,7 @@
#ifndef OPT_HINTS_INCLUDED
#define OPT_HINTS_INCLUDED
#include <functional>
#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<void(THD*, String*)> 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<void(THD*, String*)> 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);
};

View File

@ -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;
}

View File

@ -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<Parser, TokenID::keyword_MAX_EXECUTION_TIME>
{
public:
using TOKEN::TOKEN;
};
class Identifier: public TOKEN<Parser, TokenID::tIDENT>
{
public:
@ -258,6 +268,28 @@ private:
}
};
class Unsigned_Number: public TOKEN<Parser, TokenID::tUNSIGNED_NUMBER>
{
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<char *>(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<Parser, TokenID::tLPAREN>
{
public:
@ -554,21 +586,40 @@ private:
};
public:
// max_execution_time_hint ::= MAX_EXECUTION_TIME ( milliseconds )
class Max_execution_time_hint: public AND4<Parser,
Keyword_MAX_EXECUTION_TIME,
LParen,
Unsigned_Number,
RParen>
{
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<Parser,
class Hint: public OR4<Parser,
Index_level_hint,
Table_level_hint,
Qb_name_hint>
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<Hint>
{

View File

@ -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 PARSER, class A, class B, class C, class D>
class OR4: public A, public B, public C, public D
{
public:
OR4()
{ }
OR4(OR4 &&rhs)
:A(std::move(static_cast<A&&>(rhs))),
B(std::move(static_cast<B&&>(rhs))),
C(std::move(static_cast<C&&>(rhs))),
D(std::move(static_cast<D&&>(rhs)))
{ }
OR4 & operator=(OR4 &&rhs)
{
A::operator=(std::move(static_cast<A&&>(rhs)));
B::operator=(std::move(static_cast<B&&>(rhs)));
C::operator=(std::move(static_cast<C&&>(rhs)));
D::operator=(std::move(static_cast<D&&>(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

View File

@ -8229,6 +8229,7 @@ void make_leaves_list(THD *thd, List<TABLE_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<TABLE_LIST> &list, TABLE_LIST *tables,
bool setup_tables(THD *thd, Name_resolution_context *context,
List<TABLE_LIST> *from_clause, TABLE_LIST *tables,
List<TABLE_LIST> &leaves, bool select_insert,
bool full_table_list)
bool full_table_list, bool resolve_opt_hints)
{
uint tablenr= 0;
List_iterator<TABLE_LIST> 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<TABLE_LIST> ti(leaves);

View File

@ -228,7 +228,7 @@ Item ** find_item_in_list(Item *item, List<Item> &items, uint *counter,
bool setup_tables(THD *thd, Name_resolution_context *context,
List<TABLE_LIST> *from_clause, TABLE_LIST *tables,
List<TABLE_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<TABLE_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);

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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. */

View File

@ -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. */

View File

@ -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))