用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2049 lines
70 KiB

/* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql/opt_explain_json.h"
#include "my_config.h"
#include <limits.h>
#include <stdio.h>
#include <sys/types.h>
#include "m_ctype.h"
#include "m_string.h"
#include "my_compiler.h"
#include "my_dbug.h"
#include "sql/current_thd.h" // current_thd
#include "sql/enum_query_type.h"
#include "sql/item.h"
#include "sql/item_sum.h"
#include "sql/key_spec.h"
#include "sql/mysqld.h"
#include "sql/opt_trace.h" // Opt_trace_object
#include "sql/opt_trace_context.h" // Opt_trace_context
#include "sql/protocol.h" // Protocol
#include "sql/query_result.h" // Query_result
#include "sql/sql_class.h" // THD
#include "sql/sql_list.h"
#include "sql/system_variables.h"
#include "sql/table.h"
#include "sql/temp_table_param.h"
#include "sql/window.h"
#include "sql_string.h"
class SELECT_LEX_UNIT;
/**
Property names, former parts of traditional "extra" column
This array must be in sync with Extra_tag enum.
*/
static const char *json_extra_tags[ET_total] = {
NULL, // ET_none
"using_temporary_table", // ET_USING_TEMPORARY
"using_filesort", // ET_USING_FILESORT
"index_condition", // ET_USING_INDEX_CONDITION
NULL, // ET_USING
"range_checked_for_each_record", // ET_RANGE_CHECKED_FOR_EACH_RECORD
"pushed_condition", // ET_USING_PUSHED_CONDITION
"using_where", // ET_USING_WHERE
"not_exists", // ET_NOT_EXISTS
"using_MRR", // ET_USING_MRR
"using_index", // ET_USING_INDEX
"full_scan_on_NULL_key", // ET_FULL_SCAN_ON_NULL_KEY
"using_index_for_group_by", // ET_USING_INDEX_FOR_GROUP_BY
"using_index_for_skip_scan", // ET_USING_INDEX_FOR_SKIP_SCAN
"distinct", // ET_DISTINCT
"loosescan", // ET_LOOSESCAN
NULL, // ET_START_TEMPORARY
NULL, // ET_END_TEMPORARY
"first_match", // ET_FIRST_MATCH
NULL, // ET_MATERIALIZE
NULL, // ET_START_MATERIALIZE
NULL, // ET_END_MATERIALIZE
NULL, // ET_SCAN
"using_join_buffer", // ET_USING_JOIN_BUFFER
"const_row_not_found", // ET_CONST_ROW_NOT_FOUND
"unique_row_not_found", // ET_UNIQUE_ROW_NOT_FOUND
"impossible_on_condition", // ET_IMPOSSIBLE_ON_CONDITION
"pushed_join", // ET_PUSHED_JOIN
"ft_hints", // ET_FT_HINTS
"backward_index_scan", // ET_BACKWARD_SCAN
"recursive", // ET_RECURSIVE
"table_function", // ET_TABLE_FUNCTION
"skip_records_in_range_due_to_force", // ET_SKIP_RECORDS_IN_RANGE
"using_secondary_engine", // ET_USING_SECONDARY_ENGINE
"rematerialize" // ET_REMATERIALIZE
};
// JSON key names
static const char K_ACCESS_TYPE[] = "access_type";
static const char K_ATTACHED_CONDITION[] = "attached_condition";
static const char K_ATTACHED_SUBQUERIES[] = "attached_subqueries";
static const char K_BUFFER_RESULT[] = "buffer_result";
static const char K_CACHEABLE[] = "cacheable";
static const char K_DEPENDENT[] = "dependent";
static const char K_DUPLICATES_REMOVAL[] = "duplicates_removal";
static const char K_FILTERED[] = "filtered";
static const char K_FRAME_BUFFER[] = "frame_buffer";
static const char K_FUNCTIONS[] = "functions";
static const char K_GROUPING_OPERATION[] = "grouping_operation";
static const char K_GROUP_BY_SUBQUERIES[] = "group_by_subqueries";
static const char K_HAVING_SUBQUERIES[] = "having_subqueries";
static const char K_INSERT_VALUES_SUBQUERIES[] = "insert_values_subqueries";
static const char K_INSERT_UPDATE_SUBQUERIES[] = "insert_update_subqueries";
static const char K_KEY[] = "key";
static const char K_KEY_LENGTH[] = "key_length";
static const char K_MATERIALIZED_FROM_SUBQUERY[] = "materialized_from_subquery";
static const char K_MESSAGE[] = "message";
static const char K_NAME[] = "name";
static const char K_NESTED_LOOP[] = "nested_loop";
static const char K_OPTIMIZED_AWAY_SUBQUERIES[] = "optimized_away_subqueries";
static const char K_OPTIMIZED_FRAME_EVALUATION[] = "optimized_frame_evaluation";
static const char K_ORDERING_OPERATION[] = "ordering_operation";
static const char K_ORDER_BY_SUBQUERIES[] = "order_by_subqueries";
static const char K_PARTITIONS[] = "partitions";
static const char K_POSSIBLE_KEYS[] = "possible_keys";
static const char K_QUERY_BLOCK[] = "query_block";
static const char K_QUERY_SPECIFICATIONS[] = "query_specifications";
static const char K_REF[] = "ref";
static const char K_SELECT_ID[] = "select_id";
static const char K_SELECT_LIST_SUBQUERIES[] = "select_list_subqueries";
static const char K_SHARING_TMP_TABLE[] = "sharing_temporary_table_with";
static const char K_TABLE[] = "table";
static const char K_TABLE_NAME[] = "table_name";
static const char K_UNION_RESULT[] = "union_result";
static const char K_UPDATE_VALUE_SUBQUERIES[] = "update_value_subqueries";
static const char K_USED_KEY_PARTS[] = "used_key_parts";
static const char K_USING_FILESORT[] = "using_filesort";
static const char K_FILESORT_KEY[] = "filesort_key";
static const char K_USING_TMP_TABLE[] = "using_temporary_table";
static const char K_WINDOW_DEF_POS[] = "definition_position";
static const char K_WINDOW_LAST_EXECUTED[] = "last_executed_window";
static const char K_WINDOWS[] = "windows";
static const char K_WINDOWING[] = "windowing";
static const char K_ROWS[] = "rows_examined_per_scan";
static const char K_PREFIX_ROWS[] = "rows_produced_per_join";
static const char K_COST_INFO[] = "cost_info";
static const char K_READ_TIME[] = "read_cost";
static const char K_PREFIX_COST[] = "prefix_cost";
static const char K_COND_COST[] = "eval_cost";
static const char K_SORT_COST[] = "sort_cost";
static const char K_QUERY_COST[] = "query_cost";
static const char K_DATA_SIZE_QUERY[] = "data_read_per_join";
static const char K_USED_COLUMNS[] = "used_columns";
static const char *mod_type_name[] = {"", "insert", "update", "delete",
"replace"};
/*
see commentary at the beginning of opt_trace.cc
*/
namespace opt_explain_json_namespace {
class joinable_ctx;
class sort_ctx;
class subquery_ctx;
class union_result_ctx;
class window_ctx;
/**
@note Keep in sync with the @c list_names array.
*/
enum subquery_list_enum {
SQ_SELECT_LIST, ///< SELECT list subqueries
SQ_UPDATE_VALUE, ///< UPDATE ... SET field=(subquery)
SQ_INSERT_VALUES, ///< subqueries in VALUES of INSERT ... VALUES
SQ_INSERT_UPDATE, ///< subqueries in UPDATE of
///< INSERT ... ON DUPLICATE KEY UPDATE
SQ_HAVING, ///< HAVING clause subqueries
SQ_OPTIMIZED_AWAY, ///< "optimized_away_subqueries"
//--------------
SQ_toplevel, ///< SQ array size for unit_ctx
//--------------
SQ_ORDER_BY, ///< ORDER BY clause subqueries
SQ_GROUP_BY, ///< GROUP BY clause subqueries
//--------------
SQ_total
};
/**
@note Keep in sync with @c subquery_list_enum.
*/
static const char *list_names[SQ_total] = {
K_SELECT_LIST_SUBQUERIES,
K_UPDATE_VALUE_SUBQUERIES,
K_INSERT_VALUES_SUBQUERIES,
K_INSERT_UPDATE_SUBQUERIES,
K_HAVING_SUBQUERIES,
K_OPTIMIZED_AWAY_SUBQUERIES,
"",
K_ORDER_BY_SUBQUERIES,
K_GROUP_BY_SUBQUERIES,
};
/**
Base class for all intermediate tree nodes
*/
class context : public Explain_context {
protected:
const char *name;
public:
context *parent; ///< link to parent node or NULL
context(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: Explain_context(type_arg), name(name_arg), parent(parent_arg) {}
virtual ~context() {}
/**
Pass the node with its child nodes to a JSON formatter
@param json Formatter
@retval false Ok
@retval true Error
@note The @c join_ctx class overloads this function.
*/
virtual bool format(Opt_trace_context *json) {
Opt_trace_object obj(json, name);
return format_body(json, &obj);
}
bool is_query_block() const { return name == K_QUERY_BLOCK; }
private:
/**
Format JSON object body
@param json Formatter
@param obj Object of this body
@retval false Ok
@retval true Error
*/
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) = 0;
public:
/**
Analogue of the "id" column in the traditional EXPLAIN output
@param hide if true, ban the output of K_SELECT_ID JSON property
in the underlying table_with_where_and_derived_ctx
objects
@returns "Select number" that is associated with this node
*/
virtual size_t id(bool hide = false) = 0;
virtual bool cacheable() {
DBUG_ASSERT(0);
return true;
}
virtual bool dependent() {
DBUG_ASSERT(0);
return false;
}
virtual class qep_row *entry() {
DBUG_ASSERT(0);
return NULL;
}
virtual enum_mod_type get_mod_type() { return MT_NONE; }
/**
Associate a child node with this node
This function is to be overloaded by subquery_ctx.
*/
virtual void set_child(context *) {}
/// associate CTX_UNION_RESULT node with CTX_UNION node
virtual void set_union_result(union_result_ctx *) { DBUG_ASSERT(0); }
/**
Append a subquery node to the specified list of the unit node
@param subquery_type Describes the Item tree where the subquery exists
@param ctx Subquery node
@retval false Ok
@retval true Error
*/
virtual bool add_subquery(subquery_list_enum subquery_type
MY_ATTRIBUTE((unused)),
subquery_ctx *ctx MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return true;
}
/**
Format nested loop join subtree (if any) to JSON formatter
@param json Formatter
@retval false Ok
@retval true Error
*/
virtual bool format_nested_loop(
Opt_trace_context *json MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return true;
}
/**
Add a CTX_QEP_TAB node to a CTX_JOIN node
@param ctx CTX_QEP_TAB node
@retval false Ok
@retval true Error
*/
virtual bool add_join_tab(joinable_ctx *ctx MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return true;
}
/**
Set nested ORDER BY/GROUP BY/DISTINCT node to @c ctx
@retval false Ok
@retval true Error
*/
virtual void set_sort(sort_ctx *ctx MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
}
/**
Set nested WINDOW node to @c ctx
@retval false Ok
@retval true Error
*/
virtual void set_window(window_ctx *ctx MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
}
/**
Add a query specification node to the CTX_UNION node
@param ctx query specification node
@retval false Ok
@retval true Error
*/
virtual bool add_query_spec(context *ctx MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return true;
}
/**
Try to associate a derived subquery node with this or underlying node
@param subquery Derived subquery node
@retval true Success
@retval false Can't associate: this node or its child nodes are not
derived from the subquery
*/
virtual bool find_and_set_derived(context *subquery MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return false;
}
/**
Associate WHERE subqueries of given context and unit with this object
@param ctx Context of WHERE subquery
@param subquery For CTX_QEP_TAB: match given unit with a previously
collected by the register_where_subquery function.
@returns
-1 subquery wasn't found
0 subqusery were added
1 error occurred
*/
virtual int add_where_subquery(subquery_ctx *ctx MY_ATTRIBUTE((unused)),
SELECT_LEX_UNIT *subquery
MY_ATTRIBUTE((unused))) {
DBUG_ASSERT(0);
return false;
}
/// Helper function to format output for derived subquery if any
virtual bool format_derived(Opt_trace_context *) { return false; }
/// Helper function to format output for associated WHERE subqueries if any
virtual bool format_where(Opt_trace_context *) { return false; }
/// Helper function to format output for HAVING, ORDER/GROUP BY subqueries
virtual bool format_unit(Opt_trace_context *) { return false; }
};
/**
Node class to wrap a subquery node tree
Implements CTX_WHERE, CTX_HAVING, CTX_ORDER_BY_SQ, CTX_GROUP_BY_SQ and
CTX_OPTIMIZED_AWAY_SUBQUERY context nodes.
This class hosts underlying join_ctx or uion_ctx.
Is used for a subquery, a derived table.
*/
class subquery_ctx : virtual public context, public qep_row {
/*
TODO: After the conversion from multiple inheritace to templates
convert "context" to "unit_ctx" (common base of uion_ctx & join_ctx).
*/
context *subquery; ///< hosted subquery tree: CTX_JOIN or CTX_UNION
public:
subquery_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: context(type_arg, name_arg, parent_arg), subquery(NULL) {}
virtual qep_row *entry() { return this; }
/*
Materialized subquery statuses of dependency on the outer query and
cacheability may differ from the source subquery, for example, if
we "push down" the outer look up value for SJ.
Thus, for materialized subqueries return direct is_cacheable and
is_dependent values instead of source subquery statuses:
*/
virtual bool cacheable() {
return is_materialized_from_subquery ? is_cacheable : subquery->cacheable();
}
virtual bool dependent() {
return is_materialized_from_subquery ? is_dependent : subquery->dependent();
}
virtual bool format(Opt_trace_context *json) {
if (name)
return context::format(json);
else {
/*
Subquery is always a homogeneous array element,
create anonymous wrapper object:
*/
Opt_trace_object anonymous_wrapper(json);
return format_body(json, &anonymous_wrapper);
}
}
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
if (type == CTX_DERIVED) {
if (derived_clone_id) {
Opt_trace_object(json, K_SHARING_TMP_TABLE)
.add(K_SELECT_ID, derived_clone_id);
// Don't show underlying tables of derived table clone
return false;
}
obj->add(K_USING_TMP_TABLE, true);
obj->add(K_DEPENDENT, dependent());
obj->add(K_CACHEABLE, cacheable());
return subquery->format(json);
} else if (using_temporary) {
if (!is_materialized_from_subquery) {
obj->add(K_USING_TMP_TABLE, true);
obj->add(K_DEPENDENT, dependent());
obj->add(K_CACHEABLE, cacheable());
}
{
Opt_trace_object tmp_table(json, K_TABLE);
if (!col_table_name.is_empty())
tmp_table.add_utf8(K_TABLE_NAME, col_table_name.str);
if (!col_join_type.is_empty())
tmp_table.add_alnum(K_ACCESS_TYPE, col_join_type.str);
if (!col_key.is_empty()) tmp_table.add_utf8(K_KEY, col_key.str);
if (!col_key_len.is_empty())
tmp_table.add_alnum(K_KEY_LENGTH, col_key_len.str);
if (!col_rows.is_empty()) tmp_table.add(K_ROWS, col_rows.value);
if (is_materialized_from_subquery) {
Opt_trace_object materialized(json, K_MATERIALIZED_FROM_SUBQUERY);
materialized.add(K_USING_TMP_TABLE, true);
materialized.add(K_DEPENDENT, dependent());
materialized.add(K_CACHEABLE, cacheable());
return format_query_block(json);
}
}
return format_query_block(json);
} else {
obj->add(K_DEPENDENT, dependent());
obj->add(K_CACHEABLE, cacheable());
return subquery->format(json);
}
}
bool format_query_block(Opt_trace_context *json) {
if (subquery->is_query_block()) return subquery->format(json);
Opt_trace_object query_block(json, K_QUERY_BLOCK);
return subquery->format(json);
}
public:
virtual void set_child(context *child) {
DBUG_ASSERT(subquery == NULL);
DBUG_ASSERT(child->type == CTX_JOIN || child->type == CTX_UNION);
subquery = child;
}
virtual size_t id(bool hide) { return subquery->id(hide); }
};
/**
Helper function to pass a subquery list to a JSON formatter
@param json output formatter
@param subqueries subquery list to output
@param name name for the output section
@retval false Ok
@retval true Error
*/
static bool format_list(Opt_trace_context *json, List<subquery_ctx> &subqueries,
const char *name) {
if (!subqueries.is_empty()) {
Opt_trace_array subs(json, name);
List_iterator<subquery_ctx> it(subqueries);
subquery_ctx *t;
while ((t = it++)) {
// Homogeneous array: additional anonymous wrapper object is not needed
if (t->format(json)) return true;
}
}
return false;
}
/**
Helper base class to host HAVING, ORDER BY and GROUP BY subquery nodes
*/
class unit_ctx : virtual public context {
List<subquery_ctx> subquery_lists[SQ_total];
public:
unit_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: context(type_arg, name_arg, parent_arg) {}
/**
Helper function to distinguish subquery-less nodes
@retval true Node hosts no subqueries
@retval false Node hosts some subqueries
*/
bool has_no_subqueries() const {
for (size_t i = 0; i < SQ_total; i++) {
if (!subquery_lists[i].is_empty()) return false;
}
return true;
}
virtual bool format_unit(Opt_trace_context *json) {
for (size_t i = 0; i < SQ_total; i++) {
if (format_list(json, subquery_lists[i], list_names[i])) return true;
}
return false;
}
virtual bool add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx) {
return subquery_lists[subquery_type].push_back(ctx);
}
};
class table_base_ctx : virtual public context, virtual public qep_row {
protected:
bool is_hidden_id; ///< if true, don't output K_SELECT_ID property
public:
table_base_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: context(type_arg, name_arg, parent_arg), is_hidden_id(false) {}
virtual qep_row *entry() { return this; }
protected:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj);
public:
virtual size_t id(bool) { return col_id.is_empty() ? 0 : col_id.value; }
virtual bool cacheable() { return is_cacheable; }
virtual bool dependent() { return is_dependent; }
};
static void add_string_array(Opt_trace_context *json, const char *list_name,
List<const char> &strings) {
if (!strings.is_empty()) {
Opt_trace_array extra(json, list_name);
List_iterator<const char> it(strings);
const char *s;
while ((s = it++)) extra.add_utf8(s);
}
}
static void print_cost(char *buf, uint buf_len, double cost) {
if (cost < 100000000000000.0)
snprintf(buf, buf_len, "%.2f", cost);
else
snprintf(buf, buf_len, "%.14g", cost);
}
static void print_filtered(char *buf, uint buf_len, double filtered) {
snprintf(buf, buf_len, "%.2f", filtered);
}
bool table_base_ctx::format_body(Opt_trace_context *json,
Opt_trace_object *obj) {
StringBuffer<64> buff;
if (mod_type != MT_NONE) obj->add(mod_type_name[mod_type], true);
if (!col_id.is_empty() && !is_hidden_id) obj->add(K_SELECT_ID, col_id.value);
if (!col_table_name.is_empty())
obj->add_utf8(K_TABLE_NAME, col_table_name.str);
add_string_array(json, K_PARTITIONS, col_partitions);
if (!col_join_type.is_empty())
obj->add_alnum(K_ACCESS_TYPE, col_join_type.str);
add_string_array(json, K_POSSIBLE_KEYS, col_possible_keys);
if (!col_key.is_empty()) obj->add_utf8(K_KEY, col_key.str);
if (!col_key_parts.is_empty())
add_string_array(json, K_USED_KEY_PARTS, col_key_parts);
if (!col_key_len.is_empty()) obj->add_alnum(K_KEY_LENGTH, col_key_len.str);
add_string_array(json, K_REF, col_ref);
if (!col_rows.is_empty()) obj->add(K_ROWS, col_rows.value);
if (!col_prefix_rows.is_empty())
obj->add(K_PREFIX_ROWS, col_prefix_rows.value);
if (!col_filtered.is_empty()) {
char buf[32]; // 32 is enough for digits of a double
print_filtered(buf, sizeof(buf), col_filtered.value);
obj->add_utf8(K_FILTERED, buf);
}
format_extra(obj);
if (!col_read_cost.is_empty()) {
Opt_trace_object cost_info(json, K_COST_INFO);
char buf[32]; // 32 is enough for digits of a double
print_cost(buf, sizeof(buf), col_read_cost.value);
cost_info.add_utf8(K_READ_TIME, buf);
if (!col_cond_cost.is_empty()) {
print_cost(buf, sizeof(buf), col_cond_cost.value);
cost_info.add_utf8(K_COND_COST, buf);
}
if (!col_prefix_cost.is_empty()) {
print_cost(buf, sizeof(buf), col_prefix_cost.value);
cost_info.add_utf8(K_PREFIX_COST, buf);
}
if (!col_data_size_query.is_empty())
cost_info.add_utf8(K_DATA_SIZE_QUERY, col_data_size_query.str);
}
if (!col_used_columns.is_empty())
add_string_array(json, K_USED_COLUMNS, col_used_columns);
if (!col_partial_update_columns.is_empty())
add_string_array(json, "partial_update_columns",
col_partial_update_columns);
if (!col_message.is_empty() && type != CTX_MESSAGE) {
DBUG_ASSERT(col_extra.is_empty());
obj->add_alnum(K_MESSAGE, col_message.str);
}
{ // Keep together for better output readability
if (!col_attached_condition.is_empty())
obj->add_utf8(K_ATTACHED_CONDITION, col_attached_condition.str);
if (format_where(json)) return true;
}
return format_derived(json) || format_unit(json);
}
/**
Node class for the CTX_UNION_RESULT
*/
class union_result_ctx : public table_base_ctx, public unit_ctx {
List<context> *query_specs; ///< query specification nodes (inner selects)
List<subquery_ctx> order_by_subqueries;
List<subquery_ctx> optimized_away_subqueries;
joinable_ctx *message;
public:
explicit union_result_ctx(context *parent_arg)
: context(CTX_UNION_RESULT, K_UNION_RESULT, parent_arg),
table_base_ctx(CTX_UNION_RESULT, K_UNION_RESULT, parent_arg),
unit_ctx(CTX_UNION_RESULT, K_UNION_RESULT, parent_arg),
message(NULL) {}
// Remove warnings: 'inherits ... from ... via dominance'
virtual size_t id(bool hide) { return table_base_ctx::id(hide); }
virtual bool cacheable() { return table_base_ctx::cacheable(); }
virtual bool dependent() { return table_base_ctx::dependent(); }
virtual qep_row *entry() { return table_base_ctx::entry(); }
virtual bool format_unit(Opt_trace_context *json) {
return table_base_ctx::format_unit(json);
}
void push_down_query_specs(List<context> *specs) { query_specs = specs; }
virtual bool add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx) {
switch (subquery_type) {
case SQ_ORDER_BY:
return order_by_subqueries.push_back(ctx);
case SQ_OPTIMIZED_AWAY:
return optimized_away_subqueries.push_back(ctx);
default:
DBUG_ASSERT(!"Unknown query type!");
return false; // ignore in production
}
}
virtual bool add_join_tab(joinable_ctx *ctx) {
DBUG_ASSERT(!message);
message = ctx;
return false;
}
virtual bool format(Opt_trace_context *json);
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj);
};
/**
Common part of CTX_QEP_TAB and CTX_MESSAGE nodes
This class implements functionality for WHERE and derived subqueries that
are associated with the table node.
*/
class table_with_where_and_derived : public table_base_ctx {
public:
List<subquery_ctx> where_subqueries; ///< associated WHERE clause subqueries
table_with_where_and_derived(enum_parsing_context type_arg,
const char *name_arg, context *parent_arg)
: context(type_arg, name_arg, parent_arg),
table_base_ctx(type_arg, name_arg, parent_arg) {}
virtual size_t id(bool hide) {
if (hide) is_hidden_id = true;
return table_base_ctx::id(hide);
}
virtual bool format_where(Opt_trace_context *json) {
return format_list(json, where_subqueries, K_ATTACHED_SUBQUERIES);
}
virtual bool format_derived(Opt_trace_context *json) {
if (derived_from.elements == 0)
return false;
else if (derived_from.elements == 1)
return derived_from.head()->format(json);
else {
Opt_trace_array loops(json, K_NESTED_LOOP);
List_iterator<context> it(derived_from);
context *c;
while ((c = it++)) {
Opt_trace_object anonymous_wrapper(json);
if (c->format(json)) return true;
}
}
return false;
}
};
/**
Base for CTX_QEP_TAB, CTX_DUPLICATES_WEEDOUT and CTX_MATERIALIZATION nodes
This class implements a base to explain individual JOIN_TABs as well
as JOIN_TAB groups like in semi-join materialization.
*/
class joinable_ctx : virtual public context {
public:
joinable_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: context(type_arg, name_arg, parent_arg) {}
};
/**
Node class for CTX_MESSAGE
This class is designed to represent fake tables with some messages in the
"extra" column ("Impossible where" etc).
We do EXPLAIN of these fake tables to replace explanation of:
1) usual actual JOIN_TABs of the whole JOIN or
2) a modifying TABLE of single-table UPDATE/DELETE/etc.
So, message_ctx always represent a single half-empty fake table in a
"query_block" node with optional subqueries.
*/
class message_ctx : public joinable_ctx, public table_with_where_and_derived {
public:
explicit message_ctx(context *parent_arg)
: context(CTX_MESSAGE, K_TABLE, parent_arg),
joinable_ctx(CTX_MESSAGE, K_TABLE, parent_arg),
table_with_where_and_derived(CTX_MESSAGE, K_TABLE, parent_arg) {}
// Remove warnings: 'inherits ... from ... via dominance'
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
return table_base_ctx::format_body(json, obj);
}
virtual size_t id(bool hide) {
return table_with_where_and_derived::id(hide);
}
virtual bool cacheable() { return table_base_ctx::cacheable(); }
virtual bool dependent() { return table_base_ctx::dependent(); }
virtual qep_row *entry() { return table_base_ctx::entry(); }
virtual bool format_derived(Opt_trace_context *json) {
return table_with_where_and_derived::format_derived(json);
}
virtual bool format_where(Opt_trace_context *json) {
return table_with_where_and_derived::format_where(json);
}
virtual bool find_and_set_derived(context *subquery) {
/*
message_ctx is designed to represent a single fake JOIN_TAB in the JOIN,
so if the JOIN have a derived table, then this message_ctx represent this
derived table.
Unconditionally add subquery:
*/
derived_from.push_back(subquery);
return true;
}
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *) {
return where_subqueries.push_back(ctx);
}
};
/**
Node class for the CTX_QEP_TAB context
*/
class join_tab_ctx : public joinable_ctx, public table_with_where_and_derived {
/**
Subquery units that are associated with this JOIN_TAB's condition
This list is used to match with the @c subquery parameter of
the @c add_where_subquery function.
*/
List<SELECT_LEX_UNIT> where_subquery_units;
public:
join_tab_ctx(enum_parsing_context type_arg, context *parent_arg)
: context(type_arg, K_TABLE, parent_arg),
joinable_ctx(type_arg, K_TABLE, parent_arg),
table_with_where_and_derived(type_arg, K_TABLE, parent_arg) {}
// Remove warnings: 'inherits ... from ... via dominance'
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
return table_base_ctx::format_body(json, obj);
}
virtual size_t id(bool hide) {
return table_with_where_and_derived::id(hide);
}
virtual bool cacheable() { return table_base_ctx::cacheable(); }
virtual bool dependent() { return table_base_ctx::dependent(); }
virtual qep_row *entry() { return table_base_ctx::entry(); }
virtual bool format_derived(Opt_trace_context *json) {
return table_with_where_and_derived::format_derived(json);
}
virtual bool format_where(Opt_trace_context *json) {
return table_with_where_and_derived::format_where(json);
}
virtual void register_where_subquery(SELECT_LEX_UNIT *subquery) {
List_iterator<SELECT_LEX_UNIT> it(where_subquery_units);
SELECT_LEX_UNIT *u;
while ((u = it++)) {
/*
The server may transform (x = (SELECT FROM DUAL)) to
(x <=> (SELECT FROM DUAL) AND x = (SELECT FROM DUAL)),
so ignore duplicates:
*/
if (u == subquery) return;
}
where_subquery_units.push_back(subquery);
}
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery) {
List_iterator<SELECT_LEX_UNIT> it(where_subquery_units);
SELECT_LEX_UNIT *u;
while ((u = it++)) {
if (u == subquery) return where_subqueries.push_back(ctx);
}
return -1;
}
virtual bool find_and_set_derived(context *subquery) {
if (query_block_id == subquery->id()) {
derived_from.push_back(subquery);
return true;
}
return false;
}
virtual enum_mod_type get_mod_type() { return entry()->mod_type; }
};
/**
Base class for CTX_ORDER_BY, CTX_GROUP_BY and node class for CTX_DISTINCT
This class represents context for simple ORDER BY/GROUP BY/DISTINCT clauses
(the clause is effective for the single JOIN_TAB).
*/
class simple_sort_ctx : public joinable_ctx {
protected:
/** Single JOIN_TAB that we sort. */
joinable_ctx *join_tab;
private:
/** True if the clause creates intermediate table. */
const bool using_tmptable;
/** True if the clause uses filesort. */
const bool using_filesort;
public:
simple_sort_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg, const Explain_format_flags *flags,
Explain_sort_clause clause)
: context(type_arg, name_arg, parent_arg),
joinable_ctx(type_arg, name_arg, parent_arg),
join_tab(NULL),
using_tmptable(flags->get(clause, ESP_USING_TMPTABLE)),
using_filesort(flags->get(clause, ESP_USING_FILESORT)) {}
virtual bool add_join_tab(joinable_ctx *ctx) {
join_tab = ctx;
return false;
}
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery) {
return join_tab->add_where_subquery(ctx, subquery);
}
virtual bool find_and_set_derived(context *subquery) {
return join_tab->find_and_set_derived(subquery);
}
virtual size_t id(bool hide) { return join_tab->id(hide); }
virtual bool cacheable() { return join_tab->cacheable(); }
virtual bool dependent() { return join_tab->dependent(); }
protected:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
if (using_tmptable) obj->add(K_USING_TMP_TABLE, true);
obj->add(K_USING_FILESORT, using_filesort);
return join_tab->format(json);
}
};
/**
Node class for "simple" CTX_ORDER_BY and CTX_GROUP_BY
This class represents context for simple ORDER BY or GROUP BY clauses
(the clause is effective for the single JOIN_TAB).
*/
class simple_sort_with_subqueries_ctx : public simple_sort_ctx {
/** Type of this clause subqueries. */
const subquery_list_enum subquery_type;
List<subquery_ctx> subqueries;
public:
simple_sort_with_subqueries_ctx(enum_parsing_context type_arg,
const char *name_arg, context *parent_arg,
subquery_list_enum subquery_type_arg,
const Explain_format_flags *flags,
Explain_sort_clause clause)
: context(type_arg, name_arg, parent_arg),
simple_sort_ctx(type_arg, name_arg, parent_arg, flags, clause),
subquery_type(subquery_type_arg) {}
virtual bool add_subquery(subquery_list_enum subquery_type_arg,
subquery_ctx *ctx) {
if (subquery_type != subquery_type_arg)
return simple_sort_ctx::add_subquery(subquery_type_arg, ctx);
else
return subqueries.push_back(ctx);
}
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
return (simple_sort_ctx::format_body(json, obj) ||
(format_list(json, subqueries, list_names[subquery_type])));
}
};
/**
Node class for the CTX_JOIN context
*/
class join_ctx : public unit_ctx, virtual public qep_row {
protected:
List<joinable_ctx> join_tabs; ///< hosted JOIN_TAB nodes
sort_ctx *sort;
window_ctx *window;
public:
join_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg)
: context(type_arg, name_arg, parent_arg),
unit_ctx(type_arg, name_arg, parent_arg),
sort(nullptr),
window(nullptr) {}
virtual bool add_join_tab(joinable_ctx *ctx) {
return join_tabs.push_back(ctx);
}
virtual void set_sort(sort_ctx *ctx) {
DBUG_ASSERT(!sort);
sort = ctx;
}
virtual void set_window(window_ctx *ctx) {
DBUG_ASSERT(!sort);
window = ctx;
}
virtual qep_row *entry() { return this; }
/**
Associate a CTX_DERIVED node with its CTX_QEP_TAB node
@param subquery derived subquery tree
*/
virtual bool find_and_set_derived(context *subquery);
virtual bool add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx);
protected:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj);
bool format_body_inner(Opt_trace_context *json, Opt_trace_object *obj);
virtual const char *get_cost_tag() { return K_QUERY_COST; }
public:
virtual bool format_nested_loop(Opt_trace_context *json);
virtual size_t id(bool hide);
virtual bool cacheable();
virtual bool dependent();
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery);
};
/**
Node class for CTX_SIMPLE_ORDER_BY, CTX_SIMPLE_GROUP_BY and
CTX_SIMPLE_DISTINCT
CTX_JOIN context (see join_ctx class) may contain nested loop join node *or*
ORDER BY/GROUP BY/DISTINCT node that is represented by this class:
join: { nested_loop: [ ... ] }
or
join: { order_by|group_by|distinct : { ... } }
CTX_ORDER_BY may contain nested loop join tree *or* GROUP BY/DISTINCT node:
order_by: { nested_loop|group_by|distinct: ... }
CTX_DISTINCT context structure:
distinct: { nested_loop|group_by: ... }
CTX_GROUP_BY:
group_by: { nested_loop: [ ... ] }
I.e. the most complex CTX_JOIN may have such a structure of JSON output as:
join: {
order_by: {
distinct: {
group_by: {
nested_loop: [ ... ]
}
}
}
}
TODO
*/
class sort_ctx : public join_ctx {
/** The clause creates temporary table. */
const bool using_tmptable;
/** The clause uses filesort. */
const bool using_filesort;
public:
sort_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg, const Explain_format_flags *flags,
Explain_sort_clause clause)
: context(type_arg, name_arg, parent_arg),
join_ctx(type_arg, name_arg, parent_arg),
using_tmptable(flags->get(clause, ESP_USING_TMPTABLE)),
using_filesort(flags->get(clause, ESP_USING_FILESORT)) {}
protected:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
DBUG_ASSERT(!sort || join_tabs.is_empty());
if (using_tmptable) obj->add(K_USING_TMP_TABLE, true);
if (type != CTX_BUFFER_RESULT) obj->add(K_USING_FILESORT, using_filesort);
return join_ctx::format_body(json, obj);
}
const char *get_cost_tag() { return K_SORT_COST; }
};
class sort_with_subqueries_ctx : public sort_ctx {
/** Subquery type for this clause. */
const subquery_list_enum subquery_type;
List<subquery_ctx> subqueries;
public:
sort_with_subqueries_ctx(enum_parsing_context type_arg, const char *name_arg,
context *parent_arg,
subquery_list_enum subquery_type_arg,
const Explain_format_flags *flags,
Explain_sort_clause clause)
: context(type_arg, name_arg, parent_arg),
sort_ctx(type_arg, name_arg, parent_arg, flags, clause),
subquery_type(subquery_type_arg) {}
virtual bool add_subquery(subquery_list_enum subquery_type_arg,
subquery_ctx *ctx) {
if (subquery_type_arg != subquery_type)
return sort_ctx::add_subquery(subquery_type_arg, ctx);
else
return subqueries.push_back(ctx);
return false;
}
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
return (sort_ctx::format_body(json, obj) ||
format_list(json, subqueries, list_names[subquery_type]));
}
};
/**
When the query has window functions, an outer node named "windowing" is
added:
query_block: {
select_id: 1,
windowing: {
windows: [ ... list of all windows' details ...
],
nested_loop: [ ... ]
Due to this "outer-node" layout, the implementation of window_ctx is similar
to that of sort_ctx. Except that while it makes sense to describe to the
user the "subqueries included in ORDER BY", it doesn't for "subqueries
included in the window function's arguments" (the window function belongs to
the SELECT list or ORDER BY: so does the subquery, simply), so there is no
window_with_subqueries_ctx.
*/
class window_ctx : public join_ctx {
public:
window_ctx(context *parent_arg)
: context(CTX_WINDOW, K_WINDOWING, parent_arg),
join_ctx(CTX_WINDOW, K_WINDOWING, parent_arg) {}
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
Opt_trace_array windows(json, K_WINDOWS);
List_iterator<Window> li(*m_windows);
Window *w;
while ((w = li++)) {
Opt_trace_object to(json);
to.add_utf8(K_NAME, w->printable_name());
if (m_windows->elements > 1) {
// Help the user relate this to a window in his query:
if (w->def_pos())
to.add(K_WINDOW_DEF_POS, w->def_pos());
else
to.add_alnum(K_WINDOW_DEF_POS, "artificial_window"); // dummy
// Make him notice the top-to-bottom order of execution of windows:
if (w->is_last()) to.add(K_WINDOW_LAST_EXECUTED, true);
}
if (!w->outtable_param()->m_window_short_circuit)
to.add(K_USING_TMP_TABLE, true);
if (w->needs_sorting()) {
obj->add(K_USING_FILESORT, true);
Opt_trace_array sort_order(json, K_FILESORT_KEY);
ORDER *ord = w->sorting_order();
for (; ord != NULL; ord = ord->next) {
String str;
(*ord->item)
->print_for_order(current_thd, &str,
(enum_query_type)(QT_NO_DB | QT_NO_TABLE),
ord->used_alias);
if (ord->direction == ORDER_DESC)
str.append(STRING_WITH_LEN(" desc"));
sort_order.add_utf8(str.ptr(), str.length());
}
}
if (w->needs_buffering()) {
Opt_trace_object to(json, K_FRAME_BUFFER);
to.add(K_USING_TMP_TABLE, true);
if (w->optimizable_range_aggregates() ||
w->optimizable_row_aggregates() || w->static_aggregates())
to.add(K_OPTIMIZED_FRAME_EVALUATION, true);
}
Opt_trace_array wfs(json, K_FUNCTIONS);
List_iterator<Item_sum> wfs_it(w->functions());
Item_sum *wf;
while ((wf = wfs_it++)) wfs.add_utf8(wf->func_name());
}
windows.end();
return join_ctx::format_body(json, obj);
}
protected:
const char *get_cost_tag() { return K_SORT_COST; }
};
bool join_ctx::find_and_set_derived(context *subquery) {
DBUG_ASSERT(subquery->id() != 0);
if (sort)
return sort->find_and_set_derived(subquery);
else if (window)
return window->find_and_set_derived(subquery);
List_iterator<joinable_ctx> it(join_tabs);
joinable_ctx *t;
while ((t = it++)) {
if (t->find_and_set_derived(subquery)) return true;
}
return false;
}
bool join_ctx::add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx) {
if (sort)
return sort->add_subquery(subquery_type, ctx);
else if (window)
return window->add_subquery(subquery_type, ctx);
if (subquery_type > SQ_toplevel) {
List_iterator<joinable_ctx> it(join_tabs);
joinable_ctx *j;
while ((j = it++)) {
switch (j->type) {
case CTX_ORDER_BY:
case CTX_DISTINCT:
case CTX_GROUP_BY:
case CTX_SIMPLE_ORDER_BY:
case CTX_SIMPLE_DISTINCT:
case CTX_SIMPLE_GROUP_BY:
return j->add_subquery(subquery_type, ctx);
case CTX_MESSAGE: // The 'no plan' case
DBUG_ASSERT(subquery_type == SQ_ORDER_BY ||
subquery_type == SQ_GROUP_BY);
return unit_ctx::add_subquery(subquery_type, ctx);
default:
DBUG_ASSERT(0); /* purecov: inspected */
}
}
} else
return unit_ctx::add_subquery(subquery_type, ctx);
return true; /* purecov: inspected */
}
bool join_ctx::format_body(Opt_trace_context *json, Opt_trace_object *obj) {
if (type == CTX_JOIN) obj->add(K_SELECT_ID, id(true));
format_extra(obj);
if (!col_read_cost.is_empty()) {
char buf[32]; // 32 is enough for digits of a double
Opt_trace_object cost_info(json, K_COST_INFO);
print_cost(buf, sizeof(buf), col_read_cost.value);
cost_info.add_utf8(get_cost_tag(), buf);
}
// Print target table for INSERT/REPLACE SELECT outside of nested loop
if (join_tabs.elements && (join_tabs.head()->get_mod_type() == MT_INSERT ||
join_tabs.head()->get_mod_type() == MT_REPLACE)) {
join_tabs.head()->format(json);
if (sort || join_tabs.elements > 1) {
Opt_trace_object insert_from(json, "insert_from");
if (format_body_inner(json, obj)) return true; /* purecov: inspected */
}
} else if (format_body_inner(json, obj))
return true; /* purecov: inspected */
return format_unit(json);
}
bool join_ctx::format_body_inner(Opt_trace_context *json,
Opt_trace_object *obj) {
if (sort) {
if (sort->format(json)) return true; /* purecov: inspected */
} else if (window) {
if (window->format(json)) return true; /* purecov: inspected */
} else if (join_tabs.elements && join_tabs.head()->type == CTX_MESSAGE) {
// Could be only 1 message per join
DBUG_ASSERT(join_tabs.elements == 1);
message_ctx *msg = (message_ctx *)join_tabs.head();
obj->add_alnum(K_MESSAGE, msg->entry()->col_message.str);
if (msg->derived_from.elements)
msg->format(json);
else if (msg->where_subqueries.elements)
msg->format_where(json);
} else if (format_nested_loop(json))
return true;
return false;
}
bool join_ctx::format_nested_loop(Opt_trace_context *json) {
List_iterator<joinable_ctx> it(join_tabs);
uint join_tab_num = join_tabs.elements;
DBUG_ASSERT(join_tabs.elements > 0);
if (join_tabs.head()->get_mod_type() == MT_INSERT ||
join_tabs.head()->get_mod_type() == MT_REPLACE) {
it++;
join_tab_num--;
}
/*
For single table skip "nested_loop" object creation and
format its contents only (the 1st join_tab).
*/
if (join_tab_num == 1) return (it++)->format(json);
Opt_trace_array loops(json, K_NESTED_LOOP);
joinable_ctx *t;
while ((t = it++)) {
Opt_trace_object anonymous_wrapper(json);
if (t->format(json)) return true;
}
return false;
}
bool union_result_ctx::format_body(Opt_trace_context *json,
Opt_trace_object *obj) {
obj->add(K_USING_TMP_TABLE, true);
if (table_base_ctx::format_body(json, obj))
return true; /* purecov: inspected */
if (message) {
message_ctx *msg = (message_ctx *)message;
obj->add_alnum(K_MESSAGE, msg->entry()->col_message.str);
}
Opt_trace_array specs(json, K_QUERY_SPECIFICATIONS);
List_iterator<context> it(*query_specs);
context *ctx;
while ((ctx = it++)) {
if (ctx->format(json)) return true; /* purecov: inspected */
}
return false;
}
/**
Auxiliary function to walk through the list and propagate "hide" value
@param list list of context (*_ctx) objects
@param hide if true, ban the output of K_SELECT_ID JSON property
in the underlying table_with_where_and_derived_ctx
and materialize_ctx objects
@return id of underlying objects
*/
template <typename T>
static size_t get_id(List<T> &list, bool hide) {
if (!hide) return list.head()->id();
List_iterator<T> it(list);
T *j;
size_t ret = 0;
while ((j = it++)) ret = j->id(hide);
return ret;
}
size_t join_ctx::id(bool hide) {
return (sort ? sort->id(hide)
: (window ? window->id(hide) : get_id(join_tabs, hide)));
}
bool join_ctx::cacheable() {
return (sort
? sort->cacheable()
: (window ? window->cacheable() : join_tabs.head()->cacheable()));
}
bool join_ctx::dependent() {
return (sort
? sort->dependent()
: (window ? window->dependent() : join_tabs.head()->dependent()));
}
int join_ctx::add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery) {
if (sort)
return sort->join_ctx::add_where_subquery(ctx, subquery);
else if (window)
return window->join_ctx::add_where_subquery(ctx, subquery);
List_iterator<joinable_ctx> it(join_tabs);
joinable_ctx *j;
bool found = false;
while ((j = it++)) {
int ret = j->add_where_subquery(ctx, subquery);
if (ret > 0) return true;
found |= (ret == 0);
}
if (!found) return add_subquery(SQ_OPTIMIZED_AWAY, ctx);
return false;
}
/**
Context class to group materialized JOIN_TABs to "materialized" array.
Is used for semijoin materialization.
*/
class materialize_ctx : public joinable_ctx,
public join_ctx,
public table_base_ctx {
public:
explicit materialize_ctx(context *parent_arg)
: context(CTX_MATERIALIZATION, K_TABLE, parent_arg),
joinable_ctx(CTX_MATERIALIZATION, K_TABLE, parent_arg),
join_ctx(CTX_MATERIALIZATION, K_TABLE, parent_arg),
table_base_ctx(CTX_MATERIALIZATION, K_TABLE, parent_arg) {}
virtual size_t id(bool hide) {
if (hide) {
is_hidden_id = true;
/* Set the materizlize table's id to hide */
join_ctx::id(hide);
}
return table_base_ctx::id(hide);
}
virtual bool cacheable() { return join_ctx::cacheable(); }
virtual bool dependent() { return join_ctx::dependent(); }
// Remove warnings: 'inherits ... from ... via dominance'
virtual qep_row *entry() { return table_base_ctx::entry(); }
virtual bool add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx) {
return join_ctx::add_subquery(subquery_type, ctx);
}
virtual bool add_join_tab(joinable_ctx *ctx) {
return join_ctx::add_join_tab(ctx);
}
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery) {
return join_ctx::add_where_subquery(ctx, subquery);
}
virtual bool find_and_set_derived(context *subquery) {
return join_ctx::find_and_set_derived(subquery);
}
virtual bool format_unit(Opt_trace_context *json) {
return unit_ctx::format_unit(json);
}
virtual bool format_nested_loop(Opt_trace_context *json) {
return join_ctx::format_nested_loop(json);
}
virtual void set_sort(sort_ctx *ctx) { return join_ctx::set_sort(ctx); }
virtual void set_window(window_ctx *ctx) { return join_ctx::set_window(ctx); }
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
DBUG_ASSERT(!col_join_type.is_empty());
if (!col_table_name.is_empty())
obj->add_utf8(K_TABLE_NAME, col_table_name.str);
obj->add_alnum(K_ACCESS_TYPE, col_join_type.str);
if (!col_key.is_empty()) obj->add_utf8(K_KEY, col_key.str);
if (!col_key_len.is_empty()) obj->add_alnum(K_KEY_LENGTH, col_key_len.str);
add_string_array(json, K_REF, col_ref);
if (!col_rows.is_empty()) obj->add(K_ROWS, col_rows.value);
format_extra(obj);
/*
Currently K-REF/col_ref is not shown; it would always be "func", since
{subquery,semijoin} materialization use store_key_item; using
get_store_key() instead would allow "const" and outer column's name,
if applicable.
The looked up expression can anyway be inferred from the condition:
*/
if (!col_attached_condition.is_empty())
obj->add_utf8(K_ATTACHED_CONDITION, col_attached_condition.str);
if (format_where(json)) return true;
Opt_trace_object m(json, K_MATERIALIZED_FROM_SUBQUERY);
obj->add(K_USING_TMP_TABLE, true);
Opt_trace_object q(json, K_QUERY_BLOCK);
return format_nested_loop(json);
}
};
/**
Context class to represent JOIN_TABs in duplication weedout sequence
*/
class duplication_weedout_ctx : public joinable_ctx, public join_ctx {
public:
explicit duplication_weedout_ctx(context *parent_arg)
: context(CTX_DUPLICATES_WEEDOUT, K_DUPLICATES_REMOVAL, parent_arg),
joinable_ctx(CTX_DUPLICATES_WEEDOUT, K_DUPLICATES_REMOVAL, parent_arg),
join_ctx(CTX_DUPLICATES_WEEDOUT, K_DUPLICATES_REMOVAL, parent_arg) {}
virtual size_t id(bool hide) { return join_ctx::id(hide); }
virtual bool cacheable() { return join_ctx::cacheable(); }
virtual bool dependent() { return join_ctx::dependent(); }
// Remove warnings: 'inherits ... from ... via dominance'
virtual bool add_join_tab(joinable_ctx *ctx) {
return join_ctx::add_join_tab(ctx);
}
virtual bool add_subquery(subquery_list_enum subquery_type,
subquery_ctx *ctx) {
return join_ctx::add_subquery(subquery_type, ctx);
}
virtual int add_where_subquery(subquery_ctx *ctx, SELECT_LEX_UNIT *subquery) {
return join_ctx::add_where_subquery(ctx, subquery);
}
virtual bool find_and_set_derived(context *subquery) {
return join_ctx::find_and_set_derived(subquery);
}
virtual bool format_nested_loop(Opt_trace_context *json) {
return join_ctx::format_nested_loop(json);
}
virtual bool format_unit(Opt_trace_context *json) {
return unit_ctx::format_unit(json);
}
virtual void set_sort(sort_ctx *ctx) { return join_ctx::set_sort(ctx); }
virtual void set_window(window_ctx *ctx) { return join_ctx::set_window(ctx); }
virtual qep_row *entry() { return join_ctx::entry(); }
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *obj) {
obj->add(K_USING_TMP_TABLE, true);
return format_nested_loop(json);
}
};
/**
Node class for UNION (query expression)
*/
class union_ctx : public unit_ctx {
union_result_ctx *union_result; ///< associated CTX_UNION_RESULT node
List<context> query_specs; ///< query specification nodes (inner selects)
public:
explicit union_ctx(context *parent_arg)
: context(CTX_UNION, K_QUERY_BLOCK, parent_arg),
unit_ctx(CTX_UNION, K_QUERY_BLOCK, parent_arg),
union_result(NULL) {}
private:
virtual bool format_body(Opt_trace_context *json, Opt_trace_object *) {
if (union_result)
return (union_result->format(json)) || format_unit(json);
else {
/*
UNION without temporary table. There is no union_result since
there is no fake_select_lex.
*/
Opt_trace_object union_res(json, K_UNION_RESULT);
union_res.add(K_USING_TMP_TABLE, false);
Opt_trace_array specs(json, K_QUERY_SPECIFICATIONS);
List_iterator<context> it(query_specs);
context *ctx;
while ((ctx = it++)) {
if (ctx->format(json)) return true; /* purecov: inspected */
}
return format_unit(json);
}
}
public:
virtual size_t id(bool hide) { return get_id(query_specs, hide); }
virtual bool cacheable() { return query_specs.head()->cacheable(); }
virtual bool dependent() { return query_specs.head()->dependent(); }
virtual void set_union_result(union_result_ctx *ctx) {
DBUG_ASSERT(union_result == NULL);
union_result = ctx;
union_result->push_down_query_specs(&query_specs);
}
virtual bool add_query_spec(context *ctx) {
return query_specs.push_back(ctx);
}
};
bool union_result_ctx::format(Opt_trace_context *json) {
if (order_by_subqueries.is_empty() && optimized_away_subqueries.is_empty())
return table_base_ctx::format(json);
Opt_trace_object order_by(json, K_ORDERING_OPERATION);
order_by.add(K_USING_FILESORT, !order_by_subqueries.is_empty());
if (table_base_ctx::format(json)) return true; /* purecov: inspected */
if (!order_by_subqueries.is_empty() &&
format_list(json, order_by_subqueries, K_ORDER_BY_SUBQUERIES))
return true; /* purecov: inspected */
if (!optimized_away_subqueries.is_empty() &&
format_list(json, optimized_away_subqueries, K_OPTIMIZED_AWAY_SUBQUERIES))
return true; /* purecov: inspected */
return false;
}
} // namespace opt_explain_json_namespace
qep_row *Explain_format_JSON::entry() { return current_context->entry(); }
bool Explain_format_JSON::begin_context(enum_parsing_context ctx,
SELECT_LEX_UNIT *subquery,
const Explain_format_flags *flags) {
using namespace opt_explain_json_namespace;
context *prev_context = current_context;
switch (ctx) {
case CTX_JOIN:
DBUG_ASSERT(current_context == NULL ||
// subqueries:
current_context->type == CTX_SELECT_LIST ||
current_context->type == CTX_UPDATE_VALUE ||
current_context->type == CTX_INSERT_VALUES ||
current_context->type == CTX_INSERT_UPDATE ||
current_context->type == CTX_DERIVED ||
current_context->type == CTX_OPTIMIZED_AWAY_SUBQUERY ||
current_context->type == CTX_WHERE ||
current_context->type == CTX_HAVING ||
current_context->type == CTX_ORDER_BY_SQ ||
current_context->type == CTX_GROUP_BY_SQ ||
current_context->type == CTX_QUERY_SPEC);
if ((current_context = new (*THR_MALLOC)
join_ctx(CTX_JOIN, K_QUERY_BLOCK, current_context)) == NULL)
return true;
break;
case CTX_ORDER_BY: {
DBUG_ASSERT(current_context->type == CTX_JOIN);
sort_ctx *ctx = new (*THR_MALLOC) sort_with_subqueries_ctx(
CTX_ORDER_BY, K_ORDERING_OPERATION, current_context, SQ_ORDER_BY,
flags, ESC_ORDER_BY);
if (ctx == NULL) return true;
current_context->set_sort(ctx);
current_context = ctx;
break;
}
case CTX_GROUP_BY: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW);
sort_ctx *ctx = new (*THR_MALLOC) sort_with_subqueries_ctx(
CTX_GROUP_BY, K_GROUPING_OPERATION, current_context, SQ_GROUP_BY,
flags, ESC_GROUP_BY);
if (ctx == NULL) return true;
current_context->set_sort(ctx);
current_context = ctx;
break;
}
case CTX_DISTINCT: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_ORDER_BY);
sort_ctx *ctx =
new (*THR_MALLOC) sort_ctx(CTX_DISTINCT, K_DUPLICATES_REMOVAL,
current_context, flags, ESC_DISTINCT);
if (ctx == NULL) return true;
current_context->set_sort(ctx);
current_context = ctx;
break;
}
case CTX_BUFFER_RESULT: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_GROUP_BY);
sort_ctx *ctx =
new (*THR_MALLOC) sort_ctx(CTX_BUFFER_RESULT, K_BUFFER_RESULT,
current_context, flags, ESC_BUFFER_RESULT);
if (ctx == NULL) return true;
current_context->set_sort(ctx);
current_context = ctx;
break;
}
case CTX_QEP_TAB: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_MATERIALIZATION ||
current_context->type == CTX_DUPLICATES_WEEDOUT ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_SIMPLE_GROUP_BY ||
current_context->type == CTX_SIMPLE_ORDER_BY ||
current_context->type == CTX_SIMPLE_DISTINCT);
join_tab_ctx *ctx =
new (*THR_MALLOC) join_tab_ctx(CTX_QEP_TAB, current_context);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_SIMPLE_ORDER_BY: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_MATERIALIZATION ||
current_context->type == CTX_DUPLICATES_WEEDOUT ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_DISTINCT);
simple_sort_ctx *ctx = new (*THR_MALLOC) simple_sort_with_subqueries_ctx(
CTX_SIMPLE_ORDER_BY, K_ORDERING_OPERATION, current_context,
SQ_ORDER_BY, flags, ESC_ORDER_BY);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_SIMPLE_GROUP_BY: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_MATERIALIZATION ||
current_context->type == CTX_DUPLICATES_WEEDOUT ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_SIMPLE_ORDER_BY ||
current_context->type == CTX_SIMPLE_DISTINCT);
simple_sort_ctx *ctx = new (*THR_MALLOC) simple_sort_with_subqueries_ctx(
CTX_SIMPLE_GROUP_BY, K_GROUPING_OPERATION, current_context,
SQ_GROUP_BY, flags, ESC_GROUP_BY);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_SIMPLE_DISTINCT: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_MATERIALIZATION ||
current_context->type == CTX_DUPLICATES_WEEDOUT ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_SIMPLE_ORDER_BY);
simple_sort_ctx *ctx = new (*THR_MALLOC)
simple_sort_ctx(CTX_SIMPLE_DISTINCT, K_DUPLICATES_REMOVAL,
current_context, flags, ESC_DISTINCT);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_MATERIALIZATION: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_DUPLICATES_WEEDOUT);
materialize_ctx *ctx = new (*THR_MALLOC) materialize_ctx(current_context);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_DUPLICATES_WEEDOUT: {
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_MATERIALIZATION);
duplication_weedout_ctx *ctx =
new (*THR_MALLOC) duplication_weedout_ctx(current_context);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_SELECT_LIST: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_SELECT_LIST, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_SELECT_LIST, ctx))
return true;
current_context = ctx;
break;
}
case CTX_UPDATE_VALUE: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_UPDATE_VALUE, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_UPDATE_VALUE, ctx))
return true;
current_context = ctx;
break;
}
case CTX_INSERT_VALUES: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_INSERT_VALUES, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_INSERT_VALUES, ctx))
return true;
current_context = ctx;
break;
}
case CTX_INSERT_UPDATE: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_INSERT_UPDATE, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_INSERT_UPDATE, ctx))
return true;
current_context = ctx;
break;
}
case CTX_DERIVED: {
current_context = new (*THR_MALLOC) subquery_ctx(
CTX_DERIVED, K_MATERIALIZED_FROM_SUBQUERY, current_context);
if (current_context == NULL) return true;
break;
}
case CTX_OPTIMIZED_AWAY_SUBQUERY: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_OPTIMIZED_AWAY_SUBQUERY, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_OPTIMIZED_AWAY, ctx))
return true;
current_context = ctx;
break;
}
case CTX_WHERE: {
DBUG_ASSERT(subquery != NULL);
subquery_ctx *ctx =
new (*THR_MALLOC) subquery_ctx(CTX_WHERE, NULL, current_context);
if (ctx == NULL || current_context->add_where_subquery(ctx, subquery))
return true;
current_context = ctx;
break;
}
case CTX_HAVING: {
subquery_ctx *ctx =
new (*THR_MALLOC) subquery_ctx(CTX_HAVING, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_HAVING, ctx))
return true;
current_context = ctx;
break;
}
case CTX_ORDER_BY_SQ: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_ORDER_BY_SQ, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_ORDER_BY, ctx))
return true;
current_context = ctx;
break;
}
case CTX_GROUP_BY_SQ: {
subquery_ctx *ctx = new (*THR_MALLOC)
subquery_ctx(CTX_GROUP_BY_SQ, NULL, current_context);
if (ctx == NULL || current_context->add_subquery(SQ_GROUP_BY, ctx))
return true;
current_context = ctx;
break;
}
case CTX_UNION:
DBUG_ASSERT(current_context == NULL ||
// subqueries:
current_context->type == CTX_SELECT_LIST ||
current_context->type == CTX_UPDATE_VALUE ||
current_context->type == CTX_DERIVED ||
current_context->type == CTX_OPTIMIZED_AWAY_SUBQUERY ||
current_context->type == CTX_WHERE ||
current_context->type == CTX_HAVING ||
current_context->type == CTX_ORDER_BY_SQ ||
current_context->type == CTX_GROUP_BY_SQ ||
current_context->type == CTX_QUERY_SPEC);
current_context = new (*THR_MALLOC) union_ctx(current_context);
if (current_context == NULL) return true;
break;
case CTX_UNION_RESULT: {
DBUG_ASSERT(current_context->type == CTX_UNION);
union_result_ctx *ctx =
new (*THR_MALLOC) union_result_ctx(current_context);
if (ctx == NULL) return true;
current_context->set_union_result(ctx);
current_context = ctx;
break;
}
case CTX_QUERY_SPEC: {
DBUG_ASSERT(current_context->type == CTX_UNION);
subquery_ctx *ctx =
new (*THR_MALLOC) subquery_ctx(CTX_QUERY_SPEC, NULL, current_context);
if (ctx == NULL || current_context->add_query_spec(ctx)) return true;
current_context = ctx;
break;
}
case CTX_MESSAGE: {
/*
Like CTX_QEP_TAB:
*/
DBUG_ASSERT(current_context->type == CTX_JOIN ||
current_context->type == CTX_MATERIALIZATION ||
current_context->type == CTX_DUPLICATES_WEEDOUT ||
current_context->type == CTX_GROUP_BY ||
current_context->type == CTX_ORDER_BY ||
current_context->type == CTX_DISTINCT ||
current_context->type == CTX_WINDOW ||
current_context->type == CTX_BUFFER_RESULT ||
current_context->type == CTX_SIMPLE_GROUP_BY ||
current_context->type == CTX_SIMPLE_ORDER_BY ||
current_context->type == CTX_SIMPLE_DISTINCT ||
current_context->type == CTX_UNION_RESULT);
joinable_ctx *ctx = new (*THR_MALLOC) message_ctx(current_context);
if (ctx == NULL || current_context->add_join_tab(ctx)) return true;
current_context = ctx;
break;
}
case CTX_WINDOW: {
window_ctx *ctx = new (*THR_MALLOC) window_ctx(current_context);
if (ctx == NULL) return true;
current_context->set_window(ctx);
current_context = ctx;
break;
}
default:
DBUG_ASSERT(!"Unknown EXPLAIN context!");
return true;
}
if (prev_context) prev_context->set_child(current_context);
return false;
}
bool Explain_format_JSON::end_context(enum_parsing_context ctx) {
DBUG_ASSERT(current_context->type == ctx);
bool ret = false;
if (current_context->parent == NULL) {
Item *item;
Opt_trace_context json;
const size_t max_size = ULONG_MAX;
if (json.start(true, // support_I_S (enable JSON generation)
false, // support_dbug_or_missing_priv
current_thd->variables.end_markers_in_json, // end_marker
false, // one_line
0, // offset
1, // limit
max_size, // max_mem_size
Opt_trace_context::MISC))
return true;
{
Opt_trace_object braces(&json);
if (current_context->format(&json)) return true;
}
json.end();
Opt_trace_iterator it(&json);
if (!it.at_end()) {
Opt_trace_info info;
it.get_value(&info);
item =
new Item_string(info.trace_ptr, static_cast<uint>(info.trace_length),
system_charset_info);
} else
item = new Item_null();
List<Item> field_list;
ret = (item == NULL || field_list.push_back(item) ||
output->send_data(current_thd, field_list));
} else if (ctx == CTX_DERIVED) {
if (!current_context->parent->find_and_set_derived(current_context)) {
DBUG_ASSERT(!"No derived table found!");
return true;
}
}
current_context = current_context->parent;
return ret;
}
bool Explain_format_JSON::send_headers(Query_result *result) {
if (Explain_format::send_headers(result)) return true;
List<Item> field_list;
Item *item = new Item_empty_string("EXPLAIN", 78, system_charset_info);
if (item == NULL || field_list.push_back(item)) return true;
return result->send_result_set_metadata(
current_thd, field_list, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
}
void qep_row::format_extra(Opt_trace_object *obj) {
List_iterator<qep_row::extra> it(col_extra);
qep_row::extra *e;
while ((e = it++)) {
DBUG_ASSERT(json_extra_tags[e->tag] != NULL);
if (e->data)
obj->add_utf8(json_extra_tags[e->tag], e->data);
else
obj->add(json_extra_tags[e->tag], true);
}
}