1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

MDEV-9695: Wrong window frame when using RANGE BETWEEN N FOLLOWING AND PRECEDING

Part#2: Fix a couple more issues in rows-type frames.
This also has a code cleanup:
- introduce a separate Frame_rows_current_row_(top,bottom). This is
  is a special case which doesn't need its cursor or partition bound check
- Split Frame_n_rows into
  = Frame_n_rows_preceding (this one is now much simpler)
  = Frame_n_rows_following (simpler and works but may need some work still)
This commit is contained in:
Sergei Petrunia
2016-03-11 20:23:24 +03:00
parent 0e9fb982f1
commit 53784d9abc
3 changed files with 318 additions and 91 deletions

View File

@ -609,87 +609,54 @@ public:
/*
ROWS $n (PRECEDING|FOLLOWING) frame bound.
*/
ROWS $n PRECEDING frame bound
class Frame_n_rows : public Frame_cursor
*/
class Frame_n_rows_preceding : public Frame_cursor
{
/* Whether this is top of the frame or bottom */
/* Whether this is top of the frame or bottom */
const bool is_top_bound;
const ha_rows n_rows;
const bool is_preceding;
/* Number of rows that we need to skip before our cursor starts moving */
ha_rows n_rows_to_skip;
Table_read_cursor cursor;
bool cursor_eof; //TODO: need this still?
Group_bound_tracker bound_tracker;
bool at_partition_start;
bool at_partition_end;
public:
Frame_n_rows(bool is_top_bound_arg, bool is_preceding_arg, ha_rows n_rows_arg) :
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg), is_preceding(is_preceding_arg)
Frame_n_rows_preceding(bool is_top_bound_arg, ha_rows n_rows_arg) :
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg)
{}
void init(THD *thd, READ_RECORD *info, SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
cursor_eof= false;
at_partition_start= true;
bound_tracker.init(thd, partition_list);
}
void next_partition(longlong rownum, Item_sum* item)
{
cursor_eof= false;
at_partition_start= true;
at_partition_end= false;
if (is_preceding)
{
if (rownum != 0)
{
/* The cursor in "ROWS n PRECEDING" lags behind by n_rows rows. */
cursor.move_to(rownum);
}
n_rows_to_skip= n_rows - (is_top_bound? 0:1);
}
else
{
/*
"ROWS n FOLLOWING" is already at the first row in the next partition.
Move it to be n_rows ahead.
*/
n_rows_to_skip= 0;
/*
Position our cursor to point at the first row in the new partition
(for rownum=0, it is already there, otherwise, it lags behind)
*/
if (rownum != 0)
cursor.move_to(rownum);
if ((rownum != 0) && (!is_top_bound || n_rows))
{
// We are positioned at the first row in the partition anyway
//cursor.restore_cur_row();
if (is_top_bound) // this is frame top endpoint
item->remove();
else
item->add();
}
/*
Note: i_end=-1 when this is a top-endpoint "CURRENT ROW" which is
implemented as "ROWS 0 FOLLOWING".
*/
longlong i_end= n_rows + ((rownum==0)?1:0)- is_top_bound;
for (longlong i= 0; i < i_end; i++)
{
if (next_row_intern(item))
break;
}
if (i_end == -1)
{
if (!cursor.get_next())
bound_tracker.check_if_next_group();
}
}
/*
Suppose the bound is ROWS 2 PRECEDING, and current row is row#n:
...
n-3
n-2 --- bound row
n-1
n --- current_row
...
The bound should point at row #(n-2). Bounds are inclusive, so
- bottom bound should add row #(n-2) into the window function
- top bound should remove row (#n-3) from the window function.
*/
n_rows_to_skip= n_rows + (is_top_bound? 1:0) - 1;
}
void next_row(Item_sum* item)
{
if (n_rows_to_skip)
@ -697,6 +664,133 @@ public:
n_rows_to_skip--;
return;
}
if (cursor.get_next())
return; // this is not expected to happen.
if (is_top_bound) // this is frame start endpoint
item->remove();
else
item->add();
}
};
/*
ROWS ... CURRENT ROW, Bottom bound.
This case is moved to separate class because here we don't need to maintain
our own cursor, or check for partition bound.
*/
class Frame_rows_current_row_bottom : public Frame_cursor
{
public:
void pre_next_partition(longlong rownum, Item_sum* item)
{
item->add();
}
void next_partition(longlong rownum, Item_sum* item) {}
void pre_next_row(Item_sum* item)
{
/* Temp table's current row is current_row. Add it to the window func */
item->add();
}
void next_row(Item_sum* item) {};
};
/*
ROWS-type CURRENT ROW, top bound.
This serves for processing "ROWS BETWEEN CURRENT ROW AND ..." frames.
n-1
n --+ --- current_row, and top frame bound
n+1 |
... |
when the current_row moves to row #n, this frame bound should remove the
row #(n-1) from the window function.
In other words, we need what "ROWS PRECEDING 0" provides.
*/
class Frame_rows_current_row_top: public Frame_n_rows_preceding
{
public:
Frame_rows_current_row_top() :
Frame_n_rows_preceding(true /*top*/, 0 /* n_rows */)
{}
};
/*
ROWS $n FOLLOWING frame bound.
*/
class Frame_n_rows_following : public Frame_cursor
{
/* Whether this is top of the frame or bottom */
const bool is_top_bound;
const ha_rows n_rows;
Table_read_cursor cursor;
bool at_partition_end;
/*
This cursor reaches partition end before the main cursor has reached it.
bound_tracker is used to detect partition end.
*/
Group_bound_tracker bound_tracker;
public:
Frame_n_rows_following(bool is_top_bound_arg, ha_rows n_rows_arg) :
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg)
{
DBUG_ASSERT(n_rows > 0);
}
void init(THD *thd, READ_RECORD *info, SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
at_partition_end= false;
bound_tracker.init(thd, partition_list);
}
void pre_next_partition(longlong rownum, Item_sum* item)
{
at_partition_end= false;
// Fetch current partition value
bound_tracker.check_if_next_group();
if (rownum != 0)
{
// This is only needed for "FOLLOWING 1". It is one row behind
cursor.move_to(rownum+1);
// Current row points at the first row in the partition
if (is_top_bound) // this is frame top endpoint
item->remove();
else
item->add();
}
}
/* Move our cursor to be n_rows ahead. */
void next_partition(longlong rownum, Item_sum* item)
{
longlong i_end= n_rows + ((rownum==0)?1:0)- is_top_bound;
for (longlong i= 0; i < i_end; i++)
{
if (next_row_intern(item))
break;
}
}
void next_row(Item_sum* item)
{
if (at_partition_end)
return;
next_row_intern(item);
@ -705,43 +799,25 @@ public:
private:
bool next_row_intern(Item_sum *item)
{
if (!cursor_eof)
if (!cursor.get_next())
{
if (!(cursor_eof= (0 != cursor.get_next())))
if (bound_tracker.check_if_next_group())
at_partition_end= true;
else
{
bool new_group= is_preceding? false: bound_tracker.check_if_next_group();
if (at_partition_start || !new_group)
{
if (is_top_bound) // this is frame start endpoint
item->remove();
else
item->add();
at_partition_start= false;
return false; /* Action done */
}
if (is_top_bound) // this is frame start endpoint
item->remove();
else
{
at_partition_end= true;
return true;
}
item->add();
}
}
return true; /* Action not done */
else
at_partition_end= true;
return at_partition_end;
}
};
/* CURRENT ROW is the same as "ROWS 0 FOLLOWING" */
class Frame_current_row : public Frame_n_rows
{
public:
Frame_current_row(bool is_top_bound_arg) :
Frame_n_rows(is_top_bound_arg, false /*is_preceding*/, ha_rows(0))
{}
};
Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
{
// TODO-cvicentiu When a frame is not specified, which is the frame type
@ -777,7 +853,10 @@ Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
if (frame->units == Window_frame::UNITS_ROWS)
{
longlong n_rows= bound->offset->val_int();
return new Frame_n_rows(is_top_bound, is_preceding, n_rows);
if (is_preceding)
return new Frame_n_rows_preceding(is_top_bound, n_rows);
else
return new Frame_n_rows_following(is_top_bound, n_rows);
}
else
{
@ -789,7 +868,12 @@ Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
if (bound->precedence_type == Window_frame_bound::CURRENT)
{
if (frame->units == Window_frame::UNITS_ROWS)
return new Frame_current_row(is_top_bound);
{
if (is_top_bound)
return new Frame_rows_current_row_top;
else
return new Frame_rows_current_row_bottom;
}
else
{
if (is_top_bound)
@ -798,7 +882,7 @@ Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
return new Frame_range_current_row_bottom;
}
}
return NULL;
return NULL;
}