#ifndef OPT_HINTS_PARSER_H #define OPT_HINTS_PARSER_H /* Copyright (c) 2024, MariaDB This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ #include "lex_ident_sys.h" #include "opt_hints_structs.h" #include "simple_tokenizer.h" #include "sql_list.h" #include "sql_string.h" #include "sql_type_int.h" #include "simple_parser.h" class st_select_lex; class Opt_hints_qb; /** Environment data for the name resolution phase */ struct Parse_context { THD * const thd; MEM_ROOT *mem_root; st_select_lex *select; Parse_context(THD *thd, st_select_lex *select); Parse_context(Parse_context *pc, st_select_lex *select); }; class Optimizer_hint_tokenizer: public Extended_string_tokenizer { public: Optimizer_hint_tokenizer(CHARSET_INFO *cs, const LEX_CSTRING &hint) :Extended_string_tokenizer(cs, hint) { } // Let's use "enum class" to easier distinguish token IDs vs rule names enum class TokenID { // Special purpose tokens: tNULL= 0, // returned if the tokenizer failed to detect a token // also used if the parser failed to parse a token tEMPTY= 1, // returned on empty optional constructs in a grammar like: // rule ::= [ rule1 ] // when rule1 does not present in the input. tEOF= 2, // returned when the end of input is reached // One character tokens tDOT= '.', tCOMMA= ',', tAT= '@', tLPAREN= '(', tRPAREN= ')', // Other token types tIDENT= 'i', tUNSIGNED_NUMBER= 'n', // Keywords keyword_BKA = 256, // Value must be greater than any of the above keyword_BNL, keyword_NO_BKA, keyword_NO_BNL, keyword_NO_ICP, keyword_NO_MRR, keyword_NO_RANGE_OPTIMIZATION, keyword_MRR, keyword_QB_NAME, keyword_MAX_EXECUTION_TIME, keyword_SEMIJOIN, keyword_NO_SEMIJOIN, keyword_SUBQUERY, keyword_MATERIALIZATION, keyword_FIRSTMATCH, keyword_LOOSESCAN, keyword_DUPSWEEDOUT, keyword_INTOEXISTS, keyword_JOIN_PREFIX, keyword_JOIN_SUFFIX, keyword_JOIN_ORDER, keyword_JOIN_FIXED_ORDER, keyword_DERIVED_CONDITION_PUSHDOWN, keyword_NO_DERIVED_CONDITION_PUSHDOWN, keyword_MERGE, keyword_NO_MERGE, keyword_SPLIT_MATERIALIZED, keyword_NO_SPLIT_MATERIALIZED, keyword_INDEX, keyword_NO_INDEX, keyword_JOIN_INDEX, keyword_NO_JOIN_INDEX, keyword_GROUP_INDEX, keyword_NO_GROUP_INDEX, keyword_ORDER_INDEX, keyword_NO_ORDER_INDEX, keyword_ROWID_FILTER, keyword_NO_ROWID_FILTER, keyword_INDEX_MERGE, keyword_NO_INDEX_MERGE }; class Token: public Lex_cstring { protected: TokenID m_id; public: Token() :Lex_cstring(), m_id(TokenID::tNULL) { } Token(const LEX_CSTRING &str, TokenID id) :Lex_cstring(str), m_id(id) { } TokenID id() const { return m_id; } static Token empty(const char *pos) { return Token(Lex_cstring(pos, pos), TokenID::tEMPTY); } operator bool() const { return m_id != TokenID::tNULL; } }; protected: Token get_token(CHARSET_INFO *cs); static TokenID find_keyword(const LEX_CSTRING &str); }; class Printable_parser_rule { public: virtual void append_args(THD *thd, String *str) const = 0; virtual ~Printable_parser_rule() {} }; class Optimizer_hint_parser: public Optimizer_hint_tokenizer, public Parser_templates { private: Token m_look_ahead_token; THD *m_thd; const char *m_start; bool m_syntax_error; bool m_fatal_error; public: Optimizer_hint_parser(THD *thd, CHARSET_INFO *cs, const LEX_CSTRING &hint) :Optimizer_hint_tokenizer(cs, hint), m_look_ahead_token(get_token(cs)), m_thd(thd), m_start(hint.str), m_syntax_error(false), m_fatal_error(false) { } THD *thd() const { return m_thd; } bool set_syntax_error() { m_syntax_error= true; return false; } bool set_fatal_error() { m_fatal_error= true; return false; } // Calculate the line number inside the whole hint uint lineno(const char *ptr) const { DBUG_ASSERT(m_start <= ptr); DBUG_ASSERT(ptr <= m_end); uint lineno= 0; for ( ; ptr >= m_start; ptr--) { if (*ptr == '\n') lineno++; } return lineno; } uint lineno() const { return lineno(m_ptr); } TokenID look_ahead_token_id() const { return is_error() ? TokenID::tNULL : m_look_ahead_token.id(); } /* Return an empty token at the position of the current look ahead token with a zero length. Used for optional grammar constructs. For example, if the grammar is "rule ::= ruleA [ruleB] ruleC" and the input is "A C", then: - the optional rule "ruleB" will point to the input position "C" with a zero length - while the rule "ruleC" will point to the same input position "C" with a non-zero length */ Token empty_token() const { return Token::empty(m_look_ahead_token.str); } static Token null_token() { return Token(); } /* Return the current look ahead token and scan the next one */ Token shift() { DBUG_ASSERT(!is_error()); const Token res= m_look_ahead_token; m_look_ahead_token= get_token(m_cs); return res; } public: /* Return the current look ahead token if it matches the given ID and scan the next one. */ Token token(TokenID id) { if (m_look_ahead_token.id() != id || is_error()) return Token(); return shift(); } bool is_error() const { return m_syntax_error || m_fatal_error; } bool is_syntax_error() const { return m_syntax_error; } bool is_fatal_error() const { return m_fatal_error; } bool parse_token_list(THD *thd); // For debug purposes void push_warning_syntax_error(THD *thd, uint lineno); private: using Parser= Optimizer_hint_parser; // for a shorter notation // Rules consisting of a single token using TokenAT= TokenParser; using TokenCOMMA= TokenParser; using TokenEOF= TokenParser; using Keyword_QB_NAME= TokenParser; using Keyword_MAX_EXECUTION_TIME= TokenParser; using Keyword_SUBQUERY= TokenParser; class Identifier: public TokenParser { public: using TokenParser::TokenParser; Identifier(Token &&tok) :TokenParser(std::move(tok)) { } Lex_ident_cli_st to_ident_cli() const { Lex_ident_cli_st cli; if (length >= 2 && (str[0] == '`' || str[0] == '"')) return cli.set_ident_quoted(str + 1, length - 2, true, str[0]); return cli.set_ident(str, length, true); } Lex_ident_sys to_ident_sys(THD *thd) const { const Lex_ident_cli_st cli= to_ident_cli(); return Lex_ident_sys(thd, &cli); } }; class Unsigned_Number: public TokenParser { public: using TokenParser::TokenParser; /* Converts token string to a non-negative number ( >=0 ). Returns the converted number if the conversion succeeds. Returns non-NULL ULonglong_null value on successful string conversion and NULL ULonglong_null if the conversion failed or the number is negative */ ULonglong_null get_ulonglong() const { int error; char *end= const_cast(str + length); longlong n= my_strtoll10(str, &end, &error); if (error != 0 || end != str + length || n < 0) return ULonglong_null(0, true); return ULonglong_null(n, false); } }; using LParen= TokenParser; using RParen= TokenParser; // Rules consisting of multiple choices of tokens // table_level_hint_type ::= BKA | BNL | NO_BKA | NO_BNL class Table_level_hint_type_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_BKA || id == TokenID::keyword_BNL || id == TokenID::keyword_NO_BKA || id == TokenID::keyword_NO_BNL || id == TokenID::keyword_DERIVED_CONDITION_PUSHDOWN || id == TokenID::keyword_NO_DERIVED_CONDITION_PUSHDOWN || id == TokenID::keyword_MERGE || id == TokenID::keyword_NO_MERGE || id == TokenID::keyword_SPLIT_MATERIALIZED || id == TokenID::keyword_NO_SPLIT_MATERIALIZED; } }; class Table_level_hint_type: public TokenChoice { public: using TokenChoice::TokenChoice; }; /* index_level_hint_type ::= MRR | NO_RANGE_OPTIMIZATION | NO_ICP | NO_MRR | INDEX | NO_INDEX | JOIN_INDEX | NO_JOIN_INDEX | ORDER_INDEX | NO_ORDER_INDEX | GROUP_INDEX | NO_GROUP_INDEX */ class Index_level_hint_type_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_MRR || id == TokenID::keyword_NO_RANGE_OPTIMIZATION || id == TokenID::keyword_NO_ICP || id == TokenID::keyword_NO_MRR || id == TokenID::keyword_INDEX || id == TokenID::keyword_NO_INDEX || id == TokenID::keyword_JOIN_INDEX || id == TokenID::keyword_NO_JOIN_INDEX || id == TokenID::keyword_ORDER_INDEX || id == TokenID::keyword_NO_ORDER_INDEX || id == TokenID::keyword_GROUP_INDEX || id == TokenID::keyword_NO_GROUP_INDEX || id == TokenID::keyword_ROWID_FILTER || id == TokenID::keyword_NO_ROWID_FILTER || id == TokenID::keyword_INDEX_MERGE || id == TokenID::keyword_NO_INDEX_MERGE; } }; class Index_level_hint_type: public TokenChoice { public: using TokenChoice::TokenChoice; }; // Identifiers of various kinds // query_block_name ::= identifier class Query_block_name: public Identifier { public: using Identifier::Identifier; }; // table_name ::= identifier class Table_name: public Identifier { public: using Identifier::Identifier; }; // hint_param_index ::= identifier class Hint_param_index: public Identifier { public: using Identifier::Identifier; }; // More complex rules /* at_query_block_name ::= @ query_block_name */ class At_query_block_name: public AND2 { public: using AND2::AND2; using AND2::operator=; }; /* opt_qb_name ::= [ @ query_block_name ] */ class Opt_qb_name: public OPT { public: using OPT::OPT; }; /* hint_param_table ::= table_name opt_qb_name */ class Hint_param_table: public AND2 { public: using AND2::AND2; }; /* hint_param_table_list ::= hint_param_table [ {, hint_param_table}... ] opt_hint_param_table_list ::= [ hint_param_table_list ] */ using Hint_param_table_list_container= List_container; class Opt_hint_param_table_list: public LIST { using LIST::LIST; }; /* table_name_list ::= table_name [ {, table_name }... ] opt_table_name_list ::= [ table_name_list ] */ using Table_name_list_container= List_container; class Opt_table_name_list: public LIST { public: using LIST::LIST; }; /* hint_param_index_list ::= hint_param_index [ {, hint_param_index }...] opt_hint_param_index_list ::= [ hint_param_index_list ] */ using Hint_param_index_list_container= List_container; class Opt_hint_param_index_list: public LIST { public: using LIST::LIST; }; /* hint_param_table_ext ::= hint_param_table | @ query_block_name table_name */ class At_query_block_name_table_name: public AND2 { public: using AND2::AND2; }; class Hint_param_table_ext_container: public Query_block_name, public Table_name { public: Hint_param_table_ext_container() { } Hint_param_table_ext_container(const Hint_param_table &hint_param_table) :Query_block_name(hint_param_table), Table_name(hint_param_table) { } Hint_param_table_ext_container(const At_query_block_name_table_name &qbt) :Query_block_name(qbt), Table_name(qbt) { } operator bool() const { return Query_block_name::operator bool() && Table_name::operator bool(); } }; class Hint_param_table_ext: public OR2C { public: using OR2C::OR2C; }; /* at_query_block_name_opt_table_name_list ::= @ query_block_name opt_table_name_list */ class At_query_block_name_opt_table_name_list: public AND2< Parser, At_query_block_name, Opt_table_name_list> { public: using AND2::AND2; }; /* table_level_hint_body: @ query_block_name opt_table_name_list | opt_hint_param_table_list */ class Table_level_hint_body: public OR2< Parser, At_query_block_name_opt_table_name_list, Opt_hint_param_table_list> { public: using OR2::OR2; }; // table_level_hint ::= table_level_hint_type ( table_level_hint_body ) class Table_level_hint: public AND4 { public: using AND4::AND4; bool resolve(Parse_context *pc) const; }; // index_level_hint_body ::= hint_param_table_ext opt_hint_param_index_list class Index_level_hint_body: public AND2 { public: using AND2::AND2; }; public: // index_level_hint ::= index_level_hint_type ( index_level_hint_body ) class Index_level_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; /* If no index names are given, this is a table level hint, for example: GROUP_INDEX(t1), NO_MRR(t2). Otherwise this is an index-level hints: NO_INDEX(t1 idx1, idx2) NO_ICP(t2 idx_a, idx_b, idx_c) */ bool is_table_level_hint() const { return is_empty(); } bool resolve(Parse_context *pc) const; void append_args(THD *thd, String *str) const override; }; // qb_path_element_view_name ::= identifier class QB_path_element_view_name: public Identifier { public: using Identifier::Identifier; }; // qb_path_element_select_num ::= identifier class QB_path_element_select_num: public Identifier { public: using Identifier::Identifier; }; // at_qb_path_element_select_num ::= @ qb_path_element_select_num class At_QB_path_element_select_num: public AND2 { public: using AND2::AND2; using AND2::operator=; }; // just a clone of At_QB_path_element_select_num to avoid inheritance issues class At_QB_path_element_select_num2: public AND2 { public: using AND2::AND2; using AND2::operator=; }; class Opt_at_QB_path_element_select_num: public OPT { public: using OPT::OPT; }; /* qb_path_element_view_sel ::= qb_path_element_view_name [ @ qb_path_element_select_num ] */ class QB_path_element_view_sel: public AND2 { public: using AND2::AND2; }; // qb_path_element ::= @ qb_path_element_select_num | qb_path_element_view_sel class QB_path_element: public OR2 { public: using OR2::OR2; }; // Container for query block path elements class Query_block_path_list: public List_container { public: static Query_block_path_list empty(const Parser &) { return Query_block_path_list(); } }; /* query_block_path ::= query_block_path_element [ {, query_block_path_element }... ] */ class Query_block_path: public LIST { using LIST::LIST; }; // opt_query_block_path ::= [, query_block_path] class Opt_query_block_path: public AND2::Opt { public: using Opt::Opt; }; // qb_name_with_opt_path ::= query_block_name [, query_block_path] class QB_name_with_opt_path: public AND2 { public: using AND2::AND2; }; // qb_name_hint ::= QB_NAME ( qb_name_with_opt_path ) class Qb_name_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; bool resolve(Parse_context *pc) const; void append_args(THD *thd, String *str) const override; String get_hint_string(THD *thd) const; }; public: // max_execution_time_hint ::= MAX_EXECUTION_TIME ( milliseconds ) class Max_execution_time_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; bool resolve(Parse_context *pc) const; void append_args(THD *thd, String *str) const override; ulonglong get_milliseconds() const; }; private: // semijoin_hint_type ::= SEMIJOIN | NO_SEMIJOIN class Semijoin_hint_type_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_SEMIJOIN || id == TokenID::keyword_NO_SEMIJOIN; } }; class Semijoin_hint_type: public TokenChoice { public: using TokenChoice::TokenChoice; }; // semijoin_strategy ::= MATERIALIZATION | FIRSTMATCH | LOOSESCAN | DUPSWEEDOUT class Semijoin_strategy_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_MATERIALIZATION || id == TokenID::keyword_FIRSTMATCH || id == TokenID::keyword_LOOSESCAN || id == TokenID::keyword_DUPSWEEDOUT; } }; class Semijoin_strategy: public TokenChoice { public: using TokenChoice::TokenChoice; }; /* strategy_list ::= strategy_name [ {, strategy_name }... ] opt_strategy_list ::= [ strategy_list ] */ using Semijoin_strategy_list_container= List_container; class Opt_sj_strategy_list: public LIST { public: using LIST::LIST; }; class Hint_param_opt_sj_strategy_list: public LIST { public: using LIST::LIST; }; /* at_query_block_name_opt_strategies_list ::= @ query_block_name opt_strategies_list */ class At_query_block_name_opt_strategy_list: public AND2< Parser, At_query_block_name, Opt_sj_strategy_list> { public: using AND2::AND2; }; /* semijoin_hint_body: @ query_block_name opt_sj_strategy_list | opt_sj_strategy_list */ class Semijoin_hint_body: public OR2 { public: using OR2::OR2; }; public: /* semijoin_hint ::= semijoin_hint_type ( semijoin_hint_body ) */ class Semijoin_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; bool resolve(Parse_context *pc) const; void append_args(THD *thd, String *str) const override; private: Opt_hints_qb* resolve_for_qb_name(Parse_context *pc, bool hint_state, const Lex_ident_sys *qb_name) const; void fill_strategies_map(Opt_hints_qb *qb) const; void add_strategy_to_map(TokenID token_id, Opt_hints_qb *qb) const; void append_strategy_name(TokenID token_id, String *str) const; }; private: // subquery_strategy ::= MATERIALIZATION | INTOEXISTS class Subquery_strategy_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_MATERIALIZATION || id == TokenID::keyword_INTOEXISTS; } }; class Subquery_strategy: public TokenChoice { public: using TokenChoice::TokenChoice; }; class Hint_param_subquery_strategy: public TokenChoice { public: using TokenChoice::TokenChoice; }; /* at_query_block_name_subquery_strategy ::= @ query_block_name subquery_strategy */ class At_query_block_name_subquery_strategy: public AND2< Parser, At_query_block_name, Subquery_strategy> { public: using AND2::AND2; }; /* subquery_hint_body: @ query_block_name subquery_strategy | subquery_strategy */ class Subquery_hint_body: public OR2 { public: using OR2::OR2; }; public: // subquery_hint ::= SUBQUERY( subquery_hint_body ) class Subquery_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; bool resolve(Parse_context *pc) const; void append_args(THD *thd, String *str) const override; private: void set_subquery_strategy(TokenID token_id, Opt_hints_qb *qb) const; Opt_hints_qb* resolve_for_qb_name(Parse_context *pc, TokenID token_id, const Lex_ident_sys *qb_name) const; }; // // Structures required for join order hints // /* join_order_hint_type ::= JOIN_FIXED_ORDER | JOIN_ORDER | JOIN_PREFIX | JOIN_SUFFIX */ class Join_order_hint_type_cond { public: static bool allowed_token_id(TokenID id) { return id == TokenID::keyword_JOIN_FIXED_ORDER || id == TokenID::keyword_JOIN_ORDER || id == TokenID::keyword_JOIN_PREFIX || id == TokenID::keyword_JOIN_SUFFIX; } }; // table_name_ext ::= table_name opt_qb_name class Table_name_ext: public AND2 { public: using AND2::AND2; }; using Table_name_ext_container= List_container; /* table_name_ext_list ::= table_name_ext [ {, table_name_ext}... ] */ class Table_name_ext_list: public LIST { using LIST::LIST; }; /* at_query_block_name_table_name_ext_list ::= @ query_block_name table_name_ext_list */ class At_query_block_name_table_name_ext_list: public AND2< Parser, At_query_block_name, Table_name_ext_list> { public: using AND2::AND2; }; class Table_level_hint_body_extended_container { public: At_query_block_name m_qb_name; Table_name_ext_list m_tables; Table_level_hint_body_extended_container() = default; Table_level_hint_body_extended_container( At_query_block_name_table_name_ext_list &&src) : m_qb_name(std::move(static_cast(src))), m_tables(std::move(static_cast(src))) {} Table_level_hint_body_extended_container(Table_name_ext_list &&tables) : m_tables(std::move(tables)) {} Table_level_hint_body_extended_container &operator=( At_query_block_name_table_name_ext_list &&src) { m_qb_name= std::move(static_cast(src)); m_tables= std::move(static_cast(src)); return *this; } Table_level_hint_body_extended_container &operator=( Table_name_ext_list &&tables) { m_tables= std::move(tables); return *this; } operator bool() const { return (bool) m_qb_name || (bool) m_tables; } }; class Table_level_hint_body_extended: public OR2C< Parser, Table_level_hint_body_extended_container, At_query_block_name_table_name_ext_list, Table_name_ext_list> { public: using OR2C::OR2C; }; class Join_order_hint_type: public TokenChoice { public: using TokenChoice::TokenChoice; }; /* Struct representing table names listed in optimizer hints bodies. They may optionally include query block names, for example: t1, t2@qb1, t3, t4@qb5 */ struct Table_name_and_Qb: public Sql_alloc { Lex_ident_sys table_name; Lex_ident_sys qb_name; // may be empty Table_name_and_Qb(const Lex_ident_sys& tbl, const Lex_ident_sys& qb) : table_name(tbl), qb_name(qb) {} Table_name_and_Qb(Lex_ident_sys&& tbl, Lex_ident_sys&& qb) : table_name(std::move(tbl)), qb_name(std::move(qb)) {} }; /* join_order_hint ::= join_order_hint_type ( table_level_hint_body ) */ class Join_order_hint: public AND4, public Printable_parser_rule { public: using AND4::AND4; opt_hints_enum hint_type= MAX_HINT_ENUM; bool resolve(Parse_context *pc); void append_args(THD *thd, String *str) const override; /* Table names (optionally augmented with query block names) listed in the hint body. */ List table_names; }; // // End of structures required for join order hints // /* hint ::= index_level_hint | table_level_hint | qb_name_hint | max_execution_time_hint | semijoin_hint | subquery_hint | join_order_hint */ class Hint: public OR7 { public: using OR7::OR7; }; private: // hint_list ::= hint [ hint... ] using Hint_list_container= List_container; class Hint_list: public LIST { public: using LIST::LIST; bool resolve(Parse_context *pc) const; }; public: /* The main rule: hints ::= hint_list EOF */ class Hints: public AND2 { public: using AND2::AND2; }; }; /* These wrapper class is needed to use a forward declarations in sql_lex.h instead of including the entire opt_hints_parser.h. (forward declarations of qualified nested classes are not possible in C++) */ class Optimizer_hint_parser_output: public Optimizer_hint_parser::Hints { public: using Hints::Hints; }; #endif // OPT_HINTS_PARSER