Skip to content

Commit 06be797

Browse files
author
Dag Wanvik
committed
Bug#26389508 INT JOIN_READ_KEY(QEP_TAB*): ASSERTION `!TABLE->HAS_NULL_ROW()' FAILED
Analysis: When a window with buffering follows a equijoin on a unique index (JT_EQ_REF) , we can get into trouble because windowing modifies the input record, presuming that once the windowing has been handed the record, next time control passes back to the join code a (new) record will be read to the input record. However, this does not hold with JT_EQ_REF, cf. the caching done in join_read_key: From its Doxygen: "Since the eq_ref access method will always return the same row, it is not necessary to read the row more than once, regardless of how many times it is needed in execution. This cache element is used when a row is needed after it has been read once, unless a key conversion error has occurred, or the cache has been disabled." Fix: We solve this problem by reinstating the input record before handing control back from end_write_wf. We optimize: only do this if the window in question follows after such a JOIN, i.e. window #1, and it has actually clobbered the input record. This can only happen if the last qep_tab has type JT_EQ_REF. Another, perhaps better approach, is to refactor to never touch the input record but keep the copying between the out record and the frame table record instead. Left for future refactoring. Added some missing Window method "const"s, and folded a couple of one-liners into window.h (from .cc). Repro added. Change-Id: I33bc43cd99ff79303b17d181abc3805ce226fb85
1 parent f83207e commit 06be797

File tree

6 files changed

+197
-39
lines changed

6 files changed

+197
-39
lines changed

mysql-test/r/window_functions.result

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8764,3 +8764,57 @@ FROM_UNIXTIME(LAG('-778:36:16.905133',246) RESPECT NULLS OVER(),
87648764
REPLACE('%M%V ','',''))
87658765
NULL
87668766
# End of test for Bug#26739028
8767+
#
8768+
# Bug#26389508: INT JOIN_READ_KEY(QEP_TAB*): ASSERTION
8769+
# `!TABLE->HAS_NULL_ROW()' FAILED
8770+
#
8771+
CREATE TABLE t1 (
8772+
pk int(11) NOT NULL AUTO_INCREMENT,
8773+
col_int int(11) DEFAULT NULL,
8774+
PRIMARY KEY (pk)
8775+
);
8776+
INSERT INTO t1 VALUES (1,NULL),(2,4),(3,-501481472),(4,NULL),(5,3);
8777+
CREATE TABLE t2 (
8778+
col_int_key int(11) DEFAULT NULL,
8779+
KEY col_int_key (col_int_key)
8780+
);
8781+
INSERT INTO t2 VALUES (NULL),(NULL),(NULL),(NULL),(5),(5);
8782+
This JOIN needs the fix
8783+
SELECT FIRST_VALUE( alias1.pk ) OVER
8784+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
8785+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
8786+
ON alias1.pk = alias2.col_int_key;
8787+
field1
8788+
NULL
8789+
NULL
8790+
NULL
8791+
NULL
8792+
NULL
8793+
NULL
8794+
This doesn't and shouldn't do save/restore it (not JT_EQ_REF). Verify
8795+
with debugger.
8796+
SELECT FIRST_VALUE( alias1.pk ) OVER
8797+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
8798+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
8799+
ON alias1.pk > alias2.col_int_key;
8800+
field1
8801+
NULL
8802+
NULL
8803+
NULL
8804+
NULL
8805+
NULL
8806+
NULL
8807+
Nor this one (not first window). Verify with debugger.
8808+
SELECT ROW_NUMBER() OVER () AS `row#`,
8809+
FIRST_VALUE( alias1.pk ) OVER
8810+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
8811+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
8812+
ON alias1.pk = alias2.col_int_key;
8813+
row# field1
8814+
1 NULL
8815+
2 NULL
8816+
3 NULL
8817+
4 NULL
8818+
5 NULL
8819+
6 NULL
8820+
DROP TABLE t1, t2;

mysql-test/t/window_functions.test

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3758,6 +3758,46 @@ SELECT FROM_UNIXTIME(LAG('-778:36:16.905133',246) RESPECT NULLS OVER(),
37583758

37593759
--echo # End of test for Bug#26739028
37603760

3761+
--echo #
3762+
--echo # Bug#26389508: INT JOIN_READ_KEY(QEP_TAB*): ASSERTION
3763+
--echo # `!TABLE->HAS_NULL_ROW()' FAILED
3764+
--echo #
3765+
CREATE TABLE t1 (
3766+
pk int(11) NOT NULL AUTO_INCREMENT,
3767+
col_int int(11) DEFAULT NULL,
3768+
PRIMARY KEY (pk)
3769+
);
3770+
3771+
INSERT INTO t1 VALUES (1,NULL),(2,4),(3,-501481472),(4,NULL),(5,3);
3772+
CREATE TABLE t2 (
3773+
col_int_key int(11) DEFAULT NULL,
3774+
KEY col_int_key (col_int_key)
3775+
);
3776+
3777+
INSERT INTO t2 VALUES (NULL),(NULL),(NULL),(NULL),(5),(5);
3778+
3779+
--echo This JOIN needs the fix
3780+
SELECT FIRST_VALUE( alias1.pk ) OVER
3781+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
3782+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
3783+
ON alias1.pk = alias2.col_int_key;
3784+
3785+
--echo This doesn't and shouldn't do save/restore it (not JT_EQ_REF). Verify
3786+
--echo with debugger.
3787+
SELECT FIRST_VALUE( alias1.pk ) OVER
3788+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
3789+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
3790+
ON alias1.pk > alias2.col_int_key;
3791+
3792+
--echo Nor this one (not first window). Verify with debugger.
3793+
SELECT ROW_NUMBER() OVER () AS `row#`,
3794+
FIRST_VALUE( alias1.pk ) OVER
3795+
(ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) AS field1
3796+
FROM t1 AS alias1 RIGHT JOIN t2 AS alias2
3797+
ON alias1.pk = alias2.col_int_key;
3798+
3799+
DROP TABLE t1, t2;
3800+
37613801
# Local Variables:
37623802
# mode: sql
37633803
# sql-product: mysql

sql/sql_executor.cc

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3936,6 +3936,12 @@ buffer_record_somewhere(THD *thd, Window *w, int64 rowno)
39363936
w->save_special_record(rowno, t);
39373937
DBUG_RETURN(false); // special record, don't put in frame buffer
39383938
}
3939+
else if (w->needs_restore_input_row())
3940+
{
3941+
w->save_special_record(Window::FBC_LAST_BUFFERED_ROW, t);
3942+
// Also put in frame buffer
3943+
}
3944+
39393945

39403946
DBUG_ASSERT(t->is_created());
39413947

@@ -4030,8 +4036,7 @@ buffer_record_somewhere(THD *thd, Window *w, int64 rowno)
40304036
if ((w->m_tmp_pos.m_position =
40314037
(uchar*)sql_alloc(t->file->ref_length)) == nullptr)
40324038
DBUG_RETURN(true);
4033-
4034-
}
4039+
}
40354040

40364041
error= t->file->ha_rnd_next(record);
40374042
t->file->position(record);
@@ -4267,13 +4272,17 @@ bring_back_frame_row(THD *thd,
42674272
DBUG_ASSERT(reason == Window::REA_MISC_POSITIONS || fno == 0);
42684273

42694274
uchar *fb_rec= w.frame_buffer()->record[0];
4275+
w.set_input_row_clobbered(rowno != w.last_rowno_in_cache() &&
4276+
rowno != Window::FBC_LAST_BUFFERED_ROW);
42704277

4271-
if (rowno == Window::FBC_FIRST_IN_NEXT_PARTITION)
4278+
if (rowno == Window::FBC_FIRST_IN_NEXT_PARTITION ||
4279+
rowno == Window::FBC_LAST_BUFFERED_ROW)
42724280
{
42734281
w.restore_special_record(rowno, fb_rec);
42744282
}
42754283
else
42764284
{
4285+
DBUG_ASSERT(reason != Window::REA_WONT_UPDATE_HINT);
42774286

42784287
#if !defined(DBUG_OFF) && defined(WF_DEBUG)
42794288
/*
@@ -4287,6 +4296,7 @@ bring_back_frame_row(THD *thd,
42874296
void *tmpbuff= std::malloc(v.size());
42884297
std::memcpy(tmpbuff, v.data(), v.size());
42894298
#endif
4299+
42904300
if (read_frame_buffer_row(rowno, &w, reason == Window::REA_MISC_POSITIONS))
42914301
DBUG_RETURN(true);
42924302

@@ -5573,7 +5583,7 @@ reestablish_new_partition_row(Window &w,
55735583

55745584
/* bring back saved row for next partition */
55755585
if (bring_back_frame_row(thd, w, Window::FBC_FIRST_IN_NEXT_PARTITION,
5576-
Window::REA_FIRST_IN_PARTITION))
5586+
Window::REA_WONT_UPDATE_HINT))
55775587
DBUG_RETURN(true);
55785588

55795589
DBUG_RETURN(false);
@@ -5840,6 +5850,21 @@ end_write_wf(JOIN *join, QEP_TAB *const qep_tab, bool end_of_records)
58405850
{
58415851
out_tbl->m_window->reset_partition_state();
58425852
}
5853+
else if (win->input_row_clobbered() &&
5854+
win->needs_restore_input_row())
5855+
{
5856+
/*
5857+
Reestablish last row read from input table in case it is needed again
5858+
before reading a new row. May be necessary if this is the first window
5859+
following after a join, cf. the caching presumption in join_read_key.
5860+
This logic can be removed if we move to copying between out
5861+
tmp record and frame buffer record, instead of involving the in
5862+
record. FIXME.
5863+
*/
5864+
if (bring_back_frame_row(thd, *win, Window::FBC_LAST_BUFFERED_ROW,
5865+
Window::REA_WONT_UPDATE_HINT))
5866+
DBUG_RETURN(NESTED_LOOP_ERROR);
5867+
}
58435868
}
58445869
else
58455870
{

sql/sql_select.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4771,6 +4771,9 @@ bool JOIN::make_tmp_tables_info()
47714771
*/
47724772
const uint widx= REF_SLICE_WIN_1 + wno;
47734773
const int fbidx= widx + m_windows.elements; // use far area
4774+
m_windows[wno]->set_needs_restore_input_row(
4775+
wno == 0 && qep_tab[primary_tables - 1].type() == JT_EQ_REF);
4776+
47744777

47754778
if (m_windows[wno]->needs_buffering())
47764779
{

sql/window.cc

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,13 +1362,6 @@ bool Window::setup_windows(THD* thd,
13621362
}
13631363

13641364

1365-
bool Window::needs_sorting()
1366-
{
1367-
return (m_partition_by != nullptr ||
1368-
m_order_by != nullptr);
1369-
}
1370-
1371-
13721365
bool Window::has_dynamic_frame_upper_bound() const
13731366
{
13741367
return m_frame == nullptr && m_order_by != nullptr;
@@ -1526,12 +1519,6 @@ void Window::reset_execution_state(Reset_level level)
15261519
}
15271520

15281521

1529-
bool Window::is_last_row_in_frame ()
1530-
{
1531-
return (m_is_last_row_in_frame ||
1532-
m_select->table_list.elements == 0);
1533-
}
1534-
15351522
//int64 Window::frame_cardinality()
15361523
//{
15371524
// if (!m_needs_frame_buffering)

0 commit comments

Comments
 (0)