diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 45eb477694a..be6dd1af678 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -160,6 +160,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sp_cursor.cc ../sql/opt_hints_parser.cc ../sql/opt_hints_parser.h ../sql/scan_char.h + ../sql/opt_hints.cc ../sql/opt_hints.h ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/mysql-test/main/opt_hints.result b/mysql-test/main/opt_hints.result index a8b3d20fd9a..acad0725860 100644 --- a/mysql-test/main/opt_hints.result +++ b/mysql-test/main/opt_hints.result @@ -139,7 +139,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t4 ref y_idx y_idx 5 const 1 100.00 1 SIMPLE t5 range x_idx x_idx 5 NULL 2 100.00 Using where; Using join buffer (flat, BNL join) Warnings: -Warning 4199 Unresolved name `t5`@`select#2` `z_idx` for NO_ICP hint +Warning 4205 Unresolved index name `t5`@`select#2` `z_idx` for NO_ICP hint Note 1003 select /*+ NO_ICP(`t5`@`select#2` `y_idx`) NO_ICP(`t5`@`select#2` `x_idx`) */ `test`.`t4`.`x` AS `x`,`test`.`t5`.`y` AS `y` from `test`.`t4` join `test`.`t4` `t5` where `test`.`t4`.`y` = 8 and `test`.`t5`.`x` between 7 and (8 + 0) # ICP should still be used EXPLAIN EXTENDED SELECT * FROM @@ -405,7 +405,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 1 SIMPLE t2 ALL f1 NULL NULL NULL 28 25.00 Using where; Using join buffer (flat, BNL join) Warnings: -Warning 4198 Query block name `qb1` is not found for BKA hint +Warning 4203 Query block name `qb1` is not found for BKA hint Note 1003 select /*+ QB_NAME(`qb1`) */ `test`.`t2`.`f1` AS `f1`,`test`.`t2`.`f2` AS `f2`,`test`.`t2`.`f3` AS `f3` from `test`.`t1` join `test`.`t2` where `test`.`t2`.`f1` = `test`.`t1`.`f1` and `test`.`t2`.`f2` between `test`.`t1`.`f1` and `test`.`t1`.`f2` and `test`.`t2`.`f2` + 1 >= `test`.`t1`.`f1` + 1 # Should not crash PREPARE stmt1 FROM "SELECT /*+ BKA(t2) */ t2.f1, t2.f2, t2.f3 FROM t1,t2 @@ -449,9 +449,9 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 1 SIMPLE t2 ALL f1 NULL NULL NULL 28 25.00 Using where; Using join buffer (flat, BNL join) Warnings: -Warning 4199 Unresolved name `t3`@`select#1` for BKA hint -Warning 4199 Unresolved name `t3`@`select#1` for NO_RANGE_OPTIMIZATION hint -Warning 4199 Unresolved name `t3`@`select#1` `idx1` for NO_RANGE_OPTIMIZATION hint +Warning 4204 Unresolved table name `t3`@`select#1` for BKA hint +Warning 4204 Unresolved table name `t3`@`select#1` for NO_RANGE_OPTIMIZATION hint +Warning 4205 Unresolved index name `t3`@`select#1` `idx1` for NO_RANGE_OPTIMIZATION hint Note 1003 select /*+ BKA(`t2`@`select#1`) NO_BNL(`t1`@`select#1`) */ `test`.`t2`.`f1` AS `f1`,`test`.`t2`.`f2` AS `f2`,`test`.`t2`.`f3` AS `f3` from `test`.`t1` join `test`.`t2` where `test`.`t2`.`f1` = `test`.`t1`.`f1` and `test`.`t2`.`f2` between `test`.`t1`.`f1` and `test`.`t1`.`f2` and `test`.`t2`.`f2` + 1 >= `test`.`t1`.`f1` + 1 # Check illegal syntax EXPLAIN EXTENDED SELECT /*+ BKA(qb1 t3@qb1) */ f2 FROM @@ -488,7 +488,7 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE tbl12 ALL NULL NULL NULL NULL 10 100.00 Using where 1 SIMPLE tbl13 hash_ALL a #hash#a 5 test.tbl12.a 1000 0.10 Using where; Using join buffer (flat, BNLH join) Warnings: -Warning 4199 Unresolved name `tbl2`@`select#1` for BKA hint +Warning 4204 Unresolved table name `tbl2`@`select#1` for BKA hint Note 1003 select `test`.`tbl12`.`a` AS `a`,`test`.`tbl12`.`b` AS `b`,`test`.`tbl13`.`a` AS `a`,`test`.`tbl13`.`b` AS `b`,`test`.`tbl13`.`c` AS `c`,`test`.`tbl13`.`filler` AS `filler` from `test`.`t12` `tbl12` join `test`.`t13` `tbl13` where `test`.`tbl13`.`a` = `test`.`tbl12`.`a` and `test`.`tbl13`.`b` + 1 <= `test`.`tbl13`.`b` + 1 # Check that PS and conventional statements give the same result. FLUSH STATUS; @@ -930,19 +930,19 @@ SELECT /*+ BKA() BKA() */ 1; 1 1 Warnings: -Warning 4197 Hint BKA( ) is ignored as conflicting/duplicated +Warning 4202 Hint BKA() is ignored as conflicting/duplicated SELECT /*+ BKA(t1) BKA(t1) */ * FROM t1; i Warnings: -Warning 4197 Hint BKA(`t1` ) is ignored as conflicting/duplicated +Warning 4202 Hint BKA(`t1`) is ignored as conflicting/duplicated SELECT /*+ QB_NAME(q1) BKA(t1@q1) BKA(t1@q1) */ * FROM t1; i Warnings: -Warning 4197 Hint BKA(`t1`@`q1` ) is ignored as conflicting/duplicated +Warning 4202 Hint BKA(`t1`@`q1`) is ignored as conflicting/duplicated SELECT /*+ QB_NAME(q1) NO_ICP(@q1 t1 PRIMARY) NO_ICP(@q1 t1 PRIMARY) */ * FROM t1; i Warnings: -Warning 4197 Hint NO_ICP(`t1`@`q1` `PRIMARY` ) is ignored as conflicting/duplicated +Warning 4202 Hint NO_ICP(`t1`@`q1` `PRIMARY`) is ignored as conflicting/duplicated DROP TABLE t1; # WL#8016 Parser for optimizer hints CREATE TABLE t1 (i INT, j INT); @@ -955,12 +955,12 @@ SELECT /*+*/ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '*/' at line 1 +Warning 1064 Optimizer hint syntax error near '*/ 1' at line 1 SELECT /*+ */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '*/' at line 1 +Warning 1064 Optimizer hint syntax error near '*/ 1' at line 1 SELECT /*+ * ** / // /* */ 1; 1 1 @@ -990,22 +990,22 @@ SELECT /*+ `@` */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '@` */ 1' at line 1 +Warning 1064 Optimizer hint syntax error near '`@` */ 1' at line 1 SELECT /*+ `@foo` */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '@foo` */ 1' at line 1 +Warning 1064 Optimizer hint syntax error near '`@foo` */ 1' at line 1 SELECT /*+ `foo@bar` */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near 'foo@bar` */ 1' at line 1 +Warning 1064 Optimizer hint syntax error near '`foo@bar` */ 1' at line 1 SELECT /*+ `foo @bar` */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near 'foo @bar` */ 1' at line 1 +Warning 1064 Optimizer hint syntax error near '`foo @bar` */ 1' at line 1 SELECT /*+ BKA( @) */ 1; 1 1 @@ -1020,7 +1020,7 @@ SELECT /*+ BKA(t1 @) */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '@) */ 1' at line 1 +Warning 1064 Optimizer hint syntax error near ') */ 1' at line 1 # We don't support "*/" inside quoted identifiers (syntax error): @@ -1079,15 +1079,15 @@ Warning 1064 Optimizer hint syntax error near 'b) */ 1 FROM t1 a, t1 b' at lin SELECT /*+ NO_ICP(i1) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `i1`@`select#1` for NO_ICP hint +Warning 4204 Unresolved table name `i1`@`select#1` for NO_ICP hint SELECT /*+ NO_ICP(i1 i2) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `i1`@`select#1` `i2` for NO_ICP hint +Warning 4205 Unresolved index name `i1`@`select#1` `i2` for NO_ICP hint SELECT /*+ NO_ICP(@qb ident) */ 1 FROM t1; 1 Warnings: -Warning 4198 Query block name `qb` is not found for NO_ICP hint +Warning 4203 Query block name `qb` is not found for NO_ICP hint # valid hint sequences, no warnings expected: @@ -1142,12 +1142,12 @@ CREATE INDEX 3rd_index ON t1(i, j); SELECT /*+ NO_ICP(3rd_index) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `3rd_index`@`select#1` for NO_ICP hint +Warning 4204 Unresolved table name `3rd_index`@`select#1` for NO_ICP hint CREATE INDEX $index ON t1(j, i); SELECT /*+ NO_ICP($index) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `$index`@`select#1` for NO_ICP hint +Warning 4204 Unresolved table name `$index`@`select#1` for NO_ICP hint CREATE TABLE ` quoted name test` (i INT); SELECT /*+ BKA(` quoted name test`) */ 1 FROM t1; 1 @@ -1184,7 +1184,7 @@ SET SQL_MODE = ''; SELECT /*+ BKA(" quoted name test") */ 1 FROM t1; 1 Warnings: -Warning 1064 Optimizer hint syntax error near '" quoted name test") */ 1 FROM t1' at line 1 +Warning 4204 Unresolved table name `" quoted name test"`@`select#1` for BKA hint DROP TABLE ` quoted name test`; DROP TABLE `test1``test2```; # Valid hints, no warning: @@ -1228,11 +1228,11 @@ CREATE TABLE tableТ (i INT); SELECT /*+ BKA(tableТ) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `tableТ`@`select#1` for BKA hint +Warning 4204 Unresolved table name `tableТ`@`select#1` for BKA hint SELECT /*+ BKA(test@tableТ) */ 1 FROM t1; 1 Warnings: -Warning 4198 Query block name `tableТ` is not found for BKA hint +Warning 4203 Query block name `tableТ` is not found for BKA hint DROP TABLE tableТ; CREATE TABLE таблица (i INT); SELECT /*+ BKA(`таблица`) */ 1 FROM t1; @@ -1242,11 +1242,11 @@ Warning 4199 Unresolved name `таблица`@`select#1` for BKA hint SELECT /*+ BKA(таблица) */ 1 FROM t1; 1 Warnings: -Warning 4199 Unresolved name `таблица`@`select#1` for BKA hint +Warning 4204 Unresolved table name `таблица`@`select#1` for BKA hint SELECT /*+ BKA(test@таблица) */ 1 FROM t1; 1 Warnings: -Warning 4198 Query block name `таблица` is not found for BKA hint +Warning 4203 Query block name `таблица` is not found for BKA hint SELECT /*+ NO_ICP(`\D1`) */ 1 FROM t1; 1 Warnings: @@ -1287,12 +1287,12 @@ SELECT /*+ NO_ICP(10) */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '10) */ 1' at line 1 +Warning 4204 Unresolved table name `10`@`select#1` for NO_ICP hint SELECT /*+ NO_ICP( */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '*/' at line 1 +Warning 1064 Optimizer hint syntax error near '*/ 1' at line 1 SELECT /*+ NO_ICP) */ 1; 1 1 @@ -1302,7 +1302,7 @@ SELECT /*+ NO_ICP(t1 */ 1; 1 1 Warnings: -Warning 1064 Optimizer hint syntax error near '*/' at line 1 +Warning 1064 Optimizer hint syntax error near '*/ 1' at line 1 SELECT /*+ NO_ICP(t1 ( */ 1; 1 1 @@ -1347,7 +1347,7 @@ NO_ICP(@qb ident) 1 1 Warnings: -Warning 4198 Query block name `qb` is not found for NO_ICP hint +Warning 4203 Query block name `qb` is not found for NO_ICP hint SELECT /*+ ? bad syntax */ 1; diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index d1100119276..685fcdad220 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -194,6 +194,7 @@ SET (SQL_SOURCE opt_vcol_substitution.h opt_vcol_substitution.cc opt_hints_parser.cc opt_hints_parser.h scan_char.h + opt_hints.cc opt_hints.h ${CMAKE_CURRENT_BINARY_DIR}/lex_hash.h ${CMAKE_CURRENT_BINARY_DIR}/lex_token.h ${GEN_SOURCES} diff --git a/sql/mem_root_array.h b/sql/mem_root_array.h index 3d03a5a5db2..4284b704202 100644 --- a/sql/mem_root_array.h +++ b/sql/mem_root_array.h @@ -43,6 +43,7 @@ __has_trivial_destructor is supported by some (but not all) compilers we use. */ + template class Mem_root_array { @@ -53,7 +54,6 @@ public: Mem_root_array(MEM_ROOT *root) : m_root(root), m_array(NULL), m_size(0), m_capacity(0) { - DBUG_ASSERT(m_root != NULL); } Mem_root_array(MEM_ROOT *root, size_t n, const value_type &val= value_type()) @@ -62,6 +62,20 @@ public: resize(n, val); } + Mem_root_array(const Mem_root_array& other) + { + do_copy_construct(other); + } + + Mem_root_array &operator=(const Mem_root_array& other) + { + if(this != &other) + { + clear(); + do_copy_construct(other); + } + } + ~Mem_root_array() { clear(); @@ -231,14 +245,22 @@ public: const MEM_ROOT *mem_root() const { return m_root; } private: - MEM_ROOT *const m_root; - Element_type *m_array; - size_t m_size; - size_t m_capacity; + MEM_ROOT *m_root; + Element_type *m_array= nullptr; + size_t m_size= 0; + size_t m_capacity= 0; - // Not (yet) implemented. - Mem_root_array(const Mem_root_array&); - Mem_root_array &operator=(const Mem_root_array&); + void do_copy_construct(const Mem_root_array& other) + { + m_root= other.m_root; + reserve(other.size()); + for (size_t ix= 0; ix < other.size(); ++ix) + { + Element_type *p= &m_array[ix]; + new (p) Element_type(other[ix]); + } + m_size= other.m_size; + } }; diff --git a/sql/multi_range_read.cc b/sql/multi_range_read.cc index cc6c9958892..9f4875c2229 100644 --- a/sql/multi_range_read.cc +++ b/sql/multi_range_read.cc @@ -21,6 +21,7 @@ #include "sql_statistics.h" #include "rowid_filter.h" #include "optimizer_defaults.h" +#include "opt_hints.h" static void get_sweep_read_cost(TABLE *table, ha_rows nrows, bool interrupted, Cost_estimate *cost); @@ -1148,7 +1149,9 @@ int DsMrr_impl::dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs, buf_manager.reset_buffer_sizes= do_nothing; buf_manager.redistribute_buffer_space= do_nothing; - if (mode & (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)) + if (!hint_key_state(thd, table, h_arg->active_index, + MRR_HINT_ENUM, OPTIMIZER_SWITCH_MRR) || + mode & (HA_MRR_USE_DEFAULT_IMPL | HA_MRR_SORTED)) goto use_default_impl; /* @@ -1901,10 +1904,16 @@ bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, THD *thd= primary_file->get_table()->in_use; TABLE_SHARE *share= primary_file->get_table_share(); + const bool mrr_on= hint_key_state(thd, table, keyno, MRR_HINT_ENUM, + OPTIMIZER_SWITCH_MRR); + const bool force_dsmrr_by_hints= + hint_key_state(thd, table, keyno, MRR_HINT_ENUM, 0) || + hint_table_state(thd, table, BKA_HINT_ENUM, 0); + bool doing_cpk_scan= check_cpk_scan(thd, share, keyno, *flags); bool using_cpk= primary_file->is_clustering_key(keyno); *flags &= ~HA_MRR_IMPLEMENTATION_FLAGS; - if (!optimizer_flag(thd, OPTIMIZER_SWITCH_MRR) || + if (!(mrr_on || force_dsmrr_by_hints) || *flags & HA_MRR_INDEX_ONLY || (using_cpk && !doing_cpk_scan) || key_uses_partial_cols(share, keyno)) { @@ -1919,15 +1928,17 @@ bool DsMrr_impl::choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, &dsmrr_cost)) return TRUE; - bool force_dsmrr; /* If mrr_cost_based flag is not set, then set cost of DS-MRR to be minimum of DS-MRR and Default implementations cost. This allows one to force use of DS-MRR whenever it is applicable without affecting other cost-based - choices. + choices. Note that if MRR or BKA hint is + specified, DS-MRR will be used regardless of cost. */ - if ((force_dsmrr= !optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_COST_BASED)) && - dsmrr_cost.total_cost() > cost->total_cost()) + const bool force_dsmrr= + (force_dsmrr_by_hints || + !optimizer_flag(thd, OPTIMIZER_SWITCH_MRR_COST_BASED)); + if (force_dsmrr && dsmrr_cost.total_cost() > cost->total_cost()) dsmrr_cost= *cost; if (force_dsmrr || dsmrr_cost.total_cost() <= cost->total_cost()) diff --git a/sql/opt_hints.cc b/sql/opt_hints.cc new file mode 100644 index 00000000000..a4db2d9e6aa --- /dev/null +++ b/sql/opt_hints.cc @@ -0,0 +1,726 @@ +/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + + 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. 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_h../cnm ints.[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}, + {"QB_NAME", 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 + + @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) +{ + return system_charset_info-> + coll->strnncollsp(system_charset_info, + (uchar *) s->str, s->length, + (uchar *) t->str, t->length); +} + + +static void print_warn(THD *thd, uint err_code, opt_hints_enum hint_type, + bool hint_state, + const LEX_CSTRING *qb_name_arg, + const LEX_CSTRING *table_name_arg, + const LEX_CSTRING *key_name_arg/*, PT_hint *hint*/) +{ + String str; + + /* Append hint name */ + if (!hint_state) + str.append(STRING_WITH_LEN("NO_")); + str.append(opt_hint_info[hint_type].hint_name); + + /* 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_printf(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_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 */ + if (qb_name_arg && qb_name_arg->length > 0) + { + 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 */ + // OLEGS: todo + // if (hint) + // { + // if (qb_name_arg || table_name_arg || key_name_arg) + // str.append(' '); + + // hint->append_args(thd, &str); + // } + + str.append(')'); + + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + 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 +*/ + +static Opt_hints_global *get_global_hints(Parse_context *pc) +{ + LEX *lex= pc->thd->lex; + + if (!lex->opt_hints_global) + lex->opt_hints_global= new Opt_hints_global(pc->thd->mem_root); + if (lex->opt_hints_global) + lex->opt_hints_global->set_resolved(); + return lex->opt_hints_global; +} + + +static 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 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; + qb->set_resolved(); + } + 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 table_name query block name + @param hint processed hint // OLEGS: amend this + + @return pointer to Opt_hints_table object if found, + NULL otherwise +*/ + +static Opt_hints_qb *find_qb_hints(Parse_context *pc, + const LEX_CSTRING *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 + (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); + } + 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 +*/ + +static Opt_hints_table *get_table_hints(Parse_context *pc, + const LEX_CSTRING *table_name, + Opt_hints_qb *qb) +{ + Opt_hints_table *tab= + static_cast (qb->find_by_name(table_name)); + if (!tab) + { + tab= new 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.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 +{ + 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)) + return child_array[i]; + } + return NULL; +} + + +void Opt_hints::print(THD *thd, String *str) +{ + for (uint i= 0; i < MAX_HINT_ENUM; i++) + { + if (is_specified(static_cast(i)) && is_resolved()) + { + append_hint_type(str, static_cast(i)); + str->append(STRING_WITH_LEN("(")); + append_name(thd, str); + // OLEGS: + //if (!opt_hint_info[i].switch_hint) + // get_complex_hints(i)->append_args(thd, str); + str->append(STRING_WITH_LEN(") ")); + } + } + + for (uint i= 0; i < child_array.size(); i++) + child_array[i]->print(thd, str); +} + + +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(i))) + { + hint_type_str.length(0); + append_hint_type(&hint_type_str, static_cast(i)); + push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, + get_warn_unresolved_code(), + ER_THD(thd, get_warn_unresolved_code()), + 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(uint type) +{ + DBUG_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) +{ + sys_name.str= buff; + sys_name.length= my_snprintf(buff, sizeof(buff), "%s%lx", + sys_qb_prefix.str, select_number); +} + + +Opt_hints_table *Opt_hints_qb::adjust_table_hints(TABLE *table, + const LEX_CSTRING *alias) +{ + Opt_hints_table *tab= static_cast(find_by_name(alias)); + + table->pos_in_table_list->opt_hints_qb= this; + + if (!tab) // Tables not found + return NULL; + + tab->adjust_key_hints(table); + return tab; +} + + +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 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 (!cmp_lex_string((*hint)->get_name(), &key_info->name)) + { + (*hint)->set_resolved(); + keyinfo_array[j]= static_cast(*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) +{ + DBUG_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 */ + DBUG_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 optimizer_flag(thd, 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 optimizer_flag(thd, optimizer_switch); +} + + +bool hint_table_state_or_fallback(const THD *thd, const TABLE *table, + opt_hints_enum type_arg, + bool fallback_value) +{ + 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 fallback_value; +} + + +bool Optimizer_hint_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; + 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 Query_block_name &qb_name= *this; + Opt_hints_qb *qb= find_qb_hints(pc, &qb_name, 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, NULL, NULL/*, this*/); + 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) + { + Opt_hints_table *tab= get_table_hints(pc, &table, qb); + if (!tab) + return true; // OLEGS: why no warning? + if (tab->set_switch(hint_state, hint_type, true)) + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, + &qb_name, &table, NULL/*, this*/); + } + } + } + else + { + // this is opt_hint_param_table_list + const Opt_table_name_list &table_name_list= *this; + const Opt_hint_param_table_list &opt_hint_param_table_list= *this; + Opt_hints_qb *qb= find_qb_hints(pc, &null_clex_str, hint_type, hint_state); + if (qb == NULL) + return false; + if (table_name_list.is_empty() && 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_clex_str, NULL, NULL/*, this*/); + return false; + } + for (const Table_name &table : table_name_list) + { + // e.g. BKA(t1, t2) + Opt_hints_table *tab= get_table_hints(pc, &table, qb); + if (!tab) + return true; // OLEGS: no warning? + if (tab->set_switch(hint_state, hint_type, true)) + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, + &null_clex_str, &table, NULL/*, this*/); + } + + for (const Hint_param_table &table : opt_hint_param_table_list) + { + // e.g. BKA(t1@qb1, t2@qb2) + const Query_block_name &qb_name= table; + const Table_name &table_name= table; + Opt_hints_qb *qb= find_qb_hints(pc, &qb_name, hint_type, hint_state); + if (qb == NULL) + return false; + // OLEGS: todo + Opt_hints_table *tab= get_table_hints(pc, &table_name, qb); + if (!tab) + return true; // OLEGS: why no warning? + + if (tab->set_switch(hint_state, hint_type, true)) + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, + &qb_name, &table_name, NULL/*, this*/); + } + } + return false; +} + + +bool Optimizer_hint_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 + + 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; + default: + DBUG_ASSERT(0); + return true; + } + + const Hint_param_table_ext &table_ext= *this; + const Query_block_name &qb_name= table_ext; + const Table_name &table_name= table_ext; + + Opt_hints_qb *qb= find_qb_hints(pc, &qb_name, hint_type, hint_state); + if (qb == NULL) + return false; + + Opt_hints_table *tab= get_table_hints(pc, &table_name, qb); + if (!tab) + return true; // OLEGS: why no warning? + + if (is_empty()) // Table level hint + { + if (tab->set_switch(hint_state, hint_type, false)) + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, + &qb_name, &table_name, NULL/*, this*/); + return false; + } + + for (const Hint_param_index &index_name : *this) + { + Opt_hints_key *idx= (Opt_hints_key *)tab->find_by_name(&index_name); + if (!idx) + { + idx= new Opt_hints_key(&index_name, tab, pc->thd->mem_root); + tab->register_child(idx); + } + + if (idx->set_switch(hint_state, hint_type, true)) + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, hint_type, hint_state, + &qb_name, &table_name, &index_name/*, this*/); + } + + return false; +} + + +bool Optimizer_hint_parser::Qb_name_hint::resolve(Parse_context *pc) const +{ + Opt_hints_qb *qb= pc->select->opt_hints_qb; + + DBUG_ASSERT(qb); + + const Query_block_name &qb_name= *this; + + if (qb->get_name() || // QB name is already set + qb->get_parent()->find_by_name(&qb_name)) // Name is already used + { + print_warn(pc->thd, ER_WARN_CONFLICTING_HINT, QB_NAME_HINT_ENUM, false, + NULL, NULL, NULL/*, this*/); + return false; + } + + qb->set_name(&qb_name); + return false; +} + + +bool Optimizer_hint_parser::Hint_list::resolve(Parse_context *pc) +{ + if (!get_qb_hints(pc)) + return true; + + List_iterator_fast li(*this); + while(Optimizer_hint_parser::Hint *hint= li++) + { + if (const Table_level_hint &table_hint= + static_cast(*hint)) + { + if (table_hint.resolve(pc)) + return true; + } + else if (const Index_level_hint &index_hint= + static_cast(*hint)) + { + if (index_hint.resolve(pc)) + return true; + } + else if (const Qb_name_hint &qb_hint= + static_cast(*hint)) + { + if (qb_hint.resolve(pc)) + return true; // OLEGS: check this result + } + } + return false; +} diff --git a/sql/opt_hints.h b/sql/opt_hints.h new file mode 100644 index 00000000000..7844cb32600 --- /dev/null +++ b/sql/opt_hints.h @@ -0,0 +1,507 @@ +/* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + + 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 */ + +/* + Parse tree node classes for optimizer hint syntax +*/ + + +#ifndef OPT_HINTS_INCLUDED +#define OPT_HINTS_INCLUDED + +#include "my_config.h" +#include "sql_alloc.h" +#include "sql_list.h" +#include "mem_root_array.h" +#include "sql_string.h" +#include "sql_bitmap.h" +#include "sql_show.h" +#include "mysqld_error.h" + + +struct LEX; +struct TABLE; + +/** + Hint types, MAX_HINT_ENUM should be always last. + This enum should be synchronized with opt_hint_info + array(see opt_hints.cc). +*/ +enum opt_hints_enum +{ + BKA_HINT_ENUM= 0, + BNL_HINT_ENUM, + ICP_HINT_ENUM, + MRR_HINT_ENUM, + NO_RANGE_HINT_ENUM, + QB_NAME_HINT_ENUM, + MAX_HINT_ENUM +}; + + +struct st_opt_hint_info +{ + const char* hint_name; // Hint name. + bool check_upper_lvl; // true if upper level hint check is needed (for hints + // which can be specified on more than one level). + bool switch_hint; // true if hint is not complex. +}; + + +/** + Opt_hints_map contains information + about hint state(specified or not, hint value). +*/ + +class Opt_hints_map : public Sql_alloc +{ + Bitmap<64> hints; // hint state + Bitmap<64> hints_specified; // true if hint is specified + +public: + Opt_hints_map() + { + hints.clear_all(); + hints_specified.clear_all(); + } + + /** + Check if hint is specified. + + @param type_arg hint type + + @return true if hint is specified, + false otherwise + */ + my_bool is_specified(opt_hints_enum type_arg) const + { + return hints_specified.is_set(type_arg); + } + /** + Set switch value and set hint into specified state. + + @param type_arg hint type + @param switch_state_arg switch value + */ + void set_switch(opt_hints_enum type_arg, + bool switch_state_arg) + { + if (switch_state_arg) + hints.set_bit(type_arg); + else + hints.clear_bit(type_arg); + hints_specified.set_bit(type_arg); + } + /** + Get switch value. + + @param type_arg hint type + + @return switch value. + */ + bool switch_on(opt_hints_enum type_arg) const + { + return hints.is_set(type_arg); + } +}; + + +class PT_hint; +class PT_hint_max_execution_time; +class Opt_hints_key; + + +/** + Opt_hints class is used as ancestor for Opt_hints_global, + Opt_hints_qb, Opt_hints_table, Opt_hints_key classes. + + Opt_hints_global class is hierarchical structure. + It contains information about global hints and also + conains array of QUERY BLOCK level objects (Opt_hints_qb class). + Each QUERY BLOCK level object contains array of TABLE level hints + (class Opt_hints_table). Each TABLE level hint contains array of + KEY lelev hints (Opt_hints_key class). + Hint information(specified, on|off state) is stored in hints_map object. +*/ + +class Opt_hints : public Sql_alloc +{ + /* + Name of object referred by the hint. + This name is empty for global level, + query block name for query block level, + table name for table level and key name + for key level. + */ + const LEX_CSTRING *name; + /* + Parent object. There is no parent for global level, + for query block level parent is Opt_hints_global object, + for table level parent is Opt_hints_qb object, + for key level parent is Opt_hints_key object. + */ + Opt_hints *parent; + + Opt_hints_map hints_map; // Hint map + + /* Array of child objects. i.e. array of the lower level objects */ + Mem_root_array child_array; + /* true if hint is connected to the real object */ + bool resolved; + /* Number of resolved children */ + uint resolved_children; + +public: + + Opt_hints(const LEX_CSTRING *name_arg, + Opt_hints *parent_arg, + MEM_ROOT *mem_root_arg) + : name(name_arg), parent(parent_arg), child_array(mem_root_arg), + resolved(false), resolved_children(0) + { } + + bool is_specified(opt_hints_enum type_arg) const + { + return hints_map.is_specified(type_arg); + } + + /** + Function sets switch hint state. + + @param switch_state_arg switch hint state + @param type_arg hint type + @param check_parent true if hint can be on parent level + + @return true if hint is already specified, + false otherwise + */ + bool set_switch(bool switch_state_arg, + opt_hints_enum type_arg, + bool check_parent) + { + if (is_specified(type_arg) || + (check_parent && parent->is_specified(type_arg))) + return true; + + hints_map.set_switch(type_arg, switch_state_arg); + return false; + } + + /** + Function returns switch hint state. + + @param type_arg hint type + + @return hint value if hint is specified, + false otherwise + */ + bool get_switch(opt_hints_enum type_arg) const; + + virtual const LEX_CSTRING *get_name() const { return name; } + void set_name(const LEX_CSTRING *name_arg) { name= name_arg; } + Opt_hints *get_parent() const { return parent; } + void set_resolved() { resolved= true; } + bool is_resolved() const { return resolved; } + void incr_resolved_children() { resolved_children++; } + Mem_root_array *child_array_ptr() { return &child_array; } + + bool is_all_resolved() const + { + return child_array.size() == resolved_children; + } + + void register_child(Opt_hints* hint_arg) + { + child_array.push_back(hint_arg); + } + + // OLEGS: remove it if not used + /** + Returns pointer to complex hint for a given type + + @param type hint type + + @return pointer to complex hint for a given type. + */ + // virtual PT_hint *get_complex_hints(uint type) + // { + // DBUG_ASSERT(0); + // return NULL; /* error C4716: must return a value*/ + // }; + + /** + Find hint among lower-level hint objects. + + @param name_arg hint name + + @return hint if found, + NULL otherwise + */ + Opt_hints *find_by_name(const LEX_CSTRING *name_arg) const; + /** + Print all hints except of QB_NAME hint. + + @param thd Pointer to THD object + @param str Pointer to String object + */ + void print(THD *thd, String *str); + /** + Check if there are any unresolved hint objects and + print warnings for them. + + @param thd Pointer to THD object + */ + void check_unresolved(THD *thd); + virtual void append_name(THD *thd, String *str)= 0; + + virtual ~Opt_hints() {} + +private: + /** + Append hint type. + + @param str Pointer to String object + @param type Hint type + */ + void append_hint_type(String *str, opt_hints_enum type); + /** + Print warning for unresolved hint name. + + @param thd Pointer to THD object + */ + void print_warn_unresolved(THD *thd); + +protected: + /** + Override this function in descendants so that print_warn_unresolved() + prints the proper warning text for table/index level unresolved hints + */ + virtual uint get_warn_unresolved_code() const { return 0; } +}; + + +/** + Global level hints. +*/ + +class Opt_hints_global : public Opt_hints +{ + +public: + PT_hint_max_execution_time *max_exec_time; + + Opt_hints_global(MEM_ROOT *mem_root_arg) + : Opt_hints(NULL, NULL, mem_root_arg) + { + max_exec_time= NULL; + } + + virtual void append_name(THD *thd, String *str) {} + virtual PT_hint *get_complex_hints(uint type); +}; + + +class Opt_hints_table; + +/** + Query block level hints. +*/ + +class Opt_hints_qb : public Opt_hints +{ + uint select_number; // SELECT_LEX number + LEX_CSTRING sys_name; // System QB name + char buff[32]; // Buffer to hold sys name + +public: + + Opt_hints_qb(Opt_hints *opt_hints_arg, + MEM_ROOT *mem_root_arg, + uint select_number_arg); + + const LEX_CSTRING *get_print_name() + { + const LEX_CSTRING *str= Opt_hints::get_name(); + return str ? str : &sys_name; + } + + /** + Append query block hint. + + @param thd pointer to THD object + @param str pointer to String object + */ + void append_qb_hint(THD *thd, String *str) + { + if (get_name()) + { + str->append(STRING_WITH_LEN("QB_NAME(")); + append_identifier(thd, str, get_name()->str, get_name()->length); + str->append(STRING_WITH_LEN(") ")); + } + } + /** + Append query block name. + + @param thd pointer to THD object + @param str pointer to String object + */ + virtual void append_name(THD *thd, String *str) + { + str->append(STRING_WITH_LEN("@")); + append_identifier(thd, str, get_print_name()->str, get_print_name()->length); + } + + /** + Function finds Opt_hints_table object corresponding to + table alias in the query block and attaches corresponding + key hint objects to appropriate KEY structures. + + @param table Pointer to TABLE object + @param alias Table alias + + @return pointer Opt_hints_table object if this object is found, + NULL otherwise. + */ + Opt_hints_table *adjust_table_hints(TABLE *table, const LEX_CSTRING *alias); +}; + + +/** + Table level hints. +*/ + +class Opt_hints_table : public Opt_hints +{ +public: + Mem_root_array keyinfo_array; + + Opt_hints_table(const LEX_CSTRING *table_name_arg, + Opt_hints_qb *qb_hints_arg, + MEM_ROOT *mem_root_arg) + : Opt_hints(table_name_arg, qb_hints_arg, mem_root_arg), + keyinfo_array(mem_root_arg) + { } + + /** + Append table name. + + @param thd pointer to THD object + @param str pointer to String object + */ + virtual void append_name(THD *thd, String *str) + { + append_identifier(thd, str, get_name()->str, get_name()->length); + get_parent()->append_name(thd, str); + } + /** + Function sets correlation between key hint objects and + appropriate KEY structures. + + @param table Pointer to TABLE object + */ + void adjust_key_hints(TABLE *table); + + virtual uint get_warn_unresolved_code() const override + { + return ER_UNRESOLVED_TABLE_HINT_NAME; + } +}; + + +/** + Key level hints. +*/ + +class Opt_hints_key : public Opt_hints +{ +public: + + Opt_hints_key(const LEX_CSTRING *key_name_arg, + Opt_hints_table *table_hints_arg, + MEM_ROOT *mem_root_arg) + : Opt_hints(key_name_arg, table_hints_arg, mem_root_arg) + { } + + /** + Append key name. + + @param thd pointer to THD object + @param str pointer to String object + */ + virtual void append_name(THD *thd, String *str) + { + get_parent()->append_name(thd, str); + str->append(' '); + append_identifier(thd, str, get_name()->str, get_name()->length); + } + + virtual uint get_warn_unresolved_code() const override + { + return ER_UNRESOLVED_INDEX_HINT_NAME; + } +}; + + +/** + Returns key hint value if hint is specified, returns + optimizer switch value if hint is not specified. + + @param thd Pointer to THD object + @param tab Pointer to TABLE object + @param keyno Key number + @param type_arg Hint type + @param optimizer_switch Optimizer switch flag + + @return key hint value if hint is specified, + otherwise optimizer switch value. +*/ +bool hint_key_state(const THD *thd, const TABLE *table, + uint keyno, opt_hints_enum type_arg, + uint optimizer_switch); + +/** + Returns table hint value if hint is specified, returns + optimizer switch value if hint is not specified. + + @param thd Pointer to THD object + @param tab Pointer to TABLE object + @param type_arg Hint type + @param optimizer_switch Optimizer switch flag + + @return table hint value if hint is specified, + otherwise optimizer switch value. +*/ +bool hint_table_state(const THD *thd, const TABLE *table, + opt_hints_enum type_arg, + uint optimizer_switch); + +/** + Returns table hint value if hint is specified, returns + fallback value if hint is not specified. + + @param thd Pointer to THD object + @param tab Pointer to TABLE object + @param type_arg Hint type + @param fallback_value Value to be returned if the hint is not set + + @return table hint value if hint is specified, + otherwise fallback value. +*/ +bool hint_table_state_or_fallback(const THD *thd, const TABLE *table, + opt_hints_enum type_arg, + bool fallback_value); + +#endif /* OPT_HINTS_INCLUDED */ diff --git a/sql/opt_hints_parser.cc b/sql/opt_hints_parser.cc index 70046ee608f..6e8d1f4e6a6 100644 --- a/sql/opt_hints_parser.cc +++ b/sql/opt_hints_parser.cc @@ -22,6 +22,17 @@ #include "mysqld_error.h" #include "sql_class.h" + +extern struct st_opt_hint_info opt_hint_info[]; + + +Parse_context::Parse_context(THD *thd, st_select_lex *select) +: thd(thd), + mem_root(thd->mem_root), + select(select) +{} + + // This method is for debug purposes bool Optimizer_hint_parser::parse_token_list(THD *thd) { diff --git a/sql/opt_hints_parser.h b/sql/opt_hints_parser.h index ca60b96e803..467020a26d4 100644 --- a/sql/opt_hints_parser.h +++ b/sql/opt_hints_parser.h @@ -23,6 +23,19 @@ #include "sql_list.h" #include "simple_parser.h" +class st_select_lex; + +/** + Environment data for the name resolution phase +*/ +struct Parse_context { + THD * const thd; ///< Current thread handler + MEM_ROOT *mem_root; ///< Current MEM_ROOT + st_select_lex * select; ///< Current SELECT_LEX object + + Parse_context(THD *thd, st_select_lex *select); +}; + class Optimizer_hint_tokenizer: public Extended_string_tokenizer { @@ -513,7 +526,6 @@ private: using OR2::OR2; }; - // table_level_hint ::= table_level_hint_type ( table_level_hint_body ) class Table_level_hint: public AND4table->key_info[keyno].index_flags & HA_DO_INDEX_COND_PUSHDOWN) && - optimizer_flag(tab->join->thd, OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN) && + hint_key_state(tab->join->thd, tab->table, keyno, ICP_HINT_ENUM, + OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN) && tab->join->thd->lex->sql_command != SQLCOM_UPDATE_MULTI && tab->join->thd->lex->sql_command != SQLCOM_DELETE_MULTI && tab->type != JT_CONST && tab->type != JT_SYSTEM && diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 41f7895d1be..d425b18e23d 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -116,6 +116,7 @@ #include "sql_statistics.h" #include "uniques.h" #include "my_json_writer.h" +#include "opt_hints.h" #ifndef EXTRA_DEBUG #define test_rb_tree(A,B) {} @@ -2854,6 +2855,13 @@ SQL_SELECT::test_quick_select(THD *thd, add("cause", "not applicable"); continue; } + if (hint_key_state(thd, head, idx, NO_RANGE_HINT_ENUM, 0)) + { + trace_idx_details. + add("usable", false). + add("cause", "no_range_optimization hint"); + continue; + } if (key_info->algorithm == HA_KEY_ALG_FULLTEXT) { trace_idx_details.add("usable", false).add("cause", "fulltext"); diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index fd20bd91efa..f8c4a0a264d 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -12318,5 +12318,7 @@ ER_WARN_CONFLICTING_HINT eng "Hint %s is ignored as conflicting/duplicated" ER_WARN_UNKNOWN_QB_NAME eng "Query block name %s is not found for %s hint" -ER_UNRESOLVED_HINT_NAME - eng "Unresolved name %s for %s hint" +ER_UNRESOLVED_TABLE_HINT_NAME + eng "Unresolved table name %s for %s hint" +ER_UNRESOLVED_INDEX_HINT_NAME + eng "Unresolved index name %s for %s hint" diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 785db99441b..8e2d5246b32 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -65,6 +65,7 @@ #include "wsrep_thd.h" #include "wsrep_trans_observer.h" #endif /* WITH_WSREP */ +#include "opt_hints.h" bool No_such_table_error_handler::handle_condition(THD *, @@ -8266,6 +8267,7 @@ bool setup_tables(THD *thd, Name_resolution_context *context, 0); SELECT_LEX *select_lex= select_insert ? thd->lex->first_select_lex() : thd->lex->current_select; + Opt_hints_qb *qb_hints= select_lex->opt_hints_qb; if (select_lex->first_cond_optimization) { leaves.empty(); @@ -8317,6 +8319,12 @@ bool setup_tables(THD *thd, Name_resolution_context *context, my_error(ER_TOO_MANY_TABLES, MYF(0), static_cast(MAX_TABLES)); DBUG_RETURN(1); } + if (qb_hints && // QB hints initialized + !table_list->opt_hints_table) // Table hints are not adjusted yet + { + table_list->opt_hints_table= + qb_hints->adjust_table_hints(table_list->table, &table_list->alias); + } } if (select_insert && !is_insert_tables_num_set) { @@ -8385,6 +8393,8 @@ bool setup_tables(THD *thd, Name_resolution_context *context, if (setup_natural_join_row_types(thd, from_clause, context)) DBUG_RETURN(1); + if (qb_hints) + qb_hints->check_unresolved(thd); DBUG_RETURN(0); } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 92ba1aa2935..7b69535eecf 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -38,6 +38,7 @@ #include "sql_partition.h" #include "sql_partition_admin.h" // Sql_cmd_alter_table_*_part #include "event_parse_data.h" +#include "opt_hints_parser.h" #ifdef WITH_WSREP #include "mysql/service_wsrep.h" #endif @@ -1338,6 +1339,7 @@ void LEX::start(THD *thd_arg) table_count_update= 0; needs_reprepare= false; + opt_hints_global= 0; memset(&trg_chistics, 0, sizeof(trg_chistics)); DBUG_VOID_RETURN; @@ -3108,6 +3110,8 @@ void st_select_lex::init_query() versioned_tables= 0; pushdown_select= 0; orig_names_of_item_list_elems= 0; + opt_hints_qb= 0; + parsed_optimizer_hints= 0; // OLEGS: remove if not used } void st_select_lex::init_select() @@ -3161,6 +3165,8 @@ void st_select_lex::init_select() nest_flags= 0; orig_names_of_item_list_elems= 0; item_list_usage= MARK_COLUMNS_READ; + opt_hints_qb= 0; + parsed_optimizer_hints= 0; // OLEGS: remove if not used } /* @@ -4084,9 +4090,9 @@ void Query_tables_list::destroy_query_tables_list() */ LEX::LEX() - : explain(NULL), result(0), part_info(NULL), arena_for_set_stmt(0), - mem_root_for_set_stmt(0), json_table(NULL), analyze_stmt(0), - default_used(0), + : explain(NULL), result(0), opt_hints_global(NULL), part_info(NULL), + arena_for_set_stmt(0), mem_root_for_set_stmt(0), json_table(NULL), + analyze_stmt(0), default_used(0), with_rownum(0), is_lex_started(0), without_validation(0), option_type(OPT_DEFAULT), context_analysis_only(0), sphead(0), sp_mem_root_ptr(nullptr), limit_rows_examined_cnt(ULONGLONG_MAX) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index c1f116db1ef..79b6462d3fe 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -168,6 +168,8 @@ class With_clause; class my_var; class select_handler; class Pushdown_select; +class Opt_hints_global; +class Opt_hints_qb; #define ALLOC_ROOT_SET 1024 @@ -1247,6 +1249,8 @@ public: */ table_map select_list_tables; + Opt_hints_qb *opt_hints_qb; + /* Set to 1 if any field in field list has ROWNUM() */ bool rownum_in_field_list; @@ -1256,8 +1260,10 @@ public: index_clause_map current_index_hint_clause; /* it is for correct printing SELECT options */ - thr_lock_type lock_type; - + thr_lock_type lock_type; + + Optimizer_hint_parser::Hint_list *parsed_optimizer_hints; + /** System Versioning */ int vers_setup_conds(THD *thd, TABLE_LIST *tables); /* push new Item_field into item_list */ @@ -1554,6 +1560,10 @@ public: bool is_unit_nest() { return (nest_flags & UNIT_NEST_FL); } void mark_as_unit_nest() { nest_flags= UNIT_NEST_FL; } bool is_sj_conversion_prohibited(THD *thd); + void set_optimizer_hints(Optimizer_hint_parser::Hint_list *hl) + { + parsed_optimizer_hints= hl; + } }; typedef class st_select_lex SELECT_LEX; @@ -3188,6 +3198,9 @@ public: LEX_USER *grant_user; XID *xid; THD *thd; + + /* Optimizer hints */ + Opt_hints_global *opt_hints_global; /* maintain a list of used plugins for this LEX */ DYNAMIC_ARRAY plugins; @@ -3723,6 +3736,20 @@ public: DBUG_RETURN(select_lex); } + void resolve_optimizer_hints() + { + SELECT_LEX *select_lex; + if (likely(select_stack_top)) + select_lex= select_stack[select_stack_top - 1]; + else + select_lex= nullptr; + if (select_lex && select_lex->parsed_optimizer_hints) + { + Parse_context pc(thd, select_lex); + select_lex->parsed_optimizer_hints->resolve(&pc); + } + } + SELECT_LEX *current_select_or_default() { return current_select ? current_select : &builtin_select; diff --git a/sql/sql_list.h b/sql/sql_list.h index 9f75b106cf0..03f855723c8 100644 --- a/sql/sql_list.h +++ b/sql/sql_list.h @@ -294,7 +294,7 @@ public: inline list_node* first_node() { return first;} inline void *head() { return first->info; } inline void **head_ref() { return first != &end_of_list ? &first->info : 0; } - inline bool is_empty() { return first == &end_of_list ; } + inline bool is_empty() const { return first == &end_of_list ; } inline list_node *last_ref() { return &end_of_list; } template inline bool add_unique(T *info, bool (*eq)(T *a, T *b)) @@ -531,9 +531,9 @@ public: } class Iterator; + class Const_Iterator; using value_type= T; using iterator= Iterator; - iterator begin() const { return iterator(first); } iterator end() const { return iterator(); } @@ -579,6 +579,49 @@ public: private: list_node *node{&end_of_list}; }; + + class Const_Iterator + { + public: + using iterator_category= std::forward_iterator_tag; + using value_type= const T; + using difference_type= std::ptrdiff_t; + using pointer= const T *; + using reference= const T &; + + Const_Iterator(const list_node *p= &end_of_list) : node{p} {} + + Const_Iterator &operator++() + { + DBUG_ASSERT(node != &end_of_list); + + node= node->next; + return *this; + } + + Const_Iterator operator++(int) + { + Const_Iterator tmp(*this); + operator++(); + return tmp; + } + + const T &operator*() { return *static_cast(node->info); } + const T *operator->() { return static_cast(node->info); } + + bool operator==(const typename List::const_iterator &rhs) + { + return node == rhs.node; + } + + bool operator!=(const typename List::const_iterator &rhs) + { + return node != rhs.node; + } + + private: + const list_node* node{&end_of_list}; + }; }; @@ -735,7 +778,7 @@ class base_ilist public: inline void empty() { first= &last; last.prev= &first; } base_ilist() { empty(); } - inline bool is_empty() { return first == &last; } + inline bool is_empty() const { return first == &last; } // Returns true if p is the last "real" object in the list, // i.e. p->next points to the sentinel. inline bool is_last(ilink *p) { return p->next == NULL || p->next == &last; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7183d2e31b2..1760635514f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -68,6 +68,7 @@ #include "create_tmp_table.h" #include "optimizer_defaults.h" #include "derived_handler.h" +#include "opt_hints.h" /* A key part number that means we're using a fulltext scan. @@ -15677,12 +15678,14 @@ uint check_join_cache_usage(JOIN_TAB *tab, !(join->allowed_join_cache_types & JOIN_CACHE_INCREMENTAL_BIT); bool no_hashed_cache= !(join->allowed_join_cache_types & JOIN_CACHE_HASHED_BIT); - bool no_bka_cache= - !(join->allowed_join_cache_types & JOIN_CACHE_BKA_BIT); - + bool no_bnl_cache= !hint_table_state_or_fallback(join->thd, + tab->tab_list->table, BNL_HINT_ENUM, true); + bool no_bka_cache= !hint_table_state_or_fallback(join->thd, + tab->tab_list->table, BKA_HINT_ENUM, + join->allowed_join_cache_types & JOIN_CACHE_BKA_BIT); join->return_tab= 0; - if (tab->no_forced_join_cache) + if (tab->no_forced_join_cache || (no_bnl_cache && no_bka_cache)) goto no_join_cache; /* @@ -15806,6 +15809,8 @@ uint check_join_cache_usage(JOIN_TAB *tab, case JT_NEXT: case JT_ALL: case JT_RANGE: + if (no_bnl_cache) + goto no_join_cache; if (cache_level == 1) prev_cache= 0; if ((tab->cache= new (root) JOIN_CACHE_BNL(join, tab, prev_cache))) @@ -32100,6 +32105,36 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) return; } + char buff[NAME_LEN]; + String hint_str(buff, sizeof(buff), system_charset_info); + hint_str.length(0); + if (thd->lex->opt_hints_global) + { + char tmp_buff[NAME_LEN]; + String hints_tmp(tmp_buff, sizeof(tmp_buff), system_charset_info); + hints_tmp.length(0); + if (select_number == 1) + { + if (opt_hints_qb) + opt_hints_qb->append_qb_hint(thd, &hints_tmp); + thd->lex->opt_hints_global->print(thd, &hints_tmp); + } + else if (opt_hints_qb) + opt_hints_qb->append_qb_hint(thd, &hints_tmp); + + if (hints_tmp.length() > 0) + { + hint_str.append(STRING_WITH_LEN("/*+ ")); + hint_str.append(hints_tmp); + hint_str.append(STRING_WITH_LEN("*/ ")); + } + } + + if (hint_str.length() > 0 && (sel_type == SELECT_CMD || + sel_type == INSERT_CMD || + sel_type == REPLACE_CMD)) + str->append(hint_str); + /* First add options */ if (options & SELECT_STRAIGHT_JOIN) str->append(STRING_WITH_LEN("straight_join ")); @@ -32153,7 +32188,11 @@ void st_select_lex::print(THD *thd, String *str, enum_query_type query_type) query_type); } if (sel_type == UPDATE_CMD || sel_type == DELETE_CMD) + { str->append(get_explainable_cmd_name(sel_type)); + if (hint_str.length() > 0) + str->append(hint_str); + } if (sel_type == DELETE_CMD) { str->append(STRING_WITH_LEN(" from ")); diff --git a/sql/sql_string.h b/sql/sql_string.h index 9f97923c194..dd1da64c94a 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -1015,7 +1015,10 @@ public: { return Binary_string::append(s); } - + bool append(const char *s) + { + return append(s, (uint) strlen(s)); + } inline bool append(char chr) { return Binary_string::append_char(chr); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 96067bd4fd6..596e4db30fc 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8981,6 +8981,7 @@ query_specification_start: if (!(sel= lex->alloc_select(TRUE)) || lex->push_select(sel)) MYSQL_YYABORT; sel->init_select(); + sel->set_optimizer_hints($2); sel->braces= FALSE; } select_options @@ -9153,6 +9154,7 @@ query_expression_body: query_simple { Lex->push_select($1); + Lex->resolve_optimizer_hints(); if (!($$= Lex->create_unit($1))) MYSQL_YYABORT; } @@ -13593,11 +13595,13 @@ insert: } insert_start insert_lock_option opt_ignore opt_into insert_table { + Lex->first_select_lex()->set_optimizer_hints($2); Select->set_lock_for_tables($5, true, false); } insert_field_spec opt_insert_update opt_returning - stmt_end + insert_stmt_end { + Lex->resolve_optimizer_hints(); Lex->mark_first_table_as_inserting(); thd->get_stmt_da()->reset_current_row_for_warning(0); } @@ -13613,6 +13617,7 @@ replace: } insert_start replace_lock_option opt_into insert_table { + Lex->first_select_lex()->set_optimizer_hints($2); Select->set_lock_for_tables($5, true, false); } insert_field_spec opt_returning @@ -13633,6 +13638,14 @@ insert_start: { ; stmt_end: { + Lex->resolve_optimizer_hints(); + Lex->pop_select(); //main select + if (Lex->check_main_unit_semantics()) + MYSQL_YYABORT; + } + ; + +insert_stmt_end: { Lex->pop_select(); //main select if (Lex->check_main_unit_semantics()) MYSQL_YYABORT; @@ -13894,6 +13907,7 @@ update: if (Lex->main_select_push()) MYSQL_YYABORT; lex->init_select(); + Lex->first_select_lex()->set_optimizer_hints($2); lex->sql_command= SQLCOM_UPDATE; lex->duplicates= DUP_ERROR; } @@ -13985,11 +13999,13 @@ delete: mysql_init_delete(lex); lex->ignore= 0; lex->first_select_lex()->order_list.empty(); + lex->first_select_lex()->set_optimizer_hints($2); } delete_part2 { if (Lex->check_cte_dependencies_and_resolve_references()) MYSQL_YYABORT; + Lex->resolve_optimizer_hints(); } ; diff --git a/sql/table.h b/sql/table.h index 0cd63d75ce4..80fe8c7d208 100644 --- a/sql/table.h +++ b/sql/table.h @@ -84,6 +84,8 @@ class Table_function_json_table; class Open_table_context; class MYSQL_LOG; struct rpl_group_info; +class Opt_hints_qb; +class Opt_hints_table; /* Used to identify NESTED_JOIN structures within a join (applicable only to @@ -2992,6 +2994,11 @@ struct TABLE_LIST List *partition_names; #endif /* WITH_PARTITION_STORAGE_ENGINE */ + /** Table level optimizer hints for this table. */ + Opt_hints_table *opt_hints_table; + /* Hints for query block of this table. */ + Opt_hints_qb *opt_hints_qb; + void calc_md5(char *buffer); int view_check_option(THD *thd, bool ignore_failure); bool create_field_translation(THD *thd); diff --git a/tests/mysql_client_test.c b/tests/mysql_client_test.c index b498894d7a9..ca797f30f7c 100644 --- a/tests/mysql_client_test.c +++ b/tests/mysql_client_test.c @@ -20050,6 +20050,33 @@ static void test_bug17512527() } #endif +/** + Parser for optimizer hints +*/ +static void test_optimizer_hints() +{ + MYSQL_RES *result; + int rc; + + myheader("test_optimizer_hints"); + + rc= mysql_query(mysql, "SELECT /*+ "); + DIE_UNLESS(rc); + + rc= mysql_query(mysql, "SELECT /*+ ICP(`test"); + DIE_UNLESS(rc); + + rc= mysql_query(mysql, "SELECT /*+ ICP(`test*/ 1"); + myquery(rc); + result= mysql_store_result(mysql); + mytest(result); + (void) my_process_result_set(result); + mysql_free_result(result); + + rc= mysql_query(mysql, "SELECT /*+ ICP(`test*/`*/ 1"); + DIE_UNLESS(rc); +} + /* Check compressed protocol @@ -23297,6 +23324,7 @@ static struct my_tests_st my_tests[]= { #ifndef _WIN32 { "test_bug17512527", test_bug17512527}, #endif + { "test_optimizer_hints", test_optimizer_hints}, { "test_compressed_protocol", test_compressed_protocol }, { "test_big_packet", test_big_packet }, { "test_prepare_analyze", test_prepare_analyze },