1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-29 05:21:33 +03:00

MDEV-9676: RANGE-type frames for window functions

Add support for "RANGE n PRECEDING|FOLLOWING" frame bounds.
- n is currently limited to whatever Item and Item_sum_plus/minus
  can handle (i.e. no DATETIME intervals).
- Didn't check NULL value handling yet.
This commit is contained in:
Sergei Petrunia
2016-03-13 03:34:31 +03:00
parent 879731aa84
commit e859c2dbb8
6 changed files with 479 additions and 16 deletions

View File

@ -299,7 +299,7 @@ public:
{
for (ORDER *curr = list->first; curr; curr=curr->next)
{
Cached_item *tmp= new_Cached_item(thd, curr->item[0], TRUE);
Cached_item *tmp= new_Cached_item(thd, curr->item[0], TRUE);
group_fields.push_back(tmp);
}
}
@ -388,6 +388,206 @@ public:
// RANGE-type frames
//////////////////////////////////////////////////////////////////////////////
/*
Frame_range_n_top handles the top end of RANGE-type frame.
That is, it handles:
RANGE BETWEEN n PRECEDING AND ...
RANGE BETWEEN n FOLLOWING AND ...
Top of the frame doesn't need to check for partition end, since bottom will
reach it before.
*/
class Frame_range_n_top : public Frame_cursor
{
Table_read_cursor cursor;
Cached_item_item *range_expr;
Item *n_val;
Item *item_add;
const bool is_preceding;
public:
Frame_range_n_top(bool is_preceding_arg, Item *n_val_arg) :
n_val(n_val_arg), item_add(NULL), is_preceding(is_preceding_arg)
{}
void init(THD *thd, READ_RECORD *info,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
DBUG_ASSERT(order_list->elements == 1);
Item *src_expr= order_list->first->item[0];
range_expr= (Cached_item_item*) new_Cached_item(thd, src_expr, FALSE);
if (is_preceding)
item_add= new (thd->mem_root) Item_func_minus(thd, src_expr, n_val);
else
item_add= new (thd->mem_root) Item_func_plus(thd, src_expr, n_val);
item_add->fix_fields(thd, &item_add);
}
void pre_next_partition(longlong rownum, Item_sum* item)
{
// Save the value of FUNC(current_row)
range_expr->fetch_value_from(item_add);
}
void next_partition(longlong rownum, Item_sum* item)
{
cursor.move_to(rownum);
walk_till_non_peer(item);
}
void pre_next_row(Item_sum* item)
{
range_expr->fetch_value_from(item_add);
}
void next_row(Item_sum* item)
{
/*
Ok, our cursor is at the first row R where
(prev_row + n) >= R
We need to check about the current row.
*/
if (cursor.restore_last_row())
{
if (range_expr->cmp_read_only() <= 0)
return;
item->remove();
}
walk_till_non_peer(item);
}
private:
void walk_till_non_peer(Item_sum* item)
{
while (!cursor.get_next())
{
if (range_expr->cmp_read_only() <= 0)
break;
item->remove();
}
}
};
/*
Frame_range_n_bottom handles bottom end of RANGE-type frame.
That is, it handles frame bounds in form:
RANGE BETWEEN ... AND n PRECEDING
RANGE BETWEEN ... AND n FOLLOWING
Bottom end moves first so it needs to check for partition end
(todo: unless it's PRECEDING and in that case it doesnt)
*/
class Frame_range_n_bottom: public Frame_cursor
{
Table_read_cursor cursor;
Cached_item_item *range_expr;
Item *n_val;
Item *item_add;
const bool is_preceding;
Group_bound_tracker bound_tracker;
bool end_of_partition;
public:
Frame_range_n_bottom(bool is_preceding_arg, Item *n_val_arg) :
n_val(n_val_arg), item_add(NULL), is_preceding(is_preceding_arg)
{}
void init(THD *thd, READ_RECORD *info,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
DBUG_ASSERT(order_list->elements == 1);
Item *src_expr= order_list->first->item[0];
range_expr= (Cached_item_item*) new_Cached_item(thd, src_expr, FALSE);
if (is_preceding)
item_add= new (thd->mem_root) Item_func_minus(thd, src_expr, n_val);
else
item_add= new (thd->mem_root) Item_func_plus(thd, src_expr, n_val);
item_add->fix_fields(thd, &item_add);
bound_tracker.init(thd, partition_list);
}
void pre_next_partition(longlong rownum, Item_sum* item)
{
// Save the value of FUNC(current_row)
range_expr->fetch_value_from(item_add);
bound_tracker.check_if_next_group();
end_of_partition= false;
}
void next_partition(longlong rownum, Item_sum* item)
{
cursor.move_to(rownum);
walk_till_non_peer(item);
}
void pre_next_row(Item_sum* item)
{
if (end_of_partition)
return;
range_expr->fetch_value_from(item_add);
}
void next_row(Item_sum* item)
{
if (end_of_partition)
return;
/*
Ok, our cursor is at the first row R where
(prev_row + n) >= R
We need to check about the current row.
*/
if (cursor.restore_last_row())
{
if (range_expr->cmp_read_only() < 0)
return;
item->add();
}
walk_till_non_peer(item);
}
private:
void walk_till_non_peer(Item_sum* item)
{
int res;
while (!(res= cursor.get_next()))
{
if (bound_tracker.check_if_next_group())
{
end_of_partition= true;
break;
}
if (range_expr->cmp_read_only() < 0)
break;
item->add();
}
if (res)
end_of_partition= true;
}
};
/*
RANGE BETWEEN ... AND CURRENT ROW, bottom frame bound for CURRENT ROW
...
@ -469,7 +669,6 @@ private:
item->add();
}
}
};
@ -893,8 +1092,10 @@ Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
}
else
{
// todo: Frame_range_n_rows here .
DBUG_ASSERT(0);
if (is_top_bound)
return new Frame_range_n_top(is_preceding, bound->offset);
else
return new Frame_range_n_bottom(is_preceding, bound->offset);
}
}