Commit 602e15a0 authored by Igor Babaev's avatar Igor Babaev

Changed the base class for Item_window_func from Item_result_field to

Item_func_or_sum.
Implemented method update_used_tables for class Item_findow_func.
Added the flag Item::with_window_func.
Made sure that window functions could be used only in SELECT list
and ORDER BY clause.
Added test cases that checked different illegal placements of
window functions.
parent a74e8d36
......@@ -12,9 +12,9 @@ select rank() from t1;
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'from t1' at line 1
# Attempt to use window function in the WHERE clause
select * from t1 where 1=rank() over (order by a);
ERROR HY000: Invalid use of group function
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
select * from t1 where 1>row_number() over (partition by b order by a);
ERROR HY000: Invalid use of group function
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
drop table t1;
# ########################################################################
# # Functionality tests
......@@ -303,7 +303,7 @@ pk c
9 2
10 2
select
pk, c,
pk, c,
count(*) over w1 as CNT
from t1
window w1 as (partition by c order by pk
......@@ -452,6 +452,49 @@ from t1
window w1 as (partition by c), w2 as (w1 order by pk);
ERROR HY000: Unacceptable combination of window frame bound specifications
select
pk, c
from t1 where rank() over w1 > 2
window w1 as (partition by c order by pk);
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
select
c, max(pk) as m
from t1
group by c + rank() over w1
window w1 as (order by m);
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
select
c, max(pk) as m, rank() over w1 as r
from t1
group by c+r
window w1 as (order by m);
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
select
c, max(pk) as m, rank() over w1 as r
from t1
group by c having c+r > 3
window w1 as (order by m);
ERROR HY000: Window function is allowed only in SELECT list and ORDER BY clause
select
c, max(pk) as m, rank() over w1 as r,
rank() over (partition by r+1 order by m)
from t1
group by c
window w1 as (order by m);
ERROR HY000: Window function is not allowed in window specification
select
c, max(pk) as m, rank() over w1 as r,
rank() over (partition by m order by r)
from t1
group by c
window w1 as (order by m);
ERROR HY000: Window function is not allowed in window specification
select
c, max(pk) as m, rank() over w1 as r, dense_rank() over w2 as dr
from t1
group by c
window w1 as (order by m), w2 as (partition by r order by m);
ERROR HY000: Window function is not allowed in window specification
select
pk, c,
row_number() over (partition by c order by pk
range between unbounded preceding and current row) as r
......
......@@ -20,9 +20,9 @@ select row_number() from t1;
select rank() from t1;
--echo # Attempt to use window function in the WHERE clause
--error ER_INVALID_GROUP_FUNC_USE
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select * from t1 where 1=rank() over (order by a);
--error ER_INVALID_GROUP_FUNC_USE
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select * from t1 where 1>row_number() over (partition by b order by a);
drop table t1;
......@@ -181,7 +181,7 @@ update t1 set c=2 where pk not in (1,2,3,4);
select * from t1;
select
pk, c,
pk, c,
count(*) over w1 as CNT
from t1
window w1 as (partition by c order by pk
......@@ -289,6 +289,56 @@ select
from t1
window w1 as (partition by c), w2 as (w1 order by pk);
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select
pk, c
from t1 where rank() over w1 > 2
window w1 as (partition by c order by pk);
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select
c, max(pk) as m
from t1
group by c + rank() over w1
window w1 as (order by m);
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select
c, max(pk) as m, rank() over w1 as r
from t1
group by c+r
window w1 as (order by m);
--error ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
select
c, max(pk) as m, rank() over w1 as r
from t1
group by c having c+r > 3
window w1 as (order by m);
--error ER_WINDOW_FUNCTION_IN_WINDOW_SPEC
select
c, max(pk) as m, rank() over w1 as r,
rank() over (partition by r+1 order by m)
from t1
group by c
window w1 as (order by m);
--error ER_WINDOW_FUNCTION_IN_WINDOW_SPEC
select
c, max(pk) as m, rank() over w1 as r,
rank() over (partition by m order by r)
from t1
group by c
window w1 as (order by m);
--error ER_WINDOW_FUNCTION_IN_WINDOW_SPEC
select
c, max(pk) as m, rank() over w1 as r, dense_rank() over w2 as dr
from t1
group by c
window w1 as (order by m), w2 as (partition by r order by m);
--error ER_NOT_ALLOWED_WINDOW_FRAME
select
pk, c,
......
......@@ -455,7 +455,7 @@ Item::Item(THD *thd):
{
DBUG_ASSERT(thd);
marker= 0;
maybe_null=null_value=with_sum_func=with_field=0;
maybe_null=null_value=with_sum_func=with_window_func=with_field=0;
in_rollup= 0;
with_subselect= 0;
/* Initially this item is not attached to any JOIN_TAB. */
......@@ -500,6 +500,7 @@ Item::Item(THD *thd, Item *item):
in_rollup(item->in_rollup),
null_value(item->null_value),
with_sum_func(item->with_sum_func),
with_window_func(item->with_window_func),
with_field(item->with_field),
fixed(item->fixed),
is_autogenerated_name(item->is_autogenerated_name),
......@@ -6913,6 +6914,7 @@ void Item_ref::set_properties()
split_sum_func() doesn't try to change the reference.
*/
with_sum_func= (*ref)->with_sum_func;
with_window_func= (*ref)->with_window_func;
with_field= (*ref)->with_field;
fixed= 1;
if (alias_name_used)
......
......@@ -697,6 +697,7 @@ class Item: public Value_source,
of a query with ROLLUP */
bool null_value; /* if item is null */
bool with_sum_func; /* True if item contains a sum func */
bool with_window_func; /* True if item contains a window func */
/**
True if any item except Item_sum contains a field. Set during parsing.
*/
......
......@@ -132,6 +132,7 @@ void Item_func::sync_with_sum_func_and_with_field(List<Item> &list)
while ((item= li++))
{
with_sum_func|= item->with_sum_func;
with_window_func|= item->with_window_func;
with_field|= item->with_field;
}
}
......@@ -226,6 +227,7 @@ Item_func::fix_fields(THD *thd, Item **ref)
maybe_null=1;
with_sum_func= with_sum_func || item->with_sum_func;
with_window_func= with_window_func || item->with_window_func;
with_field= with_field || item->with_field;
used_tables_and_const_cache_join(item);
with_subselect|= item->has_subquery();
......
......@@ -45,23 +45,52 @@ Item_window_func::resolve_window_name(THD *thd)
}
void
Item_window_func::update_used_tables()
{
used_tables_cache= 0;
window_func()->update_used_tables();
used_tables_cache|= window_func()->used_tables();
for (ORDER *ord= window_spec->partition_list->first; ord; ord=ord->next)
{
Item *item= *ord->item;
item->update_used_tables();
used_tables_cache|= item->used_tables();
}
for (ORDER *ord= window_spec->order_list->first; ord; ord=ord->next)
{
Item *item= *ord->item;
item->update_used_tables();
used_tables_cache|= item->used_tables();
}
}
bool
Item_window_func::fix_fields(THD *thd, Item **ref)
{
DBUG_ASSERT(fixed == 0);
enum_parsing_place place= thd->lex->current_select->parsing_place;
if (!(place == SELECT_LIST || place == IN_ORDER_BY))
{
my_error(ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION, MYF(0));
return true;
}
if (window_name && resolve_window_name(thd))
return true;
if (window_spec->window_frame && is_frame_prohibited())
{
my_error(ER_NOT_ALLOWED_WINDOW_FRAME, MYF(0), window_func->func_name());
my_error(ER_NOT_ALLOWED_WINDOW_FRAME, MYF(0), window_func()->func_name());
return true;
}
if (window_spec->order_list->elements == 0 && is_order_list_mandatory())
{
my_error(ER_NO_ORDER_LIST_IN_WINDOW_SPEC, MYF(0), window_func->func_name());
my_error(ER_NO_ORDER_LIST_IN_WINDOW_SPEC, MYF(0), window_func()->func_name());
return true;
}
/*
......@@ -70,13 +99,17 @@ Item_window_func::fix_fields(THD *thd, Item **ref)
This will substitute *this (an Item_window_func object) with Item_sum
object. Is this the intent?
*/
if (window_func->fix_fields(thd, ref))
if (window_func()->fix_fields(thd, ref))
return true;
const_item_cache= false;
with_window_func= true;
with_sum_func= false;
fix_length_and_dec();
max_length= window_func->max_length;
maybe_null= window_func->maybe_null;
max_length= window_func()->max_length;
maybe_null= window_func()->maybe_null;
fixed= 1;
force_return_blank= true;
......@@ -113,9 +146,9 @@ Item_window_func::fix_fields(THD *thd, Item **ref)
void Item_window_func::split_sum_func(THD *thd, Ref_ptr_array ref_pointer_array,
List<Item> &fields, uint flags)
{
for (uint i=0; i < window_func->argument_count(); i++)
for (uint i=0; i < window_func()->argument_count(); i++)
{
Item **p_item= &window_func->arguments()[i];
Item **p_item= &window_func()->arguments()[i];
(*p_item)->split_sum_func2(thd, ref_pointer_array, fields, p_item, flags);
}
}
......@@ -138,7 +171,7 @@ void Item_window_func::setup_partition_border_check(THD *thd)
Cached_item *tmp= new_Cached_item(thd, curr->item[0], TRUE);
partition_fields.push_back(tmp);
}
window_func->setup_window_func(thd, window_spec);
window_func()->setup_window_func(thd, window_spec);
}
......@@ -196,9 +229,9 @@ void Item_window_func::advance_window()
if (changed > -1)
{
/* Next partition */
window_func->clear();
window_func()->clear();
}
window_func->add();
window_func()->add();
}
bool Item_sum_percent_rank::add()
......
......@@ -408,37 +408,40 @@ class Item_sum_cume_dist: public Item_sum_percent_rank
};
class Item_window_func : public Item_result_field
class Item_window_func : public Item_func_or_sum
{
/* Window function parameters as we've got them from the parser */
public:
Item_sum *window_func;
LEX_STRING *window_name;
public:
Window_spec *window_spec;
/*
This stores the data bout the partition we're currently in.
This stores the data about the partition we're currently in.
advance_window() uses this to tell when we've left one partition and
entered another.
entered another
*/
List<Cached_item> partition_fields;
public:
Item_window_func(THD *thd, Item_sum *win_func, LEX_STRING *win_name)
: Item_result_field(thd), window_func(win_func),
: Item_func_or_sum(thd, (Item *) win_func),
window_name(win_name), window_spec(NULL),
force_return_blank(true),
read_value_from_result_field(false) {}
read_value_from_result_field(false) {}
Item_window_func(THD *thd, Item_sum *win_func, Window_spec *win_spec)
: Item_result_field(thd), window_func(win_func),
: Item_func_or_sum(thd, (Item *) win_func),
window_name(NULL), window_spec(win_spec),
force_return_blank(true),
read_value_from_result_field(false) {}
read_value_from_result_field(false) {}
Item_sum *window_func() { return (Item_sum *) args[0]; }
void update_used_tables();
bool is_frame_prohibited()
{
switch (window_func->sum_func()) {
switch (window_func()->sum_func()) {
case Item_sum::ROW_NUMBER_FUNC:
case Item_sum::RANK_FUNC:
case Item_sum::DENSE_RANK_FUNC:
......@@ -452,7 +455,7 @@ class Item_window_func : public Item_result_field
bool is_order_list_mandatory()
{
switch (window_func->sum_func()) {
switch (window_func()->sum_func()) {
case Item_sum::RANK_FUNC:
case Item_sum::DENSE_RANK_FUNC:
case Item_sum::PERCENT_RANK_FUNC:
......@@ -472,7 +475,10 @@ class Item_window_func : public Item_result_field
void advance_window();
int check_partition_bound();
enum_field_types field_type() const { return window_func->field_type(); }
enum_field_types field_type() const
{
return ((Item_sum *) args[0])->field_type();
}
enum Item::Type type() const { return Item::WINDOW_FUNC_ITEM; }
/*
......@@ -485,7 +491,7 @@ class Item_window_func : public Item_result_field
- Phase#2: executor does the scan in {PARTITION, ORDER BY} order of this
window function. It calls appropriate methods to inform the window
function about rows entering/leaving the window.
It calls window_func->val_int() so that current window function value
It calls window_func()->val_int() so that current window function value
can be saved and stored in the temp.table.
- Phase#3: the temporary table is read and passed to query output.
......@@ -521,8 +527,8 @@ class Item_window_func : public Item_result_field
}
else
{
res= window_func->val_real();
null_value= window_func->null_value;
res= window_func()->val_real();
null_value= window_func()->null_value;
}
return res;
}
......@@ -542,8 +548,8 @@ class Item_window_func : public Item_result_field
}
else
{
res= window_func->val_int();
null_value= window_func->null_value;
res= window_func()->val_int();
null_value= window_func()->null_value;
}
return res;
}
......@@ -566,8 +572,8 @@ class Item_window_func : public Item_result_field
}
else
{
res= window_func->val_str(str);
null_value= window_func->null_value;
res= window_func()->val_str(str);
null_value= window_func()->null_value;
}
return res;
}
......@@ -590,8 +596,8 @@ class Item_window_func : public Item_result_field
}
else
{
res= window_func->val_decimal(dec);
null_value= window_func->null_value;
res= window_func()->val_decimal(dec);
null_value= window_func()->null_value;
}
return res;
}
......@@ -600,7 +606,7 @@ class Item_window_func : public Item_result_field
List<Item> &fields, uint flags);
void fix_length_and_dec()
{
decimals = window_func->decimals;
decimals = window_func()->decimals;
}
const char* func_name() const { return "WF"; }
......
......@@ -7148,6 +7148,10 @@ 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"
ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION
eng "Window function is allowed only in SELECT list and ORDER BY clause"
ER_WINDOW_FUNCTION_IN_WINDOW_SPEC
eng "Window function is not allowed in window specification"
ER_NOT_ALLOWED_WINDOW_FRAME
eng "Window frame is not allowed with '%s'"
ER_NO_ORDER_LIST_IN_WINDOW_SPEC
......
......@@ -1935,6 +1935,7 @@ void st_select_lex::init_query()
m_non_agg_field_used= false;
m_agg_func_used= false;
window_specs.empty();
window_funcs.empty();
}
void st_select_lex::init_select()
......
......@@ -48,6 +48,7 @@ class sys_var;
class Item_func_match;
class File_parser;
class Key_part_spec;
class Item_window_func;
struct sql_digest_state;
#define ALLOC_ROOT_SET 1024
......@@ -1081,6 +1082,11 @@ class st_select_lex: public st_select_lex_node
SQL_I_List<ORDER> win_partition_list,
SQL_I_List<ORDER> win_order_list,
Window_frame *win_frame);
List<Item_window_func> window_funcs;
bool add_window_func(Item_window_func *win_func)
{
return window_funcs.push_back(win_func);
}
private:
bool m_non_agg_field_used;
......
......@@ -344,6 +344,7 @@ enum enum_parsing_place
IN_WHERE,
IN_ON,
IN_GROUP_BY,
IN_ORDER_BY,
PARSING_PLACE_SIZE /* always should be the last */
};
......
......@@ -622,14 +622,16 @@ setup_without_group(THD *thd, Ref_ptr_array ref_pointer_array,
ORDER *order,
ORDER *group,
List<Window_spec> &win_specs,
List<Item_window_func> &win_funcs,
bool *hidden_group_fields,
uint *reserved)
{
int res;
enum_parsing_place save_place;
st_select_lex *const select= thd->lex->current_select;
nesting_map save_allow_sum_func= thd->lex->allow_sum_func;
/*
Need to save the value, so we can turn off only any new non_agg_field_used
Need to stave the value, so we can turn off only any new non_agg_field_used
additions coming from the WHERE
*/
const bool saved_non_agg_field_used= select->non_agg_field_used();
......@@ -649,14 +651,21 @@ setup_without_group(THD *thd, Ref_ptr_array ref_pointer_array,
select->set_non_agg_field_used(saved_non_agg_field_used);
thd->lex->allow_sum_func|= (nesting_map)1 << select->nest_level;
save_place= thd->lex->current_select->parsing_place;
thd->lex->current_select->parsing_place= IN_ORDER_BY;
res= res || setup_order(thd, ref_pointer_array, tables, fields, all_fields,
order);
thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
thd->lex->current_select->parsing_place= save_place;
thd->lex->allow_sum_func&= ~((nesting_map)1 << select->nest_level);
save_place= thd->lex->current_select->parsing_place;
thd->lex->current_select->parsing_place= IN_GROUP_BY;
res= res || setup_group(thd, ref_pointer_array, tables, fields, all_fields,
group, hidden_group_fields);
thd->lex->current_select->parsing_place= save_place;
thd->lex->allow_sum_func|= (nesting_map)1 << select->nest_level;
res= res || setup_windows(thd, ref_pointer_array, tables, fields, all_fields,
win_specs);
win_specs, win_funcs);
thd->lex->allow_sum_func= save_allow_sum_func;
DBUG_RETURN(res);
}
......@@ -791,14 +800,18 @@ JOIN::prepare(TABLE_LIST *tables_init,
ref_ptrs= ref_ptr_array_slice(0);
enum_parsing_place save_place= thd->lex->current_select->parsing_place;
thd->lex->current_select->parsing_place= SELECT_LIST;
if (setup_fields(thd, ref_ptrs, fields_list, MARK_COLUMNS_READ,
&all_fields, 1))
DBUG_RETURN(-1);
thd->lex->current_select->parsing_place= save_place;
if (setup_without_group(thd, ref_ptrs, tables_list,
select_lex->leaf_tables, fields_list,
all_fields, &conds, order, group_list,
select_lex->window_specs,
select_lex->window_funcs,
&hidden_group_fields,
&select_lex->select_n_reserved))
DBUG_RETURN(-1);
......@@ -834,6 +847,12 @@ JOIN::prepare(TABLE_LIST *tables_init,
if (having_fix_rc || thd->is_error())
DBUG_RETURN(-1); /* purecov: inspected */
thd->lex->allow_sum_func= save_allow_sum_func;
if (having->with_window_func)
{
my_error(ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION, MYF(0));
DBUG_RETURN(-1);
}
}
int res= check_and_do_in_subquery_rewrites(this);
......@@ -21841,13 +21860,19 @@ find_order_in_list(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables
int setup_order(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields, ORDER *order)
{
{
enum_parsing_place parsing_place= thd->lex->current_select->parsing_place;
thd->where="order clause";
for (; order; order=order->next)
{
if (find_order_in_list(thd, ref_pointer_array, tables, order, fields,
all_fields, FALSE))
return 1;
if ((*order->item)->with_window_func && parsing_place != IN_ORDER_BY)
{
my_error(ER_WINDOW_FUNCTION_IN_WINDOW_SPEC, MYF(0));
return 1;
}
}
return 0;
}
......@@ -21884,6 +21909,7 @@ setup_group(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields, ORDER *order,
bool *hidden_group_fields)
{
enum_parsing_place parsing_place= thd->lex->current_select->parsing_place;
*hidden_group_fields=0;
ORDER *ord;
......@@ -21893,22 +21919,26 @@ setup_group(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
uint org_fields=all_fields.elements;
thd->where="group statement";
enum_parsing_place save_place= thd->lex->current_select->parsing_place;
thd->lex->current_select->parsing_place= IN_GROUP_BY;
for (ord= order; ord; ord= ord->next)
{
if (find_order_in_list(thd, ref_pointer_array, tables, ord, fields,
all_fields, TRUE))
return 1;
(*ord->item)->marker= UNDEF_POS; /* Mark found */
if ((*ord->item)->with_sum_func)
if ((*ord->item)->with_sum_func && parsing_place == IN_GROUP_BY)
{
my_error(ER_WRONG_GROUP_FIELD, MYF(0), (*ord->item)->full_name());
return 1;
}
if ((*ord->item)->with_window_func)
{
if (parsing_place == IN_GROUP_BY)
my_error(ER_WRONG_PLACEMENT_OF_WINDOW_FUNCTION, MYF(0));
else
my_error(ER_WINDOW_FUNCTION_IN_WINDOW_SPEC, MYF(0));
return 1;
}
}
thd->lex->current_select->parsing_place= save_place;
if (thd->variables.sql_mode & MODE_ONLY_FULL_GROUP_BY)
{
/*
......
......@@ -83,7 +83,7 @@ Window_frame::check_frame_bounds()
int
setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields,
List<Window_spec> &win_specs)
List<Window_spec> &win_specs, List<Item_window_func> &win_funcs)
{
Window_spec *win_spec;
DBUG_ENTER("setup_windows");
......@@ -207,6 +207,14 @@ setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
}
}
}
List_iterator_fast<Item_window_func> li(win_funcs);
Item_window_func *win_func_item;
while ((win_func_item= li++))
{
win_func_item->update_used_tables();
}
DBUG_RETURN(0);
}
......@@ -1289,7 +1297,7 @@ bool compute_window_func_with_frames(Item_window_func *item_win,
Frame_cursor *top_bound;
Frame_cursor *bottom_bound;
Item_sum *sum_func= item_win->window_func;
Item_sum *sum_func= item_win->window_func();
/* This algorithm doesn't support DISTINCT aggregator */
sum_func->set_aggregator(Aggregator::SIMPLE_AGGREGATOR);
......@@ -1382,7 +1390,7 @@ bool compute_two_pass_window_functions(Item_window_func *item_win,
bool first_row= true;
clone_read_record(info, info2);
Item_sum_window_with_context *window_func=
static_cast<Item_sum_window_with_context *>(item_win->window_func);
static_cast<Item_sum_window_with_context *>(item_win->window_func());
uchar *rowid_buf= (uchar*) my_malloc(table->file->ref_length, MYF(0));
is_error= window_func->create_window_context();
......@@ -1653,7 +1661,7 @@ bool JOIN::process_window_functions(List<Item> *curr_fields_list)
item_win->setup_partition_border_check(thd);
Item_sum::Sumfunctype type= item_win->window_func->sum_func();
Item_sum::Sumfunctype type= item_win->window_func()->sum_func();
switch (type) {
case Item_sum::ROW_NUMBER_FUNC:
case Item_sum::RANK_FUNC:
......
......@@ -5,6 +5,8 @@
#include "my_global.h"
#include "item.h"
class Item_window_func;
/*
Window functions module.
......@@ -130,6 +132,6 @@ class Window_def : public Window_spec
int setup_windows(THD *thd, Ref_ptr_array ref_pointer_array, TABLE_LIST *tables,
List<Item> &fields, List<Item> &all_fields,
List<Window_spec> &win_specs);
List<Window_spec> &win_specs, List<Item_window_func> &win_funcs);
#endif /* SQL_WINDOW_INCLUDED */
......@@ -10411,6 +10411,8 @@ window_func_expr:
thd->lex->win_spec);
if ($$ == NULL)
MYSQL_YYABORT;
if (Select->add_window_func((Item_window_func *) $$))
MYSQL_YYABORT;
}
;
......
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