mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
Fix for MIN/MAX with empty tables
MIN(key_column) could in some cases return NULL on a column with NULL and other values. MIN(key_column) and MAX(key_column) could in some cases return wrong values when used in OUTER JOIN.
This commit is contained in:
@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script.
|
|||||||
AC_INIT(sql/mysqld.cc)
|
AC_INIT(sql/mysqld.cc)
|
||||||
AC_CANONICAL_SYSTEM
|
AC_CANONICAL_SYSTEM
|
||||||
# The Docs Makefile.am parses this line!
|
# The Docs Makefile.am parses this line!
|
||||||
AM_INIT_AUTOMAKE(mysql, 4.0.10-gamma)
|
AM_INIT_AUTOMAKE(mysql, 4.0.11)
|
||||||
AM_CONFIG_HEADER(config.h)
|
AM_CONFIG_HEADER(config.h)
|
||||||
|
|
||||||
PROTOCOL_VERSION=10
|
PROTOCOL_VERSION=10
|
||||||
|
@ -62,7 +62,7 @@ my_string mysql_unix_port=0;
|
|||||||
#define closesocket(A) close(A)
|
#define closesocket(A) close(A)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void mysql_once_init(void);
|
void STDCALL mysql_once_init(void);
|
||||||
static MYSQL_DATA *read_rows (MYSQL *mysql,MYSQL_FIELD *fields,
|
static MYSQL_DATA *read_rows (MYSQL *mysql,MYSQL_FIELD *fields,
|
||||||
uint field_count);
|
uint field_count);
|
||||||
static int read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row,
|
static int read_one_row(MYSQL *mysql,uint fields,MYSQL_ROW row,
|
||||||
|
@ -151,3 +151,56 @@ sum(num)
|
|||||||
45.63
|
45.63
|
||||||
51.80
|
51.80
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
create table t1 (a1 int, a2 char(3), key k1(a1), key k2(a2));
|
||||||
|
insert into t1 values(10,'aaa'), (10,null), (10,'bbb'), (20,'zzz');
|
||||||
|
create table t2(a1 char(3), a2 int, a3 real, key k1(a1), key k2(a2, a1));
|
||||||
|
select * from t1;
|
||||||
|
a1 a2
|
||||||
|
10 aaa
|
||||||
|
10 NULL
|
||||||
|
10 bbb
|
||||||
|
20 zzz
|
||||||
|
select min(a2) from t1;
|
||||||
|
min(a2)
|
||||||
|
aaa
|
||||||
|
select max(t1.a1), max(t2.a2) from t1, t2;
|
||||||
|
max(t1.a1) max(t2.a2)
|
||||||
|
NULL NULL
|
||||||
|
select max(t1.a1) from t1, t2;
|
||||||
|
max(t1.a1)
|
||||||
|
NULL
|
||||||
|
select max(t2.a2), max(t1.a1) from t1, t2;
|
||||||
|
max(t2.a2) max(t1.a1)
|
||||||
|
NULL NULL
|
||||||
|
explain select min(a2) from t1;
|
||||||
|
Comment
|
||||||
|
Select tables optimized away
|
||||||
|
explain select max(t1.a1), max(t2.a2) from t1, t2;
|
||||||
|
Comment
|
||||||
|
No matching min/max row
|
||||||
|
insert into t2 values('AAA', 10, 0.5);
|
||||||
|
select max(t1.a1), max(t2.a1) from t1, t2 where t2.a2=9;
|
||||||
|
max(t1.a1) max(t2.a1)
|
||||||
|
NULL NULL
|
||||||
|
select max(t2.a1), max(t1.a1) from t1, t2 where t2.a2=9;
|
||||||
|
max(t2.a1) max(t1.a1)
|
||||||
|
NULL NULL
|
||||||
|
select t1.a1, t1.a2, t2.a1, t2.a2 from t1 left outer join t2 on t1.a1=10;
|
||||||
|
a1 a2 a1 a2
|
||||||
|
10 aaa AAA 10
|
||||||
|
10 NULL AAA 10
|
||||||
|
10 bbb AAA 10
|
||||||
|
20 zzz NULL NULL
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10;
|
||||||
|
max(t1.a2)
|
||||||
|
zzz
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10 where t1.a1=20;
|
||||||
|
max(t1.a2)
|
||||||
|
zzz
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10 where t1.a1=10;
|
||||||
|
max(t1.a2)
|
||||||
|
bbb
|
||||||
|
select max(t2.a1) from t1 left outer join t2 on t1.a2=t2.a1 and 1=0 where t2.a1='AAA';
|
||||||
|
max(t2.a1)
|
||||||
|
NULL
|
||||||
|
drop table t1,t2;
|
||||||
|
@ -99,3 +99,30 @@ insert into t1 values (5.2,'nem'),(8.64,'monty'),(11.12,'sinisa');
|
|||||||
select sum(num) from t1;
|
select sum(num) from t1;
|
||||||
select sum(num) from t1 group by user;
|
select sum(num) from t1 group by user;
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test problem with MIN() optimization in case of null values
|
||||||
|
#
|
||||||
|
|
||||||
|
create table t1 (a1 int, a2 char(3), key k1(a1), key k2(a2));
|
||||||
|
insert into t1 values(10,'aaa'), (10,null), (10,'bbb'), (20,'zzz');
|
||||||
|
create table t2(a1 char(3), a2 int, a3 real, key k1(a1), key k2(a2, a1));
|
||||||
|
select * from t1;
|
||||||
|
# The following returned NULL in 4.0.10
|
||||||
|
select min(a2) from t1;
|
||||||
|
select max(t1.a1), max(t2.a2) from t1, t2;
|
||||||
|
select max(t1.a1) from t1, t2;
|
||||||
|
select max(t2.a2), max(t1.a1) from t1, t2;
|
||||||
|
|
||||||
|
explain select min(a2) from t1;
|
||||||
|
explain select max(t1.a1), max(t2.a2) from t1, t2;
|
||||||
|
|
||||||
|
insert into t2 values('AAA', 10, 0.5);
|
||||||
|
select max(t1.a1), max(t2.a1) from t1, t2 where t2.a2=9;
|
||||||
|
select max(t2.a1), max(t1.a1) from t1, t2 where t2.a2=9;
|
||||||
|
select t1.a1, t1.a2, t2.a1, t2.a2 from t1 left outer join t2 on t1.a1=10;
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10;
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10 where t1.a1=20;
|
||||||
|
select max(t1.a2) from t1 left outer join t2 on t1.a1=10 where t1.a1=10;
|
||||||
|
select max(t2.a1) from t1 left outer join t2 on t1.a2=t2.a1 and 1=0 where t2.a1='AAA';
|
||||||
|
drop table t1,t2;
|
||||||
|
@ -89,6 +89,7 @@ public:
|
|||||||
virtual void set_result_field(Field *field) {}
|
virtual void set_result_field(Field *field) {}
|
||||||
virtual bool is_result_field() { return 0; }
|
virtual bool is_result_field() { return 0; }
|
||||||
virtual void save_in_result_field(bool no_conversions) {}
|
virtual void save_in_result_field(bool no_conversions) {}
|
||||||
|
virtual void no_rows_in_result() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -69,6 +69,7 @@ public:
|
|||||||
void make_field(Send_field *field);
|
void make_field(Send_field *field);
|
||||||
void print(String *str);
|
void print(String *str);
|
||||||
void fix_num_length_and_dec();
|
void fix_num_length_and_dec();
|
||||||
|
void no_rows_in_result() { reset(); }
|
||||||
virtual bool setup(THD *thd) {return 0;}
|
virtual bool setup(THD *thd) {return 0;}
|
||||||
unsigned int size_of() { return sizeof(*this);}
|
unsigned int size_of() { return sizeof(*this);}
|
||||||
};
|
};
|
||||||
@ -135,6 +136,7 @@ class Item_sum_count :public Item_sum_int
|
|||||||
bool const_item() const { return !used_table_cache; }
|
bool const_item() const { return !used_table_cache; }
|
||||||
enum Sumfunctype sum_func () const { return COUNT_FUNC; }
|
enum Sumfunctype sum_func () const { return COUNT_FUNC; }
|
||||||
void reset();
|
void reset();
|
||||||
|
void no_rows_in_result() { count=0; }
|
||||||
bool add();
|
bool add();
|
||||||
void make_const(longlong count_arg) { count=count_arg; used_table_cache=0; }
|
void make_const(longlong count_arg) { count=count_arg; used_table_cache=0; }
|
||||||
longlong val_int();
|
longlong val_int();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
|
/* Copyright (C) 2000-2003 MySQL AB
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
This program is free software; you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@ -22,28 +22,55 @@
|
|||||||
|
|
||||||
static bool find_range_key(TABLE_REF *ref, Field* field,COND *cond);
|
static bool find_range_key(TABLE_REF *ref, Field* field,COND *cond);
|
||||||
|
|
||||||
/*****************************************************************************
|
/*
|
||||||
** This function is only called for queries with sum functions and no
|
Substitutes constants for some COUNT(), MIN() and MAX() functions.
|
||||||
** GROUP BY part.
|
|
||||||
** This substitutes constants for some COUNT(), MIN() and MAX() functions.
|
SYNOPSIS
|
||||||
** The function returns 1 if all items was resolved and -1 on impossible
|
opt_sum_query()
|
||||||
** conditions
|
tables Tables in query
|
||||||
****************************************************************************/
|
all_fields All fields to be returned
|
||||||
|
conds WHERE clause
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
This function is only called for queries with sum functions and no
|
||||||
|
GROUP BY part.
|
||||||
|
|
||||||
|
RETURN VALUES
|
||||||
|
0 No errors
|
||||||
|
1 if all items was resolved
|
||||||
|
-1 on impossible conditions
|
||||||
|
*/
|
||||||
|
|
||||||
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
||||||
{
|
{
|
||||||
List_iterator_fast<Item> it(all_fields);
|
List_iterator_fast<Item> it(all_fields);
|
||||||
int const_result=1;
|
int const_result= 1;
|
||||||
bool recalc_const_item=0;
|
bool recalc_const_item= 0;
|
||||||
table_map removed_tables=0;
|
table_map removed_tables= 0, outer_tables= 0, used_tables= 0;
|
||||||
|
table_map where_tables= 0;
|
||||||
Item *item;
|
Item *item;
|
||||||
COND *org_conds= conds;
|
COND *org_conds= conds;
|
||||||
|
|
||||||
/* Add all ON conditions to WHERE condition */
|
if (conds)
|
||||||
|
where_tables= conds->used_tables();
|
||||||
|
|
||||||
|
/* Don't replace expression on a table that is part of an outer join */
|
||||||
for (TABLE_LIST *tl=tables; tl ; tl= tl->next)
|
for (TABLE_LIST *tl=tables; tl ; tl= tl->next)
|
||||||
{
|
{
|
||||||
if (tl->on_expr)
|
if (tl->on_expr)
|
||||||
conds= and_expressions(conds, tl->on_expr, &org_conds);
|
{
|
||||||
|
outer_tables|= tl->table->map;
|
||||||
|
|
||||||
|
/*
|
||||||
|
We can't optimise LEFT JOIN in cases where the WHERE condition
|
||||||
|
restricts the table that is used, like in:
|
||||||
|
SELECT MAX(t1.a) FROM t1 LEFT JOIN t2 join-condition
|
||||||
|
WHERE t2.field IS NULL;
|
||||||
|
*/
|
||||||
|
if (tl->table->map & where_tables)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
used_tables|= tl->table->map;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -68,8 +95,8 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
|||||||
TABLE_LIST *table;
|
TABLE_LIST *table;
|
||||||
for (table=tables; table ; table=table->next)
|
for (table=tables; table ; table=table->next)
|
||||||
{
|
{
|
||||||
if (table->on_expr || (table->table->file->table_flags() &
|
if (outer_tables || (table->table->file->table_flags() &
|
||||||
HA_NOT_EXACT_COUNT))
|
HA_NOT_EXACT_COUNT))
|
||||||
{
|
{
|
||||||
const_result=0; // Can't optimize left join
|
const_result=0; // Can't optimize left join
|
||||||
break;
|
break;
|
||||||
@ -99,21 +126,35 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
|||||||
byte key_buff[MAX_KEY_LENGTH];
|
byte key_buff[MAX_KEY_LENGTH];
|
||||||
TABLE_REF ref;
|
TABLE_REF ref;
|
||||||
ref.key_buff=key_buff;
|
ref.key_buff=key_buff;
|
||||||
|
Item_field *item_field= ((Item_field*) expr);
|
||||||
|
TABLE *table= item_field->field->table;
|
||||||
|
|
||||||
if (!find_range_key(&ref, ((Item_field*) expr)->field,conds))
|
if ((outer_tables & table->map) ||
|
||||||
|
(!find_range_key(&ref, item_field->field,conds)))
|
||||||
{
|
{
|
||||||
const_result=0;
|
const_result=0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
TABLE *table=((Item_field*) expr)->field->table;
|
bool error= table->file->index_init((uint) ref.key);
|
||||||
bool error=table->file->index_init((uint) ref.key);
|
enum ha_rkey_function find_flag= HA_READ_KEY_OR_NEXT;
|
||||||
|
uint prefix_len= ref.key_length;
|
||||||
|
/*
|
||||||
|
If we are doing MIN() on a column with NULL fields
|
||||||
|
we must read the key after the NULL column
|
||||||
|
*/
|
||||||
|
if (item_field->field->null_bit)
|
||||||
|
{
|
||||||
|
ref.key_buff[ref.key_length++]=1;
|
||||||
|
find_flag= HA_READ_AFTER_KEY;
|
||||||
|
}
|
||||||
|
|
||||||
if (!ref.key_length)
|
if (!ref.key_length)
|
||||||
error=table->file->index_first(table->record[0]) !=0;
|
error=table->file->index_first(table->record[0]) !=0;
|
||||||
else
|
else
|
||||||
error=table->file->index_read(table->record[0],key_buff,
|
error=table->file->index_read(table->record[0],key_buff,
|
||||||
ref.key_length,
|
ref.key_length,
|
||||||
HA_READ_KEY_OR_NEXT) ||
|
find_flag) ||
|
||||||
key_cmp(table, key_buff, ref.key, ref.key_length);
|
key_cmp(table, key_buff, ref.key, prefix_len);
|
||||||
if (table->key_read)
|
if (table->key_read)
|
||||||
{
|
{
|
||||||
table->key_read=0;
|
table->key_read=0;
|
||||||
@ -121,7 +162,7 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
|||||||
}
|
}
|
||||||
table->file->index_end();
|
table->file->index_end();
|
||||||
if (error)
|
if (error)
|
||||||
return -1; // Impossible query
|
return -1; // No rows matching where
|
||||||
removed_tables|= table->map;
|
removed_tables|= table->map;
|
||||||
}
|
}
|
||||||
else if (!expr->const_item()) // This is VERY seldom false
|
else if (!expr->const_item()) // This is VERY seldom false
|
||||||
@ -147,13 +188,14 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
|||||||
byte key_buff[MAX_KEY_LENGTH];
|
byte key_buff[MAX_KEY_LENGTH];
|
||||||
TABLE_REF ref;
|
TABLE_REF ref;
|
||||||
ref.key_buff=key_buff;
|
ref.key_buff=key_buff;
|
||||||
|
TABLE *table=((Item_field*) expr)->field->table;
|
||||||
|
|
||||||
if (!find_range_key(&ref, ((Item_field*) expr)->field,conds))
|
if ((outer_tables & table->map) ||
|
||||||
|
!find_range_key(&ref, ((Item_field*) expr)->field,conds))
|
||||||
{
|
{
|
||||||
const_result=0;
|
const_result=0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
TABLE *table=((Item_field*) expr)->field->table;
|
|
||||||
if ((table->file->table_flags() & HA_NOT_READ_AFTER_KEY))
|
if ((table->file->table_flags() & HA_NOT_READ_AFTER_KEY))
|
||||||
{
|
{
|
||||||
const_result=0;
|
const_result=0;
|
||||||
@ -203,8 +245,8 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
|
|||||||
const_result=0;
|
const_result=0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (conds && (conds->used_tables() & ~ removed_tables))
|
if (used_tables != removed_tables)
|
||||||
const_result=0;
|
const_result=0; // We didn't remove all tables
|
||||||
return const_result;
|
return const_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3104,7 +3104,13 @@ return_zero_rows(JOIN *join, select_result *result,TABLE_LIST *tables,
|
|||||||
if (!(result->send_fields(fields,1)))
|
if (!(result->send_fields(fields,1)))
|
||||||
{
|
{
|
||||||
if (send_row)
|
if (send_row)
|
||||||
|
{
|
||||||
|
List_iterator_fast<Item> it(fields);
|
||||||
|
Item *item;
|
||||||
|
while ((item= it++))
|
||||||
|
item->no_rows_in_result();
|
||||||
result->send_data(fields);
|
result->send_data(fields);
|
||||||
|
}
|
||||||
if (tables) // Not from do_select()
|
if (tables) // Not from do_select()
|
||||||
{
|
{
|
||||||
/* Close open cursors */
|
/* Close open cursors */
|
||||||
|
Reference in New Issue
Block a user