List:Commits« Previous MessageNext Message »
From:Oystein.Grovlen Date:November 11 2009 1:20pm
Subject:bzr commit into mysql-6.0-codebase-bugfixing branch
(oystein.grovlen:3703) Bug#47217
View as plain text  
#At file:///home/oysteing/mysql/mysql-6.0-codebase-bugfixing/ based on revid:jon.hauglid@stripped

 3703 oystein.grovlen@stripped	2009-11-11
      Bug#47217 Simple Inner join gives wrong result when order by is used
      
      Problem: Join matches on NULL values when filesort select predicate contain
      NULL value from const table propagation.
      
      Solution: When creating a quick select for a constant reference, check whether 
      the ref contain null constants for key parts where NULLs should not match.
      If so, create an object of the new class QUICK_EMPTY_SELECT instead of an
      ordinary QUICK_SELECT_RANGE. QUICK_EMPTY_SELECT represent a quick select with 0
      rows.
     @ mysql-test/r/join_optimizer.result
        Result for new test case
     @ mysql-test/t/join_optimizer.test
        Add test case for Bug#47217
     @ sql/opt_range.cc
        get_quick_select_for_ref now returns pointer to a QUICK_EMPTY_SELECT object if ref could not possibly match any rows. That is, if part of the ref key is a NULL constant, and NULL should not match other NULLs for this part.
        
        Initialize members of QUICK_SELECT_I in constructor to make sure unitialized members will not create surprises for subclasses.
     @ sql/opt_range.h
        Added new class QUICK_EMPTY_SELECT as a subclass of QUICK_SELECT_I.
        It represents a quick select with no rows.
        
        Change *get_quick_select_for_ref to return QUICK_SELECT_I*.  This way, it
        may return a pointer to either a QUICK_RANGE_SELECT object or a
        QUICK_EMPTY_SELECT object.
     @ sql/sql_select.h
        Add a method impossible_null_ref() to TABLE_REF.  This method will check
        whether the reference contains NULL values so that it could never match any
        rows.

    modified:
      mysql-test/r/join_optimizer.result
      mysql-test/t/join_optimizer.test
      sql/opt_range.cc
      sql/opt_range.h
      sql/sql_select.h
=== modified file 'mysql-test/r/join_optimizer.result'
--- a/mysql-test/r/join_optimizer.result	2009-01-26 19:42:59 +0000
+++ b/mysql-test/r/join_optimizer.result	2009-11-11 13:20:12 +0000
@@ -35,3 +35,25 @@ id	select_type	table	type	possible_keys	
 1	SIMPLE	a	ALL	NULL	NULL	NULL	NULL	2	
 1	SIMPLE	g	ref	groups_dt	groups_dt	70	const,test.a.type	13	Using index condition
 drop table t0,t1,t2,t3;
+#
+# BUG#47217 Simple Inner join gives wrong result when order by is used
+#
+CREATE TABLE t1 (pk INT, v VARCHAR(2), PRIMARY KEY(pk));
+CREATE INDEX ix1 ON t1(v);
+CREATE TABLE t2 (pk INT, v VARCHAR(2), PRIMARY KEY(pk));
+CREATE INDEX ix2 ON t2(v);
+INSERT INTO t1 VALUES (10,'a'),(11,NULL);
+INSERT INTO t2 VALUES (10,NULL);
+EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.v = t2.v ORDER BY 1;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t2	system	ix2	NULL	NULL	NULL	1	Using filesort
+1	SIMPLE	t1	ref	ix1	ix1	5	const	1	Using where
+EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.v = t2.v;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t2	system	ix2	NULL	NULL	NULL	1	
+1	SIMPLE	t1	ref	ix1	ix1	5	const	1	
+SELECT * FROM t1 JOIN t2 ON t1.v = t2.v ORDER BY 1;
+pk	v	pk	v
+SELECT * FROM t1 JOIN t2 ON t1.v = t2.v;
+pk	v	pk	v
+DROP TABLE t1, t2;

=== modified file 'mysql-test/t/join_optimizer.test'
--- a/mysql-test/t/join_optimizer.test	2009-01-26 19:42:59 +0000
+++ b/mysql-test/t/join_optimizer.test	2009-11-11 13:20:12 +0000
@@ -43,3 +43,23 @@ SELECT STRAIGHT_JOIN g.id FROM t2 a, t3 
 WHERE g.domain = 'queue' AND g.type = a.type;
 
 drop table t0,t1,t2,t3;
+
+--echo #
+--echo # BUG#47217 Simple Inner join gives wrong result when order by is used
+--echo #
+CREATE TABLE t1 (pk INT, v VARCHAR(2), PRIMARY KEY(pk));
+CREATE INDEX ix1 ON t1(v);
+CREATE TABLE t2 (pk INT, v VARCHAR(2), PRIMARY KEY(pk));
+CREATE INDEX ix2 ON t2(v);
+
+INSERT INTO t1 VALUES (10,'a'),(11,NULL);
+INSERT INTO t2 VALUES (10,NULL);
+
+EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.v = t2.v ORDER BY 1;
+EXPLAIN SELECT * FROM t1 JOIN t2 ON t1.v = t2.v;
+SELECT * FROM t1 JOIN t2 ON t1.v = t2.v ORDER BY 1;
+SELECT * FROM t1 JOIN t2 ON t1.v = t2.v;
+
+DROP TABLE t1, t2;
+
+

=== modified file 'sql/opt_range.cc'
--- a/sql/opt_range.cc	2009-11-06 07:40:19 +0000
+++ b/sql/opt_range.cc	2009-11-11 13:20:12 +0000
@@ -1147,8 +1147,8 @@ SQL_SELECT::~SQL_SELECT()
 #undef index					// Fix for Unixware 7
 
 QUICK_SELECT_I::QUICK_SELECT_I()
-  :max_used_key_length(0),
-   used_key_parts(0)
+  :records(0), read_time(0), head(0), index(0), max_used_key_length(0),
+   used_key_parts(0), last_rowid(0), record(0)
 {}
 
 QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(THD *thd, TABLE *table, uint key_nr,
@@ -8115,7 +8115,7 @@ key_has_nulls(const KEY* key_info, const
     NULL on error.
 */
 
-QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
+QUICK_SELECT_I *get_quick_select_for_ref(THD *thd, TABLE *table,
                                              TABLE_REF *ref, ha_rows records)
 {
   MEM_ROOT *old_root, *alloc;
@@ -8127,6 +8127,9 @@ QUICK_RANGE_SELECT *get_quick_select_for
   bool create_err= FALSE;
   COST_VECT cost;
 
+  if (ref->impossible_null_ref())
+    return new QUICK_EMPTY_SELECT();
+
   old_root= thd->mem_root;
   /* The following call may change thd->mem_root */
   quick= new QUICK_RANGE_SELECT(thd, table, ref->key, 0, 0, &create_err);

=== modified file 'sql/opt_range.h'
--- a/sql/opt_range.h	2009-09-30 15:24:05 +0000
+++ b/sql/opt_range.h	2009-11-11 13:20:12 +0000
@@ -203,7 +203,8 @@ public:
     QS_TYPE_FULLTEXT   = 3,
     QS_TYPE_ROR_INTERSECT = 4,
     QS_TYPE_ROR_UNION = 5,
-    QS_TYPE_GROUP_MIN_MAX = 6
+    QS_TYPE_GROUP_MIN_MAX = 6,
+    QS_TYPE_EMPTY = 7
   };
 
   /* Get type of this quick select - one of the QS_TYPE_* values */
@@ -271,6 +272,27 @@ public:
 };
 
 
+/**
+ * Class representing a quick select with no rows.
+ */
+class QUICK_EMPTY_SELECT: public QUICK_SELECT_I
+{
+public:
+  virtual int init() { return 0; }
+  virtual int reset() { return 0; }
+  virtual int get_next() { return HA_ERR_END_OF_FILE; }
+  virtual bool reverse_sorted() { return false; }
+  virtual void need_sorted_output() {}
+  virtual int get_type() { return QS_TYPE_EMPTY; }
+  virtual void add_keys_and_lengths(String*, String*) {}
+#ifndef DBUG_OFF
+  virtual void dbug_dump(int indent, bool verbose)
+  {
+    fprintf(DBUG_FILE, "%*squick empty selects",indent, "");
+  }
+#endif
+};
+
 struct st_qsel_param;
 class PARAM;
 class SEL_ARG;
@@ -355,10 +377,9 @@ public:
 private:
   /* Default copy ctor used by QUICK_SELECT_DESC */
   friend class TRP_ROR_INTERSECT;
-  friend
-  QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
-                                               struct st_table_ref *ref,
-                                               ha_rows records);
+  friend QUICK_SELECT_I *get_quick_select_for_ref(THD *thd, TABLE *table,
+                                                  struct st_table_ref *ref,
+                                                  ha_rows records);
   friend bool get_quick_keys(PARAM *param, QUICK_RANGE_SELECT *quick, 
                              KEY_PART *key, SEL_ARG *key_tree, 
                              uchar *min_key, uint min_key_flag,
@@ -780,9 +801,9 @@ public:
 };
 
 FT_SELECT *get_ft_select(THD *thd, TABLE *table, uint key);
-QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
-                                             struct st_table_ref *ref,
-                                             ha_rows records);
+QUICK_SELECT_I *get_quick_select_for_ref(THD *thd, TABLE *table,
+                                         struct st_table_ref *ref,
+                                         ha_rows records);
 uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit);
 
 #ifdef WITH_PARTITION_STORAGE_ENGINE

=== modified file 'sql/sql_select.h'
--- a/sql/sql_select.h	2009-10-26 14:02:26 +0000
+++ b/sql/sql_select.h	2009-11-11 13:20:12 +0000
@@ -103,6 +103,25 @@ typedef struct st_table_ref
     produce different results (because of Index Condition Pushdown)
   */
   bool          disable_cache;
+
+  /**
+   * Check if reference contains NULL values so that it could never
+   * match any rows.
+   *
+   * @return
+   *    TRUE if part of key is a NULL constant, and null_rejecting is set for
+   *         this part.
+   *    FALSE otherwise
+   */
+  bool impossible_null_ref() 
+  {
+    for (uint part=0; part < key_parts; part++)
+      if ((null_rejecting & (1 << part)) &&
+          items[part]->const_item() && items[part]->is_null())
+        return true;
+    return false;
+  }
+
 } TABLE_REF;
 
 


Attachment: [text/bzr-bundle]
Thread
bzr commit into mysql-6.0-codebase-bugfixing branch(oystein.grovlen:3703) Bug#47217Oystein.Grovlen11 Nov
  • Re: bzr commit into mysql-6.0-codebase-bugfixing branch(oystein.grovlen:3703) Bug#47217Evgeny Potemkin12 Nov