From 5e14047e233e62bca79511e3d69ece6ccaa946a3 Mon Sep 17 00:00:00 2001
From: "igor@olga.mysql.com" <>
Date: Sat, 26 Jan 2008 21:45:35 -0800
Subject: [PATCH] Fixed bug #33833. Two disjuncts containing equalities of the
 form key=const1 and key=const2 can be merged into one if const1 is equal to
 const2. To check it the common collation of the constants were used rather
 than the collation of the field key. For example when the default collation
 of the constants was cases insensitive while the collation of the field was
 case sensitive, then two or-ed equality predicates key='b' and key='B'
 incorrectly were merged into one f='b'. As a result ref access was used
 instead of range access and wrong result sets were returned in many cases.
 Fixed the problem by comparing constant in the or-ed predicate with collation
 of the key field.

---
 mysql-test/r/range.result | 13 ++++++++++++
 mysql-test/t/range.test   | 17 ++++++++++++++++
 sql/item.cc               | 43 +++++++++++++++++++++++++++++++++++++++
 sql/item.h                |  1 +
 sql/sql_select.cc         |  4 +++-
 5 files changed, 77 insertions(+), 1 deletion(-)

diff --git a/mysql-test/r/range.result b/mysql-test/r/range.result
index e0084b53320..9b1da4ffc48 100644
--- a/mysql-test/r/range.result
+++ b/mysql-test/r/range.result
@@ -1153,3 +1153,16 @@ explain select * from t1 where dateval >= '2007-01-01 00:00:00' and dateval <= '
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
 1	SIMPLE	t1	range	dateval	dateval	4	NULL	2	Using where
 drop table t1;
+CREATE TABLE t1 (
+a varchar(32), index (a)
+) DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+INSERT INTO t1 VALUES
+('B'), ('A'), ('A'), ('C'), ('B'), ('A'), ('A');
+SELECT a FROM t1 WHERE a='b' OR a='B';
+a
+B
+B
+EXPLAIN SELECT a FROM t1 WHERE a='b' OR a='B';
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	range	a	a	35	NULL	3	Using where; Using index
+DROP TABLE t1;
diff --git a/mysql-test/t/range.test b/mysql-test/t/range.test
index 87ba3510326..1352b366508 100644
--- a/mysql-test/t/range.test
+++ b/mysql-test/t/range.test
@@ -955,4 +955,21 @@ explain select * from t1 where dateval >= '2007-01-01 00:00:00' and dateval <= '
 
 drop table t1;
 
+#
+# Bug #33833: different or-ed predicates were erroneously merged into one that
+# resulted in ref access instead of range access and  a wrong result set
+#
+
+CREATE TABLE t1 (
+  a varchar(32), index (a)
+) DEFAULT CHARSET=latin1 COLLATE=latin1_bin;
+
+INSERT INTO t1 VALUES
+  ('B'), ('A'), ('A'), ('C'), ('B'), ('A'), ('A');
+
+SELECT a FROM t1 WHERE a='b' OR a='B';
+EXPLAIN SELECT a FROM t1 WHERE a='b' OR a='B';
+
+DROP TABLE t1;
+
 # End of 5.0 tests
diff --git a/sql/item.cc b/sql/item.cc
index 713e7709bcb..182632bb40f 100644
--- a/sql/item.cc
+++ b/sql/item.cc
@@ -4302,6 +4302,49 @@ String *Item::check_well_formed_result(String *str, bool send_error)
   return str;
 }
 
+/*
+  Compare two items using a given collation
+  
+  SYNOPSIS
+    eq_by_collation()
+    item               item to compare with
+    binary_cmp         TRUE <-> compare as binaries
+    cs                 collation to use when comparing strings
+
+  DESCRIPTION
+    This method works exactly as Item::eq if the collation cs coincides with
+    the collation of the compared objects. Otherwise, first the collations that
+    differ from cs are replaced for cs and then the items are compared by
+    Item::eq. After the comparison the original collations of items are
+    restored.
+
+  RETURN
+    1    compared items has been detected as equal   
+    0    otherwise
+*/
+
+bool Item::eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs)
+{
+  CHARSET_INFO *save_cs= 0;
+  CHARSET_INFO *save_item_cs= 0;
+  if (collation.collation != cs)
+  {
+    save_cs= collation.collation;
+    collation.collation= cs;
+  }
+  if (item->collation.collation != cs)
+  {
+    save_item_cs= item->collation.collation;
+    item->collation.collation= cs;
+  }
+  bool res= eq(item, binary_cmp);
+  if (save_cs)
+    collation.collation= save_cs;
+  if (save_item_cs)
+    item->collation.collation= save_item_cs;
+  return res;
+}  
+
 
 /*
   Create a field to hold a string value from an item
diff --git a/sql/item.h b/sql/item.h
index 5f511557f47..f87499f23e3 100644
--- a/sql/item.h
+++ b/sql/item.h
@@ -873,6 +873,7 @@ public:
   virtual Field::geometry_type get_geometry_type() const
     { return Field::GEOM_GEOMETRY; };
   String *check_well_formed_result(String *str, bool send_error= 0);
+  bool eq_by_collation(Item *item, bool binary_cmp, CHARSET_INFO *cs); 
 };
 
 
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index 87935b5548f..bdea25ab99d 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -2887,7 +2887,9 @@ merge_key_fields(KEY_FIELD *start,KEY_FIELD *new_fields,KEY_FIELD *end,
 	  }
 	}
 	else if (old->eq_func && new_fields->eq_func &&
-		 old->val->eq(new_fields->val, old->field->binary()))
+                 old->val->eq_by_collation(new_fields->val, 
+                                           old->field->binary(),
+                                           old->field->charset()))
 
 	{
 	  old->level= and_level;