Commit ae8630e5 authored by Oleg Smirnov's avatar Oleg Smirnov

MDEV-33281 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
parent 2594881d
--echo #
--echo # MAX_EXECUTION_TIME hint testing
--echo #
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;
--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 # 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(1) */ INTO t2 SELECT 1;
REPLACE /*+ MAX_EXECUTION_TIME(1) */ INTO t2 SELECT 1;
UPDATE /*+ MAX_EXECUTION_TIME(1) */ t2 SET i = 1;
DELETE /*+ MAX_EXECUTION_TIME(1) */ 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;
DROP TABLE t1, t2;
...@@ -1070,7 +1070,9 @@ i ...@@ -1070,7 +1070,9 @@ i
Warnings: Warnings:
Warning 4202 Hint NO_ICP(`t1`@`q1` `PRIMARY`) is ignored as conflicting/duplicated Warning 4202 Hint NO_ICP(`t1`@`q1` `PRIMARY`) is ignored as conflicting/duplicated
DROP TABLE t1; DROP TABLE t1;
# WL#8016 Parser for optimizer hints #
# Tests of the optimizer hints parser
#
CREATE TABLE t1 (i INT, j INT); CREATE TABLE t1 (i INT, j INT);
CREATE INDEX i1 ON t1(i); CREATE INDEX i1 ON t1(i);
CREATE INDEX i2 ON t1(j); CREATE INDEX i2 ON t1(j);
...@@ -1224,7 +1226,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra ...@@ -1224,7 +1226,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
2 UNION NULL NULL NULL NULL NULL NULL NULL NULL No tables used 2 UNION NULL NULL NULL NULL NULL NULL NULL NULL No tables used
NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL NULL NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL NULL
Warnings: Warnings:
Note 1003 /* select#1 */ select /*+ QB_NAME(`qb1`) */ 1 AS `1` union /* select#2 */ select 1 AS `1` Note 1003 /* select#1 */ select /*+ QB_NAME(`qb1`) */ 1 AS `1` union /* select#2 */ select /*+ QB_NAME(`qb2`) */ 1 AS `1`
EXPLAIN EXTENDED (SELECT /*+ QB_NAME(qb1) */ 1) UNION (SELECT /*+ QB_NAME(qb2) */ 1); EXPLAIN EXTENDED (SELECT /*+ QB_NAME(qb1) */ 1) UNION (SELECT /*+ QB_NAME(qb2) */ 1);
id select_type table type possible_keys key key_len ref rows filtered Extra id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL No tables used
......
...@@ -500,9 +500,10 @@ SELECT /*+ QB_NAME(q1) NO_ICP(@q1 t1 PRIMARY) NO_ICP(@q1 t1 PRIMARY) */ * FROM t ...@@ -500,9 +500,10 @@ SELECT /*+ QB_NAME(q1) NO_ICP(@q1 t1 PRIMARY) NO_ICP(@q1 t1 PRIMARY) */ * FROM t
DROP TABLE t1; DROP TABLE t1;
--echo # WL#8016 Parser for optimizer hints
--echo #
--echo # Tests of the optimizer hints parser
--echo #
CREATE TABLE t1 (i INT, j INT); CREATE TABLE t1 (i INT, j INT);
CREATE INDEX i1 ON t1(i); CREATE INDEX i1 ON t1(i);
CREATE INDEX i2 ON t1(j); CREATE INDEX i2 ON t1(j);
......
...@@ -4792,7 +4792,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi) ...@@ -4792,7 +4792,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 set/reset the slave thread's timer; a Rows_log_event update needs to set
the timer itself 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 If there are no tables open, this must be the first row event seen
......
...@@ -40,6 +40,7 @@ struct st_opt_hint_info opt_hint_info[]= ...@@ -40,6 +40,7 @@ struct st_opt_hint_info opt_hint_info[]=
{{STRING_WITH_LEN("MRR")}, true, true}, {{STRING_WITH_LEN("MRR")}, true, true},
{{STRING_WITH_LEN("NO_RANGE_OPTIMIZATION")}, true, true}, {{STRING_WITH_LEN("NO_RANGE_OPTIMIZATION")}, true, true},
{{STRING_WITH_LEN("QB_NAME")}, false, false}, {{STRING_WITH_LEN("QB_NAME")}, false, false},
{{STRING_WITH_LEN("MAX_EXECUTION_TIME")}, false, false},
{null_clex_str, 0, 0} {null_clex_str, 0, 0}
}; };
...@@ -52,11 +53,21 @@ const LEX_CSTRING sys_qb_prefix= {"select#", 7}; ...@@ -52,11 +53,21 @@ const LEX_CSTRING sys_qb_prefix= {"select#", 7};
static const Lex_ident_sys null_ident_sys; static const Lex_ident_sys null_ident_sys;
static void append_ulong_to_str(String* str, ulong number)
{
char numbuf[STRING_BUFFER_USUAL_SIZE];
size_t size= my_snprintf(numbuf, sizeof(numbuf), "%u", number);
str->append(numbuf, size);
}
static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type,
bool hint_state, bool hint_state,
const Lex_ident_sys *qb_name_arg, const Lex_ident_sys *qb_name_arg,
const Lex_ident_sys *table_name_arg, const Lex_ident_sys *table_name_arg,
const Lex_ident_sys *key_name_arg) const Lex_ident_sys *key_name_arg,
ulong *numeric_arg)
{ {
String str; String str;
...@@ -103,6 +114,10 @@ static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, ...@@ -103,6 +114,10 @@ static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type,
str.append(' '); str.append(' ');
append_identifier(thd, &str, key_name_arg->str, key_name_arg->length); append_identifier(thd, &str, key_name_arg->str, key_name_arg->length);
} }
if (numeric_arg)
{
append_ulong_to_str(&str, *numeric_arg);
}
str.append(')'); str.append(')');
...@@ -130,8 +145,6 @@ static Opt_hints_global *get_global_hints(Parse_context *pc) ...@@ -130,8 +145,6 @@ static Opt_hints_global *get_global_hints(Parse_context *pc)
lex->opt_hints_global= new (pc->thd->mem_root) lex->opt_hints_global= new (pc->thd->mem_root)
Opt_hints_global(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; return lex->opt_hints_global;
} }
...@@ -183,7 +196,7 @@ static Opt_hints_qb *find_qb_hints(Parse_context *pc, ...@@ -183,7 +196,7 @@ static Opt_hints_qb *find_qb_hints(Parse_context *pc,
if (qb == NULL) if (qb == NULL)
{ {
print_warn(pc->thd, ER_WARN_UNKNOWN_QB_NAME, hint_type, hint_state, print_warn(pc->thd, ER_WARN_UNKNOWN_QB_NAME, hint_type, hint_state,
&qb_name, NULL, NULL); &qb_name, NULL, NULL, NULL);
} }
return qb; return qb;
} }
...@@ -303,13 +316,6 @@ void Opt_hints::check_unresolved(THD *thd) ...@@ -303,13 +316,6 @@ void Opt_hints::check_unresolved(THD *thd)
} }
PT_hint *Opt_hints_global::get_complex_hints(uint type)
{
DBUG_ASSERT(0);
return NULL;
}
Opt_hints_qb::Opt_hints_qb(Opt_hints *opt_hints_arg, Opt_hints_qb::Opt_hints_qb(Opt_hints *opt_hints_arg,
MEM_ROOT *mem_root_arg, MEM_ROOT *mem_root_arg,
uint select_number_arg) uint select_number_arg)
...@@ -519,7 +525,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const ...@@ -519,7 +525,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const
// e.g. BKA(@qb1) // e.g. BKA(@qb1)
if (qb->set_switch(hint_state, hint_type, false)) if (qb->set_switch(hint_state, hint_type, false))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state,
&qb_name_sys, NULL, NULL); &qb_name_sys, NULL, NULL, NULL);
return false; return false;
} }
else else
...@@ -534,7 +540,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const ...@@ -534,7 +540,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const
return true; return true;
if (tab->set_switch(hint_state, hint_type, true)) if (tab->set_switch(hint_state, hint_type, true))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, 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, NULL);
} }
} }
} }
...@@ -551,7 +557,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const ...@@ -551,7 +557,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const
// e.g. BKA() // e.g. BKA()
if (qb->set_switch(hint_state, hint_type, false)) if (qb->set_switch(hint_state, hint_type, false))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state,
&null_ident_sys, NULL, NULL); &null_ident_sys, NULL, NULL, NULL);
return false; return false;
} }
for (const Table_name &table : table_name_list) for (const Table_name &table : table_name_list)
...@@ -563,7 +569,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const ...@@ -563,7 +569,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const
return true; return true;
if (tab->set_switch(hint_state, hint_type, true)) if (tab->set_switch(hint_state, hint_type, true))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, 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, NULL);
} }
for (const Hint_param_table &table : opt_hint_param_table_list) for (const Hint_param_table &table : opt_hint_param_table_list)
...@@ -581,7 +587,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const ...@@ -581,7 +587,7 @@ bool Optimizer_hint_parser::Table_level_hint::resolve(Parse_context *pc) const
return true; return true;
if (tab->set_switch(hint_state, hint_type, true)) if (tab->set_switch(hint_state, hint_type, true))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, 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, NULL);
} }
} }
return false; return false;
...@@ -634,7 +640,7 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const ...@@ -634,7 +640,7 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const
{ {
if (tab->set_switch(hint_state, hint_type, false)) if (tab->set_switch(hint_state, hint_type, false))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, 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, NULL);
return false; return false;
} }
...@@ -651,7 +657,7 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const ...@@ -651,7 +657,7 @@ bool Optimizer_hint_parser::Index_level_hint::resolve(Parse_context *pc) const
if (idx->set_switch(hint_state, hint_type, true)) if (idx->set_switch(hint_state, hint_type, true))
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, 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, NULL);
} }
return false; return false;
...@@ -670,7 +676,7 @@ bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const ...@@ -670,7 +676,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 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, print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, QB_NAME_HINT_ENUM, true,
&qb_name_sys, NULL, NULL); &qb_name_sys, NULL, NULL, NULL);
return false; return false;
} }
...@@ -678,6 +684,73 @@ bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const ...@@ -678,6 +684,73 @@ bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const
return false; 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 Optimizer_hint_parser::Max_execution_time_hint::resolve(
Parse_context *pc) const
{
const long long exec_time_ms= Number::get_number();
if (exec_time_ms == -1)
{
print_warn(pc->thd, ER_WARN_BAD_MAX_EXECUTION_TIME, MAX_EXEC_TIME_HINT_ENUM,
true, NULL, NULL, NULL, NULL);
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, (ulong*)&exec_time_ms);
return false;
}
global_hint->set_switch(true, MAX_EXEC_TIME_HINT_ENUM, false);
global_hint->max_exec_time_ms= (ulong*)pc->thd->alloc(sizeof(ulong));
*global_hint->max_exec_time_ms= std::move(exec_time_ms);
global_hint->max_exec_time_select_lex= pc->select;
return false;
}
bool Opt_hints_global::resolve(THD *thd)
{
if (!max_exec_time_select_lex || is_resolved())
return false;
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
{
print_warn(thd, ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME,
MAX_EXEC_TIME_HINT_ENUM, true, NULL, NULL, NULL,
max_exec_time_ms);
}
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_ms);
}
else
{
thd->set_query_timer_force(*max_exec_time_ms * 1000);
}
set_resolved();
return false;
}
bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc) bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc)
{ {
...@@ -705,6 +778,19 @@ bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc) ...@@ -705,6 +778,19 @@ bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc)
if (qb_hint.resolve(pc)) if (qb_hint.resolve(pc))
return true; return true;
} }
else if (const Max_execution_time_hint &max_hint=
static_cast<const Max_execution_time_hint &>(*hint))
{
if (max_hint.resolve(pc))
return true;
}
} }
return false; return false;
} }
void Opt_hints_global::append_name(THD *thd, String *str)
{
if (max_exec_time_ms)
append_ulong_to_str(str, *max_exec_time_ms);
}
...@@ -48,6 +48,7 @@ enum opt_hints_enum ...@@ -48,6 +48,7 @@ enum opt_hints_enum
MRR_HINT_ENUM, MRR_HINT_ENUM,
NO_RANGE_HINT_ENUM, NO_RANGE_HINT_ENUM,
QB_NAME_HINT_ENUM, QB_NAME_HINT_ENUM,
MAX_EXEC_TIME_HINT_ENUM,
MAX_HINT_ENUM MAX_HINT_ENUM
}; };
...@@ -119,8 +120,6 @@ class Opt_hints_map : public Sql_alloc ...@@ -119,8 +120,6 @@ class Opt_hints_map : public Sql_alloc
}; };
class PT_hint;
class PT_hint_max_execution_time;
class Opt_hints_key; class Opt_hints_key;
...@@ -295,18 +294,27 @@ class Opt_hints : public Sql_alloc ...@@ -295,18 +294,27 @@ class Opt_hints : public Sql_alloc
class Opt_hints_global : public Opt_hints class Opt_hints_global : public Opt_hints
{ {
public: public:
PT_hint_max_execution_time *max_exec_time; /*
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;
/*
Value of MAX_EXECUTION_TIME() in milliseconds in the case when the hint
was provided.
Otherwise NULL.
*/
ulong *max_exec_time_ms= nullptr; // NULL if not set
Opt_hints_global(MEM_ROOT *mem_root_arg) Opt_hints_global(MEM_ROOT *mem_root_arg)
: Opt_hints(Lex_ident_sys(), NULL, mem_root_arg) : Opt_hints(Lex_ident_sys(), NULL, mem_root_arg)
{ { }
max_exec_time= NULL;
} virtual void append_name(THD *thd, String *str) override;
virtual void append_name(THD *thd, String *str) override {} bool resolve(THD *thd);
virtual PT_hint *get_complex_hints(uint type);
}; };
......
...@@ -57,12 +57,17 @@ Optimizer_hint_tokenizer::find_keyword(const LEX_CSTRING &str) ...@@ -57,12 +57,17 @@ Optimizer_hint_tokenizer::find_keyword(const LEX_CSTRING &str)
return TokenID::keyword_QB_NAME; return TokenID::keyword_QB_NAME;
break; break;
case 18:
if ("MAX_EXECUTION_TIME"_Lex_ident_column.streq(str))
return TokenID::keyword_MAX_EXECUTION_TIME;
break;
case 21: case 21:
if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str)) if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str))
return TokenID::keyword_NO_RANGE_OPTIMIZATION; return TokenID::keyword_NO_RANGE_OPTIMIZATION;
break; break;
} }
return TokenID::tIDENT; return TokenID::tIDENTorNUMBER;
} }
...@@ -86,7 +91,7 @@ Optimizer_hint_tokenizer::get_token(CHARSET_INFO *cs) ...@@ -86,7 +91,7 @@ Optimizer_hint_tokenizer::get_token(CHARSET_INFO *cs)
all identifiers in the same way in the hint parser. all identifiers in the same way in the hint parser.
*/ */
if (delimited_ident.length > 2) if (delimited_ident.length > 2)
return Token(delimited_ident, TokenID::tIDENT); return Token(delimited_ident, TokenID::tIDENTorNUMBER);
/* /*
If the string is empty, "unget" it to have a good If the string is empty, "unget" it to have a good
syntax error position in the message text. syntax error position in the message text.
...@@ -100,7 +105,7 @@ Optimizer_hint_tokenizer::get_token(CHARSET_INFO *cs) ...@@ -100,7 +105,7 @@ Optimizer_hint_tokenizer::get_token(CHARSET_INFO *cs)
const Token_with_metadata ident= get_ident(); const Token_with_metadata ident= get_ident();
if (ident.length) if (ident.length)
return Token(ident, ident.m_extended_chars ? return Token(ident, ident.m_extended_chars ?
TokenID::tIDENT : find_keyword(ident)); TokenID::tIDENTorNUMBER : find_keyword(ident));
if (!get_char(',')) if (!get_char(','))
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tCOMMA); return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tCOMMA);
if (!get_char('@')) if (!get_char('@'))
......
...@@ -72,9 +72,10 @@ class Optimizer_hint_tokenizer: public Extended_string_tokenizer ...@@ -72,9 +72,10 @@ class Optimizer_hint_tokenizer: public Extended_string_tokenizer
keyword_NO_RANGE_OPTIMIZATION, keyword_NO_RANGE_OPTIMIZATION,
keyword_MRR, keyword_MRR,
keyword_QB_NAME, keyword_QB_NAME,
keyword_MAX_EXECUTION_TIME,
// Other token types // Other token types
tIDENT tIDENTorNUMBER
}; };
class Token: public Lex_cstring class Token: public Lex_cstring
...@@ -240,7 +241,14 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer, ...@@ -240,7 +241,14 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
using TOKEN::TOKEN; using TOKEN::TOKEN;
}; };
class Identifier: public TOKEN<PARSER, TokenID::tIDENT> class Keyword_MAX_EXECUTION_TIME:
public TOKEN<PARSER, TokenID::keyword_MAX_EXECUTION_TIME>
{
public:
using TOKEN::TOKEN;
};
class Identifier: public TOKEN<PARSER, TokenID::tIDENTorNUMBER>
{ {
public: public:
using TOKEN::TOKEN; using TOKEN::TOKEN;
...@@ -258,6 +266,27 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer, ...@@ -258,6 +266,27 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
} }
}; };
class Number: public TOKEN<PARSER, TokenID::tIDENTorNUMBER>
{
public:
using TOKEN::TOKEN;
/*
Converts token string to a non-negative number ( >=0 ).
Returns the converted number if the conversion succeeds.
Returns -1 if the string conversion failed or the number is negative
*/
longlong get_number() const
{
int error;
char *end= const_cast<char *>(str + length);
longlong n= my_strtoll10(str, &end, &error);
if (error != 0 || end != str + length || n < 1 || n > INT_MAX32)
return -1;
return n;
}
};
class LParen: public TOKEN<PARSER, TokenID::tLPAREN> class LParen: public TOKEN<PARSER, TokenID::tLPAREN>
{ {
public: public:
...@@ -312,7 +341,6 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer, ...@@ -312,7 +341,6 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
using TokenChoice::TokenChoice; using TokenChoice::TokenChoice;
}; };
// Identifiers of various kinds // Identifiers of various kinds
...@@ -554,18 +582,34 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer, ...@@ -554,18 +582,34 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
}; };
// max_execution_time_hint ::= MAX_EXECUTION_TIME ( milliseconds )
class Max_execution_time_hint: public AND4<PARSER,
Keyword_MAX_EXECUTION_TIME,
LParen,
Number,
RParen>
{
public:
using AND4::AND4;
bool resolve(Parse_context *pc) const;
};
/* /*
hint ::= index_level_hint hint ::= index_level_hint
| table_level_hint | table_level_hint
| qb_name_hint | qb_name_hint
| statement_level_hint
*/ */
class Hint: public OR3<PARSER, class Hint: public OR4<PARSER,
Index_level_hint, Index_level_hint,
Table_level_hint, Table_level_hint,
Qb_name_hint> Qb_name_hint,
Max_execution_time_hint>
{ {
public: public:
using OR3::OR3; using OR4::OR4;
}; };
......
...@@ -12290,3 +12290,7 @@ ER_UNRESOLVED_TABLE_HINT_NAME ...@@ -12290,3 +12290,7 @@ ER_UNRESOLVED_TABLE_HINT_NAME
eng "Unresolved table name %s for %s hint" eng "Unresolved table name %s for %s hint"
ER_UNRESOLVED_INDEX_HINT_NAME ER_UNRESOLVED_INDEX_HINT_NAME
eng "Unresolved index name %s for %s hint" eng "Unresolved index name %s for %s hint"
ER_WARN_BAD_MAX_EXECUTION_TIME
eng "Unsupported MAX_EXECUTION_TIME"
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME
eng "MAX_EXECUTION_TIME hint is supported by top-level standalone SELECT statements only"
...@@ -345,7 +345,7 @@ class Parser_templates ...@@ -345,7 +345,7 @@ class Parser_templates
/* /*
A rule consisting of a choice of thee rules: A rule consisting of a choice of three rules:
rule ::= rule1 | rule2 | rule3 rule ::= rule1 | rule2 | rule3
For the case when the three branches have incompatible storage For the case when the three branches have incompatible storage
...@@ -443,7 +443,117 @@ class Parser_templates ...@@ -443,7 +443,117 @@ class Parser_templates
/* /*
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 rule consisting of a choice of four rules, e.g.
rule ::= rule1 | rule2 | rule3 | rule4
For the cases when the three branches have a compatible storage,
passed as a CONTAINER, which must have constructors:
CONTAINER(const A &a)
CONTAINER(const B &b)
CONTAINER(const C &c)
CONTAINER(const D &d)
*/
template<class PARSER, class CONTAINER, class A, class B, class C, class D>
class OR4C: public CONTAINER
{
public:
OR4C()
{ }
OR4C(OR4C &&rhs)
:CONTAINER(std::move(rhs))
{ }
OR4C(A &&a)
:CONTAINER(std::move(a))
{ }
OR4C(B &&b)
:CONTAINER(std::move(b))
{ }
OR4C(C &&c)
:CONTAINER(std::move(c))
{ }
OR4C & operator=(OR4C &&rhs)
{
CONTAINER::operator=(std::move(rhs));
return *this;
}
OR4C & operator=(A &&rhs)
{
CONTAINER::operator=(std::move(rhs));
return *this;
}
OR4C & operator=(B &&rhs)
{
CONTAINER::operator=(std::move(rhs));
return *this;
}
OR4C & operator=(C &&rhs)
{
CONTAINER::operator=(std::move(rhs));
return *this;
}
OR4C & operator=(D &&rhs)
{
CONTAINER::operator=(std::move(rhs));
return *this;
}
OR4C(PARSER *p)
:CONTAINER(A(p))
{
if (CONTAINER::operator bool() ||
CONTAINER::operator=(B(p)) ||
CONTAINER::operator=(C(p)) ||
CONTAINER::operator=(D(p)))
return;
DBUG_ASSERT(!CONTAINER::operator bool());
}
};
/*
A list with at least MIN_COUNT elements (typically 0 or 1),
with or without a token separator between elements: with or without a token separator between elements:
list ::= element [ {, element }... ] // with a separator list ::= element [ {, element }... ] // with a separator
......
...@@ -8431,6 +8431,8 @@ bool setup_tables(THD *thd, Name_resolution_context *context, ...@@ -8431,6 +8431,8 @@ bool setup_tables(THD *thd, Name_resolution_context *context,
if (setup_natural_join_row_types(thd, from_clause, context)) if (setup_natural_join_row_types(thd, from_clause, context))
DBUG_RETURN(1); DBUG_RETURN(1);
if (thd->lex->opt_hints_global && select_lex->select_number == 1)
thd->lex->opt_hints_global->resolve(thd);
if (qb_hints) if (qb_hints)
qb_hints->check_unresolved(thd); qb_hints->check_unresolved(thd);
DBUG_RETURN(0); DBUG_RETURN(0);
......
...@@ -5826,7 +5826,7 @@ class THD: public THD_count, /* this must be first */ ...@@ -5826,7 +5826,7 @@ class THD: public THD_count, /* this must be first */
thr_timer_t query_timer; thr_timer_t query_timer;
public: public:
void set_query_timer() void set_query_timer_if_needed()
{ {
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
/* /*
...@@ -5845,9 +5845,13 @@ class THD: public THD_count, /* this must be first */ ...@@ -5845,9 +5845,13 @@ class THD: public THD_count, /* this must be first */
*/ */
if (!timeout_val || spcont || in_sub_stmt || query_timer.expired == 0) if (!timeout_val || spcont || in_sub_stmt || query_timer.expired == 0)
return; return;
thr_timer_settime(&query_timer, timeout_val); set_query_timer_force(timeout_val);
#endif #endif
} }
void set_query_timer_force(ulonglong timeout_val)
{
thr_timer_settime(&query_timer, timeout_val);
}
void reset_query_timer() void reset_query_timer()
{ {
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
......
...@@ -3822,7 +3822,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt) ...@@ -3822,7 +3822,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
thd->query_plan_flags|= QPLAN_ADMIN; thd->query_plan_flags|= QPLAN_ADMIN;
/* Start timeouts */ /* Start timeouts */
thd->set_query_timer(); thd->set_query_timer_if_needed();
#ifdef WITH_WSREP #ifdef WITH_WSREP
/* Check wsrep_mode rules before command execution. */ /* Check wsrep_mode rules before command execution. */
......
...@@ -8765,6 +8765,7 @@ query_specification: ...@@ -8765,6 +8765,7 @@ query_specification:
opt_having_clause opt_having_clause
opt_window_clause opt_window_clause
{ {
Lex->resolve_optimizer_hints();
$$= Lex->pop_select(); $$= Lex->pop_select();
} }
; ;
...@@ -8917,7 +8918,6 @@ query_expression_body: ...@@ -8917,7 +8918,6 @@ query_expression_body:
query_simple query_simple
{ {
Lex->push_select($1); Lex->push_select($1);
Lex->resolve_optimizer_hints();
if (!($$= Lex->create_unit($1))) if (!($$= Lex->create_unit($1)))
MYSQL_YYABORT; MYSQL_YYABORT;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment