forked from StoneAtom/StoneDB
3783 lines
124 KiB
C++
3783 lines
124 KiB
C++
/* Copyright (c) 2000, 2021, Oracle and/or its affiliates.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL hereby grant you an additional
|
|
permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with MySQL.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License, version 2.0, for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
/**
|
|
@file
|
|
|
|
@brief
|
|
Implementation of name resolution stage
|
|
|
|
|
|
@defgroup Query_Resolver Query Resolver
|
|
@{
|
|
*/
|
|
|
|
#include "sql_select.h"
|
|
#include "sql_resolver.h"
|
|
#include "sql_optimizer.h"
|
|
#include "opt_trace.h"
|
|
#include "sql_base.h"
|
|
#include "auth_common.h"
|
|
#include "opt_explain_format.h"
|
|
#include "sql_test.h" // print_where
|
|
#include "aggregate_check.h"
|
|
#include "template_utils.h"
|
|
|
|
static void propagate_nullability(List<TABLE_LIST> *tables, bool nullable);
|
|
|
|
static const Item::enum_walk walk_subquery=
|
|
Item::enum_walk(Item::WALK_POSTFIX | Item::WALK_SUBQUERY);
|
|
|
|
uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
|
|
uint first_unused);
|
|
|
|
|
|
/**
|
|
Prepare query block for optimization.
|
|
|
|
Resolve table and column information.
|
|
Resolve all expressions (item trees), ie WHERE clause, join conditions,
|
|
GROUP BY clause, HAVING clause, ORDER BY clause, LIMIT clause.
|
|
Prepare all subqueries recursively as part of resolving the expressions.
|
|
Apply permanent transformations to the abstract syntax tree, such as
|
|
semi-join transformation, derived table transformation, elimination of
|
|
constant values and redundant clauses (e.g ORDER BY, GROUP BY).
|
|
|
|
@param thd thread handler
|
|
|
|
@returns false if success, true if error
|
|
|
|
@note on privilege checking for SELECT query that possibly contains view
|
|
or derived table references:
|
|
|
|
- When this function is called, it is assumed that the precheck() function
|
|
has been called. precheck() ensures that the user has some SELECT
|
|
privileges to the tables involved in the query. When resolving views
|
|
it has also been established that the user has some privileges for them.
|
|
To prepare a view for privilege checking, it is also needed to call
|
|
check_view_privileges() after views have been merged into the query.
|
|
This is not necessary for unnamed derived tables since it has already
|
|
been established that we have SELECT privileges for the underlying tables
|
|
by the precheck functions. (precheck() checks a query without resolved
|
|
views, ie. before tables are opened, so underlying tables of views
|
|
are not yet available).
|
|
|
|
- When a query block is resolved, always ensure that the user has SELECT
|
|
privileges to the columns referenced in the WHERE clause, the join
|
|
conditions, the GROUP BY clause, the HAVING clause and the ORDER BY clause.
|
|
|
|
- When resolving the outer-most query block, ensure that the user also has
|
|
SELECT privileges to the columns in the selected expressions.
|
|
|
|
- When setting up a derived table or view for materialization, ensure that
|
|
the user has SELECT privileges to the columns in the selected expressions
|
|
|
|
- Column privileges are normally checked by Item_field::fix_fields().
|
|
Exceptions are select list of derived tables/views which are checked
|
|
in TABLE_LIST::setup_materialized_derived(), and natural/using join
|
|
conditions that are checked in mark_common_columns().
|
|
|
|
- As far as INSERT, UPDATE and DELETE statements have the same expressions
|
|
as a SELECT statement, this note applies to those statements as well.
|
|
*/
|
|
bool SELECT_LEX::prepare(THD *thd)
|
|
{
|
|
DBUG_ENTER("SELECT_LEX::prepare");
|
|
|
|
// We may do subquery transformation, or Item substitution:
|
|
Prepare_error_tracker tracker(thd);
|
|
|
|
assert(this == thd->lex->current_select());
|
|
assert(join == NULL);
|
|
|
|
SELECT_LEX_UNIT *const unit= master_unit();
|
|
|
|
if (top_join_list.elements > 0)
|
|
propagate_nullability(&top_join_list, false);
|
|
|
|
is_item_list_lookup= true;
|
|
|
|
/*
|
|
Determine whether immediate derived tables can be merged:
|
|
- DTs belonging to outermost query block: always
|
|
- DTs belonging to first level subqueries: Yes if inside SELECT statement,
|
|
no otherwise (including UPDATE and DELETE).
|
|
This is required to support a workaround for allowing subqueries
|
|
containing the same table as is target for delete or update,
|
|
by forcing a materialization of the subquery.
|
|
- All other cases inherit status of parent query block.
|
|
*/
|
|
allow_merge_derived=
|
|
outer_select() == NULL ||
|
|
master_unit()->item == NULL ||
|
|
(outer_select()->outer_select() == NULL ?
|
|
parent_lex->sql_command == SQLCOM_SELECT :
|
|
outer_select()->allow_merge_derived);
|
|
|
|
Opt_trace_context * const trace= &thd->opt_trace;
|
|
Opt_trace_object trace_wrapper(trace);
|
|
Opt_trace_object trace_prepare(trace, "join_preparation");
|
|
trace_prepare.add_select_number(select_number);
|
|
Opt_trace_array trace_steps(trace, "steps");
|
|
|
|
// Initially, "all_fields" is the select list
|
|
all_fields= fields_list;
|
|
|
|
/* Check that all tables, fields, conds and order are ok */
|
|
|
|
if (!(active_options() & OPTION_SETUP_TABLES_DONE))
|
|
{
|
|
if (setup_tables(thd, get_table_list(), false))
|
|
DBUG_RETURN(true);
|
|
|
|
if (derived_table_count && resolve_derived(thd, true))
|
|
DBUG_RETURN(true);
|
|
|
|
// Wait with privilege checking until all derived tables are resolved.
|
|
if (!thd->derived_tables_processing &&
|
|
check_view_privileges(thd, SELECT_ACL, SELECT_ACL))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
// Precompute and store the row types of NATURAL/USING joins.
|
|
if (leaf_table_count >= 2 &&
|
|
setup_natural_join_row_types(thd, join_list, &context))
|
|
DBUG_RETURN(true);
|
|
|
|
Mem_root_array<Item_exists_subselect *, true>
|
|
sj_candidates_local(thd->mem_root);
|
|
sj_candidates= &sj_candidates_local;
|
|
|
|
/*
|
|
Item and Item_field CTORs will both increment some counters
|
|
in current_select(), based on the current parsing context.
|
|
We are not parsing anymore: any new Items created now are due to
|
|
query rewriting, so stop incrementing counters.
|
|
*/
|
|
assert(parsing_place == CTX_NONE);
|
|
parsing_place= CTX_NONE;
|
|
|
|
resolve_place= RESOLVE_SELECT_LIST;
|
|
|
|
/*
|
|
Setup the expressions in the SELECT list. Wait with privilege checking
|
|
until all derived tables are resolved, except do privilege checking for
|
|
subqueries inside a derived table.
|
|
*/
|
|
const bool check_privs= !thd->derived_tables_processing ||
|
|
master_unit()->item != NULL;
|
|
thd->mark_used_columns= check_privs ? MARK_COLUMNS_READ : MARK_COLUMNS_NONE;
|
|
ulonglong want_privilege_saved= thd->want_privilege;
|
|
thd->want_privilege= check_privs ? SELECT_ACL : 0;
|
|
|
|
if (with_wild && setup_wild(thd))
|
|
DBUG_RETURN(true);
|
|
if (setup_ref_array(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
if (setup_fields(thd, ref_ptrs, fields_list, thd->want_privilege,
|
|
&all_fields, true, false))
|
|
DBUG_RETURN(true);
|
|
|
|
resolve_place= RESOLVE_NONE;
|
|
|
|
const nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
|
|
|
|
// Do not allow local set functions for join conditions, WHERE and GROUP BY
|
|
thd->lex->allow_sum_func&= ~((nesting_map)1 << nest_level);
|
|
|
|
thd->mark_used_columns= MARK_COLUMNS_READ;
|
|
thd->want_privilege= SELECT_ACL;
|
|
|
|
// Set up join conditions and WHERE clause
|
|
if (setup_conds(thd))
|
|
DBUG_RETURN(true);
|
|
|
|
// Set up the GROUP BY clause
|
|
int all_fields_count= all_fields.elements;
|
|
if (group_list.elements && setup_group(thd))
|
|
DBUG_RETURN(true);
|
|
hidden_group_field_count= all_fields.elements - all_fields_count;
|
|
|
|
// Allow local set functions in HAVING and ORDER BY
|
|
thd->lex->allow_sum_func|= (nesting_map)1 << nest_level;
|
|
|
|
// Setup the HAVING clause
|
|
if (m_having_cond)
|
|
{
|
|
thd->where="having clause";
|
|
having_fix_field= true;
|
|
resolve_place= RESOLVE_HAVING;
|
|
if (!m_having_cond->fixed &&
|
|
(m_having_cond->fix_fields(thd, &m_having_cond) ||
|
|
m_having_cond->check_cols(1)))
|
|
DBUG_RETURN(true);
|
|
|
|
having_fix_field= false;
|
|
resolve_place= RESOLVE_NONE;
|
|
}
|
|
|
|
// Set up the ORDER BY clause
|
|
all_fields_count= all_fields.elements;
|
|
if (order_list.elements)
|
|
{
|
|
if (setup_order(thd, ref_ptrs, get_table_list(), fields_list, all_fields,
|
|
order_list.first))
|
|
DBUG_RETURN(true);
|
|
hidden_order_field_count= all_fields.elements - all_fields_count;
|
|
}
|
|
|
|
// Query block is completely resolved, restore set function allowance
|
|
thd->lex->allow_sum_func= save_allow_sum_func;
|
|
|
|
thd->want_privilege= want_privilege_saved;
|
|
/*
|
|
Permanently remove redundant parts from the query if
|
|
1) This is a subquery
|
|
2) This is the first time this query is prepared (since the
|
|
transformation is permanent)
|
|
3) Not normalizing a view. Removal should take place when a
|
|
query involving a view is optimized, not when the view
|
|
is created
|
|
*/
|
|
if (unit->item && // 1)
|
|
first_execution && // 2)
|
|
!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW)) // 3)
|
|
{
|
|
remove_redundant_subquery_clauses(thd, hidden_group_field_count);
|
|
}
|
|
|
|
if (order_list.elements &&
|
|
setup_order_final(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
if (is_distinct() &&
|
|
is_grouped() &&
|
|
hidden_group_field_count == 0 &&
|
|
olap == UNSPECIFIED_OLAP_TYPE)
|
|
{
|
|
/*
|
|
All GROUP expressions are in SELECT list, so resulting rows are distinct.
|
|
ROLLUP is not specified, so adds no row. So all rows in the result set
|
|
are distinct, DISTINCT is useless.
|
|
@todo could remove DISTINCT if ROLLUP were specified and all GROUP
|
|
expressions were non-nullable, because ROLLUP adds only NULL values.
|
|
Currently, ROLLUP+DISTINCT is rejected because executor cannot handle
|
|
it in all cases.
|
|
*/
|
|
remove_base_options(SELECT_DISTINCT);
|
|
}
|
|
|
|
/*
|
|
Printing the expanded query should happen here and not elsewhere, because
|
|
when a view is merged (when the view is opened in open_tables()), the
|
|
parent query's select_lex does not yet contain a correct WHERE clause (it
|
|
misses the view's merged WHERE clause). This is corrected only just above,
|
|
in TABLE_LIST::prep_where(), called by
|
|
setup_without_group()->setup_conds().
|
|
We also have to wait for fix_fields() on HAVING, above.
|
|
At this stage, we also have properly set up Item_ref-s.
|
|
*/
|
|
{
|
|
Opt_trace_object trace_wrapper(trace);
|
|
opt_trace_print_expanded_query(thd, this, &trace_wrapper);
|
|
}
|
|
|
|
/*
|
|
When normalizing a view (like when writing a view's body to the FRM),
|
|
subquery transformations don't apply (if they did, IN->EXISTS could not be
|
|
undone in favour of materialization, when optimizing a later statement
|
|
using the view)
|
|
*/
|
|
if (unit->item && // This is a subquery
|
|
this != unit->fake_select_lex && // A real query block
|
|
// Not normalizing a view
|
|
!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW))
|
|
{
|
|
// Query block represents a subquery within an IN/ANY/ALL/EXISTS predicate
|
|
if (resolve_subquery(thd))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (m_having_cond && m_having_cond->with_sum_func)
|
|
m_having_cond->split_sum_func2(thd, ref_ptrs, all_fields, &m_having_cond,
|
|
true);
|
|
if (inner_sum_func_list)
|
|
{
|
|
Item_sum *end=inner_sum_func_list;
|
|
Item_sum *item_sum= end;
|
|
do
|
|
{
|
|
item_sum= item_sum->next;
|
|
item_sum->split_sum_func2(thd, ref_ptrs,
|
|
all_fields, item_sum->ref_by, false);
|
|
} while (item_sum != end);
|
|
}
|
|
|
|
if (inner_refs_list.elements && fix_inner_refs(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
if (group_list.elements)
|
|
{
|
|
/*
|
|
Because HEAP tables can't index BIT fields we need to use an
|
|
additional hidden field for grouping because later it will be
|
|
converted to a LONG field. Original field will remain of the
|
|
BIT type and will be returned to a client.
|
|
*/
|
|
for (ORDER *ord= group_list.first; ord; ord= ord->next)
|
|
{
|
|
if ((*ord->item)->type() == Item::FIELD_ITEM &&
|
|
(*ord->item)->field_type() == MYSQL_TYPE_BIT)
|
|
{
|
|
Item_field *field= new Item_field(thd, *(Item_field**)ord->item);
|
|
ord->item= add_hidden_item(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (setup_ftfuncs(this)) /* should be after having->fix_fields */
|
|
DBUG_RETURN(true);
|
|
|
|
if (query_result() && query_result()->prepare(fields_list, unit))
|
|
DBUG_RETURN(true);
|
|
|
|
if (olap == ROLLUP_TYPE && resolve_rollup(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
if (flatten_subqueries())
|
|
DBUG_RETURN(true);
|
|
|
|
sj_candidates= NULL;
|
|
|
|
/*
|
|
When reaching the top-most query block, or the next-to-top query block for
|
|
the SQL command SET and for SP instructions (indicated with SQLCOM_END),
|
|
apply local transformations to this query block and all underlying query
|
|
blocks.
|
|
*/
|
|
if (outer_select() == NULL ||
|
|
((parent_lex->sql_command == SQLCOM_SET_OPTION ||
|
|
parent_lex->sql_command == SQLCOM_END) &&
|
|
outer_select()->outer_select() == NULL))
|
|
{
|
|
/*
|
|
This code is invoked in the following cases:
|
|
- if this is an outer-most query block of a SELECT or multi-table
|
|
UPDATE/DELETE statement. Notice that for a UNION, this applies to
|
|
all query blocks. It also applies to a fake_select_lex object.
|
|
- if this is one of highest-level subqueries, if the statement is
|
|
something else; like subq-i in:
|
|
UPDATE t1 SET col1=(subq-1), col2=(subq-2);
|
|
- If this is a subquery in a SET command
|
|
@todo: Refactor SET so that this is not needed.
|
|
|
|
Local transforms are applied after query block merging.
|
|
This means that we avoid unnecessary invocations, as local transforms
|
|
would otherwise have been performed first before query block merging and
|
|
then another time after query block merging.
|
|
Thus, apply_local_transforms() may run only after the top query
|
|
is finished with query block merging. That's why
|
|
apply_local_transforms() is initiated only by the top query, and then
|
|
recurses into subqueries.
|
|
*/
|
|
if (apply_local_transforms(thd, true))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
assert(!thd->is_error());
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Apply local transformations, such as query block merging.
|
|
Also perform partition pruning, which is most effective after transformations
|
|
have been done.
|
|
|
|
@param thd thread handler
|
|
@param prune if true, then prune partitions based on const conditions
|
|
|
|
@returns false if success, true if error
|
|
|
|
Since this is called after flattening of query blocks, call this function
|
|
while traversing the query block hierarchy top-down.
|
|
*/
|
|
|
|
bool SELECT_LEX::apply_local_transforms(THD *thd, bool prune)
|
|
{
|
|
DBUG_ENTER("SELECT_LEX::apply_local_transforms");
|
|
|
|
/*
|
|
If query block contains one or more merged derived tables/views,
|
|
walk through lists of columns in select lists and remove unused columns.
|
|
*/
|
|
if (derived_table_count &&
|
|
first_execution &&
|
|
!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW))
|
|
delete_unused_merged_columns(&top_join_list);
|
|
|
|
for (SELECT_LEX_UNIT *unit= first_inner_unit();
|
|
unit;
|
|
unit= unit->next_unit())
|
|
{
|
|
for (SELECT_LEX *sl= unit->first_select();
|
|
sl;
|
|
sl= sl->next_select())
|
|
{
|
|
// Prune all subqueries, regardless of passed argument
|
|
if (sl->apply_local_transforms(thd, true))
|
|
DBUG_RETURN(true);
|
|
}
|
|
if (unit->fake_select_lex &&
|
|
unit->fake_select_lex->apply_local_transforms(thd, false))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (first_execution &&
|
|
!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW))
|
|
{
|
|
/*
|
|
The following code will allocate the new items in a permanent
|
|
MEMROOT for prepared statements and stored procedures.
|
|
*/
|
|
Prepared_stmt_arena_holder ps_arena_holder(thd);
|
|
// Convert all outer joins to inner joins if possible
|
|
if (simplify_joins(thd, &top_join_list, true, false, &m_where_cond))
|
|
DBUG_RETURN(true);
|
|
if (record_join_nest_info(&top_join_list))
|
|
DBUG_RETURN(true);
|
|
build_bitmap_for_nested_joins(&top_join_list, 0);
|
|
|
|
/*
|
|
Here are the reasons why we do the following check here (i.e. late).
|
|
* setup_fields () may have done split_sum_func () on aggregate items of
|
|
the SELECT list, so for reliable comparison of the ORDER BY list with
|
|
the SELECT list, we need to wait until split_sum_func() is done with
|
|
the ORDER BY list.
|
|
* we get resolved expressions "most of the time", which is always a good
|
|
thing. Some outer references may not be resolved, though.
|
|
* we need nested_join::used_tables, and this member is set in
|
|
simplify_joins()
|
|
* simplify_joins() does outer-join-to-inner conversion, which increases
|
|
opportunities for functional dependencies (weak-to-strong, which is
|
|
unusable, becomes strong-to-strong).
|
|
* check_only_full_group_by() is dependent on processing done by
|
|
simplify_joins() (for example it uses the value of
|
|
SELECT_LEX::outer_join).
|
|
|
|
The drawback is that the checks are after resolve_subquery(), so can
|
|
meet strange "internally added" items.
|
|
|
|
Note that when we are creating a view, simplify_joins() doesn't run so
|
|
check_only_full_group_by() cannot run, any error will be raised only
|
|
when the view is later used (SELECTed...)
|
|
*/
|
|
if ((is_distinct() || is_grouped()) &&
|
|
(thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
|
|
check_only_full_group_by(thd))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
fix_prepare_information(thd);
|
|
|
|
/*
|
|
Prune partitions for all query blocks after query block merging, if
|
|
pruning is wanted.
|
|
*/
|
|
if (partitioned_table_count && prune)
|
|
{
|
|
for (TABLE_LIST *tbl= leaf_tables; tbl; tbl= tbl->next_leaf)
|
|
{
|
|
/*
|
|
This will only prune constant conditions, which will be used for
|
|
lock pruning.
|
|
*/
|
|
if (prune_partitions(thd, tbl->table,
|
|
tbl->join_cond() ? tbl->join_cond() :
|
|
m_where_cond))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Check if the subquery predicate can be executed via materialization.
|
|
|
|
@param predicate IN subquery predicate
|
|
@param thd THD
|
|
@param select_lex SELECT_LEX of the subquery
|
|
@param outer Parent SELECT_LEX (outer to subquery)
|
|
|
|
@return TRUE if subquery allows materialization, FALSE otherwise.
|
|
*/
|
|
|
|
bool subquery_allows_materialization(Item_in_subselect *predicate,
|
|
THD *thd,
|
|
SELECT_LEX *select_lex,
|
|
const SELECT_LEX *outer)
|
|
{
|
|
const uint elements= predicate->unit->first_select()->item_list.elements;
|
|
DBUG_ENTER("subquery_allows_materialization");
|
|
assert(elements >= 1);
|
|
assert(predicate->left_expr->cols() == elements);
|
|
|
|
OPT_TRACE_TRANSFORM(&thd->opt_trace, trace_wrapper, trace_mat,
|
|
select_lex->select_number,
|
|
"IN (SELECT)", "materialization");
|
|
|
|
const char *cause= NULL;
|
|
if (predicate->substype() != Item_subselect::IN_SUBS)
|
|
{
|
|
// Subq-mat cannot handle 'outer_expr > {ANY|ALL}(subq)'...
|
|
cause= "not an IN predicate";
|
|
}
|
|
else if (select_lex->is_part_of_union())
|
|
{
|
|
// Subquery must be a single query specification clause (not a UNION)
|
|
cause= "in UNION";
|
|
}
|
|
else if (!select_lex->master_unit()->first_select()->leaf_tables)
|
|
{
|
|
// Subquery has no tables, hence no point in materializing.
|
|
cause= "no inner tables";
|
|
}
|
|
else if (!outer->join)
|
|
{
|
|
/*
|
|
Maybe this is a subquery of a single table UPDATE/DELETE (TODO:
|
|
handle this by switching to multi-table UPDATE/DELETE).
|
|
*/
|
|
cause= "parent query has no JOIN";
|
|
}
|
|
else if (!outer->leaf_tables)
|
|
{
|
|
// The upper query is SELECT ... FROM DUAL. No gain in materializing.
|
|
cause= "no tables in outer query";
|
|
}
|
|
else if (predicate->dependent_before_in2exists())
|
|
{
|
|
/*
|
|
Subquery should not be correlated; the correlation due to predicates
|
|
injected by IN->EXISTS does not count as we will remove them if we
|
|
choose materialization.
|
|
|
|
TODO:
|
|
This is an overly restrictive condition. It can be extended to:
|
|
(Subquery is non-correlated ||
|
|
Subquery is correlated to any query outer to IN predicate ||
|
|
(Subquery is correlated to the immediate outer query &&
|
|
Subquery !contains {GROUP BY, ORDER BY [LIMIT],
|
|
aggregate functions}) && subquery predicate is not under "NOT IN"))
|
|
*/
|
|
cause= "correlated";
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
Check that involved expression types allow materialization.
|
|
This is a temporary fix for BUG#36752; see bug report for
|
|
description of restrictions we need to put on the compared expressions.
|
|
*/
|
|
assert(predicate->left_expr->fixed);
|
|
// @see comment in Item_subselect::element_index()
|
|
bool has_nullables= predicate->left_expr->maybe_null;
|
|
|
|
List_iterator<Item> it(predicate->unit->first_select()->item_list);
|
|
for (uint i= 0; i < elements; i++)
|
|
{
|
|
Item * const inner= it++;
|
|
Item * const outer= predicate->left_expr->element_index(i);
|
|
if (!types_allow_materialization(outer, inner))
|
|
{
|
|
cause= "type mismatch";
|
|
break;
|
|
}
|
|
if (inner->is_blob_field()) // 6
|
|
{
|
|
cause= "inner blob";
|
|
break;
|
|
}
|
|
has_nullables|= inner->maybe_null;
|
|
}
|
|
|
|
if (!cause)
|
|
{
|
|
trace_mat.add("has_nullable_expressions", has_nullables);
|
|
/*
|
|
Subquery materialization cannot handle NULLs partial matching
|
|
properly, yet. If the outer or inner values are NULL, the
|
|
subselect_hash_sj_engine may reply FALSE when it should reply UNKNOWN.
|
|
So, we must limit it to those three cases:
|
|
- when FALSE and UNKNOWN are equivalent answers. I.e. this is a a
|
|
top-level predicate (this implies it is not negated).
|
|
- when outer and inner values cannot be NULL.
|
|
- when there is a single inner column (because for this we have a
|
|
limited implementation of NULLs partial matching).
|
|
*/
|
|
const bool is_top_level= predicate->is_top_level_item();
|
|
trace_mat.add("treat_UNKNOWN_as_FALSE", is_top_level);
|
|
|
|
if (!is_top_level && has_nullables && (elements > 1))
|
|
cause= "cannot_handle_partial_matches";
|
|
else
|
|
{
|
|
trace_mat.add("possible", true);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
}
|
|
}
|
|
assert(cause != NULL);
|
|
trace_mat.add("possible", false).add_alnum("cause", cause);
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Make list of leaf tables of join table tree
|
|
|
|
@param list pointer to pointer on list first element
|
|
@param tables table list
|
|
|
|
@returns pointer on pointer to next_leaf of last element
|
|
*/
|
|
|
|
static TABLE_LIST **make_leaf_tables(TABLE_LIST **list, TABLE_LIST *tables)
|
|
{
|
|
for (TABLE_LIST *table= tables; table; table= table->next_local)
|
|
{
|
|
// A mergable view is not allowed to have a table pointer.
|
|
assert(!(table->is_view() && table->is_merged() && table->table));
|
|
if (table->merge_underlying_list)
|
|
{
|
|
assert(table->is_merged());
|
|
|
|
list= make_leaf_tables(list, table->merge_underlying_list);
|
|
}
|
|
else
|
|
{
|
|
*list= table;
|
|
list= &table->next_leaf;
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
|
|
/**
|
|
Check privileges for the view tables merged into a query block.
|
|
|
|
@param thd Thread context.
|
|
@param want_privilege_first Privileges requested for the first leaf.
|
|
@param want_privilege_next Privileges requested for the remaining leaves.
|
|
|
|
@note Beware that it can't properly check privileges in cases when
|
|
table being changed is not the first table in the list of leaf
|
|
tables (for example, for multi-UPDATE).
|
|
|
|
@note The inner loop is slightly inefficient. A view will have its privileges
|
|
checked once for every base table that it refers to.
|
|
|
|
@returns false if success, true if error.
|
|
*/
|
|
|
|
bool st_select_lex::check_view_privileges(THD *thd,
|
|
ulong want_privilege_first,
|
|
ulong want_privilege_next)
|
|
{
|
|
ulong want_privilege= want_privilege_first;
|
|
Internal_error_handler_holder<View_error_handler, TABLE_LIST>
|
|
view_handler(thd, true, leaf_tables);
|
|
|
|
for (TABLE_LIST *tl= leaf_tables; tl; tl= tl->next_leaf)
|
|
{
|
|
for (TABLE_LIST *ref= tl; ref->referencing_view; ref= ref->referencing_view)
|
|
{
|
|
if (check_single_table_access(thd, want_privilege, ref, false))
|
|
return true;
|
|
}
|
|
want_privilege= want_privilege_next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Set up table leaves in the query block based on list of tables.
|
|
|
|
@param thd Thread handler
|
|
@param tables List of tables to handle
|
|
@param select_insert It is SELECT ... INSERT command
|
|
|
|
@note
|
|
Check also that the 'used keys' and 'ignored keys' exists and set up the
|
|
table structure accordingly.
|
|
Create a list of leaf tables.
|
|
|
|
This function has to be called for all tables that are used by items,
|
|
as otherwise table->map is not set and all Item_field will be regarded
|
|
as const items.
|
|
|
|
@returns False on success, true on error
|
|
*/
|
|
|
|
bool st_select_lex::setup_tables(THD *thd, TABLE_LIST *tables,
|
|
bool select_insert)
|
|
{
|
|
DBUG_ENTER("st_select_lex::setup_tables");
|
|
|
|
assert ((select_insert && !tables->next_name_resolution_table) ||
|
|
!tables ||
|
|
(context.table_list && context.first_name_resolution_table));
|
|
|
|
make_leaf_tables(&leaf_tables, tables);
|
|
|
|
TABLE_LIST *first_select_table= NULL;
|
|
if (select_insert)
|
|
{
|
|
// "insert_table" is needed for remap_tables().
|
|
thd->lex->insert_table= leaf_tables->top_table();
|
|
|
|
// Get first table in SELECT part
|
|
first_select_table= thd->lex->insert_table->next_local;
|
|
|
|
// Then, find the first leaf table
|
|
if (first_select_table)
|
|
first_select_table= first_select_table->first_leaf_table();
|
|
}
|
|
uint tableno= 0;
|
|
leaf_table_count= 0;
|
|
partitioned_table_count= 0;
|
|
|
|
for (TABLE_LIST *tr= leaf_tables; tr; tr= tr->next_leaf, tableno++)
|
|
{
|
|
TABLE *const table= tr->table;
|
|
if (tr == first_select_table)
|
|
{
|
|
/*
|
|
For INSERT ... SELECT command, restart numbering from zero for first
|
|
leaf table from SELECT part of query.
|
|
*/
|
|
first_select_table= 0;
|
|
tableno= 0;
|
|
}
|
|
if (tableno >= MAX_TABLES)
|
|
{
|
|
my_error(ER_TOO_MANY_TABLES,MYF(0), static_cast<int>(MAX_TABLES));
|
|
DBUG_RETURN(true);
|
|
}
|
|
tr->set_tableno(tableno);
|
|
leaf_table_count++; // Count the input tables of the query
|
|
if (table == NULL)
|
|
continue;
|
|
table->pos_in_table_list= tr;
|
|
tr->reset();
|
|
|
|
/*
|
|
Only set hints on first execution. Otherwise, hints will refer to
|
|
wrong query block after semijoin transformation
|
|
*/
|
|
if (first_execution &&
|
|
opt_hints_qb && // QB hints initialized
|
|
!tr->opt_hints_table) // Table hints are not adjusted yet
|
|
{
|
|
tr->opt_hints_table= opt_hints_qb->adjust_table_hints(table, tr->alias);
|
|
}
|
|
|
|
if (tr->process_index_hints(table))
|
|
DBUG_RETURN(true);
|
|
if (table->part_info) // Count number of partitioned tables
|
|
partitioned_table_count++;
|
|
}
|
|
|
|
if (opt_hints_qb)
|
|
opt_hints_qb->check_unresolved(thd);
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Re-map table numbers for all tables in a query block.
|
|
|
|
@param thd Thread handler
|
|
|
|
@note
|
|
This function needs to be called after setup_tables() has been called,
|
|
and after a query block for a subquery has been merged into a parent
|
|
quary block.
|
|
*/
|
|
|
|
void st_select_lex::remap_tables(THD *thd)
|
|
{
|
|
LEX *const lex= thd->lex;
|
|
TABLE_LIST *first_select_table= NULL;
|
|
if (lex->insert_table &&
|
|
lex->insert_table == leaf_tables->top_table())
|
|
{
|
|
/*
|
|
For INSERT ... SELECT command, restart numbering from zero for first
|
|
leaf table from SELECT part of query.
|
|
*/
|
|
// Get first table in SELECT part
|
|
first_select_table= lex->insert_table->next_local;
|
|
|
|
// Then, recurse down to get first leaf table
|
|
if (first_select_table)
|
|
first_select_table= first_select_table->first_leaf_table();
|
|
}
|
|
|
|
uint tableno= 0;
|
|
for (TABLE_LIST *tl= leaf_tables; tl; tl= tl->next_leaf)
|
|
{
|
|
// Reset table number after having reached first table after insert table
|
|
if (first_select_table == tl)
|
|
tableno= 0;
|
|
tl->set_tableno(tableno++);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief Resolve derived table and view references in query block
|
|
|
|
@param thd Pointer to THD.
|
|
@param apply_semijoin if true, apply semi-join transform when possible
|
|
|
|
@return false if success, true if error
|
|
*/
|
|
|
|
bool st_select_lex::resolve_derived(THD *thd, bool apply_semijoin)
|
|
{
|
|
DBUG_ENTER("st_select_lex::resolve_derived");
|
|
|
|
assert(derived_table_count);
|
|
|
|
// Prepare derived tables and views that belong to this query block.
|
|
for (TABLE_LIST *tl= get_table_list(); tl; tl= tl->next_local)
|
|
{
|
|
if (!tl->is_view_or_derived() || tl->is_merged())
|
|
continue;
|
|
if (tl->resolve_derived(thd, apply_semijoin))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
/*
|
|
Merge the derived tables that do not require materialization into
|
|
the current query block, if possible.
|
|
Merging is only done once and must not be repeated for prepared execs.
|
|
*/
|
|
if (!(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW) &&
|
|
first_execution)
|
|
{
|
|
for (TABLE_LIST *tl= get_table_list(); tl; tl= tl->next_local)
|
|
{
|
|
if (!tl->is_view_or_derived() ||
|
|
tl->is_merged() ||
|
|
!tl->is_mergeable())
|
|
continue;
|
|
if (merge_derived(thd, tl))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
}
|
|
}
|
|
|
|
// Prepare remaining derived tables for materialization
|
|
for (TABLE_LIST *tl= get_table_list(); tl; tl= tl->next_local)
|
|
{
|
|
// Ensure that any derived table is merged or materialized after prepare:
|
|
assert(first_execution || !tl->is_view_or_derived() ||
|
|
tl->is_merged() || tl->uses_materialization());
|
|
if (!tl->is_view_or_derived() || tl->is_merged())
|
|
continue;
|
|
if (tl->setup_materialized_derived(thd))
|
|
DBUG_RETURN(true);
|
|
materialized_derived_table_count++;
|
|
}
|
|
|
|
/*
|
|
The loops above will not reach derived tables that are contained within
|
|
other derived tables that have been merged into the enclosing query block.
|
|
To reach them, traverse the list of leaf tables and resolve and
|
|
setup for materialization those derived tables that have no TABLE
|
|
object (they have not been set up yet).
|
|
*/
|
|
if (!first_execution)
|
|
{
|
|
for (TABLE_LIST *tl= leaf_tables; tl; tl= tl->next_leaf)
|
|
{
|
|
if (!tl->is_view_or_derived() || tl->table != NULL)
|
|
continue;
|
|
assert(!tl->is_merged());
|
|
if (tl->resolve_derived(thd, apply_semijoin))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
if (tl->setup_materialized_derived(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
/*
|
|
materialized_derived_table_count was incremented during preparation,
|
|
so do not do it once more.
|
|
*/
|
|
}
|
|
}
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Resolve predicate involving subquery
|
|
|
|
@param thd Pointer to THD.
|
|
|
|
@retval FALSE Success.
|
|
@retval TRUE Error.
|
|
|
|
@details
|
|
Perform early unconditional subquery transformations:
|
|
- Convert subquery predicate into semi-join, or
|
|
- Mark the subquery for execution using materialization, or
|
|
- Perform IN->EXISTS transformation, or
|
|
- Perform more/less ALL/ANY -> MIN/MAX rewrite
|
|
- Substitute trivial scalar-context subquery with its value
|
|
|
|
@todo for PS, make the whole block execute only on the first execution
|
|
|
|
*/
|
|
|
|
bool SELECT_LEX::resolve_subquery(THD *thd)
|
|
{
|
|
DBUG_ENTER("resolve_subquery");
|
|
|
|
bool chose_semijoin= false;
|
|
SELECT_LEX *const outer= outer_select();
|
|
|
|
/*
|
|
@todo for PS, make the whole block execute only on the first execution.
|
|
resolve_subquery() is only invoked in the first execution for subqueries
|
|
that are transformed to semijoin, but for other subqueries, this function
|
|
is called for every execution. One solution is perhaps to define
|
|
exec_method in class Item_subselect and exit immediately if unequal to
|
|
EXEC_UNSPECIFIED.
|
|
*/
|
|
Item_subselect *subq_predicate= master_unit()->item;
|
|
assert(subq_predicate);
|
|
/**
|
|
@note
|
|
In this case: IN (SELECT ... UNION SELECT ...), SELECT_LEX::prepare() is
|
|
called for each of the two UNION members, and in those two calls,
|
|
subq_predicate is the same, not sure this is desired (double work?).
|
|
*/
|
|
|
|
Item_in_subselect * const in_predicate=
|
|
(subq_predicate->substype() == Item_subselect::IN_SUBS) ?
|
|
static_cast<Item_in_subselect *>(subq_predicate) : NULL;
|
|
|
|
if (in_predicate)
|
|
{
|
|
thd->lex->set_current_select(outer);
|
|
char const *save_where= thd->where;
|
|
thd->where= "IN/ALL/ANY subquery";
|
|
Disable_semijoin_flattening DSF(outer, true);
|
|
|
|
bool result= !in_predicate->left_expr->fixed &&
|
|
in_predicate->left_expr->fix_fields(thd,
|
|
&in_predicate->left_expr);
|
|
thd->lex->set_current_select(this);
|
|
thd->where= save_where;
|
|
if (result)
|
|
DBUG_RETURN(true);
|
|
|
|
/*
|
|
Check if the left and right expressions have the same # of
|
|
columns, i.e. we don't have a case like
|
|
(oe1, oe2) IN (SELECT ie1, ie2, ie3 ...)
|
|
|
|
TODO why do we have this duplicated in IN->EXISTS transformers?
|
|
psergey-todo: fix these: grep for duplicated_subselect_card_check
|
|
*/
|
|
if (item_list.elements != in_predicate->left_expr->cols())
|
|
{
|
|
my_error(ER_OPERAND_COLUMNS, MYF(0), in_predicate->left_expr->cols());
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
|
|
DBUG_PRINT("info", ("Checking if subq can be converted to semi-join"));
|
|
/*
|
|
Check if we're in subquery that is a candidate for flattening into a
|
|
semi-join (which is done in flatten_subqueries()). The requirements are:
|
|
1. Subquery predicate is an IN/=ANY subquery predicate
|
|
2. Subquery is a single SELECT (not a UNION)
|
|
3. Subquery does not have GROUP BY
|
|
4. Subquery does not use aggregate functions or HAVING
|
|
5. Subquery predicate is (a) in an ON/WHERE clause, and (b) at
|
|
the AND-top-level of that clause.
|
|
6. Parent query block accepts semijoins (i.e we are not in a subquery of
|
|
a single table UPDATE/DELETE (TODO: We should handle this at some
|
|
point by switching to multi-table UPDATE/DELETE)
|
|
7. We're not in a confluent table-less subquery, like "SELECT 1".
|
|
8. No execution method was already chosen (by a prepared statement).
|
|
9. Parent query block is not a confluent table-less query block.
|
|
10. Neither parent nor child query block has straight join.
|
|
11. Parent query block does not prohibit semi-join.
|
|
*/
|
|
if (semijoin_enabled(thd) &&
|
|
in_predicate && // 1
|
|
!is_part_of_union() && // 2
|
|
!group_list.elements && // 3
|
|
!m_having_cond && !with_sum_func && // 4
|
|
(outer->resolve_place == st_select_lex::RESOLVE_CONDITION || // 5a
|
|
outer->resolve_place == st_select_lex::RESOLVE_JOIN_NEST) && // 5a
|
|
!outer->semijoin_disallowed && // 5b
|
|
outer->sj_candidates && // 6
|
|
leaf_table_count > 0 && // 7
|
|
in_predicate->exec_method ==
|
|
Item_exists_subselect::EXEC_UNSPECIFIED && // 8
|
|
outer->leaf_table_count && // 9
|
|
!((active_options() | outer->active_options()) &
|
|
SELECT_STRAIGHT_JOIN) && //10
|
|
!(outer->active_options() & SELECT_NO_SEMI_JOIN)) //11
|
|
{
|
|
DBUG_PRINT("info", ("Subquery is semi-join conversion candidate"));
|
|
|
|
/* Notify in the subquery predicate where it belongs in the query graph */
|
|
in_predicate->embedding_join_nest= outer->resolve_nest;
|
|
|
|
/* Register the subquery for further processing in flatten_subqueries() */
|
|
outer->sj_candidates->push_back(in_predicate);
|
|
chose_semijoin= true;
|
|
}
|
|
|
|
if (in_predicate)
|
|
{
|
|
Opt_trace_context * const trace= &thd->opt_trace;
|
|
OPT_TRACE_TRANSFORM(trace, oto0, oto1,
|
|
select_number, "IN (SELECT)", "semijoin");
|
|
oto1.add("chosen", chose_semijoin);
|
|
}
|
|
|
|
if (!chose_semijoin &&
|
|
subq_predicate->select_transformer(this) ==
|
|
Item_subselect::RES_ERROR)
|
|
DBUG_RETURN(true);
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Expand all '*' in list of expressions with the matching column references
|
|
|
|
Function should not be called with no wild cards in select list
|
|
|
|
@param thd thread handler
|
|
|
|
@returns false if OK, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::setup_wild(THD *thd)
|
|
{
|
|
DBUG_ENTER("SELECT_LEX::setup_wild");
|
|
|
|
assert(with_wild);
|
|
|
|
// PS/SP uses arena so that changes are made permanently.
|
|
Prepared_stmt_arena_holder ps_arena_holder(thd);
|
|
|
|
Item *item;
|
|
List_iterator<Item> it(fields_list);
|
|
|
|
while (with_wild && (item= it++))
|
|
{
|
|
Item_field *item_field;
|
|
if (item->type() == Item::FIELD_ITEM &&
|
|
(item_field= down_cast<Item_field *>(item)) &&
|
|
item_field->is_asterisk())
|
|
{
|
|
assert(item_field->field == NULL);
|
|
const uint elem= fields_list.elements;
|
|
const bool any_privileges= item_field->any_privileges;
|
|
Item_subselect *subsel= master_unit()->item;
|
|
|
|
/*
|
|
In case of EXISTS(SELECT * ... HAVING ...), don't use this
|
|
transformation. The columns in HAVING will need to resolve to the
|
|
select list. Replacing * with 1 effectively eliminates this
|
|
possibility.
|
|
*/
|
|
if (subsel && subsel->substype() == Item_subselect::EXISTS_SUBS &&
|
|
!having_cond())
|
|
{
|
|
/*
|
|
It is EXISTS(SELECT * ...) and we can replace * by any constant.
|
|
|
|
Item_int do not need fix_fields() because it is basic constant.
|
|
*/
|
|
it.replace(new Item_int(NAME_STRING("Not_used"), (longlong) 1,
|
|
MY_INT64_NUM_DECIMAL_DIGITS));
|
|
}
|
|
else
|
|
{
|
|
if (insert_fields(thd, item_field->context,
|
|
item_field->db_name, item_field->table_name,
|
|
&it, any_privileges))
|
|
DBUG_RETURN(true);
|
|
}
|
|
/*
|
|
all_fields is a list that has the fields list as a tail.
|
|
Because of this we have to update the element count also for this
|
|
list after expanding the '*' entry.
|
|
*/
|
|
all_fields.elements+= fields_list.elements - elem;
|
|
|
|
with_wild--;
|
|
}
|
|
}
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
/**
|
|
Resolve WHERE condition and join conditions
|
|
|
|
@param thd thread handler
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
bool SELECT_LEX::setup_conds(THD *thd)
|
|
{
|
|
DBUG_ENTER("SELECT_LEX::setup_conds");
|
|
|
|
/*
|
|
it_is_update set to TRUE when tables of primary SELECT_LEX (SELECT_LEX
|
|
which belong to LEX, i.e. most up SELECT) will be updated by
|
|
INSERT/UPDATE/LOAD
|
|
NOTE: using this condition helps to prevent call of prepare_check_option()
|
|
from subquery of VIEW, because tables of subquery belongs to VIEW
|
|
(see condition before prepare_check_option() call)
|
|
*/
|
|
const bool it_is_update= (this == thd->lex->select_lex) &&
|
|
thd->lex->which_check_option_applicable();
|
|
const bool save_is_item_list_lookup= is_item_list_lookup;
|
|
is_item_list_lookup= false;
|
|
|
|
DBUG_PRINT("info", ("thd->mark_used_columns: %d", thd->mark_used_columns));
|
|
|
|
if (m_where_cond)
|
|
{
|
|
resolve_place= st_select_lex::RESOLVE_CONDITION;
|
|
thd->where="where clause";
|
|
if ((!m_where_cond->fixed &&
|
|
m_where_cond->fix_fields(thd, &m_where_cond)) ||
|
|
m_where_cond->check_cols(1))
|
|
DBUG_RETURN(true);
|
|
resolve_place= st_select_lex::RESOLVE_NONE;
|
|
}
|
|
|
|
/*
|
|
Apply fix_fields() to all ON clauses at all levels of nesting,
|
|
including the ones inside view definitions.
|
|
*/
|
|
for (TABLE_LIST *table= leaf_tables; table; table= table->next_leaf)
|
|
{
|
|
TABLE_LIST *embedded; /* The table at the current level of nesting. */
|
|
TABLE_LIST *embedding= table; /* The parent nested table reference. */
|
|
do
|
|
{
|
|
embedded= embedding;
|
|
if (embedded->join_cond())
|
|
{
|
|
resolve_place= st_select_lex::RESOLVE_JOIN_NEST;
|
|
resolve_nest= embedded;
|
|
thd->where="on clause";
|
|
if ((!embedded->join_cond()->fixed &&
|
|
embedded->join_cond()->fix_fields(thd, embedded->join_cond_ref())) ||
|
|
embedded->join_cond()->check_cols(1))
|
|
DBUG_RETURN(true);
|
|
cond_count++;
|
|
resolve_place= st_select_lex::RESOLVE_NONE;
|
|
resolve_nest= NULL;
|
|
}
|
|
embedding= embedded->embedding;
|
|
}
|
|
while (embedding &&
|
|
embedding->nested_join->join_list.head() == embedded);
|
|
|
|
/* process CHECK OPTION */
|
|
if (it_is_update)
|
|
{
|
|
TABLE_LIST *view= table->top_table();
|
|
if (view->is_view() && view->is_merged())
|
|
{
|
|
if (view->prepare_check_option(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
table->check_option= view->check_option;
|
|
}
|
|
}
|
|
}
|
|
|
|
is_item_list_lookup= save_is_item_list_lookup;
|
|
|
|
assert(thd->lex->current_select() == this);
|
|
assert(!thd->is_error());
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Set NESTED_JOIN::counter=0 in all nested joins in passed list.
|
|
|
|
@param join_list Pass NULL. Non-NULL is reserved for recursive inner calls,
|
|
then it is a list of nested joins to process, and may also contain base
|
|
tables which will be ignored.
|
|
*/
|
|
|
|
void SELECT_LEX::reset_nj_counters(List<TABLE_LIST> *join_list)
|
|
{
|
|
if (join_list == NULL)
|
|
join_list= &top_join_list;
|
|
List_iterator<TABLE_LIST> li(*join_list);
|
|
TABLE_LIST *table;
|
|
DBUG_ENTER("reset_nj_counters");
|
|
while ((table= li++))
|
|
{
|
|
NESTED_JOIN *nested_join;
|
|
if ((nested_join= table->nested_join))
|
|
{
|
|
nested_join->nj_counter= 0;
|
|
reset_nj_counters(&nested_join->join_list);
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
Simplify joins replacing outer joins by inner joins whenever it's
|
|
possible.
|
|
|
|
The function, during a retrieval of join_list, eliminates those
|
|
outer joins that can be converted into inner join, possibly nested.
|
|
It also moves the join conditions for the converted outer joins
|
|
and from inner joins to conds.
|
|
The function also calculates some attributes for nested joins:
|
|
|
|
-# used_tables
|
|
-# not_null_tables
|
|
-# dep_tables.
|
|
-# on_expr_dep_tables
|
|
|
|
The first two attributes are used to test whether an outer join can
|
|
be substituted by an inner join. The third attribute represents the
|
|
relation 'to be dependent on' for tables. If table t2 is dependent
|
|
on table t1, then in any evaluated execution plan table access to
|
|
table t2 must precede access to table t2. This relation is used also
|
|
to check whether the query contains invalid cross-references.
|
|
The fourth attribute is an auxiliary one and is used to calculate
|
|
dep_tables.
|
|
As the attribute dep_tables qualifies possibles orders of tables in the
|
|
execution plan, the dependencies required by the straight join
|
|
modifiers are reflected in this attribute as well.
|
|
The function also removes all parentheses that can be removed from the join
|
|
expression without changing its meaning.
|
|
|
|
@note
|
|
An outer join can be replaced by an inner join if the where condition
|
|
or the join condition for an embedding nested join contains a conjunctive
|
|
predicate rejecting null values for some attribute of the inner tables.
|
|
|
|
E.g. in the query:
|
|
@code
|
|
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5
|
|
@endcode
|
|
the predicate t2.b < 5 rejects nulls.
|
|
The query is converted first to:
|
|
@code
|
|
SELECT * FROM t1 INNER JOIN t2 ON t2.a=t1.a WHERE t2.b < 5
|
|
@endcode
|
|
then to the equivalent form:
|
|
@code
|
|
SELECT * FROM t1, t2 ON t2.a=t1.a WHERE t2.b < 5 AND t2.a=t1.a
|
|
@endcode
|
|
|
|
|
|
Similarly the following query:
|
|
@code
|
|
SELECT * from t1 LEFT JOIN (t2, t3) ON t2.a=t1.a t3.b=t1.b
|
|
WHERE t2.c < 5
|
|
@endcode
|
|
is converted to:
|
|
@code
|
|
SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a t3.b=t1.b
|
|
|
|
@endcode
|
|
|
|
One conversion might trigger another:
|
|
@code
|
|
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a
|
|
LEFT JOIN t3 ON t3.b=t2.b
|
|
WHERE t3 IS NOT NULL =>
|
|
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a, t3
|
|
WHERE t3 IS NOT NULL AND t3.b=t2.b =>
|
|
SELECT * FROM t1, t2, t3
|
|
WHERE t3 IS NOT NULL AND t3.b=t2.b AND t2.a=t1.a
|
|
@endcode
|
|
|
|
The function removes all unnecessary parentheses from the expression
|
|
produced by the conversions.
|
|
E.g.
|
|
@code
|
|
SELECT * FROM t1, (t2, t3) WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b
|
|
@endcode
|
|
finally is converted to:
|
|
@code
|
|
SELECT * FROM t1, t2, t3 WHERE t2.c < 5 AND t2.a=t1.a AND t3.b=t1.b
|
|
|
|
@endcode
|
|
|
|
|
|
It also will remove parentheses from the following queries:
|
|
@code
|
|
SELECT * from (t1 LEFT JOIN t2 ON t2.a=t1.a) LEFT JOIN t3 ON t3.b=t2.b
|
|
SELECT * from (t1, (t2,t3)) WHERE t1.a=t2.a AND t2.b=t3.b.
|
|
@endcode
|
|
|
|
The benefit of this simplification procedure is that it might return
|
|
a query for which the optimizer can evaluate execution plans with more
|
|
join orders. With a left join operation the optimizer does not
|
|
consider any plan where one of the inner tables is before some of outer
|
|
tables.
|
|
|
|
IMPLEMENTATION
|
|
The function is implemented by a recursive procedure. On the recursive
|
|
ascent all attributes are calculated, all outer joins that can be
|
|
converted are replaced and then all unnecessary parentheses are removed.
|
|
As join list contains join tables in the reverse order sequential
|
|
elimination of outer joins does not require extra recursive calls.
|
|
|
|
SEMI-JOIN NOTES
|
|
Remove all semi-joins that have are within another semi-join (i.e. have
|
|
an "ancestor" semi-join nest)
|
|
|
|
EXAMPLES
|
|
Here is an example of a join query with invalid cross references:
|
|
@code
|
|
SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t3.a LEFT JOIN t3 ON t3.b=t1.b
|
|
@endcode
|
|
|
|
@param thd thread handler
|
|
@param join_list list representation of the join to be converted
|
|
@param top true <=> cond is the where condition
|
|
@param in_sj TRUE <=> processing semi-join nest's children
|
|
@param[in,out] cond In: condition to which the join condition for converted
|
|
outer joins is to be added;
|
|
Out: new condition
|
|
@param changelog Don't specify this parameter, it is reserved for
|
|
recursive calls inside this function
|
|
|
|
@returns true for error, false for success
|
|
*/
|
|
bool
|
|
SELECT_LEX::simplify_joins(THD *thd,
|
|
List<TABLE_LIST> *join_list, bool top,
|
|
bool in_sj, Item **cond, uint *changelog)
|
|
{
|
|
/*
|
|
Each type of change done by this function, or its recursive calls, is
|
|
tracked in a bitmap:
|
|
*/
|
|
enum change
|
|
{
|
|
NONE= 0,
|
|
OUTER_JOIN_TO_INNER= 1 << 0,
|
|
JOIN_COND_TO_WHERE= 1 << 1,
|
|
PAREN_REMOVAL= 1 << 2,
|
|
SEMIJOIN= 1 << 3
|
|
};
|
|
uint changes= 0; // To keep track of changes.
|
|
if (changelog == NULL) // This is the top call.
|
|
changelog= &changes;
|
|
|
|
TABLE_LIST *table;
|
|
NESTED_JOIN *nested_join;
|
|
TABLE_LIST *prev_table= 0;
|
|
List_iterator<TABLE_LIST> li(*join_list);
|
|
const bool straight_join= active_options() & SELECT_STRAIGHT_JOIN;
|
|
DBUG_ENTER("simplify_joins");
|
|
|
|
/*
|
|
Try to simplify join operations from join_list.
|
|
The most outer join operation is checked for conversion first.
|
|
*/
|
|
while ((table= li++))
|
|
{
|
|
table_map used_tables;
|
|
table_map not_null_tables= (table_map) 0;
|
|
|
|
if ((nested_join= table->nested_join))
|
|
{
|
|
/*
|
|
If the element of join_list is a nested join apply
|
|
the procedure to its nested join list first.
|
|
*/
|
|
if (table->join_cond())
|
|
{
|
|
Item *join_cond= table->join_cond();
|
|
/*
|
|
If a join condition JC is attached to the table,
|
|
check all null rejected predicates in this condition.
|
|
If such a predicate over an attribute belonging to
|
|
an inner table of an embedded outer join is found,
|
|
the outer join is converted to an inner join and
|
|
the corresponding join condition is added to JC.
|
|
*/
|
|
if (simplify_joins(thd, &nested_join->join_list,
|
|
false, in_sj || table->sj_cond(),
|
|
&join_cond, changelog))
|
|
DBUG_RETURN(true);
|
|
|
|
if (join_cond != table->join_cond())
|
|
{
|
|
assert(join_cond);
|
|
table->set_join_cond(join_cond);
|
|
}
|
|
}
|
|
nested_join->used_tables= (table_map) 0;
|
|
nested_join->not_null_tables=(table_map) 0;
|
|
if (simplify_joins(thd, &nested_join->join_list, top,
|
|
in_sj || table->sj_cond(), cond, changelog))
|
|
DBUG_RETURN(true);
|
|
used_tables= nested_join->used_tables;
|
|
not_null_tables= nested_join->not_null_tables;
|
|
}
|
|
else
|
|
{
|
|
used_tables= table->map();
|
|
if (*cond)
|
|
not_null_tables= (*cond)->not_null_tables();
|
|
}
|
|
|
|
if (table->embedding)
|
|
{
|
|
table->embedding->nested_join->used_tables|= used_tables;
|
|
table->embedding->nested_join->not_null_tables|= not_null_tables;
|
|
}
|
|
|
|
if (!table->outer_join || (used_tables & not_null_tables))
|
|
{
|
|
/*
|
|
For some of the inner tables there are conjunctive predicates
|
|
that reject nulls => the outer join can be replaced by an inner join.
|
|
*/
|
|
if (table->outer_join)
|
|
{
|
|
*changelog|= OUTER_JOIN_TO_INNER;
|
|
table->outer_join= 0;
|
|
}
|
|
if (table->join_cond())
|
|
{
|
|
*changelog|= JOIN_COND_TO_WHERE;
|
|
/* Add join condition to the WHERE or upper-level join condition. */
|
|
if (*cond)
|
|
{
|
|
Item_cond_and *new_cond=
|
|
static_cast<Item_cond_and*>(and_conds(*cond, table->join_cond()));
|
|
if (!new_cond)
|
|
DBUG_RETURN(true);
|
|
new_cond->top_level_item();
|
|
/*
|
|
It is always a new item as both the upper-level condition and a
|
|
join condition existed
|
|
*/
|
|
assert(!new_cond->fixed);
|
|
if (new_cond->fix_fields(thd, NULL))
|
|
DBUG_RETURN(true);
|
|
|
|
/* If join condition has a pending rollback in THD::change_list */
|
|
List_iterator<Item> lit(*new_cond->argument_list());
|
|
Item *arg;
|
|
while ((arg= lit++))
|
|
{
|
|
/*
|
|
Check whether the arguments to AND need substitution
|
|
of rollback location.
|
|
*/
|
|
thd->replace_rollback_place(lit.ref());
|
|
}
|
|
*cond= new_cond;
|
|
}
|
|
else
|
|
{
|
|
*cond= table->join_cond();
|
|
/* If join condition has a pending rollback in THD::change_list */
|
|
thd->replace_rollback_place(cond);
|
|
}
|
|
table->set_join_cond(NULL);
|
|
}
|
|
}
|
|
|
|
if (!top)
|
|
continue;
|
|
|
|
/*
|
|
Only inner tables of non-convertible outer joins remain with
|
|
the join condition.
|
|
*/
|
|
if (table->join_cond())
|
|
{
|
|
table->dep_tables|= table->join_cond()->used_tables();
|
|
|
|
// At this point the joined tables always have an embedding join nest:
|
|
assert(table->embedding);
|
|
|
|
table->dep_tables&= ~table->embedding->nested_join->used_tables;
|
|
|
|
// Embedding table depends on tables used in embedded join conditions.
|
|
table->embedding->on_expr_dep_tables|= table->join_cond()->used_tables();
|
|
}
|
|
|
|
if (prev_table)
|
|
{
|
|
/* The order of tables is reverse: prev_table follows table */
|
|
if (prev_table->straight || straight_join)
|
|
prev_table->dep_tables|= used_tables;
|
|
if (prev_table->join_cond())
|
|
{
|
|
prev_table->dep_tables|= table->on_expr_dep_tables;
|
|
table_map prev_used_tables= prev_table->nested_join ?
|
|
prev_table->nested_join->used_tables :
|
|
prev_table->map();
|
|
/*
|
|
If join condition contains only references to inner tables
|
|
we still make the inner tables dependent on the outer tables.
|
|
It would be enough to set dependency only on one outer table
|
|
for them. Yet this is really a rare case.
|
|
Note:
|
|
PSEUDO_TABLE_BITS mask should not be counted as it
|
|
prevents update of inner table dependencies.
|
|
For example it might happen if RAND()/COUNT(*) function
|
|
is used in JOIN ON clause.
|
|
*/
|
|
if (!((prev_table->join_cond()->used_tables() & ~PSEUDO_TABLE_BITS) &
|
|
~prev_used_tables))
|
|
prev_table->dep_tables|= used_tables;
|
|
}
|
|
}
|
|
prev_table= table;
|
|
}
|
|
|
|
/*
|
|
Flatten nested joins that can be flattened.
|
|
no join condition and not a semi-join => can be flattened.
|
|
*/
|
|
li.rewind();
|
|
while ((table= li++))
|
|
{
|
|
nested_join= table->nested_join;
|
|
if (table->sj_cond() && !in_sj)
|
|
{
|
|
/*
|
|
If this is a semi-join that is not contained within another semi-join,
|
|
leave it intact (otherwise it is flattened)
|
|
*/
|
|
*changelog|= SEMIJOIN;
|
|
}
|
|
else if (nested_join && !table->join_cond())
|
|
{
|
|
*changelog|= PAREN_REMOVAL;
|
|
TABLE_LIST *tbl;
|
|
List_iterator<TABLE_LIST> it(nested_join->join_list);
|
|
while ((tbl= it++))
|
|
{
|
|
tbl->embedding= table->embedding;
|
|
tbl->join_list= table->join_list;
|
|
tbl->dep_tables|= table->dep_tables;
|
|
}
|
|
li.replace(nested_join->join_list);
|
|
}
|
|
}
|
|
|
|
if (changes)
|
|
{
|
|
Opt_trace_context * trace= &thd->opt_trace;
|
|
if (unlikely(trace->is_started()))
|
|
{
|
|
Opt_trace_object trace_wrapper(trace);
|
|
Opt_trace_object trace_object(trace, "transformations_to_nested_joins");
|
|
{
|
|
Opt_trace_array trace_changes(trace, "transformations");
|
|
if (changes & SEMIJOIN)
|
|
trace_changes.add_alnum("semijoin");
|
|
if (changes & OUTER_JOIN_TO_INNER)
|
|
trace_changes.add_alnum("outer_join_to_inner_join");
|
|
if (changes & JOIN_COND_TO_WHERE)
|
|
trace_changes.add_alnum("JOIN_condition_to_WHERE");
|
|
if (changes & PAREN_REMOVAL)
|
|
trace_changes.add_alnum("parenthesis_removal");
|
|
}
|
|
// the newly transformed query is worth printing
|
|
opt_trace_print_expanded_query(thd, this, &trace_object);
|
|
}
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Record join nest info in the select block.
|
|
|
|
After simplification of inner join, outer join and semi-join structures:
|
|
- record the remaining semi-join structures in the enclosing query block.
|
|
- record transformed join conditions in TABLE_LIST objects.
|
|
|
|
This function is called recursively for each join nest and/or table
|
|
in the query block.
|
|
|
|
@param select The query block
|
|
@param tables List of tables and join nests
|
|
|
|
@return False if successful, True if failure
|
|
*/
|
|
bool SELECT_LEX::record_join_nest_info(List<TABLE_LIST> *tables)
|
|
{
|
|
TABLE_LIST *table;
|
|
List_iterator<TABLE_LIST> li(*tables);
|
|
DBUG_ENTER("record_join_nest_info");
|
|
|
|
while ((table= li++))
|
|
{
|
|
if (table->nested_join == NULL)
|
|
{
|
|
if (table->join_cond())
|
|
outer_join|= table->map();
|
|
continue;
|
|
}
|
|
|
|
if (record_join_nest_info(&table->nested_join->join_list))
|
|
DBUG_RETURN(true);
|
|
/*
|
|
sj_inner_tables is set properly later in pull_out_semijoin_tables().
|
|
This assignment is required in case pull_out_semijoin_tables()
|
|
is not called.
|
|
*/
|
|
if (table->sj_cond())
|
|
table->sj_inner_tables= table->nested_join->used_tables;
|
|
|
|
if (table->sj_cond() && sj_nests.push_back(table))
|
|
DBUG_RETURN(true);
|
|
|
|
if (table->join_cond())
|
|
outer_join|= table->nested_join->used_tables;
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
static int subq_sj_candidate_cmp(Item_exists_subselect* const *el1,
|
|
Item_exists_subselect* const *el2)
|
|
{
|
|
/*
|
|
Remove this assert when we support semijoin on non-IN subqueries.
|
|
*/
|
|
assert((*el1)->substype() == Item_subselect::IN_SUBS &&
|
|
(*el2)->substype() == Item_subselect::IN_SUBS);
|
|
return ((*el1)->sj_convert_priority < (*el2)->sj_convert_priority) ? 1 :
|
|
( ((*el1)->sj_convert_priority == (*el2)->sj_convert_priority)? 0 : -1);
|
|
}
|
|
|
|
|
|
/**
|
|
Update table reference information for conditions and expressions due to
|
|
query blocks having been merged in from derived tables/views and due to
|
|
semi-join transformation.
|
|
|
|
This is needed for two reasons:
|
|
|
|
1. Since table numbers are changed, we need to update used_tables
|
|
information for all conditions and expressions that are possibly touched.
|
|
|
|
2. For semi-join, some column references are changed from outer references
|
|
to local references.
|
|
|
|
The function needs to recursively walk down into join nests,
|
|
in order to cover all conditions and expressions.
|
|
|
|
For a semi-join, tables from the subquery are added last in the query block.
|
|
This means that conditions and expressions from the outer query block
|
|
are unaffected. But all conditions inside the semi-join nest, including
|
|
join conditions, must have their table numbers changed.
|
|
|
|
For a derived table/view, tables from the subquery are merged into the
|
|
outer query, and this function is called for every derived table that is
|
|
merged in. This algorithm only works when derived tables are merged in
|
|
the order of their original table numbers.
|
|
|
|
A hypothetical example with a triple self-join over a mergeable view:
|
|
|
|
CREATE VIEW v AS SELECT t1.a, t2.b FROM t1 JOIN t2 USING (a);
|
|
SELECT v1.a, v1.b, v2.b, v3.b
|
|
FROM v AS v1 JOIN v AS v2 ON ... JOIN v AS v3 ON ...;
|
|
|
|
The analysis starts with three tables v1, v2 and v3 having numbers 0, 1, 2.
|
|
First we merge in v1, so we get (t1, t2, v2, v3). v2 and v3 are shifted up.
|
|
Tables from v1 need to have their table numbers altered (actually they do not
|
|
since both old and new numbers are 0 and 1, but this is a special case).
|
|
v2 and v3 are not merged in yet, so we delay pullout on them until they
|
|
are merged. Conditions and expressions from the outer query are not resolved
|
|
yet, so regular resolving will take of them later.
|
|
Then we merge in v2, so we get (t1, t2, t1, t2, v3). The tables from this
|
|
view gets numbers 2 and 3, and v3 gets number 4.
|
|
Because v2 had a higher number than the tables from v1, the join nest
|
|
representing v1 is unaffected. And v3 is still not merged, so the only
|
|
join nest we need to consider is v2.
|
|
Finally we merge in v3, and then we have tables (t1, t2, t1, t2, t1, t2),
|
|
with numbers 0 through 5.
|
|
Again, since v3 has higher number than any of the already merged in views,
|
|
only this join nest needs the pullout.
|
|
|
|
@param parent_select Query block being merged into
|
|
@param removed_select Query block that is removed (subquery)
|
|
@param tr Table object this pullout is applied to
|
|
@param table_adjust Number of positions that a derived table nest is
|
|
adjusted, used to fix up semi-join related fields.
|
|
Tables are adjusted from position N to N+table_adjust
|
|
*/
|
|
|
|
static void fix_tables_after_pullout(st_select_lex *parent_select,
|
|
st_select_lex *removed_select,
|
|
TABLE_LIST *tr,
|
|
uint table_adjust)
|
|
{
|
|
if (tr->is_merged())
|
|
{
|
|
// Update select list of merged derived tables:
|
|
for (Field_translator *transl= tr->field_translation;
|
|
transl < tr->field_translation_end;
|
|
transl++)
|
|
{
|
|
assert(transl->item->fixed);
|
|
transl->item->fix_after_pullout(parent_select, removed_select);
|
|
}
|
|
// Update used table info for the WHERE clause of the derived table
|
|
assert(!tr->derived_where_cond ||
|
|
tr->derived_where_cond->fixed);
|
|
if (tr->derived_where_cond)
|
|
tr->derived_where_cond->fix_after_pullout(parent_select,
|
|
removed_select);
|
|
}
|
|
|
|
/*
|
|
If join_cond() is fixed, it contains a join condition from a subquery
|
|
that has already been resolved. Call fix_after_pullout() to update
|
|
used table information since table numbers may have changed.
|
|
If join_cond() is not fixed, it contains a condition that was generated
|
|
in the derived table merge operation, which will be fixed later.
|
|
This condition may also contain a fixed part, but this is saved as
|
|
derived_where_cond and is pulled out explicitly.
|
|
*/
|
|
if (tr->join_cond() && tr->join_cond()->fixed)
|
|
tr->join_cond()->fix_after_pullout(parent_select, removed_select);
|
|
|
|
if (tr->nested_join)
|
|
{
|
|
// In case a derived table is merged-in, these fields need adjustment:
|
|
tr->nested_join->sj_corr_tables<<= table_adjust;
|
|
tr->nested_join->sj_depends_on<<= table_adjust;
|
|
|
|
List_iterator<TABLE_LIST> it(tr->nested_join->join_list);
|
|
TABLE_LIST *child;
|
|
while ((child= it++))
|
|
fix_tables_after_pullout(parent_select, removed_select, child,
|
|
table_adjust);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Convert a subquery predicate of this query block into a TABLE_LIST semi-join
|
|
nest.
|
|
|
|
@param subq_pred Subquery predicate to be converted.
|
|
This is either an IN, =ANY or EXISTS predicate.
|
|
|
|
@retval FALSE OK
|
|
@retval TRUE Error
|
|
|
|
@details
|
|
|
|
The following transformations are performed:
|
|
|
|
1. IN/=ANY predicates on the form:
|
|
|
|
SELECT ...
|
|
FROM ot1 ... otN
|
|
WHERE (oe1, ... oeM) IN (SELECT ie1, ..., ieM)
|
|
FROM it1 ... itK
|
|
[WHERE inner-cond])
|
|
[AND outer-cond]
|
|
[GROUP BY ...] [HAVING ...] [ORDER BY ...]
|
|
|
|
are transformed into:
|
|
|
|
SELECT ...
|
|
FROM (ot1 ... otN) SJ (it1 ... itK)
|
|
ON (oe1, ... oeM) = (ie1, ..., ieM)
|
|
[AND inner-cond]
|
|
[WHERE outer-cond]
|
|
[GROUP BY ...] [HAVING ...] [ORDER BY ...]
|
|
|
|
Notice that the inner-cond may contain correlated and non-correlated
|
|
expressions. Further transformations will analyze and break up such
|
|
expressions.
|
|
|
|
Prepared Statements: the transformation is permanent:
|
|
- Changes in TABLE_LIST structures are naturally permanent
|
|
- Item tree changes are performed on statement MEM_ROOT:
|
|
= we activate statement MEM_ROOT
|
|
= this function is called before the first fix_prepare_information call.
|
|
|
|
This is intended because the criteria for subquery-to-sj conversion remain
|
|
constant for the lifetime of the Prepared Statement.
|
|
*/
|
|
bool
|
|
SELECT_LEX::convert_subquery_to_semijoin(Item_exists_subselect *subq_pred)
|
|
{
|
|
TABLE_LIST *emb_tbl_nest= NULL;
|
|
List<TABLE_LIST> *emb_join_list= &top_join_list;
|
|
THD *const thd= subq_pred->unit->thd;
|
|
DBUG_ENTER("convert_subquery_to_semijoin");
|
|
|
|
assert(subq_pred->substype() == Item_subselect::IN_SUBS);
|
|
|
|
bool outer_join= false; // True if predicate is inner to an outer join
|
|
|
|
/*
|
|
Find out where to insert the semi-join nest and the generated condition.
|
|
|
|
For t1 LEFT JOIN t2, embedding_join_nest will be t2.
|
|
Note that t2 may be a simple table or may itself be a join nest
|
|
(e.g. in the case t1 LEFT JOIN (t2 JOIN t3))
|
|
*/
|
|
if (subq_pred->embedding_join_nest != NULL)
|
|
{
|
|
// Is this on inner side of an outer join?
|
|
outer_join= subq_pred->embedding_join_nest->is_inner_table_of_outer_join();
|
|
|
|
if (subq_pred->embedding_join_nest->nested_join)
|
|
{
|
|
/*
|
|
We're dealing with
|
|
|
|
... [LEFT] JOIN ( ... ) ON (subquery AND condition) ...
|
|
|
|
The sj-nest will be inserted into the brackets nest.
|
|
*/
|
|
emb_tbl_nest= subq_pred->embedding_join_nest;
|
|
emb_join_list= &emb_tbl_nest->nested_join->join_list;
|
|
}
|
|
else if (!subq_pred->embedding_join_nest->outer_join)
|
|
{
|
|
/*
|
|
We're dealing with
|
|
|
|
... INNER JOIN tblX ON (subquery AND condition) ...
|
|
|
|
The sj-nest will be tblX's "sibling", i.e. another child of its
|
|
parent. This is ok because tblX is joined as an inner join.
|
|
*/
|
|
emb_tbl_nest= subq_pred->embedding_join_nest->embedding;
|
|
if (emb_tbl_nest)
|
|
emb_join_list= &emb_tbl_nest->nested_join->join_list;
|
|
}
|
|
else if (!subq_pred->embedding_join_nest->nested_join)
|
|
{
|
|
TABLE_LIST *outer_tbl= subq_pred->embedding_join_nest;
|
|
/*
|
|
We're dealing with
|
|
|
|
... LEFT JOIN tbl ON (on_expr AND subq_pred) ...
|
|
|
|
we'll need to convert it into:
|
|
|
|
... LEFT JOIN ( tbl SJ (subq_tables) ) ON (on_expr AND subq_pred) ...
|
|
| |
|
|
|<----- wrap_nest ---->|
|
|
|
|
Q: other subqueries may be pointing to this element. What to do?
|
|
A1: simple solution: copy *subq_pred->embedding_join_nest= *parent_nest.
|
|
But we'll need to fix other pointers.
|
|
A2: Another way: have TABLE_LIST::next_ptr so the following
|
|
subqueries know the table has been nested.
|
|
A3: changes in the TABLE_LIST::outer_join will make everything work
|
|
automatically.
|
|
*/
|
|
TABLE_LIST *const wrap_nest=
|
|
TABLE_LIST::new_nested_join(thd->mem_root, "(sj-wrap)",
|
|
outer_tbl->embedding, outer_tbl->join_list,
|
|
this);
|
|
if (wrap_nest == NULL)
|
|
DBUG_RETURN(true);
|
|
|
|
wrap_nest->nested_join->join_list.push_back(outer_tbl);
|
|
|
|
outer_tbl->embedding= wrap_nest;
|
|
outer_tbl->join_list= &wrap_nest->nested_join->join_list;
|
|
|
|
/*
|
|
An important note, if this 'PREPARE stmt'.
|
|
The FROM clause of the outer query now looks like
|
|
CONCAT(original FROM clause of outer query, sj-nest).
|
|
Given that the original FROM clause is reversed, this list is
|
|
interpreted as "sj-nest is first".
|
|
Thus, at a next execution, setup_natural_join_types() will decide that
|
|
the name resolution context of the FROM clause should start at the
|
|
first inner table in sj-nest.
|
|
However, note that in the present function we do not change
|
|
first_name_resolution_table (and friends) of sj-inner tables.
|
|
So, at the next execution, name resolution for columns of
|
|
outer-table columns is bound to fail (the first inner table does
|
|
not have outer tables in its chain of resolution).
|
|
Fortunately, Item_field::cached_table, which is set during resolution
|
|
of 'PREPARE stmt', gives us the answer and avoids a failing search.
|
|
*/
|
|
|
|
/*
|
|
wrap_nest will take place of outer_tbl, so move the outer join flag
|
|
and join condition.
|
|
*/
|
|
wrap_nest->outer_join= outer_tbl->outer_join;
|
|
outer_tbl->outer_join= 0;
|
|
|
|
// There are item-rollback problems in this function: see bug#16926177
|
|
wrap_nest->set_join_cond(outer_tbl->join_cond()->real_item());
|
|
outer_tbl->set_join_cond(NULL);
|
|
|
|
List_iterator<TABLE_LIST> li(*wrap_nest->join_list);
|
|
TABLE_LIST *tbl;
|
|
while ((tbl= li++))
|
|
{
|
|
if (tbl == outer_tbl)
|
|
{
|
|
li.replace(wrap_nest);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
outer_tbl is replaced by wrap_nest.
|
|
For subselects, update embedding_join_nest to point to wrap_nest
|
|
instead of outer_tbl
|
|
*/
|
|
for (Item_exists_subselect **subquery= sj_candidates->begin();
|
|
subquery < sj_candidates->end();
|
|
subquery++)
|
|
{
|
|
if ((*subquery)->embedding_join_nest == outer_tbl)
|
|
(*subquery)->embedding_join_nest= wrap_nest;
|
|
}
|
|
|
|
/*
|
|
Ok now wrap_nest 'contains' outer_tbl and we're ready to add the
|
|
semi-join nest into it
|
|
*/
|
|
emb_join_list= &wrap_nest->nested_join->join_list;
|
|
emb_tbl_nest= wrap_nest;
|
|
}
|
|
}
|
|
|
|
TABLE_LIST *const sj_nest=
|
|
TABLE_LIST::new_nested_join(thd->mem_root, "(sj-nest)",
|
|
emb_tbl_nest, emb_join_list, this);
|
|
if (sj_nest == NULL)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
NESTED_JOIN *const nested_join= sj_nest->nested_join;
|
|
|
|
/* Nests do not participate in those 'chains', so: */
|
|
/* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/
|
|
emb_join_list->push_back(sj_nest);
|
|
|
|
/*
|
|
Natural joins inside a semi-join nest were already processed when the
|
|
subquery went through initial preparation.
|
|
*/
|
|
sj_nest->nested_join->natural_join_processed= true;
|
|
/*
|
|
nested_join->used_tables and nested_join->not_null_tables are
|
|
initialized in simplify_joins().
|
|
*/
|
|
|
|
st_select_lex *const subq_select= subq_pred->unit->first_select();
|
|
|
|
nested_join->query_block_id= subq_select->select_number;
|
|
|
|
// Merge tables from underlying query block into this join nest
|
|
if (sj_nest->merge_underlying_tables(subq_select))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
/*
|
|
Add tables from subquery at end of leaf table chain.
|
|
(This also means that table map for parent query block tables are unchanged)
|
|
*/
|
|
TABLE_LIST *tl;
|
|
for (tl= leaf_tables; tl->next_leaf; tl= tl->next_leaf)
|
|
{}
|
|
tl->next_leaf= subq_select->leaf_tables;
|
|
|
|
// Add tables from subquery at end of next_local chain.
|
|
for (tl= get_table_list(); tl->next_local; tl= tl->next_local)
|
|
{}
|
|
tl->next_local= subq_select->get_table_list();
|
|
|
|
// Note that subquery's tables are already in the next_global chain
|
|
|
|
// Remove the original subquery predicate from the WHERE/ON
|
|
// The subqueries were replaced for Item_int(1) earlier
|
|
// @todo also reset the 'with_subselect' there.
|
|
|
|
// Walk through child's tables and adjust table map
|
|
uint table_no= leaf_table_count;
|
|
for (tl= subq_select->leaf_tables; tl; tl= tl->next_leaf, table_no++)
|
|
tl->set_tableno(table_no);
|
|
|
|
// Adjust table and expression counts in parent query block:
|
|
derived_table_count+= subq_select->derived_table_count;
|
|
materialized_derived_table_count+=
|
|
subq_select->materialized_derived_table_count;
|
|
has_sj_nests|= subq_select->has_sj_nests;
|
|
partitioned_table_count+= subq_select->partitioned_table_count;
|
|
leaf_table_count+= subq_select->leaf_table_count;
|
|
cond_count+= subq_select->cond_count;
|
|
between_count+= subq_select->between_count;
|
|
|
|
if (outer_join)
|
|
propagate_nullability(&sj_nest->nested_join->join_list, true);
|
|
|
|
nested_join->sj_outer_exprs.empty();
|
|
nested_join->sj_inner_exprs.empty();
|
|
|
|
/*
|
|
@todo: Add similar conversion for subqueries other than IN.
|
|
*/
|
|
if (subq_pred->substype() == Item_subselect::IN_SUBS)
|
|
{
|
|
Item_in_subselect *in_subq_pred= (Item_in_subselect *)subq_pred;
|
|
|
|
assert(is_fixed_or_outer_ref(in_subq_pred->left_expr));
|
|
|
|
in_subq_pred->exec_method= Item_exists_subselect::EXEC_SEMI_JOIN;
|
|
/*
|
|
sj_corr_tables is supposed to contain non-trivially correlated tables,
|
|
but here it is set to contain all correlated tables.
|
|
@todo: Add analysis step that assigns only the set of non-trivially
|
|
correlated tables to sj_corr_tables.
|
|
*/
|
|
nested_join->sj_corr_tables= subq_pred->used_tables();
|
|
|
|
/*
|
|
sj_depends_on contains the set of outer tables referred in the
|
|
subquery's WHERE clause as well as tables referred in the IN predicate's
|
|
left-hand side.
|
|
*/
|
|
nested_join->sj_depends_on= subq_pred->used_tables() |
|
|
in_subq_pred->left_expr->used_tables();
|
|
|
|
// Put the subquery's WHERE into semi-join's condition.
|
|
Item *sj_cond= subq_select->where_cond();
|
|
|
|
/*
|
|
Create the IN-equalities and inject them into semi-join's ON condition.
|
|
Additionally, for LooseScan strategy
|
|
- Record the number of IN-equalities.
|
|
- Create list of pointers to (oe1, ..., ieN). We'll need the list to
|
|
see which of the expressions are bound and which are not (for those
|
|
we'll produce a distinct stream of (ie_i1,...ie_ik).
|
|
|
|
(TODO: can we just create a list of pointers and hope the expressions
|
|
will not substitute themselves on fix_fields()? or we need to wrap
|
|
them into Item_direct_view_refs and store pointers to those. The
|
|
pointers to Item_direct_view_refs are guaranteed to be stable as
|
|
Item_direct_view_refs doesn't substitute itself with anything in
|
|
Item_direct_view_ref::fix_fields.
|
|
|
|
We have a special case for IN predicates with a scalar subquery or a
|
|
row subquery in the predicand (left operand), such as this:
|
|
(SELECT 1,2 FROM t1) IN (SELECT x,y FROM t2)
|
|
We cannot make the join condition 1=x AND 2=y, since that might evaluate
|
|
to TRUE even if t1 is empty. Instead make the join condition
|
|
(SELECT 1,2 FROM t1) = (x,y) in this case.
|
|
|
|
*/
|
|
|
|
Item_subselect *left_subquery=
|
|
(in_subq_pred->left_expr->type() == Item::SUBSELECT_ITEM) ?
|
|
static_cast<Item_subselect *>(in_subq_pred->left_expr) : NULL;
|
|
|
|
if (left_subquery &&
|
|
(left_subquery->substype() == Item_subselect::SINGLEROW_SUBS))
|
|
{
|
|
List<Item> ref_list;
|
|
Item *header= subq_select->ref_pointer_array[0];
|
|
for (uint i= 1; i < in_subq_pred->left_expr->cols(); i++)
|
|
{
|
|
ref_list.push_back(subq_select->ref_pointer_array[i]);
|
|
}
|
|
|
|
Item_row *right_expr= new Item_row(header, ref_list);
|
|
if (!right_expr)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
nested_join->sj_outer_exprs.push_back(in_subq_pred->left_expr);
|
|
nested_join->sj_inner_exprs.push_back(right_expr);
|
|
Item_func_eq *item_eq=
|
|
new Item_func_eq(in_subq_pred->left_expr,
|
|
right_expr);
|
|
if (item_eq == NULL)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
sj_cond= and_items(sj_cond, item_eq);
|
|
if (sj_cond == NULL)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
}
|
|
else
|
|
{
|
|
for (uint i= 0; i < in_subq_pred->left_expr->cols(); i++)
|
|
{
|
|
Item *const li= in_subq_pred->left_expr->element_index(i);
|
|
nested_join->sj_outer_exprs.push_back(li);
|
|
nested_join->sj_inner_exprs.push_back(subq_select->ref_pointer_array[i]);
|
|
|
|
Item_func_eq *item_eq=
|
|
new Item_func_eq(li, subq_select->ref_pointer_array[i]);
|
|
|
|
if (item_eq == NULL)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
/*
|
|
li [left_expr->element_index(i)] can be a transient Item_outer_ref,
|
|
whose usage has already been marked for rollback, but we need to roll
|
|
back this location (inside Item_func_eq) in stead, since this is the
|
|
place that matters after this semijoin transformation. arguments()
|
|
gets the address of li as stored in item_eq ("place").
|
|
*/
|
|
thd->replace_rollback_place(item_eq->arguments());
|
|
|
|
sj_cond= and_items(sj_cond, item_eq);
|
|
if (sj_cond == NULL)
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
}
|
|
}
|
|
// Fix the created equality and AND
|
|
|
|
Opt_trace_array sj_on_trace(&thd->opt_trace,
|
|
"evaluating_constant_semijoin_conditions");
|
|
sj_cond->top_level_item();
|
|
if (sj_cond->fix_fields(thd, &sj_cond))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
// Attach semi-join condition to semi-join nest
|
|
sj_nest->set_sj_cond(sj_cond);
|
|
}
|
|
|
|
// Unlink the subquery's query expression:
|
|
subq_select->master_unit()->exclude_level();
|
|
|
|
// Merge subquery's name resolution contexts into parent's
|
|
merge_contexts(subq_select);
|
|
|
|
repoint_contexts_of_join_nests(subq_select->top_join_list);
|
|
|
|
// Update table map for the semi-join condition
|
|
sj_nest->sj_cond()->fix_after_pullout(this, subq_select);
|
|
|
|
// Update table map for semi-join nest's WHERE condition and join conditions
|
|
fix_tables_after_pullout(this, subq_select, sj_nest, 0);
|
|
|
|
//TODO fix QT_
|
|
DBUG_EXECUTE("where",
|
|
print_where(sj_nest->sj_cond(),"SJ-COND", QT_ORDINARY););
|
|
|
|
if (emb_tbl_nest)
|
|
{
|
|
// Inject semi-join condition into parent's join condition
|
|
emb_tbl_nest->set_join_cond(and_items(emb_tbl_nest->join_cond(),
|
|
sj_nest->sj_cond()));
|
|
if (emb_tbl_nest->join_cond() == NULL)
|
|
DBUG_RETURN(true);
|
|
emb_tbl_nest->join_cond()->top_level_item();
|
|
if (!emb_tbl_nest->join_cond()->fixed &&
|
|
emb_tbl_nest->join_cond()->fix_fields(thd,
|
|
emb_tbl_nest->join_cond_ref()))
|
|
DBUG_RETURN(true);
|
|
}
|
|
else
|
|
{
|
|
// Inject semi-join condition into parent's WHERE condition
|
|
m_where_cond= and_items(m_where_cond, sj_nest->sj_cond());
|
|
if (m_where_cond == NULL)
|
|
DBUG_RETURN(true);
|
|
m_where_cond->top_level_item();
|
|
if (m_where_cond->fix_fields(thd, &m_where_cond))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (subq_select->ftfunc_list->elements &&
|
|
add_ftfunc_list(subq_select->ftfunc_list))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
// This query block has semi-join nests
|
|
has_sj_nests= true;
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Merge a derived table or view into a query block.
|
|
If some constraint prevents the derived table from being merged then do
|
|
nothing, which means the table will be prepared for materialization later.
|
|
|
|
After this call, check is_merged() to see if the table was really merged.
|
|
|
|
@param thd Thread handler
|
|
@param derived_table Derived table which is to be merged.
|
|
|
|
@return false if successful, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::merge_derived(THD *thd, TABLE_LIST *derived_table)
|
|
{
|
|
DBUG_ENTER("SELECT_LEX::merge_derived");
|
|
|
|
if (!derived_table->is_view_or_derived() || derived_table->is_merged())
|
|
DBUG_RETURN(false);
|
|
|
|
SELECT_LEX_UNIT *const derived_unit= derived_table->derived_unit();
|
|
|
|
// A derived table must be prepared before we can merge it
|
|
assert(derived_unit->is_prepared());
|
|
|
|
LEX *const lex= parent_lex;
|
|
|
|
// Check whether the outer query allows merged views
|
|
if ((master_unit() == lex->unit &&
|
|
!lex->can_use_merged()) ||
|
|
lex->can_not_use_merged())
|
|
DBUG_RETURN(false);
|
|
|
|
/*
|
|
@todo: The implementation of LEX::can_use_merged() currently avoids
|
|
merging of views that are contained in other views if
|
|
can_use_merged() returns false.
|
|
*/
|
|
// Check whether derived table is mergeable, and directives allow merging
|
|
if (!derived_unit->is_mergeable() ||
|
|
derived_table->algorithm == VIEW_ALGORITHM_TEMPTABLE ||
|
|
(!thd->optimizer_switch_flag(OPTIMIZER_SWITCH_DERIVED_MERGE) &&
|
|
derived_table->algorithm != VIEW_ALGORITHM_MERGE))
|
|
DBUG_RETURN(false);
|
|
|
|
SELECT_LEX *const derived_select= derived_unit->first_select();
|
|
/*
|
|
If STRAIGHT_JOIN is specified, it is not valid to merge in a query block
|
|
that contains semi-join nests
|
|
*/
|
|
if ((active_options() & SELECT_STRAIGHT_JOIN) && derived_select->has_sj_nests)
|
|
DBUG_RETURN(false);
|
|
|
|
// Check that we have room for the merged tables in the table map:
|
|
if (leaf_table_count + derived_select->leaf_table_count - 1 > MAX_TABLES)
|
|
DBUG_RETURN(false);
|
|
|
|
derived_table->set_merged();
|
|
|
|
DBUG_PRINT("info", ("algorithm: MERGE"));
|
|
|
|
Opt_trace_context *const trace= &thd->opt_trace;
|
|
Opt_trace_object trace_wrapper(trace);
|
|
Opt_trace_object trace_derived(trace,
|
|
derived_table->is_view() ? "view" : "derived");
|
|
trace_derived.add_utf8_table(derived_table).
|
|
add("select#", derived_select->select_number).
|
|
add("merged", true);
|
|
|
|
Prepared_stmt_arena_holder ps_arena_holder(thd);
|
|
|
|
// Save offset for table number adjustment
|
|
uint table_adjust= derived_table->tableno();
|
|
|
|
// Set up permanent list of underlying tables of a merged view
|
|
derived_table->merge_underlying_list= derived_select->get_table_list();
|
|
|
|
/**
|
|
A view is updatable if any underlying table is updatable.
|
|
A view is insertable-into if all underlying tables are insertable.
|
|
A view is not updatable nor insertable if it contains an outer join
|
|
@see mysql_register_view()
|
|
*/
|
|
if (derived_table->is_view())
|
|
{
|
|
bool updatable= false;
|
|
bool insertable= true;
|
|
bool outer_joined= false;
|
|
for (TABLE_LIST *tr= derived_table->merge_underlying_list;
|
|
tr;
|
|
tr= tr->next_local)
|
|
{
|
|
updatable|= tr->is_updatable();
|
|
insertable&= tr->is_insertable();
|
|
outer_joined|= tr->is_inner_table_of_outer_join();
|
|
}
|
|
updatable&= !outer_joined;
|
|
insertable&= !outer_joined;
|
|
if (updatable)
|
|
derived_table->set_updatable();
|
|
if (insertable)
|
|
derived_table->set_insertable();
|
|
}
|
|
// Add a nested join object to the derived table object
|
|
if (!(derived_table->nested_join=
|
|
(NESTED_JOIN *) thd->mem_calloc(sizeof(NESTED_JOIN))))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
derived_table->nested_join->join_list.empty();//Should be done by constructor!
|
|
|
|
// Merge tables from underlying query block into this join nest
|
|
if (derived_table->merge_underlying_tables(derived_select))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
// Replace derived table in leaf table list with underlying tables:
|
|
for (TABLE_LIST **tl= &leaf_tables; *tl; tl= &(*tl)->next_leaf)
|
|
{
|
|
if (*tl == derived_table)
|
|
{
|
|
for (TABLE_LIST *leaf= derived_select->leaf_tables; leaf;
|
|
leaf= leaf->next_leaf)
|
|
{
|
|
if (leaf->next_leaf == NULL)
|
|
{
|
|
leaf->next_leaf= (*tl)->next_leaf;
|
|
break;
|
|
}
|
|
}
|
|
*tl= derived_select->leaf_tables;
|
|
break;
|
|
}
|
|
}
|
|
|
|
leaf_table_count+= (derived_select->leaf_table_count - 1);
|
|
derived_table_count+= derived_select->derived_table_count;
|
|
materialized_derived_table_count+=
|
|
derived_select->materialized_derived_table_count;
|
|
has_sj_nests|= derived_select->has_sj_nests;
|
|
partitioned_table_count+= derived_select->partitioned_table_count;
|
|
cond_count+= derived_select->cond_count;
|
|
between_count+= derived_select->between_count;
|
|
|
|
// Propagate schema table indication:
|
|
// @todo: Add to BASE options instead
|
|
if (derived_select->active_options() & OPTION_SCHEMA_TABLE)
|
|
add_base_options(OPTION_SCHEMA_TABLE);
|
|
|
|
// Propagate nullability for derived tables within outer joins:
|
|
if (derived_table->is_inner_table_of_outer_join())
|
|
propagate_nullability(&derived_table->nested_join->join_list, true);
|
|
|
|
select_n_having_items+= derived_select->select_n_having_items;
|
|
|
|
// Merge the WHERE clause into the outer query block
|
|
if (derived_table->merge_where(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
if (derived_table->create_field_translation(thd))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
// Exclude the derived table query expression from query graph.
|
|
derived_unit->exclude_level();
|
|
|
|
// Don't try to access it:
|
|
derived_table->set_derived_unit((SELECT_LEX_UNIT *)1);
|
|
|
|
// Merge subquery's name resolution contexts into parent's
|
|
merge_contexts(derived_select);
|
|
|
|
repoint_contexts_of_join_nests(derived_select->top_join_list);
|
|
|
|
// Leaf tables have been shuffled, so update table numbers for them
|
|
remap_tables(thd);
|
|
|
|
// Update table info of referenced expressions after query block is merged
|
|
fix_tables_after_pullout(this, derived_select, derived_table, table_adjust);
|
|
|
|
if (derived_select->is_ordered())
|
|
{
|
|
/*
|
|
An ORDER BY clause is moved to an outer query block
|
|
- if the outer query block allows ordering, and
|
|
- that refers to this view/derived table only, and
|
|
- is not part of a UNION, and
|
|
- may have a WHERE clause but is not grouped or aggregated and is not
|
|
itself ordered.
|
|
Otherwise the ORDER BY clause is ignored.
|
|
|
|
Only SELECT statements and single-table UPDATE and DELETE statements
|
|
allow ordering.
|
|
|
|
Up to version 5.6 included, ORDER BY was unconditionally merged.
|
|
Currently we only merge in the simple case above, which ensures
|
|
backward compatibility for most reasonable use cases.
|
|
|
|
Note that table numbers in order_list do not need updating, since
|
|
the outer query contains only one table reference.
|
|
*/
|
|
// LIMIT currently blocks derived table merge
|
|
assert(!derived_select->has_limit());
|
|
|
|
if ((lex->sql_command == SQLCOM_SELECT ||
|
|
lex->sql_command == SQLCOM_UPDATE ||
|
|
lex->sql_command == SQLCOM_DELETE) &&
|
|
!(master_unit()->is_union() ||
|
|
is_grouped() ||
|
|
is_distinct() ||
|
|
is_ordered() ||
|
|
get_table_list()->next_local != NULL))
|
|
{
|
|
order_list.push_back(&derived_select->order_list);
|
|
/*
|
|
If at outer-most level (not within another derived table), ensure
|
|
the ordering columns are marked in read_set, since columns selected
|
|
from derived tables are not marked in initial resolving.
|
|
*/
|
|
if (!thd->derived_tables_processing)
|
|
{
|
|
Mark_field mf(thd->mark_used_columns);
|
|
for (ORDER *o = derived_select->order_list.first; o != NULL;
|
|
o = o->next)
|
|
o->item[0]->walk(&Item::mark_field_in_map, Item::WALK_POSTFIX,
|
|
pointer_cast<uchar *>(&mf));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
derived_select->empty_order_list(this);
|
|
trace_derived.add_alnum("transformations_to_derived_table",
|
|
"removed_ordering");
|
|
}
|
|
}
|
|
|
|
// Add any full-text functions from derived table into outer query
|
|
if (derived_select->ftfunc_list->elements &&
|
|
add_ftfunc_list(derived_select->ftfunc_list))
|
|
DBUG_RETURN(true); /* purecov: inspected */
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Destructively replaces a sub-condition inside a condition tree. The
|
|
parse tree is also altered.
|
|
|
|
@note Because of current requirements for semijoin flattening, we do not
|
|
need to recurse here, hence this function will only examine the top-level
|
|
AND conditions. (see SELECT_LEX::prepare, comment starting with "Check if
|
|
the subquery predicate can be executed via materialization".)
|
|
|
|
@param thd thread handler
|
|
|
|
@param tree Must be the handle to the top level condition. This is needed
|
|
when the top-level condition changes.
|
|
|
|
@param old_cond The condition to be replaced.
|
|
|
|
@param new_cond The condition to be substituted.
|
|
|
|
@param do_fix_fields If true, Item::fix_fields(THD*, Item**) is called for
|
|
the new condition.
|
|
|
|
@return error status
|
|
|
|
@retval true If there was an error.
|
|
@retval false If successful.
|
|
*/
|
|
|
|
static bool replace_subcondition(THD *thd, Item **tree,
|
|
Item *old_cond, Item *new_cond,
|
|
bool do_fix_fields)
|
|
{
|
|
if (*tree == old_cond)
|
|
{
|
|
*tree= new_cond;
|
|
if (do_fix_fields && new_cond->fix_fields(thd, tree))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
else if ((*tree)->type() == Item::COND_ITEM)
|
|
{
|
|
List_iterator<Item> li(*((Item_cond*)(*tree))->argument_list());
|
|
Item *item;
|
|
while ((item= li++))
|
|
{
|
|
if (item == old_cond)
|
|
{
|
|
li.replace(new_cond);
|
|
if (do_fix_fields && new_cond->fix_fields(thd, li.ref()))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
// If we came here it means there were an error during prerequisites check.
|
|
assert(FALSE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
Convert semi-join subquery predicates into semi-join join nests
|
|
|
|
DESCRIPTION
|
|
|
|
Convert candidate subquery predicates into semi-join join nests. This
|
|
transformation is performed once in query lifetime and is irreversible.
|
|
|
|
Conversion of one subquery predicate
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
We start with a join that has a semi-join subquery:
|
|
|
|
SELECT ...
|
|
FROM ot, ...
|
|
WHERE oe IN (SELECT ie FROM it1 ... itN WHERE subq_where) AND outer_where
|
|
|
|
and convert it into a semi-join nest:
|
|
|
|
SELECT ...
|
|
FROM ot SEMI JOIN (it1 ... itN), ...
|
|
WHERE outer_where AND subq_where AND oe=ie
|
|
|
|
that is, in order to do the conversion, we need to
|
|
|
|
* Create the "SEMI JOIN (it1 .. itN)" part and add it into the parent
|
|
query's FROM structure.
|
|
* Add "AND subq_where AND oe=ie" into parent query's WHERE (or ON if
|
|
the subquery predicate was in an ON expression)
|
|
* Remove the subquery predicate from the parent query's WHERE
|
|
|
|
Considerations when converting many predicates
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
A join may have at most MAX_TABLES tables. This may prevent us from
|
|
flattening all subqueries when the total number of tables in parent and
|
|
child selects exceeds MAX_TABLES. In addition, one slot is reserved per
|
|
semi-join nest, in case the subquery needs to be materialized in a
|
|
temporary table.
|
|
We deal with this problem by flattening children's subqueries first and
|
|
then using a heuristic rule to determine each subquery predicate's
|
|
"priority".
|
|
|
|
RETURN
|
|
FALSE OK
|
|
TRUE Error
|
|
*/
|
|
bool SELECT_LEX::flatten_subqueries()
|
|
{
|
|
DBUG_ENTER("flatten_subqueries");
|
|
|
|
if (sj_candidates->empty())
|
|
DBUG_RETURN(FALSE);
|
|
|
|
Item_exists_subselect **subq,
|
|
**subq_begin= sj_candidates->begin(),
|
|
**subq_end= sj_candidates->end();
|
|
|
|
THD *const thd= (*subq_begin)->unit->thd;
|
|
Opt_trace_context *const trace= &thd->opt_trace;
|
|
|
|
/*
|
|
Semijoin flattening is bottom-up. Indeed, we have this execution flow,
|
|
for SELECT#1 WHERE X IN (SELECT #2 WHERE Y IN (SELECT#3)) :
|
|
|
|
SELECT_LEX::prepare() (select#1)
|
|
-> fix_fields() on IN condition
|
|
-> SELECT_LEX::prepare() on subquery (select#2)
|
|
-> fix_fields() on IN condition
|
|
-> SELECT_LEX::prepare() on subquery (select#3)
|
|
<- SELECT_LEX::prepare()
|
|
<- fix_fields()
|
|
-> flatten_subqueries: merge #3 in #2
|
|
<- flatten_subqueries
|
|
<- SELECT_LEX::prepare()
|
|
<- fix_fields()
|
|
-> flatten_subqueries: merge #2 in #1
|
|
|
|
Note that flattening of #(N) is done by its parent JOIN#(N-1), because
|
|
there are cases where flattening is not possible and only the parent can
|
|
know.
|
|
*/
|
|
for (subq= subq_begin; subq < subq_end; subq++)
|
|
{
|
|
/*
|
|
Currently, we only support transformation of IN subqueries.
|
|
*/
|
|
assert((*subq)->substype() == Item_subselect::IN_SUBS);
|
|
|
|
st_select_lex *child_select= (*subq)->unit->first_select();
|
|
|
|
// Check that we proceeded bottom-up
|
|
assert(child_select->sj_candidates == NULL);
|
|
|
|
(*subq)->sj_convert_priority=
|
|
(((*subq)->unit->uncacheable & UNCACHEABLE_DEPENDENT) ? MAX_TABLES : 0) +
|
|
child_select->leaf_table_count;
|
|
}
|
|
|
|
/*
|
|
2. Pick which subqueries to convert:
|
|
sort the subquery array
|
|
- prefer correlated subqueries over uncorrelated;
|
|
- prefer subqueries that have greater number of outer tables;
|
|
*/
|
|
my_qsort(subq_begin,
|
|
sj_candidates->size(), sj_candidates->element_size(),
|
|
reinterpret_cast<qsort_cmp>(subq_sj_candidate_cmp));
|
|
|
|
// A permanent transformation is going to start, so:
|
|
Prepared_stmt_arena_holder ps_arena_holder(thd);
|
|
|
|
// #tables-in-parent-query + #tables-in-subquery + sj nests <= MAX_TABLES
|
|
/* Replace all subqueries to be flattened with Item_int(1) */
|
|
|
|
uint table_count= leaf_table_count;
|
|
for (subq= subq_begin; subq < subq_end; subq++)
|
|
{
|
|
// Add the tables in the subquery nest plus one in case of materialization:
|
|
const uint tables_added=
|
|
(*subq)->unit->first_select()->leaf_table_count + 1;
|
|
(*subq)->sj_chosen= table_count + tables_added <= MAX_TABLES;
|
|
|
|
if (!(*subq)->sj_chosen)
|
|
continue;
|
|
|
|
table_count+= tables_added;
|
|
|
|
// In WHERE/ON of parent query, replace IN(subq) with "1" (<=>TRUE)
|
|
Item **tree= ((*subq)->embedding_join_nest == NULL) ?
|
|
&m_where_cond :
|
|
(*subq)->embedding_join_nest->join_cond_ref();
|
|
if (replace_subcondition(thd, tree, *subq, new Item_int(1), FALSE))
|
|
DBUG_RETURN(TRUE); /* purecov: inspected */
|
|
}
|
|
|
|
for (subq= subq_begin; subq < subq_end; subq++)
|
|
{
|
|
if (!(*subq)->sj_chosen)
|
|
continue;
|
|
|
|
OPT_TRACE_TRANSFORM(trace, oto0, oto1,
|
|
(*subq)->unit->first_select()->select_number,
|
|
"IN (SELECT)", "semijoin");
|
|
oto1.add("chosen", true);
|
|
if (convert_subquery_to_semijoin(*subq))
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
/*
|
|
3. Finalize the subqueries that we did not convert,
|
|
ie. perform IN->EXISTS rewrite.
|
|
*/
|
|
for (subq= subq_begin; subq < subq_end; subq++)
|
|
{
|
|
if ((*subq)->sj_chosen)
|
|
continue;
|
|
{
|
|
OPT_TRACE_TRANSFORM(trace, oto0, oto1,
|
|
(*subq)->unit->first_select()->select_number,
|
|
"IN (SELECT)", "semijoin");
|
|
oto1.add("chosen", false);
|
|
}
|
|
Item_subselect::trans_res res;
|
|
(*subq)->changed= 0;
|
|
(*subq)->fixed= 0;
|
|
|
|
SELECT_LEX *save_select_lex= thd->lex->current_select();
|
|
thd->lex->set_current_select((*subq)->unit->first_select());
|
|
|
|
// This is the only part of the function which uses a JOIN.
|
|
res= (*subq)->select_transformer((*subq)->unit->first_select());
|
|
|
|
thd->lex->set_current_select(save_select_lex);
|
|
|
|
if (res == Item_subselect::RES_ERROR)
|
|
DBUG_RETURN(TRUE);
|
|
|
|
(*subq)->changed= 1;
|
|
(*subq)->fixed= 1;
|
|
|
|
Item *substitute= (*subq)->substitution;
|
|
const bool do_fix_fields= !(*subq)->substitution->fixed;
|
|
const bool subquery_in_join_clause= (*subq)->embedding_join_nest != NULL;
|
|
|
|
Item **tree= subquery_in_join_clause ?
|
|
((*subq)->embedding_join_nest->join_cond_ref()) : &m_where_cond;
|
|
if (replace_subcondition(thd, tree, *subq, substitute, do_fix_fields))
|
|
DBUG_RETURN(TRUE);
|
|
(*subq)->substitution= NULL;
|
|
}
|
|
|
|
sj_candidates->clear();
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
bool SELECT_LEX::is_in_select_list(Item *cand) {
|
|
List_iterator<Item> li(fields_list);
|
|
Item *item;
|
|
while ((item = li++)) {
|
|
// Use a walker to detect if cand is present in this select item
|
|
|
|
if (item->walk(&Item::find_item_processor, Item::WALK_SUBQUERY_POSTFIX,
|
|
pointer_cast<uchar *>(cand)))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
Propagate nullability into inner tables of outer join operation
|
|
|
|
@param tables: List of tables and join nests, start at top_join_list
|
|
@param nullable: true: Set all underlying tables as nullable
|
|
*/
|
|
static void propagate_nullability(List<TABLE_LIST> *tables, bool nullable)
|
|
{
|
|
List_iterator<TABLE_LIST> li(*tables);
|
|
TABLE_LIST *tr;
|
|
|
|
while ((tr= li++))
|
|
{
|
|
if (tr->table && !tr->table->is_nullable() && (nullable || tr->outer_join))
|
|
tr->table->set_nullable();
|
|
if (tr->nested_join == NULL)
|
|
continue;
|
|
propagate_nullability(&tr->nested_join->join_list,
|
|
nullable || tr->outer_join);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Propagate exclusion from unique table check into all subqueries belonging
|
|
to this query block.
|
|
|
|
This function can be applied to all subqueries of a materialized derived
|
|
table or view.
|
|
*/
|
|
|
|
void st_select_lex::propagate_unique_test_exclusion()
|
|
{
|
|
for (SELECT_LEX_UNIT *unit= first_inner_unit(); unit; unit= unit->next_unit())
|
|
for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
|
|
sl->propagate_unique_test_exclusion();
|
|
|
|
exclude_from_table_unique_test= true;
|
|
}
|
|
|
|
|
|
/**
|
|
Add a list of full-text function elements into a query block.
|
|
|
|
@param ftfuncs List of full-text function elements to add.
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::add_ftfunc_list(List<Item_func_match> *ftfuncs)
|
|
{
|
|
Item_func_match *ifm;
|
|
List_iterator_fast<Item_func_match> li(*ftfuncs);
|
|
while ((ifm= li++))
|
|
{
|
|
if (ftfunc_list->push_back(ifm))
|
|
return true; /* purecov: inspected */
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Go through a list of tables and join nests, recursively, and repoint
|
|
its select_lex pointer.
|
|
|
|
@param join_list List of tables and join nests
|
|
*/
|
|
void SELECT_LEX::repoint_contexts_of_join_nests(List<TABLE_LIST> join_list)
|
|
{
|
|
List_iterator_fast<TABLE_LIST> ti(join_list);
|
|
TABLE_LIST *tbl;
|
|
while ((tbl= ti++))
|
|
{
|
|
tbl->select_lex= this;
|
|
if (tbl->nested_join)
|
|
repoint_contexts_of_join_nests(tbl->nested_join->join_list);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Merge name resolution context objects belonging to an inner subquery
|
|
to parent query block.
|
|
Update all context objects to have this base query block.
|
|
Used when a subquery's query block is merged into its parent.
|
|
|
|
@param inner Subquery for which context objects are to be merged.
|
|
*/
|
|
void SELECT_LEX::merge_contexts(SELECT_LEX *inner)
|
|
{
|
|
for (Name_resolution_context *ctx= inner->first_context;
|
|
ctx != NULL;
|
|
ctx= ctx->next_context)
|
|
{
|
|
ctx->select_lex= this;
|
|
if (ctx->next_context == NULL)
|
|
{
|
|
ctx->next_context= first_context;
|
|
first_context= inner->first_context;
|
|
inner->first_context= NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Fix fields referenced from inner query blocks.
|
|
|
|
@param thd Thread handle
|
|
|
|
@details
|
|
The function serves 3 purposes
|
|
|
|
- adds fields referenced from inner query blocks to the current select list
|
|
|
|
- Decides which class to use to reference the items (Item_ref or
|
|
Item_direct_ref)
|
|
|
|
- fixes references (Item_ref objects) to these fields.
|
|
|
|
If a field isn't already on the select list and the ref_ptrs array
|
|
is provided then it is added to the all_fields list and the pointer to
|
|
it is saved in the ref_ptrs array.
|
|
|
|
The class to access the outer field is determined by the following rules:
|
|
|
|
-#. If the outer field isn't used under an aggregate function then the
|
|
Item_ref class should be used.
|
|
|
|
-#. If the outer field is used under an aggregate function and this
|
|
function is, in turn, aggregated in the query block where the outer
|
|
field was resolved or some query nested therein, then the
|
|
Item_direct_ref class should be used. Also it should be used if we are
|
|
grouping by a subquery containing the outer field.
|
|
|
|
The resolution is done here and not at the fix_fields() stage as
|
|
it can be done only after aggregate functions are fixed and pulled up to
|
|
selects where they are to be aggregated.
|
|
|
|
When the class is chosen it substitutes the original field in the
|
|
Item_outer_ref object.
|
|
|
|
After this we proceed with fixing references (Item_outer_ref objects) to
|
|
this field from inner subqueries.
|
|
|
|
@return false if success, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::fix_inner_refs(THD *thd)
|
|
{
|
|
Item_outer_ref *ref;
|
|
|
|
List_iterator<Item_outer_ref> ref_it(inner_refs_list);
|
|
while ((ref= ref_it++))
|
|
{
|
|
bool direct_ref= false;
|
|
Item *item= ref->outer_ref;
|
|
Item **item_ref= ref->ref;
|
|
/*
|
|
TODO: this field item already might be present in the select list.
|
|
In this case instead of adding new field item we could use an
|
|
existing one. The change will lead to less operations for copying fields,
|
|
smaller temporary tables and less data passed through filesort.
|
|
*/
|
|
if (!ref_ptrs.is_null() && !ref->found_in_select_list)
|
|
{
|
|
/*
|
|
Add the field item to the select list of the current select.
|
|
If it's needed reset each Item_ref item that refers this field with
|
|
a new reference taken from ref_pointer_array.
|
|
*/
|
|
item_ref= add_hidden_item(item);
|
|
}
|
|
|
|
if (ref->in_sum_func)
|
|
{
|
|
if (ref->in_sum_func->nest_level > nest_level)
|
|
direct_ref= true;
|
|
else
|
|
{
|
|
for (Item_sum *sum_func= ref->in_sum_func;
|
|
sum_func && sum_func->aggr_level >= nest_level;
|
|
sum_func= sum_func->in_sum_func)
|
|
{
|
|
if (sum_func->aggr_level == nest_level)
|
|
{
|
|
direct_ref= true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
Check if GROUP BY item trees contain the outer ref:
|
|
in this case we have to use Item_direct_ref instead of Item_ref.
|
|
*/
|
|
for (ORDER *group= group_list.first; group; group= group->next)
|
|
{
|
|
if ((*group->item)->walk(&Item::find_item_processor, walk_subquery,
|
|
(uchar *) ref))
|
|
{
|
|
direct_ref= true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Item_ref *const new_ref= direct_ref ?
|
|
new Item_direct_ref(ref->context, item_ref, ref->table_name,
|
|
ref->field_name, ref->is_alias_of_expr()) :
|
|
new Item_ref(ref->context, item_ref, ref->table_name,
|
|
ref->field_name, ref->is_alias_of_expr());
|
|
if (!new_ref)
|
|
return true; /* purecov: inspected */
|
|
ref->outer_ref= new_ref;
|
|
ref->ref= &ref->outer_ref;
|
|
|
|
if (!ref->fixed && ref->fix_fields(thd, 0))
|
|
return true; /* purecov: inspected */
|
|
thd->lex->used_tables|= item->used_tables();
|
|
select_list_tables|= item->used_tables();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Since LIMIT is not supported for table subquery predicates
|
|
(IN/ALL/EXISTS/etc), the following clauses are redundant for
|
|
subqueries:
|
|
|
|
ORDER BY
|
|
DISTINCT
|
|
GROUP BY if there are no aggregate functions and no HAVING clause
|
|
|
|
This removal is permanent. Thus, it only makes sense to call this function
|
|
for regular queries and on first execution of SP/PS
|
|
|
|
@param thd thread handler
|
|
@param hidden_group_field_count Number of hidden group fields added
|
|
by setup_group().
|
|
*/
|
|
|
|
|
|
void SELECT_LEX::remove_redundant_subquery_clauses(THD *thd,
|
|
int hidden_group_field_count)
|
|
{
|
|
Item_subselect *subq_predicate= master_unit()->item;
|
|
/*
|
|
The removal should happen for IN, ALL, ANY and EXISTS subqueries,
|
|
which means all but single row subqueries. Example single row
|
|
subqueries:
|
|
a) SELECT * FROM t1 WHERE t1.a = (<single row subquery>)
|
|
b) SELECT a, (<single row subquery) FROM t1
|
|
*/
|
|
if (subq_predicate->substype() == Item_subselect::SINGLEROW_SUBS)
|
|
return;
|
|
|
|
// A subquery that is not single row should be one of IN/ALL/ANY/EXISTS.
|
|
assert (subq_predicate->substype() == Item_subselect::EXISTS_SUBS ||
|
|
subq_predicate->substype() == Item_subselect::IN_SUBS ||
|
|
subq_predicate->substype() == Item_subselect::ALL_SUBS ||
|
|
subq_predicate->substype() == Item_subselect::ANY_SUBS);
|
|
|
|
enum change
|
|
{
|
|
REMOVE_NONE=0,
|
|
REMOVE_ORDER= 1 << 0,
|
|
REMOVE_DISTINCT= 1 << 1,
|
|
REMOVE_GROUP= 1 << 2
|
|
};
|
|
|
|
uint changelog= 0;
|
|
|
|
if (order_list.elements)
|
|
{
|
|
changelog|= REMOVE_ORDER;
|
|
empty_order_list(this);
|
|
}
|
|
|
|
if (is_distinct())
|
|
{
|
|
changelog|= REMOVE_DISTINCT;
|
|
remove_base_options(SELECT_DISTINCT);
|
|
}
|
|
|
|
// Remove GROUP BY if there are no aggregate functions and no HAVING clause
|
|
|
|
if (group_list.elements && !agg_func_used() && !having_cond())
|
|
{
|
|
changelog|= REMOVE_GROUP;
|
|
for (ORDER *g= group_list.first; g != NULL; g= g->next)
|
|
{
|
|
if (*g->item == g->item_ptr)
|
|
(*g->item)->walk(&Item::clean_up_after_removal, walk_subquery,
|
|
reinterpret_cast<uchar*>(this));
|
|
}
|
|
group_list.empty();
|
|
while (hidden_group_field_count-- > 0)
|
|
{
|
|
all_fields.pop();
|
|
ref_ptrs[all_fields.elements]= NULL;
|
|
}
|
|
}
|
|
|
|
if (changelog)
|
|
{
|
|
Opt_trace_context * trace= &thd->opt_trace;
|
|
if (unlikely(trace->is_started()))
|
|
{
|
|
Opt_trace_object trace_wrapper(trace);
|
|
Opt_trace_array trace_changes(trace, "transformations_to_subquery");
|
|
if (changelog & REMOVE_ORDER)
|
|
trace_changes.add_alnum("removed_ordering");
|
|
if (changelog & REMOVE_DISTINCT)
|
|
trace_changes.add_alnum("removed_distinct");
|
|
if (changelog & REMOVE_GROUP)
|
|
trace_changes.add_alnum("removed_grouping");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Empty the ORDER list.
|
|
Delete corresponding elements from all_fields and ref_ptrs too.
|
|
If ORDER list contain any subqueries, delete them from the query block list.
|
|
|
|
@param sl Query block that possible subquery blocks in the ORDER BY clause
|
|
are attached to (may be different from "this" when query block has
|
|
been merged into an outer query block).
|
|
*/
|
|
|
|
void SELECT_LEX::empty_order_list(SELECT_LEX *sl)
|
|
{
|
|
for (ORDER *o= order_list.first; o != NULL; o= o->next)
|
|
{
|
|
/*
|
|
Do not remove the order by expression if it has a view
|
|
reference. There is possibility that this view reference could
|
|
be used elsewhere in the query
|
|
*/
|
|
if (*o->item == o->item_ptr &&
|
|
(!o->item_ptr->has_subquery() ||
|
|
!o->item_ptr->walk(&Item::is_direct_view_ref,
|
|
Item::WALK_SUBQUERY_PREFIX, NULL))) {
|
|
(*o->item)->walk(&Item::clean_up_after_removal, walk_subquery,
|
|
pointer_cast<uchar *>(sl));
|
|
}
|
|
}
|
|
order_list.empty();
|
|
while (hidden_order_field_count-- > 0)
|
|
{
|
|
all_fields.pop();
|
|
ref_ptrs[all_fields.elements]= NULL;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Group and order functions
|
|
*****************************************************************************/
|
|
|
|
/**
|
|
Resolve an ORDER BY or GROUP BY column reference.
|
|
|
|
Given a column reference (represented by 'order') from a GROUP BY or ORDER
|
|
BY clause, find the actual column it represents. If the column being
|
|
resolved is from the GROUP BY clause, the procedure searches the SELECT
|
|
list 'fields' and the columns in the FROM list 'tables'. If 'order' is from
|
|
the ORDER BY clause, only the SELECT list is being searched.
|
|
|
|
If 'order' is resolved to an Item, then order->item is set to the found
|
|
Item. If there is no item for the found column (that is, it was resolved
|
|
into a table field), order->item is 'fixed' and is added to all_fields and
|
|
ref_pointer_array.
|
|
|
|
ref_pointer_array and all_fields are updated.
|
|
|
|
@param[in] thd Pointer to current thread structure
|
|
@param[in,out] ref_pointer_array All select, group and order by fields
|
|
@param[in] tables List of tables to search in (usually
|
|
FROM clause)
|
|
@param[in] order Column reference to be resolved
|
|
@param[in] fields List of fields to search in (usually
|
|
SELECT list)
|
|
@param[in,out] all_fields All select, group and order by fields
|
|
@param[in] is_group_field True if order is a GROUP field, false if
|
|
ORDER by field
|
|
|
|
@retval
|
|
FALSE if OK
|
|
@retval
|
|
TRUE if error occurred
|
|
*/
|
|
|
|
static bool
|
|
find_order_in_list(THD *thd, Ref_ptr_array ref_pointer_array,
|
|
TABLE_LIST *tables, ORDER *order,
|
|
List<Item> &fields, List<Item> &all_fields,
|
|
bool is_group_field)
|
|
{
|
|
Item *order_item= *order->item; /* The item from the GROUP/ORDER caluse. */
|
|
Item::Type order_item_type;
|
|
Item **select_item; /* The corresponding item from the SELECT clause. */
|
|
Field *from_field; /* The corresponding field from the FROM clause. */
|
|
uint counter;
|
|
enum_resolution_type resolution;
|
|
|
|
/*
|
|
Local SP variables may be int but are expressions, not positions.
|
|
(And they can't be used before fix_fields is called for them).
|
|
*/
|
|
if (order_item->type() == Item::INT_ITEM && order_item->basic_const_item())
|
|
{ /* Order by position */
|
|
uint count= (uint) order_item->val_int();
|
|
if (!count || count > fields.elements)
|
|
{
|
|
my_error(ER_BAD_FIELD_ERROR, MYF(0),
|
|
order_item->full_name(), thd->where);
|
|
return TRUE;
|
|
}
|
|
order->item= &ref_pointer_array[count - 1];
|
|
order->in_field_list= 1;
|
|
order->is_position= true;
|
|
return FALSE;
|
|
}
|
|
/* Lookup the current GROUP/ORDER field in the SELECT clause. */
|
|
select_item= find_item_in_list(order_item, fields, &counter,
|
|
REPORT_EXCEPT_NOT_FOUND, &resolution);
|
|
if (!select_item)
|
|
return TRUE; /* The item is not unique, or some other error occured. */
|
|
|
|
|
|
/* Check whether the resolved field is not ambiguos. */
|
|
if (select_item != not_found_item)
|
|
{
|
|
Item *view_ref= NULL;
|
|
/*
|
|
If we have found field not by its alias in select list but by its
|
|
original field name, we should additionally check if we have conflict
|
|
for this name (in case if we would perform lookup in all tables).
|
|
*/
|
|
if (resolution == RESOLVED_BEHIND_ALIAS && !order_item->fixed &&
|
|
order_item->fix_fields(thd, order->item))
|
|
return TRUE;
|
|
|
|
/* Lookup the current GROUP field in the FROM clause. */
|
|
order_item_type= order_item->type();
|
|
from_field= not_found_field;
|
|
if ((is_group_field &&
|
|
order_item_type == Item::FIELD_ITEM) ||
|
|
order_item_type == Item::REF_ITEM)
|
|
{
|
|
from_field= find_field_in_tables(thd, (Item_ident*) order_item, tables,
|
|
NULL, &view_ref, IGNORE_ERRORS, TRUE,
|
|
FALSE);
|
|
if (thd->is_error())
|
|
return true;
|
|
|
|
if (!from_field)
|
|
from_field= not_found_field;
|
|
}
|
|
|
|
if (from_field == not_found_field ||
|
|
(from_field != view_ref_found ?
|
|
/* it is field of base table => check that fields are same */
|
|
((*select_item)->type() == Item::FIELD_ITEM &&
|
|
((Item_field*) (*select_item))->field->eq(from_field)) :
|
|
/*
|
|
in is field of view table => check that references on translation
|
|
table are same
|
|
*/
|
|
((*select_item)->type() == Item::REF_ITEM &&
|
|
view_ref->type() == Item::REF_ITEM &&
|
|
((Item_ref *) (*select_item))->ref ==
|
|
((Item_ref *) view_ref)->ref)))
|
|
{
|
|
/*
|
|
If there is no such field in the FROM clause, or it is the same field
|
|
as the one found in the SELECT clause, then use the Item created for
|
|
the SELECT field. As a result if there was a derived field that
|
|
'shadowed' a table field with the same name, the table field will be
|
|
chosen over the derived field.
|
|
|
|
If we replace *order->item with one from the select list or
|
|
from a table in the FROM list, we should clean up after
|
|
removing the old *order->item from the query. The item has not
|
|
been fixed (so there are no aggregation functions that need
|
|
cleaning up), but it may contain subqueries that should be
|
|
unlinked.
|
|
*/
|
|
if (*order->item != *select_item)
|
|
(*order->item)->walk(&Item::clean_up_after_removal, walk_subquery,
|
|
NULL);
|
|
order->item= &ref_pointer_array[counter];
|
|
order->in_field_list=1;
|
|
if (resolution == RESOLVED_AGAINST_ALIAS)
|
|
order->used_alias= true;
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
There is a field with the same name in the FROM clause. This
|
|
is the field that will be chosen. In this case we issue a
|
|
warning so the user knows that the field from the FROM clause
|
|
overshadows the column reference from the SELECT list.
|
|
*/
|
|
push_warning_printf(thd, Sql_condition::SL_WARNING, ER_NON_UNIQ_ERROR,
|
|
ER(ER_NON_UNIQ_ERROR),
|
|
((Item_ident*) order_item)->field_name,
|
|
current_thd->where);
|
|
}
|
|
}
|
|
|
|
order->in_field_list=0;
|
|
/*
|
|
The call to order_item->fix_fields() means that here we resolve
|
|
'order_item' to a column from a table in the list 'tables', or to
|
|
a column in some outer query. Exactly because of the second case
|
|
we come to this point even if (select_item == not_found_item),
|
|
inspite of that fix_fields() calls find_item_in_list() one more
|
|
time.
|
|
|
|
We check order_item->fixed because Item_func_group_concat can put
|
|
arguments for which fix_fields already was called.
|
|
|
|
group_fix_field= TRUE is to resolve aliases from the SELECT list
|
|
without creating of Item_ref-s: JOIN::exec() wraps aliased items
|
|
in SELECT list with Item_copy items. To re-evaluate such a tree
|
|
that includes Item_copy items we have to refresh Item_copy caches,
|
|
but:
|
|
- filesort() never refresh Item_copy items,
|
|
- end_send_group() checks every record for group boundary by the
|
|
test_if_group_changed function that obtain data from these
|
|
Item_copy items, but the copy_fields function that
|
|
refreshes Item copy items is called after group boundaries only -
|
|
that is a vicious circle.
|
|
So we prevent inclusion of Item_copy items.
|
|
*/
|
|
bool save_group_fix_field= thd->lex->current_select()->group_fix_field;
|
|
if (is_group_field)
|
|
thd->lex->current_select()->group_fix_field= TRUE;
|
|
bool ret= (!order_item->fixed &&
|
|
(order_item->fix_fields(thd, order->item) ||
|
|
(order_item= *order->item)->check_cols(1)));
|
|
thd->lex->current_select()->group_fix_field= save_group_fix_field;
|
|
if (ret)
|
|
return TRUE; /* Wrong field. */
|
|
|
|
uint el= all_fields.elements;
|
|
all_fields.push_front(order_item); /* Add new field to field list. */
|
|
ref_pointer_array[el]= order_item;
|
|
/*
|
|
If the order_item is a SUM_FUNC_ITEM, when fix_fields is called
|
|
ref_by is set to order->item which is the address of order_item.
|
|
But this needs to be address of order_item in the all_fields list.
|
|
As a result, when it gets replaced with Item_aggregate_ref
|
|
object in Item::split_sum_func2, we will be able to retrieve the
|
|
newly created object.
|
|
*/
|
|
if (order_item->type() == Item::SUM_FUNC_ITEM)
|
|
((Item_sum *)order_item)->ref_by= all_fields.head_ref();
|
|
|
|
/*
|
|
Currently, we assume that this assertion holds. If it turns out
|
|
that it fails for some query, order->item has changed and the old
|
|
item is removed from the query. In that case, we must call walk()
|
|
with clean_up_after_removal() on the old order->item.
|
|
*/
|
|
assert(order_item == *order->item);
|
|
order->item= &ref_pointer_array[el];
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
Resolve and setup list of expressions in ORDER BY clause.
|
|
|
|
Change order to point at item in select list.
|
|
If item isn't a number and doesn't exists in the select list, add it to the
|
|
the field list.
|
|
|
|
@param thd Thread handler
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool setup_order(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
|
|
List<Item> &fields, List<Item> &all_fields, ORDER *order)
|
|
{
|
|
assert(order);
|
|
|
|
SELECT_LEX *const select= thd->lex->current_select();
|
|
|
|
thd->where="order clause";
|
|
|
|
const bool for_union= select->master_unit()->is_union() &&
|
|
select == select->master_unit()->fake_select_lex;
|
|
const bool is_aggregated= select->is_grouped();
|
|
|
|
for (uint number= 1; order; order=order->next, number++)
|
|
{
|
|
if (find_order_in_list(thd, ref_pointer_array, tables, order, fields,
|
|
all_fields, false))
|
|
return true;
|
|
if ((*order->item)->with_sum_func)
|
|
{
|
|
/*
|
|
Aggregated expressions in ORDER BY are not supported by SQL standard,
|
|
but MySQL has some limited support for them. The limitations are
|
|
checked below:
|
|
|
|
1. A UNION query is not aggregated, so ordering by a set function
|
|
is always wrong.
|
|
*/
|
|
if (for_union)
|
|
{
|
|
my_error(ER_AGGREGATE_ORDER_FOR_UNION, MYF(0), number);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
2. A non-aggregated query combined with a set function in ORDER BY
|
|
that does not contain an outer reference is illegal, because it
|
|
would cause the query to become aggregated.
|
|
(Since is_aggregated is false, this expression would cause
|
|
agg_func_used() to become true).
|
|
*/
|
|
if (!is_aggregated && select->agg_func_used())
|
|
{
|
|
my_error(ER_AGGREGATE_ORDER_NON_AGG_QUERY, MYF(0), number);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Runs checks mandated by ONLY_FULL_GROUP_BY
|
|
|
|
@param thd THD pointer
|
|
|
|
@returns true if ONLY_FULL_GROUP_BY is violated.
|
|
*/
|
|
|
|
bool SELECT_LEX::check_only_full_group_by(THD *thd)
|
|
{
|
|
bool rc= false;
|
|
|
|
if (is_grouped())
|
|
{
|
|
MEM_ROOT root;
|
|
/*
|
|
"root" has very short lifetime, and should not consume much
|
|
=> not instrumented.
|
|
*/
|
|
init_sql_alloc(PSI_NOT_INSTRUMENTED, &root, MEM_ROOT_BLOCK_SIZE, 0);
|
|
{
|
|
Group_check gc(this, &root);
|
|
rc= gc.check_query(thd);
|
|
gc.to_opt_trace(thd);
|
|
} // scope, to let any destructor run before free_root().
|
|
free_root(&root, MYF(0));
|
|
}
|
|
|
|
if (!rc && is_distinct())
|
|
{
|
|
Distinct_check dc(this);
|
|
rc= dc.check_query(thd);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/**
|
|
Do final setup of ORDER BY clause, after the query block is fully resolved.
|
|
|
|
Check that ORDER BY clause is not redundant.
|
|
Split any aggregate functions.
|
|
|
|
@param thd Thread handler
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
bool SELECT_LEX::setup_order_final(THD *thd)
|
|
{
|
|
if (is_implicitly_grouped())
|
|
{
|
|
// Result will contain zero or one row - ordering is redundant
|
|
empty_order_list(this);
|
|
return false;
|
|
}
|
|
|
|
if ((master_unit()->is_union() || master_unit()->fake_select_lex) &&
|
|
this != master_unit()->fake_select_lex &&
|
|
!(braces && explicit_limit))
|
|
{
|
|
// Part of UNION which requires global ordering may skip local order
|
|
empty_order_list(this);
|
|
return false;
|
|
}
|
|
|
|
for (ORDER *ord= order_list.first; ord; ord= ord->next)
|
|
{
|
|
Item *const item= *ord->item;
|
|
|
|
if (item->with_sum_func && item->type() != Item::SUM_FUNC_ITEM)
|
|
{
|
|
item->split_sum_func(thd, ref_ptrs, all_fields);
|
|
if (thd->is_error())
|
|
return true; /* purecov: inspected */
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Resolve and set up the GROUP BY list.
|
|
|
|
@param thd Thread handler
|
|
|
|
@todo
|
|
change ER_WRONG_FIELD_WITH_GROUP to more detailed
|
|
ER_NON_GROUPING_FIELD_USED
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::setup_group(THD *thd)
|
|
{
|
|
assert(group_list.elements);
|
|
|
|
thd->where="group statement";
|
|
for (ORDER *group= group_list.first; group; group= group->next)
|
|
{
|
|
if (find_order_in_list(thd, ref_ptrs, get_table_list(), group, fields_list,
|
|
all_fields, true))
|
|
return true;
|
|
if ((*group->item)->with_sum_func)
|
|
{
|
|
my_error(ER_WRONG_GROUP_FIELD, MYF(0), (*group->item)->full_name());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/****************************************************************************
|
|
ROLLUP handling
|
|
****************************************************************************/
|
|
|
|
/**
|
|
Replace occurrences of group by fields in an expression by ref items.
|
|
|
|
The function replaces occurrences of group by fields in expr
|
|
by ref objects for these fields unless they are under aggregate
|
|
functions.
|
|
The function also corrects value of the the maybe_null attribute
|
|
for the items of all subexpressions containing group by fields.
|
|
|
|
@b EXAMPLES
|
|
@code
|
|
SELECT a+1 FROM t1 GROUP BY a WITH ROLLUP
|
|
SELECT SUM(a)+a FROM t1 GROUP BY a WITH ROLLUP
|
|
@endcode
|
|
|
|
@b IMPLEMENTATION
|
|
|
|
The function recursively traverses the tree of the expr expression,
|
|
looks for occurrences of the group by fields that are not under
|
|
aggregate functions and replaces them for the corresponding ref items.
|
|
|
|
@note
|
|
This substitution is needed GROUP BY queries with ROLLUP if
|
|
SELECT list contains expressions over group by attributes.
|
|
|
|
@param thd reference to the context
|
|
@param expr expression to make replacement
|
|
@param changed[out] returns true if item contains a replaced field item
|
|
|
|
@todo
|
|
- TODO: Some functions are not null-preserving. For those functions
|
|
updating of the maybe_null attribute is an overkill.
|
|
|
|
@returns false if success, true if error
|
|
|
|
*/
|
|
|
|
bool SELECT_LEX::change_group_ref(THD *thd, Item_func *expr, bool *changed)
|
|
{
|
|
bool arg_changed= false;
|
|
for (uint i= 0; i < expr->arg_count; i++)
|
|
{
|
|
Item **arg= expr->arguments() + i;
|
|
Item *const item= *arg;
|
|
if (item->type() == Item::FIELD_ITEM || item->type() == Item::REF_ITEM)
|
|
{
|
|
for (ORDER *group= group_list.first; group; group= group->next)
|
|
{
|
|
if (item->eq(*group->item, 0))
|
|
{
|
|
Item *new_item;
|
|
if (!(new_item= new Item_ref(&context, group->item, 0,
|
|
item->item_name.ptr())))
|
|
return true; /* purecov: inspected */
|
|
|
|
expr->replace_argument(thd, arg, new_item);
|
|
arg_changed= true;
|
|
}
|
|
}
|
|
}
|
|
else if (item->type() == Item::FUNC_ITEM)
|
|
{
|
|
if (change_group_ref(thd, (Item_func *) item, &arg_changed))
|
|
return true;
|
|
}
|
|
}
|
|
if (arg_changed)
|
|
{
|
|
expr->maybe_null= true;
|
|
*changed= true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Resolve items for rollup processing
|
|
|
|
@param thd Thread handler
|
|
|
|
@returns false if success, true if error
|
|
*/
|
|
|
|
bool SELECT_LEX::resolve_rollup(THD *thd)
|
|
{
|
|
List_iterator<Item> it(all_fields);
|
|
Item *item;
|
|
while ((item= it++))
|
|
{
|
|
bool found_in_group= false;
|
|
|
|
for (ORDER *group= group_list.first; group; group= group->next)
|
|
{
|
|
if (*group->item == item)
|
|
{
|
|
item->maybe_null= true;
|
|
found_in_group= true;
|
|
break;
|
|
}
|
|
}
|
|
if (item->type() == Item::FUNC_ITEM && !found_in_group)
|
|
{
|
|
bool changed= false;
|
|
if (change_group_ref(thd, (Item_func *) item, &changed))
|
|
return true; /* purecov: inspected */
|
|
/*
|
|
We have to prevent creation of a field in a temporary table for
|
|
an expression that contains GROUP BY attributes.
|
|
Marking the expression item as 'with_sum_func' will ensure this.
|
|
*/
|
|
if (changed)
|
|
item->with_sum_func= true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
@brief validate_gc_assignment
|
|
Check whether the other values except DEFAULT are assigned
|
|
for generated columns.
|
|
|
|
@param thd thread handler
|
|
@param fields Item_fields list to be filled
|
|
@param values values to fill with
|
|
@param table table to be checked
|
|
@return Operation status
|
|
@retval false OK
|
|
@retval true Error occured
|
|
|
|
@Note: This function must be called after table->write_set has been
|
|
filled.
|
|
*/
|
|
bool
|
|
validate_gc_assignment(THD * thd, List<Item> *fields,
|
|
List<Item> *values, TABLE *table)
|
|
{
|
|
Field **fld= NULL;
|
|
MY_BITMAP *bitmap= table->write_set;
|
|
bool use_table_field= false;
|
|
DBUG_ENTER("validate_gc_assignment");
|
|
|
|
if (!values || (values->elements == 0))
|
|
DBUG_RETURN(false);
|
|
|
|
// If fields has no elements, we use all table fields
|
|
if (fields->elements == 0)
|
|
{
|
|
use_table_field= true;
|
|
fld= table->field;
|
|
}
|
|
List_iterator_fast<Item> f(*fields),v(*values);
|
|
Item *value;
|
|
while ((value= v++))
|
|
{
|
|
Field *rfield;
|
|
|
|
if (!use_table_field)
|
|
rfield= (down_cast<Item_field*>((f++)->real_item()))->field;
|
|
else
|
|
rfield= *(fld++);
|
|
if (rfield->table != table)
|
|
continue;
|
|
/* skip non marked fields */
|
|
if (!bitmap_is_set(bitmap, rfield->field_index))
|
|
continue;
|
|
if (rfield->gcol_info &&
|
|
value->type() != Item::DEFAULT_VALUE_ITEM)
|
|
{
|
|
my_error(ER_NON_DEFAULT_VALUE_FOR_GENERATED_COLUMN, MYF(0),
|
|
rfield->field_name, rfield->table->s->table_name.str);
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Delete unused columns from merged tables.
|
|
|
|
This function is called recursively for each join nest and/or table
|
|
in the query block. For each merged table that it finds, each column
|
|
that contains a subquery and is not marked as used is removed and
|
|
the translation item is set to NULL.
|
|
|
|
@param tables List of tables and join nests
|
|
*/
|
|
|
|
void SELECT_LEX::delete_unused_merged_columns(List<TABLE_LIST> *tables)
|
|
{
|
|
DBUG_ENTER("delete_unused_merged_columns");
|
|
|
|
TABLE_LIST *tl;
|
|
List_iterator<TABLE_LIST> li(*tables);
|
|
while ((tl= li++))
|
|
{
|
|
if (tl->nested_join == NULL)
|
|
continue;
|
|
if (tl->is_merged())
|
|
{
|
|
for (Field_translator *transl= tl->field_translation;
|
|
transl < tl->field_translation_end;
|
|
transl++)
|
|
{
|
|
Item *const item= transl->item;
|
|
|
|
assert(item->fixed);
|
|
if (!item->has_subquery())
|
|
continue;
|
|
|
|
/*
|
|
All used columns selected from derived tables are already marked
|
|
as such. But unmarked columns may still refer to other columns
|
|
from underlying derived tables, and in that case we cannot
|
|
delete these columns as they share the same items.
|
|
Thus, dive into the expression and mark such columns as "used".
|
|
(This is a bit incorrect, as only a part of its underlying expression
|
|
is "used", but that has no practical meaning.)
|
|
*/
|
|
if (!item->is_derived_used() &&
|
|
item->walk(&Item::propagate_derived_used, Item::WALK_POSTFIX, NULL))
|
|
item->walk(&Item::propagate_set_derived_used,
|
|
Item::WALK_SUBQUERY_POSTFIX, NULL);
|
|
|
|
if (!item->is_derived_used())
|
|
{
|
|
item->walk(&Item::clean_up_after_removal, walk_subquery,
|
|
pointer_cast<uchar *>(this));
|
|
transl->item= NULL;
|
|
}
|
|
}
|
|
}
|
|
delete_unused_merged_columns(&tl->nested_join->join_list);
|
|
}
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
Add item to the hidden part of select list.
|
|
|
|
@param item item to add
|
|
|
|
@return Pointer to ref_ptr for the added item
|
|
*/
|
|
|
|
Item **SELECT_LEX::add_hidden_item(Item *item)
|
|
{
|
|
uint el= all_fields.elements;
|
|
ref_ptrs[el]= item;
|
|
all_fields.push_front(item);
|
|
return &ref_ptrs[el];
|
|
}
|
|
|
|
/**
|
|
@} (end of group Query_Resolver)
|
|
*/
|