mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
MDEV-6768 Wrong result with aggregate with join with no result set
When a query does implicit grouping and join operation produces an empty result set, a NULL-complemented row combination is generated. However, constant table fields still show non-NULL values. What happens in the is that end_send_group() is called with a const row but without any rows matching the WHERE clause. This last part is shown by 'join->first_record' not being set. This causes item->no_rows_in_result() to be called for all items to reset all sum functions to their initial state. However fields are not set to NULL. The used fix is to produce NULL-complemented records for constant tables as well. Also, reset the constant table's records back in case we're in a subquery which may get re-executed. An alternative fix would have item->no_rows_in_result() also work with Item_field objects. There is some other issues with the code: - join->no_rows_in_result_called is used but never set. - Tables that are used with group functions are not properly marked as maybe_null, which is required if the table rows should be regarded as null-complemented (not existing). - The code that tries to detect if mixed_implicit_grouping should be set didn't take into account all usage of fields and sum functions. - Item_func::restore_to_before_no_rows_in_result() called the wrong function. - join->clear() does not use a table_map argument to clear_tables(), which caused it to ignore constant tables. - unclear_tables() does not correctly restore status to what is was before clear_tables(). Main bug fix was to always use a table_map argument to clear_tables() and always use join->clear() and clear_tables() together with unclear_tables(). Other fixes: - Fixed Item_func::restore_to_before_no_rows_in_result() - Set 'join->no_rows_in_result_called' when no_rows_in_result_set() is called. - Removed not used argument from setup_end_select_func(). - More code comments - Ensure that end_send_group() modifies the same fields as are in the result set. - Changed return_zero_rows() to use pointers instead of references, similar to the rest of the code. Reviewer: Sergei Petrunia <sergey@mariadb.com>
This commit is contained in:
@ -4095,5 +4095,115 @@ MIN(pk) a
|
|||||||
5 10
|
5 10
|
||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
#
|
#
|
||||||
|
# MDEV-6768 Wrong result with agregate with join with no resultset
|
||||||
|
#
|
||||||
|
create table t1
|
||||||
|
(
|
||||||
|
PARENT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
PARENT_FIELD VARCHAR(10),
|
||||||
|
PRIMARY KEY (PARENT_ID)
|
||||||
|
) engine=innodb;
|
||||||
|
create table t2
|
||||||
|
(
|
||||||
|
CHILD_ID INT NOT NULL AUTO_INCREMENT,
|
||||||
|
PARENT_ID INT NOT NULL,
|
||||||
|
CHILD_FIELD varchar(10),
|
||||||
|
PRIMARY KEY (CHILD_ID)
|
||||||
|
)engine=innodb;
|
||||||
|
INSERT INTO t1 (PARENT_FIELD)
|
||||||
|
SELECT 'AAAA';
|
||||||
|
INSERT INTO t2 (PARENT_ID, CHILD_FIELD)
|
||||||
|
SELECT 1, 'BBBB';
|
||||||
|
explain select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 const PRIMARY PRIMARY 4 const 1 Using index
|
||||||
|
1 SIMPLE t2 ALL NULL NULL NULL NULL 1 Using where
|
||||||
|
select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
PARENT_ID min(CHILD_FIELD)
|
||||||
|
NULL NULL
|
||||||
|
select
|
||||||
|
1,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
1 min(CHILD_FIELD)
|
||||||
|
1 NULL
|
||||||
|
select
|
||||||
|
IFNULL(t1.PARENT_ID,1),
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
IFNULL(t1.PARENT_ID,1) min(CHILD_FIELD)
|
||||||
|
1 NULL
|
||||||
|
# Check that things works with MyISAM (which has different explain)
|
||||||
|
alter table t1 engine=myisam;
|
||||||
|
alter table t2 engine=myisam;
|
||||||
|
explain select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables
|
||||||
|
select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
PARENT_ID min(CHILD_FIELD)
|
||||||
|
NULL NULL
|
||||||
|
drop table t1,t2;
|
||||||
|
# Check that things works if sub queries are re-executed
|
||||||
|
create table t1 (a int primary key, b int);
|
||||||
|
create table t2 (a int primary key, b int);
|
||||||
|
create table t3 (a int primary key, b int);
|
||||||
|
insert into t1 values (1,1),(2,2),(3,3);
|
||||||
|
insert into t2 values (1,1),(2,2),(3,3);
|
||||||
|
insert into t3 values (1,1),(3,3);
|
||||||
|
explain
|
||||||
|
select *,
|
||||||
|
(select
|
||||||
|
CONCAT('t2:', IFNULL(t2.a, 't2a-null'), ';',
|
||||||
|
'min_t3_b:', IFNULL(min(t3.b), 't3b-null'))
|
||||||
|
from t2,t3
|
||||||
|
where t2.a=1 and t1.b = t3.a) as s1
|
||||||
|
from t1;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 PRIMARY t1 ALL NULL NULL NULL NULL 3
|
||||||
|
2 DEPENDENT SUBQUERY t2 const PRIMARY PRIMARY 4 const 1 Using index
|
||||||
|
2 DEPENDENT SUBQUERY t3 eq_ref PRIMARY PRIMARY 4 test.t1.b 1
|
||||||
|
select *,
|
||||||
|
(select
|
||||||
|
CONCAT('t2:', IFNULL(t2.a, 't2a-null'), ';',
|
||||||
|
'min_t3_b:', IFNULL(min(t3.b), 't3b-null'))
|
||||||
|
from t2,t3
|
||||||
|
where t2.a=1 and t1.b = t3.a) as s1
|
||||||
|
from t1;
|
||||||
|
a b s1
|
||||||
|
1 1 t2:1;min_t3_b:1
|
||||||
|
2 2 t2:t2a-null;min_t3_b:t3b-null
|
||||||
|
3 3 t2:1;min_t3_b:3
|
||||||
|
drop table t1,t2,t3;
|
||||||
|
#
|
||||||
# End of 10.5 tests
|
# End of 10.5 tests
|
||||||
#
|
#
|
||||||
|
@ -1748,6 +1748,116 @@ SELECT MIN(pk), a FROM t1 WHERE pk <> 1 GROUP BY a;
|
|||||||
|
|
||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
|
|
||||||
|
--echo #
|
||||||
|
--echo # MDEV-6768 Wrong result with agregate with join with no resultset
|
||||||
|
--echo #
|
||||||
|
|
||||||
|
create table t1
|
||||||
|
(
|
||||||
|
PARENT_ID int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
PARENT_FIELD VARCHAR(10),
|
||||||
|
PRIMARY KEY (PARENT_ID)
|
||||||
|
) engine=innodb;
|
||||||
|
|
||||||
|
create table t2
|
||||||
|
(
|
||||||
|
CHILD_ID INT NOT NULL AUTO_INCREMENT,
|
||||||
|
PARENT_ID INT NOT NULL,
|
||||||
|
CHILD_FIELD varchar(10),
|
||||||
|
PRIMARY KEY (CHILD_ID)
|
||||||
|
)engine=innodb;
|
||||||
|
|
||||||
|
INSERT INTO t1 (PARENT_FIELD)
|
||||||
|
SELECT 'AAAA';
|
||||||
|
|
||||||
|
INSERT INTO t2 (PARENT_ID, CHILD_FIELD)
|
||||||
|
SELECT 1, 'BBBB';
|
||||||
|
|
||||||
|
explain select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
select
|
||||||
|
1,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
select
|
||||||
|
IFNULL(t1.PARENT_ID,1),
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
|
||||||
|
--echo # Check that things works with MyISAM (which has different explain)
|
||||||
|
|
||||||
|
alter table t1 engine=myisam;
|
||||||
|
alter table t2 engine=myisam;
|
||||||
|
|
||||||
|
explain select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
select
|
||||||
|
t1.PARENT_ID,
|
||||||
|
min(CHILD_FIELD)
|
||||||
|
from t1 straight_join t2
|
||||||
|
where t1.PARENT_ID = 1
|
||||||
|
and t1.PARENT_ID = t2.PARENT_ID
|
||||||
|
and t2.CHILD_FIELD = "ZZZZ";
|
||||||
|
|
||||||
|
drop table t1,t2;
|
||||||
|
|
||||||
|
--echo # Check that things works if sub queries are re-executed
|
||||||
|
|
||||||
|
create table t1 (a int primary key, b int);
|
||||||
|
create table t2 (a int primary key, b int);
|
||||||
|
create table t3 (a int primary key, b int);
|
||||||
|
|
||||||
|
insert into t1 values (1,1),(2,2),(3,3);
|
||||||
|
insert into t2 values (1,1),(2,2),(3,3);
|
||||||
|
insert into t3 values (1,1),(3,3);
|
||||||
|
|
||||||
|
explain
|
||||||
|
select *,
|
||||||
|
(select
|
||||||
|
CONCAT('t2:', IFNULL(t2.a, 't2a-null'), ';',
|
||||||
|
'min_t3_b:', IFNULL(min(t3.b), 't3b-null'))
|
||||||
|
from t2,t3
|
||||||
|
where t2.a=1 and t1.b = t3.a) as s1
|
||||||
|
from t1;
|
||||||
|
|
||||||
|
select *,
|
||||||
|
(select
|
||||||
|
CONCAT('t2:', IFNULL(t2.a, 't2a-null'), ';',
|
||||||
|
'min_t3_b:', IFNULL(min(t3.b), 't3b-null'))
|
||||||
|
from t2,t3
|
||||||
|
where t2.a=1 and t1.b = t3.a) as s1
|
||||||
|
from t1;
|
||||||
|
|
||||||
|
drop table t1,t2,t3;
|
||||||
|
|
||||||
--echo #
|
--echo #
|
||||||
--echo # End of 10.5 tests
|
--echo # End of 10.5 tests
|
||||||
--echo #
|
--echo #
|
||||||
|
@ -1230,6 +1230,8 @@ SELECT * FROM t1 HAVING MIN(t1.c1) >= ALL(SELECT 'a' UNION SELECT 'r');
|
|||||||
c1
|
c1
|
||||||
Warnings:
|
Warnings:
|
||||||
Warning 1292 Truncated incorrect datetime value: 'r'
|
Warning 1292 Truncated incorrect datetime value: 'r'
|
||||||
|
SELECT * FROM t1 HAVING MIN(t1.c1) > 0;
|
||||||
|
c1
|
||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
CREATE TABLE t1 (c1 timestamp);
|
CREATE TABLE t1 (c1 timestamp);
|
||||||
INSERT INTO t1 VALUES ('2010-01-01 00:00:00');
|
INSERT INTO t1 VALUES ('2010-01-01 00:00:00');
|
||||||
|
@ -810,6 +810,7 @@ DROP TABLE t1;
|
|||||||
CREATE TABLE t1 (c1 timestamp);
|
CREATE TABLE t1 (c1 timestamp);
|
||||||
SELECT MIN(t1.c1) AS k1 FROM t1 HAVING (k1 >= ALL(SELECT 'a' UNION SELECT 'r'));
|
SELECT MIN(t1.c1) AS k1 FROM t1 HAVING (k1 >= ALL(SELECT 'a' UNION SELECT 'r'));
|
||||||
SELECT * FROM t1 HAVING MIN(t1.c1) >= ALL(SELECT 'a' UNION SELECT 'r');
|
SELECT * FROM t1 HAVING MIN(t1.c1) >= ALL(SELECT 'a' UNION SELECT 'r');
|
||||||
|
SELECT * FROM t1 HAVING MIN(t1.c1) > 0;
|
||||||
DROP TABLE t1;
|
DROP TABLE t1;
|
||||||
|
|
||||||
CREATE TABLE t1 (c1 timestamp);
|
CREATE TABLE t1 (c1 timestamp);
|
||||||
|
@ -380,7 +380,7 @@ public:
|
|||||||
{
|
{
|
||||||
for (uint i= 0; i < arg_count; i++)
|
for (uint i= 0; i < arg_count; i++)
|
||||||
{
|
{
|
||||||
args[i]->no_rows_in_result();
|
args[i]->restore_to_before_no_rows_in_result();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void convert_const_compared_to_int_field(THD *thd);
|
void convert_const_compared_to_int_field(THD *thd);
|
||||||
|
@ -141,10 +141,10 @@ static void update_depend_map_for_order(JOIN *join, ORDER *order);
|
|||||||
static ORDER *remove_const(JOIN *join,ORDER *first_order,COND *cond,
|
static ORDER *remove_const(JOIN *join,ORDER *first_order,COND *cond,
|
||||||
bool change_list, bool *simple_order);
|
bool change_list, bool *simple_order);
|
||||||
static int return_zero_rows(JOIN *join, select_result *res,
|
static int return_zero_rows(JOIN *join, select_result *res,
|
||||||
List<TABLE_LIST> &tables,
|
List<TABLE_LIST> *tables,
|
||||||
List<Item> &fields, bool send_row,
|
List<Item> *fields, bool send_row,
|
||||||
ulonglong select_options, const char *info,
|
ulonglong select_options, const char *info,
|
||||||
Item *having, List<Item> &all_fields);
|
Item *having, List<Item> *all_fields);
|
||||||
static COND *build_equal_items(JOIN *join, COND *cond,
|
static COND *build_equal_items(JOIN *join, COND *cond,
|
||||||
COND_EQUAL *inherited,
|
COND_EQUAL *inherited,
|
||||||
List<TABLE_LIST> *join_list,
|
List<TABLE_LIST> *join_list,
|
||||||
@ -1165,11 +1165,40 @@ int SELECT_LEX::vers_setup_conds(THD *thd, TABLE_LIST *tables)
|
|||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
Check fields, find best join, do the select and output fields.
|
Check fields, find best join, do the select and output fields.
|
||||||
mysql_select assumes that all tables are already opened
|
mysql_select assumes that all tables are already opened
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if we have a field reference. If yes, we have to use
|
||||||
|
mixed_implicit_grouping.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static bool check_list_for_field(List<Item> *items)
|
||||||
|
{
|
||||||
|
List_iterator_fast <Item> select_it(*items);
|
||||||
|
Item *select_el;
|
||||||
|
|
||||||
|
while ((select_el= select_it++))
|
||||||
|
{
|
||||||
|
if (select_el->with_field)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_list_for_field(ORDER *order)
|
||||||
|
{
|
||||||
|
for (; order; order= order->next)
|
||||||
|
{
|
||||||
|
if (order->item[0]->with_field)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Prepare of whole select (including sub queries in future).
|
Prepare of whole select (including sub queries in future).
|
||||||
@ -1248,53 +1277,45 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num,
|
|||||||
DBUG_RETURN(-1);
|
DBUG_RETURN(-1);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TRUE if the SELECT list mixes elements with and without grouping,
|
mixed_implicit_grouping will be set to TRUE if the SELECT list
|
||||||
and there is no GROUP BY clause. Mixing non-aggregated fields with
|
mixes elements with and without grouping, and there is no GROUP BY
|
||||||
aggregate functions in the SELECT list is a MySQL extenstion that
|
clause.
|
||||||
is allowed only if the ONLY_FULL_GROUP_BY sql mode is not set.
|
Mixing non-aggregated fields with aggregate functions in the
|
||||||
|
SELECT list or HAVING is a MySQL extension that is allowed only if
|
||||||
|
the ONLY_FULL_GROUP_BY sql mode is not set.
|
||||||
*/
|
*/
|
||||||
mixed_implicit_grouping= false;
|
mixed_implicit_grouping= false;
|
||||||
if ((~thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
|
if ((~thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
|
||||||
select_lex->with_sum_func && !group_list)
|
select_lex->with_sum_func && !group_list)
|
||||||
{
|
{
|
||||||
List_iterator_fast <Item> select_it(fields_list);
|
if (check_list_for_field(&fields_list) ||
|
||||||
Item *select_el; /* Element of the SELECT clause, can be an expression. */
|
check_list_for_field(order))
|
||||||
bool found_field_elem= false;
|
|
||||||
bool found_sum_func_elem= false;
|
|
||||||
|
|
||||||
while ((select_el= select_it++))
|
|
||||||
{
|
{
|
||||||
if (select_el->with_sum_func())
|
TABLE_LIST *tbl;
|
||||||
found_sum_func_elem= true;
|
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
|
||||||
if (select_el->with_field)
|
|
||||||
found_field_elem= true;
|
mixed_implicit_grouping= true; // mark for future
|
||||||
if (found_sum_func_elem && found_field_elem)
|
|
||||||
|
while ((tbl= li++))
|
||||||
{
|
{
|
||||||
mixed_implicit_grouping= true;
|
/*
|
||||||
break;
|
If the query uses implicit grouping where the select list
|
||||||
|
contains both aggregate functions and non-aggregate fields,
|
||||||
|
any non-aggregated field may produce a NULL value. Set all
|
||||||
|
fields of each table as nullable before semantic analysis to
|
||||||
|
take into account this change of nullability.
|
||||||
|
|
||||||
|
Note: this loop doesn't touch tables inside merged
|
||||||
|
semi-joins, because subquery-to-semijoin conversion has not
|
||||||
|
been done yet. This is intended.
|
||||||
|
*/
|
||||||
|
if (tbl->table)
|
||||||
|
tbl->table->maybe_null= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table_count= select_lex->leaf_tables.elements;
|
table_count= select_lex->leaf_tables.elements;
|
||||||
|
|
||||||
TABLE_LIST *tbl;
|
|
||||||
List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
|
|
||||||
while ((tbl= li++))
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
If the query uses implicit grouping where the select list contains both
|
|
||||||
aggregate functions and non-aggregate fields, any non-aggregated field
|
|
||||||
may produce a NULL value. Set all fields of each table as nullable before
|
|
||||||
semantic analysis to take into account this change of nullability.
|
|
||||||
|
|
||||||
Note: this loop doesn't touch tables inside merged semi-joins, because
|
|
||||||
subquery-to-semijoin conversion has not been done yet. This is intended.
|
|
||||||
*/
|
|
||||||
if (mixed_implicit_grouping && tbl->table)
|
|
||||||
tbl->table->maybe_null= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint real_og_num= og_num;
|
uint real_og_num= og_num;
|
||||||
if (skip_order_by &&
|
if (skip_order_by &&
|
||||||
select_lex != select_lex->master_unit()->global_parameters())
|
select_lex != select_lex->master_unit()->global_parameters())
|
||||||
@ -3838,7 +3859,7 @@ bool JOIN::make_aggr_tables_info()
|
|||||||
set_items_ref_array(items0);
|
set_items_ref_array(items0);
|
||||||
if (join_tab)
|
if (join_tab)
|
||||||
join_tab[exec_join_tab_cnt() + aggr_tables - 1].next_select=
|
join_tab[exec_join_tab_cnt() + aggr_tables - 1].next_select=
|
||||||
setup_end_select_func(this, NULL);
|
setup_end_select_func(this);
|
||||||
group= has_group_by;
|
group= has_group_by;
|
||||||
|
|
||||||
DBUG_RETURN(false);
|
DBUG_RETURN(false);
|
||||||
@ -4216,13 +4237,7 @@ JOIN::reinit()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset of sum functions */
|
clear_sum_funcs();
|
||||||
if (sum_funcs)
|
|
||||||
{
|
|
||||||
Item_sum *func, **func_ptr= sum_funcs;
|
|
||||||
while ((func= *(func_ptr++)))
|
|
||||||
func->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (no_rows_in_result_called)
|
if (no_rows_in_result_called)
|
||||||
{
|
{
|
||||||
@ -4507,12 +4522,12 @@ void JOIN::exec_inner()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
(void) return_zero_rows(this, result, select_lex->leaf_tables,
|
(void) return_zero_rows(this, result, &select_lex->leaf_tables,
|
||||||
*columns_list,
|
columns_list,
|
||||||
send_row_on_empty_set(),
|
send_row_on_empty_set(),
|
||||||
select_options,
|
select_options,
|
||||||
zero_result_cause,
|
zero_result_cause,
|
||||||
having ? having : tmp_having, all_fields);
|
having ? having : tmp_having, &all_fields);
|
||||||
DBUG_VOID_RETURN;
|
DBUG_VOID_RETURN;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14768,10 +14783,36 @@ ORDER *simple_remove_const(ORDER *order, COND *where)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set all fields in the table to have a null value
|
||||||
|
|
||||||
|
@param tables Table list
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void make_tables_null_complemented(List<TABLE_LIST> *tables)
|
||||||
|
{
|
||||||
|
List_iterator<TABLE_LIST> ti(*tables);
|
||||||
|
TABLE_LIST *table;
|
||||||
|
while ((table= ti++))
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Don't touch semi-join materialization tables, as the a join_free()
|
||||||
|
call may have freed them (and HAVING clause can't have references to
|
||||||
|
them anyway).
|
||||||
|
*/
|
||||||
|
if (!table->is_jtbm())
|
||||||
|
{
|
||||||
|
TABLE *tbl= table->table;
|
||||||
|
mark_as_null_row(tbl); // Set fields to NULL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
|
return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> *tables,
|
||||||
List<Item> &fields, bool send_row, ulonglong select_options,
|
List<Item> *fields, bool send_row, ulonglong select_options,
|
||||||
const char *info, Item *having, List<Item> &all_fields)
|
const char *info, Item *having, List<Item> *all_fields)
|
||||||
{
|
{
|
||||||
DBUG_ENTER("return_zero_rows");
|
DBUG_ENTER("return_zero_rows");
|
||||||
|
|
||||||
@ -14787,24 +14828,15 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
|
|||||||
Set all tables to have NULL row. This is needed as we will be evaluating
|
Set all tables to have NULL row. This is needed as we will be evaluating
|
||||||
HAVING condition.
|
HAVING condition.
|
||||||
*/
|
*/
|
||||||
List_iterator<TABLE_LIST> ti(tables);
|
make_tables_null_complemented(tables);
|
||||||
TABLE_LIST *table;
|
|
||||||
while ((table= ti++))
|
List_iterator_fast<Item> it(*all_fields);
|
||||||
{
|
|
||||||
/*
|
|
||||||
Don't touch semi-join materialization tables, as the above join_free()
|
|
||||||
call has freed them (and HAVING clause can't have references to them
|
|
||||||
anyway).
|
|
||||||
*/
|
|
||||||
if (!table->is_jtbm())
|
|
||||||
mark_as_null_row(table->table); // All fields are NULL
|
|
||||||
}
|
|
||||||
List_iterator_fast<Item> it(all_fields);
|
|
||||||
Item *item;
|
Item *item;
|
||||||
/*
|
/*
|
||||||
Inform all items (especially aggregating) to calculate HAVING correctly,
|
Inform all items (especially aggregating) to calculate HAVING correctly,
|
||||||
also we will need it for sending results.
|
also we will need it for sending results.
|
||||||
*/
|
*/
|
||||||
|
join->no_rows_in_result_called= 1;
|
||||||
while ((item= it++))
|
while ((item= it++))
|
||||||
item->no_rows_in_result();
|
item->no_rows_in_result();
|
||||||
if (having && having->val_int() == 0)
|
if (having && having->val_int() == 0)
|
||||||
@ -14818,12 +14850,12 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
|
|||||||
join->thd->limit_found_rows= 0;
|
join->thd->limit_found_rows= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(result->send_result_set_metadata(fields,
|
if (!(result->send_result_set_metadata(*fields,
|
||||||
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)))
|
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)))
|
||||||
{
|
{
|
||||||
bool send_error= FALSE;
|
bool send_error= FALSE;
|
||||||
if (send_row)
|
if (send_row)
|
||||||
send_error= result->send_data_with_check(fields, join->unit, 0) > 0;
|
send_error= result->send_data_with_check(*fields, join->unit, 0) > 0;
|
||||||
if (likely(!send_error))
|
if (likely(!send_error))
|
||||||
result->send_eof(); // Should be safe
|
result->send_eof(); // Should be safe
|
||||||
}
|
}
|
||||||
@ -14839,49 +14871,42 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
used only in JOIN::clear (always) and in do_select()
|
Reset table rows to contain a null-complement row (all fields are null)
|
||||||
(if there where no matching rows)
|
|
||||||
|
Used only in JOIN::clear() and in do_select() if there where no matching rows.
|
||||||
|
|
||||||
@param join JOIN
|
@param join JOIN
|
||||||
@param cleared_tables If not null, clear also const tables and mark all
|
@param cleared_tables Used to mark all cleared tables in the map. Needed for
|
||||||
cleared tables in the map. cleared_tables is only
|
unclear_tables() to know which tables to restore to
|
||||||
set when called from do_select() when there is a
|
their original state.
|
||||||
group function and there where no matching rows.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void clear_tables(JOIN *join, table_map *cleared_tables)
|
static void clear_tables(JOIN *join, table_map *cleared_tables)
|
||||||
{
|
{
|
||||||
/*
|
DBUG_ASSERT(cleared_tables);
|
||||||
must clear only the non-const tables as const tables are not re-calculated.
|
|
||||||
*/
|
|
||||||
for (uint i= 0 ; i < join->table_count ; i++)
|
for (uint i= 0 ; i < join->table_count ; i++)
|
||||||
{
|
{
|
||||||
TABLE *table= join->table[i];
|
TABLE *table= join->table[i];
|
||||||
|
|
||||||
if (table->null_row)
|
if (table->null_row)
|
||||||
continue; // Nothing more to do
|
continue; // Nothing more to do
|
||||||
if (!(table->map & join->const_table_map) || cleared_tables)
|
(*cleared_tables)|= (((table_map) 1) << i);
|
||||||
|
if (table->s->null_bytes)
|
||||||
{
|
{
|
||||||
if (cleared_tables)
|
/*
|
||||||
{
|
Remember null bits for the record so that we can restore the
|
||||||
(*cleared_tables)|= (((table_map) 1) << i);
|
original const record in unclear_tables()
|
||||||
if (table->s->null_bytes)
|
*/
|
||||||
{
|
memcpy(table->record[1], table->null_flags, table->s->null_bytes);
|
||||||
/*
|
|
||||||
Remember null bits for the record so that we can restore the
|
|
||||||
original const record in unclear_tables()
|
|
||||||
*/
|
|
||||||
memcpy(table->record[1], table->null_flags, table->s->null_bytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mark_as_null_row(table); // All fields are NULL
|
|
||||||
}
|
}
|
||||||
|
mark_as_null_row(table); // All fields are NULL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reverse null marking for tables and restore null bits.
|
Reverse null marking for tables and restore null bits.
|
||||||
|
This return the tables to the state of before clear_tables().
|
||||||
|
|
||||||
We have to do this because the tables may be re-used in a sub query
|
We have to do this because the tables may be re-used in a sub query
|
||||||
and the subquery will assume that the const tables contains the original
|
and the subquery will assume that the const tables contains the original
|
||||||
@ -20519,9 +20544,9 @@ void set_postjoin_aggr_write_func(JOIN_TAB *tab)
|
|||||||
end_select function to use. This function can't fail.
|
end_select function to use. This function can't fail.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Next_select_func setup_end_select_func(JOIN *join, JOIN_TAB *tab)
|
Next_select_func setup_end_select_func(JOIN *join)
|
||||||
{
|
{
|
||||||
TMP_TABLE_PARAM *tmp_tbl= tab ? tab->tmp_table_param : &join->tmp_table_param;
|
TMP_TABLE_PARAM *tmp_tbl= &join->tmp_table_param;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Choose method for presenting result to user. Use end_send_group
|
Choose method for presenting result to user. Use end_send_group
|
||||||
@ -20591,7 +20616,7 @@ do_select(JOIN *join, Procedure *procedure)
|
|||||||
join->duplicate_rows= join->send_records=0;
|
join->duplicate_rows= join->send_records=0;
|
||||||
if (join->only_const_tables() && !join->need_tmp)
|
if (join->only_const_tables() && !join->need_tmp)
|
||||||
{
|
{
|
||||||
Next_select_func end_select= setup_end_select_func(join, NULL);
|
Next_select_func end_select= setup_end_select_func(join);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
HAVING will be checked after processing aggregate functions,
|
HAVING will be checked after processing aggregate functions,
|
||||||
@ -21077,6 +21102,7 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Restore state if mark_as_null_row() have been called */
|
||||||
if (join_tab->last_inner)
|
if (join_tab->last_inner)
|
||||||
{
|
{
|
||||||
JOIN_TAB *last_inner_tab= join_tab->last_inner;
|
JOIN_TAB *last_inner_tab= join_tab->last_inner;
|
||||||
@ -22461,11 +22487,18 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
{
|
{
|
||||||
int idx= -1;
|
int idx= -1;
|
||||||
enum_nested_loop_state ok_code= NESTED_LOOP_OK;
|
enum_nested_loop_state ok_code= NESTED_LOOP_OK;
|
||||||
|
/*
|
||||||
|
join_tab can be 0 in the case all tables are const tables and we did not
|
||||||
|
need a temporary table to store the result.
|
||||||
|
In this case we use the original given fields, which is stored in
|
||||||
|
join->fields.
|
||||||
|
*/
|
||||||
List<Item> *fields= join_tab ? (join_tab-1)->fields : join->fields;
|
List<Item> *fields= join_tab ? (join_tab-1)->fields : join->fields;
|
||||||
DBUG_ENTER("end_send_group");
|
DBUG_ENTER("end_send_group");
|
||||||
|
|
||||||
if (!join->items3.is_null() && !join->set_group_rpa)
|
if (!join->items3.is_null() && !join->set_group_rpa)
|
||||||
{
|
{
|
||||||
|
/* Move ref_pointer_array to points to items3 */
|
||||||
join->set_group_rpa= true;
|
join->set_group_rpa= true;
|
||||||
join->set_items_ref_array(join->items3);
|
join->set_items_ref_array(join->items3);
|
||||||
}
|
}
|
||||||
@ -22473,10 +22506,12 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
if (!join->first_record || end_of_records ||
|
if (!join->first_record || end_of_records ||
|
||||||
(idx=test_if_group_changed(join->group_fields)) >= 0)
|
(idx=test_if_group_changed(join->group_fields)) >= 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!join->group_sent &&
|
if (!join->group_sent &&
|
||||||
(join->first_record ||
|
(join->first_record ||
|
||||||
(end_of_records && !join->group && !join->group_optimized_away)))
|
(end_of_records && !join->group && !join->group_optimized_away)))
|
||||||
{
|
{
|
||||||
|
table_map cleared_tables= (table_map) 0;
|
||||||
if (join->procedure)
|
if (join->procedure)
|
||||||
join->procedure->end_group();
|
join->procedure->end_group();
|
||||||
if (idx < (int) join->send_group_parts)
|
if (idx < (int) join->send_group_parts)
|
||||||
@ -22499,11 +22534,13 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
{
|
{
|
||||||
if (!join->first_record)
|
if (!join->first_record)
|
||||||
{
|
{
|
||||||
List_iterator_fast<Item> it(*join->fields);
|
|
||||||
Item *item;
|
|
||||||
/* No matching rows for group function */
|
/* No matching rows for group function */
|
||||||
join->clear();
|
|
||||||
|
|
||||||
|
List_iterator_fast<Item> it(*fields);
|
||||||
|
Item *item;
|
||||||
|
join->no_rows_in_result_called= 1;
|
||||||
|
|
||||||
|
join->clear(&cleared_tables);
|
||||||
while ((item= it++))
|
while ((item= it++))
|
||||||
item->no_rows_in_result();
|
item->no_rows_in_result();
|
||||||
}
|
}
|
||||||
@ -22531,7 +22568,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
if (join->rollup_send_data((uint) (idx+1)))
|
if (join->rollup_send_data((uint) (idx+1)))
|
||||||
error= 1;
|
error= 1;
|
||||||
}
|
}
|
||||||
}
|
if (join->no_rows_in_result_called)
|
||||||
|
{
|
||||||
|
/* Restore null tables to original state */
|
||||||
|
join->no_rows_in_result_called= 0;
|
||||||
|
if (cleared_tables)
|
||||||
|
unclear_tables(join, &cleared_tables);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (unlikely(error > 0))
|
if (unlikely(error > 0))
|
||||||
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
|
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
|
||||||
if (end_of_records)
|
if (end_of_records)
|
||||||
@ -22835,6 +22879,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
{
|
{
|
||||||
if (join->first_record || (end_of_records && !join->group))
|
if (join->first_record || (end_of_records && !join->group))
|
||||||
{
|
{
|
||||||
|
table_map cleared_tables= (table_map) 0;
|
||||||
if (join->procedure)
|
if (join->procedure)
|
||||||
join->procedure->end_group();
|
join->procedure->end_group();
|
||||||
int send_group_parts= join->send_group_parts;
|
int send_group_parts= join->send_group_parts;
|
||||||
@ -22843,7 +22888,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
if (!join->first_record)
|
if (!join->first_record)
|
||||||
{
|
{
|
||||||
/* No matching rows for group function */
|
/* No matching rows for group function */
|
||||||
join->clear();
|
join->clear(&cleared_tables);
|
||||||
}
|
}
|
||||||
copy_sum_funcs(join->sum_funcs,
|
copy_sum_funcs(join->sum_funcs,
|
||||||
join->sum_funcs_end[send_group_parts]);
|
join->sum_funcs_end[send_group_parts]);
|
||||||
@ -22866,6 +22911,8 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
|
|||||||
DBUG_RETURN(NESTED_LOOP_ERROR);
|
DBUG_RETURN(NESTED_LOOP_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cleared_tables)
|
||||||
|
unclear_tables(join, &cleared_tables);
|
||||||
if (end_of_records)
|
if (end_of_records)
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
@ -26965,11 +27012,8 @@ int JOIN::rollup_write_data(uint idx, TMP_TABLE_PARAM *tmp_table_param_arg, TABL
|
|||||||
(end_send_group/end_write_group)
|
(end_send_group/end_write_group)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void JOIN::clear()
|
void inline JOIN::clear_sum_funcs()
|
||||||
{
|
{
|
||||||
clear_tables(this, 0);
|
|
||||||
copy_fields(&tmp_table_param);
|
|
||||||
|
|
||||||
if (sum_funcs)
|
if (sum_funcs)
|
||||||
{
|
{
|
||||||
Item_sum *func, **func_ptr= sum_funcs;
|
Item_sum *func, **func_ptr= sum_funcs;
|
||||||
@ -26979,6 +27023,22 @@ void JOIN::clear()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prepare for returning 'empty row' when there is no matching row.
|
||||||
|
|
||||||
|
- Mark all tables with mark_as_null_row()
|
||||||
|
- Make a copy of of all simple SELECT items
|
||||||
|
- Reset all sum functions to NULL or 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
void JOIN::clear(table_map *cleared_tables)
|
||||||
|
{
|
||||||
|
clear_tables(this, cleared_tables);
|
||||||
|
copy_fields(&tmp_table_param);
|
||||||
|
clear_sum_funcs();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Print an EXPLAIN line with all NULLs and given message in the 'Extra' column
|
Print an EXPLAIN line with all NULLs and given message in the 'Extra' column
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ enum sj_strategy_enum
|
|||||||
|
|
||||||
typedef enum_nested_loop_state
|
typedef enum_nested_loop_state
|
||||||
(*Next_select_func)(JOIN *, struct st_join_table *, bool);
|
(*Next_select_func)(JOIN *, struct st_join_table *, bool);
|
||||||
Next_select_func setup_end_select_func(JOIN *join, JOIN_TAB *tab);
|
Next_select_func setup_end_select_func(JOIN *join);
|
||||||
int rr_sequential(READ_RECORD *info);
|
int rr_sequential(READ_RECORD *info);
|
||||||
int rr_sequential_and_unpack(READ_RECORD *info);
|
int rr_sequential_and_unpack(READ_RECORD *info);
|
||||||
Item *remove_pushed_top_conjuncts(THD *thd, Item *cond);
|
Item *remove_pushed_top_conjuncts(THD *thd, Item *cond);
|
||||||
@ -1767,7 +1767,8 @@ public:
|
|||||||
void join_free();
|
void join_free();
|
||||||
/** Cleanup this JOIN, possibly for reuse */
|
/** Cleanup this JOIN, possibly for reuse */
|
||||||
void cleanup(bool full);
|
void cleanup(bool full);
|
||||||
void clear();
|
void clear(table_map *cleared_tables);
|
||||||
|
void inline clear_sum_funcs();
|
||||||
bool send_row_on_empty_set()
|
bool send_row_on_empty_set()
|
||||||
{
|
{
|
||||||
return (do_send_rows && implicit_grouping && !group_optimized_away &&
|
return (do_send_rows && implicit_grouping && !group_optimized_away &&
|
||||||
|
10
sql/table.h
10
sql/table.h
@ -3306,10 +3306,16 @@ inline void mark_as_null_row(TABLE *table)
|
|||||||
bfill(table->null_flags,table->s->null_bytes,255);
|
bfill(table->null_flags,table->s->null_bytes,255);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Restore table to state before mark_as_null_row() call.
|
||||||
|
This assumes that the caller has restored table->null_flags,
|
||||||
|
as is done in unclear_tables().
|
||||||
|
*/
|
||||||
|
|
||||||
inline void unmark_as_null_row(TABLE *table)
|
inline void unmark_as_null_row(TABLE *table)
|
||||||
{
|
{
|
||||||
table->null_row=0;
|
table->null_row= 0;
|
||||||
table->status= STATUS_NO_RECORD;
|
table->status&= ~STATUS_NULL_ROW;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_simple_order(ORDER *order);
|
bool is_simple_order(ORDER *order);
|
||||||
|
Reference in New Issue
Block a user