diff options
author | Monty <monty@mariadb.org> | 2021-02-27 19:56:46 +0200 |
---|---|---|
committer | Monty <monty@mariadb.org> | 2021-03-01 22:09:05 +0200 |
commit | 6983ce704baff7a6e5dd411a289e3580bc7bea1a (patch) | |
tree | 565c9fd66978b9f0eeaca90a2c9bdde6dd60d4ef | |
parent | 43a0a8139727314f89fc0f0f0d2ba80ffa33b221 (diff) | |
download | mariadb-git-6983ce704baff7a6e5dd411a289e3580bc7bea1a.tar.gz |
MDEV-24710 Uninitialized value upon CREATE .. SELECT ... VALUE...
The failure happened for group by queries when all tables where marked as
'const tables' (tables with 0-1 matching rows) and no row matched the
where clause and there was in addition a direct reference to a field.
In this case the field would not be properly reset and the query would
return 'random data' that happended to be in table->record[0].
Fixed by marking all const tables as null tables in this particular case.
Sergei also provided an extra test case for the code.
@reviewer Sergei Petrunia <psergey@askmonty.org>
-rw-r--r-- | mysql-test/main/group_by.result | 29 | ||||
-rw-r--r-- | mysql-test/main/group_by.test | 23 | ||||
-rw-r--r-- | sql/sql_select.cc | 87 | ||||
-rw-r--r-- | sql/table.h | 6 |
4 files changed, 135 insertions, 10 deletions
diff --git a/mysql-test/main/group_by.result b/mysql-test/main/group_by.result index 246cceb96c3..8be5b4d29b9 100644 --- a/mysql-test/main/group_by.result +++ b/mysql-test/main/group_by.result @@ -2934,5 +2934,34 @@ f COUNT(*) NULL 1 DROP TABLE t1; # +# MDEV-24710 Uninitialized value upon CREATE .. SELECT ... VALUE +# +CREATE TABLE t1 (a VARCHAR(8) NOT NULL DEFAULT ''); +INSERT INTO t1 (a) VALUES ('foo'); +CREATE TABLE t2 AS SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE VALUE(a) IS NOT NULL; +SELECT * from t2; +f1 f2 +NULL NULL +SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE VALUE(a) IS NOT NULL; +f1 f2 +NULL NULL +SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE 1=0; +f1 f2 +NULL NULL +drop table t1,t2; +# Extra test by to check the fix for MDEV-24710 +create table t20 (pk int primary key, a int); +insert into t20 values (1,1); +create table t21 (pk int primary key, b int not null); +insert into t21 values (1,1); +create table t22 (a int); +insert into t22 values (1),(2); +select a, (select max(t21.b) from t20 left join t21 on t21.pk=t20.a+10 +where t20.pk=1 and rand(123) < 0.5) as SUBQ from t22; +a SUBQ +1 NULL +2 NULL +drop table t20, t21, t22; +# # End of 10.3 tests # diff --git a/mysql-test/main/group_by.test b/mysql-test/main/group_by.test index 2866fab3822..8d690222bcc 100644 --- a/mysql-test/main/group_by.test +++ b/mysql-test/main/group_by.test @@ -2041,5 +2041,28 @@ SELECT d != '2023-03-04' AS f, COUNT(*) FROM t1 GROUP BY d WITH ROLLUP; DROP TABLE t1; --echo # +--echo # MDEV-24710 Uninitialized value upon CREATE .. SELECT ... VALUE +--echo # + +CREATE TABLE t1 (a VARCHAR(8) NOT NULL DEFAULT ''); +INSERT INTO t1 (a) VALUES ('foo'); +CREATE TABLE t2 AS SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE VALUE(a) IS NOT NULL; +SELECT * from t2; +SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE VALUE(a) IS NOT NULL; +SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE 1=0; +drop table t1,t2; + +--echo # Extra test by to check the fix for MDEV-24710 + +create table t20 (pk int primary key, a int); +insert into t20 values (1,1);create table t21 (pk int primary key, b int not null); +insert into t21 values (1,1); +create table t22 (a int); +insert into t22 values (1),(2); +select a, (select max(t21.b) from t20 left join t21 on t21.pk=t20.a+10 + where t20.pk=1 and rand(123) < 0.5) as SUBQ from t22; +drop table t20, t21, t22; + +--echo # --echo # End of 10.3 tests --echo # diff --git a/sql/sql_select.cc b/sql/sql_select.cc index c802aa9c18e..7658d843d8b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -13668,22 +13668,71 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables, DBUG_RETURN(0); } -/* - used only in JOIN::clear +/** + used only in JOIN::clear (always) 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. */ -static void clear_tables(JOIN *join) + +static void clear_tables(JOIN *join, table_map *cleared_tables) { /* - must clear only the non-const tables, as const tables - are not re-calculated. + must clear only the non-const tables as const tables are not re-calculated. */ for (uint i= 0 ; i < join->table_count ; i++) { - if (!(join->table[i]->map & join->const_table_map)) - mark_as_null_row(join->table[i]); // All fields are NULL + TABLE *table= join->table[i]; + + if (table->null_row) + continue; // Nothing more to do + if (!(table->map & join->const_table_map) || cleared_tables) + { + 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 + } } } + +/** + Reverse null marking for tables and restore null bits. + + 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 + data before clear_tables(). +*/ + +static void unclear_tables(JOIN *join, table_map *cleared_tables) +{ + for (uint i= 0 ; i < join->table_count ; i++) + { + if ((*cleared_tables) & (((table_map) 1) << i)) + { + TABLE *table= join->table[i]; + if (table->s->null_bytes) + memcpy(table->null_flags, table->record[1], table->s->null_bytes); + unmark_as_null_row(table); + } + } +} + + /***************************************************************************** Make som simple condition optimization: If there is a test 'field = const' change all refs to 'field' to 'const' @@ -19194,6 +19243,7 @@ do_select(JOIN *join, Procedure *procedure) if (join->only_const_tables() && !join->need_tmp) { Next_select_func end_select= setup_end_select_func(join, NULL); + /* HAVING will be checked after processing aggregate functions, But WHERE should checked here (we alredy have read tables). @@ -19220,12 +19270,29 @@ do_select(JOIN *join, Procedure *procedure) } else if (join->send_row_on_empty_set()) { + table_map cleared_tables= (table_map) 0; + if (end_select == end_send_group) + { + /* + Was a grouping query but we did not find any rows. In this case + we clear all tables to get null in any referenced fields, + like in case of: + SELECT MAX(a) AS f1, a AS f2 FROM t1 WHERE VALUE(a) IS NOT NULL + */ + clear_tables(join, &cleared_tables); + } if (!join->having || join->having->val_int()) { List<Item> *columns_list= (procedure ? &join->procedure_fields_list : join->fields); rc= join->result->send_data(*columns_list) > 0; } + /* + We have to remove the null markings from the tables as this table + may be part of a sub query that is re-evaluated + */ + if (cleared_tables) + unclear_tables(join, &cleared_tables); } /* An error can happen when evaluating the conds @@ -20197,8 +20264,8 @@ join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos) if ((table->null_row= MY_TEST((*tab->on_expr_ref)->val_int() == 0))) mark_as_null_row(table); } - if (!table->null_row) - table->maybe_null=0; + if (!table->null_row && ! tab->join->mixed_implicit_grouping) + table->maybe_null= 0; { JOIN *join= tab->join; @@ -25331,7 +25398,7 @@ int JOIN::rollup_write_data(uint idx, TMP_TABLE_PARAM *tmp_table_param_arg, TABL void JOIN::clear() { - clear_tables(this); + clear_tables(this, 0); copy_fields(&tmp_table_param); if (sum_funcs) diff --git a/sql/table.h b/sql/table.h index 3c31a6364df..6a4c6eb2a03 100644 --- a/sql/table.h +++ b/sql/table.h @@ -3145,6 +3145,12 @@ inline void mark_as_null_row(TABLE *table) bfill(table->null_flags,table->s->null_bytes,255); } +inline void unmark_as_null_row(TABLE *table) +{ + table->null_row=0; + table->status= STATUS_NO_RECORD; +} + bool is_simple_order(ORDER *order); class Open_tables_backup; |