static unique_ptr SetObjectMembers()

in sql/join_optimizer/explain_access_path.cc [1165:2047]


static unique_ptr<Json_object> SetObjectMembers(
    unique_ptr<Json_object> ret_obj, const AccessPath *path,
    const AccessPath *materialized_path, JOIN *join,
    vector<ExplainChild> *children) {
  bool error = false;
  string description;

  // The obj to be returned might get changed when processing some of the
  // paths. So keep a handle to the original object, in case we later add any
  // more fields.
  Json_object *obj = ret_obj.get();

  /* Get path-specific info, including the description string */
  switch (path->type) {
    case AccessPath::TABLE_SCAN: {
      const TABLE &table = *path->table_scan().table;
      description += string("Table scan on ") + table.alias;
      if (table.s->is_secondary_engine()) {
        error |= AddMemberToObject<Json_string>(obj, "secondary_engine",
                                                table.file->table_type());
        description +=
            string(" in secondary engine ") + table.file->table_type();
      }
      description += table.file->explain_extra();

      error |= AddTableInfoToObject(obj, &table);
      error |= AddMemberToObject<Json_string>(obj, "access_type", "table");
      if (!table.file->explain_extra().empty())
        error |= AddMemberToObject<Json_string>(obj, "message",
                                                table.file->explain_extra());
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::SAMPLE_SCAN: {
      const TABLE &table = *path->sample_scan().table;
      description += string("Sample scan on ") + table.alias;
      if (table.s->is_secondary_engine()) {
        error |= AddMemberToObject<Json_string>(obj, "secondary_engine",
                                                table.file->table_type());
        description +=
            string(" in secondary engine ") + table.file->table_type();
      }
      description += table.file->explain_extra();

      error |= AddMemberToObject<Json_string>(obj, "table_name", table.alias);
      error |= AddMemberToObject<Json_string>(obj, "access_type", "table");
      error |= AddChildrenFromPushedCondition(table, children);

      error |= AddMemberToObject<Json_string>(
          obj, "sampling_type",
          SamplingTypeToString(table.pos_in_table_list->get_sampling_type()));
      error |= AddMemberToObject<Json_double>(
          obj, "percentage",
          table.pos_in_table_list->get_sampling_percentage());

      break;
    }
    case AccessPath::INDEX_SCAN: {
      const TABLE &table = *path->index_scan().table;
      assert(table.file->pushed_idx_cond == nullptr);

      const KEY &key = table.key_info[path->index_scan().idx];
      error |= SetIndexInfoInObject(
          &description, path, "index_scan", nullptr, table, key, "scan",
          /*index_lookup*/ nullptr, /*range*/ nullptr, nullptr,
          path->index_scan().reverse, /*push_condition*/ nullptr, obj);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::INDEX_DISTANCE_SCAN: {
      const TABLE &table = *path->index_distance_scan().table;
      assert(table.file->pushed_idx_cond == nullptr);

      const KEY &key = table.key_info[path->index_distance_scan().idx];
      error |= SetIndexInfoInObject(&description, path, "index_distance_scan",
                                    nullptr, table, key, "distance scan",
                                    /*index_lookup*/ nullptr,
                                    /*range*/ nullptr, nullptr, false,
                                    /*push_condition*/ nullptr, obj);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::REF: {
      const TABLE &table = *path->ref().table;
      const KEY &key = table.key_info[path->ref().ref->key];
      error |=
          SetIndexInfoInObject(&description, path, "index_lookup", nullptr,
                               table, key, "lookup", path->ref().ref,
                               /*ranges=*/nullptr, nullptr, path->ref().reverse,
                               table.file->pushed_idx_cond, obj);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::REF_OR_NULL: {
      const TABLE &table = *path->ref_or_null().table;
      const KEY &key = table.key_info[path->ref_or_null().ref->key];
      error |= SetIndexInfoInObject(
          &description, path, "index_lookup", nullptr, table, key, "lookup",
          path->ref_or_null().ref,
          /*ranges=*/nullptr, nullptr, false, table.file->pushed_idx_cond, obj);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::EQ_REF: {
      const TABLE &table = *path->eq_ref().table;
      const KEY &key = table.key_info[path->eq_ref().ref->key];
      error |= SetIndexInfoInObject(
          &description, path, "index_lookup", "Single-row", table, key,
          "lookup", path->eq_ref().ref,
          /*ranges=*/nullptr, nullptr, false, table.file->pushed_idx_cond, obj);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::PUSHED_JOIN_REF: {
      const TABLE &table = *path->pushed_join_ref().table;
      assert(table.file->pushed_idx_cond == nullptr);
      const KEY &key = table.key_info[path->pushed_join_ref().ref->key];
      error |= SetIndexInfoInObject(
          &description, path, "pushed_join_ref",
          path->pushed_join_ref().is_unique ? "Single-row" : nullptr, table,
          key, "lookup", path->pushed_join_ref().ref,
          /*ranges=*/nullptr, nullptr,
          /*reverse=*/false, nullptr, obj);
      break;
    }
    case AccessPath::FULL_TEXT_SEARCH: {
      const TABLE &table = *path->full_text_search().table;
      assert(table.file->pushed_idx_cond == nullptr);
      const KEY &key = table.key_info[path->full_text_search().ref->key];
      error |= SetIndexInfoInObject(&description, path, "full_text_search",
                                    "Full-text", table, key, "search",
                                    path->full_text_search().ref,
                                    /*ranges=*/nullptr, nullptr,
                                    /*reverse=*/false, nullptr, obj);
      break;
    }
    case AccessPath::CONST_TABLE: {
      const TABLE &table = *path->const_table().table;
      assert(table.file->pushed_idx_cond == nullptr);
      assert(table.file->pushed_cond == nullptr);
      description = string("Constant row from ") + table.alias;
      error |=
          AddMemberToObject<Json_string>(obj, "access_type", "constant_row");
      error |= AddTableInfoToObject(obj, &table);
      break;
    }
    case AccessPath::MRR: {
      const TABLE &table = *path->mrr().table;
      const KEY &key = table.key_info[path->mrr().ref->key];
      error |= SetIndexInfoInObject(
          &description, path, "multi_range_read", "Multi-range", table, key,
          "lookup", path->mrr().ref,
          /*ranges=*/nullptr, nullptr, false, table.file->pushed_idx_cond, obj);
      error |= AddMemberToObject<Json_boolean>(obj, "multi_range_read", true);
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::FOLLOW_TAIL:
      description =
          string("Scan new records on ") + path->follow_tail().table->alias;
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "scan_new_records");
      error |= AddTableInfoToObject(obj, path->follow_tail().table);
      error |=
          AddChildrenFromPushedCondition(*path->follow_tail().table, children);
      break;
    case AccessPath::INDEX_RANGE_SCAN: {
      const auto &param = path->index_range_scan();
      const TABLE &table = *param.used_key_part[0].field->table;
      const KEY &key_info = table.key_info[param.index];

      unique_ptr<Json_array> range_arr(new (std::nothrow) Json_array());
      if (range_arr == nullptr) return nullptr;
      string ranges;
      error |= PrintRanges(param.ranges, param.num_ranges, key_info.key_part,
                           /*single_part_only=*/false, range_arr, &ranges);

      // A range scan could use MRR optimization if possible. However, unless
      // multi_range_read_init() is called, we do not have a way to know if the
      // optimization will be used even though range optimizer has picked it. If
      // the range scan requires rows in order, the storage engine might not be
      // able to provide the order when using MRR optimization, in which case it
      // switches to the default implementation. Calling multi_range_read_init()
      // can potentially be costly, so it is not done when executing an EXPLAIN.
      // We therefore simulate its effect here:
      uint mrr_flags = param.mrr_flags;
      bool using_mrr = false;
      if ((!(mrr_flags & HA_MRR_SORTED) || mrr_flags & HA_MRR_SUPPORT_SORTED) &&
          !(mrr_flags & HA_MRR_USE_DEFAULT_IMPL)) {
        using_mrr = true;
        error |= AddMemberToObject<Json_boolean>(obj, "multi_range_read", true);
      }
      error |= SetIndexInfoInObject(
          &description, path, "index_range_scan", nullptr, table, key_info,
          using_mrr ? "range scan (Multi-Range Read)" : "range scan",
          /*index_lookup*/ nullptr, &ranges, std::move(range_arr),
          path->index_range_scan().reverse, table.file->pushed_idx_cond, obj);

      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::INDEX_MERGE: {
      const auto &param = path->index_merge();
      error |=
          AddMemberToObject<Json_string>(obj, "access_type", "index_merge");
      description = "Sort-deduplicate by row ID";
      for (AccessPath *child : *path->index_merge().children) {
        if (param.allow_clustered_primary_key_scan &&
            param.table->file->primary_key_is_clustered() &&
            child->index_range_scan().index == param.table->s->primary_key) {
          children->push_back(
              {child, "Clustered primary key (scanned separately)"});
        } else {
          children->push_back({child});
        }
      }
      break;
    }
    case AccessPath::ROWID_INTERSECTION: {
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "rowid_intersection");
      description = "Intersect rows sorted by row ID";
      for (AccessPath *child : *path->rowid_intersection().children) {
        children->push_back({child});
      }
      if (AccessPath *cpk_child = path->rowid_intersection().cpk_child;
          cpk_child != nullptr) {
        children->push_back({cpk_child});
      }
      break;
    }
    case AccessPath::ROWID_UNION: {
      error |=
          AddMemberToObject<Json_string>(obj, "access_type", "rowid_union");
      description = "Deduplicate rows sorted by row ID";
      for (AccessPath *child : *path->rowid_union().children) {
        children->push_back({child});
      }
      break;
    }
    case AccessPath::INDEX_SKIP_SCAN: {
      error |= ExplainIndexSkipScanAccessPath(obj, path, join, &description);
      break;
    }
    case AccessPath::GROUP_INDEX_SKIP_SCAN: {
      error |=
          ExplainGroupIndexSkipScanAccessPath(obj, path, join, &description);
      break;
    }
    case AccessPath::DYNAMIC_INDEX_RANGE_SCAN: {
      const TABLE &table = *path->dynamic_index_range_scan().table;
      description += string("Index range scan on ") + table.alias +
                     " (re-planned for each iteration)";
      if (table.file->pushed_idx_cond != nullptr) {
        description += ", with index condition: " +
                       ItemToString(table.file->pushed_idx_cond);
      }
      description += table.file->explain_extra();
      error |= AddMemberToObject<Json_string>(obj, "access_type", "index");
      error |= AddMemberToObject<Json_string>(obj, "index_access_type",
                                              "dynamic_index_range_scan");
      error |= AddTableInfoToObject(obj, &table);
      if (table.file->pushed_idx_cond != nullptr) {
        error |= AddMemberToObject<Json_string>(
            obj, "pushed_index_condition",
            ItemToString(table.file->pushed_idx_cond));
      }
      if (!table.file->explain_extra().empty()) {
        error |= AddMemberToObject<Json_string>(obj, "message",
                                                table.file->explain_extra());
      }
      error |= AddChildrenFromPushedCondition(table, children);
      break;
    }
    case AccessPath::TABLE_VALUE_CONSTRUCTOR:
    case AccessPath::FAKE_SINGLE_ROW:
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "rows_fetched_before_execution");
      description = "Rows fetched before execution";
      break;
    case AccessPath::ZERO_ROWS:
      error |= AddMemberToObject<Json_string>(obj, "access_type", "zero_rows");
      error |= AddMemberToObject<Json_string>(obj, "zero_rows_cause",
                                              path->zero_rows().cause);
      description = string("Zero rows (") + path->zero_rows().cause + ")";
      // The child is not printed as part of the iterator tree.
      break;
    case AccessPath::ZERO_ROWS_AGGREGATED:
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "zero_rows_aggregated");
      error |= AddMemberToObject<Json_string>(
          obj, "zero_rows_cause", path->zero_rows_aggregated().cause);
      description = string("Zero input rows (") +
                    path->zero_rows_aggregated().cause +
                    "), aggregated into one output row";
      break;
    case AccessPath::MATERIALIZED_TABLE_FUNCTION:
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "materialized_table_function");
      description = "Materialize table function";
      break;
    case AccessPath::UNQUALIFIED_COUNT:
      error |= AddMemberToObject<Json_string>(obj, "access_type", "count_rows");
      error |= AddTableInfoToObject(obj, join->qep_tab->table());
      description = "Count rows in " + string(join->qep_tab->table()->alias);
      break;
    case AccessPath::NESTED_LOOP_JOIN: {
      string join_type = JoinTypeToString(path->nested_loop_join().join_type);
      error |= AddMemberToObject<Json_string>(obj, "access_type", "join");
      error |= AddMemberToObject<Json_string>(obj, "join_type", join_type);
      error |=
          AddMemberToObject<Json_string>(obj, "join_algorithm", "nested_loop");
      description = "Nested loop " + join_type;
      if (path->nested_loop_join().join_type == JoinType::SEMI) {
        description = description + " (FirstMatch)";
        error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                "firstmatch");
      }
      const JoinPredicate *predicate = path->nested_loop_join().join_predicate;
      if (predicate != nullptr &&
          predicate->expr->type == RelationalExpression::SEMIJOIN &&
          path->nested_loop_join().join_type == JoinType::INNER) {
        if (path->nested_loop_join().outer->type ==
            AccessPath::REMOVE_DUPLICATES) {
          description.append(" (LooseScan)");
          error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                  "loosescan");
        } else {
          description.append(" (FirstMatch)");
          error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                  "firstmatch");
        }
      }
      if (path->nested_loop_join().outer->type ==
          AccessPath::REMOVE_DUPLICATES_ON_INDEX) {
        description.append(" (LooseScan)");
        error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                "loosescan");
      }
      children->push_back({path->nested_loop_join().outer});
      children->push_back({path->nested_loop_join().inner});
      break;
    }
    case AccessPath::NESTED_LOOP_SEMIJOIN_WITH_DUPLICATE_REMOVAL:
      // No json fields since this path is not supported in hypergraph
      description =
          string(
              "Nested loop semijoin (FirstMatch) with duplicate removal "
              "(LooseScan) on ") +
          path->nested_loop_semijoin_with_duplicate_removal().key->name;
      error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                              "firstmatch_with_loosescan");
      children->push_back(
          {path->nested_loop_semijoin_with_duplicate_removal().outer});
      children->push_back(
          {path->nested_loop_semijoin_with_duplicate_removal().inner});
      break;
    case AccessPath::BKA_JOIN: {
      string join_type = JoinTypeToString(path->bka_join().join_type);
      error |= AddMemberToObject<Json_string>(obj, "access_type", "join");
      error |= AddMemberToObject<Json_string>(obj, "join_type", join_type);
      error |= AddMemberToObject<Json_string>(obj, "join_algorithm",
                                              "batch_key_access");
      description = "Batched key access " + join_type;
      children->push_back({path->bka_join().outer, "Batch input rows"});
      children->push_back({path->bka_join().inner});
      break;
    }
    case AccessPath::HASH_JOIN: {
      const JoinPredicate *predicate = path->hash_join().join_predicate;
      RelationalExpression::Type type = path->hash_join().rewrite_semi_to_inner
                                            ? RelationalExpression::INNER_JOIN
                                            : predicate->expr->type;
      THD *const thd = current_thd;

      string json_join_type;
      description = HashJoinTypeToString(type, &json_join_type);
      if (predicate->expr->type == RelationalExpression::SEMIJOIN) {
        error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                "firstmatch");
      }
      if (path->hash_join().rewrite_semi_to_inner) {
        if (path->hash_join().outer->type == AccessPath::REMOVE_DUPLICATES) {
          description.append(" (LooseScan)");
          error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                  "loosescan");
        } else {
          description.append(" (FirstMatch)");
          error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                  "firstmatch");
        }
      }
      if ((type != RelationalExpression::SEMIJOIN) &&
          path->hash_join().inner->type ==
              AccessPath::REMOVE_DUPLICATES_ON_INDEX) {
        description.append(" (LooseScan)");
        error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                "loosescan");
      }

      unique_ptr<Json_array> hash_condition(new (std::nothrow) Json_array());
      if (hash_condition == nullptr) return nullptr;
      vector<HashJoinCondition> equijoin_conditions;
      equijoin_conditions.reserve(predicate->expr->equijoin_conditions.size());
      for (Item_eq_base *cond : predicate->expr->equijoin_conditions) {
        equijoin_conditions.emplace_back(cond, thd->mem_root);
      }
      if (equijoin_conditions.empty()) {
        if ((type != RelationalExpression::SEMIJOIN) &&
            path->hash_join().inner->type == AccessPath::LIMIT_OFFSET &&
            path->hash_join().inner->limit_offset().limit == 1) {
          description.append(" (FirstMatch)");
          error |= AddMemberToObject<Json_string>(obj, "semijoin_strategy",
                                                  "firstmatch");
        } else {
          description.append(" (no condition)");
        }
      } else {
        bool first = true;
        for (const HashJoinCondition &hj_cond : equijoin_conditions) {
          if (!first) {
            description.push_back(',');
          }
          first = false;
          string condition_str;
          if (!hj_cond.store_full_sort_key()) {
            condition_str =
                "(<hash>(" + ItemToString(hj_cond.left_extractor()) +
                ")=<hash>(" + ItemToString(hj_cond.right_extractor()) + "))";
          } else {
            condition_str = ItemToString(hj_cond.join_condition());
          }
          error |=
              AddElementToArray<Json_string>(hash_condition, condition_str);
          description.append(" " + condition_str);
        }
      }
      error |= obj->add_alias("hash_condition", std::move(hash_condition));

      const Mem_root_array<Item *> *extra_join_conditions =
          GetExtraHashJoinConditions(
              thd->mem_root, thd->lex->using_hypergraph_optimizer(),
              equijoin_conditions, predicate->expr->join_conditions);
      if (extra_join_conditions == nullptr) return nullptr;

      unique_ptr<Json_array> extra_condition(new (std::nothrow) Json_array());
      if (extra_condition == nullptr) return nullptr;
      bool first = true;
      for (Item *cond : *extra_join_conditions) {
        if (first) {
          description.append(", extra conditions: ");
          first = false;
        } else {
          description += " and ";
        }
        string condition_str = ItemToString(cond);
        description += condition_str;
        error |= AddElementToArray<Json_string>(extra_condition, condition_str);
      }
      if (extra_condition->size() > 0)
        error |= obj->add_alias("extra_condition", std::move(extra_condition));

      error |= AddMemberToObject<Json_string>(obj, "access_type", "join");
      error |= AddMemberToObject<Json_string>(obj, "join_type", json_join_type);
      error |= AddMemberToObject<Json_string>(obj, "join_algorithm", "hash");
      children->push_back({path->hash_join().outer});
      children->push_back({path->hash_join().inner, "Hash"});

      const RelationalExpression *join_predicate =
          path->hash_join().join_predicate->expr;
      ColumnNameCollector cnc;
      for (Item_eq_base *cond : join_predicate->equijoin_conditions) {
        AddSubqueryPaths(cond, "condition", children);
        WalkItem(cond, enum_walk::PREFIX, cnc);
      }
      for (Item *cond : join_predicate->join_conditions) {
        AddSubqueryPaths(cond, "extra conditions", children);
        WalkItem(cond, enum_walk::PREFIX, cnc);
      }
      unique_ptr<Json_array> join_columns(new (std::nothrow) Json_array());
      if (join_columns == nullptr) return nullptr;
      for (const std::string &column_name : cnc.column_names()) {
        error |= AddElementToArray<Json_string>(join_columns, column_name);
      }
      error |= obj->add_alias("join_columns", std::move(join_columns));

      break;
    }
    case AccessPath::FILTER: {
      error |= AddMemberToObject<Json_string>(obj, "access_type", "filter");
      string filter = ItemToString(path->filter().condition);
      error |= AddMemberToObject<Json_string>(obj, "condition", filter);
      description = "Filter: " + filter;
      children->push_back({path->filter().child});
      AddSubqueryPaths(path->filter().condition, "condition", children);
      ColumnNameCollector cnc;
      WalkItem(path->filter().condition, enum_walk::PREFIX, cnc);
      unique_ptr<Json_array> filter_columns(new (std::nothrow) Json_array());
      if (filter_columns == nullptr) return nullptr;
      for (const std::string &column_name : cnc.column_names()) {
        error |= AddElementToArray<Json_string>(filter_columns, column_name);
      }
      error |= obj->add_alias("filter_columns", std::move(filter_columns));

      break;
    }
    case AccessPath::SORT: {
      error |= AddMemberToObject<Json_string>(obj, "access_type", "sort");
      if (path->sort().force_sort_rowids) {
        description = "Sort row IDs";
        error |= AddMemberToObject<Json_boolean>(obj, "row_ids", true);
      } else {
        description = "Sort";
      }
      if (path->sort().remove_duplicates) {
        description += " with duplicate removal: ";
        error |=
            AddMemberToObject<Json_boolean>(obj, "duplicate_removal", true);
      } else {
        description += ": ";
      }

      unique_ptr<Json_array> sort_fields(new (std::nothrow) Json_array());
      if (sort_fields == nullptr) return nullptr;
      for (ORDER *order = path->sort().order; order != nullptr;
           order = order->next) {
        if (order != path->sort().order) {
          description += ", ";
        }

        // We usually want to print the item_name if it's set, so that we get
        // the alias instead of the full expression when there is an alias. If
        // it is a field reference, we prefer ItemToString() because item_name
        // in Item_field doesn't include the table name.
        string sort_field;
        if (const Item *item = *order->item;
            item->item_name.is_set() && item->type() != Item::FIELD_ITEM) {
          sort_field = item->item_name.ptr();
        } else {
          sort_field = ItemToString(item);
        }
        if (order->direction == ORDER_DESC) {
          sort_field += " DESC";
        }
        description += sort_field;
        error |= AddElementToArray<Json_string>(sort_fields, sort_field);
      }
      error |= obj->add_alias("sort_fields", std::move(sort_fields));

      if (const ha_rows limit = path->sort().limit; limit != HA_POS_ERROR) {
        char buf[256];
        error |= AddMemberToObject<Json_int>(obj, "per_chunk_limit", limit);
        snprintf(buf, sizeof(buf), ", limit input to %llu row(s) per chunk",
                 limit);
        description += buf;
      }
      children->push_back({path->sort().child});
      break;
    }
    case AccessPath::AGGREGATE: {
      string ret;
      error |= AddMemberToObject<Json_string>(obj, "access_type", "aggregate");
      if (join->grouped || join->group_optimized_away) {
        error |= AddMemberToObject<Json_boolean>(obj, "group_by", true);
        if (*join->sum_funcs == nullptr) {
          description = "Group (no aggregates)";
        } else if (path->aggregate().olap == ROLLUP_TYPE) {
          error |= AddMemberToObject<Json_boolean>(obj, "rollup", true);
          description = "Group aggregate with rollup: ";
        } else if (path->aggregate().olap == CUBE_TYPE) {
          error |= AddMemberToObject<Json_boolean>(obj, "cube", true);
          description = "Group aggregate with cube: ";
        } else {
          description = "Group aggregate: ";
        }
      } else {
        description = "Aggregate: ";
      }

      unique_ptr<Json_array> funcs(new (std::nothrow) Json_array());
      if (funcs == nullptr) return nullptr;
      bool first = true;
      for (Item_sum **item = join->sum_funcs; *item != nullptr; ++item) {
        if (first) {
          first = false;
        } else {
          description += ", ";
        }
        string func = (path->aggregate().olap == ROLLUP_TYPE
                           ? ItemToString((*item)->unwrap_sum())
                           : ItemToString(*item));
        description += func;
        error |= AddElementToArray<Json_string>(funcs, func);
      }

      // If there are no aggs, still let this field print a "" rather than
      // omit this field.
      error |= obj->add_alias("functions", std::move(funcs));

      children->push_back({path->aggregate().child});
      break;
    }
    case AccessPath::TEMPTABLE_AGGREGATE: {
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "temp_table_aggregate");
      // Old optimizer does not do cost estimation, so don't care about passing
      // materialization path.
      const AccessPath *materialization_path =
          (current_thd->lex->using_hypergraph_optimizer() ? path : nullptr);

      ret_obj =
          AssignParentPath(path->temptable_aggregate().table_path,
                           materialization_path, std::move(ret_obj), join);
      if (ret_obj == nullptr) return nullptr;
      description = "Aggregate using temporary table";
      children->push_back({path->temptable_aggregate().subquery_path});
      break;
    }
    case AccessPath::LIMIT_OFFSET: {
      error |= AddMemberToObject<Json_string>(obj, "access_type", "limit");
      char buf[256];
      if (path->limit_offset().offset == 0) {
        snprintf(buf, sizeof(buf), "Limit: %llu row(s)",
                 path->limit_offset().limit);
      } else if (path->limit_offset().limit == HA_POS_ERROR) {
        snprintf(buf, sizeof(buf), "Offset: %llu row(s)",
                 path->limit_offset().offset);
      } else {
        snprintf(buf, sizeof(buf), "Limit/Offset: %llu/%llu row(s)",
                 path->limit_offset().limit - path->limit_offset().offset,
                 path->limit_offset().offset);
      }
      error |=
          AddMemberToObject<Json_int>(obj, "limit", path->limit_offset().limit);
      error |= AddMemberToObject<Json_int>(obj, "limit_offset",
                                           path->limit_offset().offset);
      if (path->limit_offset().count_all_rows) {
        error |= AddMemberToObject<Json_boolean>(obj, "count_all_rows", true);
        description =
            string(buf) + " (no early end due to SQL_CALC_FOUND_ROWS)";
      } else {
        description = buf;
      }
      children->push_back({path->limit_offset().child});
      break;
    }
    case AccessPath::STREAM:
      error |= AddMemberToObject<Json_string>(obj, "access_type", "stream");
      description = "Stream results";
      children->push_back({path->stream().child});
      break;
    case AccessPath::MATERIALIZE:
      error |=
          AddMemberToObject<Json_string>(obj, "access_type", "materialize");
      ret_obj =
          ExplainMaterializeAccessPath(path, join, std::move(ret_obj), children,
                                       current_thd->lex->is_explain_analyze);
      if (ret_obj == nullptr) return nullptr;
      break;
    case AccessPath::MATERIALIZE_INFORMATION_SCHEMA_TABLE: {
      ret_obj = AssignParentPath(
          path->materialize_information_schema_table().table_path, nullptr,
          std::move(ret_obj), join);
      if (ret_obj == nullptr) return nullptr;
      const TABLE *table =
          path->materialize_information_schema_table().table_list->table;
      error |= AddTableInfoToObject(obj, table);
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "materialize_information_schema");
      description = "Fill information schema table " + string(table->alias);
      break;
    }
    case AccessPath::APPEND:
      error |= AddMemberToObject<Json_string>(obj, "access_type", "append");
      description = "Append";
      for (const AppendPathParameters &child : *path->append().children) {
        children->push_back({child.path, "", child.join});
      }
      break;
    case AccessPath::WINDOW: {
      Window *const window = path->window().window;
      if (path->window().needs_buffering) {
        error |= AddMemberToObject<Json_boolean>(obj, "buffering", true);
        if (window->optimizable_row_aggregates() ||
            window->optimizable_range_aggregates() ||
            window->static_aggregates()) {
          description = "Window aggregate with buffering: ";
        } else {
          error |= AddMemberToObject<Json_boolean>(obj, "multi_pass", true);
          description = "Window multi-pass aggregate with buffering: ";
        }
      } else {
        description = "Window aggregate: ";
      }

      unique_ptr<Json_array> funcs(new (std::nothrow) Json_array());
      if (funcs == nullptr) return nullptr;
      bool first = true;
      for (const Item_sum &func : window->functions()) {
        if (!first) {
          description += ", ";
        }
        string func_str = ItemToString(&func);
        description += func_str;
        error |= AddElementToArray<Json_string>(funcs, func_str);
        first = false;
      }
      error |= obj->add_alias("functions", std::move(funcs));
      error |= AddMemberToObject<Json_string>(obj, "access_type", "window");
      children->push_back({path->window().child});
      // temp_table_param may be nullptr for secondary engine,
      // see ExplainWindowForExternalExecutor in hypergraph_optimizer-t.cc.
      if (path->window().temp_table_param != nullptr) {
        for (const Func_ptr &func :
             *path->window().temp_table_param->items_to_copy) {
          AddSubqueryPaths(func.func(), "projection", children);
        }
      }

      break;
    }
    case AccessPath::WEEDOUT: {
      SJ_TMP_TABLE *sj = path->weedout().weedout_table;
      unique_ptr<Json_array> tables(new (std::nothrow) Json_array());
      if (tables == nullptr) return nullptr;

      description = "Remove duplicate ";
      if (sj->tabs_end == sj->tabs + 1) {  // Only one table.
        description += sj->tabs->qep_tab->table()->alias;
        error |= AddElementToArray<Json_string>(
            tables, sj->tabs->qep_tab->table()->alias);
      } else {
        description += "(";
        for (SJ_TMP_TABLE_TAB *tab = sj->tabs; tab != sj->tabs_end; ++tab) {
          if (tab != sj->tabs) {
            description += ", ";
          }
          description += tab->qep_tab->table()->alias;
          error |= AddElementToArray<Json_string>(tables,
                                                  tab->qep_tab->table()->alias);
        }
        description += ")";
      }
      description += " rows using temporary table (weedout)";
      error |= obj->add_alias("tables", std::move(tables));
      error |=
          AddMemberToObject<Json_string>(obj, "semijoin_strategy", "weedout");
      children->push_back({path->weedout().child});
      break;
    }
    case AccessPath::REMOVE_DUPLICATES: {
      description = "Remove duplicates from input grouped on ";
      unique_ptr<Json_array> group_items(new (std::nothrow) Json_array());
      if (group_items == nullptr) return nullptr;
      for (int i = 0; i < path->remove_duplicates().group_items_size; ++i) {
        string group_item =
            ItemToString(path->remove_duplicates().group_items[i]);
        if (i != 0) {
          description += ", ";
        }
        description += group_item;
        error |= AddElementToArray<Json_string>(group_items, group_item);
      }
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "remove_duplicates_from_groups");
      error |= obj->add_alias("group_items", std::move(group_items));
      children->push_back({path->remove_duplicates().child});
      break;
    }
    case AccessPath::REMOVE_DUPLICATES_ON_INDEX: {
      const char *keyname = path->remove_duplicates_on_index().key->name;
      description = string("Remove duplicates from input sorted on ") + keyname;
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "remove_duplicates_on_index");
      error |= AddMemberToObject<Json_string>(obj, "index_name", keyname);
      children->push_back({path->remove_duplicates_on_index().child});
      break;
    }
    case AccessPath::ALTERNATIVE: {
      const TABLE &table =
          *path->alternative().table_scan_path->table_scan().table;
      const Index_lookup *ref = path->alternative().used_ref;
      const KEY &key = table.key_info[ref->key];

      int num_applicable_cond_guards = 0;
      for (unsigned key_part_idx = 0; key_part_idx < ref->key_parts;
           ++key_part_idx) {
        if (ref->cond_guards[key_part_idx] != nullptr) {
          ++num_applicable_cond_guards;
        }
      }

      description = "Alternative plans for IN subquery: Index lookup unless ";
      if (num_applicable_cond_guards > 1) {
        description += " any of (";
      }
      bool first = true;
      for (unsigned key_part_idx = 0; key_part_idx < ref->key_parts;
           ++key_part_idx) {
        if (ref->cond_guards[key_part_idx] != nullptr) {
          if (!first) {
            description += ", ";
          }
          first = false;
          description += key.key_part[key_part_idx].field->field_name;
        }
      }
      if (num_applicable_cond_guards > 1) {
        description += ")";
      }
      description += " IS NULL";
      error |= AddMemberToObject<Json_string>(
          obj, "access_type", "alternative_plans_for_in_subquery");
      children->push_back({path->alternative().child});
      children->push_back({path->alternative().table_scan_path});
      break;
    }
    case AccessPath::CACHE_INVALIDATOR:
      description = string("Invalidate materialized tables (row from ") +
                    path->cache_invalidator().name + ")";
      error |= AddMemberToObject<Json_string>(obj, "access_type",
                                              "invalidate_materialized_tables");
      error |= AddMemberToObject<Json_string>(obj, "table_name",
                                              path->cache_invalidator().name);
      children->push_back({path->cache_invalidator().child});
      break;
    case AccessPath::DELETE_ROWS: {
      error |=
          AddMemberToObject<Json_string>(obj, "access_type", "delete_rows");
      string tables;
      for (Table_ref *t = join->query_block->leaf_tables; t != nullptr;
           t = t->next_leaf) {
        if (Overlaps(t->map(), path->delete_rows().tables_to_delete_from)) {
          if (!tables.empty()) {
            tables.append(", ");
          }
          tables.append(t->alias);
          if (Overlaps(t->map(), path->delete_rows().immediate_tables)) {
            tables.append(" (immediate)");
          } else {
            tables.append(" (buffered)");
          }
        }
      }
      error |= AddMemberToObject<Json_string>(obj, "tables", tables);
      description = string("Delete from ") + tables;
      children->push_back({path->delete_rows().child});
      break;
    }
    case AccessPath::UPDATE_ROWS: {
      string tables;
      for (Table_ref *t = join->query_block->leaf_tables; t != nullptr;
           t = t->next_leaf) {
        if (Overlaps(t->map(), path->update_rows().tables_to_update)) {
          if (!tables.empty()) {
            tables.append(", ");
          }
          tables.append(t->alias);
          if (Overlaps(t->map(), path->update_rows().immediate_tables)) {
            tables.append(" (immediate)");
          } else {
            tables.append(" (buffered)");
          }
        }
      }
      description = string("Update ") + tables;
      children->push_back({path->update_rows().child});
      break;
    }
  }

  // Append the various costs.
  error |= AddPathCosts(path, materialized_path, obj,
                        current_thd->lex->is_explain_analyze);

  // Empty description means the object already has the description set above.
  if (!description.empty()) {
    // Create JSON objects for description strings.
    error |= AddMemberToObject<Json_string>(obj, "operation", description);
  }

  return (error ? nullptr : std::move(ret_obj));
}