/* Copyright (c) 2001, 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 */

/*
  Process query expressions that are composed of

  1. UNION of query blocks, and/or

  2. have ORDER BY / LIMIT clauses in more than one level.

  An example of 2) is:

    (SELECT * FROM t1 ORDER BY a LIMIT 10) ORDER BY b LIMIT 5
*/

#include "sql/sql_union.h"

#include <sys/types.h>

#include <algorithm>
#include <atomic>
#include <cassert>
#include <cstdio>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "mem_root_deque.h"
#include "my_alloc.h"
#include "my_base.h"
#include "my_dbug.h"
#include "my_sqlcommand.h"
#include "my_sys.h"
#include "mysql/udf_registration_types.h"
#include "mysqld_error.h"
#include "prealloced_array.h"  // Prealloced_array
#include "scope_guard.h"
#include "sql/auth/auth_acls.h"
#include "sql/current_thd.h"
#include "sql/debug_sync.h"  // DEBUG_SYNC
#include "sql/field.h"
#include "sql/handler.h"
#include "sql/item.h"
#include "sql/item_subselect.h"
#include "sql/iterators/row_iterator.h"
#include "sql/join_optimizer/access_path.h"
#include "sql/join_optimizer/bit_utils.h"
#include "sql/join_optimizer/explain_access_path.h"
#include "sql/join_optimizer/join_optimizer.h"
#include "sql/join_optimizer/materialize_path_parameters.h"
#include "sql/mem_root_array.h"
#include "sql/mysqld.h"
#include "sql/opt_explain.h"
#include "sql/opt_explain_format.h"
#include "sql/opt_trace.h"
#include "sql/parse_tree_node_base.h"
#include "sql/parse_tree_nodes.h"  // PT_with_clause
#include "sql/parser_yystype.h"
#include "sql/pfs_batch_mode.h"
#include "sql/protocol.h"
#include "sql/query_options.h"
#include "sql/sql_base.h"  // fill_record
#include "sql/sql_class.h"
#include "sql/sql_cmd.h"
#include "sql/sql_const.h"
#include "sql/sql_error.h"
#include "sql/sql_executor.h"
#include "sql/sql_lex.h"
#include "sql/sql_list.h"
#include "sql/sql_optimizer.h"  // JOIN
#include "sql/sql_select.h"
#include "sql/sql_tmp_table.h"   // tmp tables
#include "sql/table_function.h"  // Table_function
#include "sql/thd_raii.h"
#include "sql/visible_fields.h"
#include "sql/window.h"  // Window
#include "template_utils.h"

using std::vector;

class Item_rollup_group_item;
class Item_rollup_sum_switcher;
class Opt_trace_context;

bool Query_result_union::prepare(THD *, const mem_root_deque<Item *> &,
                                 Query_expression *u) {
  unit = u;
  return false;
}

bool Query_result_union::send_data(THD *thd,
                                   const mem_root_deque<Item *> &values) {
  if (fill_record(thd, table, table->visible_field_ptr(), values, nullptr,
                  nullptr, false))
    return true; /* purecov: inspected */

  if (table->is_union_or_table() && !check_unique_fields(table)) return false;

  const int error = table->file->ha_write_row(table->record[0]);
  if (!error) {
    return false;
  }
  // create_ondisk_from_heap will generate error if needed
  if (!table->file->is_ignorable_error(error)) {
    bool is_duplicate;
    if (create_ondisk_from_heap(thd, table, error, /*insert_last_record=*/true,
                                /*ignore_last_dup=*/true, &is_duplicate))
      return true; /* purecov: inspected */
    // Table's engine changed, index is not initialized anymore
    if (table->hash_field) table->file->ha_index_init(0, false);
  }
  return false;
}

bool Query_result_union::send_eof(THD *) { return false; }

bool Query_result_union::flush() { return false; }

/**
  Create a temporary table to store the result of a query expression
  (used, among others, when materializing a UNION DISTINCT).

  @param thd_arg            thread handle
  @param column_types       a list of items used to define columns of the
                            temporary table
  @param is_union_distinct  if set, the temporary table will eliminate
                            duplicates on insert
  @param options            create options
  @param table_alias        name of the temporary table
  @param bit_fields_as_long convert bit fields to ulonglong
  @param create_table If false, a table handler will not be created when
                      creating the result table.
  @param op                 If we are creating a result table for a set
                            operation, op should contain the relevant set
                            operation's query term. In other cases, op should
                            be nullptr.

  @details
    Create a temporary table that is used to store the result of a UNION,
    derived table, or a materialized cursor.

  @returns false if table created, true if error
*/

bool Query_result_union::create_result_table(
    THD *thd_arg, const mem_root_deque<Item *> &column_types,
    bool is_union_distinct, ulonglong options, const char *table_alias,
    bool bit_fields_as_long, bool create_table, Query_term_set_op *op) {
  mem_root_deque<Item *> visible_fields(thd_arg->mem_root);
  for (Item *item : VisibleFields(column_types)) {
    visible_fields.push_back(item);
  }

  assert(table == nullptr);
  tmp_table_param = Temp_table_param();
  count_field_types(thd_arg->lex->current_query_block(), &tmp_table_param,
                    visible_fields, false, true);
  tmp_table_param.skip_create_table = !create_table;
  tmp_table_param.bit_fields_as_long = bit_fields_as_long;
  if (unit != nullptr && op == nullptr) {
    if (unit->is_recursive()) {
      /*
        If the UNIQUE key specified for UNION DISTINCT were a primary key in
        InnoDB, rows would be returned by the scan in an order depending on
        their columns' values, not in insertion order.
      */
      tmp_table_param.can_use_pk_for_unique = false;
    }
    if (!unit->is_simple() && unit->can_materialize_directly_into_result()) {
      op = unit->set_operation();
    }
  }

  if (op == nullptr) {
    ;
  } else if (op->term_type() == QT_INTERSECT || op->term_type() == QT_EXCEPT) {
    tmp_table_param.m_operation = op->term_type() == QT_INTERSECT
                                      ? Temp_table_param::TTP_INTERSECT
                                      : Temp_table_param::TTP_EXCEPT;
    // No duplicate rows will exist after the last operation
    tmp_table_param.m_last_operation_is_distinct = op->m_last_distinct > 0;
    assert((op->m_first_distinct < std::numeric_limits<int64_t>::max() ||
            !tmp_table_param.m_last_operation_is_distinct) &&
           (op->m_first_distinct <= op->m_last_distinct ||
            op->m_first_distinct == std::numeric_limits<int64_t>::max()));
    tmp_table_param.force_hash_field_for_unique = true;
  } else if (op->has_mixed_distinct_operators()) {
    // If we have mixed UNION DISTINCT / UNION ALL, we can't use an unique
    // index to deduplicate, as we need to be able to turn off deduplication
    // checking when we get to the UNION ALL part. The handler supports
    // turning off indexes (and the pre-iterator executor used this to
    // implement mixed DISTINCT/ALL), but not selectively, and we might very
    // well need the other indexes when querying against the table.
    // (Also, it would be nice to be able to remove this functionality
    // altogether from the handler.) Thus, we do it manually instead.
    tmp_table_param.force_hash_field_for_unique = true;
  }

  const bool use_setop_hashing =
      op != nullptr &&
      (op->term_type() == QT_EXCEPT || op->term_type() == QT_INTERSECT) &&
      thd_arg->optimizer_switch_flag(OPTIMIZER_SWITCH_HASH_SET_OPERATIONS);

  if (!(table = create_tmp_table(thd_arg, &tmp_table_param, visible_fields,
                                 nullptr, is_union_distinct, true, options,
                                 HA_POS_ERROR, table_alias)))
    return true;
  if (create_table) {
    table->file->ha_extra(HA_EXTRA_IGNORE_DUP_KEY);
    if (table->hash_field != nullptr && !use_setop_hashing)
      table->file->ha_index_init(0, false);
  }
  return false;
}

/**
  Reset and empty the temporary table that stores the materialized query result.

  @note The cleanup performed here is exactly the same as for the two temp
  tables of JOIN - exec_tmp_table_[1 | 2].
*/

bool Query_result_union::reset() {
  return table ? table->empty_result_table() : false;
}

void Query_result_union::set_limit(ha_rows limit_rows) {
  table->m_limit_rows = limit_rows;
}
/**
  This class is effectively dead. It was used for non-DISTINCT UNIONs
  in the pre-iterator executor. Now it exists only as a shell for certain
  setup tasks, and should be removed.
*/
class Query_result_union_direct final : public Query_result_union {
 private:
  /// Result object that receives all rows
  Query_result *result;

  /// Wrapped result is optimized
  bool optimized;
  /// Wrapped result has started execution
  bool execution_started;

 public:
  Query_result_union_direct(Query_result *result, Query_block *last_query_block)
      : Query_result_union(),
        result(result),
        optimized(false),
        execution_started(false) {
    unit = last_query_block->master_query_expression();
  }
  bool change_query_result(THD *thd, Query_result *new_result) override;
  uint field_count(const mem_root_deque<Item *> &) const override {
    // Only called for top-level Query_results, usually Query_result_send
    assert(false); /* purecov: inspected */
    return 0;      /* purecov: inspected */
  }
  bool postponed_prepare(THD *thd,
                         const mem_root_deque<Item *> &types) override;
  bool send_result_set_metadata(THD *, const mem_root_deque<Item *> &,
                                uint) override {
    // Should never be called.
    my_abort();
  }
  bool send_data(THD *, const mem_root_deque<Item *> &) override {
    // Should never be called.
    my_abort();
  }
  bool start_execution(THD *thd) override {
    if (execution_started) return false;
    execution_started = true;
    return result->start_execution(thd);
  }
  bool send_eof(THD *) override {
    // Should never be called.
    my_abort();
  }
  bool flush() override { return false; }
  bool check_supports_cursor() const override {
    // Only called for top-level Query_results, usually Query_result_send
    assert(false); /* purecov: inspected */
    return false;  /* purecov: inspected */
  }
  void abort_result_set(THD *thd) override {
    result->abort_result_set(thd); /* purecov: inspected */
  }
  void cleanup() override {}
};

/**
  Replace the current query result with new_result and prepare it.

  @param thd        Thread handle
  @param new_result New query result

  @returns false if success, true if error
*/
bool Query_result_union_direct::change_query_result(THD *thd,
                                                    Query_result *new_result) {
  result = new_result;
  return result->prepare(thd, *unit->get_unit_column_types(), unit);
}

bool Query_result_union_direct::postponed_prepare(
    THD *thd, const mem_root_deque<Item *> &types) {
  if (result == nullptr) return false;

  return result->prepare(thd, types, unit);
}

/// RAII class to automate saving/restoring of current_query_block()
class Change_current_query_block {
 public:
  Change_current_query_block(THD *thd_arg)
      : thd(thd_arg), saved_query_block(thd->lex->current_query_block()) {}
  void restore() { thd->lex->set_current_query_block(saved_query_block); }
  ~Change_current_query_block() { restore(); }

 private:
  THD *thd;
  Query_block *saved_query_block;
};

/**
  Create a tmp table for a set operation.
  @param thd      session context
  @param qt       query term holding the query result to hold the tmp table
  @param types    the fields of the tmp table, inherited from
                  Query_expression::types
  @param create_options
                  create options for create_tmp_table
  @return false on success, true on error
 */
static bool create_tmp_table_for_set_op(THD *thd, Query_term *qt,
                                        mem_root_deque<Item *> &types,
                                        ulonglong create_options) {
  Query_term_set_op *const parent = qt->parent();
  const bool distinct = parent->m_last_distinct > 0;

  auto tl = new (thd->mem_root) Table_ref();
  if (tl == nullptr) return true;
  qt->set_result_table(tl);

  char *buffer = new (thd->mem_root) char[64 + 1];
  if (buffer == nullptr) return true;
  snprintf(buffer, 64, "<%s temporary>", parent->operator_string());

  if (qt->setop_query_result_union()->create_result_table(
          thd, types, distinct, create_options, buffer, false,
          /*instantiate_tmp_table*/ parent->m_is_materialized, parent))
    return true;
  qt->setop_query_result_union()->table->pos_in_table_list =
      &qt->result_table();
  qt->result_table().db = "";
  // We set the table_name and alias to an empty string here: this avoids
  // giving the user likely unwanted information about the name of the temporary
  // table e.g. as:
  //    Note  1276  Field or reference '<union temporary>.a' of SELECT #3 was
  //                resolved in SELECT #1
  // We prefer just "reference 'a'" in such a case.
  qt->result_table().table_name = "";
  qt->result_table().alias = "";
  qt->result_table().table = qt->setop_query_result_union()->table;
  qt->result_table().query_block = qt->query_block();
  qt->result_table().set_tableno(0);
  qt->result_table().set_privileges(SELECT_ACL);

  return false;
}

/**
  Prepare the query term nodes and their associated post processing
  query blocks (i.e. not the ones representing the query specifications),
  but rather ORDER BY/LIMIT processing (old "fake" query blocks).
  Also create temporary tables to consolidate set operations as needed,
  recursively.
  @param thd    session context
  @param qt     the query term at the current level in the tree
  @param common_result
                For the top node, this is not used: we use query_result()
                instead.
                Otherwise, if it is empty, we create one a query result behalf
                of this node and its siblings. This node is then the designated
                owning operand, and is responsible for releasing it after
                execution.  The siblings will see that common_result is not
                empty and use that.
  @param added_options
                options to add for the post processing query block
  @param create_options
                options to use for creating tmp table
  @param level  the current level in the query expression's query term tree
  @param[in,out] nullable
                computed nullability for the query's result set columns
  @returns false on success, true on error
 */
bool Query_expression::prepare_query_term(THD *thd, Query_term *qt,
                                          Query_result *common_result,
                                          ulonglong added_options,
                                          ulonglong create_options, int level,
                                          Mem_root_array<bool> &nullable) {
  Change_current_query_block save_and_restore(thd);
  Query_term_set_op *const parent = qt->parent();
  // We have a nested set operation structure where the leaf nodes are query
  // blocks.  We now need to prepare the nodes representing the set
  // operations. We have already merged nested set operation of the same kind
  // into multi op form, so at any level the child and parent will usually be of
  // another kind(1).  We use temporary tables marked with an * below, modulo
  // ALL optimizations, to consolidate the result of each multi set operation.
  // E.g.
  //
  //                     UNION*
  //                       |
  //            +----------------+----------+
  //            |                |          |
  //       INTERSECT*     UNARY TERM*   EXCEPT*
  //            |                |          |
  //        +---+---+            QB      +--+-+
  //        |   |   |                    |    |
  //       QB  QB  UNION*                QB   QB
  //               QB QB
  //
  // (1) an exception is that we do not merge top level trailing UNION ALL nodes
  // with preceding UNION DISTINCT in order that they can be streamed
  // efficiently.
  //
  // Note that the Query_result is owned by the first sibling
  // participating in the set operations, so the owning nodes of the above
  // example are actually:
  //                     UNION
  //                       |
  //            +----------------+----------+
  //            |                |          |
  //       INTERSECT*     UNARY TERM   EXCEPT
  //            |                |          |
  //        +---+---+            QB*     +--+-+
  //        |   |   |                    |    |
  //       QB* QB  UNION                QB*   QB
  //               QB* QB
  //
  mem_root_deque<Item *> level_item_list(thd->mem_root);
  mem_root_deque<Item *> *il = &level_item_list;

  switch (qt->term_type()) {
    case QT_UNION:
    case QT_INTERSECT:
    case QT_EXCEPT: {
      auto *qts = down_cast<Query_term_set_op *>(qt);
      auto *qb = qts->query_block();
      assert(qts->m_children.size() >= 2);

      qb->make_active_options(
          (added_options & (OPTION_FOUND_ROWS | OPTION_BUFFER_RESULT)) |
              OPTION_NO_CONST_TABLES | SELECT_NO_UNLOCK,
          0);

      if (level == 0) {
        // e.g. Query_result_send or Query_result_create
        qts->set_setop_query_result(query_result());
      } else if (common_result != nullptr) {
        /// We are part of upper level set op
        qts->set_setop_query_result(common_result);
      } else {
        auto rs = new (thd->mem_root) Query_result_union();
        if (rs == nullptr) return true;
        qts->set_setop_query_result(rs);
        qts->set_owning_operand();
      }
      qb->set_query_result(qts->setop_query_result());

      // To support SQL T101 "Enhanced nullability determination", the rules
      // for computing nullability of the result columns of a set operation
      // require that we perform different computation for UNION, INTERSECT and
      // EXCEPT, cf. SQL 2014, Vol 2, section 7.17 <query expression>, SR 18
      // and 20.  When preparing the leaf query blocks in
      // Query_expression::prepare, type unification for set operations is done
      // by calling Item_aggregate_type::join_types including setting
      // nullability.  This works correctly for UNION, but not if we have
      // INTERSECT and/or EXCEPT in the tree of set operations. We can only do
      // it correctly here in prepare_query_term since this visits the tree
      // recursively as required by the rules.
      //
      Mem_root_array<bool> child_nullable(thd->mem_root, nullable.size(),
                                          false);

      // Prepare children
      for (size_t i = 0; i < qts->m_children.size(); i++) {
        Query_result *const cmn_result =
            (i == 0) ? nullptr : qts->m_children[0]->setop_query_result();
        // operands 1..size-1 inherit operand 0's query_result: they all
        // contribute to the same result.
        if (prepare_query_term(thd, qts->m_children[i], cmn_result,
                               added_options, create_options, level + 1,
                               child_nullable))
          return true;
        for (size_t j = 0; j < nullable.size(); j++) {
          if (i == 0) {  // left side
            nullable[j] = child_nullable[j];
          } else {
            switch (qt->term_type()) {
              case QT_UNION:
                nullable[j] = nullable[j] || child_nullable[j];
                break;
              case QT_INTERSECT:
                nullable[j] = nullable[j] && child_nullable[j];
                break;
              case QT_EXCEPT:
                // Nothing to do, use left side unchanged
                break;
              default:
                assert(false);
            }
          }
        }
      }

      // Adjust tmp table fields' nullabililty. It is safe to do this because
      // fields were created with nullability if at least one query block had
      // nullable field during type joining (UNION semantics), so we will
      // only ever set nullable here if result field originally was computed
      // as nullable in join_types. And removing nullability for a Field isn't
      // a problem.
      size_t idx = 0;
      for (auto f : qb->visible_fields()) {
        f->set_nullable(nullable[idx]);
        assert(f->type() == Item::FIELD_ITEM);
        if (nullable[idx]) {
          down_cast<Item_field *>(f)->field->clear_flag(NOT_NULL_FLAG);
        } else {
          if (qt->term_type() == QT_UNION)
            down_cast<Item_field *>(f)->field->set_flag(NOT_NULL_FLAG);
          // don't set NOT_NULL_FLAG for INTERSECT, EXCEPT since we may need
          // to store a NULL value for this field during hashing even though the
          // logical result of the set operation can not be NULL.
        }
        idx++;
      }

      if (qts->m_is_materialized) {
        // Set up the result table for name resolution
        qb->context.table_list = qb->context.first_name_resolution_table =
            qb->get_table_list();
        qb->add_joined_table(qb->get_table_list());
        for (ORDER *order = qb->order_list.first; order; order = order->next) {
          Item_ident::Change_context ctx(&qb->context);
          (*order->item)
              ->walk(&Item::change_context_processor, enum_walk::POSTFIX,
                     (uchar *)&ctx);
        }

        thd->lex->set_current_query_block(qb);

        if (qb->prepare(thd, nullptr)) return true;

        if (qb->base_ref_items.is_null())
          qb->n_child_sum_items += qb->n_sum_items;
      } else {
        if (qb->resolve_limits(thd)) return true;
        if (qb->query_result() != nullptr &&
            qb->query_result()->prepare(thd, qb->fields, this))
          return true;

        auto f = new (thd->mem_root) mem_root_deque<Item *>(thd->mem_root);
        if (f == nullptr) return true;
        qts->set_fields(f);
        if (qts->query_block()->get_table_list()->table->fill_item_list(f))
          return true;
      }
    } break;
    case QT_UNARY: {
      auto *unary = down_cast<Query_term_unary *>(qt);
      auto *qb = unary->query_block();
      assert(unary->m_children.size() == 1);

      qb->make_active_options(
          (added_options & (OPTION_FOUND_ROWS | OPTION_BUFFER_RESULT)) |
              OPTION_NO_CONST_TABLES | SELECT_NO_UNLOCK,
          0);

      if (level == 0) {
        // e.g. Query_result_send or Query_result_create
        unary->set_setop_query_result(query_result());
      } else if (common_result != nullptr) {
        unary->set_setop_query_result(common_result);
      } else {
        auto qr = new (thd->mem_root) Query_result_union();
        if (qr == nullptr) return true;
        unary->set_setop_query_result(qr);
        unary->set_owning_operand();
      }
      qb->set_query_result(unary->setop_query_result());

      if (prepare_query_term(thd, unary->m_children[0],
                             /*common_result*/ nullptr, added_options,
                             create_options, level + 1, nullable))
        return true;

      // Set up the result table for name resolution
      qb->context.table_list = qb->context.first_name_resolution_table =
          qb->get_table_list();
      qb->add_joined_table(qb->get_table_list());
      for (ORDER *order = qb->order_list.first; order; order = order->next) {
        Item_ident::Change_context ctx(&qb->context);
        (*order->item)
            ->walk(&Item::change_context_processor, enum_walk::POSTFIX,
                   (uchar *)&ctx);
      }

      thd->lex->set_current_query_block(qb);

      if (qb->prepare(thd, nullptr)) return true;

      if (qb->base_ref_items.is_null())
        qb->n_child_sum_items += qb->n_sum_items;
    } break;

    case QT_QUERY_BLOCK: {
      // The query blocks themselves have already been prepared in
      // Query_expression::prepare before calling prepare_query_term. Here
      // we just set up the consolidation tmp table as input to the parent
      Query_result *inner_qr = common_result;

      if (inner_qr == nullptr) {
        inner_qr = new (thd->mem_root) Query_result_union();
        if (inner_qr == nullptr) return true;
        qt->set_owning_operand();
      }
      qt->set_setop_query_result(inner_qr);
      qt->query_block()->set_query_result(inner_qr);
      int idx = 0;
      for (auto field : qt->query_block()->visible_fields())
        nullable[idx++] = field->is_nullable();

    } break;
  }

  if (parent != nullptr && common_result == nullptr) {
    if (create_tmp_table_for_set_op(thd, qt, types, create_options))
      return true;

    auto pb = parent->query_block();
    // Parent's input is this tmp table
    Table_ref &input_to_parent = qt->result_table();
    pb->m_table_list.link_in_list(&input_to_parent,
                                  &input_to_parent.next_local);
    if (pb->get_table_list()->table->fill_item_list(il))
      return true;  // purecov: inspected
    pb->fields = *il;
  }

  return false;
}

bool Query_expression::can_materialize_directly_into_result() const {
  // There's no point in doing this if we're not already trying to materialize.
  if (!is_set_operation()) {
    return false;
  }

  // We can't materialize directly into the result if we have sorting.
  // Otherwise, we're fine.
  return global_parameters()->order_list.elements == 0;
}

/**
  Prepares all query blocks of a query expression, including
  fake_query_block. If a recursive query expression, this also creates the
  materialized temporary table.

  @param thd           Thread handler
  @param sel_result    Result object where the unit's output should go.
  @param insert_field_list Pointer to field list if INSERT op, NULL otherwise.
  @param added_options These options will be added to the query blocks.
  @param removed_options Options that cannot be used for this query

  @returns false if success, true if error
 */

bool Query_expression::prepare(THD *thd, Query_result *sel_result,
                               mem_root_deque<Item *> *insert_field_list,
                               ulonglong added_options,
                               ulonglong removed_options) {
  DBUG_TRACE;

  assert(!is_prepared());
  Change_current_query_block save_query_block(thd);

  Query_result *tmp_result = nullptr;
  Query_block *last_query_block = first_query_block();
  while (last_query_block->next_query_block())
    last_query_block = last_query_block->next_query_block();

  set_query_result(sel_result);

  thd->lex->set_current_query_block(first_query_block());

  const bool simple_query_expression = is_simple();

  if (is_simple()) {
    // Only one query block. No extra result needed:
    tmp_result = sel_result;
  } else {
    set_operation()->m_is_materialized =
        query_term()->term_type() != QT_UNION ||
        set_operation()->m_last_distinct > 0 ||
        global_parameters()->order_list.elements > 0 ||
        ((thd->lex->sql_command == SQLCOM_INSERT_SELECT ||
          thd->lex->sql_command == SQLCOM_REPLACE_SELECT) &&
         thd->lex->unit == this);
    /*
      There exists a query block that consolidates the result, so
      no need to buffer bottom level query block's result.
    */
    added_options &= ~OPTION_BUFFER_RESULT;
  }

  first_query_block()->context.resolve_in_select_list = true;

  for (Query_block *sl = first_query_block(); sl; sl = sl->next_query_block()) {
    // All query blocks get their options in this phase
    if (is_simple()) sl->set_query_result(tmp_result);
    sl->make_active_options(added_options | SELECT_NO_UNLOCK, removed_options);

    thd->lex->set_current_query_block(sl);

    if (sl == first_recursive) {
      // create_result_table() depends on current_query_block()
      save_query_block.restore();
      /*
        All next query blocks will read the temporary table, which we must
        thus create now:
      */
      if (derived_table->setup_materialized_derived_tmp_table(thd))
        return true; /* purecov: inspected */
      thd->lex->set_current_query_block(sl);
    }

    if (sl->recursive_reference)  // Make tmp table known to query block:
      derived_table->common_table_expr()->substitute_recursive_reference(thd,
                                                                         sl);

    if (sl->prepare(thd, insert_field_list)) return true;

    /*
      Use items list of underlaid select for derived tables to preserve
      information about fields lengths and exact types
    */
    if (!is_set_operation()) {
      types.clear();
      for (Item *item : first_query_block()->visible_fields()) {
        types.push_back(item);
      }
    } else if (sl == first_query_block()) {
      types.clear();
      for (Item *item_tmp : sl->visible_fields()) {
        /*
          If the outer query has a GROUP BY clause, an outer reference to this
          query block may have been wrapped in a Item_outer_ref, which has not
          been fixed yet. An Item_type_holder must be created based on a fixed
          Item, so use the inner Item instead.
        */
        assert(item_tmp->fixed ||
               (item_tmp->type() == Item::REF_ITEM &&
                down_cast<Item_ref *>(item_tmp)->ref_type() ==
                    Item_ref::OUTER_REF));
        if (!item_tmp->fixed) item_tmp = item_tmp->real_item();

        auto holder = new Item_type_holder(thd, item_tmp);
        if (!holder) return true; /* purecov: inspected */
        if (is_recursive()) {
          holder->set_nullable(true);  // Always nullable, per SQL standard.
          /*
            The UNION code relies on join_types() to change some
            transitional types like MYSQL_TYPE_DATETIME2 into other types; in
            case this is the only nonrecursive query block join_types() won't
            be called so we need an explicit call:
          */
          holder->join_types(thd, item_tmp);
        }
        types.push_back(holder);
      }
    } else {
      if (types.size() != sl->num_visible_fields()) {
        my_error(ER_WRONG_NUMBER_OF_COLUMNS_IN_SELECT, MYF(0));
        return true;
      }
      if (sl->recursive_reference) {
        /*
          Recursive query blocks don't determine output types of the result.
          The only thing to check could be if the recursive query block has a
          type which can't be cast to the output type of the result.
          But in MySQL, all types can be cast to each other (at least during
          resolution; an error may reported when trying to actually insert, for
          example an INT into a POINT). So no further compatibility check is
          needed here.
        */
      } else {
        auto it = sl->visible_fields().begin();
        auto tp = types.begin();
        for (; it != sl->visible_fields().end() && tp != types.end();
             ++it, ++tp) {
          if ((down_cast<Item_type_holder *>(*tp))->join_types(thd, *it))
            return true;
        }
      }
    }

    if (sl->recursive_reference &&
        (sl->is_grouped() || sl->m_windows.elements > 0)) {
      // Per SQL2011.
      my_error(ER_CTE_RECURSIVE_FORBIDS_AGGREGATION, MYF(0),
               derived_table->alias);
      return true;
    }
  }

  if (is_recursive()) {
    // This had to wait until all query blocks are prepared:
    if (check_materialized_derived_query_blocks(thd))
      return true; /* purecov: inspected */
  }

  if (!simple_query_expression) {
    /*
      Check that it was possible to aggregate all collations together for set
      operation.  We need this in case of setop DISTINCT, to detect duplicates
      using the proper collation.

      TODO: consider removing this test in case of UNION ALL.
    */
    for (Item *type : types) {
      if (type->result_type() == STRING_RESULT &&
          type->collation.derivation == DERIVATION_NONE) {
        my_error(ER_CANT_AGGREGATE_NCOLLATIONS, MYF(0), "UNION");
        return true;
      }
    }
    ulonglong create_options =
        first_query_block()->active_options() | TMP_TABLE_ALL_COLUMNS;

    // SQL feature T101 enhanced nullability determination: a priori calculated
    // by join_types (UNION semantics), but needs adjustment for INTERSECT and
    // EXCEPT: nullable array computed recursively
    Mem_root_array<bool> nullable(thd->mem_root, types.size(), false);
    for (size_t i = 0; i < types.size(); i++)
      nullable[i] = types[i]->is_nullable();  // a priori setting

    // Prepare the tree of set operations, aka the query term tree
    if (prepare_query_term(thd, query_term(),
                           /*common_result*/ nullptr, added_options,
                           create_options, /*level*/ 0, nullable))
      return true; /* purecov: inspected */

    for (size_t i = 0; i < types.size(); i++)
      types[i]->set_nullable(nullable[i]);
  }

  // Query blocks are prepared, update the state
  set_prepared();

  return false;
}

/// Finalizes the initialization of all the full-text functions used in the
/// given query expression, and recursively in every query expression inner to
/// the given one. We do this fairly late, since we need to know whether or not
/// the full-text function is to be used for a full-text index scan, and whether
/// or not that scan is sorted. When the iterators have been created, we know
/// that the final decision has been made, so we do it right after the iterators
/// have been created.
static bool finalize_full_text_functions(THD *thd,
                                         Query_expression *query_expression) {
  assert(thd->lex->using_hypergraph_optimizer());
  for (Query_expression *qe = query_expression; qe != nullptr;
       qe = qe->next_query_expression()) {
    for (Query_block *qb = qe->first_query_block(); qb != nullptr;
         qb = qb->next_query_block()) {
      if (qb->has_ft_funcs()) {
        if (init_ftfuncs(thd, qb)) {
          return true;
        }
      }
      if (finalize_full_text_functions(thd,
                                       qb->first_inner_query_expression())) {
        return true;
      }
    }
  }
  return false;
}

/**
  Optimize the post processing query blocks of the query expression's
  query term tree recursively.
  @param thd session context
  @param qe  the owning query expression
  @param qt  the current query term to optimize
  @returns false on success, true on error
 */
static bool optimize_set_operand(THD *thd, Query_expression *qe,
                                 Query_term *qt) {
  if (qt->term_type() == QT_QUERY_BLOCK) return false;  // done already
  Query_term_set_op *qts = down_cast<Query_term_set_op *>(qt);
  thd->lex->set_current_query_block(qts->query_block());

  // LIMIT is required for optimization
  if (qe->set_limit(thd, qts->query_block()))
    return true; /* purecov: inspected */

  if ((qts->is_unary() || qts->m_is_materialized) &&
      qts->query_block()->optimize(thd,
                                   /*finalize_access_paths=*/true))
    return true;
  for (Query_term *child : qts->m_children) {
    if (optimize_set_operand(thd, qe, child)) return true;
  }

  return false;
}

/**
  Determine if we should set or add the contribution of the given query block to
  the total row count estimate for the query expression.
  If we have INTERSECT or EXCEPT, only set row estimate for left side since
  the total number of rows in the result set can only decrease as a result
  of the set operation.
  @param qb query block
  @return true if the estimate should be added
*/
static bool contributes_to_rowcount_estimate(Query_block *qb) {
  if (qb->parent() == nullptr) return true;

  // When parent isn't nullptr, we know this is a leaf block.
  Query_term *query_term = qb;

  // See if this query block is contained in a right side of an INTERSECT
  // or EXCEPT operation anywhere in tree. If so, we can ignore its count.
  Query_term_set_op *parent = query_term->parent();
  while (parent != nullptr) {
    const auto term_type = parent->term_type();
    if ((term_type == QT_EXCEPT || term_type == QT_INTERSECT) &&
        parent->m_children[0] != query_term)
      return false;
    // Even if we are on the left side of an INTERSECT, EXCEPT, the set
    // operation itself could still be in a non-contributing side higher up, so
    // continue checking.
    query_term = parent;
    parent = parent->parent();
  }
  return true;
}

static bool use_iterator(TABLE *materialize_destination,
                         Query_term *query_term) {
  if (materialize_destination == nullptr) return false;

  switch (query_term->term_type()) {
    case QT_INTERSECT:
    case QT_EXCEPT:
      // In corner cases for transform of scalar subquery, it can happen
      // that the destination table isn't ready for INTERSECT or EXCEPT, so
      // force double materialization.
      // FIXME: find out if we can remove this exception.
      return materialize_destination->is_union_or_table();
    default:;
  }
  return false;
}

bool Query_expression::optimize(THD *thd, TABLE *materialize_destination,
                                bool create_iterators,
                                bool finalize_access_paths) {
  DBUG_TRACE;

  if (!finalize_access_paths) {
    assert(!create_iterators);
  }

  assert(is_prepared() && !is_optimized());

  Change_current_query_block save_query_block(thd);

  ha_rows estimated_rowcount = 0;
  double estimated_cost = 0.0;

  if (query_result() != nullptr) query_result()->estimated_rowcount = 0;

  for (Query_block *query_block = first_query_block(); query_block != nullptr;
       query_block = query_block->next_query_block()) {
    thd->lex->set_current_query_block(query_block);

    // LIMIT is required for optimization
    if (set_limit(thd, query_block)) return true; /* purecov: inspected */

    if (query_block->optimize(thd, finalize_access_paths)) return true;

    /*
      Accumulate estimated number of rows.
      1. Implicitly grouped query has one row (with HAVING it has zero or one
         rows).
      2. If GROUP BY clause is optimized away because it was a constant then
         query produces at most one row.
     */
    if (contributes_to_rowcount_estimate(query_block))
      estimated_rowcount += (query_block->is_implicitly_grouped() ||
                             query_block->join->group_optimized_away)
                                ? 1
                                : query_block->join->best_rowcount;

    estimated_cost += query_block->join->best_read;

    // Table_ref::fetch_number_of_rows() expects to get the number of rows
    // from all earlier query blocks from the query result, so we need to update
    // it as we go. In particular, this is used when optimizing a recursive
    // SELECT in a CTE, so that it knows how many rows the non-recursive query
    // blocks will produce.
    //
    // TODO(sgunders): Communicate this in a different way when the query result
    // goes away.
    if (query_result() != nullptr) {
      query_result()->estimated_rowcount = estimated_rowcount;
      query_result()->estimated_cost = estimated_cost;
    }
  }

  if (!is_simple() && query_term()->open_result_tables(thd, 0)) return true;

  if ((uncacheable & UNCACHEABLE_DEPENDENT) && estimated_rowcount <= 1) {
    /*
      This depends on outer references, so optimization cannot assume that all
      executions will always produce the same row. So, increase the counter to
      prevent that this table is replaced with a constant.
      Not testing all bits of "uncacheable", as if derived table sets user
      vars (UNCACHEABLE_SIDEEFFECT) the logic above doesn't apply.
    */
    estimated_rowcount = PLACEHOLDER_TABLE_ROW_ESTIMATE;
  }

  if (!is_simple()) {
    if (optimize_set_operand(thd, this, query_term())) return true;
    if (set_limit(thd, query_term()->query_block())) return true;
    if (!is_union()) query_result()->set_limit(select_limit_cnt);
  }

  query_result()->estimated_rowcount = estimated_rowcount;
  query_result()->estimated_cost = estimated_cost;

  // If the caller has asked for materialization directly into a table of its
  // own, and we can do so, do an unfinished materialization (see the comment
  // on this function for more details).
  if (thd->lex->m_sql_cmd != nullptr &&
      thd->lex->m_sql_cmd->using_secondary_storage_engine()) {
    // Not supported when using secondary storage engine.
    create_access_paths(thd);
  } else if (estimated_rowcount <= 1 ||
             use_iterator(materialize_destination, query_term())) {
    // Don't do it for const tables, as for those, optimize_derived() wants to
    // run the query during optimization, and thus needs an iterator.
    //
    // Do note that JOIN::extract_func_dependent_tables() can want to read from
    // the derived table during the optimization phase even if it has
    // estimated_rowcount larger than one (e.g., because it understands it can
    // get only one row due to a unique index), but will detect that the table
    // has not been created, and treat the the lookup as non-const.
    create_access_paths(thd);
  } else if (materialize_destination != nullptr &&
             can_materialize_directly_into_result()) {
    assert(!is_simple());
    const bool calc_found_rows =
        (first_query_block()->active_options() & OPTION_FOUND_ROWS);
    m_operands = set_operation()->setup_materialize_set_op(
        thd, materialize_destination,
        /*union_distinct_only=*/false, calc_found_rows);
  } else {
    // Recursive CTEs expect to see the rows in the result table immediately
    // after writing them.
    assert(!is_recursive());
    create_access_paths(thd);
  }

  set_optimized();  // All query blocks optimized, update the state

  if (item != nullptr) {
    // If we're part of an IN subquery, the containing engine may want to
    // add its own iterators on top, e.g. to materialize us.
    //
    // TODO(sgunders): See if we can do away with the engine concept
    // altogether, now that there's much less execution logic in them.
    assert(!unfinished_materialization());
    item->create_iterators(thd);
    if (m_root_access_path == nullptr) {
      return false;
    }
  }

  if (create_iterators && IteratorsAreNeeded(thd, m_root_access_path)) {
    JOIN *join = query_term()->query_block()->join;

    DBUG_EXECUTE_IF(
        "ast", Query_term *qn = m_query_term;
        DBUG_PRINT("ast", ("\n%s", thd->query().str)); if (qn) {
          std::ostringstream buf;
          qn->debugPrint(0, buf);
          DBUG_PRINT("ast", ("\n%s", buf.str().c_str()));
        });

    DBUG_EXECUTE_IF(
        "ast", bool is_root_of_join = (join != nullptr); DBUG_PRINT(
            "ast", ("Query plan:\n%s\n",
                    PrintQueryPlan(0, m_root_access_path, join, is_root_of_join)
                        .c_str())););

    m_root_iterator = CreateIteratorFromAccessPath(
        thd, m_root_access_path, join, /*eligible_for_batch_mode=*/true);
    if (m_root_iterator == nullptr) {
      return true;
    }

    if (thd->lex->using_hypergraph_optimizer()) {
      if (finalize_full_text_functions(thd, this)) {
        return true;
      }
    }

    if (false) {
      // This can be useful during debugging.
      // TODO(sgunders): Consider adding the SET DEBUG force-subplan line here,
      // like we have on EXPLAIN FORMAT=tree if subplan_tokens is active.
      bool is_root_of_join = (join != nullptr);
      fprintf(
          stderr, "Query plan:\n%s\n",
          PrintQueryPlan(0, m_root_access_path, join, is_root_of_join).c_str());
    }
  }

  // When done with the outermost query expression, and if max_join_size is in
  // effect, estimate the total number of row accesses in the query, and error
  // out if it exceeds max_join_size.
  if (outer_query_block() == nullptr &&
      !Overlaps(thd->variables.option_bits, OPTION_BIG_SELECTS) &&
      !thd->lex->is_explain() &&
      EstimateRowAccesses(m_root_access_path, /*num_evaluations=*/1.0,
                          std::numeric_limits<double>::infinity()) >
          static_cast<double>(thd->variables.max_join_size)) {
    my_error(ER_TOO_BIG_SELECT, MYF(0));
    return true;
  }

  return false;
}

bool Query_expression::finalize(THD *thd) {
  for (Query_block *query_block = first_query_block(); query_block != nullptr;
       query_block = query_block->next_query_block()) {
    if (query_block->join != nullptr && query_block->join->needs_finalize) {
      if (FinalizePlanForQueryBlock(thd, query_block)) {
        return true;
      }
    }
  }
  return false;
}

bool Query_expression::force_create_iterators(THD *thd) {
  if (m_root_iterator == nullptr) {
    JOIN *join = is_set_operation() ? nullptr : first_query_block()->join;
    m_root_iterator = CreateIteratorFromAccessPath(
        thd, m_root_access_path, join, /*eligible_for_batch_mode=*/true);
  }

  if (m_root_iterator == nullptr) return true;

  if (thd->lex->using_hypergraph_optimizer()) {
    if (finalize_full_text_functions(thd, this)) {
      return true;
    }
  }

  return false;
}

/**
  Helper method: create a materialized access path, estimate its cost and
  move it to the best place, cf. doc for MoveCompositeIteratorsFromTablePath
  @param thd      session state
  @param qt       query term for which we want to create a materialized access
                  path
  @param operands the constituent operands (query blocks) we want to materialize
  @param dest     the destination temporary (materialized) table
  @param limit    If not HA_POS_ERROR, the maximum number of rows allowed in
                  the materialized table
  @return non-empty access path. If empty, this is an error
*/
static AccessPath *add_materialized_access_path(
    THD *thd, Query_term *qt,
    Mem_root_array<MaterializePathParameters::Operand> &operands, TABLE *dest,
    ha_rows limit = HA_POS_ERROR) {
  AccessPath *path = qt->query_block()->join->root_access_path();
  path = NewMaterializeAccessPath(thd, std::move(operands),
                                  /*invalidators=*/nullptr, dest, path,
                                  /*cte=*/nullptr, /*unit=*/nullptr,
                                  /*ref_slice=*/-1,
                                  /*rematerialize=*/true, limit,
                                  /*reject_multiple_rows=*/false);
  EstimateMaterializeCost(thd, path);
  return MoveCompositeIteratorsFromTablePath(thd, path, *qt->query_block());
}

/**
  Recursively constructs the access path of the set operation, possibly
  materializing in a tmp table if needed, cf.
  Query_term_set_op::m_is_materialized
  @param thd    session context
  @param parent the parent of qt
  @param qt     the query term at this level of the tree
  @param union_all_subpaths
                if not nullptr, we are part of a UNION all, add constructed
                access to it.
  @param calc_found_rows
                if true, do allow for calculation of number of found rows
                even in presence of LIMIT.
  @return access path, if nullptr, this is an error
 */
AccessPath *make_set_op_access_path(
    THD *thd, Query_term_set_op *parent, Query_term *qt,
    Mem_root_array<AppendPathParameters> *union_all_subpaths,
    bool calc_found_rows) {
  AccessPath *path = nullptr;
  switch (qt->term_type()) {
    case QT_UNION:
    case QT_INTERSECT:
    case QT_EXCEPT: {
      Query_term_set_op *qts = down_cast<Query_term_set_op *>(qt);

      if (!qts->m_is_materialized) {
        // skip materialization at top level, we can stream all blocks
        ;
      } else {
        TABLE *const dest =
            qts->m_children[0]->setop_query_result_union()->table;
        Mem_root_array<MaterializePathParameters::Operand> query_blocks =
            qts->setup_materialize_set_op(
                thd, dest, union_all_subpaths != nullptr, calc_found_rows);
        const bool push_limit_down =
            qt->term_type() == QT_UNION &&
            qts->query_block()->order_list.size() == 0 && !calc_found_rows;
        const ha_rows max_rows = push_limit_down
                                     ? qts->query_block()->get_limit(thd) +
                                           qts->query_block()->get_offset(thd)
                                     : HA_POS_ERROR;
        path = add_materialized_access_path(thd, qts, query_blocks, dest,
                                            max_rows);
        if (union_all_subpaths != nullptr) {
          AppendPathParameters param;
          param.path = path;
          param.join = nullptr;
          union_all_subpaths->push_back(param);
        }
      }

      if (union_all_subpaths != nullptr) {
        assert(parent == nullptr);
        TABLE *dest = qts->m_children[0]->setop_query_result_union()->table;
        size_t start_idx =
            qts->m_last_distinct == 0 ? 0 : qts->m_last_distinct + 1;
        for (size_t i = start_idx; i < qts->m_children.size(); ++i) {
          // append UNION ALL blocks that follow last UNION [DISTINCT]
          Query_term *const term = qts->m_children[i];
          Query_block *const block = term->query_block();
          JOIN *const join = block->join;
          AccessPath *child_path = join->root_access_path();
          if (term->term_type() != QT_QUERY_BLOCK) {
            child_path = make_set_op_access_path(thd, nullptr, term, nullptr,
                                                 calc_found_rows);
          }
          assert(join && join->is_optimized());
          ConvertItemsToCopy(*join->fields, dest->visible_field_ptr(),
                             &join->tmp_table_param);
          AppendPathParameters param;
          param.path = NewStreamingAccessPath(thd, child_path, join,
                                              &join->tmp_table_param, dest,
                                              /*ref_slice=*/-1);
          param.join = join;
          CopyBasicProperties(*child_path, param.path);
          union_all_subpaths->push_back(param);
        }
      } else if (parent != nullptr) {
        assert(union_all_subpaths == nullptr);
        TABLE *const dest = qts->setop_query_result_union()->table;
        MaterializePathParameters::Operand param =
            qts->query_block()->setup_materialize_query_block(path, dest);
        Mem_root_array<MaterializePathParameters::Operand> operands(
            thd->mem_root);
        operands.push_back(std::move(param));
        path = add_materialized_access_path(thd, parent, operands, dest);
      }
    } break;

    case QT_UNARY: {
      Query_term_unary *qts = down_cast<Query_term_unary *>(qt);
      path = make_set_op_access_path(thd, qts, qts->m_children[0], nullptr,
                                     calc_found_rows);
      if (parent == nullptr) return path;
      TABLE *const dest = qts->setop_query_result_union()->table;
      MaterializePathParameters::Operand param =
          qts->query_block()->setup_materialize_query_block(path, dest);
      Mem_root_array<MaterializePathParameters::Operand> operands(
          thd->mem_root);
      operands.push_back(std::move(param));
      path = add_materialized_access_path(thd, parent, operands, dest);
    } break;

    case QT_QUERY_BLOCK: {
      TABLE *const dest = qt->setop_query_result_union()->table;
      Mem_root_array<MaterializePathParameters::Operand> operands =
          parent->setup_materialize_set_op(thd, dest, false, calc_found_rows);
      path = add_materialized_access_path(thd, parent, operands, dest);
    } break;

    default:
      assert(false);
  }

  return path;
}

/**
  Make materialization parameters for a query block given its input path
  and destination table,
  @param child_path the input access path
  @param dst_table  the table to materialize into

  @returns materialization parameter
*/
MaterializePathParameters::Operand Query_block::setup_materialize_query_block(
    AccessPath *child_path, TABLE *dst_table) const {
  ConvertItemsToCopy(*join->fields, dst_table->visible_field_ptr(),
                     &join->tmp_table_param);

  MaterializePathParameters::Operand operand;
  operand.subquery_path = child_path;
  operand.select_number = select_number;
  operand.join = join;
  operand.disable_deduplication_by_hash_field = false;
  operand.copy_items = true;
  operand.temp_table_param = &join->tmp_table_param;
  operand.is_recursive_reference = recursive_reference != nullptr;
  return operand;
}

/**
   Sets up each(*) query block in this query expression for materialization
   into the given table by making a materialization parameter for each block
   (*) modulo union_distinct_only.

   @param thd       session context
   @param dst_table the table to materialize into
   @param union_distinct_only
                    if true, materialize only UNION DISTINCT query blocks
     (any UNION ALL blocks are presumed handled higher up, by AppendIterator)
   @param calc_found_rows
                    if true, calculate rows found
   @returns array of materialization parameters
*/
Mem_root_array<MaterializePathParameters::Operand>
Query_term_set_op::setup_materialize_set_op(THD *thd, TABLE *dst_table,
                                            bool union_distinct_only,
                                            bool calc_found_rows) {
  Mem_root_array<MaterializePathParameters::Operand> operands(thd->mem_root);

  int64 idx = -1;
  for (Query_term *term : m_children) {
    ++idx;
    bool activate_deduplication =
        idx <= m_last_distinct ||
        term_type() != QT_UNION; /* always for INTERSECT and EXCEPT */
    JOIN *join = term->query_block()->join;
    AccessPath *child_path = join->root_access_path();
    assert(join->is_optimized() && child_path != nullptr);

    if (term->term_type() != QT_QUERY_BLOCK)
      child_path =
          make_set_op_access_path(thd, nullptr, term, nullptr, calc_found_rows);

    MaterializePathParameters::Operand param =
        term->query_block()->setup_materialize_query_block(child_path,
                                                           dst_table);
    param.m_first_distinct = m_first_distinct;
    param.m_operand_idx = idx;
    param.m_total_operands = m_children.size();
    param.disable_deduplication_by_hash_field =
        (has_mixed_distinct_operators() && !activate_deduplication);
    operands.push_back(std::move(param));

    if (idx == m_last_distinct && idx > 0 && union_distinct_only)
      // The rest will be done by appending.
      break;
  }
  return operands;
}

void Query_expression::create_access_paths(THD *thd) {
  if (is_simple()) {
    JOIN *join = first_query_block()->join;
    assert(join && join->is_optimized());
    m_root_access_path = join->root_access_path();
    return;
  }

  // Decide whether we can stream rows, ie., never actually put them into the
  // temporary table. If we can, we materialize the UNION DISTINCT blocks first,
  // and then stream the remaining UNION ALL blocks (if any) by means of
  // AppendIterator.
  //
  // If we cannot stream (ie., everything has to go into the temporary table),
  // our strategy for mixed UNION ALL/DISTINCT becomes a bit different;
  // see MaterializeIterator for details.
  bool streaming_allowed = true;
  if (global_parameters()->order_list.size() != 0 ||
      (!is_simple() && set_operation()->m_is_materialized)) {
    // If we're sorting, we currently put it in a real table no matter what.
    // This is a legacy decision, because we used to not know whether filesort
    // would want to refer to rows in the table after the sort (sort by row ID).
    // We could probably be more intelligent here now.
    streaming_allowed = false;
  } else if ((thd->lex->sql_command == SQLCOM_INSERT_SELECT ||
              thd->lex->sql_command == SQLCOM_REPLACE_SELECT) &&
             thd->lex->unit == this) {
    // If we're doing an INSERT or REPLACE, and we're not outputting to
    // a temporary table already (ie., we are the topmost unit), then we
    // don't want to insert any records before we're done scanning. Otherwise,
    // we would risk incorrect results and/or infinite loops, as we'd be seeing
    // our own records as they get inserted.
    //
    // @todo Figure out if we can check for OPTION_BUFFER_RESULT instead;
    //       see bug #23022426.
    streaming_allowed = false;
  }

  ha_rows offset = global_parameters()->get_offset(thd);
  ha_rows limit = global_parameters()->get_limit(thd);
  if (limit + offset >= limit)
    limit += offset;
  else
    limit = HA_POS_ERROR; /* purecov: inspected */
  const bool calc_found_rows =
      (first_query_block()->active_options() & OPTION_FOUND_ROWS);

  Mem_root_array<AppendPathParameters> *union_all_sub_paths =
      new (thd->mem_root) Mem_root_array<AppendPathParameters>(thd->mem_root);

  // If streaming is allowed, we can do all the parts that are UNION ALL by
  // streaming; the rest have to go to the table.
  //
  // Handle the query blocks that we need to materialize. This may be
  // UNION DISTINCT query blocks only, or all blocks.
  if (!streaming_allowed || !is_simple()) {
    AppendPathParameters param;
    param.path = make_set_op_access_path(
        thd, /*parent*/ nullptr, m_query_term,
        streaming_allowed ? union_all_sub_paths : nullptr, calc_found_rows);
    param.join = nullptr;
    if (!streaming_allowed) union_all_sub_paths->push_back(param);
    // else filled in by make_set_op_access_path
  }

  assert(!union_all_sub_paths->empty());
  if (union_all_sub_paths->size() == 1) {
    m_root_access_path = (*union_all_sub_paths)[0].path;
  } else {
    // Just append all the UNION ALL sub-blocks.
    assert(streaming_allowed);
    m_root_access_path = NewAppendAccessPath(thd, union_all_sub_paths);
  }

  // NOTE: If there's a fake_query_block, its JOIN's iterator already handles
  // LIMIT/OFFSET, so we don't do it again here.
  if (streaming_allowed && (limit != HA_POS_ERROR || offset != 0) &&
      (is_simple() || set_operation()->m_last_distinct == 0)) {
    m_root_access_path = NewLimitOffsetAccessPath(
        thd, m_root_access_path, limit, offset, calc_found_rows,
        /*reject_multiple_rows=*/false, &send_records);
  }
}

bool Query_expression::explain_query_term(THD *explain_thd,
                                          const THD *query_thd,
                                          Query_term *qt) {
  Explain_format *fmt = explain_thd->lex->explain_format;
  switch (qt->term_type()) {
    case QT_QUERY_BLOCK:
      if (fmt->begin_context(CTX_QUERY_SPEC)) return true;
      if (explain_query_specification(explain_thd, query_thd, qt, CTX_JOIN))
        return true;
      if (fmt->end_context(CTX_QUERY_SPEC)) return true;
      break;
    case QT_UNION:
      if (fmt->begin_context(CTX_UNION)) return true;
      for (auto child_qt : down_cast<Query_term_union *>(qt)->m_children) {
        if (explain_query_term(explain_thd, query_thd, child_qt)) return true;
      }
      if (down_cast<Query_term_union *>(qt)->m_is_materialized &&
          explain_query_specification(explain_thd, query_thd, qt,
                                      CTX_UNION_RESULT))
        return true;
      if (fmt->end_context(CTX_UNION)) return true;
      break;
    case QT_INTERSECT:
      if (fmt->begin_context(CTX_INTERSECT)) return true;
      for (auto child_qt : down_cast<Query_term_intersect *>(qt)->m_children) {
        if (explain_query_term(explain_thd, query_thd, child_qt)) return true;
      }
      if (explain_query_specification(explain_thd, query_thd, qt,
                                      CTX_INTERSECT_RESULT))
        return true;
      if (fmt->end_context(CTX_INTERSECT)) return true;
      break;
    case QT_EXCEPT:
      if (fmt->begin_context(CTX_EXCEPT)) return true;
      for (auto child_qt : down_cast<Query_term_except *>(qt)->m_children) {
        if (explain_query_term(explain_thd, query_thd, child_qt)) return true;
      }
      if (explain_query_specification(explain_thd, query_thd, qt,
                                      CTX_EXCEPT_RESULT))
        return true;
      if (fmt->end_context(CTX_EXCEPT)) return true;
      break;
    case QT_UNARY:
      if (fmt->begin_context(CTX_UNARY)) return true;
      for (auto child_qt : down_cast<Query_term_unary *>(qt)->m_children) {
        if (explain_query_term(explain_thd, query_thd, child_qt)) return true;
      }
      if (explain_query_specification(explain_thd, query_thd, qt,
                                      CTX_UNARY_RESULT))
        return true;
      if (fmt->end_context(CTX_UNARY)) return true;

      break;
  }

  return false;
}
/**
  Explain query starting from this unit.

  @param explain_thd thread handle for the connection doing explain
  @param query_thd   thread handle for the connection being explained

  @return false if success, true if error
*/

bool Query_expression::explain(THD *explain_thd, const THD *query_thd) {
  DBUG_TRACE;

#ifndef NDEBUG
  Query_block *lex_select_save = query_thd->lex->current_query_block();
#endif
  const bool other = (query_thd != explain_thd);

  assert(other || is_optimized() || outer_query_block()->is_empty_query() ||
         // @todo why is this necessary?
         outer_query_block()->join == nullptr ||
         outer_query_block()->join->zero_result_cause);

  if (explain_query_term(explain_thd, query_thd, query_term())) return true;
  if (!other)
    assert(current_thd->lex->current_query_block() == lex_select_save);
  return false;
}

bool Common_table_expr::clear_all_references() {
  bool reset_tables = false;
  for (Table_ref *tl : references) {
    if (tl->table &&
        tl->derived_query_expression()->uncacheable & UNCACHEABLE_DEPENDENT) {
      reset_tables = true;
      if (tl->derived_query_expression()->query_result()->reset()) return true;
    }
    /*
      This loop has found all non-recursive clones; one writer and N
      readers.
    */
  }
  if (!reset_tables) return false;
  for (Table_ref *tl : tmp_tables) {
    if (tl->is_derived()) continue;  // handled above
    if (tl->table->empty_result_table()) return true;
    // This loop has found all recursive clones (only readers).
  }
  /*
    Above, emptying all clones is necessary, to rewind every handler (cursor) to
    the table's start. Setting materialized=false on all is also important or
    the writer would skip materialization, see loop at start of
    Table_ref::materialize_derived()). There is one "recursive table"
    which we don't find here: it's the UNION DISTINCT tmp table. It's reset in
    unit::execute() of the unit which is the body of the CTE.
  */
  return false;
}

/**
  Empties all correlated query blocks defined within the query expression;
  that is, correlated CTEs defined in the expression's WITH clause, and
  correlated derived tables.
 */
bool Query_expression::clear_correlated_query_blocks() {
  for (Query_block *sl = first_query_block(); sl; sl = sl->next_query_block()) {
    sl->join->clear_corr_derived_tmp_tables();
    sl->join->clear_sj_tmp_tables();
    sl->join->clear_hash_tables();
  }
  if (!m_with_clause) return false;
  for (auto el : m_with_clause->m_list->elements()) {
    Common_table_expr &cte = el->m_postparse;
    if (cte.clear_all_references()) return true;
  }
  return false;
}

bool Query_expression::ClearForExecution() {
  if (is_executed()) {
    if (clear_correlated_query_blocks()) return true;

    // TODO(sgunders): Most of JOIN::reset() should be done in iterators.
    for (auto qt : query_terms<QTC_POST_ORDER>()) {
      if (qt->term_type() == QT_QUERY_BLOCK ||
          down_cast<Query_term_set_op *>(qt)->m_is_materialized) {
        Query_block *sl = qt->query_block();
        if (sl->join->is_executed()) {
          sl->join->reset();
        }
      }
    }
  }

  for (Query_block *query_block = first_query_block(); query_block;
       query_block = query_block->next_query_block()) {
    JOIN *join = query_block->join;
    query_block->join->examined_rows = 0;
    query_block->join
        ->set_executed();  // The dynamic range optimizer expects this.

    // TODO(sgunders): Consider doing this in some iterator instead.
    if (join->m_windows.elements > 0 && !join->m_windowing_steps) {
      // Initialize state of window functions as end_write_wf() will be shortcut
      for (Window &w : query_block->join->m_windows) {
        w.reset_all_wf_state();
      }
    }
  }
  return false;
}

bool Query_expression::ExecuteIteratorQuery(THD *thd) {
  THD_STAGE_INFO(thd, stage_executing);
  DEBUG_SYNC(thd, "before_join_exec");

  Opt_trace_context *const trace = &thd->opt_trace;
  Opt_trace_object trace_wrapper(trace);
  Opt_trace_object trace_exec(trace, "join_execution");
  if (is_simple()) {
    trace_exec.add_select_number(first_query_block()->select_number);
  }
  Opt_trace_array trace_steps(trace, "steps");

  if (ClearForExecution()) {
    return true;
  }

  mem_root_deque<Item *> *fields = get_field_list();
  Query_result *query_result = this->query_result();
  assert(query_result != nullptr);

  if (query_result->start_execution(thd)) return true;

  if (query_result->send_result_set_metadata(
          thd, *fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) {
    return true;
  }

  set_executed();

  // Hand over the query to the secondary engine if needed.
  if (first_query_block()->join->override_executor_func != nullptr) {
    thd->current_found_rows = 0;
    for (Query_block *select = first_query_block(); select != nullptr;
         select = select->next_query_block()) {
      if (select->join->override_executor_func(select->join, query_result)) {
        return true;
      }
      thd->current_found_rows += select->join->send_records;
    }
    const bool calc_found_rows =
        (first_query_block()->active_options() & OPTION_FOUND_ROWS);
    if (!calc_found_rows) {
      // This is for backwards compatibility reasons only;
      // we have documented that without SQL_CALC_FOUND_ROWS,
      // we return the actual number of rows returned.
      thd->current_found_rows =
          std::min(thd->current_found_rows, select_limit_cnt);
    }
    return query_result->send_eof(thd);
  }

  if (item != nullptr) {
    item->reset_has_values();

    if (item->is_value_assigned()) {
      item->reset_value_assigned();  // Prepare for re-execution of this unit
      item->reset();
    }
  }

  // We need to accumulate in the first join's send_records as long as
  // we support SQL_CALC_FOUND_ROWS, since LimitOffsetIterator will use it
  // for reporting rows skipped by OFFSET or LIMIT. When we get rid of
  // SQL_CALC_FOUND_ROWS, we can use a local variable here instead.
  ha_rows *send_records_ptr;
  if (is_simple()) {
    // Not a UNION: found_rows() applies to the join.
    // LimitOffsetIterator will write skipped OFFSET rows into the JOIN's
    // send_records, so use that.
    send_records_ptr = &first_query_block()->join->send_records;
  } else if (set_operation()->m_is_materialized) {
    send_records_ptr = &query_term()->query_block()->join->send_records;
  } else {
    // UNION, but without a fake_query_block (may or may not have a
    // LIMIT): found_rows() applies to the outermost block. See
    // Query_expression::send_records for more information.
    send_records_ptr = &send_records;
  }
  *send_records_ptr = 0;

  thd->get_stmt_da()->reset_current_row_for_condition();

  {
    auto join_cleanup = create_scope_guard([this, thd] {
      for (Query_block *sl = first_query_block(); sl;
           sl = sl->next_query_block()) {
        JOIN *join = sl->join;
        join->join_free();
        thd->inc_examined_row_count(join->examined_rows);
      }
      if (!is_simple() && set_operation()->m_is_materialized)
        thd->inc_examined_row_count(
            query_term()->query_block()->join->examined_rows);
    });

    if (m_root_iterator->Init()) {
      return true;
    }

    PFSBatchMode pfs_batch_mode(m_root_iterator.get());

    for (;;) {
      int error = m_root_iterator->Read();
      DBUG_EXECUTE_IF("bug13822652_1", thd->killed = THD::KILL_QUERY;);

      if (error > 0 || thd->is_error())  // Fatal error
        return true;
      else if (error < 0)
        break;
      else if (thd->killed)  // Aborted by user
      {
        thd->send_kill_message();
        return true;
      }

      ++*send_records_ptr;

      if (query_result->send_data(thd, *fields)) {
        return true;
      }
      thd->get_stmt_da()->inc_current_row_for_condition();

      DBUG_EXECUTE_IF("simulate_partial_result_set_scenario",
                      my_error(ER_UNKNOWN_ERROR, MYF(0)););
    }

    // NOTE: join_cleanup must be done before we send EOF, so that we get the
    // row counts right.
  }

  thd->current_found_rows = *send_records_ptr;

  return query_result->send_eof(thd);
}

/**
  Execute a query expression that may be a UNION and/or have an ordered result.

  @param thd          thread handle

  @returns false if success, true if error
*/

bool Query_expression::execute(THD *thd) {
  DBUG_TRACE;
  assert(is_optimized());

  if (is_executed() && !uncacheable) return false;

  assert(!unfinished_materialization());

  /*
    Even if we return "true" the statement might continue
    (e.g. ER_SUBQUERY_1_ROW in stmt with IGNORE), so we want to restore
    current_query_block():
  */
  Change_current_query_block save_query_block(thd);

  return ExecuteIteratorQuery(thd);
}

/**
  Cleanup this query expression object after preparation or one round
  of execution. After the cleanup, the object can be reused for a
  new round of execution, but a new optimization will be needed before
  the execution.
*/

void Query_expression::cleanup(bool full) {
  DBUG_TRACE;

  if (cleaned >= (full ? UC_CLEAN : UC_PART_CLEAN)) return;

  cleaned = (full ? UC_CLEAN : UC_PART_CLEAN);

  if (full) {
    clear_root_access_path();
  }

  m_operands.clear();

  for (auto qt : query_terms<QTC_PRE_ORDER>()) {
    if (qt->term_type() == QT_QUERY_BLOCK && slave == nullptr)
      continue;  // already invalidated
    // post order fails here for corner case SELECT 1 UNION SELECT 1 LIMIT 0
    qt->cleanup(full);
  }
  // subselect_hash_sj_engine may hold iterators that need to be cleaned up
  // before the MEM_ROOT goes away.
  if (item != nullptr) {
    item->cleanup();
  }

  /*
    explain_marker is (mostly) a property determined at prepare time and must
    thus be preserved for the next execution, if this is a prepared statement.
  */
}

void Query_expression::destroy() {
  /*
    @todo WL#6570 This is incomplete:
    - It does not handle the case where a UNIT is prepared (success or error)
      and not cleaned.
    - It does not handle the case where a UNIT is optimized with error
      and not cleaned.
  */
  assert(!is_optimized() || cleaned == UC_CLEAN);

  for (auto qt : query_terms<QTC_PRE_ORDER>()) {
    if (qt->owning_operand() && qt->setop_query_result() != nullptr &&
        qt->setop_query_result_union()->table != nullptr) {
      // Destroy materialized result set for a set operation
      free_tmp_table(qt->setop_query_result_union()->table);
      qt->result_table().table = nullptr;
    }
    qt->query_block()->destroy();
  }
  m_query_term->destroy_tree();
  m_query_term = nullptr;
  invalidate();
}

#ifndef NDEBUG
void Query_expression::assert_not_fully_clean() {
  assert(cleaned < UC_CLEAN);
  Query_block *sl = first_query_block();
  for (;;) {
    if (!sl) {
      if (is_simple())
        break;
      else
        sl = query_term()->query_block();
    }
    for (Query_expression *lex_query_expression =
             sl->first_inner_query_expression();
         lex_query_expression;
         lex_query_expression = lex_query_expression->next_query_expression())
      lex_query_expression->assert_not_fully_clean();
    if (!is_simple() && sl == query_term()->query_block())
      break;
    else
      sl = sl->next_query_block();
  }
}
#endif

/**
  Change the query result object used to return the final result of
  the unit, replacing occurrences of old_result with new_result.

  @param thd        Thread handle
  @param new_result New query result object
  @param old_result Old query result object

  @retval false Success
  @retval true  Error
*/

bool Query_expression::change_query_result(
    THD *thd, Query_result_interceptor *new_result,
    Query_result_interceptor *old_result) {
  for (Query_block *sl = first_query_block(); sl; sl = sl->next_query_block()) {
    if (sl->query_result() &&
        sl->change_query_result(thd, new_result, old_result))
      return true; /* purecov: inspected */
  }
  set_query_result(new_result);
  return false;
}

/**
  Get column type information for this query expression.

  For a single query block the column types are taken from the list
  of selected items of this block.

  For a union this function assumes that the type holders were created for
  unioned column types of all query blocks, by Query_expression::prepare().

  @note
    The implementation of this function should be in sync with
    Query_expression::prepare()

  @returns List of items as specified in function description.
    May contain hidden fields (item->hidden = true), so the caller
    needs to be able to filter them out.
*/

mem_root_deque<Item *> *Query_expression::get_unit_column_types() {
  return is_set_operation() ? &types : &first_query_block()->fields;
}

size_t Query_expression::num_visible_fields() const {
  return is_set_operation() ? CountVisibleFields(types)
                            : first_query_block()->num_visible_fields();
}

/**
  Get field list for this query expression.

  For a UNION of query blocks, return the field list generated during prepare.
  For a single query block, return the field list after all possible
  intermediate query processing steps are done (optimization is complete).

  @returns List containing fields of the query expression.
*/

mem_root_deque<Item *> *Query_expression::get_field_list() {
  assert(is_optimized());

  if (is_simple())
    return down_cast<Query_block *>(query_term())->join->fields;
  else if (set_operation()->m_is_materialized)
    return query_term()->query_block()->join->fields;
  else
    return query_term()->fields();
}

bool Query_expression::walk(Item_processor processor, enum_walk walk,
                            uchar *arg) {
  for (auto qt : query_terms<>())
    if (qt->query_block()->walk(processor, walk, arg)) return true;

  return false;
}

void Query_expression::change_to_access_path_without_in2exists(THD *thd) {
  for (Query_block *select = first_query_block(); select != nullptr;
       select = select->next_query_block()) {
    select->join->change_to_access_path_without_in2exists();
  }
  create_access_paths(thd);
}

/**
  Closes (and, if last reference, drops) temporary tables created to
  materialize derived tables, schema tables and CTEs.

  @param list List of tables to search in
*/
static void cleanup_tmp_tables(Table_ref *list) {
  for (auto tl = list; tl; tl = tl->next_local) {
    if (tl->merge_underlying_list) {
      // Find a materialized view inside another view.
      cleanup_tmp_tables(tl->merge_underlying_list);
    } else if (tl->is_table_function()) {
      tl->table_function->cleanup();
    }
    if (tl->table == nullptr) continue;  // Not materialized
    if ((tl->is_view_or_derived() || tl->is_recursive_reference() ||
         tl->schema_table || tl->is_table_function())) {
      close_tmp_table(tl->table);
      if (tl->schema_table) {
        free_tmp_table(tl->table);  // Schema tables are per execution
        tl->table = nullptr;
      } else {
        // Clear indexes added during optimization, keep possible unique index
        TABLE *t = tl->table;
        t->s->keys = t->s->is_distinct ? 1 : 0;
        t->s->first_unused_tmp_key = 0;
        t->keys_in_use_for_query.clear_all();
        t->keys_in_use_for_group_by.clear_all();
        t->keys_in_use_for_order_by.clear_all();
      }
    }
  }
}

/**
   Destroy temporary tables created to materialize derived tables,
   schema tables and CTEs.

   @param list List of tables to search in
*/
static void destroy_tmp_tables(Table_ref *list) {
  for (auto tl = list; tl; tl = tl->next_local) {
    if (tl->merge_underlying_list) {
      // Find a materialized view inside another view.
      destroy_tmp_tables(tl->merge_underlying_list);
    } else if (tl->is_table_function()) {
      tl->table_function->destroy();
    }
    // If this table has a reference to CTE, we need to remove it.
    if (tl->common_table_expr() != nullptr) {
      tl->common_table_expr()->references.erase_value(tl);
    }
    if (tl->table == nullptr) continue;  // Not materialized
    assert(tl->schema_table == nullptr);
    if (tl->is_view_or_derived() || tl->is_recursive_reference() ||
        tl->schema_table || tl->is_table_function()) {
      free_tmp_table(tl->table);
      tl->table = nullptr;
    }
  }
}

/**
  Cleanup after preparation of one round of execution.
*/
void Query_block::cleanup(bool full) {
  cleanup_query_result(full);

  if (join) {
    if (full) {
      assert(join->query_block == this);
      join->destroy();
      ::destroy_at(join);
      join = nullptr;
    } else
      join->cleanup();
  }

  if (full) {
    cleanup_tmp_tables(get_table_list());
    if (hidden_items_from_optimization > 0) remove_hidden_items();
    if (m_windows.elements > 0) {
      List_iterator<Window> li(m_windows);
      Window *w;
      while ((w = li++)) w->cleanup();
    }
  }

  for (Query_expression *qe = first_inner_query_expression(); qe != nullptr;
       qe = qe->next_query_expression()) {
    qe->cleanup(full);
  }
}

void Query_block::cleanup_all_joins() {
  if (join) join->cleanup();

  for (Query_expression *unit = first_inner_query_expression(); unit;
       unit = unit->next_query_expression()) {
    for (Query_block *sl = unit->first_query_block(); sl;
         sl = sl->next_query_block())
      sl->cleanup_all_joins();
  }
}

void Query_block::destroy() {
  Query_expression *unit = first_inner_query_expression();
  while (unit != nullptr) {
    Query_expression *next = unit->next_query_expression();
    unit->destroy();
    unit = next;
  }

  List_iterator<Window> li(m_windows);
  Window *w;
  while ((w = li++)) w->destroy();

  // Destroy allocated derived tables
  destroy_tmp_tables(get_table_list());

  // Our destructor is not called, so we need to make sure
  // all the memory for these arrays is freed.
  if (olap == ROLLUP_TYPE) {
    rollup_group_items.clear();
    rollup_group_items.shrink_to_fit();
    rollup_sums.clear();
    rollup_sums.shrink_to_fit();
  }
  invalidate();
}
