#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<Parser, TokenID::tAT>;

  using TokenCOMMA= TokenParser<Parser, TokenID::tCOMMA>;

  using TokenEOF= TokenParser<Parser, TokenID::tEOF>;

  using Keyword_QB_NAME= TokenParser<Parser, TokenID::keyword_QB_NAME>;

  using Keyword_MAX_EXECUTION_TIME=
          TokenParser<Parser, TokenID::keyword_MAX_EXECUTION_TIME>;

  using Keyword_SUBQUERY= TokenParser<Parser, TokenID::keyword_SUBQUERY>;

  class Identifier: public TokenParser<Parser, TokenID::tIDENT>
  {
  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<Parser, TokenID::tUNSIGNED_NUMBER>
  {
  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<char *>(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<Parser, TokenID::tLPAREN>;

  using RParen= TokenParser<Parser, TokenID::tRPAREN>;


  // 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<Parser,
                                                  Table_level_hint_type_cond>
  {
  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<Parser,
                                                  Index_level_hint_type_cond>
  {
  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<Parser, TokenAT, Query_block_name>
  {
  public:
    using AND2::AND2;
    using AND2::operator=;
  };

  /*
    opt_qb_name ::=  [ @ query_block_name ]
  */
  class Opt_qb_name: public OPT<Parser, At_query_block_name>
  {
  public:
    using OPT::OPT;
  };

  /*
    hint_param_table ::= table_name opt_qb_name
  */
  class Hint_param_table: public AND2<Parser, Table_name, Opt_qb_name>
  {
  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<Hint_param_table,
                                               Parser>;


  class Opt_hint_param_table_list: public LIST<Parser,
                                               Hint_param_table_list_container,
                                               Hint_param_table,
                                               TokenID::tCOMMA, 0>
  {
    using LIST::LIST;
  };

  /*
    table_name_list ::= table_name [ {, table_name }... ]
    opt_table_name_list ::= [ table_name_list ]
  */
  using Table_name_list_container= List_container<Table_name, Parser>;

  class Opt_table_name_list: public LIST<Parser,
                                         Table_name_list_container,
                                         Table_name, TokenID::tCOMMA, 0>
  {
  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<Hint_param_index,
                                                        Parser>;

  class Opt_hint_param_index_list: public LIST<Parser,
                                               Hint_param_index_list_container,
                                               Hint_param_index,
                                               TokenID::tCOMMA, 0>
  {
  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<Parser,
                                                    At_query_block_name,
                                                    Table_name>
  {
  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<Parser,
                                          Hint_param_table_ext_container,
                                          Hint_param_table,
                                          At_query_block_name_table_name>
  {
  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<Parser,
                                      Table_level_hint_type,
                                      LParen,
                                      Table_level_hint_body,
                                      RParen>
  {
  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<Parser,
                                           Hint_param_table_ext,
                                           Opt_hint_param_index_list>
  {
  public:
    using AND2::AND2;
  };

public:
  // index_level_hint ::= index_level_hint_type ( index_level_hint_body )
  class Index_level_hint: public AND4<Parser,
                                      Index_level_hint_type,
                                      LParen,
                                      Index_level_hint_body,
                                      RParen>,
                           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<Parser, TokenAT, QB_path_element_select_num>
  {
  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<Parser, TokenAT, QB_path_element_select_num>
  {
  public:
    using AND2::AND2;
    using AND2::operator=;
  };

  class Opt_at_QB_path_element_select_num:
    public OPT<Parser, At_QB_path_element_select_num2>
  {
  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<Parser, QB_path_element_view_name,
                                              Opt_at_QB_path_element_select_num>
  {
  public:
    using AND2::AND2;
  };

  // qb_path_element ::= @ qb_path_element_select_num | qb_path_element_view_sel
  class QB_path_element: public OR2<Parser, At_QB_path_element_select_num,
                                            QB_path_element_view_sel>
  {
  public:
    using OR2::OR2;
  };

  // Container for query block path elements
  class Query_block_path_list: public List_container<QB_path_element, Parser>
  {
  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<Parser,
                                      Query_block_path_list,
                                      QB_path_element,
                                      TokenID::tDOT, 0>
  {
    using LIST::LIST;
  };

  // opt_query_block_path ::= [, query_block_path]
  class Opt_query_block_path: public AND2<Parser,
                                          TokenCOMMA,
                                          Query_block_path>::Opt
  {
  public:
    using Opt::Opt;
  };

  // qb_name_with_opt_path ::= query_block_name [, query_block_path]
  class QB_name_with_opt_path: public AND2<Parser,
                                           Query_block_name,
                                           Opt_query_block_path>
  {
  public:
    using AND2::AND2;
  };

  // qb_name_hint ::= QB_NAME ( qb_name_with_opt_path )
  class Qb_name_hint: public AND4<Parser,
                                  Keyword_QB_NAME,
                                  LParen,
                                  QB_name_with_opt_path,
                                  RParen>,
                                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<Parser,
                                  Keyword_MAX_EXECUTION_TIME,
                                  LParen,
                                  Unsigned_Number,
                                  RParen>,
                                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<Parser,
                                               Semijoin_hint_type_cond>
  {
  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<Parser, Semijoin_strategy_cond>
  {
  public:
    using TokenChoice::TokenChoice;
  };


  /*
    strategy_list ::= strategy_name [ {, strategy_name }... ]
    opt_strategy_list ::= [ strategy_list ]
  */
  using Semijoin_strategy_list_container= List_container<Semijoin_strategy,
                                                         Parser>;
  class Opt_sj_strategy_list: public LIST<Parser,
                                       Semijoin_strategy_list_container,
                                       Semijoin_strategy, TokenID::tCOMMA, 0>
  {
  public:
    using LIST::LIST;
  };


  class Hint_param_opt_sj_strategy_list: public LIST<Parser,
                                       Semijoin_strategy_list_container,
                                       Semijoin_strategy, TokenID::tCOMMA, 0>
  {
  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<Parser,
                                       At_query_block_name_opt_strategy_list,
                                       Hint_param_opt_sj_strategy_list>
  {
  public:
    using OR2::OR2;
  };

public:
  /*
    semijoin_hint ::= semijoin_hint_type ( semijoin_hint_body )
  */
  class Semijoin_hint: public AND4<Parser,
                                   Semijoin_hint_type,
                                   LParen,
                                   Semijoin_hint_body,
                                   RParen>,
                        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<Parser, Subquery_strategy_cond>
  {
  public:
    using TokenChoice::TokenChoice;
  };

  class Hint_param_subquery_strategy: public TokenChoice<Parser,
                                                         Subquery_strategy_cond>
  {
  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<Parser,
                                       At_query_block_name_subquery_strategy,
                                       Hint_param_subquery_strategy>
  {
  public:
    using OR2::OR2;
  };


public:
  // subquery_hint ::= SUBQUERY( subquery_hint_body )
  class Subquery_hint: public AND4<Parser,
                                  Keyword_SUBQUERY,
                                  LParen,
                                  Subquery_hint_body,
                                  RParen>,
                       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<Parser, Table_name, Opt_qb_name>
  {
  public:
    using AND2::AND2;
  };

  using Table_name_ext_container= List_container<Table_name_ext,
                                                 Parser>;

  /*
    table_name_ext_list ::= table_name_ext [ {, table_name_ext}... ]
  */
  class Table_name_ext_list: public LIST<Parser,
                                         Table_name_ext_container,
                                         Table_name_ext,
                                         TokenID::tCOMMA, 0>
  {
    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<At_query_block_name&>(src))),
      m_tables(std::move(static_cast<Table_name_ext_list&>(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<At_query_block_name&>(src));
      m_tables= std::move(static_cast<Table_name_ext_list&>(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<Parser,
                                                 Join_order_hint_type_cond>
  {
  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<Parser,
                                   Join_order_hint_type,
                                   LParen,
                                   Table_level_hint_body_extended,
                                   RParen>,
                         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_name_and_Qb> 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<Parser,
                         Index_level_hint,
                         Table_level_hint,
                         Qb_name_hint,
                         Max_execution_time_hint,
                         Semijoin_hint,
                         Subquery_hint,
                         Join_order_hint>
  {
  public:
    using OR7::OR7;
  };

private:
  // hint_list ::= hint [ hint... ]
  using Hint_list_container= List_container<Hint, Parser>;

  class Hint_list: public LIST<Parser, Hint_list_container,
                               Hint, TokenID::tNULL/*not separated list*/, 1>
  {
  public:
    using LIST::LIST;
    
    bool resolve(Parse_context *pc) const;
  };

public:
  /*
    The main rule:
      hints ::= hint_list EOF
  */
  class Hints: public AND2<Parser, Hint_list, TokenEOF>
  {
  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
