From: Martin Hansson Date: August 30 2010 9:54am Subject: bzr commit into mysql-5.1-bugteam branch (martin.hansson:3496) Bug#51070 List-Archive: http://lists.mysql.com/commits/117125 X-Bug: 51070 Message-Id: <20100830095447.0052A13FCFC@scott> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2307202892259561163==" --===============2307202892259561163== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///data0/martin/bzr/bug51070/5.1bt/ based on revid:ramil@stripped 3496 Martin Hansson 2010-08-30 Bug#51070: Query with a NOT IN subquery predicate returns a wrong result set The EXISTS transformation has additional swithces to catch most corner cases that appear when transforming an IN predicate into EXISTS. Guarded conditions are used which are deactivated when a NULL value is seen in the outer expression's row. When the inner query block supplies NULL values, however, they are filtered out because no distinction is made between the guarded conditions; guarded NOT x IS NULL conditions in the HAVING clause that filter out NULL values cannot be de-activated in isolation from those that match values or from the outer expression or NULL's. The above problem is handled by making the guarded conditions remember whether they have rejected a NULL value or not, and index access methods are taking this into account as well. The bug consisted of 1) Not resetting the property for every nested loop iteration on the inner query's result 2) Not propagating the NULL result properly from inner query to IN optimizer. 3) A hack that may or may not have been needed at some point. According to a comment it was aimed to fix #2 by returning NULL when FALSE was actually the result. This caused failures when #2 was properly fixed. The hack is now removed. modified: mysql-test/r/subselect4.result mysql-test/t/subselect4.test sql/item_cmpfunc.cc sql/item_subselect.cc === modified file 'mysql-test/r/subselect4.result' --- a/mysql-test/r/subselect4.result 2010-08-05 10:42:14 +0000 +++ b/mysql-test/r/subselect4.result 2010-08-30 09:54:44 +0000 @@ -77,6 +77,92 @@ Note 1249 Select 2 was reduced during op CREATE VIEW v1 AS SELECT 1 LIKE ( 1 IN ( SELECT 1 ) ); CREATE VIEW v2 AS SELECT 1 LIKE '%' ESCAPE ( 1 IN ( SELECT 1 ) ); DROP VIEW v1, v2; +# +# Bug#51070: Query with a NOT IN subquery predicate returns a wrong +# result set +# +CREATE TABLE t1 ( a INT, b INT ); +INSERT INTO t1 VALUES ( 1, NULL ), ( 2, NULL ); +CREATE TABLE t2 ( c INT, d INT ); +INSERT INTO t2 VALUES ( NULL, 3 ), ( NULL, 4 ); +CREATE TABLE t3 ( e INT, f INT ); +INSERT INTO t3 VALUES ( NULL, NULL ), ( NULL, NULL ); +CREATE TABLE t4 ( a INT ); +INSERT INTO t4 VALUES (1), (2), (3); +CREATE TABLE t5 ( a INT ); +INSERT INTO t5 VALUES (NULL), (2); +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ); +id select_type table type possible_keys key key_len ref rows Extra +x PRIMARY x x x x x x x x +x DEPENDENT SUBQUERY x x x x x x x x +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ); +a b +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS NULL; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS NULL; +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE ( a, b ) IN ( SELECT c, d FROM t2 ) IS NULL; +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS UNKNOWN; +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE (( a, b ) NOT IN ( SELECT c, d FROM t2 )) IS UNKNOWN; +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE 1 = 1 AND ( a, b ) NOT IN ( SELECT c, d FROM t2 ); +a b +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT e, f FROM t3 ); +id select_type table type possible_keys key key_len ref rows Extra +x PRIMARY x x x x x x x x +x DEPENDENT SUBQUERY x x x x x x x x +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT e, f FROM t3 ); +a b +EXPLAIN +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT a, b FROM t1 ); +id select_type table type possible_keys key key_len ref rows Extra +x PRIMARY x x x x x x x x +x DEPENDENT SUBQUERY x x x x x x x x +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT a, b FROM t1 ); +c d +EXPLAIN +SELECT * FROM t3 WHERE ( e, f ) NOT IN ( SELECT c, d FROM t2 ); +id select_type table type possible_keys key key_len ref rows Extra +x PRIMARY x x x x x x x x +x DEPENDENT SUBQUERY x x x x x x x x +SELECT * FROM t3 WHERE ( e, f ) NOT IN ( SELECT c, d FROM t2 ); +e f +EXPLAIN +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT e, f FROM t3 ); +id select_type table type possible_keys key key_len ref rows Extra +x PRIMARY x x x x x x x x +x DEPENDENT SUBQUERY x x x x x x x x +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT e, f FROM t3 ); +c d +SELECT * FROM t1 WHERE ( a, b ) NOT IN +( SELECT c, d FROM t2 WHERE c = 1 AND c <> 1 ); +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE b NOT IN ( SELECT c FROM t2 WHERE c = 1 ); +a b +1 NULL +2 NULL +SELECT * FROM t1 WHERE NULL NOT IN ( SELECT c FROM t2 WHERE c = 1 AND c <> 1 ); +a b +1 NULL +2 NULL +DROP TABLE t1, t2, t3, t4, t5; # # End of 5.1 tests. # === modified file 'mysql-test/t/subselect4.test' --- a/mysql-test/t/subselect4.test 2010-08-05 10:42:14 +0000 +++ b/mysql-test/t/subselect4.test 2010-08-30 09:54:44 +0000 @@ -74,6 +74,68 @@ CREATE VIEW v1 AS SELECT 1 LIKE ( 1 IN ( CREATE VIEW v2 AS SELECT 1 LIKE '%' ESCAPE ( 1 IN ( SELECT 1 ) ); DROP VIEW v1, v2; +--echo # +--echo # Bug#51070: Query with a NOT IN subquery predicate returns a wrong +--echo # result set +--echo # +CREATE TABLE t1 ( a INT, b INT ); +INSERT INTO t1 VALUES ( 1, NULL ), ( 2, NULL ); + +CREATE TABLE t2 ( c INT, d INT ); +INSERT INTO t2 VALUES ( NULL, 3 ), ( NULL, 4 ); + +CREATE TABLE t3 ( e INT, f INT ); +INSERT INTO t3 VALUES ( NULL, NULL ), ( NULL, NULL ); + +CREATE TABLE t4 ( a INT ); +INSERT INTO t4 VALUES (1), (2), (3); + +CREATE TABLE t5 ( a INT ); +INSERT INTO t5 VALUES (NULL), (2); + +--replace_column 1 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ); +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ); + +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS NULL; +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS NULL; +SELECT * FROM t1 WHERE ( a, b ) IN ( SELECT c, d FROM t2 ) IS NULL; +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT c, d FROM t2 ) IS UNKNOWN; +SELECT * FROM t1 WHERE (( a, b ) NOT IN ( SELECT c, d FROM t2 )) IS UNKNOWN; + +SELECT * FROM t1 WHERE 1 = 1 AND ( a, b ) NOT IN ( SELECT c, d FROM t2 ); + +--replace_column 1 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT e, f FROM t3 ); +SELECT * FROM t1 WHERE ( a, b ) NOT IN ( SELECT e, f FROM t3 ); + +--replace_column 1 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT a, b FROM t1 ); +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT a, b FROM t1 ); + +--replace_column 1 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t3 WHERE ( e, f ) NOT IN ( SELECT c, d FROM t2 ); +SELECT * FROM t3 WHERE ( e, f ) NOT IN ( SELECT c, d FROM t2 ); + +--replace_column 1 x 3 x 4 x 5 x 6 x 7 x 8 x 9 x 10 x +EXPLAIN +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT e, f FROM t3 ); +SELECT * FROM t2 WHERE ( c, d ) NOT IN ( SELECT e, f FROM t3 ); + +SELECT * FROM t1 WHERE ( a, b ) NOT IN + ( SELECT c, d FROM t2 WHERE c = 1 AND c <> 1 ); + +SELECT * FROM t1 WHERE b NOT IN ( SELECT c FROM t2 WHERE c = 1 ); + +SELECT * FROM t1 WHERE NULL NOT IN ( SELECT c FROM t2 WHERE c = 1 AND c <> 1 ); + +DROP TABLE t1, t2, t3, t4, t5; + --echo # --echo # End of 5.1 tests. === modified file 'sql/item_cmpfunc.cc' --- a/sql/item_cmpfunc.cc 2010-08-05 10:42:14 +0000 +++ b/sql/item_cmpfunc.cc 2010-08-30 09:54:44 +0000 @@ -1751,6 +1751,69 @@ bool Item_in_optimizer::fix_fields(THD * } +/** + The implementation of optimized [NOT] IN + predicates. The implementation works as follows. + + For the current value of the outer expression + + - If it contains only NULL values, the original (before rewrite by the + Item_in_subselect rewrite methods) inner subquery is non-correlated and + was previously executed, there is no need to re-execute it, and the + previous return value is returned. + + - If it contains NULL values, check if there is a partial match for the + inner query block by evaluating it. For clarity we repeat here the + transformation previously performed on the sub-query. The expression + + ( oc_1, ..., oc_n ) + + ( SELECT ic_1, ..., ic_n + FROM + WHERE + ) + + was transformed into + + ( oc_1, ..., oc_n ) + + ( SELECT ic_1, ..., ic_n + FROM
+ WHERE AND ... ( ic_k = oc_k OR ic IS NULL ) + HAVING ... NOT ic_k IS NULL + ) + + The evaluation will now proceed according to special rules set up + elsewhere. These rules include: + + - The HAVING NOT IS NULL conditions added by the + aforementioned rewrite methods will detect whether they evaluated (and + rejected) a NULL value and if so, will cause the subquery to evaluate + to NULL. See Item_in_subselect::val_bool(). + + - The added WHERE and HAVING conditions are present only for those inner + columns that correspond to outer column that are not NULL at the moment. + + - If there is an eligible index for executing the subquery, the special + access method "Full scan on NULL key" is employed which ensures that + the inner query will detect if there are NULL values resulting from the + inner query. This access method will quietly resort to table scan if it + needs to find NULL values as well. + + - Under these conditions, the sub-query need only be evaluated in order to + find out whether it produced any rows. + + - If it did, we know that there was a partial match since there are + NULL values in the outer row expression. + + - If it did not, the result is FALSE or UNKNOWN. If at least one of the + HAVING sub-predicates rejected a NULL value corresponding to an outer + non-NULL, and hence the inner query block returns UNKNONW upon + evaluation, there was a partial match and the result is UNKNOWN. + + - If it contains no NULL values, the call is forwarded to the inner query + block. + */ longlong Item_in_optimizer::val_int() { bool tmp; @@ -1804,7 +1867,7 @@ longlong Item_in_optimizer::val_int() all_left_cols_null= false; } - if (!((Item_in_subselect*)args[1])->is_correlated && + if (!item_subs->is_correlated && all_left_cols_null && result_for_null_param != UNKNOWN) { /* @@ -1818,8 +1881,11 @@ longlong Item_in_optimizer::val_int() else { /* The subquery has to be evaluated */ - (void) args[1]->val_bool_result(); - null_value= !item_subs->engine->no_rows(); + (void) item_subs->val_bool_result(); + if (item_subs->engine->no_rows()) + null_value= item_subs->null_value; + else + null_value= TRUE; if (all_left_cols_null) result_for_null_param= null_value; } === modified file 'sql/item_subselect.cc' --- a/sql/item_subselect.cc 2010-06-10 20:45:22 +0000 +++ b/sql/item_subselect.cc 2010-08-30 09:54:44 +0000 @@ -47,7 +47,7 @@ Item_subselect::Item_subselect(): item value is NULL if select_subselect not changed this value (i.e. some rows will be found returned) */ - null_value= 1; + null_value= TRUE; } @@ -427,9 +427,9 @@ void Item_maxmin_subselect::print(String void Item_singlerow_subselect::reset() { - null_value= 1; + null_value= TRUE; if (value) - value->null_value= 1; + value->null_value= TRUE; } @@ -574,7 +574,7 @@ double Item_singlerow_subselect::val_rea DBUG_ASSERT(fixed == 1); if (!exec() && !value->null_value) { - null_value= 0; + null_value= FALSE; return value->val_real(); } else @@ -589,7 +589,7 @@ longlong Item_singlerow_subselect::val_i DBUG_ASSERT(fixed == 1); if (!exec() && !value->null_value) { - null_value= 0; + null_value= FALSE; return value->val_int(); } else @@ -603,7 +603,7 @@ String *Item_singlerow_subselect::val_st { if (!exec() && !value->null_value) { - null_value= 0; + null_value= FALSE; return value->val_str(str); } else @@ -618,7 +618,7 @@ my_decimal *Item_singlerow_subselect::va { if (!exec() && !value->null_value) { - null_value= 0; + null_value= FALSE; return value->val_decimal(decimal_value); } else @@ -633,7 +633,7 @@ bool Item_singlerow_subselect::val_bool( { if (!exec() && !value->null_value) { - null_value= 0; + null_value= FALSE; return value->val_bool(); } else @@ -651,7 +651,7 @@ Item_exists_subselect::Item_exists_subse bool val_bool(); init(select_lex, new select_exists_subselect(this)); max_columns= UINT_MAX; - null_value= 0; //can't be NULL + null_value= FALSE; //can't be NULL maybe_null= 0; //can't be NULL value= 0; DBUG_VOID_RETURN; @@ -814,15 +814,14 @@ double Item_in_subselect::val_real() */ DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); - null_value= 0; + null_value= was_null= FALSE; if (exec()) { reset(); - null_value= 1; return 0; } if (was_null && !value) - null_value= 1; + null_value= TRUE; return (double) value; } @@ -835,15 +834,14 @@ longlong Item_in_subselect::val_int() */ DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); - null_value= 0; + null_value= was_null= FALSE; if (exec()) { reset(); - null_value= 1; return 0; } if (was_null && !value) - null_value= 1; + null_value= TRUE; return value; } @@ -856,16 +854,15 @@ String *Item_in_subselect::val_str(Strin */ DBUG_ASSERT(0); DBUG_ASSERT(fixed == 1); - null_value= 0; + null_value= was_null= FALSE; if (exec()) { reset(); - null_value= 1; return 0; } if (was_null && !value) { - null_value= 1; + null_value= TRUE; return 0; } str->set((ulonglong)value, &my_charset_bin); @@ -876,20 +873,14 @@ String *Item_in_subselect::val_str(Strin bool Item_in_subselect::val_bool() { DBUG_ASSERT(fixed == 1); - null_value= 0; + null_value= was_null= FALSE; if (exec()) { reset(); - /* - Must mark the IN predicate as NULL so as to make sure an enclosing NOT - predicate will return FALSE. See the comments in - subselect_uniquesubquery_engine::copy_ref_key for further details. - */ - null_value= 1; return 0; } if (was_null && !value) - null_value= 1; + null_value= TRUE; return value; } @@ -900,16 +891,15 @@ my_decimal *Item_in_subselect::val_decim method should not be used */ DBUG_ASSERT(0); - null_value= 0; + null_value= was_null= FALSE; DBUG_ASSERT(fixed == 1); if (exec()) { reset(); - null_value= 1; return 0; } if (was_null && !value) - null_value= 1; + null_value= TRUE; int2my_decimal(E_DEC_FATAL_ERROR, value, 0, decimal_value); return decimal_value; } --===============2307202892259561163== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/martin.hansson@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: martin.hansson@stripped\ # ievkbrjo3n5y4se2 # target_branch: file:///data0/martin/bzr/bug51070/5.1bt/ # testament_sha1: 7bd467b86fefeb8fc63b4887c087ee5d38840b15 # timestamp: 2010-08-30 11:54:46 +0200 # base_revision_id: ramil@stripped # # Begin bundle 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 --===============2307202892259561163==--