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 ¶m = 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 ¶m = 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));
}