mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
Summarized results of two previous commits (26 July, 25 August)
This commit is contained in:
570
sql/sql_tvc.cc
570
sql/sql_tvc.cc
@ -1,14 +1,40 @@
|
||||
#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"
|
||||
|
||||
/**
|
||||
The method searches types of columns for temporary table where values from TVC will be stored
|
||||
@brief
|
||||
Defines types of matrix columns elements where matrix rows are defined by
|
||||
some lists of values.
|
||||
|
||||
@param
|
||||
@param thd_arg 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 that should be. 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 when aggregation can't be made.
|
||||
|
||||
@retval
|
||||
true if an error was reported
|
||||
false otherwise
|
||||
*/
|
||||
|
||||
bool join_type_handlers_for_tvc(List_iterator_fast<List_item> &li,
|
||||
Type_holder *holders, uint cnt)
|
||||
bool join_type_handlers_for_tvc(THD *thd_arg, 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;
|
||||
@ -18,10 +44,12 @@ bool join_type_handlers_for_tvc(List_iterator_fast<List_item> &li,
|
||||
List_iterator_fast<Item> it(*lst);
|
||||
Item *item;
|
||||
|
||||
if (cnt != lst->elements)
|
||||
if (first_list_el_count != lst->elements)
|
||||
{
|
||||
/*error wrong number of values*/
|
||||
return true;
|
||||
my_message(ER_WRONG_NUMBER_OF_COLUMNS_IN_TABLE_VALUE_CONSTRUCTOR,
|
||||
ER_THD(thd_arg, ER_WRONG_NUMBER_OF_COLUMNS_IN_TABLE_VALUE_CONSTRUCTOR),
|
||||
MYF(0));
|
||||
DBUG_RETURN(true);
|
||||
}
|
||||
for (uint pos= 0; (item=it++); pos++)
|
||||
{
|
||||
@ -30,54 +58,105 @@ bool join_type_handlers_for_tvc(List_iterator_fast<List_item> &li,
|
||||
holders[pos].set_handler(item_type_handler);
|
||||
else if (holders[pos].aggregate_for_result(item_type_handler))
|
||||
{
|
||||
/*error ER_ILLEGAL_PARAMETER_DATA_TYPES2_FOR_OPERATION*/
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
The method searches names of columns for temporary table where values from TVC will be stored
|
||||
@brief
|
||||
Defines attributes of matrix columns elements where matrix rows are defined by
|
||||
some lists of values.
|
||||
|
||||
@param
|
||||
@param thd_arg 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 that should be. 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_arg,
|
||||
List_iterator_fast<List_item> &li,
|
||||
Type_holder *holders, uint count)
|
||||
Type_holder *holders, uint count_of_lists,
|
||||
uint first_list_el_count)
|
||||
{
|
||||
DBUG_ENTER("get_type_attributes_for_tvc");
|
||||
List_item *lst;
|
||||
li.rewind();
|
||||
|
||||
lst= li++;
|
||||
uint first_list_el_count= lst->elements;
|
||||
|
||||
for (uint pos= 0; pos < first_list_el_count; pos++)
|
||||
{
|
||||
if (holders[pos].alloc_arguments(thd_arg, count))
|
||||
return true;
|
||||
if (holders[pos].alloc_arguments(thd_arg, count_of_lists))
|
||||
DBUG_RETURN(true);
|
||||
}
|
||||
|
||||
List_iterator_fast<Item> it(*lst);
|
||||
Item *item;
|
||||
|
||||
for (uint holder_pos= 0 ; (item= it++); holder_pos++)
|
||||
while ((lst=li++))
|
||||
{
|
||||
DBUG_ASSERT(item->fixed);
|
||||
holders[holder_pos].add_argument(item);
|
||||
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_arg))
|
||||
return true;
|
||||
DBUG_RETURN(true);
|
||||
}
|
||||
return false;
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
|
||||
bool table_value_constr::prepare(THD *thd_arg, SELECT_LEX *sl, select_result *tmp_result)
|
||||
|
||||
/**
|
||||
@brief
|
||||
Prepare of TVC
|
||||
|
||||
@param
|
||||
@param thd_arg The context of the statement
|
||||
@param sl The select where this TVC is defined
|
||||
@param tmp_result Structure that contains the information
|
||||
about where result of the query should be sent
|
||||
@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_arg, SELECT_LEX *sl,
|
||||
select_result *tmp_result,
|
||||
st_select_lex_unit *unit_arg)
|
||||
{
|
||||
DBUG_ENTER("table_value_constr::prepare");
|
||||
List_iterator_fast<List_item> li(lists_of_values);
|
||||
|
||||
List_item *first_elem= li++;
|
||||
@ -86,9 +165,11 @@ bool table_value_constr::prepare(THD *thd_arg, SELECT_LEX *sl, select_result *tm
|
||||
|
||||
if (!(holders= new (thd_arg->mem_root)
|
||||
Type_holder[cnt]) ||
|
||||
join_type_handlers_for_tvc(li, holders, cnt) ||
|
||||
get_type_attributes_for_tvc(thd_arg, li, holders, cnt))
|
||||
return true;
|
||||
join_type_handlers_for_tvc(thd_arg, li, holders,
|
||||
cnt) ||
|
||||
get_type_attributes_for_tvc(thd_arg, li, holders,
|
||||
lists_of_values.elements, cnt))
|
||||
DBUG_RETURN(true);
|
||||
|
||||
List_iterator_fast<Item> it(*first_elem);
|
||||
Item *item;
|
||||
@ -108,21 +189,448 @@ bool table_value_constr::prepare(THD *thd_arg, SELECT_LEX *sl, select_result *tm
|
||||
}
|
||||
|
||||
if (thd_arg->is_fatal_error)
|
||||
return true; // out of memory
|
||||
DBUG_RETURN(true); // out of memory
|
||||
|
||||
result= tmp_result;
|
||||
|
||||
return false;
|
||||
if (result && result->prepare(sl->item_list, unit_arg))
|
||||
DBUG_RETURN(true);
|
||||
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
|
||||
bool table_value_constr::exec()
|
||||
|
||||
/**
|
||||
Save Query Plan Footprint
|
||||
*/
|
||||
|
||||
int table_value_constr::save_explain_data_intern(THD *thd_arg,
|
||||
Explain_query *output)
|
||||
{
|
||||
const char *message= "No tables used";
|
||||
DBUG_ENTER("table_value_constr::save_explain_data_intern");
|
||||
DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
|
||||
(ulong)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_arg->lex->analyze_stmt);
|
||||
select_lex->set_explain_type(true);
|
||||
|
||||
explain->select_id= select_lex->select_number;
|
||||
explain->select_type= select_lex->type;
|
||||
explain->linkage= select_lex->linkage;
|
||||
explain->using_temporary= NULL;
|
||||
explain->using_filesort= NULL;
|
||||
/* 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;
|
||||
|
||||
output->add_node(explain);
|
||||
|
||||
if (select_lex->is_top_level_node())
|
||||
output->query_plan_ready();
|
||||
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
Optimization of TVC
|
||||
*/
|
||||
|
||||
void table_value_constr::optimize(THD *thd_arg)
|
||||
{
|
||||
create_explain_query_if_not_exists(thd_arg->lex, thd_arg->mem_root);
|
||||
have_query_plan= QEP_AVAILABLE;
|
||||
|
||||
if (select_lex->select_number != UINT_MAX &&
|
||||
select_lex->select_number != INT_MAX /* this is not a UNION's "fake select */ &&
|
||||
have_query_plan != QEP_NOT_PRESENT_YET &&
|
||||
thd_arg->lex->explain && // for "SET" command in SPs.
|
||||
(!thd_arg->lex->explain->get_select(select_lex->select_number)))
|
||||
{
|
||||
save_explain_data_intern(thd_arg, thd_arg->lex->explain);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
while ((elem=li++))
|
||||
{
|
||||
result->send_data(*elem);
|
||||
}
|
||||
|
||||
if (result->send_eof())
|
||||
DBUG_RETURN(true);
|
||||
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief
|
||||
Print list of lists
|
||||
|
||||
@param str Where to print to
|
||||
@param query_type The mode of printing
|
||||
@param values List of lists that needed to be print
|
||||
|
||||
@details
|
||||
The method prints a string representation of list of lists in the
|
||||
string str. The parameter query_type specifies the mode of printing.
|
||||
*/
|
||||
|
||||
void print_list_of_lists(String *str,
|
||||
enum_query_type query_type,
|
||||
List<List_item> *values)
|
||||
{
|
||||
str->append(STRING_WITH_LEN("values "));
|
||||
|
||||
bool first= 1;
|
||||
List_iterator_fast<List_item> li(*values);
|
||||
List_item *list;
|
||||
while ((list=li++))
|
||||
{
|
||||
if (first)
|
||||
first= 0;
|
||||
else
|
||||
str->append(',');
|
||||
|
||||
str->append('(');
|
||||
|
||||
List_iterator_fast<Item> it(*list);
|
||||
Item *item;
|
||||
first= 1;
|
||||
|
||||
while ((item=it++))
|
||||
{
|
||||
if (first)
|
||||
first= 0;
|
||||
else
|
||||
str->append(',');
|
||||
|
||||
item->print(str, query_type);
|
||||
}
|
||||
str->append(')');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief
|
||||
Print this TVC
|
||||
|
||||
@param thd_arg The context of the statement
|
||||
@param str Where to print to
|
||||
@param query_type The mode of printing
|
||||
|
||||
@details
|
||||
The method prints a string representation of this TVC in the
|
||||
string str. The parameter query_type specifies the mode of printing.
|
||||
*/
|
||||
|
||||
void table_value_constr::print(THD *thd_arg, String *str,
|
||||
enum_query_type query_type)
|
||||
{
|
||||
DBUG_ASSERT(thd_arg);
|
||||
|
||||
print_list_of_lists(str, query_type, &lists_of_values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief
|
||||
Creates new SELECT defined by TVC as derived table
|
||||
|
||||
@param thd_arg The context of the statement
|
||||
@param values List of values that defines TVC
|
||||
|
||||
@details
|
||||
The method creates this SELECT statement:
|
||||
|
||||
SELECT * FROM (VALUES values) AS new_tvc
|
||||
|
||||
If during creation of SELECT statement some action is
|
||||
unsuccesfull backup is made to the state in which system
|
||||
was at the beginning of the method.
|
||||
|
||||
@retval
|
||||
pointer to the created SELECT statement
|
||||
NULL - if creation was unsuccesfull
|
||||
*/
|
||||
|
||||
st_select_lex *make_new_subselect_for_tvc(THD *thd_arg,
|
||||
List<List_item> *values)
|
||||
{
|
||||
LEX *lex= thd_arg->lex;
|
||||
Item *item;
|
||||
SELECT_LEX *sel;
|
||||
SELECT_LEX_UNIT *unit;
|
||||
TABLE_LIST *new_tab;
|
||||
Table_ident *ti;
|
||||
|
||||
Query_arena backup;
|
||||
Query_arena *arena= thd_arg->activate_stmt_arena_if_needed(&backup);
|
||||
|
||||
char buff[6];
|
||||
LEX_CSTRING alias;
|
||||
alias.length= my_snprintf(buff, sizeof(buff),
|
||||
"tvc_%u", thd_arg->lex->current_select->cur_tvc);
|
||||
alias.str= thd_arg->strmake(buff, alias.length);
|
||||
if (!alias.str)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
Creation of SELECT statement: SELECT * FROM ...
|
||||
*/
|
||||
|
||||
if (mysql_new_select(lex, 1, NULL))
|
||||
goto err;
|
||||
|
||||
mysql_init_select(lex);
|
||||
lex->current_select->parsing_place= SELECT_LIST;
|
||||
|
||||
item= new (thd_arg->mem_root)
|
||||
Item_field(thd_arg, &lex->current_select->context,
|
||||
NULL, NULL, &star_clex_str);
|
||||
if (item == NULL)
|
||||
goto err;
|
||||
if (add_item_to_list(thd_arg, item))
|
||||
goto err;
|
||||
(lex->current_select->with_wild)++;
|
||||
|
||||
/*
|
||||
Creation of TVC as derived table
|
||||
*/
|
||||
|
||||
lex->derived_tables|= DERIVED_SUBQUERY;
|
||||
if (mysql_new_select(lex, 1, NULL))
|
||||
goto err;
|
||||
|
||||
mysql_init_select(lex);
|
||||
|
||||
sel= lex->current_select;
|
||||
unit= sel->master_unit();
|
||||
sel->linkage= DERIVED_TABLE_TYPE;
|
||||
|
||||
if (!(sel->tvc=
|
||||
new (thd_arg->mem_root)
|
||||
table_value_constr(*values,
|
||||
sel,
|
||||
sel->options)))
|
||||
goto err;
|
||||
|
||||
lex->check_automatic_up(UNSPECIFIED_TYPE);
|
||||
lex->current_select= sel= unit->outer_select();
|
||||
|
||||
ti= new (thd_arg->mem_root) Table_ident(unit);
|
||||
if (ti == NULL)
|
||||
goto err;
|
||||
|
||||
if (!(new_tab= sel->add_table_to_list(thd_arg,
|
||||
ti, &alias, 0,
|
||||
TL_READ, MDL_SHARED_READ)))
|
||||
goto err;
|
||||
|
||||
new_tab->is_for_tvc= true; //shows that this derived table is defined by TVC
|
||||
sel->add_joined_table(new_tab);
|
||||
|
||||
new_tab->select_lex->add_where_field(new_tab->derived->first_select());
|
||||
|
||||
sel->context.table_list=
|
||||
sel->context.first_name_resolution_table=
|
||||
sel->table_list.first;
|
||||
|
||||
sel->where= 0;
|
||||
sel->set_braces(false);
|
||||
unit->with_clause= 0;
|
||||
|
||||
return sel;
|
||||
|
||||
err:
|
||||
if (arena)
|
||||
thd_arg->restore_active_arena(arena, &backup);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@brief
|
||||
Transforms IN-predicate in IN-subselect
|
||||
|
||||
@param thd_arg The context of the statement
|
||||
@param arg Argument is 0 in this context
|
||||
|
||||
@details
|
||||
The method creates this SELECT statement:
|
||||
|
||||
SELECT * FROM (VALUES values) AS new_tvc
|
||||
|
||||
If during creation of SELECT statement some action is
|
||||
unsuccesfull backup is made to the state in which system
|
||||
was at the beginning of the procedure.
|
||||
|
||||
@retval
|
||||
pointer to the created SELECT statement
|
||||
NULL - if creation was unsuccesfull
|
||||
*/
|
||||
|
||||
Item *Item_func_in::in_predicate_to_in_subs_transformer(THD *thd,
|
||||
uchar *arg)
|
||||
{
|
||||
SELECT_LEX *old_select= thd->lex->current_select;
|
||||
List<List_item> values;
|
||||
bool list_of_lists= false;
|
||||
|
||||
if (args[1]->type() == Item::ROW_ITEM)
|
||||
list_of_lists= true;
|
||||
|
||||
for (uint i=1; i < arg_count; i++)
|
||||
{
|
||||
List<Item> *new_value= new (thd->mem_root) List<Item>();
|
||||
|
||||
if (list_of_lists)
|
||||
{
|
||||
Item_row *in_list= (Item_row *)(args[i]);
|
||||
|
||||
for (uint j=0; j < in_list->cols(); i++)
|
||||
new_value->push_back(in_list->element_index(j), thd->mem_root);
|
||||
}
|
||||
else
|
||||
new_value->push_back(args[i]);
|
||||
|
||||
values.push_back(new_value, thd->mem_root);
|
||||
}
|
||||
|
||||
st_select_lex *new_subselect=
|
||||
make_new_subselect_for_tvc(thd, &values);
|
||||
|
||||
if (new_subselect)
|
||||
{
|
||||
new_subselect->parsing_place= old_select->parsing_place;
|
||||
new_subselect->table_list.first->derived_type= 10;
|
||||
|
||||
Item_in_subselect *in_subs= new (thd->mem_root) Item_in_subselect
|
||||
(thd, args[0], new_subselect);
|
||||
thd->lex->derived_tables |= DERIVED_SUBQUERY;
|
||||
in_subs->emb_on_expr_nest= emb_on_expr_nest;
|
||||
in_subs->fix_fields(thd, (Item **)&in_subs);
|
||||
|
||||
old_select->cur_tvc++;
|
||||
thd->lex->current_select= old_select;
|
||||
return in_subs;
|
||||
}
|
||||
|
||||
thd->lex->current_select= old_select;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief
|
||||
Checks if this IN-predicate can be transformed in IN-subquery
|
||||
with TVC
|
||||
|
||||
@param thd The context of the statement
|
||||
|
||||
@details
|
||||
Compares 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::can_be_transformed_in_tvc(THD *thd)
|
||||
{
|
||||
uint opt_can_be_used= arg_count;
|
||||
|
||||
if (args[1]->type() == Item::ROW_ITEM)
|
||||
opt_can_be_used*= ((Item_row *)(args[1]))->cols();
|
||||
|
||||
if (opt_can_be_used < thd->variables.in_subquery_conversion_threshold)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief
|
||||
Calls transformer that transforms IN-predicate into IN-subquery
|
||||
for this select
|
||||
|
||||
@param thd_arg The context of the statement
|
||||
|
||||
@details
|
||||
Calls in_predicate_to_in_subs_transformer
|
||||
for WHERE-part and each table from join list of this SELECT
|
||||
*/
|
||||
|
||||
bool JOIN::transform_in_predicate_into_tvc(THD *thd_arg)
|
||||
{
|
||||
if (!select_lex->in_funcs.elements)
|
||||
return false;
|
||||
|
||||
SELECT_LEX *old_select= thd_arg->lex->current_select;
|
||||
enum_parsing_place old_parsing_place= select_lex->parsing_place;
|
||||
|
||||
thd_arg->lex->current_select= select_lex;
|
||||
if (conds)
|
||||
{
|
||||
select_lex->parsing_place= IN_WHERE;
|
||||
conds=
|
||||
conds->transform(thd_arg,
|
||||
&Item::in_predicate_to_in_subs_transformer,
|
||||
(uchar*) 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_arg,
|
||||
&Item::in_predicate_to_in_subs_transformer,
|
||||
(uchar*) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
select_lex->parsing_place= old_parsing_place;
|
||||
thd_arg->lex->current_select= old_select;
|
||||
return false;
|
||||
}
|
Reference in New Issue
Block a user