Bug #46743 | Azalea processing correlated, aggregate SELECT subqueries incorrectly | ||
---|---|---|---|
Submitted: | 15 Aug 2009 17:14 | Modified: | 23 Nov 2010 2:57 |
Reporter: | Patrick Crews | Email Updates: | |
Status: | Closed | Impact on me: | |
Category: | MySQL Server: Optimizer | Severity: | S3 (Non-critical) |
Version: | 5.4/6.0 | OS: | Any |
Assigned to: | Guilhem Bichot | CPU Architecture: | Any |
Tags: | aggregate, Correlated Subquery, GROUP BY, regression, SELECT subquery, subquery |
[15 Aug 2009 17:14]
Patrick Crews
[15 Aug 2009 17:15]
Patrick Crews
Full MTR test case with original and simplified queries
Attachment: bug46743_test.txt (text/plain), 17.41 KiB.
[14 Sep 2009 20:06]
Guilhem Bichot
Regarding the first problematic query: SELECT table2 .`int_key` field12 , ( SELECT SUM( `int_nokey` ) FROM D WHERE `int_key` = table1 .`int_key` ) FROM C table1 RIGHT JOIN C table2 ON table1 .`varchar_nokey` GROUP BY field12 ; The GROUP BY only selects one row per distinct value of field12; if this GROUP BY is removed results become correct (NULL instead of a number; like in 5.1). The same is true for the second problematic query: removing GROUP BY field1, field2, field3, field4, field5, field6, field7, field8, field9, field10, field11, field12, field13, field14 makes the result become correct.
[14 Sep 2009 20:07]
Guilhem Bichot
test which runs unchanged on 5.1 and mysql-next
Attachment: bug46743.test (application/octet-stream, text), 16.90 KiB.
[15 Sep 2009 15:11]
Guilhem Bichot
minimal testcase for the *first* query *only*
Attachment: bug46473_minimal_first_query_only.test (application/octet-stream, text), 1.87 KiB.
[15 Sep 2009 15:12]
Guilhem Bichot
result difference with the above test, between 5.1 and next-bugfixing: @@ -22,9 +22,10 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY table2 system NULL NULL NULL NULL 1 100.00 1 PRIMARY table1 system NULL NULL NULL NULL 1 100.00 -2 DEPENDENT SUBQUERY D ref int_key,varchar_key int_key 5 const 1 100.00 Using where +2 DEPENDENT SUBQUERY D ref int_key,varchar_key int_key 5 const 1 100.00 Warnings: Note 1276 Field or reference 'test.table1.int_key' of SELECT #2 was resolved in SELECT #1 +Warning 1292 Truncated incorrect INTEGER value: 'm' Note 1003 select '3' AS `field12`,(select sum(`test`.`D`.`int_nokey`) AS `SUM( ``int_nokey`` )` from `test`.`D` where (`test`.`D`.`int_key` = '3')) AS `( SELECT SUM( ``int_nokey`` ) FROM D @@ -32,6 +33,7 @@ SHOW WARNINGS; Level Code Message Note 1276 Field or reference 'test.table1.int_key' of SELECT #2 was resolved in SELECT #1 +Warning 1292 Truncated incorrect INTEGER value: 'm' Note 1003 select '3' AS `field12`,(select sum(`test`.`D`.`int_nokey`) AS `SUM( ``int_nokey`` )` from `test`.`D` where (`test`.`D`.`int_key` = '3')) AS `( SELECT SUM( ``int_nokey`` ) FROM D @@ -46,6 +48,8 @@ FROM D WHERE `int_key` = table1 .`int_key` ) 3 NULL +Warnings: +Warning 1292 Truncated incorrect INTEGER value: 'm' EXPLAIN EXTENDED SELECT table2 .`int_key` field12 , ( SELECT SUM( `int_nokey` ) FROM D @@ -55,9 +59,10 @@ id select_type table type possible_keys key key_len ref rows filtered Extra 1 PRIMARY table2 system NULL NULL NULL NULL 1 100.00 1 PRIMARY table1 system NULL NULL NULL NULL 1 100.00 -2 DEPENDENT SUBQUERY D ref int_key,varchar_key int_key 5 const 1 100.00 Using where +2 DEPENDENT SUBQUERY D ref int_key,varchar_key int_key 5 const 1 100.00 Warnings: Note 1276 Field or reference 'test.table1.int_key' of SELECT #2 was resolved in SELECT #1 +Warning 1292 Truncated incorrect INTEGER value: 'm' Note 1003 select '3' AS `field12`,(select sum(`test`.`D`.`int_nokey`) AS `SUM( ``int_nokey`` )` from `test`.`D` where (`test`.`D`.`int_key` = '3')) AS `( SELECT SUM( ``int_nokey`` ) FROM D @@ -72,4 +77,6 @@ SELECT SUM( `int_nokey` ) FROM D WHERE `int_key` = table1 .`int_key` ) -3 NULL +3 8 +Warnings: +Warning 1292 Truncated incorrect INTEGER value: 'm' In short, queries with GROUP BY return 3,8 instead of 3,NULL. 3,NULL is the correct output, see analysis of this at the end of the testcase.
[16 Sep 2009 18:07]
Guilhem Bichot
yet more minimal test case: CREATE TABLE C (c int); INSERT INTO C VALUES (NULL); CREATE TABLE D (d int , KEY (d)); # index is needed for bug INSERT INTO D VALUES (NULL),(NULL); # two rows needed for bug # we see that subquery returns 0 rows SELECT 1 AS RESULT FROM D,C WHERE d = c; # so here it ends up as NULL SELECT (SELECT 1 FROM D WHERE d = c) AS RESULT FROM C ; # We add GROUP BY and it # fails with 1242: Subquery returns more than 1 row SELECT (SELECT 1 FROM D WHERE d = c) AS RESULT FROM C GROUP BY c ; In next-bugfixing we get error 1242, but in 5.1 we get NULL which is correct. In next-bugfixing, the subquery indeed find 2 rows instead of 0, it's because it loses its WHERE, as can be seen both from the EXPLAIN ("Using where" went away, see some previous post) and from the --debug output in make_join_select(): next-bugfixing: WHERE:(after substitute_best_equal) (`test`.`D`.`d` = '0') T@3 : | | | | | | | | | | | >make_join_select T@3 : | | | | | | | | | | | | >add_not_null_conds T@3 : | | | | | | | | | | | | <add_not_null_conds no-WHERE-anymore-here 5.1: same but after "<add_not_null_conds" there is WHERE:(D) (`test`.`D`.`d` = '0').
[17 Sep 2009 16:23]
Guilhem Bichot
6.0.4 passes the very minimal test, 6.0.5 and all subsequent 6.0 versions fail it. Regression introduced in 6.0.5.
[21 Sep 2009 12:54]
Guilhem Bichot
Several facts: (A) bug goes way if I revert (in table.cc and sql_select.cc) the removal of HA_NULL_PART done by sp1r-sergefp@mysql.com-20070309210824-13110. But I didn't decide that it was a correct solution, because if I create a tree of MySQL 6.0 code at the revision which introduced this change (bzr branch -r revid:sp1r-sergefp@mysql.com-20070309210824-13110), the test passes; which means that at that moment, removing HA_NULL_PART didn't introduce my bug. So the bug must have happened as an effect of "something later". (B) bzrfind says that the test started failing at sp1r-gkodinov/kgeorge@magare.gmz-20071121140553-31481 ; it was caused by the fix for BUG#30788 (merged into 6.0 by this revision). (B1) if I take the 6.0 tree as it was right before the merge, the testcase posted in BUG#30788 already passed (this is also seen in Sveta's comment "All versions are affected except 5.2" in BUG#30788), so this suggests that the fix shouldn't have been merged. (B2) if I take the 6.0 tree as it was right after the merge, and manually revert the patch for BUG#30788 (item.h and sql_select.cc), this solves BUG#46743 and does not reintroduce BUG#30788 (the testcase posted in BUG#30788 still passes), also suggests that the fix shouldn't have been merged. (B3) BUT if I take the 6.0 tree as it is today (latest mysql-next-bugfixing, or its new name mysql-6.0-codebase-bugfixing), and manually revert the patch for BUG#30788, this does NOT solve BUG#46743 and it even INTRODUCES BUG#30788. I'm attaching the 'revert' patch below. (B4) So something changed in the 6.0 optimizer code between Nov 2007 and now, which must explain that the patch for BUG#30788 has different effects in Nov 2007 and now (in Nov 2007 it did some bad to 6.0, now it does some good). Patch to revert HA_NULL_PART: === modified file 'sql/sql_select.cc' --- sql/sql_select.cc 2009-09-08 09:51:31 +0000 +++ sql/sql_select.cc 2009-09-16 19:48:40 +0000 @@ -18063,7 +18063,7 @@ for (part=0 ; part < ref_parts ; part++,key_part++) if (field->eq(key_part->field) && - !(key_part->key_part_flag & HA_PART_KEY_SEG)) + !(key_part->key_part_flag & (HA_PART_KEY_SEG | HA_NULL_PART))) return table->reginfo.join_tab->ref.items[part]; } return (Item*) 0; === modified file 'sql/table.cc' --- sql/table.cc 2009-08-31 20:25:11 +0000 +++ sql/table.cc 2009-09-16 19:43:53 +0000 @@ -1612,11 +1612,21 @@ share->table_name.str, share->table_name.str); share->crashed= 1; // Marker for CHECK TABLE - continue; + goto to_be_deleted; } #endif key_part->key_part_flag|= HA_PART_KEY_SEG; } + + to_be_deleted: + + /* + If the field can be NULL, don't optimize away the test + key_part_column = expression from the WHERE clause + as we need to test for NULL = NULL. + */ + if (field->real_maybe_null()) + key_part->key_part_flag|= HA_NULL_PART; } keyinfo->usable_key_parts= usable_parts; // Filesort Patch to revert fix for BUG#30788: === modified file 'sql/item.h' --- sql/item.h 2009-08-31 20:25:11 +0000 +++ sql/item.h 2009-09-21 08:05:05 +0000 @@ -3037,6 +3037,7 @@ table_map used_tables() const { return used_table_map; } virtual void keep_array() {} virtual void print(String *str, enum_query_type query_type); +#if 0 bool eq_def(Field *field) { return cached_field ? cached_field->eq_def (field) : FALSE; @@ -3045,6 +3046,7 @@ { return this == item; } +#endif }; === modified file 'sql/sql_select.cc' --- sql/sql_select.cc 2009-09-08 09:51:31 +0000 +++ sql/sql_select.cc 2009-09-21 08:07:07 +0000 @@ -992,13 +992,26 @@ ((Item_func *)this->conds)->functype() == Item_func::EQ_FUNC && ((Item_func *)conds)->arguments()[0]->type() == Item::REF_ITEM && ((Item_func *)conds)->arguments()[1]->type() == Item::FIELD_ITEM && +#if 0 test_if_ref (this->conds, (Item_field *)((Item_func *)conds)->arguments()[1], ((Item_func *)conds)->arguments()[0])) +#else + 1) +#endif { *where= 0; return; } +#if 0 +#else + if (conds->type() == Item::COND_ITEM && + ((class Item_func *)this->conds)->functype() == + Item_func::COND_AND_FUNC) + { + *where= remove_additional_cond(conds); + } +#endif } @@ -1941,7 +1954,12 @@ { if (!having) { - Item *where= conds; + Item *where= +#if 0 + conds; +#else + 0; +#endif if (join_tab[0].type == JT_EQ_REF && join_tab[0].ref.items[0]->name == in_left_expr_name) { @@ -17689,12 +17707,16 @@ Item *ref_item=part_of_refkey(field->table,field); if (ref_item && ref_item->eq(right_item,1)) { +#if 0 right_item= right_item->real_item(); +#endif if (right_item->type() == Item::FIELD_ITEM) return (field->eq_def(((Item_field *) right_item)->field)); /* remove equalities injected by IN->EXISTS transformation */ +#if 0 else if (right_item->type() == Item::CACHE_ITEM) return ((Item_cache *)right_item)->eq_def (field); +#endif if (right_item->const_item() && !(right_item->is_null())) { /*
[23 Sep 2009 20:15]
Guilhem Bichot
# fails with 1242: Subquery returns more than 1 row: SELECT (SELECT 1 FROM D WHERE d = c) AS RESULT FROM C GROUP BY c ; #passes: SELECT (SELECT 1 FROM D WHERE d = ifnull(c,null)) AS RESULT FROM C GROUP BY c ; (though ifnull(c,null) is always =c).
[28 Sep 2009 9:37]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/84809 2841 Guilhem Bichot 2009-09-28 Fix for BUG#46743 "Azalea processing correlated, aggregate SELECT subqueries incorrectly". Symptom: "=x" condition matched rows when x is NULL, which should never be. Summary of the problem: likely a failure of http://forge.mysql.com/wiki/MySQL_Internals_Optimizer#Late_NULLs_Filtering which didn't eliminate NULL values in some cases. Details below. Consider tables created and filled as in the minimal testcase of subselect4.test in this revision. Consider three queries: 1) SELECT (SELECT 1 FROM D WHERE d = c) FROM C ; 2) SELECT (SELECT 1 FROM D WHERE d = c) FROM C GROUP BY c ; 3) SELECT (SELECT 1 FROM D WHERE d = IFNULL(c,NULL)) FROM C ; C has one row, C.c is NULL, so all 3 queries are equivalent and should return NULL (because the subquery gives an empty result): but the second query returns "error 1242: Subquery returns more than 1 row", which is unexpected. It's all about what happens inside the subquery. Let's compare how the subquery is optimized and executed in each of those three queries. For 1) optimizer understands that d=c should eliminate NULL D.d values: it sets "null_rejecting" to "true" in add_key_field(). It also sees that the condition d=c will be a byproduct of using the "ref" index lookup method so does not need to be explicitely evaluated: this condition (COND object) is thus removed, this way: make_join_select() calls "tmp= make_cond_for_table()" which calls make_cond_for_table_from_pred(); around "EQ_FUNC" in that function, a zero COND is returned, so "tmp" is 0, thus we come to the "else tab->select_cond= sel->cond= NULL" branch. When the subquery is executed, join_read_always_key() does not even read keys of D because as C.c is NULL, null_rejecting shortcuts. For 3) because IFNULL is a function, optimizer does not understand that d = IFNULL(c,NULL)) should eliminate null D.d values: null_rejecting is set to false. The condition is not removed, also because IFNULL is a function (both equality members have too different types). join_read_always_key() reads the index, searching for NULL keys, finds two of them, but they are eliminated by the kept condition. For 2) optimizer does not understand that d=c should eliminate NULL D.d values: null_rejecting is false. But the condition is eliminated. So join_read_always_key() finds two NULLs and returns them (no condition, and null_rejecting=false): error. How did this happen: * setting null_rejecting to true requires that both members of d=c are Item_field. But as there is GROUP BY, "c" in d=c is an Item_outer_ref (built in Item_field::fix_outer_field() around "If an outer field is resolved in a grouping select etc"), not an Item_field, so null_rejecting is false. * make_cond_for_table() eliminates the COND condition because in d=c, test_if_ref() looks for __real__ items: it sees that inside Item_outer_ref "c", there is an Item_field, so it sees two Item_field on the sides of the equality and thus sees that "ref" will verify the condition naturally (see test_if_ref() calling eq_def()). Overall it looks like there is some assymetry between test_if_ref() (compares real_item()) and add_key_field() (compares plain item), the former catching a broader set of conditions (thus in some cases the condition is eliminated (COND removal) by the former but not "restored" (in the form of null_rejecting) by the latter). This assymetry was introduced by placing "right_item= right_item->real_item();" in test_in_ref() by sp1r-gkodinov/kgeorge@magare.gmz-20071121140553-31481 (as a fix for BUG 30788; revert this line and the present bug goes away). Symmetry is put back by using real_item() in add_key_field() too, which the present patch does. It looks logical, because what matters in d=c is whether "c" comes from a field, it's ok if "c" goes through some intermediate steps like GROUP BY (which makes Item_outer_ref). @ mysql-test/r/subselect4.result result (before the fix, there would be an error in the first "GROUP BY" SELECT, and numbers instead of NULLs in the following SELECTs; each time because some rows would be said to match "=NULL"). @ mysql-test/t/subselect4.test test for BUG#46743 @ sql/sql_select.cc * making use of JOIN_TAB::set_select_cond() to track changes of JOIN_TAB::select_cond when debugging * stepping into get_best_combination() made easier (see commit comment of mi_scan.c) * the bugfix: when computing null_rejecting, use real_item() and not the base item, so that we can discover an Item_field inside an Item_outer_ref. I use if (a) null_rejecting= true; else null_rejecting=false; so that the wrong path ("=false") shows up in QA's differential coverage tool which spots lines executed only in failing tests (it's on the intranet, contact me for more details). * the assertion in add_not_null_conds() must then be updated, because it can now meet something else than FIELD_ITEM and must look at the real item. Note that add_not_null_conds() is executed in our subquery, but quits the iteration at the "continue;". * some DBUG printouts in evaluate_join_record() to track if it eliminates a record @ sql/sql_select.h I had to detect when JOIN_TAB::select_cond changed from non-NULL to NULL (COND elimination) so added this inline method to always have a DBUG printout when this condition is set. If reviewers prefer, I can remove it and add DBUG_PRINT after every "join_tab->select_cond=" line instead. @ storage/myisam/mi_scan.c Stepping into info->s->read_rnd() is made difficult by wrapping it directly in DBUG_RETURN (made me step into DBUG_RETURN() dbug internal code and not manage to step into read_rnd()).
[2 Oct 2009 13:32]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/85533 3638 Guilhem Bichot 2009-10-02 Fix for BUG#46743 "Azalea processing correlated, aggregate SELECT subqueries incorrectly". Symptom: "=x" condition matched rows when x is NULL, which should never be. Summary of the problem: likely a failure of http://forge.mysql.com/wiki/MySQL_Internals_Optimizer#Late_NULLs_Filtering which didn't eliminate NULL values in some cases. Details below. Consider tables created and filled as in the minimal testcase of subselect4.test in this revision. Consider three queries: 1) SELECT (SELECT 1 FROM D WHERE d = c) FROM C ; 2) SELECT (SELECT 1 FROM D WHERE d = c) FROM C GROUP BY c ; 3) SELECT (SELECT 1 FROM D WHERE d = IFNULL(c,NULL)) FROM C ; C has one row, C.c is NULL, so all 3 queries are equivalent and should return NULL (because the subquery gives an empty result): but the second query returns "error 1242: Subquery returns more than 1 row", which is unexpected. It's all about what happens inside the subquery. Let's compare how the subquery is optimized and executed in each of those three queries. For 1) optimizer understands that d=c should eliminate NULL D.d values: it sets "null_rejecting" to "true" in add_key_field(). It also sees that the condition d=c will be a byproduct of using the "ref" index lookup method so does not need to be explicitely evaluated: this condition (COND object) is thus removed, this way: make_join_select() calls "tmp= make_cond_for_table()" which calls make_cond_for_table_from_pred(); around "EQ_FUNC" in that function, a zero COND is returned, so "tmp" is 0, thus we come to the "else tab->select_cond= sel->cond= NULL" branch. When the subquery is executed, join_read_always_key() does not even read keys of D because as C.c is NULL, null_rejecting shortcuts. For 3) because IFNULL is a function, optimizer does not understand that d = IFNULL(c,NULL)) should eliminate null D.d values: null_rejecting is set to false. The condition is not removed, also because IFNULL is a function (both equality members have too different types). join_read_always_key() reads the index, searching for NULL keys, finds two of them, but they are eliminated by the kept condition. For 2) optimizer does not understand that d=c should eliminate NULL D.d values: null_rejecting is false. But the condition is eliminated. So join_read_always_key() finds two NULLs and returns them (no condition, and null_rejecting=false): error. How did this happen: * setting null_rejecting to true requires that both members of d=c are Item_field. But as there is GROUP BY, "c" in d=c is an Item_outer_ref (built in Item_field::fix_outer_field() around "If an outer field is resolved in a grouping select etc"), not an Item_field, so null_rejecting is false. * make_cond_for_table() eliminates the COND condition because in d=c, test_if_ref() looks for __real__ items: it sees that inside Item_outer_ref "c", there is an Item_field, so it sees two Item_field on the sides of the equality and thus sees that "ref" will verify the condition naturally (see test_if_ref() calling eq_def()). Overall it looks like there is some assymetry between test_if_ref() (compares real_item()) and add_key_field() (compares plain item), the former catching a broader set of conditions (thus in some cases the condition is eliminated (COND removal) by the former but not "restored" (in the form of null_rejecting) by the latter). This assymetry was introduced by placing "right_item= right_item->real_item();" in test_in_ref() by sp1r-gkodinov/kgeorge@magare.gmz-20071121140553-31481 (as a fix for BUG 30788; revert this line and the present bug goes away). Symmetry is put back by using real_item() in add_key_field() too, which the present patch does. It looks logical, because what matters in d=c is whether "c" comes from a field, it's ok if "c" goes through some intermediate steps like GROUP BY (which makes Item_outer_ref). @ mysql-test/r/subselect4.result result (before the fix, there would be an error in the "GROUP BY" SELECT not using IFNULL, because some rows would be said to match "=NULL"). @ mysql-test/t/subselect4.test test for BUG#46743 @ sql/sql_select.cc * the bugfix: when computing null_rejecting, use real_item() and not the base item, so that we can discover an Item_field inside an Item_outer_ref. I use if (a) null_rejecting= true; else null_rejecting=false; so that the wrong path ("=false") shows up in QA's differential coverage tool which spots lines executed only in failing tests (it's on the intranet, contact me for more details). * the assertion in add_not_null_conds() must then be updated, because it can now meet something else than FIELD_ITEM and must look at the real item. Note that add_not_null_conds() is executed in our subquery, but quits the iteration at the "continue;".
[2 Oct 2009 13:58]
Jørgen Løland
Good to push
[2 Oct 2009 14:47]
Guilhem Bichot
queued to 6.0-codebase-bugfixing. Only the bugfixing piece. The debugging changes will be queued in a separate patch soon.
[2 Oct 2009 14:56]
Guilhem Bichot
debugging changes have been queued too.
[9 Oct 2009 8:47]
Bugs System
Pushed into 6.0.14-alpha (revid:alik@ibmvm-20091009083208-0o0f0i9w1sq3c1kn) (version source revid:joerg@mysql.com-20091002170530-6ux072g5680avloo) (merge vers: 6.0.14-alpha) (pib:12)
[19 Oct 2009 14:39]
Paul DuBois
Noted in 6.0.14 changelog. In a subquery, a condition of the form subquery_column = x (where x came from an outer table) matched even when x contained NULL. The result was that extra rows were incorrectly returned.
[14 May 2010 11:12]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/108309 3162 Guilhem Bichot 2010-05-10 Fix for BUG#46743 "Azalea processing correlated, aggregate SELECT subqueries incorrectly". (Backporting of guilhem@mysql.com-20091002133112-69ychheei22nubqj ). See sql_select.cc for details. @ mysql-test/r/subselect4.result result (before the fix, there would be an error in the "GROUP BY" SELECT not using IFNULL, because some rows would be said to match "=NULL"). @ mysql-test/t/subselect4.test test for BUG#46743 @ sql/sql_select.cc Symptom: "=x" condition matched rows when x is NULL, which should never be. Summary of the problem: likely a failure of http://forge.mysql.com/wiki/MySQL_Internals_Optimizer#Late_NULLs_Filtering which didn't eliminate NULL values in some cases. Details below. Consider tables created and filled as in the minimal testcase of subselect4.test in this revision. Consider three queries: 1) SELECT (SELECT 1 FROM D WHERE d = c) FROM C ; 2) SELECT (SELECT 1 FROM D WHERE d = c) FROM C GROUP BY c ; 3) SELECT (SELECT 1 FROM D WHERE d = IFNULL(c,NULL)) FROM C ; C has one row, C.c is NULL, so all 3 queries are equivalent and should return NULL (because the subquery gives an empty result): but the second query returns "error 1242: Subquery returns more than 1 row", which is unexpected. It's all about what happens inside the subquery. Let's compare how the subquery is optimized and executed in each of those three queries. For 1) optimizer understands that d=c should eliminate NULL D.d values: it sets "null_rejecting" to "true" in add_key_field(). It also sees that the condition d=c will be a byproduct of using the "ref" index lookup method so does not need to be explicitely evaluated: this condition (COND object) is thus removed, this way: make_join_select() calls "tmp= make_cond_for_table()" which calls make_cond_for_table_from_pred(); around "EQ_FUNC" in that function, a zero COND is returned, so "tmp" is 0, thus we come to the "else tab->select_cond= sel->cond= NULL" branch. When the subquery is executed, join_read_always_key() does not even read keys of D because as C.c is NULL, null_rejecting shortcuts. For 3) because IFNULL is a function, optimizer does not understand that d = IFNULL(c,NULL)) should eliminate null D.d values: null_rejecting is set to false. The condition is not removed, also because IFNULL is a function (both equality members have too different types). join_read_always_key() reads the index, searching for NULL keys, finds two of them, but they are eliminated by the kept condition. For 2) optimizer does not understand that d=c should eliminate NULL D.d values: null_rejecting is false. But the condition is eliminated. So join_read_always_key() finds two NULLs and returns them (no condition, and null_rejecting=false): error. How did this happen: * setting null_rejecting to true requires that both members of d=c are Item_field. But as there is GROUP BY, "c" in d=c is an Item_outer_ref (built in Item_field::fix_outer_field() around "If an outer field is resolved in a grouping select etc"), not an Item_field, so null_rejecting is false. * make_cond_for_table() eliminates the COND condition because in d=c, test_if_ref() looks for __real__ items: it sees that inside Item_outer_ref "c", there is an Item_field, so it sees two Item_field on the sides of the equality and thus sees that "ref" will verify the condition naturally (see test_if_ref() calling eq_def()). Overall it looks like there is some assymetry between test_if_ref() (compares real_item()) and add_key_field() (compares plain item), the former catching a broader set of conditions (thus in some cases the condition is eliminated (COND removal) by the former but not "restored" (in the form of null_rejecting) by the latter). This assymetry was introduced by placing "right_item= right_item->real_item();" in test_in_ref() by sp1r-gkodinov/kgeorge@magare.gmz-20071121140553-31481 (as a fix for BUG 30788; revert this line and the present bug goes away). Symmetry is put back by using real_item() in add_key_field() too, which the present patch does. It looks logical, because what matters in d=c is whether "c" comes from a field, it's ok if "c" goes through some intermediate steps like GROUP BY (which makes Item_outer_ref). * the bugfix: when computing null_rejecting, use real_item() and not the base item, so that we can discover an Item_field inside an Item_outer_ref. I use if (a) null_rejecting= true; else null_rejecting=false; so that the wrong path ("=false") shows up in QA's differential coverage tool which spots lines executed only in failing tests (it's on the intranet, contact me for more details). * the assertion in add_not_null_conds() must then be updated, because it can now meet something else than FIELD_ITEM and must look at the real item. Note that add_not_null_conds() is executed in our subquery, but quits the iteration at the "continue;".
[14 May 2010 14:48]
Bugs System
A patch for this bug has been committed. After review, it may be pushed to the relevant source trees for release in the next version. You can access the patch from: http://lists.mysql.com/commits/108358 3163 Guilhem Bichot 2010-05-10 Fix for BUG#46743 "Azalea processing correlated, aggregate SELECT subqueries incorrectly". (Backporting of guilhem@mysql.com-20091002133112-69ychheei22nubqj ). See sql_select.cc for details. @ mysql-test/r/subselect4.result result (before the fix, there would be an error in the "GROUP BY" SELECT not using IFNULL, because some rows would be said to match "=NULL"). @ mysql-test/t/subselect4.test test for BUG#46743 @ sql/sql_select.cc Symptom: "=x" condition matched rows when x is NULL, which should never be. Summary of the problem: likely a failure of http://forge.mysql.com/wiki/MySQL_Internals_Optimizer#Late_NULLs_Filtering which didn't eliminate NULL values in some cases. Details below. Consider tables created and filled as in the minimal testcase of subselect4.test in this revision. Consider three queries: 1) SELECT (SELECT 1 FROM D WHERE d = c) FROM C ; 2) SELECT (SELECT 1 FROM D WHERE d = c) FROM C GROUP BY c ; 3) SELECT (SELECT 1 FROM D WHERE d = IFNULL(c,NULL)) FROM C ; C has one row, C.c is NULL, so all 3 queries are equivalent and should return NULL (because the subquery gives an empty result): but the second query returns "error 1242: Subquery returns more than 1 row", which is unexpected. It's all about what happens inside the subquery. Let's compare how the subquery is optimized and executed in each of those three queries. For 1) optimizer understands that d=c should eliminate NULL D.d values: it sets "null_rejecting" to "true" in add_key_field(). It also sees that the condition d=c will be a byproduct of using the "ref" index lookup method so does not need to be explicitely evaluated: this condition (COND object) is thus removed, this way: make_join_select() calls "tmp= make_cond_for_table()" which calls make_cond_for_table_from_pred(); around "EQ_FUNC" in that function, a zero COND is returned, so "tmp" is 0, thus we come to the "else tab->select_cond= sel->cond= NULL" branch. When the subquery is executed, join_read_always_key() does not even read keys of D because as C.c is NULL, null_rejecting shortcuts. For 3) because IFNULL is a function, optimizer does not understand that d = IFNULL(c,NULL)) should eliminate null D.d values: null_rejecting is set to false. The condition is not removed, also because IFNULL is a function (both equality members have too different types). join_read_always_key() reads the index, searching for NULL keys, finds two of them, but they are eliminated by the kept condition. For 2) optimizer does not understand that d=c should eliminate NULL D.d values: null_rejecting is false. But the condition is eliminated. So join_read_always_key() finds two NULLs and returns them (no condition, and null_rejecting=false): error. How did this happen: * setting null_rejecting to true requires that both members of d=c are Item_field. But as there is GROUP BY, "c" in d=c is an Item_outer_ref (built in Item_field::fix_outer_field() around "If an outer field is resolved in a grouping select etc"), not an Item_field, so null_rejecting is false. * make_cond_for_table() eliminates the COND condition because in d=c, test_if_ref() looks for __real__ items: it sees that inside Item_outer_ref "c", there is an Item_field, so it sees two Item_field on the sides of the equality and thus sees that "ref" will verify the condition naturally (see test_if_ref() calling eq_def()). Overall it looks like there is some assymetry between test_if_ref() (compares real_item()) and add_key_field() (compares plain item), the former catching a broader set of conditions (thus in some cases the condition is eliminated (COND removal) by the former but not "restored" (in the form of null_rejecting) by the latter). This assymetry was introduced by placing "right_item= right_item->real_item();" in test_in_ref() by sp1r-gkodinov/kgeorge@magare.gmz-20071121140553-31481 (as a fix for BUG 30788; revert this line and the present bug goes away). Symmetry is put back by using real_item() in add_key_field() too, which the present patch does. It looks logical, because what matters in d=c is whether "c" comes from a field, it's ok if "c" goes through some intermediate steps like GROUP BY (which makes Item_outer_ref). * the bugfix: when computing null_rejecting, use real_item() and not the base item, so that we can discover an Item_field inside an Item_outer_ref. I use if (a) null_rejecting= true; else null_rejecting=false; so that the wrong path ("=false") shows up in QA's differential coverage tool which spots lines executed only in failing tests (it's on the intranet, contact me for more details). * the assertion in add_not_null_conds() must then be updated, because it can now meet something else than FIELD_ITEM and must look at the real item. Note that add_not_null_conds() is executed in our subquery, but quits the iteration at the "continue;".
[16 Aug 2010 6:34]
Bugs System
Pushed into mysql-next-mr (revid:alik@sun.com-20100816062819-bluwgdq8q4xysmlg) (version source revid:alik@sun.com-20100816062612-enatdwnv809iw3s9) (pib:20)
[13 Nov 2010 16:23]
Bugs System
Pushed into mysql-trunk 5.6.99-m5 (revid:alexander.nozdrin@oracle.com-20101113155825-czmva9kg4n31anmu) (version source revid:vasil.dimov@oracle.com-20100629074804-359l9m9gniauxr94) (merge vers: 5.6.99-m4) (pib:21)
[23 Nov 2010 2:57]
Paul DuBois
Bug does not appear in any released 5.6.x version. No 5.6.1 changelog entry needed.