diff --git a/mysql-test/r/win.result b/mysql-test/r/win.result index 3d770370f65..6bd7f2c53d7 100644 --- a/mysql-test/r/win.result +++ b/mysql-test/r/win.result @@ -253,3 +253,173 @@ pk c CNT 9 2 2 10 2 1 drop table t0,t1; +# +# Resolution of window names +# +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +create table t1 (pk int, c int); +insert into t1 select a+1,1 from t0; +update t1 set c=2 where pk not in (1,2,3,4); +select * from t1; +pk c +1 1 +2 1 +3 1 +4 1 +5 2 +6 2 +7 2 +8 2 +9 2 +10 2 +select +pk, c, +count(*) over w1 as CNT +from t1 +window w1 as (partition by c order by pk +rows between 2 preceding and 2 following); +pk c CNT +1 1 3 +2 1 4 +3 1 4 +4 1 3 +5 2 3 +6 2 4 +7 2 5 +8 2 5 +9 2 4 +10 2 3 +select +pk, c, +count(*) over (w1 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c order by pk); +pk c CNT +1 1 3 +2 1 4 +3 1 4 +4 1 3 +5 2 3 +6 2 4 +7 2 5 +8 2 5 +9 2 4 +10 2 3 +select +pk, c, +count(*) over (w1 order by pk rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c); +pk c CNT +1 1 3 +2 1 4 +3 1 4 +4 1 3 +5 2 3 +6 2 4 +7 2 5 +8 2 5 +9 2 4 +10 2 3 +select +pk, c, +count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 order by pk); +pk c CNT +1 1 3 +2 1 4 +3 1 4 +4 1 3 +5 2 3 +6 2 4 +7 2 5 +8 2 5 +9 2 4 +10 2 3 +select +pk, c, +count(*) over w3 as CNT +from t1 +window +w1 as (partition by c), +w2 as (w1 order by pk), +w3 as (w2 rows between 2 preceding and 2 following); +pk c CNT +1 1 3 +2 1 4 +3 1 4 +4 1 3 +5 2 3 +6 2 4 +7 2 5 +8 2 5 +9 2 4 +10 2 3 +select +pk, c, +count(*) over w as CNT +from t1 +window w1 as (partition by c order by pk +rows between 2 preceding and 2 following); +ERROR HY000: Window specification with name 'w' is not defined +select +pk, c, +count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w1 as (order by pk); +ERROR HY000: Multiple window specifications with the same name 'w1' +select +pk, c, +count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w partition by c order by pk); +ERROR HY000: Window specification with name 'w' is not defined +select +pk, c, +count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 partition by c order by pk); +ERROR HY000: Window specification referencing another one 'w1' cannot contain partition list +select +pk, c, +count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c order by pk), w2 as (w1 order by pk); +ERROR HY000: Referenced window specification 'w1' already contains order list +select +pk, c, +count(*) over w3 as CNT +from t1 +window +w1 as (partition by c), +w2 as (w1 order by pk rows between 3 preceding and 2 following), +w3 as (w2 rows between 2 preceding and 2 following); +ERROR HY000: Referenced window specification 'w2' cannot contain window frame +select +pk, c, +count(*) over w1 as CNT +from t1 +window w1 as (partition by c order by pk +rows between unbounded following and 2 following); +ERROR HY000: Unacceptable combination of window frame bound specifications +select +pk, c, +count(*) over (w1 rows between 2 preceding and unbounded preceding) as CNT +from t1 +window w1 as (partition by c order by pk); +ERROR HY000: Unacceptable combination of window frame bound specifications +select +pk, c, +count(*) over (w1 order by pk rows between current row and 2 preceding) as CNT +from t1 +window w1 as (partition by c); +ERROR HY000: Unacceptable combination of window frame bound specifications +select +pk, c, +count(*) over (w2 rows between 2 following and current row) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 order by pk); +ERROR HY000: Unacceptable combination of window frame bound specifications +drop table t0,t1; diff --git a/mysql-test/t/win.test b/mysql-test/t/win.test index bbb706d5421..5b2e908dd3f 100644 --- a/mysql-test/t/win.test +++ b/mysql-test/t/win.test @@ -157,5 +157,134 @@ select count(*) over (partition by c order by pk rows between current row and 1 following) as CNT from t1; + drop table t0,t1; +--echo # +--echo # Resolution of window names +--echo # + +create table t0 (a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +create table t1 (pk int, c int); +insert into t1 select a+1,1 from t0; +update t1 set c=2 where pk not in (1,2,3,4); +select * from t1; + +select + pk, c, + count(*) over w1 as CNT +from t1 +window w1 as (partition by c order by pk + rows between 2 preceding and 2 following); + +select + pk, c, + count(*) over (w1 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c order by pk); + +select + pk, c, + count(*) over (w1 order by pk rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c); + +select + pk, c, + count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 order by pk); + +select + pk, c, + count(*) over w3 as CNT +from t1 +window + w1 as (partition by c), + w2 as (w1 order by pk), + w3 as (w2 rows between 2 preceding and 2 following); + +--error ER_WRONG_WINDOW_SPEC_NAME +select + pk, c, + count(*) over w as CNT +from t1 +window w1 as (partition by c order by pk + rows between 2 preceding and 2 following); + +--error ER_DUP_WINDOW_NAME +select + pk, c, + count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w1 as (order by pk); + +--error ER_WRONG_WINDOW_SPEC_NAME +select + pk, c, + count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w partition by c order by pk); + +--error ER_PARTITION_LIST_IN_REFERENCING_WINDOW_SPEC +select + pk, c, + count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 partition by c order by pk); + +--error ER_ORDER_LIST_IN_REFERENCING_WINDOW_SPEC +select + pk, c, + count(*) over (w2 rows between 2 preceding and 2 following) as CNT +from t1 +window w1 as (partition by c order by pk), w2 as (w1 order by pk); + +--error ER_WINDOW_FRAME_IN_REFERENCED_WINDOW_SPEC +select + pk, c, + count(*) over w3 as CNT +from t1 +window + w1 as (partition by c), + w2 as (w1 order by pk rows between 3 preceding and 2 following), + w3 as (w2 rows between 2 preceding and 2 following); + +--error ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS +select + pk, c, + count(*) over w1 as CNT +from t1 +window w1 as (partition by c order by pk + rows between unbounded following and 2 following); + +--error ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS +select + pk, c, + count(*) over (w1 rows between 2 preceding and unbounded preceding) as CNT +from t1 +window w1 as (partition by c order by pk); + +--error ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS +select + pk, c, + count(*) over (w1 order by pk rows between current row and 2 preceding) as CNT +from t1 +window w1 as (partition by c); + +--error ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS +select + pk, c, + count(*) over (w2 rows between 2 following and current row) as CNT +from t1 +window w1 as (partition by c), w2 as (w1 order by pk); + + +drop table t0,t1; + + + + + diff --git a/sql/item_windowfunc.cc b/sql/item_windowfunc.cc index 66aaec7280e..4bb8020b405 100644 --- a/sql/item_windowfunc.cc +++ b/sql/item_windowfunc.cc @@ -4,10 +4,48 @@ #include "sql_select.h" // test if group changed +bool +Item_window_func::resolve_window_name(THD *thd) +{ + DBUG_ASSERT(window_name != NULL && window_spec == NULL); + char *ref_name= window_name->str; + + /* !TODO: Add the code to resolve ref_name in outer queries */ + /* + First look for the deinition of the window with 'window_name' + in the current select + */ + List curr_window_specs=thd->lex->current_select->window_specs; + List_iterator_fast it(curr_window_specs); + Window_spec *win_spec; + while((win_spec= it++)) + { + char *win_spec_name= win_spec->name(); + if (win_spec_name && + my_strcasecmp(system_charset_info, ref_name, win_spec_name) == 0) + { + window_spec= win_spec; + break; + } + } + + if (!window_spec) + { + my_error(ER_WRONG_WINDOW_SPEC_NAME, MYF(0), ref_name); + return true; + } + + return false; +} + + bool Item_window_func::fix_fields(THD *thd, Item **ref) { DBUG_ASSERT(fixed == 0); + + if (window_name && resolve_window_name(thd)) + return true; /* TODO: why the last parameter is 'ref' in this call? What if window_func @@ -16,14 +54,14 @@ Item_window_func::fix_fields(THD *thd, Item **ref) object. Is this the intent? */ if (window_func->fix_fields(thd, ref)) - return TRUE; + return true; max_length= window_func->max_length; fixed= 1; force_return_blank= true; read_value_from_result_field= false; - return FALSE; + return false; } diff --git a/sql/item_windowfunc.h b/sql/item_windowfunc.h index 5e5d3bf36ed..e9a2d6936f2 100644 --- a/sql/item_windowfunc.h +++ b/sql/item_windowfunc.h @@ -386,6 +386,9 @@ public: const char* func_name() const { return "WF"; } bool fix_fields(THD *thd, Item **ref); + + bool resolve_window_name(THD *thd); + }; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 567be4d5a89..01c90e6be53 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7146,4 +7146,6 @@ ER_ORDER_LIST_IN_REFERENCING_WINDOW_SPEC eng "Referenced window specification '%s' already contains order list" ER_WINDOW_FRAME_IN_REFERENCED_WINDOW_SPEC eng "Referenced window specification '%s' cannot contain window frame" +ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS + eng "Unacceptable combination of window frame bound specifications" diff --git a/sql/sql_window.cc b/sql/sql_window.cc index aa9e3e18a16..1132a3ae607 100644 --- a/sql/sql_window.cc +++ b/sql/sql_window.cc @@ -10,44 +10,45 @@ Window_spec::check_window_names(List_iterator_fast &it) { char *name= this->name(); char *ref_name= window_reference(); - bool win_ref_is_resolved= false; it.rewind(); Window_spec *win_spec; while((win_spec= it++) && win_spec != this) { char *win_spec_name= win_spec->name(); - if (win_spec_name) + if (!win_spec_name) + break; + if (name && my_strcasecmp(system_charset_info, name, win_spec_name) == 0) { - if (name && my_strcasecmp(system_charset_info, name, win_spec_name) == 0) + my_error(ER_DUP_WINDOW_NAME, MYF(0), name); + return true; + } + if (ref_name && + my_strcasecmp(system_charset_info, ref_name, win_spec_name) == 0) + { + if (partition_list.elements) { - my_error(ER_DUP_WINDOW_NAME, MYF(0), name); + my_error(ER_PARTITION_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0), + ref_name); return true; } - if (ref_name && - my_strcasecmp(system_charset_info, ref_name, win_spec_name) == 0) + if (win_spec->order_list.elements && order_list.elements) { - if (win_spec->partition_list.elements) - { - my_error(ER_PARTITION_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0), - ref_name); - return true; - } - if (win_spec->order_list.elements && order_list.elements) - { - my_error(ER_ORDER_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0), ref_name); - return true; - } - if (win_spec->window_frame) - { - my_error(ER_WINDOW_FRAME_IN_REFERENCED_WINDOW_SPEC, MYF(0), ref_name); - return true; - } - referenced_win_spec=win_spec; - win_ref_is_resolved= true; + my_error(ER_ORDER_LIST_IN_REFERENCING_WINDOW_SPEC, MYF(0), ref_name); + return true; + } + if (win_spec->window_frame) + { + my_error(ER_WINDOW_FRAME_IN_REFERENCED_WINDOW_SPEC, MYF(0), ref_name); + return true; } + referenced_win_spec= win_spec; + if (partition_list.elements == 0) + partition_list= win_spec->partition_list; + if (order_list.elements == 0) + order_list= win_spec->order_list; } } - if (ref_name && !win_ref_is_resolved) + if (ref_name && !referenced_win_spec) { my_error(ER_WRONG_WINDOW_SPEC_NAME, MYF(0), ref_name); return true; @@ -55,6 +56,25 @@ Window_spec::check_window_names(List_iterator_fast &it) return false; } +bool +Window_frame::check_frame_bounds() +{ + if ((top_bound->is_unbounded() && + top_bound->precedence_type == Window_frame_bound::FOLLOWING) || + (bottom_bound->is_unbounded() && + bottom_bound->precedence_type == Window_frame_bound::PRECEDING) || + (top_bound->precedence_type == Window_frame_bound::CURRENT && + bottom_bound->precedence_type == Window_frame_bound::PRECEDING) || + (bottom_bound->precedence_type == Window_frame_bound::CURRENT && + top_bound->precedence_type == Window_frame_bound::FOLLOWING)) + { + my_error(ER_BAD_COMBINATION_OF_WINDOW_FRAME_BOUND_SPECS, MYF(0)); + return true; + } + + return false; +} + int setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, @@ -64,6 +84,24 @@ setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, Window_spec *win_spec; DBUG_ENTER("setup_windows"); List_iterator it(win_specs); + + /* + Move all unnamed specifications after the named ones. + We could have avoided it if we had built two separate lists for + named and unnamed specifications. + */ + uint i = 0; + uint elems= win_specs.elements; + while ((win_spec= it++) && i++ < elems) + { + if (win_spec->name() == NULL) + { + it.remove(); + win_specs.push_back(win_spec); + } + } + it.rewind(); + List_iterator_fast itp(win_specs); while ((win_spec= it++)) @@ -73,7 +111,9 @@ setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables, setup_group(thd, ref_pointer_array, tables, fields, all_fields, win_spec->partition_list.first, &hidden_group_fields) || setup_order(thd, ref_pointer_array, tables, fields, all_fields, - win_spec->order_list.first)) + win_spec->order_list.first) || + (win_spec->window_frame && + win_spec->window_frame->check_frame_bounds())) { DBUG_RETURN(1); } diff --git a/sql/sql_window.h b/sql/sql_window.h index 9f98290c227..555150248dc 100644 --- a/sql/sql_window.h +++ b/sql/sql_window.h @@ -38,7 +38,9 @@ public: Window_frame_bound(Bound_precedence_type prec_type, Item *offset_val) : precedence_type(prec_type), offset(offset_val) {} - + + bool is_unbounded() { return offset == NULL; } + }; @@ -76,6 +78,8 @@ public: : units(win_frame_units), top_bound(win_frame_top_bound), bottom_bound(win_frame_bottom_bound), exclusion(win_frame_exclusion) {} + bool check_frame_bounds(); + }; class Window_spec : public Sql_alloc @@ -99,11 +103,12 @@ class Window_spec : public Sql_alloc : window_ref(win_ref), partition_list(part_list), order_list(ord_list), window_frame(win_frame), referenced_win_spec(NULL) {} - virtual char *name() { return NULL; } + virtual char *name() { return NULL; } - bool check_window_names(List_iterator_fast &it); + bool check_window_names(List_iterator_fast &it); - char *window_reference() { return window_ref ? window_ref->str : NULL; } + + char *window_reference() { return window_ref ? window_ref->str : NULL; } }; class Window_def : public Window_spec @@ -120,7 +125,7 @@ class Window_def : public Window_spec : Window_spec(win_ref, part_list, ord_list, win_frame), window_name(win_name) {} - char *name() { return window_name->str; } + char *name() { return window_name->str; } }; diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 3fa73b72e60..6af87edea75 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -8489,19 +8489,20 @@ select_part2: opt_where_clause opt_group_clause opt_having_clause + opt_window_clause opt_order_clause opt_limit_clause opt_procedure_clause opt_into opt_select_lock_type { - if ($2 && $10) + if ($2 && $11) { /* double "INTO" clause */ my_error(ER_WRONG_USAGE, MYF(0), "INTO", "INTO"); MYSQL_YYABORT; } - if ($9 && ($2 || $10)) + if ($10 && ($2 || $11)) { /* "INTO" with "PROCEDURE ANALYSE" */ my_error(ER_WRONG_USAGE, MYF(0), "PROCEDURE", "INTO");