mirror of
https://github.com/MariaDB/server.git
synced 2025-08-07 00:04:31 +03:00
Bug #21809: Error 1356 while selecting from view with grouping though underlying
select OK. The SQL parser was using Item::name to transfer user defined function attributes to the user defined function (udf). It was not distinguishing between user defined function call arguments and stored procedure call arguments. Setting Item::name was causing Item_ref::print() method to print the argument as quoted identifiers and caused views that reference aggregate functions as udf call arguments (and rely on Item::print() for the text of the view to store) to throw an undefined identifier error. Overloaded Item_ref::print to print aggregate functions as such when printing the references to aggregate functions taken out of context by split_sum_func2() Fixed the parser to properly detect using AS clause in stored procedure arguments as an error. Fixed printing the arguments of udf call to print properly the udf attribute. mysql-test/r/udf.result: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - test cases mysql-test/t/udf.test: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - test cases sql/item.cc: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - Don't print the refs to SUM functions as refs. sql/item_func.cc: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - print the aliases in the udf calls sql/item_func.h: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - print the aliases in the udf calls sql/sql_lex.cc: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - disable aliases for arguments in stored routine calls sql/sql_lex.h: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - disable aliases for arguments in stored routine calls sql/sql_yacc.yy: Bug #21809: Error 1356 while selecting from view with grouping though underlying select OK. - disable aliases for arguments in stored routine calls - fix bison duplicate symbol warnings
This commit is contained in:
@@ -105,6 +105,85 @@ explain select myfunc_int(f1) from t1 order by 1;
|
|||||||
id select_type table type possible_keys key key_len ref rows Extra
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
CREATE TABLE t1(a INT, b INT);
|
||||||
|
INSERT INTO t1 values (1,1),(2,2);
|
||||||
|
CREATE FUNCTION fn(a int) RETURNS int DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
RETURN a;
|
||||||
|
END
|
||||||
|
||
|
||||||
|
CREATE VIEW v1 AS SELECT a, fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT myfunc_int(a AS attr_name) FROM t1;
|
||||||
|
myfunc_int(a AS attr_name)
|
||||||
|
1
|
||||||
|
2
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(a AS attr_name) FROM t1;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select myfunc_int(`test`.`t1`.`a` AS `attr_name`) AS `myfunc_int(a AS attr_name)` from `test`.`t1`
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(a) FROM t1;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select myfunc_int(`test`.`t1`.`a` AS `a`) AS `myfunc_int(a)` from `test`.`t1`
|
||||||
|
SELECT a,c FROM v1;
|
||||||
|
a c
|
||||||
|
1 1
|
||||||
|
2 2
|
||||||
|
SELECT a, fn(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx) as c FROM t1 GROUP BY a' at line 1
|
||||||
|
SELECT myfunc_int(fn(MIN(b) xx)) as c FROM t1 GROUP BY a;
|
||||||
|
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx)) as c FROM t1 GROUP BY a' at line 1
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b) xx)) as c FROM t1 GROUP BY a;
|
||||||
|
ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xx)) as c FROM t1 GROUP BY a' at line 1
|
||||||
|
SELECT myfunc_int(fn(MIN(b)) xx) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b)) xx) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select myfunc_int(min(`test`.`t1`.`b`) AS `xx`) AS `c` from `test`.`t1` group by `test`.`t1`.`a`
|
||||||
|
EXPLAIN EXTENDED SELECT test.fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select `test`.`fn`(min(`test`.`t1`.`b`)) AS `c` from `test`.`t1` group by `test`.`t1`.`a`
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select myfunc_int(`test`.`fn`(min(`test`.`t1`.`b`)) AS `fn(MIN(b))`) AS `c` from `test`.`t1` group by `test`.`t1`.`a`
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(test.fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
id select_type table type possible_keys key key_len ref rows Extra
|
||||||
|
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using temporary; Using filesort
|
||||||
|
Warnings:
|
||||||
|
Note 1003 select myfunc_int(`test`.`fn`(min(`test`.`t1`.`b`)) AS `test.fn(MIN(b))`) AS `c` from `test`.`t1` group by `test`.`t1`.`a`
|
||||||
|
SELECT myfunc_int(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
SELECT test.fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
SELECT myfunc_int(fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
c
|
||||||
|
1
|
||||||
|
2
|
||||||
|
DROP VIEW v1;
|
||||||
|
DROP TABLE t1;
|
||||||
|
DROP FUNCTION fn;
|
||||||
End of 5.0 tests.
|
End of 5.0 tests.
|
||||||
DROP FUNCTION metaphon;
|
DROP FUNCTION metaphon;
|
||||||
DROP FUNCTION myfunc_double;
|
DROP FUNCTION myfunc_double;
|
||||||
|
@@ -127,6 +127,50 @@ create table t1(f1 int);
|
|||||||
insert into t1 values(1),(2);
|
insert into t1 values(1),(2);
|
||||||
explain select myfunc_int(f1) from t1 order by 1;
|
explain select myfunc_int(f1) from t1 order by 1;
|
||||||
drop table t1;
|
drop table t1;
|
||||||
|
|
||||||
|
#
|
||||||
|
# Bug #21809: Error 1356 while selecting from view with grouping though
|
||||||
|
# underlying select OK.
|
||||||
|
#
|
||||||
|
CREATE TABLE t1(a INT, b INT); INSERT INTO t1 values (1,1),(2,2);
|
||||||
|
|
||||||
|
DELIMITER ||;
|
||||||
|
CREATE FUNCTION fn(a int) RETURNS int DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
RETURN a;
|
||||||
|
END
|
||||||
|
||
|
||||||
|
DELIMITER ;||
|
||||||
|
|
||||||
|
CREATE VIEW v1 AS SELECT a, fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
|
||||||
|
SELECT myfunc_int(a AS attr_name) FROM t1;
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(a AS attr_name) FROM t1;
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(a) FROM t1;
|
||||||
|
SELECT a,c FROM v1;
|
||||||
|
|
||||||
|
--error ER_PARSE_ERROR
|
||||||
|
SELECT a, fn(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
--error ER_PARSE_ERROR
|
||||||
|
SELECT myfunc_int(fn(MIN(b) xx)) as c FROM t1 GROUP BY a;
|
||||||
|
--error ER_PARSE_ERROR
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b) xx)) as c FROM t1 GROUP BY a;
|
||||||
|
|
||||||
|
SELECT myfunc_int(fn(MIN(b)) xx) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b)) xx) as c FROM t1 GROUP BY a;
|
||||||
|
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
EXPLAIN EXTENDED SELECT test.fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
EXPLAIN EXTENDED SELECT myfunc_int(test.fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT myfunc_int(MIN(b) xx) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT test.fn(MIN(b)) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT myfunc_int(fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
SELECT myfunc_int(test.fn(MIN(b))) as c FROM t1 GROUP BY a;
|
||||||
|
DROP VIEW v1;
|
||||||
|
DROP TABLE t1;
|
||||||
|
DROP FUNCTION fn;
|
||||||
|
|
||||||
--echo End of 5.0 tests.
|
--echo End of 5.0 tests.
|
||||||
|
|
||||||
#
|
#
|
||||||
|
24
sql/item.cc
24
sql/item.cc
@@ -1147,6 +1147,28 @@ void Item_name_const::print(String *str)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
need a special class to adjust printing : references to aggregate functions
|
||||||
|
must not be printed as refs because the aggregate functions that are added to
|
||||||
|
the front of select list are not printed as well.
|
||||||
|
*/
|
||||||
|
class Item_aggregate_ref : public Item_ref
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Item_aggregate_ref(Name_resolution_context *context_arg, Item **item,
|
||||||
|
const char *table_name_arg, const char *field_name_arg)
|
||||||
|
:Item_ref(context_arg, item, table_name_arg, field_name_arg) {}
|
||||||
|
|
||||||
|
void print (String *str)
|
||||||
|
{
|
||||||
|
if (ref)
|
||||||
|
(*ref)->print(str);
|
||||||
|
else
|
||||||
|
Item_ident::print(str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Move SUM items out from item tree and replace with reference
|
Move SUM items out from item tree and replace with reference
|
||||||
|
|
||||||
@@ -1200,7 +1222,7 @@ void Item::split_sum_func2(THD *thd, Item **ref_pointer_array,
|
|||||||
Item *new_item, *real_itm= real_item();
|
Item *new_item, *real_itm= real_item();
|
||||||
|
|
||||||
ref_pointer_array[el]= real_itm;
|
ref_pointer_array[el]= real_itm;
|
||||||
if (!(new_item= new Item_ref(&thd->lex->current_select->context,
|
if (!(new_item= new Item_aggregate_ref(&thd->lex->current_select->context,
|
||||||
ref_pointer_array + el, 0, name)))
|
ref_pointer_array + el, 0, name)))
|
||||||
return; // fatal_error is set
|
return; // fatal_error is set
|
||||||
fields.push_front(real_itm);
|
fields.push_front(real_itm);
|
||||||
|
@@ -2869,6 +2869,20 @@ void Item_udf_func::cleanup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Item_udf_func::print(String *str)
|
||||||
|
{
|
||||||
|
str->append(func_name());
|
||||||
|
str->append('(');
|
||||||
|
for (uint i=0 ; i < arg_count ; i++)
|
||||||
|
{
|
||||||
|
if (i != 0)
|
||||||
|
str->append(',');
|
||||||
|
args[i]->print_item_w_name(str);
|
||||||
|
}
|
||||||
|
str->append(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
double Item_func_udf_float::val_real()
|
double Item_func_udf_float::val_real()
|
||||||
{
|
{
|
||||||
DBUG_ASSERT(fixed == 1);
|
DBUG_ASSERT(fixed == 1);
|
||||||
|
@@ -951,6 +951,7 @@ public:
|
|||||||
Item_result result_type () const { return udf.result_type(); }
|
Item_result result_type () const { return udf.result_type(); }
|
||||||
table_map not_null_tables() const { return 0; }
|
table_map not_null_tables() const { return 0; }
|
||||||
bool is_expensive() { return 1; }
|
bool is_expensive() { return 1; }
|
||||||
|
void print(String *str);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@@ -163,6 +163,7 @@ void lex_start(THD *thd, uchar *buf,uint length)
|
|||||||
lex->select_lex.ftfunc_list= &lex->select_lex.ftfunc_list_alloc;
|
lex->select_lex.ftfunc_list= &lex->select_lex.ftfunc_list_alloc;
|
||||||
lex->select_lex.group_list.empty();
|
lex->select_lex.group_list.empty();
|
||||||
lex->select_lex.order_list.empty();
|
lex->select_lex.order_list.empty();
|
||||||
|
lex->select_lex.udf_list.empty();
|
||||||
lex->current_select= &lex->select_lex;
|
lex->current_select= &lex->select_lex;
|
||||||
lex->yacc_yyss=lex->yacc_yyvs=0;
|
lex->yacc_yyss=lex->yacc_yyvs=0;
|
||||||
lex->ignore_space=test(thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
lex->ignore_space=test(thd->variables.sql_mode & MODE_IGNORE_SPACE);
|
||||||
@@ -1166,6 +1167,7 @@ void st_select_lex::init_select()
|
|||||||
braces= 0;
|
braces= 0;
|
||||||
when_list.empty();
|
when_list.empty();
|
||||||
expr_list.empty();
|
expr_list.empty();
|
||||||
|
udf_list.empty();
|
||||||
interval_list.empty();
|
interval_list.empty();
|
||||||
use_index.empty();
|
use_index.empty();
|
||||||
ftfunc_list_alloc.empty();
|
ftfunc_list_alloc.empty();
|
||||||
|
@@ -580,6 +580,8 @@ public:
|
|||||||
/* exclude this select from check of unique_table() */
|
/* exclude this select from check of unique_table() */
|
||||||
bool exclude_from_table_unique_test;
|
bool exclude_from_table_unique_test;
|
||||||
|
|
||||||
|
List<udf_func> udf_list; /* udf function calls stack */
|
||||||
|
|
||||||
void init_query();
|
void init_query();
|
||||||
void init_select();
|
void init_select();
|
||||||
st_select_lex_unit* master_unit();
|
st_select_lex_unit* master_unit();
|
||||||
|
@@ -650,11 +650,8 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
|
|||||||
%token UNIX_TIMESTAMP
|
%token UNIX_TIMESTAMP
|
||||||
%token UNKNOWN_SYM
|
%token UNKNOWN_SYM
|
||||||
%token UNLOCK_SYM
|
%token UNLOCK_SYM
|
||||||
%token UNLOCK_SYM
|
|
||||||
%token UNSIGNED
|
%token UNSIGNED
|
||||||
%token UNTIL_SYM
|
%token UNTIL_SYM
|
||||||
%token UNTIL_SYM
|
|
||||||
%token UPDATE_SYM
|
|
||||||
%token UPDATE_SYM
|
%token UPDATE_SYM
|
||||||
%token UPGRADE_SYM
|
%token UPGRADE_SYM
|
||||||
%token USAGE
|
%token USAGE
|
||||||
@@ -764,7 +761,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize);
|
|||||||
|
|
||||||
%type <item_list>
|
%type <item_list>
|
||||||
expr_list udf_expr_list udf_expr_list2 when_list
|
expr_list udf_expr_list udf_expr_list2 when_list
|
||||||
ident_list ident_list_arg
|
ident_list ident_list_arg opt_expr_list
|
||||||
|
|
||||||
%type <var_type>
|
%type <var_type>
|
||||||
option_type opt_var_type opt_var_ident_type
|
option_type opt_var_type opt_var_ident_type
|
||||||
@@ -4724,7 +4721,7 @@ simple_expr:
|
|||||||
{ $$= new Item_func_trim($5,$3); }
|
{ $$= new Item_func_trim($5,$3); }
|
||||||
| TRUNCATE_SYM '(' expr ',' expr ')'
|
| TRUNCATE_SYM '(' expr ',' expr ')'
|
||||||
{ $$= new Item_func_round($3,$5,1); }
|
{ $$= new Item_func_round($3,$5,1); }
|
||||||
| ident '.' ident '(' udf_expr_list ')'
|
| ident '.' ident '(' opt_expr_list ')'
|
||||||
{
|
{
|
||||||
LEX *lex= Lex;
|
LEX *lex= Lex;
|
||||||
sp_name *name= new sp_name($1, $3);
|
sp_name *name= new sp_name($1, $3);
|
||||||
@@ -4741,27 +4738,27 @@ simple_expr:
|
|||||||
{
|
{
|
||||||
#ifdef HAVE_DLOPEN
|
#ifdef HAVE_DLOPEN
|
||||||
udf_func *udf= 0;
|
udf_func *udf= 0;
|
||||||
|
LEX *lex= Lex;
|
||||||
if (using_udf_functions &&
|
if (using_udf_functions &&
|
||||||
(udf= find_udf($1.str, $1.length)) &&
|
(udf= find_udf($1.str, $1.length)) &&
|
||||||
udf->type == UDFTYPE_AGGREGATE)
|
udf->type == UDFTYPE_AGGREGATE)
|
||||||
{
|
{
|
||||||
LEX *lex= Lex;
|
|
||||||
if (lex->current_select->inc_in_sum_expr())
|
if (lex->current_select->inc_in_sum_expr())
|
||||||
{
|
{
|
||||||
yyerror(ER(ER_SYNTAX_ERROR));
|
yyerror(ER(ER_SYNTAX_ERROR));
|
||||||
YYABORT;
|
YYABORT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$<udf>$= udf;
|
lex->current_select->udf_list.push_front(udf);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
udf_expr_list ')'
|
udf_expr_list ')'
|
||||||
{
|
{
|
||||||
#ifdef HAVE_DLOPEN
|
#ifdef HAVE_DLOPEN
|
||||||
udf_func *udf= $<udf>3;
|
udf_func *udf;
|
||||||
SELECT_LEX *sel= Select;
|
LEX *lex= Lex;
|
||||||
|
|
||||||
if (udf)
|
if (NULL != (udf= lex->current_select->udf_list.pop()))
|
||||||
{
|
{
|
||||||
if (udf->type == UDFTYPE_AGGREGATE)
|
if (udf->type == UDFTYPE_AGGREGATE)
|
||||||
Select->in_sum_expr--;
|
Select->in_sum_expr--;
|
||||||
@@ -4988,12 +4985,29 @@ udf_expr_list3:
|
|||||||
udf_expr:
|
udf_expr:
|
||||||
remember_name expr remember_end select_alias
|
remember_name expr remember_end select_alias
|
||||||
{
|
{
|
||||||
|
udf_func *udf= Select->udf_list.head();
|
||||||
|
/*
|
||||||
|
Use Item::name as a storage for the attribute value of user
|
||||||
|
defined function argument. It is safe to use Item::name
|
||||||
|
because the syntax will not allow having an explicit name here.
|
||||||
|
See WL#1017 re. udf attributes.
|
||||||
|
*/
|
||||||
if ($4.str)
|
if ($4.str)
|
||||||
{
|
{
|
||||||
|
if (!udf)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Disallow using AS to specify explicit names for the arguments
|
||||||
|
of stored routine calls
|
||||||
|
*/
|
||||||
|
yyerror(ER(ER_SYNTAX_ERROR));
|
||||||
|
YYABORT;
|
||||||
|
}
|
||||||
|
|
||||||
$2->is_autogenerated_name= FALSE;
|
$2->is_autogenerated_name= FALSE;
|
||||||
$2->set_name($4.str, $4.length, system_charset_info);
|
$2->set_name($4.str, $4.length, system_charset_info);
|
||||||
}
|
}
|
||||||
else
|
else if (udf)
|
||||||
$2->set_name($1, (uint) ($3 - $1), YYTHD->charset());
|
$2->set_name($1, (uint) ($3 - $1), YYTHD->charset());
|
||||||
$$= $2;
|
$$= $2;
|
||||||
}
|
}
|
||||||
@@ -5114,6 +5128,11 @@ cast_type:
|
|||||||
| DECIMAL_SYM float_options { $$=ITEM_CAST_DECIMAL; Lex->charset= NULL; }
|
| DECIMAL_SYM float_options { $$=ITEM_CAST_DECIMAL; Lex->charset= NULL; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
opt_expr_list:
|
||||||
|
/* empty */ { $$= NULL; }
|
||||||
|
| expr_list { $$= $1;}
|
||||||
|
;
|
||||||
|
|
||||||
expr_list:
|
expr_list:
|
||||||
{ Select->expr_list.push_front(new List<Item>); }
|
{ Select->expr_list.push_front(new List<Item>); }
|
||||||
expr_list2
|
expr_list2
|
||||||
|
Reference in New Issue
Block a user