StoneDB/sql/opt_hints.cc

408 lines
11 KiB
C++

/* Copyright (c) 2015, 2021, Oracle and/or its affiliates.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
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, version 2.0, 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-1301 USA */
#include "parse_tree_hints.h"
#include "sql_class.h"
#include "mysqld.h" // table_alias_charset
#include "sql_lex.h"
/**
Information about hints. Sould be
synchronized with opt_hints_enum enum.
Note: Hint name depends on hint state. 'NO_' prefix is added
if appropriate hint state bit(see Opt_hints_map::hints) is not
set. Depending on 'switch_state_arg' argument in 'parse tree
object' constructors(see parse_tree_hints.[h,cc]) implementor
can control wishful form of the hint name.
*/
struct st_opt_hint_info opt_hint_info[]=
{
{"BKA", true, true},
{"BNL", true, true},
{"ICP", true, true},
{"MRR", true, true},
{"NO_RANGE_OPTIMIZATION", true, true},
{"MAX_EXECUTION_TIME", false, false},
{"QB_NAME", false, false},
{"SEMIJOIN", false, false},
{"SUBQUERY", false, false},
{0, 0, 0}
};
/**
Prefix for system generated query block name.
Used in information warning in EXPLAIN oputput.
*/
const LEX_CSTRING sys_qb_prefix= {"select#", 7};
/*
Compare LEX_CSTRING objects.
@param s Pointer to LEX_CSTRING
@param t Pointer to LEX_CSTRING
@param cs Pointer to character set
@return 0 if strings are equal
1 if s is greater
-1 if t is greater
*/
static int cmp_lex_string(const LEX_CSTRING *s,
const LEX_CSTRING *t,
const CHARSET_INFO *cs)
{
return cs->coll->strnncollsp(cs,
(uchar *) s->str, s->length,
(uchar *) t->str, t->length, 0);
}
bool Opt_hints::get_switch(opt_hints_enum type_arg) const
{
if (is_specified(type_arg))
return hints_map.switch_on(type_arg);
if (opt_hint_info[type_arg].check_upper_lvl)
return parent->get_switch(type_arg);
return false;
}
Opt_hints* Opt_hints::find_by_name(const LEX_CSTRING *name_arg,
const CHARSET_INFO *cs) const
{
for (uint i= 0; i < child_array.size(); i++)
{
const LEX_CSTRING *name= child_array[i]->get_name();
if (name && !cmp_lex_string(name, name_arg, cs))
return child_array[i];
}
return NULL;
}
void Opt_hints::print(THD *thd, String *str, enum_query_type query_type)
{
for (uint i= 0; i < MAX_HINT_ENUM; i++)
{
opt_hints_enum hint= static_cast<opt_hints_enum>(i);
/*
If printing a normalized query, also unresolved hints will be printed.
(This is needed by query rewrite plugins which request
normalized form before resolving has been performed.)
*/
if (is_specified(hint) &&
(is_resolved() || query_type == QT_NORMALIZED_FORMAT))
{
append_hint_type(str, hint);
str->append(STRING_WITH_LEN("("));
append_name(thd, str);
if (!opt_hint_info[i].switch_hint)
get_complex_hints(hint)->append_args(thd, str);
str->append(STRING_WITH_LEN(") "));
}
}
for (uint i= 0; i < child_array.size(); i++)
child_array[i]->print(thd, str, query_type);
}
void Opt_hints::append_hint_type(String *str, opt_hints_enum type)
{
const char* hint_name= opt_hint_info[type].hint_name;
if(!hints_map.switch_on(type))
str->append(STRING_WITH_LEN("NO_"));
str->append(hint_name);
}
void Opt_hints::print_warn_unresolved(THD *thd)
{
String hint_name_str, hint_type_str;
append_name(thd, &hint_name_str);
for (uint i= 0; i < MAX_HINT_ENUM; i++)
{
if (is_specified(static_cast<opt_hints_enum>(i)))
{
hint_type_str.length(0);
append_hint_type(&hint_type_str, static_cast<opt_hints_enum>(i));
push_warning_printf(thd, Sql_condition::SL_WARNING,
ER_UNRESOLVED_HINT_NAME,
ER_THD(thd, ER_UNRESOLVED_HINT_NAME),
hint_name_str.c_ptr_safe(),
hint_type_str.c_ptr_safe());
}
}
}
void Opt_hints::check_unresolved(THD *thd)
{
if (!is_resolved())
print_warn_unresolved(thd);
if (!is_all_resolved())
{
for (uint i= 0; i < child_array.size(); i++)
child_array[i]->check_unresolved(thd);
}
}
PT_hint *Opt_hints_global::get_complex_hints(opt_hints_enum type)
{
if (type == MAX_EXEC_TIME_HINT_ENUM)
return max_exec_time;
assert(0);
return NULL;
}
Opt_hints_qb::Opt_hints_qb(Opt_hints *opt_hints_arg,
MEM_ROOT *mem_root_arg,
uint select_number_arg)
: Opt_hints(NULL, opt_hints_arg, mem_root_arg),
select_number(select_number_arg), subquery_hint(NULL), semijoin_hint(NULL)
{
sys_name.str= buff;
sys_name.length= my_snprintf(buff, sizeof(buff), "%s%lx",
sys_qb_prefix.str, select_number);
}
PT_hint *Opt_hints_qb::get_complex_hints(opt_hints_enum type)
{
if (type == SEMIJOIN_HINT_ENUM)
return semijoin_hint;
if (type == SUBQUERY_HINT_ENUM)
return subquery_hint;
assert(0);
return NULL;
}
Opt_hints_table *Opt_hints_qb::adjust_table_hints(TABLE *table,
const char *alias)
{
const LEX_CSTRING str= { alias, strlen(alias) };
Opt_hints_table *tab=
static_cast<Opt_hints_table *>(find_by_name(&str, table_alias_charset));
table->pos_in_table_list->opt_hints_qb= this;
if (!tab) // Tables not found
return NULL;
tab->adjust_key_hints(table);
return tab;
}
bool Opt_hints_qb::semijoin_enabled(THD *thd) const
{
if (subquery_hint) // SUBQUERY hint disables semi-join
return false;
if (semijoin_hint)
{
// SEMIJOIN hint will always force semijoin regardless of optimizer_switch
if (semijoin_hint->switch_on())
return true;
// NO_SEMIJOIN hint. If strategy list is empty, do not use SEMIJOIN
if (semijoin_hint->get_args() == 0)
return false;
// Fall through: NO_SEMIJOIN w/ strategies neither turns SEMIJOIN off nor on
}
return thd->optimizer_switch_flag(OPTIMIZER_SWITCH_SEMIJOIN);
}
uint Opt_hints_qb::sj_enabled_strategies(uint opt_switches) const
{
// Hints override switches
if (semijoin_hint)
{
const uint strategies= semijoin_hint->get_args();
if (semijoin_hint->switch_on()) // SEMIJOIN hint
return (strategies == 0) ? opt_switches : strategies;
// NO_SEMIJOIN hint. Hints and optimizer_switch both affect strategies
return ~strategies & opt_switches;
}
return opt_switches;
}
Item_exists_subselect::enum_exec_method
Opt_hints_qb::subquery_strategy() const
{
if (subquery_hint)
return static_cast<Item_exists_subselect::enum_exec_method>
(subquery_hint->get_args());
return Item_exists_subselect::EXEC_UNSPECIFIED;
}
void Opt_hints_table::adjust_key_hints(TABLE *table)
{
set_resolved();
if (child_array_ptr()->size() == 0) // No key level hints
{
get_parent()->incr_resolved_children();
return;
}
/*
Make sure that adjustement is done only once.
Table has already been processed if keyinfo_array is not empty.
*/
if (keyinfo_array.size())
return;
keyinfo_array.resize(table->s->keys, NULL);
for (Opt_hints** hint= child_array_ptr()->begin();
hint < child_array_ptr()->end(); ++hint)
{
KEY *key_info= table->key_info;
for (uint j= 0 ; j < table->s->keys ; j++, key_info++)
{
const LEX_CSTRING key_name= { key_info->name, strlen(key_info->name) };
if (!cmp_lex_string((*hint)->get_name(), &key_name, system_charset_info))
{
(*hint)->set_resolved();
keyinfo_array[j]= static_cast<Opt_hints_key *>(*hint);
incr_resolved_children();
}
}
}
/*
Do not increase number of resolved tables
if there are unresolved key objects. It's
important for check_unresolved() function.
*/
if (is_all_resolved())
get_parent()->incr_resolved_children();
}
/**
Function returns hint value depending on
the specfied hint level. If hint is specified
on current level, current level hint value is
returned, otherwise parent level hint is checked.
@param hint Pointer to the hint object
@param parent_hint Pointer to the parent hint object,
should never be NULL
@param type_arg hint type
@param OUT ret_val hint value depending on
what hint level is used
@return true if hint is specified, false otherwise
*/
static bool get_hint_state(Opt_hints *hint,
Opt_hints *parent_hint,
opt_hints_enum type_arg,
bool *ret_val)
{
assert(parent_hint);
if (opt_hint_info[type_arg].switch_hint)
{
if (hint && hint->is_specified(type_arg))
{
*ret_val= hint->get_switch(type_arg);
return true;
}
else if (opt_hint_info[type_arg].check_upper_lvl &&
parent_hint->is_specified(type_arg))
{
*ret_val= parent_hint->get_switch(type_arg);
return true;
}
}
else
{
/* Complex hint, not implemented atm */
assert(0);
}
return false;
}
bool hint_key_state(const THD *thd, const TABLE *table,
uint keyno, opt_hints_enum type_arg,
uint optimizer_switch)
{
Opt_hints_table *table_hints= table->pos_in_table_list->opt_hints_table;
/* Parent should always be initialized */
if (table_hints && keyno != MAX_KEY)
{
Opt_hints_key *key_hints= table_hints->keyinfo_array.size() > 0 ?
table_hints->keyinfo_array[keyno] : NULL;
bool ret_val= false;
if (get_hint_state(key_hints, table_hints, type_arg, &ret_val))
return ret_val;
}
return thd->optimizer_switch_flag(optimizer_switch);
}
bool hint_table_state(const THD *thd, const TABLE *table,
opt_hints_enum type_arg,
uint optimizer_switch)
{
TABLE_LIST *table_list= table->pos_in_table_list;
if (table_list->opt_hints_qb)
{
bool ret_val= false;
if (get_hint_state(table_list->opt_hints_table,
table_list->opt_hints_qb,
type_arg, &ret_val))
return ret_val;
}
return thd->optimizer_switch_flag(optimizer_switch);
}