mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
1559 lines
50 KiB
C++
1559 lines
50 KiB
C++
/*
|
|
Copyright (c) 2024, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; version 2 of
|
|
the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
|
|
*/
|
|
|
|
#include "opt_hints_parser.h"
|
|
#include "lex_ident_sys.h"
|
|
#include "m_ctype.h"
|
|
#include "sql_error.h"
|
|
#include "mysqld_error.h"
|
|
#include "sql_class.h"
|
|
#include "sql_show.h"
|
|
#include "opt_hints.h"
|
|
|
|
using Parser= Optimizer_hint_parser;
|
|
|
|
extern struct st_opt_hint_info opt_hint_info[];
|
|
|
|
// Forward declaration of functions
|
|
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 Printable_parser_rule *hint);
|
|
|
|
Opt_hints_qb *get_qb_hints(Parse_context *pc);
|
|
|
|
Opt_hints_qb *find_qb_hints(Parse_context *pc,
|
|
const Lex_ident_sys &qb_name,
|
|
opt_hints_enum hint_type,
|
|
bool hint_state);
|
|
|
|
Opt_hints_global *get_global_hints(Parse_context *pc);
|
|
|
|
Opt_hints_table *get_table_hints(Parse_context *pc,
|
|
const Lex_ident_sys &table_name,
|
|
Opt_hints_qb *qb);
|
|
|
|
void append_table_name(THD *thd, String *str, const LEX_CSTRING &table_name,
|
|
const LEX_CSTRING &qb_name);
|
|
|
|
int cmp_lex_string(const LEX_CSTRING &s, const LEX_CSTRING &t,
|
|
const CHARSET_INFO *cs);
|
|
|
|
int cmp_lex_string_limit(const LEX_CSTRING &s, const LEX_CSTRING &t,
|
|
const CHARSET_INFO *cs, size_t chars_limit);
|
|
|
|
static const Lex_ident_sys null_ident_sys;
|
|
|
|
|
|
Parse_context::Parse_context(THD *thd, st_select_lex *select)
|
|
: thd(thd),
|
|
mem_root(thd->mem_root),
|
|
select(select)
|
|
{}
|
|
|
|
|
|
Parse_context::Parse_context(Parse_context *pc, st_select_lex *select)
|
|
: thd(pc->thd),
|
|
mem_root(pc->mem_root),
|
|
select(select)
|
|
{}
|
|
|
|
|
|
Optimizer_hint_tokenizer::TokenID
|
|
Optimizer_hint_tokenizer::find_keyword(const LEX_CSTRING &str)
|
|
{
|
|
switch (str.length)
|
|
{
|
|
case 3:
|
|
if ("BKA"_Lex_ident_column.streq(str)) return TokenID::keyword_BKA;
|
|
if ("BNL"_Lex_ident_column.streq(str)) return TokenID::keyword_BNL;
|
|
if ("MRR"_Lex_ident_column.streq(str)) return TokenID::keyword_MRR;
|
|
break;
|
|
|
|
case 5:
|
|
if ("MERGE"_Lex_ident_column.streq(str)) return TokenID::keyword_MERGE;
|
|
if ("INDEX"_Lex_ident_column.streq(str)) return TokenID::keyword_INDEX;
|
|
break;
|
|
|
|
case 6:
|
|
if ("NO_BKA"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_BKA;
|
|
if ("NO_BNL"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_BNL;
|
|
if ("NO_ICP"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_ICP;
|
|
if ("NO_MRR"_Lex_ident_column.streq(str)) return TokenID::keyword_NO_MRR;
|
|
break;
|
|
|
|
case 7:
|
|
if ("QB_NAME"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_QB_NAME;
|
|
break;
|
|
|
|
case 8:
|
|
if ("SEMIJOIN"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_SEMIJOIN;
|
|
if ("SUBQUERY"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_SUBQUERY;
|
|
if ("NO_MERGE"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_MERGE;
|
|
if ("NO_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_INDEX;
|
|
break;
|
|
|
|
case 9:
|
|
if ("LOOSESCAN"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_LOOSESCAN;
|
|
break;
|
|
|
|
case 10:
|
|
if ("FIRSTMATCH"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_FIRSTMATCH;
|
|
if ("INTOEXISTS"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_INTOEXISTS;
|
|
if ("JOIN_ORDER"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_JOIN_ORDER;
|
|
if ("JOIN_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_JOIN_INDEX;
|
|
break;
|
|
|
|
case 11:
|
|
if ("NO_SEMIJOIN"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_SEMIJOIN;
|
|
if ("DUPSWEEDOUT"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_DUPSWEEDOUT;
|
|
if ("JOIN_PREFIX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_JOIN_PREFIX;
|
|
if ("JOIN_SUFFIX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_JOIN_SUFFIX;
|
|
if ("ORDER_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_ORDER_INDEX;
|
|
if ("GROUP_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_GROUP_INDEX;
|
|
if ("INDEX_MERGE"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_INDEX_MERGE;
|
|
break;
|
|
|
|
case 12:
|
|
if ("ROWID_FILTER"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_ROWID_FILTER;
|
|
break;
|
|
|
|
case 13:
|
|
if ("NO_JOIN_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_JOIN_INDEX;
|
|
break;
|
|
|
|
case 14:
|
|
if ("NO_ORDER_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_ORDER_INDEX;
|
|
if ("NO_GROUP_INDEX"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_GROUP_INDEX;
|
|
if ("NO_INDEX_MERGE"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_INDEX_MERGE;
|
|
break;
|
|
|
|
case 15:
|
|
if ("MATERIALIZATION"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_MATERIALIZATION;
|
|
if ("NO_ROWID_FILTER"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_ROWID_FILTER;
|
|
break;
|
|
|
|
case 16:
|
|
if ("JOIN_FIXED_ORDER"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_JOIN_FIXED_ORDER;
|
|
break;
|
|
|
|
case 18:
|
|
if ("MAX_EXECUTION_TIME"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_MAX_EXECUTION_TIME;
|
|
if ("SPLIT_MATERIALIZED"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_SPLIT_MATERIALIZED;
|
|
break;
|
|
|
|
case 21:
|
|
if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_RANGE_OPTIMIZATION;
|
|
if ("NO_SPLIT_MATERIALIZED"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_SPLIT_MATERIALIZED;
|
|
break;
|
|
|
|
case 26:
|
|
if ("DERIVED_CONDITION_PUSHDOWN"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_DERIVED_CONDITION_PUSHDOWN;
|
|
break;
|
|
|
|
case 29:
|
|
if ("NO_DERIVED_CONDITION_PUSHDOWN"_Lex_ident_column.streq(str))
|
|
return TokenID::keyword_NO_DERIVED_CONDITION_PUSHDOWN;
|
|
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;
|
|
}
|
|
|
|
|
|
Optimizer_hint_tokenizer::Token
|
|
Optimizer_hint_tokenizer::get_token(CHARSET_INFO *cs)
|
|
{
|
|
get_spaces();
|
|
if (eof())
|
|
return Token(Lex_cstring(m_ptr, m_ptr), TokenID::tEOF);
|
|
const char head= m_ptr[0];
|
|
if (head == '`' || head=='"')
|
|
{
|
|
const Token_with_metadata delimited_ident= get_quoted_string();
|
|
/*
|
|
Consider only non-empty quoted strings as identifiers.
|
|
Table and index names cannot be empty in MariaDB.
|
|
Let's also disallow empty query block names.
|
|
Note, table aliases can actually be empty:
|
|
SELECT ``.a FROM t1 ``;
|
|
But let's disallow them in hints for simplicity, to handle
|
|
all identifiers in the same way in the hint parser.
|
|
*/
|
|
if (delimited_ident.length > 2)
|
|
return Token(delimited_ident, TokenID::tIDENT);
|
|
/*
|
|
If the string is empty, "unget" it to have a good
|
|
syntax error position in the message text.
|
|
The point is to include the empty string in the error message:
|
|
EXPLAIN EXTENDED SELECT ... QB_NAME(``) ...; -->
|
|
Optimizer hint syntax error near '``) ...' at line 1
|
|
*/
|
|
m_ptr-= delimited_ident.length;
|
|
return Token(Lex_cstring(m_ptr, m_ptr), TokenID::tNULL);
|
|
}
|
|
const Token_with_metadata ident= get_ident();
|
|
if (ident.length)
|
|
return Token(ident, ident.m_extended_chars ?
|
|
TokenID::tIDENT : find_keyword(ident));
|
|
if (!get_char(','))
|
|
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tCOMMA);
|
|
if (!get_char('.'))
|
|
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tDOT);
|
|
if (!get_char('@'))
|
|
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tAT);
|
|
if (!get_char('('))
|
|
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tLPAREN);
|
|
if (!get_char(')'))
|
|
return Token(Lex_cstring(m_ptr - 1, 1), TokenID::tRPAREN);
|
|
return Token(Lex_cstring(m_ptr, m_ptr), TokenID::tNULL);
|
|
}
|
|
|
|
|
|
// This method is for debug purposes
|
|
bool Parser::parse_token_list(THD *thd)
|
|
{
|
|
for ( ; ; m_look_ahead_token= get_token(m_cs))
|
|
{
|
|
char tmp[200];
|
|
my_snprintf(tmp, sizeof(tmp), "TOKEN: %d %.*s",
|
|
(int) m_look_ahead_token.id(),
|
|
(int) m_look_ahead_token.length,
|
|
m_look_ahead_token.str);
|
|
push_warning(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_UNKNOWN_ERROR, tmp);
|
|
if (m_look_ahead_token.id() == TokenID::tNULL ||
|
|
m_look_ahead_token.id() == TokenID::tEOF)
|
|
break;
|
|
}
|
|
return true; // Success
|
|
}
|
|
|
|
void Parser::push_warning_syntax_error(THD *thd, uint start_lineno)
|
|
{
|
|
DBUG_ASSERT(m_start <= m_ptr);
|
|
DBUG_ASSERT(m_ptr <= m_end);
|
|
const char *msg= ER_THD(thd, ER_WARN_OPTIMIZER_HINT_SYNTAX_ERROR);
|
|
ErrConvString txt(m_look_ahead_token.str, strlen(m_look_ahead_token.str),
|
|
thd->variables.character_set_client);
|
|
/*
|
|
start_lineno is the line number on which the whole hint started.
|
|
Add the line number of the current tokenizer position inside the hint
|
|
(in case hints are written in multiple lines).
|
|
*/
|
|
push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_PARSE_ERROR, ER_THD(thd, ER_PARSE_ERROR),
|
|
msg, txt.ptr(), start_lineno + lineno());
|
|
}
|
|
|
|
/*
|
|
Resolve a parsed table level hint, i.e. set up proper Opt_hint_* structures
|
|
which will be used later during query preparation and optimization.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM), break further hints processing
|
|
*/
|
|
bool Parser::Table_level_hint::resolve(Parse_context *pc) const
|
|
{
|
|
const Table_level_hint_type &table_level_hint_type= *this;
|
|
opt_hints_enum hint_type;
|
|
bool hint_state; // ON or OFF
|
|
|
|
switch (table_level_hint_type.id())
|
|
{
|
|
case TokenID::keyword_BNL:
|
|
hint_type= BNL_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_BNL:
|
|
hint_type= BNL_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_BKA:
|
|
hint_type= BKA_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_BKA:
|
|
hint_type= BKA_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_DERIVED_CONDITION_PUSHDOWN:
|
|
hint_type= DERIVED_CONDITION_PUSHDOWN_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_DERIVED_CONDITION_PUSHDOWN:
|
|
hint_type= DERIVED_CONDITION_PUSHDOWN_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_MERGE:
|
|
hint_type= MERGE_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_MERGE:
|
|
hint_type= MERGE_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_SPLIT_MATERIALIZED:
|
|
hint_type= SPLIT_MATERIALIZED_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_SPLIT_MATERIALIZED:
|
|
hint_type= SPLIT_MATERIALIZED_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return true;
|
|
}
|
|
|
|
if (const At_query_block_name_opt_table_name_list &
|
|
at_query_block_name_opt_table_name_list= *this)
|
|
{
|
|
// this is @ query_block_name opt_table_name_list
|
|
const Lex_ident_sys qb_name_sys= Query_block_name::to_ident_sys(pc->thd);
|
|
Opt_hints_qb *qb= find_qb_hints(pc, qb_name_sys, hint_type, hint_state);
|
|
if (qb == NULL)
|
|
return false;
|
|
if (at_query_block_name_opt_table_name_list.is_empty())
|
|
{
|
|
// 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, nullptr, nullptr, nullptr);
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// e.g. BKA(@qb1 t1, t2, t3)
|
|
const Opt_table_name_list &opt_table_name_list= *this;
|
|
for (const Table_name &table : opt_table_name_list)
|
|
{
|
|
const Lex_ident_sys table_name_sys= table.to_ident_sys(pc->thd);
|
|
Opt_hints_table *tab= get_table_hints(pc, table_name_sys, qb);
|
|
if (!tab)
|
|
return false;
|
|
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, nullptr,
|
|
nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// this is opt_hint_param_table_list
|
|
const Opt_hint_param_table_list &opt_hint_param_table_list= *this;
|
|
Opt_hints_qb *qb= find_qb_hints(pc, Lex_ident_sys(), hint_type, hint_state);
|
|
if (qb == NULL)
|
|
return false;
|
|
if (opt_hint_param_table_list.is_empty())
|
|
{
|
|
// 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, nullptr, nullptr, nullptr);
|
|
}
|
|
return false;
|
|
}
|
|
for (const Hint_param_table &table : opt_hint_param_table_list)
|
|
{
|
|
// e.g. BKA(t1@qb1, t2@qb2, t3)
|
|
const Lex_ident_sys qb_name_sys= table.Query_block_name::
|
|
to_ident_sys(pc->thd);
|
|
Opt_hints_qb *qb= find_qb_hints(pc, qb_name_sys, hint_type, hint_state);
|
|
if (qb == NULL)
|
|
return false;
|
|
const Lex_ident_sys table_name_sys= table.Table_name::
|
|
to_ident_sys(pc->thd);
|
|
Opt_hints_table *tab= get_table_hints(pc, table_name_sys, qb);
|
|
if (!tab)
|
|
return false;
|
|
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, nullptr, nullptr);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Resolve a parsed index level hint, i.e. set up proper Opt_hint_* structures
|
|
which will be used later during query preparation and optimization.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM), break further hints processing
|
|
|
|
Taxonomy of index hints
|
|
- 2 levels of hints:
|
|
- table level hints: only table name specified but no index names
|
|
- index level hints: both table name and index names specified
|
|
- 2 kinds of hints:
|
|
- global: [NO_]INDEX, [NO_]ROWID_FILTER
|
|
- non-global: [NO_]JOIN_INDEX, [NO_]GROUP_INDEX, [NO_]ORDER_INDEX
|
|
- 5 types of hints:
|
|
- [NO_]JOIN_INDEX
|
|
- [NO_]GROUP_INDEX
|
|
- [NO_]ORDER_INDEX
|
|
- [NO_]INDEX
|
|
- [NO_]ROWID_FILTER
|
|
|
|
Conflict checking
|
|
- A conflict happens if and only if
|
|
- for a table level hint
|
|
- a hint of the same type or opposite kind has already been specified
|
|
for the same table
|
|
- for a index level hint
|
|
- the same type of hint has already been specified for the same
|
|
table or for the same index, OR
|
|
- the opposite kind of hint has already been specified for the
|
|
same index
|
|
- For a multi index hint like JOIN_INDEX(t1 i1, i2, i3), it conflicts
|
|
with a previous hint if any of the JOIN_INDEX(t1 i1), JOIN_INDEX(t1
|
|
i2), JOIN_INDEX(t1 i3) conflicts with a previous hint
|
|
|
|
When a hint type is specified for an index, it is also marked as
|
|
specified with the same switch state for the table
|
|
*/
|
|
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;
|
|
bool hint_state; // ON or OFF
|
|
|
|
/*
|
|
Intended for the [NO_]INDEX_MERGE hints originally, this is set
|
|
true when the hint should be set also on the table as well as
|
|
the index. However, for such a hint set on the table, the check_parent flag
|
|
is inverted from this value; that is, if set_also_on_table is
|
|
true, then check_parent will be false (see below). This is
|
|
because the presence of the hint on the table is treated differently
|
|
than the hint applying to all keys on the table. For example,
|
|
INDEX_MERGE(table) means that the hint applies to all keys while
|
|
INDEX_MERGE(table, k1) means that the hint applies to key k1. In
|
|
both cases, however, we set the hint both as an index-level hint and
|
|
as a table-level hint, but when inspecting it during index_merge_hint,
|
|
it is given special treatment (see that function for details).
|
|
*/
|
|
bool set_also_on_table= false;
|
|
|
|
switch (index_level_hint_type.id())
|
|
{
|
|
case TokenID::keyword_NO_ICP:
|
|
hint_type= ICP_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_MRR:
|
|
hint_type= MRR_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_MRR:
|
|
hint_type= MRR_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_NO_RANGE_OPTIMIZATION:
|
|
hint_type= NO_RANGE_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_INDEX:
|
|
hint_type= INDEX_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_INDEX:
|
|
hint_type= INDEX_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_JOIN_INDEX:
|
|
hint_type= JOIN_INDEX_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_JOIN_INDEX:
|
|
hint_type= JOIN_INDEX_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_ORDER_INDEX:
|
|
hint_type= ORDER_INDEX_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_ORDER_INDEX:
|
|
hint_type= ORDER_INDEX_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_GROUP_INDEX:
|
|
hint_type= GROUP_INDEX_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_GROUP_INDEX:
|
|
hint_type= GROUP_INDEX_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_ROWID_FILTER:
|
|
hint_type= ROWID_FILTER_HINT_ENUM;
|
|
hint_state= true;
|
|
break;
|
|
case TokenID::keyword_NO_ROWID_FILTER:
|
|
hint_type= ROWID_FILTER_HINT_ENUM;
|
|
hint_state= false;
|
|
break;
|
|
case TokenID::keyword_INDEX_MERGE:
|
|
hint_type= INDEX_MERGE_HINT_ENUM;
|
|
hint_state= true;
|
|
set_also_on_table= true;
|
|
break;
|
|
case TokenID::keyword_NO_INDEX_MERGE:
|
|
hint_type= INDEX_MERGE_HINT_ENUM;
|
|
hint_state= false;
|
|
set_also_on_table= true;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return true;
|
|
}
|
|
|
|
const Hint_param_table_ext &table_ext= *this;
|
|
const Lex_ident_sys qb_name_sys= table_ext.Query_block_name::
|
|
to_ident_sys(pc->thd);
|
|
const Lex_ident_sys table_name_sys= table_ext.Table_name::
|
|
to_ident_sys(pc->thd);
|
|
Opt_hints_qb *qb= find_qb_hints(pc, qb_name_sys, hint_type, hint_state);
|
|
if (qb == NULL)
|
|
return false;
|
|
|
|
Opt_hints_table *tab= get_table_hints(pc, table_name_sys, qb);
|
|
if (!tab)
|
|
return false;
|
|
|
|
/*
|
|
If no index names are given, this is a table level hint, for example:
|
|
GROUP_INDEX(t1), NO_MRR(t2).
|
|
Otherwise this is a group of index-level hints:
|
|
NO_INDEX(t1 idx1, idx2) NO_ICP(t2 idx_a, idx_b, idx_c)
|
|
*/
|
|
if (is_empty() || set_also_on_table) // Table level hint
|
|
{
|
|
uint warn_code= 0;
|
|
if (is_compound_hint(hint_type) &&
|
|
is_index_hint_conflicting(tab, nullptr, hint_type))
|
|
{
|
|
warn_code= ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_TABLE;
|
|
}
|
|
else if (tab->set_switch(hint_state, hint_type, false))
|
|
{
|
|
warn_code= ER_WARN_CONFLICTING_INDEX_HINT_FOR_TABLE;
|
|
}
|
|
|
|
if (warn_code != 0)
|
|
{
|
|
print_warn(pc->thd, warn_code, hint_type, hint_state, &qb_name_sys,
|
|
&table_name_sys, nullptr, this);
|
|
}
|
|
else if (is_compound_hint(hint_type))
|
|
{
|
|
tab->get_key_hint_bitmap(hint_type)->parsed_hint= this;
|
|
}
|
|
|
|
if (is_empty())
|
|
return false;
|
|
}
|
|
|
|
// Key names for a compound hint are first collected into the array:
|
|
Mem_root_array<std::pair<Opt_hints_key *,
|
|
bool /* whether a new one was created */>>
|
|
key_hints(pc->thd->mem_root);
|
|
bool is_conflicting= false;
|
|
for (const Hint_param_index &index_name : *this)
|
|
{
|
|
const Lex_ident_sys index_name_sys= index_name.to_ident_sys(pc->thd);
|
|
bool new_opt_key_hint_created= false;
|
|
Opt_hints_key *key= (Opt_hints_key *)tab->find_by_name(index_name_sys);
|
|
if (!key)
|
|
{
|
|
key= new (pc->thd->mem_root)
|
|
Opt_hints_key(index_name_sys, tab, pc->thd->mem_root);
|
|
new_opt_key_hint_created= true;
|
|
}
|
|
|
|
if (!is_compound_hint(hint_type))
|
|
{
|
|
if (key->set_switch(hint_state, hint_type, !set_also_on_table))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_INDEX_HINT_FOR_KEY,
|
|
hint_type, hint_state, &qb_name_sys, &table_name_sys,
|
|
&index_name_sys, nullptr);
|
|
continue;
|
|
}
|
|
if (new_opt_key_hint_created)
|
|
tab->register_child(key);
|
|
}
|
|
else
|
|
{
|
|
bool is_specified= tab->is_specified(hint_type) ||
|
|
key->is_specified(hint_type);
|
|
if (is_specified || is_index_hint_conflicting(tab, key, hint_type))
|
|
{
|
|
is_conflicting= true;
|
|
uint warn_code;
|
|
if (is_specified)
|
|
{
|
|
warn_code= tab->is_specified(hint_type) ?
|
|
ER_WARN_CONFLICTING_INDEX_HINT_FOR_TABLE :
|
|
ER_WARN_CONFLICTING_INDEX_HINT_FOR_KEY;
|
|
}
|
|
else
|
|
{
|
|
warn_code= ER_WARN_CONFLICTING_COMPOUND_INDEX_HINT_FOR_KEY;
|
|
}
|
|
print_warn(pc->thd, warn_code, hint_type, hint_state,
|
|
&qb_name_sys, &table_name_sys, nullptr, this);
|
|
break;
|
|
}
|
|
key_hints.push_back({ key, new_opt_key_hint_created });
|
|
}
|
|
}
|
|
|
|
if (is_compound_hint(hint_type) && !is_conflicting)
|
|
{
|
|
/*
|
|
Process key names collected for a compound hint. They have already been
|
|
checked for conflicts/duplication above, so there is no need to examine
|
|
the `set_switch()` return value
|
|
*/
|
|
for (size_t i= 0; i < key_hints.size(); i++)
|
|
{
|
|
std::pair<Opt_hints_key *, bool> key= key_hints.at(i);
|
|
key.first->set_switch(hint_state, hint_type, true);
|
|
if (key.second)
|
|
tab->register_child(key.first);
|
|
}
|
|
|
|
tab->get_key_hint_bitmap(hint_type)->parsed_hint= this;
|
|
tab->set_switch(hint_state, hint_type, false);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void Parser::Index_level_hint::append_args(THD *thd, String *str) const
|
|
{
|
|
if (is_empty()) // Empty list of index names, no additional info
|
|
return;
|
|
|
|
bool first_index_name= true;
|
|
for (const Hint_param_index &index_name : *this)
|
|
{
|
|
if (!first_index_name)
|
|
str->append(STRING_WITH_LEN(","));
|
|
Lex_ident_sys icli= index_name.to_ident_sys(thd);
|
|
append_identifier(thd, str, &icli);
|
|
first_index_name= false;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
Check if a SELECT_LEX is a descendant of a given unit
|
|
*/
|
|
static bool is_descendant_of_unit(SELECT_LEX *sl, st_select_lex_unit *unit)
|
|
{
|
|
// Walk up the tree from sl to see if we reach unit
|
|
while (sl)
|
|
{
|
|
st_select_lex_unit *sl_unit= sl->master_unit();
|
|
if (sl_unit == unit)
|
|
return true;
|
|
// Move up to the outer SELECT
|
|
sl= sl_unit->outer_select();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
extern void push_warning_safe(THD *thd, Sql_condition::enum_warning_level level,
|
|
uint code, const char *format, ...);
|
|
|
|
/*
|
|
Resolve a parsed query block name hint, i.e. set up proper Opt_hint_*
|
|
structures which will be used later during query preparation and optimization.
|
|
|
|
@details
|
|
If the hint has form QB_NAME(new_name, path), locate the SELECT pointed by
|
|
the path and set its opt_hints_qb->name to the new_name.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM) detected, break further hints processing
|
|
*/
|
|
bool Parser::Qb_name_hint::resolve(Parse_context *pc) const
|
|
{
|
|
const opt_hints_enum hint_type= QB_NAME_HINT_ENUM;
|
|
Opt_hints_qb *target_qb= nullptr;
|
|
const Lex_ident_sys qb_name= Query_block_name::to_ident_sys(pc->thd);
|
|
|
|
const Opt_query_block_path &path= *this;
|
|
if (path.is_empty())
|
|
{
|
|
// Simple hint, e.g. QB_NAME(qb1)
|
|
target_qb= pc->select->opt_hints_qb;
|
|
}
|
|
else
|
|
{
|
|
// QB_NAME hint with path, for example QB_NAME(qb1, v1@sel_1 .@sel_2)
|
|
if (pc->thd->lex->sql_command == SQLCOM_CREATE_VIEW)
|
|
{
|
|
String str= get_hint_string(pc->thd);
|
|
push_warning_safe(
|
|
pc->thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_QB_NAME_PATH_NOT_SUPPORTED_INSIDE_VIEW,
|
|
ER_THD(pc->thd, ER_WARN_QB_NAME_PATH_NOT_SUPPORTED_INSIDE_VIEW),
|
|
str.c_ptr_safe());
|
|
return false;
|
|
}
|
|
|
|
const Query_block_path_list &qb_path_list= path;
|
|
|
|
// Start from the current SELECT_LEX
|
|
SELECT_LEX *target_select= pc->select;
|
|
// Iterate through all path elements
|
|
uint elem_num= 0;
|
|
for (const QB_path_element &elem : qb_path_list)
|
|
{
|
|
elem_num++;
|
|
Lex_ident_sys select_num_str;
|
|
Lex_ident_sys view_name;
|
|
if (const At_QB_path_element_select_num &at_select_num= elem)
|
|
{
|
|
// This path element is a SELECT_LEX number, for example @SEL_1
|
|
const QB_path_element_select_num &qb_path_select_num= at_select_num;
|
|
select_num_str= qb_path_select_num.to_ident_sys(pc->thd);
|
|
}
|
|
else if (const QB_path_element_view_sel &view_sel= elem)
|
|
{
|
|
/*
|
|
This path element is a combination of view name
|
|
and optional SELECT_LEX number, for example v1 or v1@SEL_1
|
|
*/
|
|
const QB_path_element_view_name &qb_path_view_name= view_sel;
|
|
view_name= qb_path_view_name.to_ident_sys(pc->thd);
|
|
const Opt_at_QB_path_element_select_num &opt_at_select_num= view_sel;
|
|
if (const At_QB_path_element_select_num2 &at_select_num= opt_at_select_num)
|
|
{
|
|
const QB_path_element_select_num &qb_path_select_num= at_select_num;
|
|
select_num_str= qb_path_select_num.to_ident_sys(pc->thd);
|
|
}
|
|
}
|
|
|
|
// Find SELECT_LEX corresponding to the parsed path element
|
|
if (select_num_str.length > 0)
|
|
{
|
|
// Check format and extract select number, then wind to the select_lex
|
|
const LEX_CSTRING format= { "SEL_", 4 };
|
|
if (cmp_lex_string_limit(select_num_str, format,
|
|
system_charset_info, format.length))
|
|
{
|
|
String str= get_hint_string(pc->thd);
|
|
push_warning_safe(
|
|
pc->thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_QB_NAME_PATH_INVALID_SELECT,
|
|
ER_THD(pc->thd, ER_WARN_QB_NAME_PATH_INVALID_SELECT),
|
|
str.c_ptr_safe(), elem_num);
|
|
return false;
|
|
}
|
|
|
|
const char *select_num_str_end= select_num_str.str + select_num_str.length;
|
|
char *endptr= nullptr;
|
|
int error;
|
|
long long hint_select_num=
|
|
my_strtoll10(select_num_str.str + format.length, &endptr, &error);
|
|
|
|
/*
|
|
(1) error during conversion
|
|
(2) conversion stopped before end of string
|
|
(3) select number out of valid range (1-10000)
|
|
*/
|
|
if (error != 0 || // (1)
|
|
endptr != select_num_str_end || // (2)
|
|
hint_select_num < 1 || hint_select_num > 10000) // (3)
|
|
{
|
|
String str= get_hint_string(pc->thd);
|
|
push_warning_safe(
|
|
pc->thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_QB_NAME_PATH_INVALID_SELECT,
|
|
ER_THD(pc->thd, ER_WARN_QB_NAME_PATH_INVALID_SELECT),
|
|
str.c_ptr_safe(), elem_num);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Select number in the hint is treated as a relative offset from
|
|
current unit's first_select() select_number (base select_number).
|
|
I.e., @SEL_N means: find SELECT_LEX with select_number =
|
|
base select_number + (N - 1).
|
|
@SEL_1 always points to the base SELECT_LEX, @SEL_2 - to the next
|
|
one, and so on.
|
|
*/
|
|
st_select_lex_unit *unit= target_select->master_unit();
|
|
uint base_select_num= unit->first_select()->select_number;
|
|
uint target_select_num= base_select_num + (uint) (hint_select_num - 1);
|
|
/*
|
|
Traverse the global SELECT_LEX list to find SELECT_LEX
|
|
with target_select_num
|
|
*/
|
|
target_select= nullptr;
|
|
for (SELECT_LEX *sl= pc->thd->lex->all_selects_list; sl;
|
|
sl= sl->next_select_in_list())
|
|
{
|
|
if (sl->select_number == target_select_num &&
|
|
is_descendant_of_unit(sl, unit))
|
|
{
|
|
target_select= sl;
|
|
break;
|
|
}
|
|
}
|
|
if (!target_select)
|
|
{
|
|
// Target select_number wasn't found
|
|
String str= get_hint_string(pc->thd);
|
|
push_warning_safe(
|
|
pc->thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_QB_NAME_PATH_SELECT_NOT_FOUND,
|
|
ER_THD(pc->thd, ER_WARN_QB_NAME_PATH_SELECT_NOT_FOUND),
|
|
str.c_ptr_safe(), (uint) hint_select_num, elem_num);
|
|
return false;
|
|
}
|
|
}
|
|
if (view_name.length > 0)
|
|
{
|
|
// Traverse the list of current SELECT_LEX's tables to find `view_name`
|
|
bool found= false;
|
|
for (TABLE_LIST *tbl= target_select->table_list.first; tbl;
|
|
tbl= tbl->next_local)
|
|
{
|
|
if (tbl->is_view_or_derived() &&
|
|
!cmp_lex_string(tbl->alias, view_name, system_charset_info))
|
|
{
|
|
found= true;
|
|
target_select= tbl->get_unit()->first_select();
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
String str= get_hint_string(pc->thd);
|
|
push_warning_safe(
|
|
pc->thd, Sql_condition::WARN_LEVEL_WARN,
|
|
ER_WARN_QB_NAME_PATH_VIEW_NOT_FOUND,
|
|
ER_THD(pc->thd, ER_WARN_QB_NAME_PATH_VIEW_NOT_FOUND),
|
|
str.c_ptr_safe(), view_name.str, elem_num);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
Loop is finished, and target SELECT_LEX has been successfully found.
|
|
Create Opt_hints_qb structure for it (or get if it is already created).
|
|
*/
|
|
Parse_context target_pc(pc->thd, target_select);
|
|
target_qb= get_qb_hints(&target_pc);
|
|
if (!target_qb)
|
|
return true; /* purecov: inspected */
|
|
}
|
|
|
|
DBUG_ASSERT(target_qb);
|
|
/*
|
|
(1) Name is already assigned to this query block
|
|
(2) Given name is already used inside this block of hints
|
|
*/
|
|
if (target_qb->get_name().str || // (1)
|
|
target_qb->get_parent()->find_by_name(qb_name)) // (2)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, nullptr);
|
|
return false;
|
|
}
|
|
target_qb->set_name(qb_name);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
Generate the string representation of the QB_NAME hint, for example,
|
|
"QB_NAME(`qb_v1`, `v1` .`v2`@`sel_2`)"
|
|
|
|
@return String representation of the hint
|
|
*/
|
|
String Parser::Qb_name_hint::get_hint_string(THD *thd) const
|
|
{
|
|
String str(STRING_WITH_LEN("QB_NAME("), system_charset_info);
|
|
const Lex_ident_sys qb_name= Query_block_name::to_ident_sys(thd);
|
|
append_identifier(thd, &str, qb_name.str, qb_name.length);
|
|
append_args(thd, &str);
|
|
str.append(')');
|
|
return str;
|
|
}
|
|
|
|
|
|
void Parser::Semijoin_hint::fill_strategies_map(Opt_hints_qb *qb) const
|
|
{
|
|
// Loop for hints like SEMIJOIN(firstmatch, dupsweedout)
|
|
const Hint_param_opt_sj_strategy_list &hint_param_strategy_list= *this;
|
|
for (const Semijoin_strategy &strat : hint_param_strategy_list)
|
|
add_strategy_to_map(strat.id(), qb);
|
|
|
|
// Loop for hints like SEMIJOIN(@qb1 firstmatch, dupsweedout)
|
|
const Opt_sj_strategy_list &opt_sj_strategy_list= *this;
|
|
for (const Semijoin_strategy &strat : opt_sj_strategy_list)
|
|
add_strategy_to_map(strat.id(), qb);
|
|
}
|
|
|
|
|
|
void Parser::Semijoin_hint::add_strategy_to_map(TokenID token_id,
|
|
Opt_hints_qb *qb) const
|
|
{
|
|
switch(token_id)
|
|
{
|
|
case TokenID::keyword_DUPSWEEDOUT:
|
|
qb->semijoin_strategies_map |= OPTIMIZER_SWITCH_DUPSWEEDOUT;
|
|
break;
|
|
case TokenID::keyword_FIRSTMATCH:
|
|
qb->semijoin_strategies_map |= OPTIMIZER_SWITCH_FIRSTMATCH;
|
|
break;
|
|
case TokenID::keyword_LOOSESCAN:
|
|
qb->semijoin_strategies_map |= OPTIMIZER_SWITCH_LOOSE_SCAN;
|
|
break;
|
|
case TokenID::keyword_MATERIALIZATION:
|
|
qb->semijoin_strategies_map |= OPTIMIZER_SWITCH_MATERIALIZATION;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Resolve a parsed semijoin hint, i.e. set up proper Opt_hint_* structures
|
|
which will be used later during query preparation and optimization.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM), break further hints processing
|
|
*/
|
|
bool Parser::Semijoin_hint::resolve(Parse_context *pc) const
|
|
{
|
|
const Semijoin_hint_type &semijoin_hint_type= *this;
|
|
bool hint_state; // true - SEMIJOIN(), false - NO_SEMIJOIN()
|
|
if (semijoin_hint_type.id() == TokenID::keyword_SEMIJOIN)
|
|
hint_state= true;
|
|
else
|
|
hint_state= false;
|
|
Opt_hints_qb *qb;
|
|
if (const At_query_block_name_opt_strategy_list &
|
|
at_query_block_name_opt_strategy_list __attribute__((unused)) = *this)
|
|
{
|
|
/*
|
|
This is @ query_block_name opt_strategy_list,
|
|
e.g. SEMIJOIN(@qb1) or SEMIJOIN(@qb1 firstmatch, loosescan)
|
|
*/
|
|
const Lex_ident_sys qb_name= Query_block_name::to_ident_sys(pc->thd);
|
|
qb= resolve_for_qb_name(pc, hint_state, &qb_name);
|
|
}
|
|
else
|
|
{
|
|
// This is opt_strategy_list, e.g. SEMIJOIN(loosescan, dupsweedout)
|
|
Lex_ident_sys empty_qb_name= Lex_ident_sys();
|
|
qb= resolve_for_qb_name(pc, hint_state, &empty_qb_name);
|
|
}
|
|
if (qb)
|
|
qb->semijoin_hint= this;
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
Append QB_NAME hint arguments for printing in warnings/EXPLAIN
|
|
*/
|
|
void Parser::Qb_name_hint::append_args(THD *thd, String *str) const
|
|
{
|
|
const Query_block_path &qb_path= *this;
|
|
DBUG_ASSERT(!qb_path.is_empty()); // append_args() is only valid for
|
|
// complex QB_NAME hints with path
|
|
|
|
// It is a complex QB_NAME hint with path, print the path to the query block
|
|
str->append(STRING_WITH_LEN(", "));
|
|
bool first_element= true;
|
|
for (const QB_path_element &elem : qb_path)
|
|
{
|
|
if (!first_element)
|
|
str->append(STRING_WITH_LEN(" ."));
|
|
first_element= false;
|
|
Lex_ident_sys select_num;
|
|
|
|
// Check if it's a select number or view/table name
|
|
if (const At_QB_path_element_select_num &at_select_num= elem)
|
|
{
|
|
const QB_path_element_select_num &qb_path_select_num= at_select_num;
|
|
select_num= qb_path_select_num.to_ident_sys(thd);
|
|
}
|
|
else if (const QB_path_element_view_sel &view_sel= elem)
|
|
{
|
|
const QB_path_element_view_name &qb_path_view_name= view_sel;
|
|
Lex_ident_sys view_name= qb_path_view_name.to_ident_sys(thd);
|
|
append_identifier(thd, str, &view_name);
|
|
|
|
const Opt_at_QB_path_element_select_num &opt_at_select_num= view_sel;
|
|
if (const At_QB_path_element_select_num2 &at_select_num=
|
|
opt_at_select_num)
|
|
{
|
|
const QB_path_element_select_num &qb_path_select_num= at_select_num;
|
|
select_num= qb_path_select_num.to_ident_sys(thd);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DBUG_ASSERT(false); /* purecov: deadcode */
|
|
}
|
|
if (select_num.length > 0)
|
|
{
|
|
str->append(STRING_WITH_LEN("@"));
|
|
append_identifier(thd, str, &select_num);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
Helper function to be called by Semijoin_hint::resolve().
|
|
|
|
@return pointer to Opt_hints_qb if the hint was resolved successfully
|
|
NULL if the hint was ignored
|
|
*/
|
|
Opt_hints_qb* Parser::Semijoin_hint::
|
|
resolve_for_qb_name(Parse_context *pc, bool hint_state,
|
|
const Lex_ident_sys *qb_name) const
|
|
{
|
|
Opt_hints_qb *qb= find_qb_hints(pc, *qb_name, SEMIJOIN_HINT_ENUM, hint_state);
|
|
if (!qb)
|
|
return nullptr;
|
|
if (qb->subquery_hint)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, SEMIJOIN_HINT_ENUM,
|
|
hint_state, qb_name, nullptr, nullptr, this);
|
|
return nullptr;
|
|
}
|
|
if (qb->set_switch(hint_state, SEMIJOIN_HINT_ENUM, false))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, SEMIJOIN_HINT_ENUM,
|
|
hint_state, qb_name, nullptr, nullptr, this);
|
|
return nullptr;
|
|
}
|
|
fill_strategies_map(qb);
|
|
return qb;
|
|
}
|
|
|
|
|
|
void Parser::Semijoin_hint::append_args(THD *thd, String *str) const
|
|
{
|
|
// Loop for hints without query block name, e.g. SEMIJOIN(firstmatch, dupsweedout)
|
|
const Hint_param_opt_sj_strategy_list &hint_param_strategy_list= *this;
|
|
uint32 len_before= str->length();
|
|
for (const Semijoin_strategy &strat : hint_param_strategy_list)
|
|
{
|
|
if (str->length() > len_before)
|
|
str->append(STRING_WITH_LEN(", "));
|
|
append_strategy_name(strat.id(), str);
|
|
}
|
|
|
|
// Loop for hints with query block name, e.g. SEMIJOIN(@qb1 firstmatch, dupsweedout)
|
|
const Opt_sj_strategy_list &opt_sj_strategy_list= *this;
|
|
for (const Semijoin_strategy &strat : opt_sj_strategy_list)
|
|
{
|
|
if (str->length() > len_before)
|
|
str->append(STRING_WITH_LEN(", "));
|
|
append_strategy_name(strat.id(), str);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::Semijoin_hint::
|
|
append_strategy_name(TokenID token_id, String *str) const
|
|
{
|
|
switch(token_id)
|
|
{
|
|
case TokenID::keyword_DUPSWEEDOUT:
|
|
str->append(STRING_WITH_LEN("DUPSWEEDOUT"));
|
|
break;
|
|
case TokenID::keyword_FIRSTMATCH:
|
|
str->append(STRING_WITH_LEN("FIRSTMATCH"));
|
|
break;
|
|
case TokenID::keyword_LOOSESCAN:
|
|
str->append(STRING_WITH_LEN("LOOSESCAN"));
|
|
break;
|
|
case TokenID::keyword_MATERIALIZATION:
|
|
str->append(STRING_WITH_LEN("MATERIALIZATION"));
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
Resolve a parsed subquery hint, i.e. set up proper Opt_hint_* structures
|
|
which will be used later during query preparation and optimization.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM), break further hints processing
|
|
*/
|
|
bool Parser::Subquery_hint::resolve(Parse_context *pc) const
|
|
{
|
|
Opt_hints_qb *qb;
|
|
if (const At_query_block_name_subquery_strategy &
|
|
at_query_block_name_subquery_strategy= *this)
|
|
{
|
|
/*
|
|
This is @ query_block_name subquery_strategy,
|
|
e.g. SUBQUERY(@qb1 INTOEXISTS)
|
|
*/
|
|
const Lex_ident_sys qb_name= Query_block_name::to_ident_sys(pc->thd);
|
|
const Subquery_strategy &strat= at_query_block_name_subquery_strategy;
|
|
qb= resolve_for_qb_name(pc, strat.id(), &qb_name);
|
|
}
|
|
else
|
|
{
|
|
// This is subquery_strategy, e.g. SUBQUERY(MATERIALIZATION)
|
|
Lex_ident_sys empty_qb_name= Lex_ident_sys();
|
|
const Hint_param_subquery_strategy &strat= *this;
|
|
qb= resolve_for_qb_name(pc, strat.id(), &empty_qb_name);
|
|
}
|
|
if (qb)
|
|
qb->subquery_hint= this;
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
Helper function to be called by Subquery_hint::resolve().
|
|
|
|
@return pointer to Opt_hints_qb if the hint was resolved successfully
|
|
NULL if the hint was ignored
|
|
*/
|
|
Opt_hints_qb* Parser::Subquery_hint::
|
|
resolve_for_qb_name(Parse_context *pc, TokenID token_id,
|
|
const Lex_ident_sys *qb_name) const
|
|
{
|
|
Opt_hints_qb *qb= find_qb_hints(pc, *qb_name, SUBQUERY_HINT_ENUM, true);
|
|
if (!qb)
|
|
return nullptr;
|
|
if (qb->semijoin_hint)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, SUBQUERY_HINT_ENUM,
|
|
true, qb_name, nullptr, nullptr, this);
|
|
return nullptr;
|
|
}
|
|
if (qb->set_switch(true, SUBQUERY_HINT_ENUM, false))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, SUBQUERY_HINT_ENUM,
|
|
true, qb_name, nullptr, nullptr, this);
|
|
return nullptr;
|
|
}
|
|
set_subquery_strategy(token_id, qb);
|
|
return qb;
|
|
}
|
|
|
|
|
|
void Parser::Subquery_hint::set_subquery_strategy(TokenID token_id,
|
|
Opt_hints_qb *qb) const
|
|
{
|
|
switch(token_id)
|
|
{
|
|
case TokenID::keyword_INTOEXISTS:
|
|
qb->subquery_strategy= SUBS_IN_TO_EXISTS;
|
|
break;
|
|
case TokenID::keyword_MATERIALIZATION:
|
|
qb->subquery_strategy= SUBS_MATERIALIZATION;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
void Parser::Subquery_hint::append_args(THD *thd, String *str) const
|
|
{
|
|
TokenID token_id;
|
|
if (const At_query_block_name_subquery_strategy &
|
|
at_query_block_name_subquery_strategy= *this)
|
|
{
|
|
const Subquery_strategy &strat= at_query_block_name_subquery_strategy;
|
|
token_id= strat.id();
|
|
}
|
|
else
|
|
{
|
|
const Hint_param_subquery_strategy& hint_param_strat= *this;
|
|
token_id= hint_param_strat.id();
|
|
}
|
|
|
|
switch(token_id)
|
|
{
|
|
case TokenID::keyword_INTOEXISTS:
|
|
str->append(STRING_WITH_LEN("INTOEXISTS"));
|
|
break;
|
|
case TokenID::keyword_MATERIALIZATION:
|
|
str->append(STRING_WITH_LEN("MATERIALIZATION"));
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
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::Join_order_hint::append_args(THD *thd, String *str) const
|
|
{
|
|
bool first_table_name= true;
|
|
for (const Parser::Table_name_and_Qb& tbl : table_names)
|
|
{
|
|
if (!first_table_name)
|
|
str->append(STRING_WITH_LEN(","));
|
|
append_table_name(thd, str, tbl.table_name, tbl.qb_name);
|
|
first_table_name= false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Resolve a parsed join order hint, i.e. set up proper Opt_hint_* structures
|
|
which will be used later during query preparation and optimization.
|
|
|
|
@return false: no critical errors, warnings on duplicated hints,
|
|
unresolved query block names, etc. are allowed
|
|
true: critical error (like OOM), break further hints processing
|
|
*/
|
|
bool Parser::Join_order_hint::resolve(Parse_context *pc)
|
|
{
|
|
const Join_order_hint_type &join_order_hint_type= *this;
|
|
|
|
switch (join_order_hint_type.id())
|
|
{
|
|
case TokenID::keyword_JOIN_FIXED_ORDER:
|
|
hint_type= JOIN_FIXED_ORDER_HINT_ENUM;
|
|
break;
|
|
case TokenID::keyword_JOIN_ORDER:
|
|
hint_type= JOIN_ORDER_HINT_ENUM;
|
|
break;
|
|
case TokenID::keyword_JOIN_PREFIX:
|
|
hint_type= JOIN_PREFIX_HINT_ENUM;
|
|
break;
|
|
case TokenID::keyword_JOIN_SUFFIX:
|
|
hint_type= JOIN_SUFFIX_HINT_ENUM;
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return true;
|
|
}
|
|
|
|
Opt_hints_qb *qb= nullptr;
|
|
Lex_ident_sys qb_name;
|
|
const Table_level_hint_body_extended_container &body= *this;
|
|
if (body.m_qb_name)
|
|
{
|
|
/*
|
|
This is @ query_block_name opt_table_name_ext_list,
|
|
for example JOIN_PREFIX(@q1 t3, t2, t2@subq2)
|
|
*/
|
|
qb_name= static_cast<const Query_block_name&>(body.m_qb_name).to_ident_sys(pc->thd);
|
|
qb= find_qb_hints(pc, qb_name, hint_type, true);
|
|
for (const Table_name_ext &table : body.m_tables)
|
|
{
|
|
Parser::Table_name_and_Qb *tbl_qb= new (pc->mem_root)
|
|
Parser::Table_name_and_Qb(table.Table_name::to_ident_sys(pc->thd),
|
|
table.Query_block_name::to_ident_sys(pc->thd));
|
|
if (!tbl_qb)
|
|
return true;
|
|
table_names.push_back(tbl_qb, pc->mem_root);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// global QB name is not specified, for example JOIN_ORDER(t3, t2, t2@subq2)
|
|
qb= find_qb_hints(pc, Lex_ident_sys(), hint_type, true);
|
|
for (const Table_name_ext &table : body.m_tables)
|
|
{
|
|
// e.g. JOIN_ORDER(t1@qb1, t2@qb2, t3)
|
|
Parser::Table_name_and_Qb *tbl_qb=
|
|
new (pc->mem_root) Parser::Table_name_and_Qb(
|
|
table.Table_name::to_ident_sys(pc->thd),
|
|
table.Query_block_name::to_ident_sys(pc->thd));
|
|
table_names.push_back(tbl_qb, pc->mem_root);
|
|
}
|
|
}
|
|
|
|
if (qb == nullptr)
|
|
return false;
|
|
|
|
if ((hint_type != JOIN_FIXED_ORDER_HINT_ENUM && table_names.is_empty()) ||
|
|
(hint_type == JOIN_FIXED_ORDER_HINT_ENUM && !table_names.is_empty()))
|
|
{
|
|
/*
|
|
Skipping table name(s) only allowed and required for the
|
|
JOIN_FIXED_ORDER hint and is not allowed for other hint types
|
|
*/
|
|
print_warn(pc->thd, ER_WARN_MALFORMED_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
|
|
if (hint_type == JOIN_FIXED_ORDER_HINT_ENUM)
|
|
{
|
|
/*
|
|
This is JOIN_ORDER_FIXED() or JOIN_ORDER_FIXED(@qb1)
|
|
There can be only one JOIN_ORDER_FIXED hint in a query block,
|
|
other hints are not allowed in this case
|
|
*/
|
|
if (qb->has_join_order_hints()|| qb->join_fixed_order)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
qb->join_fixed_order= this;
|
|
qb->set_switch(true, hint_type, false);
|
|
pc->select->options |= SELECT_STRAIGHT_JOIN;
|
|
return false;
|
|
}
|
|
|
|
// Finished with processing of JOIN_FIXED_ORDER()
|
|
DBUG_ASSERT(hint_type != JOIN_FIXED_ORDER_HINT_ENUM);
|
|
/*
|
|
Hints except JOIN_ORDER() must not duplicate. If there is JOIN_ORDER_FIXED()
|
|
already, then other hints are not allowed for this query block
|
|
*/
|
|
if ((qb->get_switch(hint_type) && hint_type != JOIN_ORDER_HINT_ENUM) ||
|
|
qb->join_fixed_order)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
|
|
switch(hint_type)
|
|
{
|
|
case JOIN_PREFIX_HINT_ENUM:
|
|
if (qb->join_prefix || qb->add_join_order_hint(this))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
qb->join_prefix= this;
|
|
qb->set_switch(true, JOIN_PREFIX_HINT_ENUM, false);
|
|
break;
|
|
case JOIN_SUFFIX_HINT_ENUM:
|
|
if (qb->join_suffix || qb->add_join_order_hint(this))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
qb->join_suffix= this;
|
|
qb->set_switch(true, JOIN_SUFFIX_HINT_ENUM, false);
|
|
break;
|
|
case JOIN_ORDER_HINT_ENUM:
|
|
// Multiple JOIN_ORDER() hints are allowed
|
|
if (qb->add_join_order_hint(this))
|
|
{
|
|
print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, true,
|
|
&qb_name, nullptr, nullptr, this);
|
|
return false;
|
|
}
|
|
qb->set_switch(true, JOIN_ORDER_HINT_ENUM, false);
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
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());
|
|
}
|
|
|
|
|
|
ulonglong Parser::Max_execution_time_hint::get_milliseconds() const
|
|
{
|
|
const Unsigned_Number& hint_arg= *this;
|
|
return hint_arg.get_ulonglong().value();
|
|
}
|
|
|
|
|
|
bool Parser::Hint_list::resolve(Parse_context *pc) const
|
|
{
|
|
if (!get_qb_hints(pc))
|
|
return true; /* purecov: inspected */
|
|
|
|
/*
|
|
QB_NAME hints are resolved first so following hints can be attached to
|
|
the pre-configured query blocks
|
|
*/
|
|
for (Hint_list::iterator li= this->begin(); li != this->end(); ++li)
|
|
{
|
|
Parser::Hint &hint= *li;
|
|
if (const Qb_name_hint &qb_hint= hint)
|
|
{
|
|
if (qb_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
for (Hint_list::iterator li= this->begin(); li != this->end(); ++li)
|
|
{
|
|
Parser::Hint &hint= *li;
|
|
if (const Table_level_hint &table_hint= hint)
|
|
{
|
|
if (table_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (const Index_level_hint &index_hint= hint)
|
|
{
|
|
if (index_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (const Max_execution_time_hint &max_hint= hint)
|
|
{
|
|
if (max_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (const Semijoin_hint &sj_hint= hint)
|
|
{
|
|
if (sj_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (const Subquery_hint &subq_hint= hint)
|
|
{
|
|
if (subq_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (Join_order_hint &join_order_hint= hint)
|
|
{
|
|
if (join_order_hint.resolve(pc))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
else if (const Qb_name_hint &qb_hint __attribute__((unused)) = hint)
|
|
{
|
|
// QB_NAME hints have been resolved earlier
|
|
continue;
|
|
}
|
|
else {
|
|
DBUG_ASSERT(0); /* purecov: deadcode */
|
|
}
|
|
}
|
|
return false;
|
|
}
|