MySQL Lists are EOL. Please join:

List:Commits« Previous MessageNext Message »
From:Guilhem Bichot Date:June 4 2010 12:53pm
Subject:bzr commit into mysql-next-mr-bugfixing branch (guilhem:3185) Bug#52636
View as plain text  
#At file:///home/mysql_src/bzrrepos_new/opt-back-52636/ based on revid:olav@stripped

 3185 Guilhem Bichot	2010-06-01
      Fix for BUG#52636 6.0 allowing JOINs on NULL values w/ optimizer_join_cache_level = 5-8
      Optimizer chose "ref" access for the LEFT JOIN ON condition, and considered that this
      access it implemented the condition's semantics; which would have been true if
      JOIN_CACHE_BKA and JOIN_CACHE_BKA_UNIQUE hadn't forgotten to eliminate NULL values.
     @ mysql-test/r/join_cache.result
        Before the fix, the SELECTs would show:
        both first and 2nd SELECT: (100),(NULL); 3rd SELECT: (100),(100);
        both 4th and 5th: (100),(200),(NULL).
        For example 1st SELECT would find no match in t2 for t1's 3 (thus
        emit t2.a=NULL, as it's a left join) and a match for t1's NULL
        (the bug) thus emit t2.a=100.
     @ mysql-test/t/join_cache.test
        test for bug
     @ sql/handler.cc
        After fixing sql_join_cache.cc, if the referenced table (t1 in the
        testcase) has only NULL rows, it can now happen, in
        handler::multi_range_read_next(), that the first call to
        mrr_funcs.next() (== bka_range_seq_next or bka_unique_range_seq_next)
        finds no keys (because it now internally eliminates NULLs); so
        "result" needs to be initialized for this case, otherwise behaviour
        is random (segmentation fault).
     @ sql/sql_join_cache.cc
        What happened in the test's scenario
        SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
        is:
        - plan is to access t1, and then t2 with "ref" access on key t2.b to
        the reference t1.b.
        - make_cond_for_table_from_pred() recognizes that the ON equality can
        be discarded as it's a consequence of using "ref" access
        - this "ref" access is marked as null_rejecting.
        So far so good, and at execution with optimizer_join_cache_level<=4,
        there is no problem, "ref" access is done with join_read_always_key()
        which tests ref->null_rejecting and does not pass t1.b NULL values
        to t2's index lookup.
        At higher levels, JOIN_CACHE_BKA or JOIN_CACHE_BKA_UNIQUE are used
        instead of join_read_always_key(), and they forgot to test
        ref->null_rejecting, so NULL values of t1.b where accidentally matched
        with NULL values of t2.b, leading to wrong results (see comment of
        join_cache.result).
        The fix is to make JOIN_CACHE_BKA and JOIN_CAHE_BKA_UNIQUE observe
        null_rejecting: when they scan the cached rows of the outer table t1,
        collecting key values from those cached rows in order to send them
        to t2's multi-range-read, we now don't collect any NULL key value.
        For this, JOIN_CACHE_BKA gets the same test as join_read_always_key().
        But JOIN_CACHE_BKA_UNIQUE is different, it doesn't have up-to-date
        fields pointed to by "ref", testable with ref->items[i].is_null(); it
        only has a raw key value (left by a previous duplicate-key-eliminating
        phase), so we inspect this key.
     @ sql/sql_select.cc
        comments. The last one is verbose, as failing to observe it has resulted in the present
        bug as well as BUG 46743.
     @ sql/sql_select.h
        A member function to reduce duplicated code.
        A helper which can be used to temporarily reduce a testcase when debugging
        (allows to have one-row tables and still follow the same execution as
        two-row tables).

    modified:
      mysql-test/r/join_cache.result
      mysql-test/t/join_cache.test
      sql/handler.cc
      sql/sql_join_cache.cc
      sql/sql_select.cc
      sql/sql_select.h
=== modified file 'mysql-test/r/join_cache.result'
--- a/mysql-test/r/join_cache.result	2010-05-12 16:03:52 +0000
+++ b/mysql-test/r/join_cache.result	2010-06-01 14:09:11 +0000
@@ -4212,3 +4212,63 @@ pk
 NULL
 DROP TABLE IF EXISTS t1, t2, t3;
 #
+# BUG#52636 6.0 allowing JOINs on NULL values w/ optimizer_join_cache_level = 5-8
+#
+CREATE TABLE t1 (b int);
+INSERT INTO t1 VALUES (NULL),(3);
+CREATE TABLE t2 (a int, b int, KEY (b));
+INSERT INTO t2 VALUES (100,NULL),(150,200);
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	b	b	5	test.t1.b	2	Using join buffer
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+a
+NULL
+NULL
+set optimizer_join_cache_level = 8;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	b	b	5	test.t1.b	2	Using join buffer
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+a
+NULL
+NULL
+delete from t1;
+INSERT INTO t1 VALUES (NULL),(NULL);
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	b	b	5	test.t1.b	2	Using join buffer
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+a
+NULL
+NULL
+DROP TABLE t1,t2;
+CREATE TABLE t1 (b varchar(100));
+INSERT INTO t1 VALUES (NULL),("some varchar");
+CREATE TABLE t2 (a int, b varchar(100), KEY (b));
+INSERT INTO t2 VALUES (100,NULL),(150,"varchar"),(200,NULL),(250,"long long varchar");
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	b	b	103	test.t1.b	2	Using join buffer
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+a
+NULL
+NULL
+set optimizer_join_cache_level = 8;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	b	b	103	test.t1.b	2	Using join buffer
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+a
+NULL
+NULL
+set optimizer_join_cache_level = default;
+DROP TABLE t1,t2;

=== modified file 'mysql-test/t/join_cache.test'
--- a/mysql-test/t/join_cache.test	2010-05-12 15:35:38 +0000
+++ b/mysql-test/t/join_cache.test	2010-06-01 14:09:11 +0000
@@ -1873,3 +1873,51 @@ GROUP BY 1;
 DROP TABLE IF EXISTS t1, t2, t3;
 
 --echo #
+--echo # BUG#52636 6.0 allowing JOINs on NULL values w/ optimizer_join_cache_level = 5-8
+--echo #
+
+CREATE TABLE t1 (b int);
+INSERT INTO t1 VALUES (NULL),(3);
+
+CREATE TABLE t2 (a int, b int, KEY (b));
+INSERT INTO t2 VALUES (100,NULL),(150,200);
+
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+--sorted_result
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+
+set optimizer_join_cache_level = 8;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+--sorted_result
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+
+# test crash when no key is worth collecting by BKA for t2's ref
+delete from t1;
+INSERT INTO t1 VALUES (NULL),(NULL);
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+--sorted_result
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+
+DROP TABLE t1,t2;
+
+# test varchar keys
+CREATE TABLE t1 (b varchar(100));
+INSERT INTO t1 VALUES (NULL),("some varchar");
+
+CREATE TABLE t2 (a int, b varchar(100), KEY (b));
+INSERT INTO t2 VALUES (100,NULL),(150,"varchar"),(200,NULL),(250,"long long varchar");
+
+set optimizer_join_cache_level = 5;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+--sorted_result
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+
+set optimizer_join_cache_level = 8;
+explain SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+--sorted_result
+SELECT t2.a FROM t1 LEFT JOIN t2 ON t2.b  = t1.b;
+
+set optimizer_join_cache_level = default;
+DROP TABLE t1,t2;

=== modified file 'sql/handler.cc'
--- a/sql/handler.cc	2010-05-25 15:24:05 +0000
+++ b/sql/handler.cc	2010-06-01 14:09:11 +0000
@@ -4348,10 +4348,9 @@ handler::multi_range_read_init(RANGE_SEQ
 
 int handler::multi_range_read_next(char **range_info)
 {
-  int result;
+  int result= HA_ERR_END_OF_FILE;
   int range_res;
   DBUG_ENTER("handler::multi_range_read_next");
-  LINT_INIT(result);
 
   if (!mrr_have_range)
   {
@@ -4373,11 +4372,6 @@ int handler::multi_range_read_next(char 
     {
       if (was_semi_consistent_read())
         goto scan_it_again;
-      /*
-        We need to set this for the last range only, but checking this
-        condition is more expensive than just setting the result code.
-      */
-      result= HA_ERR_END_OF_FILE;
     }
 
 start:

=== modified file 'sql/sql_join_cache.cc'
--- a/sql/sql_join_cache.cc	2010-05-14 10:58:39 +0000
+++ b/sql/sql_join_cache.cc	2010-06-01 14:09:11 +0000
@@ -738,7 +738,14 @@ bool JOIN_CACHE_BKA::check_emb_key_usage
     if (!key_part->field->eq_def(((Item_field *) item)->field))
       return FALSE;
     if (key_part->field->maybe_null())
+    {
       return FALSE;
+      /*
+        If this is changed so that embedded keys may contain nullable
+        components, get_next_key() willhave to test ref->null_rejecting in the
+        "embedded keys" case too.
+      */
+    }
   }
   
   copy= field_descr+flag_fields;
@@ -2455,84 +2462,101 @@ uint JOIN_CACHE_BKA::get_next_key(uchar 
   uint32 rec_len;
   uchar *init_pos;
   JOIN_CACHE *cache;
-  
-  /*
-    '>=' (unlike '>' in JOIN_CACHE::record()) because we are not at fields'
-    start, and previous record's fields might be empty.
-  */
-  if (pos >= last_rec_pos || !records)
+
+  if (records == 0)
     return 0;
 
-  /* Any record in a BKA cache is prepended with its length */
+  /* Any record in a BKA cache is prepended with its length, which we need */
   DBUG_ASSERT(with_length);
-   
-  /* Read the length of the record */
-  rec_len= get_rec_length(pos);
-  pos+= size_of_rec_len; 
-  init_pos= pos;
 
-  /* Read a reference to the previous cache if any */
-  if (prev_cache)
-    pos+= prev_cache->get_size_of_rec_offset();
+  /*
+    Read keys until find non-ignorable one or EOF.
+    Unlike in JOIN_CACHE::read_all_record_fields()), pos>=last_rec_pos means
+    EOF, because we are not at fields' start, and previous record's fields
+    might be empty.
+  */
+  for(len= 0 ; (len == 0) && pos < last_rec_pos ; pos= init_pos + rec_len)
+  {
+    /* Read the length of the record */
+    rec_len= get_rec_length(pos);
+    pos+= size_of_rec_len;
+    init_pos= pos;
 
-  curr_rec_pos= pos;
+    /* Read a reference to the previous cache if any */
+    if (prev_cache)
+      pos+= prev_cache->get_size_of_rec_offset();
 
-  /* Read all flag fields of the record */
-  read_flag_fields();
- 
-  if (use_emb_key)
-  {
-    /* An embedded key is taken directly from the join buffer */
-    *key= pos;
-    len= emb_key_length;
-  }
-  else
-  {
-    /* Read key arguments from previous caches if there are any such fields */
-    if (external_key_arg_fields)
+    curr_rec_pos= pos;
+
+    /* Read all flag fields of the record */
+    read_flag_fields();
+
+    if (use_emb_key)
     {
-      uchar *rec_ptr= curr_rec_pos;
-      uint key_arg_count= external_key_arg_fields;
-      CACHE_FIELD **copy_ptr= blob_ptr-key_arg_count;
-      for (cache= prev_cache; key_arg_count; cache= cache->prev_cache)
-      { 
-        uint len= 0;
-        DBUG_ASSERT(cache);
-        rec_ptr= cache->get_rec_ref(rec_ptr);
-        while (!cache->referenced_fields)
+      /* An embedded key is taken directly from the join buffer */
+      *key= pos;
+      len= emb_key_length;
+      DBUG_ASSERT(len != 0);
+    }
+    else
+    {
+      /*
+        Read key arguments from previous caches if there are any such
+        fields
+      */
+      if (external_key_arg_fields)
+      {
+        uchar *rec_ptr= curr_rec_pos;
+        uint key_arg_count= external_key_arg_fields;
+        CACHE_FIELD **copy_ptr= blob_ptr-key_arg_count;
+        for (cache= prev_cache; key_arg_count; cache= cache->prev_cache)
         {
-          cache= cache->prev_cache;
+          uint len2= 0;
           DBUG_ASSERT(cache);
           rec_ptr= cache->get_rec_ref(rec_ptr);
-        }
-        while (key_arg_count && 
-               cache->read_referenced_field(*copy_ptr, rec_ptr, &len))
-        {
-          copy_ptr++;
-          --key_arg_count;
+          while (!cache->referenced_fields)
+          {
+            cache= cache->prev_cache;
+            DBUG_ASSERT(cache);
+            rec_ptr= cache->get_rec_ref(rec_ptr);
+          }
+          while (key_arg_count && 
+                 cache->read_referenced_field(*copy_ptr, rec_ptr, &len2))
+          {
+            copy_ptr++;
+            --key_arg_count;
+          }
         }
       }
-    }
-    
-    /* 
-      Read the other key arguments from the current record. The fields for
-      these arguments are always first in the sequence of the record's fields.
-    */     
-    CACHE_FIELD *copy= field_descr+flag_fields;
-    CACHE_FIELD *copy_end= copy+local_key_arg_fields;
-    bool blob_in_rec_buff= blob_data_is_in_rec_buff(curr_rec_pos);
-    for ( ; copy < copy_end; copy++)
-      read_record_field(copy, blob_in_rec_buff);
-    
-    /* Build the key over the fields read into the record buffers */ 
-    TABLE_REF *ref= &join_tab->ref;
-    cp_buffer_from_ref(join->thd, join_tab->table, ref);
-    *key= ref->key_buff;
-    len= ref->key_length;
-  }
 
-  pos= init_pos+rec_len;
+      /*
+         Read the other key arguments from the current record. The fields for
+         these arguments are always first in the sequence of the record's
+         fields.
+      */
+      CACHE_FIELD *copy= field_descr+flag_fields;
+      CACHE_FIELD *copy_end= copy+local_key_arg_fields;
+      bool blob_in_rec_buff= blob_data_is_in_rec_buff(curr_rec_pos);
+      for ( ; copy < copy_end; copy++)
+        read_record_field(copy, blob_in_rec_buff);
 
+      TABLE_REF *ref= &join_tab->ref;
+      if (ref->impossible_null_ref())
+      {
+        DBUG_PRINT("info", ("JOIN_CACHE_BKA::get_next_key null_rejected"));
+        /* this key cannot give a match, don't collect it, go read next key */
+        len= 0;
+      }
+      else
+      {
+        /* Build the key over the fields read into the record buffers */
+        cp_buffer_from_ref(join->thd, join_tab->table, ref);
+        *key= ref->key_buff;
+        len= ref->key_length;
+        DBUG_ASSERT(len != 0);
+      }
+    }
+  }
   return len;
 } 
 
@@ -3278,16 +3302,50 @@ bool JOIN_CACHE_BKA_UNIQUE::check_all_ma
 
 uint JOIN_CACHE_BKA_UNIQUE::get_next_key(uchar ** key)
 {  
-  if (curr_key_entry == last_key_entry)
-    return 0;
 
-  curr_key_entry-= key_entry_length;
+  uint len= 0;
+
+  /* Read keys until find non-ignorable one or EOF */
+  while((curr_key_entry > last_key_entry) && (len == 0))
+  {
+    curr_key_entry-= key_entry_length;
 
-  *key = use_emb_key ? get_emb_key(curr_key_entry) : curr_key_entry;
+    *key = use_emb_key ? get_emb_key(curr_key_entry) : curr_key_entry;
 
-  DBUG_ASSERT(*key >= buff && *key < hash_table);
+    DBUG_ASSERT(*key >= buff && *key < hash_table);
 
-  return key_length;
+    len= key_length;
+    DBUG_ASSERT(len != 0);
+    const TABLE_REF *ref= &join_tab->ref;
+    if (ref->null_rejecting != 0)
+    {
+      /*
+        Unlike JOIN_CACHE_BKA::get_next_key(), we have a key just read from
+        a buffer, not up-to-date fields pointed to by "ref". So we cannot use
+        ref->null_rejected(), must inspect the key.
+      */
+      const KEY *key_info= join_tab->table->key_info + ref->key;
+      const uchar *ptr= *key;
+#ifndef DBUG_OFF
+      const uchar *key_end= ptr + key_length;
+#endif
+      for (uint i= 0 ; i < ref->key_parts ; i++)
+      {
+        KEY_PART_INFO *key_part= key_info->key_part + i;
+        if (key_part->null_bit && ptr[0] && (ref->null_rejecting & 1 << i))
+        {
+          DBUG_PRINT("info", ("JOIN_CACHE_BKA_UNIQUE::get_next_key null_rejected"));
+          len= 0;
+          break;
+        }
+        ptr+= key_part->store_length;
+        DBUG_ASSERT(ptr <= key_end);
+      }
+      if (len != 0)
+        DBUG_ASSERT(ptr == key_end); /* should have read the full key */
+    }
+  }
+  return len;
 }
 
 

=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc	2010-05-25 15:24:05 +0000
+++ b/sql/sql_select.cc	2010-06-01 14:09:11 +0000
@@ -5171,7 +5171,9 @@ add_key_field(KEY_FIELD **key_fields,uin
     othertbl.field can be NULL, there will be no matches if othertbl.field 
     has NULL value.
     We use null_rejecting in add_not_null_conds() to add
-    'othertbl.field IS NOT NULL' to tab->select_cond.
+    'othertbl.field IS NOT NULL' to tab->select_cond, if this is not an outer
+    join. We also use it to shortcut reading "tbl" when othertbl.field is
+    found to be a NULL value (in join_read_always_key() and BKA).
   */
   {
     Item *real= (*value)->real_item();
@@ -17380,7 +17382,10 @@ join_read_key2(JOIN_TAB *tab, TABLE *tab
     table->file->ha_index_init(table_ref->key, tab->sorted);
   }
 
-  /* TODO: Why don't we do "Late NULLs Filtering" here? */
+  /*
+    We needn't do "Late NULLs Filtering" because eq_ref is restricted to
+    indices on NOT NULL columns (see create_ref_for_key()).
+  */
   if (cmp_buffer_with_ref(tab->join->thd, table, table_ref) ||
       (table->status & (STATUS_GARBAGE | STATUS_NO_PARENT | STATUS_NULL_ROW)))
   {
@@ -17483,20 +17488,18 @@ join_read_always_key(JOIN_TAB *tab)
     table->file->ha_index_init(tab->ref.key, tab->sorted);
 
   /* Perform "Late NULLs Filtering" (see internals manual for explanations) */
-  for (uint i= 0 ; i < tab->ref.key_parts ; i++)
+  TABLE_REF *ref= &tab->ref;
+  if (ref->impossible_null_ref())
   {
-    if ((tab->ref.null_rejecting & 1 << i) && tab->ref.items[i]->is_null())
-    {
-      DBUG_PRINT("info", ("join_read_always_key null rejected"));
-      return -1;
-    }
+    DBUG_PRINT("info", ("join_read_always_key null_rejected"));
+    return -1;
   }
 
-  if (cp_buffer_from_ref(tab->join->thd, table, &tab->ref))
+  if (cp_buffer_from_ref(tab->join->thd, table, ref))
     return -1;
   if ((error=table->file->index_read_map(table->record[0],
-                                         tab->ref.key_buff,
-                                         make_prev_keypart_map(tab->ref.key_parts),
+                                         ref->key_buff,
+                                         make_prev_keypart_map(ref->key_parts),
                                          HA_READ_KEY_EXACT)))
   {
     if (error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
@@ -18258,6 +18261,9 @@ end_write_group(JOIN *join, JOIN_TAB *jo
 /**
   @return
     1 if right_item is used removable reference key on left_item
+
+  @note see comments in make_cond_for_table_from_pred() about careful
+  usage/modifications of test_if_ref().
 */
 
 static bool test_if_ref(COND *root_cond, 
@@ -18500,7 +18506,23 @@ make_cond_for_table_from_pred(COND *root
 
   /* 
     Remove equalities that are guaranteed to be true by use of 'ref' access
-    method
+    method.
+    Note that ref access implements "table1.field1 <=> table2.indexed_field2",
+    i.e. if it passed a NULL field1, it will return NULL indexed_field2 if
+    there are.
+    Thus the equality "table1.field1 = table2.indexed_field2",
+    is equivalent to "ref access AND table1.field1 IS NOT NULL"
+    i.e. "ref access and proper setting/testing of ref->null_rejecting".
+    Thus, we must be careful, that when we remove equalities below we also
+    set ref->null_rejecting, and test it at execution; otherwise wrong NULL
+    matches appear.
+    So:
+    - for the optimization phase, the code which is below, and the code in
+    test_if_ref(), and in add_key_field(), must be kept in sync: if the
+    applicability conditions in one place are relaxed, they should also be
+    relaxed elsewhere.
+    - for the execution phase, all possible execution methods must test
+    ref->null_rejecting.
   */
   if (cond->type() == Item::FUNC_ITEM &&
       ((Item_func*) cond)->functype() == Item_func::EQ_FUNC)

=== modified file 'sql/sql_select.h'
--- a/sql/sql_select.h	2010-05-19 14:44:18 +0000
+++ b/sql/sql_select.h	2010-06-01 14:09:11 +0000
@@ -115,6 +115,24 @@ typedef struct st_table_ref
     produce different results (because of Index Condition Pushdown)
   */
   bool          disable_cache;
+
+  /**
+    @returns whether the reference contains NULL values which could never give
+    a match.
+  */
+  bool impossible_null_ref() const
+  {
+    if (null_rejecting != 0)
+    {
+      for (uint i= 0 ; i < key_parts ; i++)
+      {
+        if ((null_rejecting & 1 << i) && items[i]->is_null())
+          return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
 } TABLE_REF;
 
 
@@ -1740,6 +1758,8 @@ public:
     rollup.state= ROLLUP::STATE_NONE;
 
     no_const_tables= FALSE;
+    /* can help debugging (makes smaller test cases): */
+    DBUG_EXECUTE_IF("no_const_tables",no_const_tables= TRUE;);
     first_select= sub_select;
   }
 


Attachment: [text/bzr-bundle] bzr/guilhem@mysql.com-20100601140911-ph3js219lqwv2r6m.bundle
Thread
bzr commit into mysql-next-mr-bugfixing branch (guilhem:3185) Bug#52636Guilhem Bichot4 Jun