From e88758330c49b40556a1f9c3d316b66ce11ca67d Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Mon, 28 Mar 2016 18:38:42 +0300 Subject: [PATCH 1/2] Make window functions computation step show up in EXPLAIN FORMAT=JSON output --- mysql-test/r/win.result | 73 +++++++++++++++++++++++++++++++++++++++++ mysql-test/t/win.test | 31 +++++++++++++++-- sql/sql_explain.cc | 3 ++ sql/sql_explain.h | 8 ++++- sql/sql_select.cc | 23 +++++++++---- sql/sql_select.h | 2 +- 6 files changed, 129 insertions(+), 11 deletions(-) diff --git a/mysql-test/r/win.result b/mysql-test/r/win.result index 4692a3f76cc..82b04e17116 100644 --- a/mysql-test/r/win.result +++ b/mysql-test/r/win.result @@ -1401,3 +1401,76 @@ pk c CNT 9 2 4 10 2 3 drop table t0,t1; +# +# EXPLAIN FORMAT=JSON support for window functions +# +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +explain format=json select rank() over (order by a) from t0; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "window_functions_computation": { + "temporary_table": { + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + } +} +create table t1 (a int, b int, c int); +insert into t1 select a,a,a from t0; +explain format=json +select +a, +rank() over (order by sum(b)) +from t1 +group by a; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "filesort": { + "window_functions_computation": { + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + } + } +} +explain format=json +select +a, +rank() over (order by sum(b)) +from t1 +group by a +order by null; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "window_functions_computation": { + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + } +} +drop table t1; +drop table t0; diff --git a/mysql-test/t/win.test b/mysql-test/t/win.test index cc01d5e678e..6469dc1dbe3 100644 --- a/mysql-test/t/win.test +++ b/mysql-test/t/win.test @@ -924,8 +924,33 @@ execute stmt; drop table t0,t1; - - - +--echo # +--echo # EXPLAIN FORMAT=JSON support for window functions +--echo # +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +explain format=json select rank() over (order by a) from t0; + +create table t1 (a int, b int, c int); +insert into t1 select a,a,a from t0; + +explain format=json +select + a, + rank() over (order by sum(b)) +from t1 +group by a; + +explain format=json +select + a, + rank() over (order by sum(b)) +from t1 +group by a +order by null; + +drop table t1; +drop table t0; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index f3001d5942a..f3d71e6ce80 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -878,6 +878,9 @@ void Explain_select::print_explain_json(Explain_query *query, case AGGR_OP_REMOVE_DUPLICATES: writer->add_member("duplicate_removal").start_object(); break; + case AGGR_OP_WINDOW_FUNCS: + writer->add_member("window_functions_computation").start_object(); + break; default: DBUG_ASSERT(0); } diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 3f57f7ac937..ba2cc482be1 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -264,7 +264,8 @@ typedef enum AGGR_OP_TEMP_TABLE, AGGR_OP_FILESORT, //AGGR_OP_READ_SORTED_FILE, // need this? - AGGR_OP_REMOVE_DUPLICATES + AGGR_OP_REMOVE_DUPLICATES, + AGGR_OP_WINDOW_FUNCS //AGGR_OP_JOIN // Need this? } enum_explain_aggr_node_type; @@ -296,6 +297,11 @@ public: enum_explain_aggr_node_type get_type() { return AGGR_OP_REMOVE_DUPLICATES; } }; +class Explain_aggr_window_funcs : public Explain_aggr_node +{ +public: + enum_explain_aggr_node_type get_type() { return AGGR_OP_WINDOW_FUNCS; } +}; ///////////////////////////////////////////////////////////////////////////// diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 30be455b3d3..b67ecaf492f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2135,7 +2135,6 @@ bool JOIN::make_aggr_tables_info() All optimization is done. Check if we can use the storage engines group by handler to evaluate the group by */ - group_by_handler *gbh= NULL; if (tables_list && (tmp_table_param.sum_func_count || group_list) && !procedure) { @@ -2337,8 +2336,8 @@ bool JOIN::make_aggr_tables_info() // psergey-todo: this is probably an incorrect place: if (select_lex->window_funcs.elements) { - curr_tab->window_funcs= new Window_funcs_computation; - if (curr_tab->window_funcs->setup(thd, &select_lex->window_funcs)) + curr_tab->window_funcs_step= new Window_funcs_computation; + if (curr_tab->window_funcs_step->setup(thd, &select_lex->window_funcs)) DBUG_RETURN(true); } @@ -19137,7 +19136,10 @@ bool test_if_use_dynamic_range_scan(JOIN_TAB *join_tab) int join_init_read_record(JOIN_TAB *tab) { int error; - + /* + Note: the query plan tree for the below operations is constructed in + save_agg_explain_data. + */ if (tab->distinct && tab->remove_duplicates()) // Remove duplicates. return 1; if (tab->filesort && tab->sort_table()) // Sort table. @@ -24158,6 +24160,14 @@ void save_agg_explain_data(JOIN *join, Explain_select *xpl_sel) node= new Explain_aggr_tmp_table; node->child= prev_node; + if (join_tab->window_funcs_step) + { + prev_node=node; + node= new Explain_aggr_window_funcs; + node->child= prev_node; + } + + /* The below matches execution in join_init_read_record() */ if (join_tab->distinct) { prev_node= node; @@ -25919,9 +25929,10 @@ AGGR_OP::end_send() // Update ref array join_tab->join->set_items_ref_array(*join_tab->ref_array); - if (join_tab->window_funcs) + if (join_tab->window_funcs_step) { - join_tab->window_funcs->exec(join); + if (join_tab->window_funcs_step->exec(join)) + return NESTED_LOOP_ERROR; } table->reginfo.lock_type= TL_UNLOCK; diff --git a/sql/sql_select.h b/sql/sql_select.h index e85931dcc7c..552ff4686ca 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -428,7 +428,7 @@ typedef struct st_join_table { Non-NULL value means this join_tab must do window function computation before reading. */ - Window_funcs_computation* window_funcs; + Window_funcs_computation* window_funcs_step; /** List of topmost expressions in the select list. The *next* JOIN TAB From d146c2cedc88e6d8728584b83861bd9b677a44a3 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Mon, 28 Mar 2016 20:53:09 +0300 Subject: [PATCH 2/2] MDEV-9787: Window functions: HAVING and GROUP BY - Hook window function computation into the right location. - Add a testcase which shows that HAVING is now checked before the window function computation step. --- mysql-test/r/win.result | 29 +++++++++++++++++++++++++++++ mysql-test/t/win.test | 9 ++++++++- sql/sql_select.cc | 26 ++++++++++++++++++-------- sql/sql_window.cc | 13 ++++++++++++- sql/sql_window.h | 6 ++++-- 5 files changed, 71 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/win.result b/mysql-test/r/win.result index 82b04e17116..11d1fe9279f 100644 --- a/mysql-test/r/win.result +++ b/mysql-test/r/win.result @@ -1472,5 +1472,34 @@ EXPLAIN } } } +# +# Check how window function works together with GROUP BY and HAVING +# +select b,max(a) as MX, rank() over (order by b) from t1 group by b having MX in (3,5,7); +b MX rank() over (order by b) +3 3 1 +5 5 2 +7 7 3 +explain format=json +select b,max(a) as MX, rank() over (order by b) from t1 group by b having MX in (3,5,7); +EXPLAIN +{ + "query_block": { + "select_id": 1, + "having_condition": "(MX in (3,5,7))", + "filesort": { + "window_functions_computation": { + "temporary_table": { + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + } + } +} drop table t1; drop table t0; diff --git a/mysql-test/t/win.test b/mysql-test/t/win.test index 6469dc1dbe3..35b6c1cbb72 100644 --- a/mysql-test/t/win.test +++ b/mysql-test/t/win.test @@ -950,7 +950,14 @@ from t1 group by a order by null; +--echo # +--echo # Check how window function works together with GROUP BY and HAVING +--echo # + +select b,max(a) as MX, rank() over (order by b) from t1 group by b having MX in (3,5,7); +explain format=json +select b,max(a) as MX, rank() over (order by b) from t1 group by b having MX in (3,5,7); + drop table t1; drop table t0; - diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b67ecaf492f..5ef9cf80ac8 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2333,14 +2333,6 @@ bool JOIN::make_aggr_tables_info() curr_tab->fields= &tmp_fields_list1; set_postjoin_aggr_write_func(curr_tab); - // psergey-todo: this is probably an incorrect place: - if (select_lex->window_funcs.elements) - { - curr_tab->window_funcs_step= new Window_funcs_computation; - if (curr_tab->window_funcs_step->setup(thd, &select_lex->window_funcs)) - DBUG_RETURN(true); - } - tmp_table_param.func_count= 0; tmp_table_param.field_count+= tmp_table_param.func_count; if (sort_and_group || curr_tab->table->group) @@ -2652,6 +2644,24 @@ bool JOIN::make_aggr_tables_info() skip_sort_order= true; } } + + /* + Window functions computation step should be attached to the last join_tab + that's doing aggregation. + The last join_tab reads the data from the temp. table. It also may do + - sorting + - duplicate value removal + Both of these operations are done after window function computation step. + */ + curr_tab= join_tab + top_join_tab_count + aggr_tables - 1; + if (select_lex->window_funcs.elements) + { + curr_tab->window_funcs_step= new Window_funcs_computation; + if (curr_tab->window_funcs_step->setup(thd, &select_lex->window_funcs, + curr_tab)) + DBUG_RETURN(true); + } + fields= curr_fields_list; // Reset before execution set_items_ref_array(items0); diff --git a/sql/sql_window.cc b/sql/sql_window.cc index 0352a0dfb5f..f28301c26a4 100644 --- a/sql/sql_window.cc +++ b/sql/sql_window.cc @@ -1565,11 +1565,20 @@ bool Window_func_runner::exec(JOIN *join) bool Window_funcs_computation::setup(THD *thd, - List *window_funcs) + List *window_funcs, + JOIN_TAB *tab) { List_iterator_fast it(*window_funcs); Item_window_func *item_win; Window_func_runner *runner; + + SQL_SELECT *sel= NULL; + if (tab->filesort && tab->filesort->select) + { + sel= tab->filesort->select; + DBUG_ASSERT(!sel->quick); + } + // for each window function while ((item_win= it++)) { @@ -1579,6 +1588,8 @@ bool Window_funcs_computation::setup(THD *thd, { return true; } + /* Apply the same condition that the subsequent sort will */ + runner->filesort->select= sel; win_func_runners.push_back(runner, thd->mem_root); } return false; diff --git a/sql/sql_window.h b/sql/sql_window.h index 4c88a49b6b9..042d978431f 100644 --- a/sql/sql_window.h +++ b/sql/sql_window.h @@ -171,9 +171,11 @@ public: bool exec(JOIN *join); void cleanup() { delete filesort; } + + friend class Window_funcs_computation; }; - +struct st_join_table; /* This is a "window function computation phase": a single object of this class takes care of computing all window functions in a SELECT. @@ -187,7 +189,7 @@ class Window_funcs_computation : public Sql_alloc { List win_func_runners; public: - bool setup(THD *thd, List *window_funcs); + bool setup(THD *thd, List *window_funcs, st_join_table *tab); bool exec(JOIN *join); void cleanup(); };