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

Support RANGE ... CURRENT ROW as frame's first and second bound.
This commit is contained in:
Sergei Petrunia
2016-03-06 23:10:20 +03:00
parent b579a626cf
commit 1fa12cdfb7
5 changed files with 576 additions and 76 deletions

View File

@ -221,6 +221,15 @@ public:
}
protected:
bool at_eof() { return (cache_pos == cache_end); }
uchar *get_last_rowid()
{
if (cache_pos == cache_start)
return NULL;
else
return cache_pos - ref_length;
}
uchar *get_curr_rowid() { return cache_pos; }
};
@ -259,6 +268,18 @@ public:
return res;
}
bool restore_last_row()
{
uchar *p;
if ((p= get_last_rowid()))
{
int rc= read_record->table->file->ha_rnd_pos(read_record->record, p);
if (!rc)
return true;
}
return false;
}
// todo: should move_to() also read row here?
};
@ -294,6 +315,19 @@ public:
return true;
return false;
}
int compare_with_cache()
{
List_iterator<Cached_item> li(group_fields);
Cached_item *ptr;
int res;
while ((ptr= li++))
{
if ((res= ptr->cmp_read_only()))
return res;
}
return 0;
}
};
@ -316,7 +350,8 @@ class Frame_cursor : public Sql_alloc
{
public:
virtual void init(THD *thd, READ_RECORD *info,
SQL_I_List<ORDER> *partition_list)
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{}
/*
@ -335,18 +370,178 @@ public:
- The callee may move tbl->file and tbl->record[0] to point to some other
row.
*/
virtual void next_partition(bool first, Item_sum* item)=0;
virtual void pre_next_partition(longlong rownum, Item_sum* item){};
virtual void next_partition(longlong rownum, Item_sum* item)=0;
/*
The current row has moved one row forward.
Move this frame bound accordingly, and update the value of aggregate
function as necessary.
*/
virtual void pre_next_row(Item_sum* item){};
virtual void next_row(Item_sum* item)=0;
virtual ~Frame_cursor(){}
};
//
// RANGE-type frames
//
/*
RANGE BETWEEN ... AND CURRENT ROW
This is a bottom endpoint of RANGE-CURRENT ROW frame.
It moves ahead of the current_row. It is located just in front of the first
peer of the currrent_row.
*/
class Frame_range_current_row_bottom: public Frame_cursor
{
Table_read_cursor cursor;
Group_bound_tracker peer_tracker;
bool dont_move;
public:
void init(THD *thd, READ_RECORD *info,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
peer_tracker.init(thd, order_list);
}
void pre_next_partition(longlong rownum, Item_sum* item)
{
// Save the value of the current_row
peer_tracker.check_if_next_group();
if (rownum != 0)
item->add(); // current row is in
}
void next_partition(longlong rownum, Item_sum* item)
{
walk_till_non_peer(item);
}
void pre_next_row(Item_sum* item)
{
// Check if our cursor is pointing at a peer of the current row.
// If not, move forward until that becomes true
dont_move= !peer_tracker.check_if_next_group();
if (!dont_move)
item->add();
}
// New condition: this now assumes that table's current
// row is pointing to the current_row's position
void next_row(Item_sum* item)
{
// Check if our cursor is pointing at a peer of the current row.
// If not, move forward until that becomes true
if (dont_move)
{
/*
Our current is not a peer of the current row.
No need to move the bound.
*/
return;
}
walk_till_non_peer(item);
}
private:
void walk_till_non_peer(Item_sum* item)
{
/*
Walk forward until we've met first row that's not a peer of the current
row
*/
while (!cursor.get_next())
{
if (peer_tracker.compare_with_cache())
break;
item->add();
}
}
};
/*
RANGE BETWEEN CURRENT ROW AND ...
This is a top endpoint of RANGE-CURRENT ROW frame.
It moves behind the current_row. It is located right after the first peer of
the current_row.
*/
class Frame_range_current_row_top : public Frame_cursor
{
Group_bound_tracker bound_tracker;
Table_read_cursor cursor;
Group_bound_tracker peer_tracker;
bool move;
bool at_partition_start;
public:
void init(THD *thd, READ_RECORD *info,
SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
bound_tracker.init(thd, partition_list);
cursor.init(info);
peer_tracker.init(thd, order_list);
}
void pre_next_partition(longlong rownum, Item_sum* item)
{
// fetch the value from the first row
peer_tracker.check_if_next_group();
}
void next_partition(longlong rownum, Item_sum* item)
{
at_partition_start= true;
cursor.move_to(rownum+1);
}
void pre_next_row(Item_sum* item)
{
// Check if our current row is pointing to a peer of the current row.
// If not, move forward until that becomes true.
move= peer_tracker.check_if_next_group();
}
void next_row(Item_sum* item)
{
bool was_at_partition_start= at_partition_start;
at_partition_start= false;
if (move)
{
if (!was_at_partition_start &&
cursor.restore_last_row())
{
item->remove();
}
do
{
if (cursor.get_next())
return;
if (!peer_tracker.compare_with_cache())
return;
item->remove();
}
while (1);
}
}
};
//////////////////////////////////////////////////////////////////////////////////
/*
UNBOUNDED PRECEDING frame bound
@ -354,7 +549,7 @@ public:
class Frame_unbounded_preceding : public Frame_cursor
{
public:
void next_partition(bool first, Item_sum* item)
void next_partition(longlong rownum, Item_sum* item)
{
/*
UNBOUNDED PRECEDING frame end just stays on the first row.
@ -378,15 +573,16 @@ class Frame_unbounded_following : public Frame_cursor
Group_bound_tracker bound_tracker;
public:
void init(THD *thd, READ_RECORD *info, SQL_I_List<ORDER> *partition_list)
void init(THD *thd, READ_RECORD *info, SQL_I_List<ORDER> *partition_list,
SQL_I_List<ORDER> *order_list)
{
cursor.init(info);
bound_tracker.init(thd, partition_list);
}
void next_partition(bool first, Item_sum* item)
void next_partition(longlong rownum, Item_sum* item)
{
if (first)
if (!rownum)
{
/* Read the first row */
if (cursor.get_next())
@ -407,7 +603,7 @@ public:
void next_row(Item_sum* item)
{
/* Do nothing, UNBOUNDED FOLLOWING frame end doesn't */
/* Do nothing, UNBOUNDED FOLLOWING frame end doesn't move */
}
};
@ -436,7 +632,8 @@ public:
is_top_bound(is_top_bound_arg), n_rows(n_rows_arg), is_preceding(is_preceding_arg)
{}
void init(THD *thd, READ_RECORD *info, SQL_I_List<ORDER> *partition_list)
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;
@ -444,14 +641,14 @@ public:
bound_tracker.init(thd, partition_list);
}
void next_partition(bool first, Item_sum* item)
void next_partition(longlong rownum, Item_sum* item)
{
cursor_eof= false;
at_partition_start= true;
at_partition_end= false;
if (is_preceding)
{
if (!first)
if (rownum != 0)
{
/*
The cursor in "ROWS n PRECEDING" lags behind by n_rows rows.
@ -474,9 +671,10 @@ public:
*/
n_rows_to_skip= 0;
if (!first && (!is_top_bound || n_rows))
if ((rownum != 0) && (!is_top_bound || n_rows))
{
// We are positioned at the first row in the partition:
// 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
@ -485,13 +683,18 @@ public:
/*
Note: i_end=-1 when this is a top-endpoint "CURRENT ROW" which is
implemented as "ROWS 0 FOLLOWING".
*/
longlong i_end= n_rows + (first?1:0)- is_top_bound;
*/
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();
}
}
}
@ -547,29 +750,49 @@ public:
};
Frame_cursor *get_frame_cursor(Window_frame_bound *bound, bool is_top_bound)
Frame_cursor *get_frame_cursor(Window_frame *frame, bool is_top_bound)
{
Window_frame_bound *bound= is_top_bound? frame->top_bound :
frame->bottom_bound;
if (bound->precedence_type == Window_frame_bound::PRECEDING ||
bound->precedence_type == Window_frame_bound::FOLLOWING)
bound->precedence_type == Window_frame_bound::FOLLOWING)
{
bool is_preceding= (bound->precedence_type ==
Window_frame_bound::PRECEDING);
if (bound->offset == NULL) /* this is UNBOUNDED */
{
/* The following serve both RANGE and ROWS: */
if (is_preceding)
return new Frame_unbounded_preceding;
else
return new Frame_unbounded_following;
}
longlong n_rows= bound->offset->val_int();
return new Frame_n_rows(is_top_bound, is_preceding, n_rows);
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);
}
else
{
// todo: Frame_range_n_rows here .
DBUG_ASSERT(0);
}
}
if (bound->precedence_type == Window_frame_bound::CURRENT)
{
return new Frame_current_row(is_top_bound);
if (frame->units == Window_frame::UNITS_ROWS)
return new Frame_current_row(is_top_bound);
else
{
if (is_top_bound)
return new Frame_range_current_row_top;
else
return new Frame_range_current_row_bottom;
}
}
return NULL;
}
@ -624,71 +847,63 @@ bool compute_window_func_with_frames(Item_window_func *item_win,
sum_func->set_aggregator(Aggregator::SIMPLE_AGGREGATOR);
Window_frame *window_frame= item_win->window_spec->window_frame;
DBUG_ASSERT(window_frame->units == Window_frame::UNITS_ROWS);
top_bound= get_frame_cursor(window_frame->top_bound, true);
bottom_bound= get_frame_cursor(window_frame->bottom_bound, false);
top_bound= get_frame_cursor(window_frame, true);
bottom_bound= get_frame_cursor(window_frame, false);
top_bound->init(thd, info, &item_win->window_spec->partition_list);
bottom_bound->init(thd, info, &item_win->window_spec->partition_list);
top_bound->init(thd, info, &item_win->window_spec->partition_list,
&item_win->window_spec->order_list);
bottom_bound->init(thd, info, &item_win->window_spec->partition_list,
&item_win->window_spec->order_list);
bool is_error= false;
bool first_row= true;
longlong rownum= 0;
uchar *rowid_buf= (uchar*) my_malloc(tbl->file->ref_length, MYF(0));
while (true)
{
if (first_row)
/* Move the current_row */
if ((err=info->read_record(info)))
{
break; /* End of file */
}
bool partition_changed= (item_win->check_partition_bound() > -1)? true:
false;
tbl->file->position(tbl->record[0]);
memcpy(rowid_buf, tbl->file->ref, tbl->file->ref_length);
/* Adjust partition bounds */
if (partition_changed || (rownum == 0))
{
/* Start the first partition */
sum_func->clear();
bottom_bound->next_partition(true, sum_func);
top_bound->next_partition(true, sum_func);
bottom_bound->pre_next_partition(rownum, sum_func);
top_bound->pre_next_partition(rownum, sum_func);
/*
We move bottom_bound first, because we want rows to be added into the
aggregate before top_bound attempts to remove them.
*/
bottom_bound->next_partition(rownum, sum_func);
top_bound->next_partition(rownum, sum_func);
}
else
{
bottom_bound->pre_next_row(sum_func);
top_bound->pre_next_row(sum_func);
/* These can write into tbl->record[0] */
bottom_bound->next_row(sum_func);
top_bound->next_row(sum_func);
}
if ((err=info->read_record(info)))
{
/* End of file */
break;
}
store_record(tbl,record[1]);
bool partition_changed= (item_win->check_partition_bound() > -1)? true:
false;
if (!first_row && partition_changed)
{
sum_func->clear();
tbl->file->position(tbl->record[0]);
memcpy(rowid_buf, tbl->file->ref, tbl->file->ref_length);
/*
Ok, the current row is the first row in the new partition.
We move bottom_bound first, because we want rows to be added into the
aggregate before top_bound attempts to remove them.
*/
bottom_bound->next_partition(false, sum_func);
/*
The problem is, the above call may have made tbl->record[0] to point to
some other record.
*/
tbl->file->ha_rnd_pos(tbl->record[0], rowid_buf);
top_bound->next_partition(false, sum_func);
rownum++;
/*
The same problem again. The above call may have moved table's current
record. We need to make the current row current, so that ha_update_row
call below updates the right row.
*/
tbl->file->ha_rnd_pos(tbl->record[0], rowid_buf);
}
first_row= false;
/* Read the current row and update it */
/*
The bounds may have made tbl->record[0] to point to some record other
than current_row. This applies to tbl->file's internal state, too.
Fix this by reading the current row again.
*/
tbl->file->ha_rnd_pos(tbl->record[0], rowid_buf);
store_record(tbl,record[1]);
item_win->save_in_field(item_win->result_field, true);
err= tbl->file->ha_update_row(tbl->record[1], tbl->record[0]);
if (err && err != HA_ERR_RECORD_IS_THE_SAME)