List:Internals« Previous MessageNext Message »
From:timour Date:September 21 2005 8:49am
Subject:bk commit into 4.1 tree (timour:1.2421) BUG#12882
View as plain text  
Below is the list of changes that have just been committed into a local
4.1 repository of timka. When timka does a push these changes will
be propagated to the main repository and, within 24 hours after the
push, to the public repository.
For information on how to access the public repository
see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html

ChangeSet
  1.2421 05/09/21 09:49:19 timour@stripped +4 -0
  Fix for BUG#12882 - min/max inconsistent on empty table.
  
  The problem was in that the MIN/MAX optimization in opt_sum_query was
  replacing MIN/MAX functions with their constant argument without
  taking into account that a query has no result rows.

  sql/opt_sum.cc
    1.43 05/09/21 09:49:16 timour@stripped +67 -29
    * Do not apply MIN/MAX optimization when the operand of MIN/MAX is
      a constant if it can't be determined whether the query has any
      result rows. The reason is that if the query has result rows,
      then the result of MIN/MAX is its constant argument, but if the
      query result is empty, then the result of MIN/MAX must be NULL
      irrespective of its argument.
    
    * The patch also simplifies a bit the branch that hadles COUNT().

  sql/item_sum.cc
    1.146 05/09/21 09:49:16 timour@stripped +1 -1
    If it is known that a query has no result rows, do not call add()
    via the call to Item_sum::no_rows_in_result() which calls reset().
    Instead directly call clear() so that the MIN and MAX functions
    produce NULL when there are no result rows.

  mysql-test/t/func_group.test
    1.31 05/09/21 09:49:16 timour@stripped +69 -0
    Test for BUG#12882.

  mysql-test/r/func_group.result
    1.39 05/09/21 09:49:16 timour@stripped +136 -0
    Test for BUG#12882.

# This is a BitKeeper patch.  What follows are the unified diffs for the
# set of deltas contained in the patch.  The rest of the patch, the part
# that BitKeeper cares about, is below these diffs.
# User:	timour
# Host:	lamia.home
# Root:	/home/timka/mysql/src/4.1-dbg

--- 1.145/sql/item_sum.cc	2005-09-08 02:33:06 +03:00
+++ 1.146/sql/item_sum.cc	2005-09-21 09:49:16 +03:00
@@ -552,8 +552,8 @@
 
 void Item_sum_hybrid::no_rows_in_result()
 {
-  Item_sum::no_rows_in_result();
   was_values= FALSE;
+  clear();
 }
 
 

--- 1.42/sql/opt_sum.cc	2005-03-05 06:13:42 +02:00
+++ 1.43/sql/opt_sum.cc	2005-09-21 09:49:16 +03:00
@@ -59,8 +59,8 @@
 
   SYNOPSIS
     opt_sum_query()
-    tables                Tables in query
-    all_fields                All fields to be returned
+    tables               Tables in query
+    all_fields           All fields to be returned
     conds                WHERE clause
 
   NOTE:
@@ -80,6 +80,8 @@
   List_iterator_fast<Item> it(all_fields);
   int const_result= 1;
   bool recalc_const_item= 0;
+  longlong count= 1;
+  bool is_exact_count= TRUE;
   table_map removed_tables= 0, outer_tables= 0, used_tables= 0;
   table_map where_tables= 0;
   Item *item;
@@ -88,9 +90,13 @@
   if (conds)
     where_tables= conds->used_tables();
 
-  /* Don't replace expression on a table that is part of an outer join */
+  /*
+    Analyze outer join dependencies, and, if possible, compute the number
+    of returned rows.
+  */
   for (TABLE_LIST *tl=tables; tl ; tl= tl->next)
   {
+    /* Don't replace expression on a table that is part of an outer join */
     if (tl->on_expr)
     {
       outer_tables|= tl->table->map;
@@ -102,15 +108,31 @@
           WHERE t2.field IS NULL;
       */
       if (tl->table->map & where_tables)
-        return 0;
+        const_result= 0;
     }
     else
       used_tables|= tl->table->map;
+
+    /*
+      If the storage manager of 'tl' gives exact row count, compute the total
+      number of rows. If there are no outer table dependencies, this count
+      may be used as the real count.
+    */
+    if (tl->table->file->table_flags() & HA_NOT_EXACT_COUNT)
+      is_exact_count= FALSE;
+    else
+    {
+      tl->table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
+      count*= tl->table->file->records;
+    }
   }
 
+  if (!const_result)
+    return 0;
+
   /*
-    Iterate through item is select part and replace COUNT(), MIN() and MAX()
-    with constants (if possible)
+    Iterate through all items in the SELECT clause and replace
+    COUNT(), MIN() and MAX() with constants (if possible).
   */
 
   while ((item= it++))
@@ -122,28 +144,14 @@
       case Item_sum::COUNT_FUNC:
         /*
           If the expr in count(expr) can never be null we can change this
-          to the number of rows in the tables
+          to the number of rows in the tables if this number is exact and
+          there are no outer joins.
         */
-        if (!conds && !((Item_sum_count*) item)->args[0]->maybe_null)
+        if (!conds && !((Item_sum_count*) item)->args[0]->maybe_null
&&
+            !outer_tables && is_exact_count)
         {
-          longlong count= 1;
-          TABLE_LIST *table;
-          for (table=tables ; table ; table=table->next)
-          {
-            if (outer_tables || (table->table->file->table_flags() &
-                                 HA_NOT_EXACT_COUNT))
-            {
-              const_result= 0;			// Can't optimize left join
-              break;
-            }
-            tables->table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK);
-            count*= table->table->file->records;
-          }
-          if (!table)
-          {
             ((Item_sum_count*) item)->make_const(count);
             recalc_const_item= 1;
-          }
         }
         else
           const_result= 0;
@@ -210,12 +218,27 @@
 	  }
           removed_tables|= table->map;
         }
-        else if (!expr->const_item())		// This is VERY seldom false
+        else if (!expr->const_item() || !is_exact_count)
         {
+          /*
+            The optimization is not applicable in both cases:
+            (a) 'expr' is a non-constant expression. Then we can't
+            replace 'expr' by a constant.
+            (b) 'expr' is a costant. According to ANSI, MIN/MAX must return
+            NULL if the query does not return any rows. Thus, if we are not
+            able to determine if the query returns any rows, we can't apply
+            the optimization and replace MIN/MAX with a constant.
+          */
           const_result= 0;
           break;
         }
-        ((Item_sum_min*) item_sum)->reset();
+        if (!count)
+        {
+          /* If count != 1, then we know that is_exact_count == TRUE. */
+          ((Item_sum_min*) item_sum)->clear(); /* Set to NULL. */
+        }
+        else
+          ((Item_sum_min*) item_sum)->reset(); /* Set to the constant value. */
         ((Item_sum_min*) item_sum)->make_const();
         recalc_const_item= 1;
         break;
@@ -282,13 +305,28 @@
 	  }
           removed_tables|= table->map;
         }
-        else if (!expr->const_item())		// This is VERY seldom false
+        else if (!expr->const_item() || !is_exact_count)
         {
+          /*
+            The optimization is not applicable in both cases:
+            (a) 'expr' is a non-constant expression. Then we can't
+            replace 'expr' by a constant.
+            (b) 'expr' is a costant. According to ANSI, MIN/MAX must return
+            NULL if the query does not return any rows. Thus, if we are not
+            able to determine if the query returns any rows, we can't apply
+            the optimization and replace MIN/MAX with a constant.
+          */
           const_result= 0;
           break;
         }
-        ((Item_sum_min*) item_sum)->reset();
-        ((Item_sum_min*) item_sum)->make_const();
+        if (!count)
+        {
+          /* If count != 1, then we know that is_exact_count == TRUE. */
+          ((Item_sum_max*) item_sum)->clear(); /* Set to NULL. */
+        }
+        else
+          ((Item_sum_max*) item_sum)->reset(); /* Set to the constant value. */
+        ((Item_sum_max*) item_sum)->make_const();
         recalc_const_item= 1;
         break;
       }

--- 1.38/mysql-test/r/func_group.result	2005-03-23 08:36:41 +02:00
+++ 1.39/mysql-test/r/func_group.result	2005-09-21 09:49:16 +03:00
@@ -780,3 +780,139 @@
 MAX(id)
 NULL
 DROP TABLE t1;
+create table t1m (a int) engine=myisam;
+create table t1i (a int) engine=innodb;
+create table t2m (a int) engine=myisam;
+create table t2i (a int) engine=innodb;
+insert into t2m values (5);
+insert into t2i values (5);
+select min(a) from t1m;
+min(a)
+NULL
+select min(7) from t1m;
+min(7)
+NULL
+select min(7) from DUAL;
+min(7)
+NULL
+explain select min(7) from t2m join t1m;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Select tables optimized away
+select min(7) from t2m join t1m;
+min(7)
+NULL
+select max(a) from t1m;
+max(a)
+NULL
+select max(7) from t1m;
+max(7)
+NULL
+select max(7) from DUAL;
+max(7)
+NULL
+explain select max(7) from t2m join t1m;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	NULL	NULL	NULL	NULL	NULL	NULL	NULL	Select tables optimized away
+select max(7) from t2m join t1m;
+max(7)
+NULL
+select 1, min(a) from t1m where a=99;
+1	min(a)
+1	NULL
+select 1, min(a) from t1m where 1=99;
+1	min(a)
+1	NULL
+select 1, min(1) from t1m where a=99;
+1	min(1)
+select 1, min(1) from t1m where 1=99;
+1	min(1)
+1	NULL
+select 1, max(a) from t1m where a=99;
+1	max(a)
+1	NULL
+select 1, max(a) from t1m where 1=99;
+1	max(a)
+1	NULL
+select 1, max(1) from t1m where a=99;
+1	max(1)
+select 1, max(1) from t1m where 1=99;
+1	max(1)
+1	NULL
+select min(a) from t1i;
+min(a)
+NULL
+select min(7) from t1i;
+min(7)
+NULL
+select min(7) from DUAL;
+min(7)
+NULL
+explain select min(7) from t2i join t1i;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t2i	ALL	NULL	NULL	NULL	NULL	1	
+1	SIMPLE	t1i	ALL	NULL	NULL	NULL	NULL	1	
+select min(7) from t2i join t1i;
+min(7)
+NULL
+select max(a) from t1i;
+max(a)
+NULL
+select max(7) from t1i;
+max(7)
+NULL
+select max(7) from DUAL;
+max(7)
+NULL
+explain select max(7) from t2i join t1i;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t2i	ALL	NULL	NULL	NULL	NULL	1	
+1	SIMPLE	t1i	ALL	NULL	NULL	NULL	NULL	1	
+select max(7) from t2i join t1i;
+max(7)
+NULL
+select 1, min(a) from t1i where a=99;
+1	min(a)
+1	NULL
+select 1, min(a) from t1i where 1=99;
+1	min(a)
+1	NULL
+select 1, min(1) from t1i where a=99;
+1	min(1)
+1	NULL
+select 1, min(1) from t1i where 1=99;
+1	min(1)
+1	NULL
+select 1, max(a) from t1i where a=99;
+1	max(a)
+1	NULL
+select 1, max(a) from t1i where 1=99;
+1	max(a)
+1	NULL
+select 1, max(1) from t1i where a=99;
+1	max(1)
+1	NULL
+select 1, max(1) from t1i where 1=99;
+1	max(1)
+1	NULL
+explain select count(*), min(7), max(7) from t1m, t1i;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1m	system	NULL	NULL	NULL	NULL	0	const row not found
+1	SIMPLE	t1i	ALL	NULL	NULL	NULL	NULL	1	
+select count(*), min(7), max(7) from t1m, t1i;
+count(*)	min(7)	max(7)
+0	NULL	NULL
+explain select count(*), min(7), max(7) from t1m, t2i;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1m	system	NULL	NULL	NULL	NULL	0	const row not found
+1	SIMPLE	t2i	ALL	NULL	NULL	NULL	NULL	1	
+select count(*), min(7), max(7) from t1m, t2i;
+count(*)	min(7)	max(7)
+0	NULL	NULL
+explain select count(*), min(7), max(7) from t2m, t1i;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t2m	system	NULL	NULL	NULL	NULL	1	
+1	SIMPLE	t1i	ALL	NULL	NULL	NULL	NULL	1	
+select count(*), min(7), max(7) from t2m, t1i;
+count(*)	min(7)	max(7)
+0	NULL	NULL
+drop table t1m, t1i, t2m, t2i;

--- 1.30/mysql-test/t/func_group.test	2005-07-28 03:21:42 +03:00
+++ 1.31/mysql-test/t/func_group.test	2005-09-21 09:49:16 +03:00
@@ -527,4 +527,73 @@
 SELECT MAX(id) FROM t1 WHERE id < 3 AND a=2 AND b=6;
 DROP TABLE t1;
 
+#
+# Bug #12882  	min/max inconsistent on empty table
+#
+
+create table t1m (a int) engine=myisam;
+create table t1i (a int) engine=innodb;
+create table t2m (a int) engine=myisam;
+create table t2i (a int) engine=innodb;
+insert into t2m values (5);
+insert into t2i values (5);
+
+# test with MyISAM
+select min(a) from t1m;
+select min(7) from t1m;
+select min(7) from DUAL;
+explain select min(7) from t2m join t1m;
+select min(7) from t2m join t1m;
+
+select max(a) from t1m;
+select max(7) from t1m;
+select max(7) from DUAL;
+explain select max(7) from t2m join t1m;
+select max(7) from t2m join t1m;
+
+select 1, min(a) from t1m where a=99;
+select 1, min(a) from t1m where 1=99;
+select 1, min(1) from t1m where a=99;
+select 1, min(1) from t1m where 1=99;
+
+select 1, max(a) from t1m where a=99;
+select 1, max(a) from t1m where 1=99;
+select 1, max(1) from t1m where a=99;
+select 1, max(1) from t1m where 1=99;
+
+# test with InnoDB
+select min(a) from t1i;
+select min(7) from t1i;
+select min(7) from DUAL;
+explain select min(7) from t2i join t1i;
+select min(7) from t2i join t1i;
+
+select max(a) from t1i;
+select max(7) from t1i;
+select max(7) from DUAL;
+explain select max(7) from t2i join t1i;
+select max(7) from t2i join t1i;
+
+select 1, min(a) from t1i where a=99;
+select 1, min(a) from t1i where 1=99;
+select 1, min(1) from t1i where a=99;
+select 1, min(1) from t1i where 1=99;
+
+select 1, max(a) from t1i where a=99;
+select 1, max(a) from t1i where 1=99;
+select 1, max(1) from t1i where a=99;
+select 1, max(1) from t1i where 1=99;
+
+# mixed MyISAM/InnoDB test
+explain select count(*), min(7), max(7) from t1m, t1i;
+select count(*), min(7), max(7) from t1m, t1i;
+
+explain select count(*), min(7), max(7) from t1m, t2i;
+select count(*), min(7), max(7) from t1m, t2i;
+
+explain select count(*), min(7), max(7) from t2m, t1i;
+select count(*), min(7), max(7) from t2m, t1i;
+
+drop table t1m, t1i, t2m, t2i;
+
 # End of 4.1 tests
Thread
bk commit into 4.1 tree (timour:1.2421) BUG#12882timour21 Sep