/* Copyright (c) 2016, 2024, Oracle and/or its affiliates.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License, version 2.0,
   as published by the Free Software Foundation.

   This program is designed to work with certain software (including
   but not limited to OpenSSL) that is licensed under separate terms,
   as designated in a particular file or component or in included license
   documentation.  The authors of MySQL hereby grant you an additional
   permission to link the program and your derivative works with the
   separately licensed software that they have either included with
   the program or referenced in the documentation.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License, version 2.0, for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#include "sql/dd/info_schema/show_query_builder.h"  // Select_lex_builder

#include <assert.h>

#include "sql/auth/sql_security_ctx.h"
#include "sql/item_cmpfunc.h"  // Item_func_like
#include "sql/item_func.h"
#include "sql/key.h"
#include "sql/key_spec.h"
#include "sql/parse_location.h"
#include "sql/parse_tree_helpers.h"
#include "sql/parse_tree_items.h"  // PTI_simple_ident_ident
#include "sql/parse_tree_node_base.h"
#include "sql/parse_tree_nodes.h"  // PT_select_item_list
#include "sql/query_options.h"     // OPTION_SELECT_FOR_SHOW
#include "sql/sql_class.h"
#include "sql/sql_lex.h"  // Query_options
#include "sql/strfunc.h"
#include "sql_string.h"
#include "string_with_len.h"

class Item;

namespace dd {
namespace info_schema {

static const Query_options options = {
    OPTION_SELECT_FOR_SHOW /* query_spec_options */
};

Select_lex_builder::Select_lex_builder(const POS *pc, THD *thd)
    : m_pos(pc),
      m_thd(thd),
      m_select_item_list(nullptr),
      m_where_clause(nullptr),
      m_order_by_list(nullptr) {
  m_table_reference_list.init(m_thd->mem_root);
}

bool Select_lex_builder::add_to_select_item_list(Item *expr) {
  // Prepare list if not exist.
  if (!m_select_item_list) {
    m_select_item_list = new (m_thd->mem_root) PT_select_item_list(*m_pos);

    if (m_select_item_list == nullptr) return true;
  }

  m_select_item_list->push_back(expr);

  return false;
}

// Add item representing star in "SELECT '*' ...".
bool Select_lex_builder::add_star_select_item() {
  Item_asterisk *ident_star =
      new (m_thd->mem_root) Item_asterisk(*m_pos, nullptr, nullptr);
  if (ident_star == nullptr) return true;

  return add_to_select_item_list(ident_star);
}

/**
  Add item representing a column as,
  "SELECT <field_name> AS <alias>, ...".
*/
bool Select_lex_builder::add_select_item(const LEX_CSTRING &field_name,
                                         const LEX_CSTRING &alias) {
  /* ... FIELD_NAME ... */
  PTI_simple_ident_ident *ident_field =
      new (m_thd->mem_root) PTI_simple_ident_ident(*m_pos, field_name);
  if (ident_field == nullptr) return true;

  /* ... FIELD_NAME as alias ... */
  PTI_expr_with_alias *expr;
  expr = new (m_thd->mem_root)
      PTI_expr_with_alias(*m_pos, ident_field, m_pos->cpp, alias);
  if (expr == nullptr) return true;

  return add_to_select_item_list(expr);
}

/**
  Add expression item representing a column as,
  "SELECT <expr> AS <alias>, ...".
*/
bool Select_lex_builder::add_select_expr(Item *select_list_item,
                                         const LEX_CSTRING &alias) {
  /* ... FIELD_NAME as alias ... */
  PTI_expr_with_alias *expr = new (m_thd->mem_root)
      PTI_expr_with_alias(*m_pos, select_list_item, m_pos->cpp, alias);
  if (expr == nullptr) return true;

  return add_to_select_item_list(expr);
}

/**
  Add item representing a FROM clause table as,
  @code
  SELECT ... FROM <schema_name>.<table_name> ...
  @endcode
*/
bool Select_lex_builder::add_from_item(const LEX_CSTRING &schema_name,
                                       const LEX_CSTRING &table_name) {
  /*
    make_table_list() might alter the database and table name
    strings. Create copies and leave the original values
    unaltered.
  */

  /* ... schame_name ... */
  LEX_CSTRING tmp_db_name;
  if (lex_string_strmake(m_thd->mem_root, &tmp_db_name, schema_name.str,
                         schema_name.length))
    return true;

  /* ... <table_name> ... */
  LEX_CSTRING tmp_table_name;
  if (lex_string_strmake(m_thd->mem_root, &tmp_table_name, table_name.str,
                         table_name.length))
    return true;

  /* ... schame_name.<table_name> ... */
  Table_ident *table_ident =
      new (m_thd->mem_root) Table_ident(tmp_db_name, tmp_table_name);
  if (table_ident == nullptr) return true;

  /* ... FROM schame_name.<table_name> ... */
  PT_table_factor_table_ident *table_factor =
      new (m_thd->mem_root) PT_table_factor_table_ident(
          *m_pos, table_ident, nullptr, NULL_CSTR, nullptr, nullptr);
  if (table_factor == nullptr) return true;

  if (m_table_reference_list.push_back(table_factor)) return true;

  return false;
}

/**
  Add item representing a FROM clause table as,
  @code
  SELECT ... FROM <sub query or derived table> ...
  @endcode
 */
bool Select_lex_builder::add_from_item(PT_derived_table *dt) {
  if (m_table_reference_list.push_back(dt)) return true;

  return false;
}

// Prepare item representing a LIKE condition.
Item *Select_lex_builder::prepare_like_item(const LEX_CSTRING &field_name,
                                            const String *wild) {
  /* ... FIELD_NAME ... */
  PTI_simple_ident_ident *ident_field =
      new (m_thd->mem_root) PTI_simple_ident_ident(*m_pos, field_name);
  if (ident_field == nullptr) return nullptr;

  /* ... <value> ... */
  LEX_STRING *lex_string =
      static_cast<LEX_STRING *>(m_thd->alloc(sizeof(LEX_STRING)));
  if (lex_string == nullptr) return nullptr;
  lex_string->length = wild->length();
  lex_string->str = m_thd->strmake(wild->ptr(), wild->length());
  if (lex_string->str == nullptr) return nullptr;

  PTI_text_literal_text_string *wild_string =
      new (m_thd->mem_root) PTI_text_literal_text_string(
          *m_pos, false, *lex_string);  // TODO WL#6629 check is_7bit
  if (wild_string == nullptr) return nullptr;

  /* ... field_name LIKE <value> ... */
  Item_func_like *func_like =
      new (m_thd->mem_root) Item_func_like(*m_pos, ident_field, wild_string);

  return func_like;
}

// Prepare item representing an equal to comparison condition.
Item *Select_lex_builder::prepare_equal_item(const LEX_CSTRING &field_name,
                                             const LEX_CSTRING &value) {
  /* ... FIELD_NAME ... */
  PTI_simple_ident_ident *ident_field =
      new (m_thd->mem_root) PTI_simple_ident_ident(*m_pos, field_name);
  if (ident_field == nullptr) return nullptr;

  /* ... <value> ... */
  LEX_STRING *lex_string =
      static_cast<LEX_STRING *>(m_thd->alloc(sizeof(LEX_STRING)));
  if (lex_string == nullptr) return nullptr;
  lex_string->length = value.length;
  lex_string->str = m_thd->strmake(value.str, value.length);
  if (lex_string->str == nullptr) return nullptr;

  PTI_text_literal_underscore_charset *value_string =
      new (m_thd->mem_root) PTI_text_literal_underscore_charset(
          *m_pos, false, system_charset_info,
          *lex_string);  // TODO WL#6629 check is_7bit
  if (value_string == nullptr) return nullptr;

  /* ... FIELD_NAME = <value> ... */
  Item_func_eq *func_eq =
      new (m_thd->mem_root) Item_func_eq(*m_pos, ident_field, value_string);

  return func_eq;
}

// Add a WHERE clause condition to Select_lex_builder.
bool Select_lex_builder::add_condition(Item *a) {
  assert(a != nullptr);

  /* ... WHERE cond ... */
  if (m_where_clause == nullptr) {
    m_where_clause = a;
  }
  /* ... WHERE <cond> AND <cond> ... */
  else {
    m_where_clause =
        flatten_associative_operator<Item_cond_and, Item_func::COND_AND_FUNC>(
            m_thd->mem_root, *m_pos, m_where_clause, a);

    if (m_where_clause == nullptr) return true;
  }

  return false;
}

// Add a ORDER BY clause field to Select_lex_builder.
bool Select_lex_builder::add_order_by(const LEX_CSTRING &field_name) {
  /* ... ORDER BY <field_name> ASC... */
  if (!m_order_by_list) {
    m_order_by_list = new (m_thd->mem_root) PT_order_list(*m_pos);
    if (m_order_by_list == nullptr) return true;
  }

  /* ... FIELD_NAME ... */
  PTI_simple_ident_ident *ident_field =
      new (m_thd->mem_root) PTI_simple_ident_ident(*m_pos, field_name);
  if (ident_field == nullptr) return true;

  PT_order_expr *expression =
      new (m_thd->mem_root) PT_order_expr(*m_pos, ident_field, ORDER_ASC);
  m_order_by_list->push_back(expression);

  return expression == nullptr;
}

/**
  This function build ParseTree node that represents this
  Select_lex_builder as sub-query.
*/
PT_derived_table *Select_lex_builder::prepare_derived_table(
    const LEX_CSTRING &table_alias) {
  PT_query_primary *query_specification = new (m_thd->mem_root)
      PT_query_specification(*m_pos, options, m_select_item_list,
                             m_table_reference_list, m_where_clause);

  if (query_specification == nullptr) return nullptr;

  PT_query_expression *query_expression =
      new (m_thd->mem_root) PT_query_expression(*m_pos, query_specification);
  if (query_expression == nullptr) return nullptr;

  PT_subquery *sub_query =
      new (m_thd->mem_root) PT_subquery(*m_pos, query_expression);
  if (sub_query == nullptr) return nullptr;

  Create_col_name_list column_names;
  column_names.init(m_thd->mem_root);
  PT_derived_table *derived_table = new (m_thd->mem_root)
      PT_derived_table(*m_pos, false, sub_query, table_alias, &column_names);

  return derived_table;
}

/**
  Prepare a Query_block using all the information information
  added to this Select_lex_builder.
*/
Query_block *Select_lex_builder::prepare_query_block() {
  PT_query_specification *query_specification = new (m_thd->mem_root)
      PT_query_specification(*m_pos, options, m_select_item_list,
                             m_table_reference_list, m_where_clause);
  if (query_specification == nullptr) return nullptr;

  PT_order *pt_order_by = nullptr;
  if (m_order_by_list) {
    pt_order_by = new (m_thd->mem_root) PT_order(*m_pos, m_order_by_list);
    if (pt_order_by == nullptr) return nullptr;
  }

  PT_query_expression *query_expression = new (m_thd->mem_root)
      PT_query_expression(*m_pos, query_specification, pt_order_by, nullptr);
  if (query_expression == nullptr) return nullptr;

  LEX *lex = m_thd->lex;
  Query_block *current_query_block = lex->current_query_block();

  lex->sql_command = SQLCOM_SELECT;
  Parse_context pc(m_thd, current_query_block);
  if (m_thd->is_error()) return nullptr;

  if (query_expression->contextualize(&pc)) return nullptr;
  if (pc.finalize_query_expression()) return nullptr;
  return current_query_block;
}

}  // namespace info_schema
}  // namespace dd
