1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-36356 Server crash in Item::save_int_in_field with Window functions

A 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 everything the tail registers - its window
functions, the window specifications they introduce and the units of
its subqueries - ends up registered in that inner select.

When the parenthesized query expression already has a tail of its own,
LEX::add_tail_to_query_expression_body_ext_parens() wraps it into a
derived table and attaches the new tail to the wrapping select. The
registrations were left behind in the inner select, so the ORDER BY of
the wrapping select contained window function items that no select had
registered: they never got a Window_funcs_sort, were never computed,
and their result field was read unset. The subqueries of the tail kept
the inner select as their master and name resolution context.

Move these registrations to the wrapping select in the new
move_tail_registrations():

- The window functions of the tail are the ones its ORDER BY items
  contain, looked up with Item::walk() and walk_subquery == FALSE so
  that a window function belonging to a subquery of the tail is not
  found and stays registered where it was parsed. Each one 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.

- Item::walk() does not descend into a window specification, so the
  items of a moved specification's PARTITION BY and ORDER BY lists are
  not re-targeted by Lex_order_limit_lock::set_to() and are pointed at
  the wrapping select's name resolution context here.

- The subquery units of the tail are the ones registered in front of
  the first unit the inner select had when the tail started to be
  parsed; the grammar remembers it in a mid-rule action. 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. They are
  excluded from the inner select, re-registered in the wrapping select
  and their Item_subselect::parent_select is updated.

Per-select parse state that is not a registration (n_sum_items,
with_sum_func, with_rownum, ftfunc_list, uncacheable, and the
select_n_where_fields of the tail's own fields) is deliberately left
behind, where it is merely over-counted.

Also remove the unreachable !unit check - unit is dereferenced on
entry - and fix the indentation of the surrounding block.
This commit is contained in:
bsrikanth-mariadb
2026-09-09 10:43:47 +05:30
parent fd85202eb2
commit f1fea53c75
5 changed files with 371 additions and 10 deletions

View File

@@ -224,3 +224,159 @@ drop table t;
#
# End of 10.11 tests
#
#
# 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 11.4 tests
#

View File

@@ -178,3 +178,88 @@ drop table t;
--echo #
--echo # End of 10.11 tests
--echo #
--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 11.4 tests
--echo #

View File

@@ -10827,13 +10827,121 @@ SELECT_LEX_UNIT *LEX::add_tail_to_query_expression_body(SELECT_LEX_UNIT *unit,
}
static bool order_list_contains(SQL_I_List<ORDER> *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<Window_spec> 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> *order_list,
SELECT_LEX_UNIT *first_unit)
{
if (order_list)
{
List_iterator<Item_window_func> 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();
@@ -10847,14 +10955,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();

View File

@@ -4900,7 +4900,8 @@ public:
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,

View File

@@ -8889,12 +8889,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().
*/
$<select_lex_unit>$= 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, $<select_lex_unit>2)))
MYSQL_YYABORT;
}
;