From: Jorgen Loland Date: May 6 2011 1:26pm Subject: bzr commit into mysql-trunk branch (jorgen.loland:3381) Bug#11765831 List-Archive: http://lists.mysql.com/commits/136839 X-Bug: 11765831 Message-Id: <20110506132634.CC13AB8E@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============8640064105266782167==" --===============8640064105266782167== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///export/home/jl208045/mysql/mysql-trunk-11765831/ based on revid:alexander.nozdrin@stripped 3381 Jorgen Loland 2011-05-06 BUG#11765831: 'RANGE ACCESS' MAY INCORRECTLY FILTER AWAY QUALIFYING ROWS Preparation patch (does not include fix for the bug): * Extensively document key_or() * Remove tab indentations from key_or() * Minor code changes like using existing utility functions in key_or() @ sql/opt_range.cc Extensively document key_or(), remove tab indentations from key_or(), minor code changes like using existing utility functions in key_or() ****** In key_or(): When a range in key1 was split due to a partially overlapping range in key2, the non-overlaping part incorrectly got next_key_part from the R-B tree root node instead of the currently active range. key_or() could also create adjacent ranges that could be replaced by a single continous range. This is also fixed. modified: sql/opt_range.cc === modified file 'sql/opt_range.cc' --- a/sql/opt_range.cc 2011-04-23 20:44:45 +0000 +++ b/sql/opt_range.cc 2011-05-06 13:26:31 +0000 @@ -6725,7 +6725,7 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * { key1->free_tree(); key2->free_tree(); - return 0; // Can't optimize this + return 0; // Can't optimize this } // If one of the key is MAYBE_KEY then the found region may be bigger @@ -6749,246 +6749,493 @@ key_or(RANGE_OPT_PARAM *param, SEL_ARG * swap_variables(SEL_ARG *,key1,key2); } if (key1->use_count > 0 || !(key1=key1->clone_tree(param))) - return 0; // OOM + return 0; // OOM } // Add tree at key2 to tree at key1 bool key2_shared=key2->use_count != 0; key1->maybe_flag|=key2->maybe_flag; + /* + Notation for illustrations used in the rest of this function: + + Range: [--------] + ^ ^ + start stop + + Two overlapping ranges: + [-----] [----] [--] + [---] or [---] or [-------] + + Ambiguity: *** + The range starts or stops somewhere in the "***" range. + Example: a starts before b and may end before/the same plase/after b + a: [----***] + b: [---] + + Adjacent ranges: + Ranges that meet but do not overlap. Example: a = "x < 3", b = "x >= 3" + a: ----] + b: [---- + */ + for (key2=key2->first(); key2; ) { - SEL_ARG *tmp=key1->find_range(key2); // Find key1.min <= key2.min - int cmp; + /* + key1 consists of one or more ranges. tmp is the range currently + being handled. + + initialize tmp to the latest range in key1 that starts the same + place or before the range in key2 starts + + key2: [------] + key1: [---] [-----] [----] + ^ + tmp + */ + SEL_ARG *tmp=key1->find_range(key2); + + /* + Used to describe how two key values are positioned compared to + each other. Consider key_value_a.(key_value_b): + + -2: key_value_a is smaller than key_value_b, and they are adjacent + -1: key_value_a is smaller than key_value_b (not adjacent) + 0: the key values are equal + 1: key_value_a is bigger than key_value_b (not adjacent) + -2: key_value_a is bigger than key_value_b, and they are adjacent + + Example: "cmp= tmp->cmp_max_to_min(key2)" + + key2: [-------- (10 <= x ...) + tmp: -----] (... x < 10) => cmp==-2 + tmp: ----] (... x <= 9) => cmp==-1 + tmp: ------] (... x = 10) => cmp== 0 + tmp: --------] (... x <= 12) => cmp== 1 + (cmp == 2 does not make sense for cmp_max_to_min()) + */ + int cmp= 0; if (!tmp) { - tmp=key1->first(); // tmp.min > key2.min + /* + The range in key2 starts before the first range in key1. Use + the first range in key1 as tmp. + + key2: [--------] + key1: [****--] [----] [-------] + ^ + tmp + */ + tmp=key1->first(); cmp= -1; } - else if ((cmp=tmp->cmp_max_to_min(key2)) < 0) - { // Found tmp.max < key2.min + else if ((cmp= tmp->cmp_max_to_min(key2)) < 0) + { + /* + This is the case: + key2: [-------] + tmp: [----**] + */ SEL_ARG *next=tmp->next; - /* key1 on the left of key2 non-overlapping */ if (cmp == -2 && eq_tree(tmp->next_key_part,key2->next_key_part)) { - // Join near ranges like tmp.max < 0 and key2.min >= 0 - SEL_ARG *key2_next=key2->next; - if (key2_shared) - { - if (!(key2=new SEL_ARG(*key2))) - return 0; // out of memory - key2->increment_use_count(key1->use_count+1); - key2->next=key2_next; // New copy of key2 - } - key2->copy_min(tmp); - if (!(key1=key1->tree_delete(tmp))) - { // Only one key in tree - key1=key2; - key1->make_root(); - key2=key2_next; - break; - } + /* + Adjacent (cmp==-2) and equal next_key_parts => ranges can be merged + + This is the case: + key2: [-------] + tmp: [----] + + Result: + key2: [-------------] => inserted into key1 below + tmp: => deleted + */ + SEL_ARG *key2_next=key2->next; + if (key2_shared) + { + if (!(key2=new SEL_ARG(*key2))) + return 0; // out of memory + key2->increment_use_count(key1->use_count+1); + key2->next=key2_next; // New copy of key2 + } + + key2->copy_min(tmp); + if (!(key1=key1->tree_delete(tmp))) + { // Only one key in tree + key1=key2; + key1->make_root(); + key2=key2_next; + break; + } } - if (!(tmp=next)) // tmp.min > key2.min - break; // Copy rest of key2 + if (!(tmp=next)) // Move to next range in key1. Now tmp.min > key2.min + break; // No more ranges in key1. Copy rest of key2 } + if (cmp < 0) - { // tmp.min > key2.min + { + /* + This is the case: + key2: [--***] + tmp: [----] + */ int tmp_cmp; - if ((tmp_cmp=tmp->cmp_min_to_max(key2)) > 0) // if tmp.min > key2.max + if ((tmp_cmp=tmp->cmp_min_to_max(key2)) > 0) { - /* tmp is on the right of key2 non-overlapping */ - if (tmp_cmp == 2 && eq_tree(tmp->next_key_part,key2->next_key_part)) - { // ranges are connected - tmp->copy_min_to_min(key2); - key1->merge_flags(key2); - if (tmp->min_flag & NO_MIN_RANGE && - tmp->max_flag & NO_MAX_RANGE) - { - if (key1->maybe_flag) - return new SEL_ARG(SEL_ARG::MAYBE_KEY); - return 0; - } - key2->increment_use_count(-1); // Free not used tree - key2=key2->next; - continue; - } - else - { - SEL_ARG *next=key2->next; // Keys are not overlapping - if (key2_shared) - { - SEL_ARG *cpy= new SEL_ARG(*key2); // Must make copy - if (!cpy) - return 0; // OOM - key1=key1->insert(cpy); - key2->increment_use_count(key1->use_count+1); - } - else - key1=key1->insert(key2); // Will destroy key2_root - key2=next; - continue; - } + /* + This is the case: + key2: [------**] + tmp: [----] + */ + if (tmp_cmp == 2 && eq_tree(tmp->next_key_part,key2->next_key_part)) + { + /* + Adjacent ranges with equal next_key_part. Merge like this: + + This is the case: + key2: [------] + tmp: [-----] + + Result: + key2: [------] + tmp: [-------------] + + Then move on to next key2 range. + */ + tmp->copy_min_to_min(key2); + key1->merge_flags(key2); + if (tmp->min_flag & NO_MIN_RANGE && + tmp->max_flag & NO_MAX_RANGE) + { + if (key1->maybe_flag) + return new SEL_ARG(SEL_ARG::MAYBE_KEY); + return 0; + } + key2->increment_use_count(-1); // Free not used tree + key2=key2->next; + continue; + } + else + { + /* + key2 not adjacent to tmp or has different next_key_part. + Insert into key1 and move to next range in key2 + + This is the case: + key2: [------**] + tmp: [----] + + Result: + key1_ [------**][----] + ^ ^ + insert tmp + */ + SEL_ARG *next=key2->next; + if (key2_shared) + { + SEL_ARG *cpy= new SEL_ARG(*key2); // Must make copy + if (!cpy) + return 0; // OOM + key1=key1->insert(cpy); + key2->increment_use_count(key1->use_count+1); + } + else + key1=key1->insert(key2); // Will destroy key2_root + key2=next; + continue; + } } } - /* - tmp.min >= key2.min && tmp.min <= key.max (overlapping ranges) - key2.min <= tmp.min <= key2.max - */ + /* + The ranges in tmp and key2 are overlapping: + + key2: [----------] + tmp: [*****-----*****] + + Corollary: tmp.min <= key2.max + */ if (eq_tree(tmp->next_key_part,key2->next_key_part)) { + // Merge overlapping ranges with equal next_key_part if (tmp->is_same(key2)) { - /* - Found exact match of key2 inside key1. + /* + Found exact match of key2 inside key1. Use the relevant range in key1. */ - tmp->merge_flags(key2); // Copy maybe flags - key2->increment_use_count(-1); // Free not used tree + tmp->merge_flags(key2); // Copy maybe flags + key2->increment_use_count(-1); // Free not used tree } else { - SEL_ARG *last=tmp; - SEL_ARG *first=tmp; - /* - Find the last range in tmp that overlaps key2 and has the same - condition on the rest of the keyparts. + SEL_ARG *last= tmp; + SEL_ARG *first= tmp; + + /* + Find the last range in key1 that overlaps key2 and + where all ranges first...last have the same next_key_part as + key2. + + key2: [****----------------------*******] + key1: [--] [----] [---] [-----] [xxxx] + ^ ^ ^ + first last different next_key_part + + Since key2 covers them, the ranges between first and last + are merged into one range by deleting first...last-1 from + the key1 tree. In the figure, this applies to first and the + two consecutive ranges. The range of last is then extended: + * last.min: Set to min(key2.min, first.min) + * last.max: If there is a last->next that overlaps key2 (i.e., + last->next has a different next_key_part): + Set adjacent to last->next.min + Otherwise: Set to max(key2.max, last.max) + + Result: + key2: [****----------------------*******] + [--] [----] [---] => deleted from key1 + key1: [**------------------------***][xxxx] + ^ ^ + tmp=last different next_key_part */ - while (last->next && last->next->cmp_min_to_max(key2) <= 0 && - eq_tree(last->next->next_key_part,key2->next_key_part)) - { + while (last->next && last->next->cmp_min_to_max(key2) <= 0 && + eq_tree(last->next->next_key_part,key2->next_key_part)) + { /* - We've found the last overlapping key1 range in last. - This means that the ranges between (and including) the - first overlapping range (tmp) and the last overlapping range - (last) are fully nested into the current range of key2 - and can safely be discarded. We just need the minimum endpoint - of the first overlapping range (tmp) so we can compare it with - the minimum endpoint of the enclosing key2 range. + last->next is covered by key2 and has same next_key_part. + last can be deleted */ - SEL_ARG *save=last; - last=last->next; - key1=key1->tree_delete(save); - } + SEL_ARG *save=last; + last=last->next; + key1=key1->tree_delete(save); + } + // Redirect tmp to last which will cover the entire range + tmp= last; + /* - The tmp range (the first overlapping range) could have been discarded - by the previous loop. We should re-direct tmp to the new united range - that's taking its place. + We need the minimum endpoint of first so we can compare it + with the minimum endpoint of the enclosing key2 range. */ - tmp= last; last->copy_min(first); bool full_range= last->copy_min(key2); if (!full_range) { if (last->next && key2->cmp_max_to_min(last->next) >= 0) { - last->max_value= last->next->min_value; - if (last->next->min_flag & NEAR_MIN) - last->max_flag&= ~NEAR_MAX; - else - last->max_flag|= NEAR_MAX; + /* + This is the case: + key2: [-------------] + key1: [***------] [xxxx] + ^ ^ + last different next_key_part + + Extend range of last up to last->next: + key2: [-------------] + key1: [***--------][xxxx] + */ + last->copy_min_to_max(last->next); } else + /* + This is the case: + key2: [--------*****] + key1: [***---------] [xxxx] + ^ ^ + last different next_key_part + + Extend range of last up to max(last.max, key2.max): + key2: [--------*****] + key1: [***----------**] [xxxx] + */ full_range= last->copy_max(key2); } - if (full_range) - { // Full range - key1->free_tree(); - for (; key2 ; key2=key2->next) - key2->increment_use_count(-1); // Free not used tree - if (key1->maybe_flag) - return new SEL_ARG(SEL_ARG::MAYBE_KEY); - return 0; - } + if (full_range) + { // Full range + key1->free_tree(); + for (; key2 ; key2=key2->next) + key2->increment_use_count(-1); // Free not used tree + if (key1->maybe_flag) + return new SEL_ARG(SEL_ARG::MAYBE_KEY); + return 0; + } } } if (cmp >= 0 && tmp->cmp_min_to_min(key2) < 0) - { // tmp.min <= x < key2.min + { + /* + This is the case ("cmp>=0" means that tmp.max >= key2.min): + key2: [----] + tmp: [------------*****] + + The ranges are overlapping but have not been merged because + next_key_part of tmp and key2 are different + + Result: + key2: [----] + key1: [--------][--*****] + ^ ^ + insert tmp + */ SEL_ARG *new_arg=tmp->clone_first(key2); if (!new_arg) - return 0; // OOM + return 0; // OOM if ((new_arg->next_key_part= key1->next_key_part)) - new_arg->increment_use_count(key1->use_count+1); + new_arg->increment_use_count(key1->use_count+1); tmp->copy_min_to_min(key2); key1=key1->insert(new_arg); - } + } // tmp.min >= key2.min due to this if() - // tmp.min >= key2.min && tmp.min <= key2.max - SEL_ARG key(*key2); // Get copy we can modify + /* + Now key2.min <= tmp.min <= key2.max: + key2: [---------] + tmp: [****---*****] + */ + SEL_ARG key2_cpy(*key2); // Get copy we can modify for (;;) { - if (tmp->cmp_min_to_min(&key) > 0) - { // key.min <= x < tmp.min - SEL_ARG *new_arg=key.clone_first(tmp); - if (!new_arg) - return 0; // OOM - if ((new_arg->next_key_part=key.next_key_part)) - new_arg->increment_use_count(key1->use_count+1); - key1=key1->insert(new_arg); - } - if ((cmp=tmp->cmp_max_to_max(&key)) <= 0) - { // tmp.min. <= x <= tmp.max - tmp->maybe_flag|= key.maybe_flag; - key.increment_use_count(key1->use_count+1); - tmp->next_key_part= key_or(param, tmp->next_key_part, key.next_key_part); - if (!cmp) // Key2 is ready - break; - key.copy_max_to_min(tmp); - if (!(tmp=tmp->next)) - { - SEL_ARG *tmp2= new SEL_ARG(key); - if (!tmp2) - return 0; // OOM - key1=key1->insert(tmp2); - key2=key2->next; - goto end; - } - if (tmp->cmp_min_to_max(&key) > 0) - { - SEL_ARG *tmp2= new SEL_ARG(key); - if (!tmp2) - return 0; // OOM - key1=key1->insert(tmp2); - break; - } + if (tmp->cmp_min_to_min(&key2_cpy) > 0) + { + /* + This is the case: + key2_cpy: [------------] + key1: [-*****] + ^ + tmp + + Result: + key2_cpy: [---] + key1: [-------][-*****] + ^ ^ + insert tmp + */ + SEL_ARG *new_arg=key2_cpy.clone_first(tmp); + if (!new_arg) + return 0; // OOM + if ((new_arg->next_key_part=key2_cpy.next_key_part)) + new_arg->increment_use_count(key1->use_count+1); + key1=key1->insert(new_arg); + key2_cpy.copy_min_to_min(tmp); + } + // Now key2_cpy.min == tmp.min + + if ((cmp= tmp->cmp_max_to_max(&key2_cpy)) <= 0) + { + /* + tmp.max <= key2_cpy.max: + key2_cpy: a) [-------] or b) [----] + tmp: [----] [----] + + Steps: + 1) Update next_key_part of tmp: OR it with key2_cpy->next_key_part. + 2) If case a: Insert range [tmp.max, key2_cpy.max] into key1 using + next_key_part of key2_cpy + + Result: + key1: a) [----][-] or b) [----] + */ + tmp->maybe_flag|= key2_cpy.maybe_flag; + key2_cpy.increment_use_count(key1->use_count+1); + tmp->next_key_part= key_or(param, tmp->next_key_part, + key2_cpy.next_key_part); + + if (!cmp) + break; // case b: done with this key2 range + + // Make key2_cpy the range [tmp.max, key2_cpy.max] + key2_cpy.copy_max_to_min(tmp); + if (!(tmp=tmp->next)) + { + /* + No more ranges in key1. Insert key2_cpy and go to "end" + label to insert remaining ranges in key2 if any. + */ + SEL_ARG *tmp2= new SEL_ARG(key2_cpy); + if (!tmp2) + return 0; // OOM + key1=key1->insert(tmp2); + key2=key2->next; + goto end; + } + if (tmp->cmp_min_to_max(&key2_cpy) > 0) + { + /* + The next range in key1 does not overlap with key2_cpy. + Insert this range into key1 and move on to the next range + in key2. + */ + SEL_ARG *tmp2= new SEL_ARG(key2_cpy); + if (!tmp2) + return 0; // OOM + key1=key1->insert(tmp2); + break; + } + /* + key2_cpy overlaps with the next range in key1 and the case + is now "key2.min <= tmp.min <= key2.max". Go back to for(;;) + to handle this situation. + */ + continue; } else { - SEL_ARG *new_arg=tmp->clone_last(&key); // tmp.min <= x <= key.max - if (!new_arg) - return 0; // OOM - tmp->copy_max_to_min(&key); - tmp->increment_use_count(key1->use_count+1); - /* Increment key count as it may be used for next loop */ - key.increment_use_count(1); - new_arg->next_key_part= key_or(param, tmp->next_key_part, key.next_key_part); - key1=key1->insert(new_arg); - break; + /* + This is the case: + key2_cpy: [-------] + tmp: [------------] + + Result: + key1: [-------][---] + ^ ^ + new_arg tmp + Steps: + 1) Make new_arg with range [tmp.min, key2_cpy.max]. + new_arg->next_key_part is OR between next_key_part + of tmp and key2_cpy + 2) Make tmp the range [key2.max, tmp.max] + 3) Insert new_arg into key1 + */ + SEL_ARG *new_arg=tmp->clone_last(&key2_cpy); + if (!new_arg) + return 0; // OOM + tmp->copy_max_to_min(&key2_cpy); + tmp->increment_use_count(key1->use_count+1); + /* Increment key count as it may be used for next loop */ + key2_cpy.increment_use_count(1); + new_arg->next_key_part= key_or(param, tmp->next_key_part, + key2_cpy.next_key_part); + key1=key1->insert(new_arg); + break; } } - key2=key2->next; + // Move on to next range in key2 + key2=key2->next; } end: + /* + Add key2 ranges that are non-overlapping with and higher than the + highest range in key1. + */ while (key2) { SEL_ARG *next=key2->next; if (key2_shared) { - SEL_ARG *tmp=new SEL_ARG(*key2); // Must make copy + SEL_ARG *tmp=new SEL_ARG(*key2); // Must make copy if (!tmp) - return 0; + return 0; key2->increment_use_count(key1->use_count+1); key1=key1->insert(tmp); } else - key1=key1->insert(key2); // Will destroy key2_root + key1=key1->insert(key2); // Will destroy key2_root key2=next; } key1->use_count++; + return key1; } --===============8640064105266782167== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/jorgen.loland@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: jorgen.loland@stripped\ # 5wickj6dvrh1dpj6 # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11765831/ # testament_sha1: 2e399f4464756c613a690ea52381354150293efb # timestamp: 2011-05-06 15:26:34 +0200 # source_branch: file:///export/home/jl208045/mysql/mysql-5.5/ # base_revision_id: alexander.nozdrin@stripped\ # 46459va9vcbd4nz0 # # Begin bundle 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 --===============8640064105266782167==--