/* Copyright (c) 2002, 2023, 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 also distributed 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 included with MySQL.

   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,
   51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */


/*
  Derived tables
  These were introduced by Sinisa <sinisa@mysql.com>
*/


#include "my_global.h"                          /* NO_EMBEDDED_ACCESS_CHECKS */
#include "sql_derived.h"
#include "sql_select.h"
#include "sql_resolver.h"
#include "sql_optimizer.h"                    // JOIN
#include "sql_view.h"                         // check_duplicate_names
#include "auth_common.h"                      // SELECT_ACL
#include "sql_tmp_table.h"                    // Tmp tables
#include "sql_union.h"                        // Query_result_union
#include "opt_trace.h"                        // opt_trace_disable_etc


/**
  Resolve a derived table or view reference, including recursively resolving
  contained subqueries.

  @param thd thread handle
  @param apply_semijoin Apply possible semi-join transforms if this is true

  @returns false if success, true if error
*/

bool TABLE_LIST::resolve_derived(THD *thd, bool apply_semijoin)
{
  DBUG_ENTER("TABLE_LIST::resolve_derived");

  if (!is_view_or_derived() || is_merged())
    DBUG_RETURN(false);

  const bool derived_tables_saved= thd->derived_tables_processing;

  thd->derived_tables_processing= true;

#ifndef NDEBUG
  for (SELECT_LEX *sl= derived->first_select(); sl; sl= sl->next_select())
  {
    // Make sure there are no outer references
    assert(sl->context.outer_context == NULL);
  }
#endif
  if (!(derived_result= new (thd->mem_root) Query_result_union))
    DBUG_RETURN(true);

  /*
    Prepare the underlying query expression of the derived table.
    The SELECT_STRAIGHT_JOIN option prevents semi-join transformation.
  */
  if (derived->prepare(thd, derived_result,
                       !apply_semijoin ? SELECT_NO_SEMI_JOIN : 0, 0))
    DBUG_RETURN(true);

  if (check_duplicate_names(derived->types, 0))
    DBUG_RETURN(true);

#ifndef NO_EMBEDDED_ACCESS_CHECKS
  /*
    A derived table is transparent with respect to privilege checking.
    This setting means that privilege checks ignore the derived table
    and are done properly in underlying base tables and views.
    SELECT_ACL is used because derived tables cannot be used for update,
    delete or insert.
  */
  if (is_derived())
    set_privileges(SELECT_ACL);
#endif

  thd->derived_tables_processing= derived_tables_saved;

  DBUG_RETURN(false);
}


/**
  Prepare a derived table or view for materialization.

  @param  thd   THD pointer

  @return false if successful, true if error
*/
bool TABLE_LIST::setup_materialized_derived(THD *thd)

{
  DBUG_ENTER("TABLE_LIST::setup_materialized_derived");

  assert(is_view_or_derived() && !is_merged() && table == NULL);

  DBUG_PRINT("info", ("algorithm: TEMPORARY TABLE"));

  Opt_trace_context *const trace= &thd->opt_trace;
  Opt_trace_object trace_wrapper(trace);
  Opt_trace_object trace_derived(trace, is_view() ? "view" : "derived");
  trace_derived.add_utf8_table(this).
    add("select#", derived->first_select()->select_number).
    add("materialized", true);

  set_uses_materialization();

  // Create the result table for the materialization
  const ulonglong create_options= derived->first_select()->active_options() |
                                  TMP_TABLE_ALL_COLUMNS;
  if (derived_result->create_result_table(thd, &derived->types, false, 
                                          create_options,
                                          alias, false, false))
    DBUG_RETURN(true);        /* purecov: inspected */

  table= derived_result->table;
  table->pos_in_table_list= this;

  // Make table's name same as the underlying materialized table
  set_name_temporary();

  table->s->tmp_table= NON_TRANSACTIONAL_TMP_TABLE;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
  if (referencing_view)
    table->grant= grant;
  else
    table->grant.privilege= SELECT_ACL;
#endif

  // Table is "nullable" if inner table of an outer_join
  if (is_inner_table_of_outer_join())
    table->set_nullable();

  // Add new temporary table to list of open derived tables
  table->next= thd->derived_tables;
  thd->derived_tables= table;

  for (SELECT_LEX *sl= derived->first_select(); sl; sl= sl->next_select())
  {
    /*
      Derived tables/view are materialized prior to UPDATE, thus we can skip
      them from table uniqueness check
    */
    sl->propagate_unique_test_exclusion();

    /*
      SELECT privilege is needed for all materialized derived tables and views,
      and columns must be marked for read, unless command is SHOW FIELDS.
    */
    if (thd->lex->sql_command == SQLCOM_SHOW_FIELDS)
      continue;

    if (sl->check_view_privileges(thd, SELECT_ACL, SELECT_ACL))
      DBUG_RETURN(true);

    // Set all selected fields to be read:
    // @todo Do not set fields that are not referenced from outer query
    assert(thd->mark_used_columns == MARK_COLUMNS_READ);
    List_iterator<Item> it(sl->all_fields);
    Item *item;
    Column_privilege_tracker tracker(thd, SELECT_ACL);
    Mark_field mf(thd->mark_used_columns);
    while ((item= it++))
    {
      if (item->walk(&Item::check_column_privileges, Item::WALK_PREFIX,
                     (uchar *)thd))
        DBUG_RETURN(true);
      item->walk(&Item::mark_field_in_map, Item::WALK_POSTFIX, (uchar *)&mf);
    }
  }

  DBUG_RETURN(false);
}


/**
  Optimize the query expression representing a derived table/view.

  @note
  If optimizer finds out that the derived table/view is of the type
  "SELECT a_constant" this functions also materializes it.

  @param thd thread handle

  @returns false if success, true if error.
*/

bool TABLE_LIST::optimize_derived(THD *thd)
{
  DBUG_ENTER("TABLE_LIST::optimize_derived");

  SELECT_LEX_UNIT *const unit= derived_unit();

  assert(unit && !unit->is_optimized());

  if (unit->optimize(thd) || thd->is_error())
    DBUG_RETURN(true);

  if (materializable_is_const() &&
      (create_derived(thd) || materialize_derived(thd)))
    DBUG_RETURN(true);

  DBUG_RETURN(false);
}


/**
  Create result table for a materialized derived table/view.

  @param thd     thread handle

  This function actually creates the result table for given 'derived'
  table/view, but it doesn't fill it.

  @returns false if success, true if error.
*/

bool TABLE_LIST::create_derived(THD *thd)
{
  DBUG_ENTER("TABLE_LIST::create_derived");

  SELECT_LEX_UNIT *const unit= derived_unit();

  // @todo: Be able to assert !table->is_created() as well
  assert(unit && uses_materialization() && table);

  /*
    Don't create result table if:
    1) Table is already created, or
    2) Table is a constant one with all NULL values.
  */
  if (table->is_created() ||                              // 1
      (select_lex->join != NULL &&                        // 2
       (select_lex->join->const_table_map & map())))      // 2
  {
    /*
      At this point, JT_CONST derived tables should be null rows. Otherwise
      they would have been materialized already.
    */
#ifndef NDEBUG
    if (table != NULL)
    {
      QEP_TAB *tab= table->reginfo.qep_tab;
      assert(tab == NULL ||
             tab->type() != JT_CONST ||
             table->has_null_row());
    }
#endif
    DBUG_RETURN(false);
  }
  /* create tmp table */
  Query_result_union *result= (Query_result_union*)unit->query_result();

  if (instantiate_tmp_table(table, table->key_info,
                            result->tmp_table_param.start_recinfo,
                            &result->tmp_table_param.recinfo,
                            unit->first_select()->active_options() |
                            thd->lex->select_lex->active_options() |
                            TMP_TABLE_ALL_COLUMNS,
                            thd->variables.big_tables, &thd->opt_trace))
    DBUG_RETURN(true);        /* purecov: inspected */

  table->file->extra(HA_EXTRA_WRITE_CACHE);
  table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);

  table->set_created();

  DBUG_RETURN(false);
}


/**
  Materialize derived table

  @param  thd	    Thread handle

  Derived table is resolved with temporary table. It is created based on the
  queries defined. After temporary table is materialized, if this is not
  EXPLAIN, then the entire unit / node is deleted. unit is deleted if UNION is
  used for derived table and node is deleted is it is a  simple SELECT.
  If you use this function, make sure it's not called at prepare.
  Due to evaluation of LIMIT clause it can not be used at prepared stage.

  @returns false if success, true if error.
*/

bool TABLE_LIST::materialize_derived(THD *thd)
{
  DBUG_ENTER("TABLE_LIST::materialize_derived");

  assert(is_view_or_derived() && uses_materialization());

  SELECT_LEX_UNIT *const unit= derived_unit();
  bool res= false;

  assert(table && table->is_created());

  if (unit->is_union())
  {
    // execute union without clean up
    res= unit->execute(thd);
  }
  else
  {
    SELECT_LEX *first_select= unit->first_select();
    JOIN *join= first_select->join;
    SELECT_LEX *save_current_select= thd->lex->current_select();
    thd->lex->set_current_select(first_select);

    assert(join && join->is_optimized());

    unit->set_limit(first_select);

    join->exec();
    res= join->error;
    thd->lex->set_current_select(save_current_select);
  }

  if (!res)
  {
    /*
      Here we entirely fix both TABLE_LIST and list of SELECT's as
      there were no derived tables
    */
    if (derived_result->flush())
      res= true;                  /* purecov: inspected */
  }

  DBUG_RETURN(res);
}


/**
   Clean up the query expression for a materialized derived table
*/

bool TABLE_LIST::cleanup_derived()
{
  assert(is_view_or_derived() && uses_materialization());

  return derived_unit()->cleanup(false);
}
