summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMonty <monty@mariadb.org>2023-05-02 22:30:57 +0300
committerMonty <monty@mariadb.org>2023-05-02 23:43:12 +0300
commit7f96dd50e25abc2a9a75d96cc1c711124b6be765 (patch)
treefbfd42b83a80f834d3db2c783b29c9a9bb8c8a90
parent4f7317579e700a8ac4375b3b85e5fb1a91a1a20f (diff)
downloadmariadb-git-7f96dd50e25abc2a9a75d96cc1c711124b6be765.tar.gz
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.
-rw-r--r--mysql-test/main/group_min_max.result113
-rw-r--r--mysql-test/main/group_min_max.test115
-rw-r--r--mysql-test/main/type_timestamp.result2
-rw-r--r--mysql-test/main/type_timestamp.test1
-rw-r--r--sql/item_func.h2
-rw-r--r--sql/sql_select.cc264
-rw-r--r--sql/sql_select.h5
-rw-r--r--sql/table.h10
8 files changed, 405 insertions, 107 deletions
diff --git a/mysql-test/main/group_min_max.result b/mysql-test/main/group_min_max.result
index fd9b5be4260..4c2693e5e4c 100644
--- a/mysql-test/main/group_min_max.result
+++ b/mysql-test/main/group_min_max.result
@@ -4025,3 +4025,116 @@ drop table t1;
#
# End of 10.1 tests
#
+#
+# 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.4 tests
+#
diff --git a/mysql-test/main/group_min_max.test b/mysql-test/main/group_min_max.test
index 506323599cb..3c5c1b5bb9b 100644
--- a/mysql-test/main/group_min_max.test
+++ b/mysql-test/main/group_min_max.test
@@ -5,6 +5,7 @@
--source include/no_valgrind_without_big.inc
--source include/default_optimizer_switch.inc
+--source include/have_innodb.inc
#
# TODO:
@@ -1688,3 +1689,117 @@ drop table t1;
--echo #
--echo # End of 10.1 tests
--echo #
+
+--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 # End of 10.4 tests
+--echo #
diff --git a/mysql-test/main/type_timestamp.result b/mysql-test/main/type_timestamp.result
index 58cb12ae267..dbccee08ada 100644
--- a/mysql-test/main/type_timestamp.result
+++ b/mysql-test/main/type_timestamp.result
@@ -1230,6 +1230,8 @@ SELECT * FROM t1 HAVING MIN(t1.c1) >= ALL(SELECT 'a' UNION SELECT 'r');
c1
Warnings:
Warning 1292 Truncated incorrect datetime value: 'r'
+SELECT * FROM t1 HAVING MIN(t1.c1) > 0;
+c1
DROP TABLE t1;
CREATE TABLE t1 (c1 timestamp);
INSERT INTO t1 VALUES ('2010-01-01 00:00:00');
diff --git a/mysql-test/main/type_timestamp.test b/mysql-test/main/type_timestamp.test
index f12cc2a4bc3..c8517656071 100644
--- a/mysql-test/main/type_timestamp.test
+++ b/mysql-test/main/type_timestamp.test
@@ -810,6 +810,7 @@ DROP TABLE t1;
CREATE TABLE t1 (c1 timestamp);
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) > 0;
DROP TABLE t1;
CREATE TABLE t1 (c1 timestamp);
diff --git a/sql/item_func.h b/sql/item_func.h
index 41f2a52616d..3afe0d00661 100644
--- a/sql/item_func.h
+++ b/sql/item_func.h
@@ -381,7 +381,7 @@ public:
{
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);
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index 56a185acdd5..2be1fea9b03 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -142,10 +142,10 @@ static void update_depend_map_for_order(JOIN *join, ORDER *order);
static ORDER *remove_const(JOIN *join,ORDER *first_order,COND *cond,
bool change_list, bool *simple_order);
static int return_zero_rows(JOIN *join, select_result *res,
- List<TABLE_LIST> &tables,
- List<Item> &fields, bool send_row,
+ List<TABLE_LIST> *tables,
+ List<Item> *fields, bool send_row,
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,
COND_EQUAL *inherited,
List<TABLE_LIST> *join_list,
@@ -1157,11 +1157,40 @@ int SELECT_LEX::vers_setup_conds(THD *thd, TABLE_LIST *tables)
DBUG_RETURN(0);
}
+
/*****************************************************************************
Check fields, find best join, do the select and output fields.
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).
@@ -1241,53 +1270,45 @@ JOIN::prepare(TABLE_LIST *tables_init,
DBUG_RETURN(-1);
/*
- TRUE if the SELECT list mixes elements with and without grouping,
- and there is no GROUP BY clause. Mixing non-aggregated fields with
- aggregate functions in the SELECT list is a MySQL extenstion that
- is allowed only if the ONLY_FULL_GROUP_BY sql mode is not set.
+ mixed_implicit_grouping will be set to TRUE if the SELECT list
+ mixes elements with and without grouping, and there is no GROUP BY
+ clause.
+ 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;
if ((~thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY) &&
select_lex->with_sum_func && !group_list)
{
- List_iterator_fast <Item> select_it(fields_list);
- Item *select_el; /* Element of the SELECT clause, can be an expression. */
- bool found_field_elem= false;
- bool found_sum_func_elem= false;
-
- while ((select_el= select_it++))
+ if (check_list_for_field(&fields_list) ||
+ check_list_for_field(order))
{
- if (select_el->with_sum_func())
- found_sum_func_elem= true;
- if (select_el->with_field)
- found_field_elem= true;
- if (found_sum_func_elem && found_field_elem)
+ TABLE_LIST *tbl;
+ List_iterator_fast<TABLE_LIST> li(select_lex->leaf_tables);
+
+ mixed_implicit_grouping= true; // mark for future
+
+ 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_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;
if (skip_order_by &&
select_lex != select_lex->master_unit()->global_parameters())
@@ -3838,7 +3859,7 @@ bool JOIN::make_aggr_tables_info()
set_items_ref_array(items0);
if (join_tab)
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;
DBUG_RETURN(false);
@@ -4220,13 +4241,7 @@ JOIN::reinit()
}
}
- /* Reset of sum functions */
- if (sum_funcs)
- {
- Item_sum *func, **func_ptr= sum_funcs;
- while ((func= *(func_ptr++)))
- func->clear();
- }
+ clear_sum_funcs();
if (no_rows_in_result_called)
{
@@ -4510,12 +4525,12 @@ void JOIN::exec_inner()
}
else
{
- (void) return_zero_rows(this, result, select_lex->leaf_tables,
- *columns_list,
+ (void) return_zero_rows(this, result, &select_lex->leaf_tables,
+ columns_list,
send_row_on_empty_set(),
select_options,
zero_result_cause,
- having ? having : tmp_having, all_fields);
+ having ? having : tmp_having, &all_fields);
DBUG_VOID_RETURN;
}
}
@@ -14626,10 +14641,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
-return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
- List<Item> &fields, bool send_row, ulonglong select_options,
- const char *info, Item *having, List<Item> &all_fields)
+return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> *tables,
+ List<Item> *fields, bool send_row, ulonglong select_options,
+ const char *info, Item *having, List<Item> *all_fields)
{
DBUG_ENTER("return_zero_rows");
@@ -14645,24 +14686,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
HAVING condition.
*/
- List_iterator<TABLE_LIST> ti(tables);
- TABLE_LIST *table;
- while ((table= ti++))
- {
- /*
- 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);
+ make_tables_null_complemented(tables);
+
+ List_iterator_fast<Item> it(*all_fields);
Item *item;
/*
Inform all items (especially aggregating) to calculate HAVING correctly,
also we will need it for sending results.
*/
+ join->no_rows_in_result_called= 1;
while ((item= it++))
item->no_rows_in_result();
if (having && having->val_int() == 0)
@@ -14676,12 +14708,12 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
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)))
{
bool send_error= FALSE;
if (send_row)
- send_error= result->send_data(fields) > 0;
+ send_error= result->send_data(*fields) > 0;
if (likely(!send_error))
result->send_eof(); // Should be safe
}
@@ -14697,49 +14729,42 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
}
/**
- used only in JOIN::clear (always) and in do_select()
- (if there where no matching rows)
+ Reset table rows to contain a null-complement row (all fields are null)
+
+ Used only in JOIN::clear() and in do_select() if there where no matching rows.
@param join JOIN
- @param cleared_tables If not null, clear also const tables and mark all
- cleared tables in the map. cleared_tables is only
- set when called from do_select() when there is a
- group function and there where no matching rows.
+ @param cleared_tables Used to mark all cleared tables in the map. Needed for
+ unclear_tables() to know which tables to restore to
+ their original state.
*/
static void clear_tables(JOIN *join, table_map *cleared_tables)
{
- /*
- must clear only the non-const tables as const tables are not re-calculated.
- */
+ DBUG_ASSERT(cleared_tables);
for (uint i= 0 ; i < join->table_count ; i++)
{
TABLE *table= join->table[i];
if (table->null_row)
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)
- {
- (*cleared_tables)|= (((table_map) 1) << i);
- if (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
+ /*
+ 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
}
}
/**
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
and the subquery will assume that the const tables contains the original
@@ -20216,9 +20241,9 @@ void set_postjoin_aggr_write_func(JOIN_TAB *tab)
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
@@ -20289,7 +20314,7 @@ do_select(JOIN *join, Procedure *procedure)
join->duplicate_rows= join->send_records=0;
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,
@@ -20764,6 +20789,7 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
{
DBUG_ENTER("sub_select");
+ /* Restore state if mark_as_null_row() have been called */
if (join_tab->last_inner)
{
JOIN_TAB *last_inner_tab= join_tab->last_inner;
@@ -22126,11 +22152,18 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
int idx= -1;
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;
DBUG_ENTER("end_send_group");
if (!join->items3.is_null() && !join->set_group_rpa)
{
+ /* Move ref_pointer_array to points to items3 */
join->set_group_rpa= true;
join->set_items_ref_array(join->items3);
}
@@ -22138,10 +22171,12 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (!join->first_record || end_of_records ||
(idx=test_if_group_changed(join->group_fields)) >= 0)
{
+
if (!join->group_sent &&
(join->first_record ||
(end_of_records && !join->group && !join->group_optimized_away)))
{
+ table_map cleared_tables= (table_map) 0;
if (join->procedure)
join->procedure->end_group();
if (idx < (int) join->send_group_parts)
@@ -22164,11 +22199,13 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
if (!join->first_record)
{
- List_iterator_fast<Item> it(*join->fields);
- Item *item;
/* 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++))
item->no_rows_in_result();
}
@@ -22194,7 +22231,14 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (join->rollup_send_data((uint) (idx+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))
DBUG_RETURN(NESTED_LOOP_ERROR); /* purecov: inspected */
if (end_of_records)
@@ -22496,6 +22540,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
{
if (join->first_record || (end_of_records && !join->group))
{
+ table_map cleared_tables= (table_map) 0;
if (join->procedure)
join->procedure->end_group();
int send_group_parts= join->send_group_parts;
@@ -22504,7 +22549,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (!join->first_record)
{
/* No matching rows for group function */
- join->clear();
+ join->clear(&cleared_tables);
}
copy_sum_funcs(join->sum_funcs,
join->sum_funcs_end[send_group_parts]);
@@ -22527,6 +22572,8 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
DBUG_RETURN(NESTED_LOOP_ERROR);
}
}
+ if (cleared_tables)
+ unclear_tables(join, &cleared_tables);
if (end_of_records)
goto end;
}
@@ -26629,11 +26676,8 @@ int JOIN::rollup_write_data(uint idx, TMP_TABLE_PARAM *tmp_table_param_arg, TABL
(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)
{
Item_sum *func, **func_ptr= sum_funcs;
@@ -26643,6 +26687,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
diff --git a/sql/sql_select.h b/sql/sql_select.h
index 0dfecc98a48..4bac201c3b4 100644
--- a/sql/sql_select.h
+++ b/sql/sql_select.h
@@ -227,7 +227,7 @@ enum sj_strategy_enum
typedef enum_nested_loop_state
(*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_and_unpack(READ_RECORD *info);
Item *remove_pushed_top_conjuncts(THD *thd, Item *cond);
@@ -1730,7 +1730,8 @@ public:
void join_free();
/** Cleanup this JOIN, possibly for reuse */
void cleanup(bool full);
- void clear();
+ void clear(table_map *cleared_tables);
+ void inline clear_sum_funcs();
bool send_row_on_empty_set()
{
return (do_send_rows && implicit_grouping && !group_optimized_away &&
diff --git a/sql/table.h b/sql/table.h
index 6f7f4e63473..1414659b78f 100644
--- a/sql/table.h
+++ b/sql/table.h
@@ -3337,10 +3337,16 @@ inline void mark_as_null_row(TABLE *table)
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)
{
- table->null_row=0;
- table->status= STATUS_NO_RECORD;
+ table->null_row= 0;
+ table->status&= ~STATUS_NULL_ROW;
}
bool is_simple_order(ORDER *order);