1
0
mirror of https://github.com/MariaDB/server.git synced 2025-08-07 00:04:31 +03:00

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>
This commit is contained in:
Monty
2021-02-27 19:56:46 +02:00
parent 43a0a81397
commit 6983ce704b
4 changed files with 135 additions and 10 deletions

View File

@@ -2934,5 +2934,34 @@ f COUNT(*)
NULL 1 NULL 1
DROP TABLE t1; 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 # End of 10.3 tests
# #

View File

@@ -2040,6 +2040,29 @@ INSERT INTO t1 VALUES ('2032-10-08');
SELECT d != '2023-03-04' AS f, COUNT(*) FROM t1 GROUP BY d WITH ROLLUP; SELECT d != '2023-03-04' AS f, COUNT(*) FROM t1 GROUP BY d WITH ROLLUP;
DROP TABLE t1; 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 #
--echo # End of 10.3 tests --echo # End of 10.3 tests
--echo # --echo #

View File

@@ -13668,21 +13668,70 @@ return_zero_rows(JOIN *join, select_result *result, List<TABLE_LIST> &tables,
DBUG_RETURN(0); 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 must clear only the non-const tables as const tables are not re-calculated.
are not re-calculated.
*/ */
for (uint i= 0 ; i < join->table_count ; i++) for (uint i= 0 ; i < join->table_count ; i++)
{ {
if (!(join->table[i]->map & join->const_table_map)) TABLE *table= join->table[i];
mark_as_null_row(join->table[i]); // All fields are NULL
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: Make som simple condition optimization:
@@ -19194,6 +19243,7 @@ do_select(JOIN *join, Procedure *procedure)
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, NULL);
/* /*
HAVING will be checked after processing aggregate functions, HAVING will be checked after processing aggregate functions,
But WHERE should checked here (we alredy have read tables). 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()) 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()) if (!join->having || join->having->val_int())
{ {
List<Item> *columns_list= (procedure ? &join->procedure_fields_list : List<Item> *columns_list= (procedure ? &join->procedure_fields_list :
join->fields); join->fields);
rc= join->result->send_data(*columns_list) > 0; 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 An error can happen when evaluating the conds
@@ -20197,7 +20264,7 @@ join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos)
if ((table->null_row= MY_TEST((*tab->on_expr_ref)->val_int() == 0))) if ((table->null_row= MY_TEST((*tab->on_expr_ref)->val_int() == 0)))
mark_as_null_row(table); mark_as_null_row(table);
} }
if (!table->null_row) if (!table->null_row && ! tab->join->mixed_implicit_grouping)
table->maybe_null= 0; table->maybe_null= 0;
{ {
@@ -25331,7 +25398,7 @@ int JOIN::rollup_write_data(uint idx, TMP_TABLE_PARAM *tmp_table_param_arg, TABL
void JOIN::clear() void JOIN::clear()
{ {
clear_tables(this); clear_tables(this, 0);
copy_fields(&tmp_table_param); copy_fields(&tmp_table_param);
if (sum_funcs) if (sum_funcs)

View File

@@ -3145,6 +3145,12 @@ 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);
} }
inline void unmark_as_null_row(TABLE *table)
{
table->null_row=0;
table->status= STATUS_NO_RECORD;
}
bool is_simple_order(ORDER *order); bool is_simple_order(ORDER *order);
class Open_tables_backup; class Open_tables_backup;