From 65fca9d6c197e4f3c449e2ab2346d02c053137ef Mon Sep 17 00:00:00 2001 From: Jaeheon Shim Date: Wed, 12 Aug 2026 17:16:56 -0400 Subject: [PATCH] MDEV-40698 Fix ROLLUP query results with empty result set ROLLUP is defined as the UNION of grouping by every prefix of fields in the original GROUP BY list. A ROLLUP query with an empty result set returned zero rows when it should return a summary NULL, since grouping by the empty prefix returns a single summary row. The empty row case is handled in two separate locations. First, if the optimizer is able to determine that no rows will be produced, e.g. due to the table being empty or the WHERE condition resolving to false, JOIN::send_row_on_empty_set is used to determine whether or not to send an empty result row. Therefore, send_row_on_empty_set is modified to include select_lex->olap == ROLLUP_TYPE. Second, it may be the case that the absence of rows is not confirmed until the execution phase. For instance when the WHERE condition is not constant, or in the case of InnoDB where an empty table is not detected during optimization. This is handled in both end_send_group and end_write_group by this expression join->first_record || (end_of_records && !join->group && !join->group_optimized_away) The condition is extracted into need_empty_set_row and a second variable empty_set_send_rollup_total is recorded to prevent running the default rollup_send_data/rollup_write_data on the empty row case. This is because the null summary row is already handled by send_data_with_check. --- mysql-test/main/mdev_40698.result | 54 +++++++++++++++++++ mysql-test/main/mdev_40698.test | 30 +++++++++++ mysql-test/main/ps_error.result | 1 + mysql-test/main/win.result | 1 + .../encryption/r/tempfiles_encrypted.result | 1 + sql/opt_subselect.cc | 2 +- sql/sql_select.cc | 18 +++++-- sql/sql_select.h | 19 ++++++- 8 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 mysql-test/main/mdev_40698.result create mode 100644 mysql-test/main/mdev_40698.test diff --git a/mysql-test/main/mdev_40698.result b/mysql-test/main/mdev_40698.result new file mode 100644 index 0000000000000..3c51bf2d37d84 --- /dev/null +++ b/mysql-test/main/mdev_40698.result @@ -0,0 +1,54 @@ +# +# MDEV-40698: ROLLUP query returns incorrect result with empty result set +# +CREATE TABLE t0(a INT, b INT, c INT); +SELECT *, COUNT(0) FROM t0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT *, COUNT(0) FROM t0 WHERE 1=0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT *, COUNT(0), ROW_NUMBER() OVER () FROM t0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) ROW_NUMBER() OVER () +NULL NULL NULL 0 1 +SELECT 1 IN (SELECT a FROM t0 WHERE a>0 GROUP BY a WITH ROLLUP); +1 IN (SELECT a FROM t0 WHERE a>0 GROUP BY a WITH ROLLUP) +NULL +INSERT INTO t0 VALUES (0, 0, 0),(0, 0, 0); +SELECT *, COUNT(0) FROM t0 WHERE a > 0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT EXISTS (SELECT t0.a FROM t0 x WHERE 1=0 GROUP BY x.a WITH ROLLUP) AS e +FROM t0; +e +1 +1 +SELECT DISTINCT a FROM t0 GROUP BY a, b WITH ROLLUP; +a +0 +NULL +CREATE TABLE t1 (a INT, b INT, c INT) ENGINE=InnoDB; +SELECT *, COUNT(0) FROM t1 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT *, COUNT(0) FROM t1 WHERE 1=0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT *, COUNT(0), ROW_NUMBER() OVER () FROM t1 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) ROW_NUMBER() OVER () +NULL NULL NULL 0 1 +INSERT INTO t1 VALUES (0, 0, 0),(0, 0, 0); +SELECT *, COUNT(0) FROM t1 WHERE a > 0 GROUP BY a, b, c WITH ROLLUP; +a b c COUNT(0) +NULL NULL NULL 0 +SELECT EXISTS (SELECT t1.a FROM t1 x WHERE 1=0 GROUP BY x.a WITH ROLLUP) AS e +FROM t1; +e +1 +1 +SELECT DISTINCT a FROM t1 GROUP BY a, b WITH ROLLUP; +a +0 +NULL +DROP TABLE t0, t1; +# End of 10.11 tests diff --git a/mysql-test/main/mdev_40698.test b/mysql-test/main/mdev_40698.test new file mode 100644 index 0000000000000..dfe7a0fe68bdb --- /dev/null +++ b/mysql-test/main/mdev_40698.test @@ -0,0 +1,30 @@ +--source include/have_innodb.inc + +--echo # +--echo # MDEV-40698: ROLLUP query returns incorrect result with empty result set +--echo # + +CREATE TABLE t0(a INT, b INT, c INT); +SELECT *, COUNT(0) FROM t0 GROUP BY a, b, c WITH ROLLUP; +SELECT *, COUNT(0) FROM t0 WHERE 1=0 GROUP BY a, b, c WITH ROLLUP; +SELECT *, COUNT(0), ROW_NUMBER() OVER () FROM t0 GROUP BY a, b, c WITH ROLLUP; +SELECT 1 IN (SELECT a FROM t0 WHERE a>0 GROUP BY a WITH ROLLUP); +INSERT INTO t0 VALUES (0, 0, 0),(0, 0, 0); +SELECT *, COUNT(0) FROM t0 WHERE a > 0 GROUP BY a, b, c WITH ROLLUP; +SELECT EXISTS (SELECT t0.a FROM t0 x WHERE 1=0 GROUP BY x.a WITH ROLLUP) AS e + FROM t0; +SELECT DISTINCT a FROM t0 GROUP BY a, b WITH ROLLUP; + +CREATE TABLE t1 (a INT, b INT, c INT) ENGINE=InnoDB; +SELECT *, COUNT(0) FROM t1 GROUP BY a, b, c WITH ROLLUP; +SELECT *, COUNT(0) FROM t1 WHERE 1=0 GROUP BY a, b, c WITH ROLLUP; +SELECT *, COUNT(0), ROW_NUMBER() OVER () FROM t1 GROUP BY a, b, c WITH ROLLUP; +INSERT INTO t1 VALUES (0, 0, 0),(0, 0, 0); +SELECT *, COUNT(0) FROM t1 WHERE a > 0 GROUP BY a, b, c WITH ROLLUP; +SELECT EXISTS (SELECT t1.a FROM t1 x WHERE 1=0 GROUP BY x.a WITH ROLLUP) AS e + FROM t1; +SELECT DISTINCT a FROM t1 GROUP BY a, b WITH ROLLUP; + +DROP TABLE t0, t1; + +--echo # End of 10.11 tests diff --git a/mysql-test/main/ps_error.result b/mysql-test/main/ps_error.result index ad178f869155a..f3fa78a1bbe36 100644 --- a/mysql-test/main/ps_error.result +++ b/mysql-test/main/ps_error.result @@ -10,6 +10,7 @@ EXECUTE stmt; ERROR 22007: Truncated incorrect DOUBLE value: 'foo' SELECT a FROM t1 GROUP BY NULL WITH ROLLUP; a +NULL DROP TABLE t1; SET sql_mode=DEFAULT; SET SQL_MODE= 'STRICT_ALL_TABLES'; diff --git a/mysql-test/main/win.result b/mysql-test/main/win.result index 8f38c424ddf9c..7891c737dfd43 100644 --- a/mysql-test/main/win.result +++ b/mysql-test/main/win.result @@ -3503,6 +3503,7 @@ 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 distinct 1, row_number() over (order by 1) from t1 where a=0 group by a with rollup; 1 row_number() over (order by 1) +1 1 drop table t1; explain SELECT DISTINCT BIT_OR(100) OVER () FROM dual diff --git a/mysql-test/suite/encryption/r/tempfiles_encrypted.result b/mysql-test/suite/encryption/r/tempfiles_encrypted.result index aff2b9306aa73..f856ff1a465f1 100644 --- a/mysql-test/suite/encryption/r/tempfiles_encrypted.result +++ b/mysql-test/suite/encryption/r/tempfiles_encrypted.result @@ -3509,6 +3509,7 @@ 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 distinct 1, row_number() over (order by 1) from t1 where a=0 group by a with rollup; 1 row_number() over (order by 1) +1 1 drop table t1; explain SELECT DISTINCT BIT_OR(100) OVER () FROM dual diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index d02a7678073af..5a99526a3ab79 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -6805,7 +6805,7 @@ bool JOIN::choose_tableless_subquery_plan() */ if (zero_result_cause) { - if (!implicit_grouping) + if (!implicit_grouping && select_lex->olap != ROLLUP_TYPE) { /* Both group by queries and non-group by queries without aggregate diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f0ef090d25059..183c29a933930 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -16825,6 +16825,10 @@ return_zero_rows(JOIN *join, select_result *result, List *tables, join->thd->set_examined_row_count(0); join->thd->limit_found_rows= 0; } + else + { + join->thd->limit_found_rows= send_row ? 1 : 0; + } if (!(result->send_result_set_metadata(*fields, Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))) @@ -24711,6 +24715,7 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) join->fields. */ List *fields= join_tab ? (join_tab-1)->fields : join->fields; + bool empty_set_send_rollup_total= false; DBUG_ENTER("end_send_group"); if (!join->items3.is_null() && !join->set_group_rpa) @@ -24726,7 +24731,8 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) if (!join->group_sent && (join->first_record || - (end_of_records && !join->group && !join->group_optimized_away))) + join->need_empty_set_row(end_of_records, + &empty_set_send_rollup_total))) { table_map cleared_tables= (table_map) 0; if (join->procedure) @@ -24782,7 +24788,8 @@ end_send_group(JOIN *join, JOIN_TAB *join_tab, bool end_of_records) join->send_records++; join->group_sent= true; } - if (unlikely(join->rollup.state != ROLLUP::STATE_NONE && error <= 0)) + if (unlikely(join->rollup.state != ROLLUP::STATE_NONE && + !empty_set_send_rollup_total && error <= 0)) { if (join->rollup_send_data((uint) (idx+1))) error= 1; @@ -25103,13 +25110,15 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), { TABLE *table= join_tab->table; int idx= -1; + bool empty_set_send_rollup_total= false; DBUG_ENTER("end_write_group"); join->accepted_rows++; if (!join->first_record || end_of_records || (idx=test_if_group_changed(join->group_fields)) >= 0) { - if (join->first_record || (end_of_records && !join->group)) + if (join->first_record || + join->need_empty_set_row(end_of_records, &empty_set_send_rollup_total)) { table_map cleared_tables= (table_map) 0; if (join->procedure) @@ -25134,7 +25143,8 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)), error, 0, NULL)) DBUG_RETURN(NESTED_LOOP_ERROR); } - if (unlikely(join->rollup.state != ROLLUP::STATE_NONE)) + if (unlikely(join->rollup.state != ROLLUP::STATE_NONE && + !empty_set_send_rollup_total)) { if (unlikely(join->rollup_write_data((uint) (idx+1), join_tab->tmp_table_param, diff --git a/sql/sql_select.h b/sql/sql_select.h index 1c607883be91e..d03edab058186 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1746,12 +1746,27 @@ class JOIN :public Sql_alloc void cleanup(bool full); void clear(table_map *cleared_tables); void inline clear_sum_funcs(); + bool need_empty_set_row(bool end_of_records, + bool *empty_set_send_rollup_total) const + { + *empty_set_send_rollup_total= + end_of_records && !first_record && rollup.state != ROLLUP::STATE_NONE; + return (end_of_records && !first_record && !group && + !group_optimized_away) || + *empty_set_send_rollup_total; + } bool send_row_on_empty_set() { - return (do_send_rows && implicit_grouping && !group_optimized_away && + return (do_send_rows && + (select_lex->olap == ROLLUP_TYPE || + (implicit_grouping && !group_optimized_away)) && having_value != Item::COND_FALSE); } - bool empty_result() { return (zero_result_cause && !implicit_grouping); } + bool empty_result() + { + return (zero_result_cause && !implicit_grouping && + select_lex->olap != ROLLUP_TYPE); + } bool change_result(select_result *new_result, select_result *old_result); bool is_top_level_join() const {