mirror of
https://github.com/MariaDB/server.git
synced 2025-01-28 17:54:16 +01:00
be093c81a7
The ROWNUM() function is for SELECT mapped to JOIN->accepted_rows, which is incremented for each accepted rows. For Filesort, update, insert, delete and load data, we map ROWNUM() to internal variables incremented when the table is changed. The connection between the row counter and Item_func_rownum is done in sql_select.cc::fix_items_after_optimize() and sql_insert.cc::fix_rownum_pointers() When ROWNUM() is used anywhere in query, the optimization to ignore ORDER BY in sub queries are disabled. This was done to get the following common Oracle query to work: select * from (select * from t1 order by a desc) as t where rownum() <= 2; MDEV-3926 "Wrong result with GROUP BY ... WITH ROLLUP" contains a discussion about this topic. LIMIT optimization is enabled when in a top level WHERE clause comparing ROWNUM() with a numerical constant using any of the following expressions: - ROWNUM() < # - ROWNUM() <= # - ROWNUM() = 1 ROWNUM() can be also be the right argument to the comparison function. LIMIT optimization is done in two cases: - For the current sub query when the ROWNUM comparison is done on the top level: SELECT * from t1 WHERE rownum() <= 2 AND t1.a > 0 - For an inner sub query, when the upper level has only a ROWNUM comparison in the WHERE clause: SELECT * from (select * from t1) as t WHERE rownum() <= 2 In Oracle mode, one can also use ROWNUM without parentheses. Other things: - Fixed bug where the optimizer tries to optimize away sub queries with RAND_TABLE_BIT set (non-deterministic queries). Now these sub queries will not be converted to joins. This bug fix was also needed to get rownum() working inside subqueries. - In remove_const() remove setting simple_order to FALSE if ROLLUP is USED. This code was disable a long time ago because of wrong assignment in the following code. Instead we set simple_order to false if RAND_TABLE_BIT was used in the SELECT list. This ensures that we don't delete ORDER BY if the result set is not deterministic, like in 'SELECT RAND() AS 'r' FROM t1 ORDER BY r'; - Updated parameters for Sort_param::init_for_filesort() to be able to provide filesort with information where the number of accepted rows should be stored - Reordered fields in class Filesort to optimize storage layout - Added new error messsage to tell that a function can't be used in HAVING - Added field 'with_rownum' to THD to mark that ROWNUM() is used in the query. Co-author: Oleksandr Byelkin <sanja@mariadb.com> LIMIT optimization for sub query
1185 lines
32 KiB
C++
1185 lines
32 KiB
C++
/* Copyright (c) 2017, 2020, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
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 for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
|
|
|
|
#include "mariadb.h"
|
|
#include "sql_list.h"
|
|
#include "sql_tvc.h"
|
|
#include "sql_class.h"
|
|
#include "opt_range.h"
|
|
#include "sql_select.h"
|
|
#include "sql_explain.h"
|
|
#include "sql_parse.h"
|
|
#include "sql_cte.h"
|
|
#include "my_json_writer.h"
|
|
|
|
|
|
/**
|
|
@brief
|
|
Walk through all VALUES items.
|
|
@param
|
|
@param processor - the processor to call for each Item
|
|
@param walk_qubquery - if should dive into subquery items
|
|
@param argument - the argument to pass recursively
|
|
@retval
|
|
true on error
|
|
false on success
|
|
*/
|
|
bool table_value_constr::walk_values(Item_processor processor,
|
|
bool walk_subquery,
|
|
void *argument)
|
|
{
|
|
List_iterator_fast<List_item> list_item_it(lists_of_values);
|
|
while (List_item *list= list_item_it++)
|
|
{
|
|
List_iterator_fast<Item> item_it(*list);
|
|
while (Item *item= item_it++)
|
|
{
|
|
if (item->walk(&Item::unknown_splocal_processor, false, argument))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Fix fields for TVC values
|
|
|
|
@param
|
|
@param thd The context of the statement
|
|
@param li The iterator on the list of lists
|
|
|
|
@details
|
|
Call fix_fields procedure for TVC values.
|
|
|
|
@retval
|
|
true if an error was reported
|
|
false otherwise
|
|
*/
|
|
|
|
bool fix_fields_for_tvc(THD *thd, List_iterator_fast<List_item> &li)
|
|
{
|
|
DBUG_ENTER("fix_fields_for_tvc");
|
|
List_item *lst;
|
|
li.rewind();
|
|
|
|
while ((lst= li++))
|
|
{
|
|
List_iterator<Item> it(*lst);
|
|
Item *item;
|
|
|
|
while ((item= it++))
|
|
{
|
|
/*
|
|
Some items have already been fixed.
|
|
For example Item_splocal items get fixed in
|
|
Item_splocal::append_for_log(), which is called from subst_spvars()
|
|
while replacing their values to NAME_CONST()s.
|
|
So fix only those that have not been.
|
|
*/
|
|
if (item->fix_fields_if_needed_for_scalar(thd, it.ref()) ||
|
|
item->check_is_evaluable_expression_or_error())
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Defines types of matrix columns elements where matrix rows are defined by
|
|
some lists of values.
|
|
|
|
@param
|
|
@param thd The context of the statement
|
|
@param li The iterator on the list of lists
|
|
@param holders The structure where types of matrix columns are stored
|
|
@param first_list_el_count Count of the list values. It should be the same
|
|
for each list of lists elements. It contains
|
|
number of elements of the first list from list of
|
|
lists.
|
|
|
|
@details
|
|
For each list list_a from list of lists the procedure gets its elements
|
|
types and aggregates them with the previous ones stored in holders. If
|
|
list_a is the first one in the list of lists its elements types are put in
|
|
holders. The errors can be reported when count of list_a elements is
|
|
different from the first_list_el_count. Also error can be reported whe
|
|
n aggregation can't be made.
|
|
|
|
@retval
|
|
true if an error was reported
|
|
false otherwise
|
|
*/
|
|
|
|
bool join_type_handlers_for_tvc(THD *thd, List_iterator_fast<List_item> &li,
|
|
Type_holder *holders, uint first_list_el_count)
|
|
{
|
|
DBUG_ENTER("join_type_handlers_for_tvc");
|
|
List_item *lst;
|
|
li.rewind();
|
|
bool first= true;
|
|
|
|
while ((lst= li++))
|
|
{
|
|
List_iterator_fast<Item> it(*lst);
|
|
Item *item;
|
|
|
|
if (first_list_el_count != lst->elements)
|
|
{
|
|
my_message(ER_WRONG_NUMBER_OF_VALUES_IN_TVC,
|
|
ER_THD(thd, ER_WRONG_NUMBER_OF_VALUES_IN_TVC),
|
|
MYF(0));
|
|
DBUG_RETURN(true);
|
|
}
|
|
for (uint pos= 0; (item=it++); pos++)
|
|
{
|
|
const Type_handler *item_type_handler= item->real_type_handler();
|
|
if (first)
|
|
holders[pos].set_handler(item_type_handler);
|
|
else if (holders[pos].aggregate_for_result(item_type_handler))
|
|
{
|
|
my_error(ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION, MYF(0),
|
|
holders[pos].type_handler()->name().ptr(),
|
|
item_type_handler->name().ptr(),
|
|
"TABLE VALUE CONSTRUCTOR");
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
first= false;
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Define attributes of matrix columns elements where matrix rows are defined
|
|
by some lists of values.
|
|
|
|
@param
|
|
@param thd The context of the statement
|
|
@param li The iterator on the list of lists
|
|
@param holders The structure where names of matrix columns are stored
|
|
@param count_of_lists Count of list of lists elements
|
|
@param first_list_el_count Count of the list values. It should be the same
|
|
for each list of lists elements. It contains
|
|
number of elements of the first list from list
|
|
of lists.
|
|
|
|
@details
|
|
For each list list_a from list of lists the procedure gets its elements
|
|
attributes and aggregates them with the previous ones stored in holders.
|
|
The errors can be reported when aggregation can't be made.
|
|
|
|
@retval
|
|
true if an error was reported
|
|
false otherwise
|
|
*/
|
|
|
|
bool get_type_attributes_for_tvc(THD *thd,
|
|
List_iterator_fast<List_item> &li,
|
|
Type_holder *holders, uint count_of_lists,
|
|
uint first_list_el_count)
|
|
{
|
|
DBUG_ENTER("get_type_attributes_for_tvc");
|
|
List_item *lst;
|
|
li.rewind();
|
|
|
|
for (uint pos= 0; pos < first_list_el_count; pos++)
|
|
{
|
|
if (holders[pos].alloc_arguments(thd, count_of_lists))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
while ((lst= li++))
|
|
{
|
|
List_iterator_fast<Item> it(*lst);
|
|
Item *item;
|
|
for (uint holder_pos= 0 ; (item= it++); holder_pos++)
|
|
{
|
|
DBUG_ASSERT(item->fixed());
|
|
holders[holder_pos].add_argument(item);
|
|
}
|
|
}
|
|
|
|
for (uint pos= 0; pos < first_list_el_count; pos++)
|
|
{
|
|
if (holders[pos].aggregate_attributes(thd))
|
|
DBUG_RETURN(true);
|
|
}
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Prepare of TVC
|
|
|
|
@param
|
|
@param thd The context of the statement
|
|
@param sl The select where this TVC is defined
|
|
@param tmp_result Structure that contains the information
|
|
about where to send the result of the query
|
|
@param unit_arg The union where sl is defined
|
|
|
|
@details
|
|
Gets types and attributes of values of this TVC that will be used
|
|
for temporary table creation for this TVC. It creates Item_type_holders
|
|
for each element of the first list from list of lists (VALUES from tvc),
|
|
using its elements name, defined type and attribute.
|
|
|
|
@retval
|
|
true if an error was reported
|
|
false otherwise
|
|
*/
|
|
|
|
bool table_value_constr::prepare(THD *thd, SELECT_LEX *sl,
|
|
select_result *tmp_result,
|
|
st_select_lex_unit *unit_arg)
|
|
{
|
|
DBUG_ENTER("table_value_constr::prepare");
|
|
select_lex->in_tvc= true;
|
|
List_iterator_fast<List_item> li(lists_of_values);
|
|
|
|
List_item *first_elem= li++;
|
|
uint cnt= first_elem->elements;
|
|
Type_holder *holders;
|
|
|
|
if (cnt == 0)
|
|
{
|
|
my_error(ER_EMPTY_ROW_IN_TVC, MYF(0));
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (fix_fields_for_tvc(thd, li))
|
|
DBUG_RETURN(true);
|
|
|
|
if (!(holders= new (thd->stmt_arena->mem_root) Type_holder[cnt]) ||
|
|
join_type_handlers_for_tvc(thd, li, holders, cnt) ||
|
|
get_type_attributes_for_tvc(thd, li, holders,
|
|
lists_of_values.elements, cnt))
|
|
DBUG_RETURN(true);
|
|
|
|
List_iterator_fast<Item> it(*first_elem);
|
|
Item *item;
|
|
Query_arena *arena, backup;
|
|
arena=thd->activate_stmt_arena_if_needed(&backup);
|
|
|
|
sl->item_list.empty();
|
|
for (uint pos= 0; (item= it++); pos++)
|
|
{
|
|
/* Error's in 'new' will be detected after loop */
|
|
Item_type_holder *new_holder= new (thd->mem_root)
|
|
Item_type_holder(thd, item, holders[pos].type_handler(),
|
|
&holders[pos]/*Type_all_attributes*/,
|
|
holders[pos].get_maybe_null());
|
|
sl->item_list.push_back(new_holder);
|
|
}
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
|
|
if (unlikely(thd->is_fatal_error))
|
|
DBUG_RETURN(true); // out of memory
|
|
|
|
result= tmp_result;
|
|
|
|
if (result && result->prepare(sl->item_list, unit_arg))
|
|
DBUG_RETURN(true);
|
|
|
|
/*
|
|
setup_order() for a TVC is not called when the following is true
|
|
(thd->lex->context_analysis_only & CONTEXT_ANALYSIS_ONLY_VIEW)
|
|
*/
|
|
|
|
thd->where="order clause";
|
|
ORDER *order= sl->order_list.first;
|
|
for (; order; order=order->next)
|
|
{
|
|
Item *order_item= *order->item;
|
|
if (order_item->is_order_clause_position())
|
|
{
|
|
uint count= 0;
|
|
if (order->counter_used)
|
|
count= order->counter; // counter was once resolved
|
|
else
|
|
count= (uint) order_item->val_int();
|
|
if (!count || count > first_elem->elements)
|
|
{
|
|
my_error(ER_BAD_FIELD_ERROR, MYF(0),
|
|
order_item->full_name(), thd->where);
|
|
DBUG_RETURN(true);
|
|
}
|
|
order->in_field_list= 1;
|
|
order->counter= count;
|
|
order->counter_used= 1;
|
|
}
|
|
}
|
|
|
|
select_lex->in_tvc= false;
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
Save Query Plan Footprint
|
|
*/
|
|
|
|
int table_value_constr::save_explain_data_intern(THD *thd,
|
|
Explain_query *output)
|
|
{
|
|
const char *message= "No tables used";
|
|
DBUG_ENTER("table_value_constr::save_explain_data_intern");
|
|
DBUG_PRINT("info", ("Select %p, type %s, message %s",
|
|
select_lex, select_lex->type,
|
|
message));
|
|
DBUG_ASSERT(have_query_plan == QEP_AVAILABLE);
|
|
|
|
/* There should be no attempts to save query plans for merged selects */
|
|
DBUG_ASSERT(!select_lex->master_unit()->derived ||
|
|
select_lex->master_unit()->derived->is_materialized_derived() ||
|
|
select_lex->master_unit()->derived->is_with_table());
|
|
|
|
explain= new (output->mem_root) Explain_select(output->mem_root,
|
|
thd->lex->analyze_stmt);
|
|
if (!explain)
|
|
DBUG_RETURN(1);
|
|
|
|
select_lex->set_explain_type(true);
|
|
|
|
explain->select_id= select_lex->select_number;
|
|
explain->select_type= select_lex->type;
|
|
explain->linkage= select_lex->get_linkage();
|
|
explain->using_temporary= false;
|
|
explain->using_filesort= false;
|
|
/* Setting explain->message means that all other members are invalid */
|
|
explain->message= message;
|
|
|
|
if (select_lex->master_unit()->derived)
|
|
explain->connection_type= Explain_node::EXPLAIN_NODE_DERIVED;
|
|
|
|
for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
|
|
unit;
|
|
unit= unit->next_unit())
|
|
{
|
|
explain->add_child(unit->first_select()->select_number);
|
|
}
|
|
|
|
output->add_node(explain);
|
|
|
|
if (select_lex->is_top_level_node())
|
|
output->query_plan_ready();
|
|
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
/**
|
|
Optimization of TVC
|
|
*/
|
|
|
|
bool table_value_constr::optimize(THD *thd)
|
|
{
|
|
create_explain_query_if_not_exists(thd->lex, thd->mem_root);
|
|
have_query_plan= QEP_AVAILABLE;
|
|
|
|
if (select_lex->select_number != FAKE_SELECT_LEX_ID &&
|
|
have_query_plan != QEP_NOT_PRESENT_YET &&
|
|
thd->lex->explain && // for "SET" command in SPs.
|
|
(!thd->lex->explain->get_select(select_lex->select_number)))
|
|
{
|
|
if (save_explain_data_intern(thd, thd->lex->explain))
|
|
return true;
|
|
}
|
|
|
|
if (select_lex->optimize_unflattened_subqueries(true))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
Execute of TVC
|
|
*/
|
|
|
|
bool table_value_constr::exec(SELECT_LEX *sl)
|
|
{
|
|
DBUG_ENTER("table_value_constr::exec");
|
|
List_iterator_fast<List_item> li(lists_of_values);
|
|
List_item *elem;
|
|
ha_rows send_records= 0;
|
|
|
|
if (select_options & SELECT_DESCRIBE)
|
|
DBUG_RETURN(false);
|
|
|
|
if (result->send_result_set_metadata(sl->item_list,
|
|
Protocol::SEND_NUM_ROWS |
|
|
Protocol::SEND_EOF))
|
|
{
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
fix_rownum_pointers(sl->parent_lex->thd, sl, &send_records);
|
|
|
|
while ((elem= li++))
|
|
{
|
|
if (send_records >= sl->master_unit()->lim.get_select_limit())
|
|
break;
|
|
int rc=
|
|
result->send_data_with_check(*elem, sl->master_unit(), send_records);
|
|
if (!rc)
|
|
send_records++;
|
|
else if (rc > 0)
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (result->send_eof())
|
|
DBUG_RETURN(true);
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Print list
|
|
|
|
@param str The reference on the string representation of the list
|
|
@param list The list that needed to be print
|
|
@param query_type The mode of printing
|
|
|
|
@details
|
|
The method saves a string representation of list in the
|
|
string str.
|
|
*/
|
|
|
|
void print_list_item(String *str, List_item *list,
|
|
enum_query_type query_type)
|
|
{
|
|
bool is_first_elem= true;
|
|
List_iterator_fast<Item> it(*list);
|
|
Item *item;
|
|
|
|
str->append('(');
|
|
|
|
while ((item= it++))
|
|
{
|
|
if (is_first_elem)
|
|
is_first_elem= false;
|
|
else
|
|
str->append(',');
|
|
|
|
item->print(str, query_type);
|
|
}
|
|
|
|
str->append(')');
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Print this TVC
|
|
|
|
@param thd The context of the statement
|
|
@param str The reference on the string representation of this TVC
|
|
@param query_type The mode of printing
|
|
|
|
@details
|
|
The method saves a string representation of this TVC in the
|
|
string str.
|
|
*/
|
|
|
|
void table_value_constr::print(THD *thd, String *str,
|
|
enum_query_type query_type)
|
|
{
|
|
DBUG_ASSERT(thd);
|
|
|
|
str->append(STRING_WITH_LEN("values "));
|
|
|
|
bool is_first_elem= true;
|
|
List_iterator_fast<List_item> li(lists_of_values);
|
|
List_item *list;
|
|
|
|
while ((list= li++))
|
|
{
|
|
if (is_first_elem)
|
|
is_first_elem= false;
|
|
else
|
|
str->append(',');
|
|
|
|
print_list_item(str, list, query_type);
|
|
}
|
|
if (select_lex->order_list.elements)
|
|
{
|
|
str->append(STRING_WITH_LEN(" order by "));
|
|
select_lex->print_order(str, select_lex->order_list.first, query_type);
|
|
}
|
|
select_lex->print_limit(thd, str, query_type);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Create list of lists for TVC from the list of this IN predicate
|
|
|
|
@param thd The context of the statement
|
|
@param values TVC list of values
|
|
|
|
@details
|
|
The method uses the list of values of this IN predicate to build
|
|
an equivalent list of values that can be used in TVC.
|
|
|
|
E.g.:
|
|
|
|
<value_list> = 5,2,7
|
|
<transformed_value_list> = (5),(2),(7)
|
|
|
|
<value_list> = (5,2),(7,1)
|
|
<transformed_value_list> = (5,2),(7,1)
|
|
|
|
@retval
|
|
false if the method succeeds
|
|
true otherwise
|
|
*/
|
|
|
|
bool Item_func_in::create_value_list_for_tvc(THD *thd,
|
|
List< List<Item> > *values)
|
|
{
|
|
bool is_list_of_rows= args[1]->type() == Item::ROW_ITEM;
|
|
|
|
for (uint i=1; i < arg_count; i++)
|
|
{
|
|
char col_name[8];
|
|
List<Item> *tvc_value;
|
|
if (!(tvc_value= new (thd->mem_root) List<Item>()))
|
|
return true;
|
|
|
|
if (is_list_of_rows)
|
|
{
|
|
Item_row *row_list= (Item_row *)(args[i]);
|
|
|
|
for (uint j=0; j < row_list->cols(); j++)
|
|
{
|
|
if (i == 1)
|
|
{
|
|
sprintf(col_name, "_col_%i", j+1);
|
|
row_list->element_index(j)->set_name(thd, col_name, strlen(col_name),
|
|
thd->charset());
|
|
}
|
|
if (tvc_value->push_back(row_list->element_index(j),
|
|
thd->mem_root))
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (i == 1)
|
|
{
|
|
sprintf(col_name, "_col_%i", 1);
|
|
args[i]->set_name(thd, col_name, strlen(col_name), thd->charset());
|
|
}
|
|
if (tvc_value->push_back(args[i]->real_item()))
|
|
return true;
|
|
}
|
|
|
|
if (values->push_back(tvc_value, thd->mem_root))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Create name for the derived table defined by TVC
|
|
|
|
@param thd The context of the statement
|
|
@param parent_select The SELECT where derived table is used
|
|
@param alias The returned created name
|
|
|
|
@details
|
|
Create name for the derived table using current TVC number
|
|
for this parent_select stored in parent_select
|
|
|
|
@retval
|
|
true if creation fails
|
|
false otherwise
|
|
*/
|
|
|
|
static bool create_tvc_name(THD *thd, st_select_lex *parent_select,
|
|
LEX_CSTRING *alias)
|
|
{
|
|
char buff[6];
|
|
|
|
alias->length= my_snprintf(buff, sizeof(buff),
|
|
"tvc_%u",
|
|
parent_select ? parent_select->curr_tvc_name : 0);
|
|
alias->str= thd->strmake(buff, alias->length);
|
|
if (!alias->str)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Check whether TVC used in unit is to be wrapped into select
|
|
|
|
@details
|
|
TVC used in unit that contains more than one members is to be wrapped
|
|
into select if it is tailed with ORDER BY ... LIMIT n [OFFSET m]
|
|
|
|
@retval
|
|
true if TVC is to be wrapped
|
|
false otherwise
|
|
*/
|
|
|
|
bool table_value_constr::to_be_wrapped_as_with_tail()
|
|
{
|
|
return select_lex->master_unit()->first_select()->next_select() &&
|
|
select_lex->order_list.elements &&
|
|
select_lex->limit_params.explicit_limit;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Wrap table value constructor into a select
|
|
|
|
@param thd The context handler
|
|
@param tvc_sl The TVC to wrap
|
|
@parent_select The parent select if tvc_sl used in a subquery
|
|
|
|
@details
|
|
The function wraps the TVC tvc_sl into a select:
|
|
the function transforms the TVC of the form VALUES (v1), ... (vn) into
|
|
the select of the form
|
|
SELECT * FROM (VALUES (v1), ... (vn)) tvc_x
|
|
|
|
@retval pointer to the result of of the transformation if successful
|
|
NULL - otherwise
|
|
*/
|
|
|
|
static
|
|
st_select_lex *wrap_tvc(THD *thd, st_select_lex *tvc_sl,
|
|
st_select_lex *parent_select)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
select_result *save_result= lex->result;
|
|
uint8 save_derived_tables= lex->derived_tables;
|
|
thd->lex->result= NULL;
|
|
|
|
Query_arena backup;
|
|
Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
|
|
|
|
Item *item;
|
|
SELECT_LEX *wrapper_sl;
|
|
SELECT_LEX_UNIT *derived_unit;
|
|
|
|
/*
|
|
Create SELECT_LEX wrapper_sl of the select used in the result
|
|
of the transformation
|
|
*/
|
|
if (!(wrapper_sl= new (thd->mem_root) SELECT_LEX()))
|
|
goto err;
|
|
wrapper_sl->select_number= ++thd->lex->stmt_lex->current_select_number;
|
|
wrapper_sl->parent_lex= lex; /* Used in init_query. */
|
|
wrapper_sl->init_query();
|
|
wrapper_sl->init_select();
|
|
|
|
wrapper_sl->nest_level= tvc_sl->nest_level;
|
|
wrapper_sl->parsing_place= tvc_sl->parsing_place;
|
|
wrapper_sl->set_linkage(tvc_sl->get_linkage());
|
|
wrapper_sl->exclude_from_table_unique_test=
|
|
tvc_sl->exclude_from_table_unique_test;
|
|
|
|
lex->current_select= wrapper_sl;
|
|
item= new (thd->mem_root) Item_field(thd, &wrapper_sl->context,
|
|
star_clex_str);
|
|
if (item == NULL || add_item_to_list(thd, item))
|
|
goto err;
|
|
(wrapper_sl->with_wild)++;
|
|
|
|
/* Include the newly created select into the global list of selects */
|
|
wrapper_sl->include_global((st_select_lex_node**)&lex->all_selects_list);
|
|
|
|
/* Substitute select node used of TVC for the newly created select */
|
|
tvc_sl->substitute_in_tree(wrapper_sl);
|
|
|
|
/*
|
|
Create a unit for the substituted select used for TVC and attach it
|
|
to the the wrapper select wrapper_sl as the only unit. The created
|
|
unit is the unit for the derived table tvc_x of the transformation.
|
|
*/
|
|
if (!(derived_unit= new (thd->mem_root) SELECT_LEX_UNIT()))
|
|
goto err;
|
|
derived_unit->init_query();
|
|
derived_unit->thd= thd;
|
|
derived_unit->include_down(wrapper_sl);
|
|
|
|
/*
|
|
Attach the select used of TVC as the only slave to the unit for
|
|
the derived table tvc_x of the transformation
|
|
*/
|
|
derived_unit->add_slave(tvc_sl);
|
|
tvc_sl->set_linkage(DERIVED_TABLE_TYPE);
|
|
|
|
/*
|
|
Generate the name of the derived table created for TVC and
|
|
add it to the FROM list of the wrapping select
|
|
*/
|
|
Table_ident *ti;
|
|
LEX_CSTRING alias;
|
|
TABLE_LIST *derived_tab;
|
|
if (!(ti= new (thd->mem_root) Table_ident(derived_unit)) ||
|
|
create_tvc_name(thd, parent_select, &alias))
|
|
goto err;
|
|
if (!(derived_tab=
|
|
wrapper_sl->add_table_to_list(thd,
|
|
ti, &alias, 0,
|
|
TL_READ, MDL_SHARED_READ)))
|
|
goto err;
|
|
wrapper_sl->add_joined_table(derived_tab);
|
|
wrapper_sl->add_where_field(derived_unit->first_select());
|
|
wrapper_sl->context.table_list= wrapper_sl->table_list.first;
|
|
wrapper_sl->context.first_name_resolution_table= wrapper_sl->table_list.first;
|
|
wrapper_sl->table_list.first->derived_type= DTYPE_TABLE | DTYPE_MATERIALIZE;
|
|
lex->derived_tables|= DERIVED_SUBQUERY;
|
|
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
lex->result= save_result;
|
|
return wrapper_sl;
|
|
|
|
err:
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
lex->result= save_result;
|
|
lex->derived_tables= save_derived_tables;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Wrap TVC with ORDER BY ... LIMIT tail into a select
|
|
|
|
@param thd The context handler
|
|
@param tvc_sl The TVC to wrap
|
|
|
|
@details
|
|
The function wraps the TVC tvc_sl into a select:
|
|
the function transforms the TVC with tail of the form
|
|
VALUES (v1), ... (vn) ORDER BY ... LIMIT n [OFFSET m]
|
|
into the select with the same tail of the form
|
|
SELECT * FROM (VALUES (v1), ... (vn)) tvc_x
|
|
ORDER BY ... LIMIT n [OFFSET m]
|
|
|
|
@retval pointer to the result of of the transformation if successful
|
|
NULL - otherwise
|
|
*/
|
|
|
|
st_select_lex *wrap_tvc_with_tail(THD *thd, st_select_lex *tvc_sl)
|
|
{
|
|
st_select_lex *wrapper_sl= wrap_tvc(thd, tvc_sl, NULL);
|
|
if (!wrapper_sl)
|
|
return NULL;
|
|
|
|
wrapper_sl->order_list= tvc_sl->order_list;
|
|
wrapper_sl->limit_params= tvc_sl->limit_params;
|
|
wrapper_sl->braces= tvc_sl->braces;
|
|
tvc_sl->order_list.empty();
|
|
tvc_sl->limit_params.clear();
|
|
tvc_sl->braces= 0;
|
|
if (tvc_sl->select_number == 1)
|
|
{
|
|
tvc_sl->select_number= wrapper_sl->select_number;
|
|
wrapper_sl->select_number= 1;
|
|
}
|
|
if (tvc_sl->master_unit()->union_distinct == tvc_sl)
|
|
{
|
|
wrapper_sl->master_unit()->union_distinct= wrapper_sl;
|
|
}
|
|
wrapper_sl->distinct= tvc_sl->distinct;
|
|
thd->lex->current_select= wrapper_sl;
|
|
return wrapper_sl;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Wrap TVC in a subselect into a select
|
|
|
|
@param thd The context handler
|
|
@param tvc_sl The TVC to wrap
|
|
|
|
@details
|
|
The function wraps the TVC tvc_sl used in a subselect into a select
|
|
the function transforms the TVC of the form VALUES (v1), ... (vn)
|
|
into the select the form
|
|
SELECT * FROM (VALUES (v1), ... (vn)) tvc_x
|
|
and replaces the subselect with the result of the transformation.
|
|
|
|
@retval wrapping select if successful
|
|
0 otherwise
|
|
*/
|
|
|
|
st_select_lex *
|
|
Item_subselect::wrap_tvc_into_select(THD *thd, st_select_lex *tvc_sl)
|
|
{
|
|
LEX *lex= thd->lex;
|
|
/* SELECT_LEX object where the transformation is performed */
|
|
SELECT_LEX *parent_select= lex->current_select;
|
|
SELECT_LEX *wrapper_sl= wrap_tvc(thd, tvc_sl, parent_select);
|
|
if (wrapper_sl)
|
|
{
|
|
if (engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE)
|
|
((subselect_single_select_engine *) engine)->change_select(wrapper_sl);
|
|
}
|
|
lex->current_select= parent_select;
|
|
return wrapper_sl;
|
|
}
|
|
|
|
|
|
/*
|
|
@brief
|
|
Check whether the items are of comparable type or not
|
|
|
|
@details
|
|
This check are done because materialization is not performed
|
|
if the left expr and right expr are of the same types.
|
|
@see subquery_types_allow_materialization()
|
|
|
|
@retval
|
|
0 comparable
|
|
1 not comparable
|
|
*/
|
|
|
|
static bool cmp_row_types(Item* item1, Item* item2)
|
|
{
|
|
uint n= item1->cols();
|
|
if (item2->check_cols(n))
|
|
return true;
|
|
|
|
for (uint i=0; i < n; i++)
|
|
{
|
|
Item *inner= item1->element_index(i);
|
|
Item *outer= item2->element_index(i);
|
|
if (!inner->type_handler()->subquery_type_allows_materialization(inner,
|
|
outer,
|
|
true))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Transform IN predicate into IN subquery
|
|
|
|
@param thd The context of the statement
|
|
@param arg Not used
|
|
|
|
@details
|
|
The method transforms this IN predicate into in equivalent IN subquery:
|
|
|
|
<left_expr> IN (<value_list>)
|
|
=>
|
|
<left_expr> IN (SELECT * FROM (VALUES <transformed_value_list>) AS tvc_#)
|
|
|
|
E.g.:
|
|
|
|
<value_list> = 5,2,7
|
|
<transformed_value_list> = (5),(2),(7)
|
|
|
|
<value_list> = (5,2),(7,1)
|
|
<transformed_value_list> = (5,2),(7,1)
|
|
|
|
If the transformation succeeds the method returns the result IN subquery,
|
|
otherwise this IN predicate is returned.
|
|
|
|
@retval
|
|
pointer to the result of transformation if succeeded
|
|
pointer to this IN predicate otherwise
|
|
*/
|
|
|
|
Item *Item_func_in::in_predicate_to_in_subs_transformer(THD *thd,
|
|
uchar *arg)
|
|
{
|
|
if (!transform_into_subq)
|
|
return this;
|
|
|
|
Json_writer_object trace_wrapper(thd);
|
|
Json_writer_object trace_conv(thd, "in_to_subquery_conversion");
|
|
trace_conv.add("item", this);
|
|
|
|
transform_into_subq= false;
|
|
|
|
List<List_item> values;
|
|
|
|
LEX *lex= thd->lex;
|
|
/* SELECT_LEX object where the transformation is performed */
|
|
SELECT_LEX *parent_select= lex->current_select;
|
|
uint8 save_derived_tables= lex->derived_tables;
|
|
|
|
/*
|
|
Make sure that create_tmp_table will not fail due to too long keys.
|
|
Here the strategy would mainly use materialization, so we need to make
|
|
sure that the materialized table can be created.
|
|
|
|
The checks here are the same as in subquery_type_allows_materialization()
|
|
*/
|
|
uint32 length= max_length_of_left_expr();
|
|
if (!length || length > tmp_table_max_key_length() ||
|
|
args[0]->cols() > tmp_table_max_key_parts())
|
|
{
|
|
trace_conv.add("done", false);
|
|
trace_conv.add("reason", "key is too long");
|
|
return this;
|
|
}
|
|
|
|
for (uint i=1; i < arg_count; i++)
|
|
{
|
|
if (!args[i]->const_item())
|
|
{
|
|
trace_conv.add("done", false);
|
|
trace_conv.add("reason", "non-constant element in the IN-list");
|
|
return this;
|
|
}
|
|
|
|
if (cmp_row_types(args[i], args[0]))
|
|
{
|
|
trace_conv.add("done", false);
|
|
trace_conv.add("reason", "type mismatch");
|
|
return this;
|
|
}
|
|
}
|
|
Json_writer_array trace_nested_obj(thd, "conversion");
|
|
|
|
Query_arena backup;
|
|
Query_arena *arena= thd->activate_stmt_arena_if_needed(&backup);
|
|
|
|
/*
|
|
Create SELECT_LEX of the subquery SQ used in the result of transformation
|
|
*/
|
|
if (mysql_new_select(lex, 1, NULL))
|
|
goto err;
|
|
mysql_init_select(lex);
|
|
/* Create item list as '*' for the subquery SQ */
|
|
Item *item;
|
|
SELECT_LEX *sq_select; // select for IN subquery;
|
|
sq_select= lex->current_select;
|
|
sq_select->parsing_place= SELECT_LIST;
|
|
item= new (thd->mem_root) Item_field(thd, &sq_select->context,
|
|
star_clex_str);
|
|
if (item == NULL || add_item_to_list(thd, item))
|
|
goto err;
|
|
(sq_select->with_wild)++;
|
|
/*
|
|
Create derived table DT that will wrap TVC in the result of transformation
|
|
*/
|
|
SELECT_LEX *tvc_select; // select for tvc
|
|
SELECT_LEX_UNIT *derived_unit; // unit for tvc_select
|
|
if (mysql_new_select(lex, 1, NULL))
|
|
goto err;
|
|
mysql_init_select(lex);
|
|
tvc_select= lex->current_select;
|
|
derived_unit= tvc_select->master_unit();
|
|
tvc_select->set_linkage(DERIVED_TABLE_TYPE);
|
|
|
|
/* Create TVC used in the transformation */
|
|
if (create_value_list_for_tvc(thd, &values))
|
|
goto err;
|
|
if (!(tvc_select->tvc=
|
|
new (thd->mem_root)
|
|
table_value_constr(values,
|
|
tvc_select,
|
|
tvc_select->options)))
|
|
goto err;
|
|
|
|
lex->current_select= sq_select;
|
|
|
|
/*
|
|
Create the name of the wrapping derived table and
|
|
add it to the FROM list of the subquery SQ
|
|
*/
|
|
Table_ident *ti;
|
|
LEX_CSTRING alias;
|
|
TABLE_LIST *derived_tab;
|
|
if (!(ti= new (thd->mem_root) Table_ident(derived_unit)) ||
|
|
create_tvc_name(thd, parent_select, &alias))
|
|
goto err;
|
|
if (!(derived_tab=
|
|
sq_select->add_table_to_list(thd,
|
|
ti, &alias, 0,
|
|
TL_READ, MDL_SHARED_READ)))
|
|
goto err;
|
|
sq_select->add_joined_table(derived_tab);
|
|
sq_select->add_where_field(derived_unit->first_select());
|
|
sq_select->context.table_list= sq_select->table_list.first;
|
|
sq_select->context.first_name_resolution_table= sq_select->table_list.first;
|
|
sq_select->table_list.first->derived_type= DTYPE_TABLE | DTYPE_MATERIALIZE;
|
|
lex->derived_tables|= DERIVED_SUBQUERY;
|
|
|
|
sq_select->where= 0;
|
|
sq_select->set_braces(false);
|
|
derived_unit->set_with_clause(0);
|
|
|
|
/* Create IN subquery predicate */
|
|
sq_select->parsing_place= parent_select->parsing_place;
|
|
Item_in_subselect *in_subs;
|
|
Item *sq;
|
|
if (!(in_subs=
|
|
new (thd->mem_root) Item_in_subselect(thd, args[0], sq_select)))
|
|
goto err;
|
|
in_subs->converted_from_in_predicate= TRUE;
|
|
sq= in_subs;
|
|
if (negated)
|
|
sq= negate_expression(thd, in_subs);
|
|
else
|
|
in_subs->emb_on_expr_nest= emb_on_expr_nest;
|
|
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
thd->lex->current_select= parent_select;
|
|
|
|
if (sq->fix_fields(thd, (Item **)&sq))
|
|
goto err;
|
|
|
|
parent_select->curr_tvc_name++;
|
|
|
|
return sq;
|
|
|
|
err:
|
|
if (arena)
|
|
thd->restore_active_arena(arena, &backup);
|
|
lex->derived_tables= save_derived_tables;
|
|
thd->lex->current_select= parent_select;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
uint32 Item_func_in::max_length_of_left_expr()
|
|
{
|
|
uint n= args[0]->cols();
|
|
uint32 length= 0;
|
|
for (uint i=0; i < n; i++)
|
|
length+= args[0]->element_index(i)->max_length;
|
|
return length;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Check if this IN-predicate can be transformed in IN-subquery
|
|
with TVC
|
|
|
|
@param thd The context of the statement
|
|
|
|
@details
|
|
Compare the number of elements in the list of
|
|
values in this IN-predicate with the
|
|
in_subquery_conversion_threshold special variable
|
|
|
|
@retval
|
|
true if transformation can be made
|
|
false otherwise
|
|
*/
|
|
|
|
bool Item_func_in::to_be_transformed_into_in_subq(THD *thd)
|
|
{
|
|
uint values_count= arg_count-1;
|
|
|
|
if (args[1]->type() == Item::ROW_ITEM)
|
|
values_count*= ((Item_row *)(args[1]))->cols();
|
|
|
|
if (thd->variables.in_subquery_conversion_threshold == 0 ||
|
|
thd->variables.in_subquery_conversion_threshold > values_count)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief
|
|
Transform IN predicates into IN subqueries in WHERE and ON expressions
|
|
|
|
@param thd The context of the statement
|
|
|
|
@details
|
|
For each IN predicate from AND parts of the WHERE condition and/or
|
|
ON expressions of the SELECT for this join the method performs
|
|
the intransformation into an equivalent IN sunquery if it's needed.
|
|
|
|
@retval
|
|
false always
|
|
*/
|
|
|
|
bool JOIN::transform_in_predicates_into_in_subq(THD *thd)
|
|
{
|
|
DBUG_ENTER("JOIN::transform_in_predicates_into_in_subq");
|
|
if (!select_lex->in_funcs.elements)
|
|
DBUG_RETURN(false);
|
|
|
|
SELECT_LEX *save_current_select= thd->lex->current_select;
|
|
enum_parsing_place save_parsing_place= select_lex->parsing_place;
|
|
thd->lex->current_select= select_lex;
|
|
if (conds)
|
|
{
|
|
select_lex->parsing_place= IN_WHERE;
|
|
conds=
|
|
conds->transform(thd,
|
|
&Item::in_predicate_to_in_subs_transformer,
|
|
(uchar*) 0);
|
|
if (!conds)
|
|
DBUG_RETURN(true);
|
|
select_lex->prep_where= conds ? conds->copy_andor_structure(thd) : 0;
|
|
select_lex->where= conds;
|
|
}
|
|
|
|
if (join_list)
|
|
{
|
|
TABLE_LIST *table;
|
|
List_iterator<TABLE_LIST> li(*join_list);
|
|
select_lex->parsing_place= IN_ON;
|
|
|
|
while ((table= li++))
|
|
{
|
|
if (table->on_expr)
|
|
{
|
|
table->on_expr=
|
|
table->on_expr->transform(thd,
|
|
&Item::in_predicate_to_in_subs_transformer,
|
|
(uchar*) 0);
|
|
if (!table->on_expr)
|
|
DBUG_RETURN(true);
|
|
table->prep_on_expr= table->on_expr ?
|
|
table->on_expr->copy_andor_structure(thd) : 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
select_lex->in_funcs.empty();
|
|
select_lex->parsing_place= save_parsing_place;
|
|
thd->lex->current_select= save_current_select;
|
|
DBUG_RETURN(false);
|
|
}
|
|
|