diff --git a/mysql-test/main/win_orderby.result b/mysql-test/main/win_orderby.result index 31694f91a5c12..e007f290d11fe 100644 --- a/mysql-test/main/win_orderby.result +++ b/mysql-test/main/win_orderby.result @@ -222,5 +222,158 @@ id select_type table type possible_keys key key_len ref rows Extra NULL UNION RESULT ALL NULL NULL NULL NULL NULL drop table t; # +# MDEV-36356 Server crash in Item::save_int_in_field and UBSAN null-pointer-use +# +(SELECT 1 FROM dual LIMIT 5) ORDER BY ROW_NUMBER() OVER (PARTITION BY 1); +1 +1 +(SELECT 1 FROM dual LIMIT 5) ORDER BY ROW_NUMBER() OVER (); +1 +1 +(SELECT 5 a FROM dual LIMIT 1) ORDER BY AVG(a) OVER (ORDER BY 1); +a +5 +(SELECT CAST('1973' AS DATETIME) a FROM dual LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +a +NULL +Warnings: +Warning 1292 Incorrect datetime value: '1973' +(SELECT CAST('1973' AS CHAR) a FROM dual LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +a +1973 +SET @save_sql_mode= @@sql_mode; +SET sql_mode=''; +(SELECT CAST('' AS CHAR) a LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +a + +SET sql_mode= @save_sql_mode; +((SELECT 1) FOR UPDATE WAIT + 1) ORDER BY PERCENTILE_DISC(COUNT(*)) WITHIN GROUP(ORDER BY 1) OVER(); +1 +1 +((SELECT 1) FOR UPDATE WAIT + 1) ORDER BY PERCENTILE_DISC(COUNT(*)) WITHIN GROUP(ORDER BY CURRENT_TIMESTAMP) OVER(); +1 +1 +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (3),(1),(2); +CREATE TABLE t2 (a INT, b INT); +INSERT INTO t2 VALUES (3,1),(1,2),(2,1); +SELECT * FROM ((SELECT a FROM t1 UNION SELECT 9 LIMIT 5) ORDER BY ROW_NUMBER() OVER ()) x ORDER BY x.a; +a +1 +2 +3 +9 +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY (SELECT MAX(a) FROM t1))) x ORDER BY x.a; +a +1 +2 +3 +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER () + (SELECT MAX(a) FROM t1)) x ORDER BY x.a; +a +1 +2 +3 +# This query used to crash, now it correctly fails with a bad field reference error +(SELECT a FROM t1 LIMIT 5) ORDER BY (SELECT a FROM t2 WHERE t2.a = t1.a LIMIT 1); +ERROR 42S22: Unknown column 't1.a' in 'WHERE' +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER ()) x, ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a)) y ORDER BY x.a, y.a; +a a +1 1 +1 2 +1 3 +2 1 +2 2 +2 3 +3 1 +3 2 +3 3 +# The value of the window function must be the one of the derived table form +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (PARTITION BY a)) x ORDER BY x.a; +a +1 +2 +3 +SELECT * FROM (SELECT * FROM (SELECT a FROM t1 LIMIT 5) dt ORDER BY ROW_NUMBER() OVER (PARTITION BY a)) x ORDER BY x.a; +a +1 +2 +3 +# A query expression nested in the ORDER BY must not hide the window +# function that precedes it: all of these must sort descending +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 LIMIT 1) DESC; +a +3 +2 +1 +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 ORDER BY a LIMIT 1) DESC; +a +3 +2 +1 +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT MAX(d.a)*0 FROM (SELECT a FROM t1 ORDER BY a) d) DESC; +a +3 +2 +1 +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 ORDER BY a LIMIT 1) + RANK() OVER (ORDER BY a) DESC; +a +3 +2 +1 +# A window function referring to a window defined inside the parentheses +# is not visible from the ORDER BY that follows them +(SELECT a FROM t1 WINDOW w AS (ORDER BY a) LIMIT 5) ORDER BY ROW_NUMBER() OVER w; +ERROR HY000: Window specification with name 'w' is not defined +# A window function of a subquery of the ORDER BY stays in that subquery +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY (SELECT ROW_NUMBER() OVER () FROM t1 LIMIT 1)) x ORDER BY x.a; +a +1 +2 +3 +# Correlated subqueries of the ORDER BY are re-registered in the wrapper +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY EXISTS (SELECT 1 FROM t2 WHERE t2.a=a AND t2.b=a)) x ORDER BY x.a; +a +1 +2 +3 +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY a IN (SELECT b FROM t2 WHERE t2.a=a)) x ORDER BY x.a; +a +1 +2 +3 +DROP TABLE t1, t2; +CREATE TABLE t1 (c1 INT, c2 TEXT); +INSERT INTO t1 (c1, c2) VALUES (0, 'a'); +SELECT * FROM +{ +ta1 t1 AS ta2 NATURAL RIGHT OUTER JOIN +( +( +SELECT * FROM +{ +ta3 t1 AS ta4 NATURAL STRAIGHT_JOIN t1 AS ta5 +} +LIMIT 1234567890 ROWS EXAMINED 1234567890 LOCK IN SHARE MODE SKIP LOCKED +) +ORDER BY FALSE <=> + INTERVAL NOT FALSE=FALSE IN +( +SELECT FALSE <=> FALSE IN (SELECT 'string') +) +SECOND_MICROSECOND + TRUE <=> TRUE IN (SELECT 'string') << ROW_NUMBER () OVER +( +PARTITION BY NOT TRUE <=> FALSE IN (SELECT 'string') DESC +) +IN (SELECT 'string') +)=ta6 +NATURAL JOIN t1 AS ta7 +}; +c1 c2 +0 a +Warnings: +Warning 1292 Truncated incorrect DECIMAL value: 'string' +Warning 1292 Truncated incorrect DECIMAL value: 'string' +Warning 1292 Incorrect datetime value: '0000-00-00 00:00:00' +DROP TABLE t1; +# # End of 10.11 tests # diff --git a/mysql-test/main/win_orderby.test b/mysql-test/main/win_orderby.test index 2700271d47ed5..90b6c512982bf 100644 --- a/mysql-test/main/win_orderby.test +++ b/mysql-test/main/win_orderby.test @@ -175,6 +175,87 @@ explain update t set c=1 where exists ( from t order by x=avg(row_number() over t) over (order by 1)); drop table t; +--echo # +--echo # MDEV-36356 Server crash in Item::save_int_in_field and UBSAN null-pointer-use +--echo # +(SELECT 1 FROM dual LIMIT 5) ORDER BY ROW_NUMBER() OVER (PARTITION BY 1); +(SELECT 1 FROM dual LIMIT 5) ORDER BY ROW_NUMBER() OVER (); +(SELECT 5 a FROM dual LIMIT 1) ORDER BY AVG(a) OVER (ORDER BY 1); +(SELECT CAST('1973' AS DATETIME) a FROM dual LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +(SELECT CAST('1973' AS CHAR) a FROM dual LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +SET @save_sql_mode= @@sql_mode; +SET sql_mode=''; +(SELECT CAST('' AS CHAR) a LIMIT 1) ORDER BY FIRST_VALUE(a) OVER (ORDER BY 1); +SET sql_mode= @save_sql_mode; +((SELECT 1) FOR UPDATE WAIT + 1) ORDER BY PERCENTILE_DISC(COUNT(*)) WITHIN GROUP(ORDER BY 1) OVER(); +((SELECT 1) FOR UPDATE WAIT + 1) ORDER BY PERCENTILE_DISC(COUNT(*)) WITHIN GROUP(ORDER BY CURRENT_TIMESTAMP) OVER(); + +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (3),(1),(2); +CREATE TABLE t2 (a INT, b INT); +INSERT INTO t2 VALUES (3,1),(1,2),(2,1); + +SELECT * FROM ((SELECT a FROM t1 UNION SELECT 9 LIMIT 5) ORDER BY ROW_NUMBER() OVER ()) x ORDER BY x.a; +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY (SELECT MAX(a) FROM t1))) x ORDER BY x.a; +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER () + (SELECT MAX(a) FROM t1)) x ORDER BY x.a; + +--echo # This query used to crash, now it correctly fails with a bad field reference error +--error ER_BAD_FIELD_ERROR +(SELECT a FROM t1 LIMIT 5) ORDER BY (SELECT a FROM t2 WHERE t2.a = t1.a LIMIT 1); +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER ()) x, ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a)) y ORDER BY x.a, y.a; + +--echo # The value of the window function must be the one of the derived table form +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (PARTITION BY a)) x ORDER BY x.a; +SELECT * FROM (SELECT * FROM (SELECT a FROM t1 LIMIT 5) dt ORDER BY ROW_NUMBER() OVER (PARTITION BY a)) x ORDER BY x.a; + +--echo # A query expression nested in the ORDER BY must not hide the window +--echo # function that precedes it: all of these must sort descending +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 LIMIT 1) DESC; +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 ORDER BY a LIMIT 1) DESC; +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT MAX(d.a)*0 FROM (SELECT a FROM t1 ORDER BY a) d) DESC; +(SELECT a FROM t1 LIMIT 5) ORDER BY ROW_NUMBER() OVER (ORDER BY a) + (SELECT 0 FROM t1 ORDER BY a LIMIT 1) + RANK() OVER (ORDER BY a) DESC; + +--echo # A window function referring to a window defined inside the parentheses +--echo # is not visible from the ORDER BY that follows them +--error ER_WRONG_WINDOW_SPEC_NAME +(SELECT a FROM t1 WINDOW w AS (ORDER BY a) LIMIT 5) ORDER BY ROW_NUMBER() OVER w; + +--echo # A window function of a subquery of the ORDER BY stays in that subquery +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY (SELECT ROW_NUMBER() OVER () FROM t1 LIMIT 1)) x ORDER BY x.a; + +--echo # Correlated subqueries of the ORDER BY are re-registered in the wrapper +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY EXISTS (SELECT 1 FROM t2 WHERE t2.a=a AND t2.b=a)) x ORDER BY x.a; +SELECT * FROM ((SELECT a FROM t1 LIMIT 5) ORDER BY a IN (SELECT b FROM t2 WHERE t2.a=a)) x ORDER BY x.a; + +DROP TABLE t1, t2; + +CREATE TABLE t1 (c1 INT, c2 TEXT); +INSERT INTO t1 (c1, c2) VALUES (0, 'a'); +SELECT * FROM +{ + ta1 t1 AS ta2 NATURAL RIGHT OUTER JOIN + ( + ( + SELECT * FROM + { + ta3 t1 AS ta4 NATURAL STRAIGHT_JOIN t1 AS ta5 + } + LIMIT 1234567890 ROWS EXAMINED 1234567890 LOCK IN SHARE MODE SKIP LOCKED + ) + ORDER BY FALSE <=> + INTERVAL NOT FALSE=FALSE IN + ( + SELECT FALSE <=> FALSE IN (SELECT 'string') + ) + SECOND_MICROSECOND + TRUE <=> TRUE IN (SELECT 'string') << ROW_NUMBER () OVER + ( + PARTITION BY NOT TRUE <=> FALSE IN (SELECT 'string') DESC + ) + IN (SELECT 'string') + )=ta6 + NATURAL JOIN t1 AS ta7 +}; +DROP TABLE t1; + --echo # --echo # End of 10.11 tests --echo # diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index c5ef832284686..e6a985667ae14 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -10686,13 +10686,121 @@ SELECT_LEX_UNIT *LEX::add_tail_to_query_expression_body(SELECT_LEX_UNIT *unit, } +static bool order_list_contains(SQL_I_List *order_list, Item *item) +{ + for (ORDER *ord= order_list->first; ord; ord= ord->next) + { + if ((*ord->item)->walk(&Item::find_item_processor, FALSE, item)) + return true; + } + return false; +} + + +static bool remove_window_spec(SELECT_LEX *sel, Window_spec *win_spec) +{ + List_iterator it(sel->window_specs); + Window_spec *cur; + while ((cur= it++)) + { + if (cur == win_spec) + { + it.remove(); + return true; + } + } + return false; +} + + +/* + Move to another select what the tail of a query expression registered + + The tail (ORDER BY / LIMIT / locking clause) that follows a parenthesized + query expression is parsed while the select inside the parentheses is the + current one, so its window functions, their window specifications and its + subqueries all get registered there. When the query expression is wrapped + into a derived table the tail belongs to the wrapping select, and these + registrations have to follow it. + + The tail's window functions are the ones its ORDER BY expressions contain: + they are looked up with Item::walk() and walk_subquery == FALSE, so a window + function that belongs to a subquery of the tail is not found and stays + registered in the select it was parsed in. Each of them takes along the + window specification it introduced; "OVER win_name" has none of its own, it + is looked up by name at fix_fields() time and never reaches window_specs. + + The tail's subquery units are the ones registered in front of 'first_unit', + the first unit 'from' had when the tail started to be parsed. There is no + equivalent of the walk above for them: a unit records neither the clause it + came from nor its position in the parsed text. + + Note that per-select parse state that is not a registration is deliberately + left behind on 'from', where it is merely over-counted: n_sum_items, + with_sum_func, with_rownum, ftfunc_list, uncacheable and the + select_n_where_fields of the tail's own fields. +*/ +static bool move_tail_registrations(THD *thd, SELECT_LEX *from, SELECT_LEX *to, + SQL_I_List *order_list, + SELECT_LEX_UNIT *first_unit) +{ + if (order_list) + { + List_iterator it(from->window_funcs); + Item_window_func *win_func; + while ((win_func= it++)) + { + if (!order_list_contains(order_list, win_func)) + continue; + it.remove(); + if (to->window_funcs.push_back(win_func, thd->mem_root)) + return true; + to->fields_in_window_functions+= + win_func->window_func()->argument_count(); + + Window_spec *win_spec= win_func->window_spec; + if (!win_spec || !remove_window_spec(from, win_spec)) + continue; + if (to->window_specs.push_back(win_spec, thd->mem_root)) + return true; + to->fields_in_window_functions+= win_spec->partition_list->elements + + win_spec->order_list->elements; + /* + Item::walk() does not descend into a window specification, so the + items of these lists are not re-targeted by + Lex_order_limit_lock::set_to(). + */ + for (ORDER *o= win_spec->partition_list->first; o; o= o->next) + (*o->item)->walk(&Item::change_context_processor, FALSE, &to->context); + for (ORDER *o= win_spec->order_list->first; o; o= o->next) + (*o->item)->walk(&Item::change_context_processor, FALSE, &to->context); + } + } + + st_select_lex_unit *next_unit; + for (st_select_lex_unit *unit= from->first_inner_unit(); + unit != first_unit; unit= next_unit) + { + next_unit= unit->next_unit(); + unit->exclude_from_tree(); + unit->cut_next(); // so add_statistics() stops here + to->add_statistics(unit); + to->register_unit(unit, &to->context); + if (unit->item) + unit->item->parent_select= to; + } + return false; +} + + /** Add non-empty tail to a parenthesized query primary */ SELECT_LEX_UNIT * LEX::add_tail_to_query_expression_body_ext_parens(SELECT_LEX_UNIT *unit, - Lex_order_limit_lock *l) + Lex_order_limit_lock *l, + SELECT_LEX_UNIT *first_unit) { SELECT_LEX *sel= unit->first_select()->next_select() ? unit->fake_select_lex : unit->first_select(); @@ -10706,14 +10814,16 @@ LEX::add_tail_to_query_expression_body_ext_parens(SELECT_LEX_UNIT *unit, l->order_list= &sel->order_list; else { - if (!unit) - return NULL; + SELECT_LEX *inner_sel= sel; sel= wrap_unit_into_derived(unit); if (!sel) return NULL; - if (!create_unit(sel)) - return NULL; - } + if (!create_unit(sel)) + return NULL; + if (move_tail_registrations(thd, inner_sel, sel, l->order_list, + first_unit)) + return NULL; + } } l->set_to(sel); return sel->master_unit(); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 0eea5ae771245..b1c8e5dc0daed 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -4826,7 +4826,8 @@ struct LEX: public Query_tables_list Lex_order_limit_lock *l); SELECT_LEX_UNIT * add_tail_to_query_expression_body_ext_parens(SELECT_LEX_UNIT *unit, - Lex_order_limit_lock *l); + Lex_order_limit_lock *l, + SELECT_LEX_UNIT *first_unit); SELECT_LEX_UNIT *parsed_body_ext_parens_primary(SELECT_LEX_UNIT *unit, SELECT_LEX *primary, enum sub_select_type unit_type, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index eabd28ba35e63..d72ff9b028855 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8742,12 +8742,21 @@ query_expression_body_ext: } | query_expression_body_ext_parens { - Lex->push_select(!$1->first_select()->next_select() ? - $1->first_select() : $1->fake_select_lex); + SELECT_LEX *sel= !$1->first_select()->next_select() ? + $1->first_select() : $1->fake_select_lex; + /* + Remember the subqueries this select already has: the ones the + tail registers in front of them have to follow it when the + query expression is wrapped into a derived table. See + LEX::add_tail_to_query_expression_body_ext_parens(). + */ + $$= sel->first_inner_unit(); + Lex->push_select(sel); } query_expression_tail { - if (!($$= Lex->add_tail_to_query_expression_body_ext_parens($1, $3))) + if (!($$= Lex->add_tail_to_query_expression_body_ext_parens( + $1, $3, $2))) MYSQL_YYABORT; } ;