mirror of
https://github.com/MariaDB/server.git
synced 2025-08-18 16:31:36 +02:00

When a hint has a list of index names, for example, `NO_INDEX(t1 idx1, idx2)` there is a possibility that some or all of the listed index names will not be resolved. If none of them are resolved, the hint becomes a table-level hint, for example, `NO_INDEX(t1)`, which erroneously disables all indexes of `t1` instead of disabling only some of them. This commit addresses this issue by adding an additional check: a hint containing a list of index names is considered resolved only when at least one of the listed names is resolved successfully.
1400 lines
42 KiB
C++
1400 lines
42 KiB
C++
/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
|
|
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-1301 USA */
|
|
|
|
#include "my_global.h"
|
|
#include "sql_class.h"
|
|
#include "sql_lex.h"
|
|
#include "sql_select.h"
|
|
#include "opt_hints.h"
|
|
|
|
/**
|
|
Information about hints. Must be in sync with opt_hints_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_h../cnm ints.[h,cc]) implementor
|
|
can control wishful form of the hint name.
|
|
*/
|
|
|
|
struct st_opt_hint_info opt_hint_info[]=
|
|
{
|
|
{{STRING_WITH_LEN("BKA")}, true, false, false},
|
|
{{STRING_WITH_LEN("BNL")}, true, false, false},
|
|
{{STRING_WITH_LEN("ICP")}, true, false, false},
|
|
{{STRING_WITH_LEN("MRR")}, true, false, false},
|
|
{{STRING_WITH_LEN("NO_RANGE_OPTIMIZATION")}, true, false, false},
|
|
{{STRING_WITH_LEN("QB_NAME")}, false, false, false},
|
|
{{STRING_WITH_LEN("MAX_EXECUTION_TIME")}, false, true, false},
|
|
{{STRING_WITH_LEN("SEMIJOIN")}, false, true, false},
|
|
{{STRING_WITH_LEN("SUBQUERY")}, false, true, false},
|
|
{{STRING_WITH_LEN("JOIN_PREFIX")}, false, true, true},
|
|
{{STRING_WITH_LEN("JOIN_SUFFIX")}, false, true, true},
|
|
{{STRING_WITH_LEN("JOIN_ORDER")}, false, true, true},
|
|
{{STRING_WITH_LEN("JOIN_FIXED_ORDER")}, false, true, false},
|
|
{{STRING_WITH_LEN("DERIVED_CONDITION_PUSHDOWN")}, false, false, false},
|
|
{{STRING_WITH_LEN("MERGE")}, false, false, false},
|
|
{{STRING_WITH_LEN("SPLIT_MATERIALIZED")}, false, false, false},
|
|
{{STRING_WITH_LEN("INDEX")}, false, true, false},
|
|
{{STRING_WITH_LEN("JOIN_INDEX")}, false, true, false},
|
|
{{STRING_WITH_LEN("GROUP_INDEX")}, false, true, false},
|
|
{{STRING_WITH_LEN("ORDER_INDEX")}, false, true, false},
|
|
{null_clex_str, 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};
|
|
|
|
|
|
/*
|
|
This is a version of push_warning_printf() guaranteeing no escalation of
|
|
the warning to the level of error
|
|
*/
|
|
void push_warning_safe(THD *thd, Sql_condition::enum_warning_level level,
|
|
uint code, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
DBUG_ENTER("push_warning_safe");
|
|
DBUG_PRINT("enter",("warning: %u", code));
|
|
|
|
va_start(args,format);
|
|
bool save_abort_on_warning= thd->abort_on_warning;
|
|
thd->abort_on_warning= false; // Don't escalate to the level of error
|
|
push_warning_printf_va_list(thd, level,code, format, args);
|
|
va_end(args);
|
|
thd->abort_on_warning= save_abort_on_warning;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*
|
|
Function prepares and prints a warning message related to hints parsing or
|
|
resolving.
|
|
|
|
@param thd Pointer to THD object for session.
|
|
err_code Enumerated code of the warning
|
|
hint_type Enumerated hint type
|
|
hint_state true: enabling hint (HINT(...),
|
|
false: disabling (NO_HINT(...))
|
|
qb_name_arg optional query block name
|
|
table_name_arg optional table name
|
|
key_name_arg optional key (index) name
|
|
hint optional pointer to the parsed hint object
|
|
add_info optional string, if given then will be appended to
|
|
the end of the message embraced in brackets
|
|
*/
|
|
|
|
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)
|
|
{
|
|
String str;
|
|
|
|
/* Append hint name */
|
|
if (!hint_state)
|
|
str.append(STRING_WITH_LEN("NO_"));
|
|
str.append(opt_hint_info[hint_type].hint_type);
|
|
|
|
/* ER_WARN_UNKNOWN_QB_NAME with two arguments */
|
|
if (err_code == ER_WARN_UNKNOWN_QB_NAME)
|
|
{
|
|
String qb_name_str;
|
|
append_identifier(thd, &qb_name_str, qb_name_arg->str, qb_name_arg->length);
|
|
push_warning_safe(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
err_code, ER_THD(thd, err_code),
|
|
qb_name_str.c_ptr_safe(), str.c_ptr_safe());
|
|
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_safe(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('(');
|
|
|
|
/* Append table name */
|
|
if (table_name_arg && table_name_arg->length > 0)
|
|
append_identifier(thd, &str, table_name_arg->str, table_name_arg->length);
|
|
|
|
/* Append QB name */
|
|
bool got_qb_name= qb_name_arg && qb_name_arg->length > 0;
|
|
if (got_qb_name)
|
|
{
|
|
if (hint_type != QB_NAME_HINT_ENUM)
|
|
{
|
|
/*
|
|
Add the delimiter for warnings like "Hint NO_ICP(`t1`@`q1` is ignored".
|
|
No need for the delimiter for warnings "Hint QB_NAME(qb1) is ignored"
|
|
*/
|
|
str.append(STRING_WITH_LEN("@"));
|
|
}
|
|
append_identifier(thd, &str, qb_name_arg->str, qb_name_arg->length);
|
|
}
|
|
|
|
/* Append key name */
|
|
if (key_name_arg && key_name_arg->length > 0)
|
|
{
|
|
str.append(' ');
|
|
append_identifier(thd, &str, key_name_arg->str, key_name_arg->length);
|
|
}
|
|
|
|
/* Append additional hint arguments if they exist */
|
|
if (hint)
|
|
{
|
|
if (got_qb_name || table_name_arg || key_name_arg)
|
|
str.append(' ');
|
|
|
|
hint->append_args(thd, &str);
|
|
}
|
|
str.append(')');
|
|
|
|
push_warning_safe(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
err_code, ER_THD(thd, err_code), str.c_ptr_safe());
|
|
}
|
|
|
|
|
|
/**
|
|
Returns a pointer to Opt_hints_global object,
|
|
creates Opt_hints object if not exist.
|
|
|
|
@param pc pointer to Parse_context object
|
|
|
|
@return pointer to Opt_hints object,
|
|
NULL if failed to create the object
|
|
*/
|
|
|
|
Opt_hints_global *get_global_hints(Parse_context *pc)
|
|
{
|
|
LEX *lex= pc->thd->lex;
|
|
|
|
if (!lex->opt_hints_global)
|
|
{
|
|
lex->opt_hints_global= new (pc->thd->mem_root)
|
|
Opt_hints_global(pc->thd->mem_root);
|
|
}
|
|
return lex->opt_hints_global;
|
|
}
|
|
|
|
|
|
Opt_hints_qb *get_qb_hints(Parse_context *pc)
|
|
{
|
|
if (pc->select->opt_hints_qb)
|
|
return pc->select->opt_hints_qb;
|
|
|
|
Opt_hints_global *global_hints= get_global_hints(pc);
|
|
if (global_hints == NULL)
|
|
return NULL;
|
|
|
|
Opt_hints_qb *qb= new (pc->thd->mem_root)
|
|
Opt_hints_qb(global_hints, pc->thd->mem_root, pc->select->select_number);
|
|
if (qb)
|
|
{
|
|
global_hints->register_child(qb);
|
|
pc->select->opt_hints_qb= qb;
|
|
/*
|
|
Mark the query block as resolved as we know which SELECT_LEX it is
|
|
attached to.
|
|
|
|
Note that children (indexes, tables) are probably not resolved, yet.
|
|
*/
|
|
qb->set_fixed();
|
|
}
|
|
return qb;
|
|
}
|
|
|
|
/**
|
|
Find existing Opt_hints_qb object, print warning
|
|
if the query block is not found.
|
|
|
|
@param pc pointer to Parse_context object
|
|
@param qb_name query block name
|
|
@param hint_type the type of the hint from opt_hints_enum
|
|
@param hint_state true: hint enables a feature; false: disables it
|
|
|
|
@return pointer to Opt_hints_table object if found,
|
|
NULL otherwise
|
|
*/
|
|
|
|
Opt_hints_qb *find_qb_hints(Parse_context *pc,
|
|
const Lex_ident_sys &qb_name,
|
|
opt_hints_enum hint_type,
|
|
bool hint_state)
|
|
{
|
|
if (qb_name.length == 0) // no QB NAME is used
|
|
return pc->select->opt_hints_qb;
|
|
|
|
Opt_hints_qb *qb= static_cast<Opt_hints_qb *>
|
|
(pc->thd->lex->opt_hints_global->find_by_name(qb_name));
|
|
|
|
if (qb == NULL)
|
|
{
|
|
print_warn(pc->thd, ER_WARN_UNKNOWN_QB_NAME, hint_type, hint_state,
|
|
&qb_name, NULL, NULL, NULL);
|
|
}
|
|
return qb;
|
|
}
|
|
|
|
|
|
/**
|
|
Returns pointer to Opt_hints_table object,
|
|
create Opt_hints_table object if not exist.
|
|
|
|
@param pc pointer to Parse_context object
|
|
@param table_name pointer to Hint_param_table object
|
|
@param qb pointer to Opt_hints_qb object
|
|
|
|
@return pointer to Opt_hints_table object,
|
|
NULL if failed to create the object
|
|
*/
|
|
|
|
Opt_hints_table *get_table_hints(Parse_context *pc,
|
|
const Lex_ident_sys &table_name,
|
|
Opt_hints_qb *qb)
|
|
{
|
|
Opt_hints_table *tab=
|
|
static_cast<Opt_hints_table *> (qb->find_by_name(table_name));
|
|
if (!tab)
|
|
{
|
|
tab= new (pc->thd->mem_root)
|
|
Opt_hints_table(table_name, qb, pc->thd->mem_root);
|
|
qb->register_child(tab);
|
|
}
|
|
|
|
return tab;
|
|
}
|
|
|
|
|
|
bool Opt_hints::get_switch(opt_hints_enum type_arg) const
|
|
{
|
|
if (is_specified(type_arg))
|
|
return hints_map.is_switched_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
|
|
{
|
|
for (uint i= 0; i < child_array.size(); i++)
|
|
{
|
|
const LEX_CSTRING name= child_array[i]->get_name();
|
|
CHARSET_INFO *cs= child_array[i]->charset_info();
|
|
if (name.str && !cs->strnncollsp(name, name_arg))
|
|
return child_array[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void Opt_hints::print(THD *thd, String *str)
|
|
{
|
|
// Print the hints stored in the bitmap
|
|
for (uint i= 0; i < MAX_HINT_ENUM; i++)
|
|
{
|
|
opt_hints_enum hint= static_cast<opt_hints_enum>(i);
|
|
if (is_specified(hint) && !ignore_print(hint) && is_fixed(hint))
|
|
{
|
|
append_hint_type(str, hint);
|
|
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)
|
|
append_hint_arguments(thd, hint, 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(") "));
|
|
}
|
|
}
|
|
|
|
print_irregular_hints(thd, str);
|
|
|
|
for (uint i= 0; i < child_array.size(); i++)
|
|
child_array[i]->print(thd, str);
|
|
}
|
|
|
|
|
|
bool Opt_hints::ignore_print(opt_hints_enum type_arg) const
|
|
{
|
|
return opt_hint_info[type_arg].irregular_hint;
|
|
}
|
|
|
|
|
|
/*
|
|
@brief
|
|
Append hint "type", for example, "NO_RANGE_OPTIMIZATION" or "BKA"
|
|
*/
|
|
|
|
void Opt_hints::append_hint_type(String *str, opt_hints_enum type)
|
|
{
|
|
if(!hints_map.is_switched_on(type))
|
|
str->append(STRING_WITH_LEN("NO_"));
|
|
str->append(opt_hint_info[type].hint_type);
|
|
}
|
|
|
|
|
|
void Opt_hints::print_unfixed_warnings(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_safe(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
get_unfixed_warning_code(),
|
|
ER_THD(thd, get_unfixed_warning_code()),
|
|
hint_name_str.c_ptr_safe(),
|
|
hint_type_str.c_ptr_safe());
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
@brief
|
|
Recursively walk the descendant hints and emit warnings for any
|
|
unresolved hints
|
|
*/
|
|
|
|
void Opt_hints::check_unfixed(THD *thd)
|
|
{
|
|
if (!are_all_fixed())
|
|
print_unfixed_warnings(thd);
|
|
|
|
if (!are_children_fully_fixed())
|
|
{
|
|
for (uint i= 0; i < child_array.size(); i++)
|
|
child_array[i]->check_unfixed(thd);
|
|
}
|
|
}
|
|
|
|
|
|
Opt_hints_qb::Opt_hints_qb(Opt_hints *opt_hints_arg,
|
|
MEM_ROOT *mem_root_arg,
|
|
uint select_number_arg)
|
|
: Opt_hints(Lex_ident_sys(), opt_hints_arg, mem_root_arg),
|
|
select_number(select_number_arg),
|
|
join_order_hints(mem_root_arg),
|
|
join_order_hints_ignored(0)
|
|
{
|
|
sys_name.str= buff;
|
|
sys_name.length= my_snprintf(buff, sizeof(buff), "%s%x",
|
|
sys_qb_prefix.str, select_number);
|
|
}
|
|
|
|
|
|
/**
|
|
fix_hints_for_derived_table allows early hint fixing for
|
|
derived tables by linking both *this and the Opt_hints_table
|
|
object to the passed TABLE_LIST instance.
|
|
|
|
@param table_list Pointer to TABLE_LIST object
|
|
*/
|
|
|
|
void Opt_hints_qb::fix_hints_for_derived_table(TABLE_LIST *table_list)
|
|
{
|
|
Opt_hints_table *tab=
|
|
static_cast<Opt_hints_table *>(find_by_name(table_list->alias));
|
|
|
|
/*
|
|
If this is fixed and the corresponding Opt_hints_table doesn't exist (or it
|
|
exists and is fixed) then there's nothing to do, so return early.
|
|
*/
|
|
if (are_all_fixed() && (!tab || tab->are_all_fixed()))
|
|
return;
|
|
|
|
/*
|
|
This instance will have been marked as fixed on the basis of its
|
|
attachment to a SELECT_LEX (during get_qb_hints) but that is
|
|
insufficient to consider it fixed for the case where a TABLE
|
|
instance is required but not yet available. If the associated
|
|
table isn't yet fixed, then fix this hint as though it were unfixed.
|
|
|
|
We mark the Opt_hints_table as 'fixed' here and this means we
|
|
won't try to fix the child hints again later. They will remain
|
|
unfixed and will eventually produce "Unresolved index name" error
|
|
in opt_hints_qb->check_unfixed(). This is acceptable because
|
|
no child hints apply to derived tables.
|
|
*/
|
|
DBUG_ASSERT(!table_list->opt_hints_table);
|
|
DBUG_ASSERT(tab);
|
|
table_list->opt_hints_qb= this;
|
|
table_list->opt_hints_table= tab;
|
|
tab->set_fixed();
|
|
}
|
|
|
|
|
|
Opt_hints_table *Opt_hints_qb::fix_hints_for_table(TABLE *table,
|
|
const Lex_ident_table &alias)
|
|
{
|
|
Opt_hints_table *tab=
|
|
static_cast<Opt_hints_table *>(find_by_name(alias));
|
|
|
|
table->pos_in_table_list->opt_hints_qb= this;
|
|
|
|
if (!tab) // Tables not found
|
|
return NULL;
|
|
|
|
if (!tab->fix_key_hints(table))
|
|
incr_fully_fixed_children();
|
|
|
|
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(get_switch(SEMIJOIN_HINT_ENUM))
|
|
return true;
|
|
|
|
// NO_SEMIJOIN hint. If strategy list is empty, do not use SEMIJOIN
|
|
if (semijoin_strategies_map == 0)
|
|
return false;
|
|
|
|
// Fall through: NO_SEMIJOIN w/ strategies neither turns SEMIJOIN off nor on
|
|
}
|
|
|
|
return optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN);
|
|
}
|
|
|
|
|
|
uint Opt_hints_qb::sj_enabled_strategies(uint opt_switches) const
|
|
{
|
|
// Hints override switches
|
|
if (semijoin_hint)
|
|
{
|
|
const uint strategies= semijoin_strategies_map;
|
|
if (get_switch(SEMIJOIN_HINT_ENUM)) // 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;
|
|
}
|
|
|
|
|
|
void Opt_hints_qb::append_hint_arguments(THD *thd, opt_hints_enum hint,
|
|
String *str)
|
|
{
|
|
switch (hint)
|
|
{
|
|
case SUBQUERY_HINT_ENUM:
|
|
subquery_hint->append_args(thd, str);
|
|
break;
|
|
case SEMIJOIN_HINT_ENUM:
|
|
semijoin_hint->append_args(thd, str);
|
|
break;
|
|
case JOIN_FIXED_ORDER_HINT_ENUM:
|
|
join_fixed_order->append_args(thd, str);
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
@brief
|
|
For each index IDX, put its hints into keyinfo_array[IDX]
|
|
*/
|
|
|
|
bool Opt_hints_table::fix_key_hints(TABLE *table)
|
|
{
|
|
/*
|
|
Ok, there's a table we attach to. Mark this hint as fixed and proceed to
|
|
fixing the child objects.
|
|
*/
|
|
set_fixed();
|
|
|
|
/* Make sure that adjustment is called only once. */
|
|
DBUG_ASSERT(keyinfo_array.size() == 0);
|
|
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++)
|
|
{
|
|
if (key_info->name.streq((*hint)->get_name()))
|
|
{
|
|
(*hint)->set_fixed();
|
|
keyinfo_array[j]= static_cast<Opt_hints_key *>(*hint);
|
|
incr_fully_fixed_children();
|
|
set_compound_key_hint_map(*hint, j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Fixing compound index hints. A compound hint is fixed in two cases:
|
|
- it is a table-level hint, i.e. does not have a list of index names
|
|
(like ORDER_INDEX(t1);
|
|
- it has a list of index names, and at least one of listed
|
|
index names is resolved successfully. So, NO_INDEX(t1 bad_idx) does not
|
|
become a table-level hint NO_INDEX(t1) if `bad_idx` cannnot be resolved.
|
|
*/
|
|
for (opt_hints_enum
|
|
hint_type : { INDEX_HINT_ENUM, JOIN_INDEX_HINT_ENUM,
|
|
GROUP_INDEX_HINT_ENUM, ORDER_INDEX_HINT_ENUM })
|
|
{
|
|
if (is_specified(hint_type))
|
|
{
|
|
Opt_hints_key_bitmap *bitmap= get_key_hint_bitmap(hint_type);
|
|
if (bitmap->is_table_level() || bitmap->bits_set() > 0)
|
|
bitmap->set_fixed();
|
|
}
|
|
}
|
|
|
|
if (are_children_fully_fixed())
|
|
return false;
|
|
|
|
return true; // Some children are not fully fixed
|
|
}
|
|
|
|
|
|
static bool table_or_key_hint_type_specified(Opt_hints_table *table_hint,
|
|
Opt_hints_key *key_hint,
|
|
opt_hints_enum type)
|
|
{
|
|
DBUG_ASSERT(table_hint || key_hint );
|
|
return key_hint ? key_hint->is_specified(type) :
|
|
table_hint->is_specified(type);
|
|
}
|
|
|
|
|
|
bool Opt_hints_table::is_fixed(opt_hints_enum type_arg)
|
|
{
|
|
if (is_compound_hint(type_arg))
|
|
return Opt_hints::is_fixed(type_arg) &&
|
|
get_key_hint_bitmap(type_arg)->is_fixed();
|
|
return Opt_hints::is_fixed(type_arg);
|
|
}
|
|
|
|
|
|
void Opt_hints_table::set_compound_key_hint_map(Opt_hints *hint, uint keynr)
|
|
{
|
|
if (hint->is_specified(INDEX_HINT_ENUM))
|
|
global_index_map.set_key_map(keynr);
|
|
if (hint->is_specified(JOIN_INDEX_HINT_ENUM))
|
|
join_index_map.set_key_map(keynr);
|
|
if (hint->is_specified(GROUP_INDEX_HINT_ENUM))
|
|
group_index_map.set_key_map(keynr);
|
|
if (hint->is_specified(ORDER_INDEX_HINT_ENUM))
|
|
order_index_map.set_key_map(keynr);
|
|
}
|
|
|
|
|
|
Opt_hints_key_bitmap *Opt_hints_table::get_key_hint_bitmap(opt_hints_enum type)
|
|
{
|
|
switch (type) {
|
|
case INDEX_HINT_ENUM:
|
|
return &global_index_map;
|
|
case JOIN_INDEX_HINT_ENUM:
|
|
return &join_index_map;
|
|
case GROUP_INDEX_HINT_ENUM:
|
|
return &group_index_map;
|
|
case ORDER_INDEX_HINT_ENUM:
|
|
return &order_index_map;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Function updates key_to_use key map depending on index hint state.
|
|
|
|
@param keys_to_use key to use
|
|
@param available_keys_to_use available keys to use
|
|
@param type_arg hint type
|
|
*/
|
|
|
|
void Opt_hints_table::update_index_hint_map(Key_map *keys_to_use,
|
|
const Key_map *available_keys_to_use,
|
|
opt_hints_enum type_arg)
|
|
{
|
|
// Check if hint is resolved.
|
|
if (is_fixed(type_arg))
|
|
{
|
|
Key_map *keys_specified_in_hint=
|
|
get_key_hint_bitmap(type_arg)->get_key_map();
|
|
if (get_switch(type_arg))
|
|
{
|
|
if (keys_specified_in_hint->is_clear_all())
|
|
{
|
|
/*
|
|
If the hint is on and no keys are specified in the hint,
|
|
then set "keys_to_use" to all the available keys.
|
|
*/
|
|
keys_to_use->merge(*available_keys_to_use);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
If the hint is on and there are keys specified in the hint, then add
|
|
the specified keys to "keys_to_use" taking care of the disabled keys
|
|
(available_keys_to_use).
|
|
*/
|
|
keys_to_use->merge(*keys_specified_in_hint);
|
|
keys_to_use->intersect(*available_keys_to_use);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (keys_specified_in_hint->is_clear_all())
|
|
{
|
|
/*
|
|
If the hint is off and there are no keys specified in the hint, then
|
|
we clear "keys_to_use".
|
|
*/
|
|
keys_to_use->clear_all();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
If hint is off and some keys are specified in the hint, then remove
|
|
the specified keys from "keys_to_use.
|
|
*/
|
|
keys_to_use->subtract(*keys_specified_in_hint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Set TABLE::keys_in_use_for_XXXX and other members according to the
|
|
specified index hints for this table
|
|
|
|
@detail
|
|
For each index hint that is not ignored, include the index in
|
|
- tbl->keys_in_use_for_query if the hint is INDEX or JOIN_INDEX
|
|
- tbl->keys_in_use_for_group_by if the hint is INDEX or
|
|
GROUP_INDEX
|
|
- tbl->keys_in_use_for_order_by if the hint is INDEX or
|
|
ORDER_INDEX
|
|
conversely, subtract the index from the corresponding
|
|
tbl->keys_in_use_for_... map if the hint is prefixed with NO_.
|
|
|
|
See also: TABLE_LIST::process_index_hints(), which handles similar logic
|
|
for old-style index hints.
|
|
|
|
@param thd pointer to THD object
|
|
@param tbl pointer to TABLE object
|
|
|
|
@return
|
|
false if no index hint is specified
|
|
true otherwise.
|
|
*/
|
|
|
|
bool Opt_hints_table::update_index_hint_maps(THD *thd, TABLE *tbl)
|
|
{
|
|
if (!is_fixed(INDEX_HINT_ENUM) && !is_fixed(JOIN_INDEX_HINT_ENUM) &&
|
|
!is_fixed(GROUP_INDEX_HINT_ENUM) && !is_fixed(ORDER_INDEX_HINT_ENUM))
|
|
return false; // No index hint is specified
|
|
|
|
Key_map usable_index_map(tbl->s->usable_indexes(thd));
|
|
tbl->keys_in_use_for_query= tbl->keys_in_use_for_group_by=
|
|
tbl->keys_in_use_for_order_by= usable_index_map;
|
|
|
|
bool is_force= is_force_index_hint(INDEX_HINT_ENUM);
|
|
tbl->force_index_join=
|
|
(is_force || is_force_index_hint(JOIN_INDEX_HINT_ENUM));
|
|
tbl->force_index_group=
|
|
(is_force || is_force_index_hint(GROUP_INDEX_HINT_ENUM));
|
|
tbl->force_index_order=
|
|
(is_force || is_force_index_hint(ORDER_INDEX_HINT_ENUM));
|
|
|
|
if (tbl->force_index_join)
|
|
tbl->keys_in_use_for_query.clear_all();
|
|
if (tbl->force_index_group)
|
|
tbl->keys_in_use_for_group_by.clear_all();
|
|
if (tbl->force_index_order)
|
|
tbl->keys_in_use_for_order_by.clear_all();
|
|
|
|
// See comment to the identical code at TABLE_LIST::process_index_hints
|
|
tbl->force_index= (tbl->force_index_order | tbl->force_index_group |
|
|
tbl->force_index_join);
|
|
|
|
update_index_hint_map(&tbl->keys_in_use_for_query, &usable_index_map,
|
|
INDEX_HINT_ENUM);
|
|
update_index_hint_map(&tbl->keys_in_use_for_group_by, &usable_index_map,
|
|
INDEX_HINT_ENUM);
|
|
update_index_hint_map(&tbl->keys_in_use_for_order_by, &usable_index_map,
|
|
INDEX_HINT_ENUM);
|
|
update_index_hint_map(&tbl->keys_in_use_for_query, &usable_index_map,
|
|
JOIN_INDEX_HINT_ENUM);
|
|
update_index_hint_map(&tbl->keys_in_use_for_group_by, &usable_index_map,
|
|
GROUP_INDEX_HINT_ENUM);
|
|
update_index_hint_map(&tbl->keys_in_use_for_order_by, &usable_index_map,
|
|
ORDER_INDEX_HINT_ENUM);
|
|
/* Make sure "covering_keys" does not include indexes disabled with a hint */
|
|
Key_map covering_keys(tbl->keys_in_use_for_query);
|
|
covering_keys.merge(tbl->keys_in_use_for_group_by);
|
|
covering_keys.merge(tbl->keys_in_use_for_order_by);
|
|
tbl->covering_keys.intersect(covering_keys);
|
|
return true;
|
|
}
|
|
|
|
|
|
void Opt_hints_table::append_hint_arguments(THD *thd, opt_hints_enum hint,
|
|
String *str)
|
|
{
|
|
switch (hint)
|
|
{
|
|
case INDEX_HINT_ENUM:
|
|
global_index_map.parsed_hint->append_args(thd, str);
|
|
break;
|
|
case JOIN_INDEX_HINT_ENUM:
|
|
join_index_map.parsed_hint->append_args(thd, str);
|
|
break;
|
|
case GROUP_INDEX_HINT_ENUM:
|
|
group_index_map.parsed_hint->append_args(thd, str);
|
|
break;
|
|
case ORDER_INDEX_HINT_ENUM:
|
|
order_index_map.parsed_hint->append_args(thd, str);
|
|
break;
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
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)
|
|
{
|
|
DBUG_ASSERT(parent_hint);
|
|
|
|
if (!opt_hint_info[type_arg].has_arguments)
|
|
{
|
|
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 with arguments, not implemented atm */
|
|
DBUG_ASSERT(0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
In addition to indicating the state of a hint, also indicates
|
|
if the hint is present or not. Serves to disambiguate cases
|
|
that the other version of hint_table_state cannot, such as
|
|
when a hint is forcing a behavior in the optimizer that it
|
|
would not normally do and the corresponding optimizer switch
|
|
is enabled.
|
|
|
|
@param thd Current thread connection state
|
|
@param table_list Table having the hint
|
|
@param type_arg The hint kind in question
|
|
|
|
@return appropriate value from hint_state enumeration
|
|
indicating hint enabled/disabled (if present) or
|
|
if the hint was not present.
|
|
*/
|
|
|
|
hint_state hint_table_state(const THD *thd,
|
|
const TABLE_LIST *table_list,
|
|
opt_hints_enum type_arg)
|
|
{
|
|
if (!table_list->opt_hints_qb)
|
|
return hint_state::NOT_PRESENT;
|
|
|
|
DBUG_ASSERT(!opt_hint_info[type_arg].has_arguments);
|
|
|
|
Opt_hints *hint= table_list->opt_hints_table;
|
|
Opt_hints *parent_hint= table_list->opt_hints_qb;
|
|
|
|
if (hint && hint->is_specified(type_arg))
|
|
{
|
|
const bool hint_value= hint->get_switch(type_arg);
|
|
return hint_value ? hint_state::ENABLED :
|
|
hint_state::DISABLED;
|
|
}
|
|
|
|
if (opt_hint_info[type_arg].check_upper_lvl &&
|
|
parent_hint->is_specified(type_arg))
|
|
{
|
|
const bool hint_value= parent_hint->get_switch(type_arg);
|
|
return hint_value ? hint_state::ENABLED :
|
|
hint_state::DISABLED;
|
|
}
|
|
|
|
return hint_state::NOT_PRESENT;
|
|
}
|
|
|
|
|
|
/*
|
|
@brief
|
|
Check whether a given optimization is enabled for table.keyno.
|
|
|
|
@detail
|
|
First check if a hint is present, then check optimizer_switch
|
|
*/
|
|
|
|
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 optimizer_flag(thd, optimizer_switch);
|
|
}
|
|
|
|
|
|
bool hint_table_state(const THD *thd, const TABLE_LIST *table_list,
|
|
opt_hints_enum type_arg, bool fallback_value)
|
|
{
|
|
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 fallback_value;
|
|
}
|
|
|
|
|
|
bool hint_table_state(const THD *thd, const TABLE *table,
|
|
opt_hints_enum type_arg,
|
|
bool fallback_value)
|
|
{
|
|
return hint_table_state(thd, table->pos_in_table_list, type_arg,
|
|
fallback_value);
|
|
}
|
|
|
|
|
|
void append_table_name(THD *thd, String *str, const LEX_CSTRING &table_name,
|
|
const LEX_CSTRING &qb_name)
|
|
{
|
|
/* Append table name */
|
|
append_identifier(thd, str, &table_name);
|
|
|
|
/* Append QB name */
|
|
if (qb_name.length > 0)
|
|
{
|
|
str->append(STRING_WITH_LEN("@"));
|
|
append_identifier(thd, str, &qb_name);
|
|
}
|
|
}
|
|
|
|
|
|
void Opt_hints_qb::apply_join_order_hints(JOIN *join)
|
|
{
|
|
if (join_fixed_order)
|
|
{
|
|
DBUG_ASSERT(join_order_hints.size() == 0 && !join_prefix && !join_suffix);
|
|
// The hint is already applied at Parser::Join_order_hint::resolve()
|
|
return;
|
|
}
|
|
DBUG_ASSERT(!join_fixed_order);
|
|
|
|
// Apply hints in the same order they were specified in the query
|
|
for (uint hint_idx= 0; hint_idx < join_order_hints.size(); hint_idx++)
|
|
{
|
|
Parser::Join_order_hint* hint= join_order_hints[hint_idx];
|
|
if (join->select_options & SELECT_STRAIGHT_JOIN)
|
|
{
|
|
// Only mark as ignored and print the warning
|
|
join_order_hints_ignored |= 1ULL << hint_idx;
|
|
print_warn(join->thd, ER_WARN_CONFLICTING_HINT, hint->hint_type, true,
|
|
nullptr, nullptr, nullptr, hint);
|
|
}
|
|
else if (set_join_hint_deps(join, hint))
|
|
{
|
|
// Mark as ignored
|
|
join_order_hints_ignored |= 1ULL << hint_idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Resolve hint tables, check and set table dependencies according to one
|
|
JOIN_ORDER, JOIN_PREFIX, or JOIN_SUFFIX hint.
|
|
|
|
@param join pointer to JOIN object
|
|
@param hint The hint
|
|
|
|
@detail
|
|
If the hint is ignored due to circular table dependencies, original
|
|
dependencies are restored and a warning is generated.
|
|
|
|
== Dependencies that we add ==
|
|
For any JOIN_HINT(t1, t2, t3, t4) we add the these dependencies:
|
|
|
|
t2.dependent|= {t1}
|
|
t3.dependent|= {t1,t2}
|
|
t4.dependent|= {t1,t2,t3}
|
|
and so forth.
|
|
|
|
This makes sure that the listed tables occur in the join order in the order
|
|
they are listed in the hint.
|
|
|
|
For JOIN_ORDER, this is all what we need.
|
|
For JOIN_PREFIX(t1, t2, ...) we also add dependencies on {t1,t2,...}
|
|
for all tables not listed in the hint.
|
|
For JOIN_SUFFIX(t1, t2, ...) dependencies on all tables that are NOT listed
|
|
in the hint are added to all tables LISTED in the hint: {t1, t2, ...}
|
|
|
|
@return false if hint is applied, true otherwise.
|
|
*/
|
|
|
|
bool Opt_hints_qb::set_join_hint_deps(JOIN *join,
|
|
const Parser::Join_order_hint *hint)
|
|
{
|
|
/*
|
|
Make a copy of original table dependencies. If an error occurs
|
|
when applying the hint, the original dependencies will be restored.
|
|
*/
|
|
table_map *orig_dep_array= join->export_table_dependencies();
|
|
|
|
// Map of the tables, specified in the hint
|
|
table_map hint_tab_map= 0;
|
|
|
|
for (const Parser::Table_name_and_Qb& tbl_name_and_qb : hint->table_names)
|
|
{
|
|
bool hint_table_found= false;
|
|
for (uint i= 0; i < join->table_count; i++)
|
|
{
|
|
TABLE_LIST *table= join->join_tab[i].tab_list;
|
|
if (!compare_table_name(&tbl_name_and_qb, table))
|
|
{
|
|
hint_table_found= true;
|
|
/*
|
|
Const tables are excluded from the process of dependency setting
|
|
since they are always first in the table order. Note that it
|
|
does not prevent the hint from being applied to the non-const tables
|
|
*/
|
|
if (join->const_table_map & table->get_map())
|
|
break;
|
|
|
|
JOIN_TAB *join_tab= &join->join_tab[i];
|
|
// Hint tables are always dependent on preceding tables
|
|
join_tab->dependent |= hint_tab_map;
|
|
update_nested_join_deps(join, join_tab, hint_tab_map);
|
|
hint_tab_map |= join_tab->tab_list->get_map();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!hint_table_found)
|
|
{
|
|
print_join_order_warn(join->thd, hint->hint_type, tbl_name_and_qb);
|
|
join->restore_table_dependencies(orig_dep_array);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Add dependencies that are related to non-hint tables
|
|
for (uint i= 0; i < join->table_count; i++)
|
|
{
|
|
JOIN_TAB *join_tab= &join->join_tab[i];
|
|
const table_map dependent_tables=
|
|
get_other_dep(join, hint->hint_type, hint_tab_map,
|
|
join_tab->tab_list->get_map());
|
|
update_nested_join_deps(join, join_tab, dependent_tables);
|
|
join_tab->dependent |= dependent_tables;
|
|
}
|
|
|
|
if (join->propagate_dependencies(join->join_tab))
|
|
{
|
|
join->restore_table_dependencies(orig_dep_array);
|
|
print_warn(join->thd, ER_WARN_CONFLICTING_HINT, hint->hint_type, true,
|
|
nullptr, nullptr, nullptr, hint);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Function updates dependencies for nested joins. If a table
|
|
specified in the hint belongs to a nested join, we need
|
|
to update dependencies of all tables of the nested join
|
|
with the same dependency as for the hint table. It is also
|
|
necessary to update all tables of the nested joins this table
|
|
is part of.
|
|
|
|
@param join pointer to JOIN object
|
|
@param hint_tab pointer to JOIN_TAB object
|
|
@param hint_tab_map map of the tables, specified in the hint
|
|
|
|
|
|
@detail
|
|
This function is called when the caller has added a dependency:
|
|
|
|
hint_tab now also depends on hint_tab_map.
|
|
|
|
For example:
|
|
|
|
FROM t0 ... LEFT JOIN ( ... t1 ... t2 ... ) ON ...
|
|
|
|
hint_tab=t1, hint_tab_map={t0}.
|
|
|
|
We want to avoid the situation where the optimizer has constructed a join
|
|
prefix with table t2 and without table t0:
|
|
|
|
... t2
|
|
|
|
and now it needs to add t1 to the join prefix (it must do so, see
|
|
add_table_function_dependencies, check_interleaving_with_nj) but it can't
|
|
do that because t0 is not in the join prefix, and it's not possible to add
|
|
t0 as that would break the NO-INTERLEAVING rule (see mentioned functions)
|
|
|
|
In order to avoid this situation, we make t2 also depend t0 (that is, also
|
|
depend on any tables outside the join nest that we've made t1 to depend on)
|
|
|
|
Note that inside the join nest
|
|
|
|
LEFT JOIN ( ... t1 ... t2 ... )
|
|
|
|
t1 and t2 may not be direct children but rather occur inside children join
|
|
nests:
|
|
|
|
LEFT JOIN ( ... LEFT JOIN (...t1...) ... LEFT JOIN (...t2...) ... )
|
|
*/
|
|
|
|
void Opt_hints_qb::update_nested_join_deps(JOIN *join, const JOIN_TAB *hint_tab,
|
|
table_map hint_tab_map)
|
|
{
|
|
const TABLE_LIST *table= hint_tab->tab_list;
|
|
if (table->embedding)
|
|
{
|
|
for (uint i= 0; i < join->table_count; i++)
|
|
{
|
|
JOIN_TAB *tab= &join->join_tab[i];
|
|
/* Walk up the nested joins that tab->table is a part of */
|
|
for (TABLE_LIST *emb= tab->tab_list->embedding; emb; emb=emb->embedding)
|
|
{
|
|
/*
|
|
Apply the rule only for outer joins. Semi-joins do not impose such
|
|
limitation
|
|
*/
|
|
if (emb->on_expr)
|
|
{
|
|
const NESTED_JOIN *const nested_join= emb->nested_join;
|
|
/* Is hint_tab somewhere inside this nested join, too? */
|
|
if (hint_tab->embedding_map & nested_join->nj_map)
|
|
{
|
|
/*
|
|
Yes, it is. Then, tab->table be also dependent on all outside
|
|
tables that hint_tab is dependent on:
|
|
*/
|
|
tab->dependent |= (hint_tab_map & ~nested_join->used_tables);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Function returns a map of dependencies which must be applied to the
|
|
particular table of a JOIN, according to the join order hint
|
|
|
|
@param join JOIN to which the hints are being applied
|
|
@param type hint type
|
|
@param hint_tab_map Bitmap of all tables listed in the hint.
|
|
@param table_map Bit of the table that we're setting extra dependencies
|
|
for.
|
|
|
|
@detail
|
|
This returns extra dependencies between tables listed in the Hint and tables
|
|
that are not listed. Depending on hint type, these are:
|
|
|
|
JOIN_PREFIX(t1, t2, ...) - all not listed tables depend on {t1,t2,...}.
|
|
|
|
JOIN_SUFFIX(t1, t2, ...) - all tables listed in the hint depend on all tables
|
|
that are not listed in the hint
|
|
JOIN_ORDER(t1, t2, ...) - No extra dependencies needed.
|
|
|
|
@return bitmap of dependencies to apply
|
|
*/
|
|
|
|
table_map Opt_hints_qb::
|
|
get_other_dep(JOIN *join, opt_hints_enum type, table_map hint_tab_map,
|
|
table_map table_map)
|
|
{
|
|
switch (type)
|
|
{
|
|
case JOIN_PREFIX_HINT_ENUM:
|
|
if (hint_tab_map & table_map) // Hint table: No additional dependencies
|
|
return 0;
|
|
// Other tables: depend on all hint tables
|
|
return hint_tab_map;
|
|
case JOIN_SUFFIX_HINT_ENUM:
|
|
if (hint_tab_map & table_map) // Hint table: depends on all other tables
|
|
return join->all_tables_map() & ~hint_tab_map;
|
|
return 0;
|
|
case JOIN_ORDER_HINT_ENUM:
|
|
return 0; // No additional dependencies
|
|
default:
|
|
DBUG_ASSERT(0);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
Function compares hint table name and TABLE_LIST table name.
|
|
Query block name is taken into account also.
|
|
|
|
@param hint_table_and_qb table/query block names given in the hint
|
|
@param table pointer to TABLE_LIST object
|
|
|
|
@return false if table names are equal, true otherwise.
|
|
*/
|
|
|
|
bool Opt_hints_qb::compare_table_name(
|
|
const Parser::Table_name_and_Qb *hint_table_and_qb,
|
|
const TABLE_LIST *table)
|
|
{
|
|
const LEX_CSTRING &join_tab_qb_name=
|
|
table->opt_hints_qb ? table->opt_hints_qb->get_name() : Lex_ident_sys();
|
|
|
|
/*
|
|
If QB name is not specified explicitly for a table name int hint,
|
|
for example `JOIN_PREFIX(t2)` or `JOIN_SUFFIX(@q1 t3)` then QB name is
|
|
considered to be equal to `Opt_hints_qb::get_name()`
|
|
*/
|
|
const LEX_CSTRING &hint_tab_qb_name=
|
|
hint_table_and_qb->qb_name.length > 0 ? hint_table_and_qb->qb_name :
|
|
this->get_name();
|
|
|
|
CHARSET_INFO *cs= charset_info();
|
|
// Compare QB names
|
|
if (cs->strnncollsp(join_tab_qb_name, hint_tab_qb_name))
|
|
return true;
|
|
// Compare table names
|
|
return cs->strnncollsp(table->alias, hint_table_and_qb->table_name);
|
|
}
|
|
|
|
|
|
void Opt_hints_qb::print_irregular_hints(THD *thd, String *str)
|
|
{
|
|
/* Print join order hints */
|
|
for (uint i= 0; i < join_order_hints.size(); i++)
|
|
{
|
|
if (join_order_hints_ignored & (1ULL << i))
|
|
continue;
|
|
const Parser::Join_order_hint *hint= join_order_hints[i];
|
|
str->append(opt_hint_info[hint->hint_type].hint_type);
|
|
str->append(STRING_WITH_LEN("("));
|
|
append_name(thd, str);
|
|
str->append(STRING_WITH_LEN(" "));
|
|
hint->append_args(thd, str);
|
|
str->append(STRING_WITH_LEN(") "));
|
|
}
|
|
}
|
|
|
|
|
|
void Opt_hints_qb::print_join_order_warn(THD *thd, opt_hints_enum type,
|
|
const Parser::Table_name_and_Qb &tbl_name)
|
|
{
|
|
String tbl_name_str, hint_type_str;
|
|
hint_type_str.append(opt_hint_info[type].hint_type);
|
|
append_table_name(thd, &tbl_name_str, tbl_name.table_name, tbl_name.qb_name);
|
|
uint err_code= ER_UNRESOLVED_TABLE_HINT_NAME;
|
|
|
|
push_warning_safe(thd, Sql_condition::WARN_LEVEL_WARN,
|
|
err_code, ER_THD(thd, err_code),
|
|
tbl_name_str.c_ptr_safe(), hint_type_str.c_ptr_safe());
|
|
}
|
|
|
|
|
|
/*
|
|
@brief
|
|
Fix global-level hints (and only them)
|
|
*/
|
|
|
|
bool Opt_hints_global::fix_hint(THD *thd)
|
|
{
|
|
if (thd->lex->is_ps_or_view_context_analysis())
|
|
return false;
|
|
|
|
if (!max_exec_time_hint)
|
|
{
|
|
/* No possible errors */
|
|
set_fixed();
|
|
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
|
|
{
|
|
thd->reset_query_timer();
|
|
thd->set_query_timer_force(max_exec_time_hint->get_milliseconds() * 1000);
|
|
}
|
|
set_fixed();
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Function checks if an INDEX (resp. JOIN_INDEX, GROUP_INDEX or
|
|
ORDER_INDEX) hint conflicts with any JOIN_INDEX, GROUP_INDEX or
|
|
ORDER_INDEX (resp. INDEX) hints, by checking if any of the latter is
|
|
already specified at table level or index level.
|
|
|
|
@param table_hint pointer to table hint
|
|
@param key_hint pointer to key hint
|
|
@param hint_type enumerated hint type
|
|
|
|
@return false if no conflict, true otherwise.
|
|
*/
|
|
|
|
bool is_index_hint_conflicting(Opt_hints_table *table_hint,
|
|
Opt_hints_key *key_hint,
|
|
opt_hints_enum hint_type)
|
|
{
|
|
if (hint_type != INDEX_HINT_ENUM)
|
|
return table_or_key_hint_type_specified(table_hint, key_hint,
|
|
INDEX_HINT_ENUM);
|
|
return (table_or_key_hint_type_specified(table_hint, key_hint,
|
|
JOIN_INDEX_HINT_ENUM) ||
|
|
table_or_key_hint_type_specified(table_hint, key_hint,
|
|
ORDER_INDEX_HINT_ENUM) ||
|
|
table_or_key_hint_type_specified(table_hint, key_hint,
|
|
GROUP_INDEX_HINT_ENUM));
|
|
}
|
|
|
|
|
|
bool is_compound_hint(opt_hints_enum type_arg)
|
|
{
|
|
return (
|
|
type_arg == INDEX_HINT_ENUM || type_arg == JOIN_INDEX_HINT_ENUM ||
|
|
type_arg == GROUP_INDEX_HINT_ENUM || type_arg == ORDER_INDEX_HINT_ENUM);
|
|
}
|
|
|
|
|
|
#ifndef DBUG_OFF
|
|
static char dbug_print_hint_buf[64];
|
|
|
|
const char *dbug_print_hints(Opt_hints_qb *hint)
|
|
{
|
|
char *buf= dbug_print_hint_buf;
|
|
THD *thd= current_thd;
|
|
String str(buf, sizeof(dbug_print_hint_buf), &my_charset_bin);
|
|
str.length(0);
|
|
if (!hint)
|
|
return "(Opt_hints_qb*)NULL";
|
|
|
|
hint->print(thd, &str);
|
|
|
|
if (str.c_ptr_safe() == buf)
|
|
return buf;
|
|
else
|
|
return "Couldn't fit into buffer";
|
|
}
|
|
#endif
|