Commit 53784d9a authored by Sergei Petrunia's avatar Sergei Petrunia

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)
parent 0e9fb982
......@@ -683,4 +683,101 @@ pk a b bit_or
6 2 64 224
7 2 128 208
8 2 16 144
# Extra ROWS n PRECEDING tests
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) as bit_or
from t1;
pk a b bit_or
1 0 1 0
2 0 2 1
3 1 4 0
4 1 8 4
5 2 32 0
6 2 64 32
7 2 128 64
8 2 16 128
drop table t1;
create table t2 (
pk int,
a int,
b int
);
insert into t2 values
( 1, 0, 1),
( 2, 0, 2),
( 3, 0, 4),
( 4, 0, 8),
( 5, 1, 16),
( 6, 1, 32),
( 7, 1, 64),
( 8, 1, 128),
( 9, 2, 256),
(10, 2, 512),
(11, 2, 1024),
(12, 2, 2048);
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) as bit_or
from t2;
pk a b bit_or
1 0 1 0
2 0 2 1
3 0 4 2
4 0 8 4
5 1 16 0
6 1 32 16
7 1 64 32
8 1 128 64
9 2 256 0
10 2 512 256
11 2 1024 512
12 2 2048 1024
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 2 PRECEDING AND 2 PRECEDING) as bit_or
from t2;
pk a b bit_or
1 0 1 0
2 0 2 0
3 0 4 1
4 0 8 2
5 1 16 0
6 1 32 0
7 1 64 16
8 1 128 32
9 2 256 0
10 2 512 0
11 2 1024 256
12 2 2048 512
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) as bit_or
from t2;
pk a b bit_or
1 0 1 0
2 0 2 1
3 0 4 3
4 0 8 6
5 1 16 0
6 1 32 16
7 1 64 48
8 1 128 96
9 2 256 0
10 2 512 256
11 2 1024 768
12 2 2048 1536
# Check CURRENT ROW
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN CURRENT ROW AND CURRENT ROW) as bit_or
from t2;
pk a b bit_or
1 0 1 1
2 0 2 2
3 0 4 4
4 0 8 8
5 1 16 16
6 1 32 32
7 1 64 64
8 1 128 128
9 2 256 256
10 2 512 512
11 2 1024 1024
12 2 2048 2048
drop table t2;
......@@ -440,4 +440,50 @@ select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) as bit_or
from t1;
--echo # Extra ROWS n PRECEDING tests
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) as bit_or
from t1;
drop table t1;
create table t2 (
pk int,
a int,
b int
);
insert into t2 values
( 1, 0, 1),
( 2, 0, 2),
( 3, 0, 4),
( 4, 0, 8),
( 5, 1, 16),
( 6, 1, 32),
( 7, 1, 64),
( 8, 1, 128),
( 9, 2, 256),
(10, 2, 512),
(11, 2, 1024),
(12, 2, 2048);
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) as bit_or
from t2;
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 2 PRECEDING AND 2 PRECEDING) as bit_or
from t2;
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) as bit_or
from t2;
--echo # Check CURRENT ROW
select pk, a, b,
bit_or(b) over (partition by a order by pk ROWS BETWEEN CURRENT ROW AND CURRENT ROW) as bit_or
from t2;
drop table t2;
......@@ -609,94 +609,188 @@ class Frame_unbounded_following : public Frame_cursor
/*
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)
/*
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);
/*
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)
{
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);
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)
{
/*
"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;
// This is only needed for "FOLLOWING 1". It is one row behind
cursor.move_to(rownum+1);
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();
}
// Current row points at the first row in the partition
if (is_top_bound) // this is frame top endpoint
item->remove();
else
item->add();
}
}
void next_row(Item_sum* item)
/* Move our cursor to be n_rows ahead. */
void next_partition(longlong rownum, Item_sum* item)
{
if (n_rows_to_skip)
longlong i_end= n_rows + ((rownum==0)?1:0)- is_top_bound;
for (longlong i= 0; i < i_end; i++)
{
n_rows_to_skip--;
return;
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 @@ class Frame_n_rows : public Frame_cursor
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;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment