From 93e3057ba574672c2f0bb1b0958221c0d7efc553 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 13 Dec 2007 11:19:05 +0100 Subject: [PATCH] Bug #32858: Erro: "Incorrect usage of UNION and INTO" does not take subselects into account It is forbidden to use the SELECT INTO construction inside UNION statements unless on the last SELECT of the union. The parser records whether it has seen INTO or not when parsing a UNION statement. But if the INTO was legally used in an outer query, an error is thrown if UNION is seen in a subquery. Fixed in 5.0 by remembering the nesting level of INTO tokens and mitigate the error unless it collides with the UNION. mysql-test/r/union.result: Bug#32858: Test result mysql-test/t/union.test: Bug#32858: Test case sql/sql_class.cc: Bug#32858: Initializing new member sql/sql_class.h: Bug#32858: Added property nest_level to select_result class. sql/sql_yacc.yy: Bug#32858: The fix. --- mysql-test/r/union.result | 42 ++++++++++++++++++++++++++++ mysql-test/t/union.test | 59 +++++++++++++++++++++++++++++++++++++++ sql/sql_class.cc | 1 + sql/sql_class.h | 37 ++++++++++++++++++++++-- sql/sql_yacc.yy | 25 +++++++++++------ 5 files changed, 152 insertions(+), 12 deletions(-) diff --git a/mysql-test/r/union.result b/mysql-test/r/union.result index efdd8195fb5..dadc6bb3558 100644 --- a/mysql-test/r/union.result +++ b/mysql-test/r/union.result @@ -1389,4 +1389,46 @@ select @var; 1 (select 2) union (select 1 into @var); ERROR 42000: Result consisted of more than one row +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +SELECT a INTO @v FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 +) alias; +SELECT a INTO OUTFILE 'union.out.file' FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 WHERE 0 +) alias; +SELECT a INTO DUMPFILE 'union.out.file2' FROM ( +SELECT a FROM t1 +UNION +SELECT a FROM t1 WHERE 0 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO @v FROM t1 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO OUTFILE 'union.out.file3' FROM t1 +) alias; +SELECT a FROM ( +SELECT a FROM t1 +UNION +SELECT a INTO DUMPFILE 'union.out.file4' FROM t1 +) alias; +SELECT a FROM t1 UNION SELECT a INTO @v FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file5' FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file6' FROM t1; +SELECT a INTO @v FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +SELECT a INTO OUTFILE 'union.out.file7' FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +SELECT a INTO DUMPFILE 'union.out.file8' FROM t1 UNION SELECT a FROM t1; +ERROR HY000: Incorrect usage of UNION and INTO +DROP TABLE t1; End of 5.0 tests diff --git a/mysql-test/t/union.test b/mysql-test/t/union.test index 22f09466b1c..54e00455d9a 100644 --- a/mysql-test/t/union.test +++ b/mysql-test/t/union.test @@ -877,4 +877,63 @@ DROP TABLE t1; select @var; --error 1172 (select 2) union (select 1 into @var); + +# +# Bug#32858: Erro: "Incorrect usage of UNION and INTO" does not take subselects +# into account +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); + +SELECT a INTO @v FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 +) alias; + +SELECT a INTO OUTFILE 'union.out.file' FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 WHERE 0 +) alias; + +SELECT a INTO DUMPFILE 'union.out.file2' FROM ( + SELECT a FROM t1 + UNION + SELECT a FROM t1 WHERE 0 +) alias; + +# +# INTO will not be allowed in subqueries in version 5.1 and above. +# +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO @v FROM t1 +) alias; + +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO OUTFILE 'union.out.file3' FROM t1 +) alias; + +SELECT a FROM ( + SELECT a FROM t1 + UNION + SELECT a INTO DUMPFILE 'union.out.file4' FROM t1 +) alias; + +SELECT a FROM t1 UNION SELECT a INTO @v FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file5' FROM t1; +SELECT a FROM t1 UNION SELECT a INTO OUTFILE 'union.out.file6' FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO @v FROM t1 UNION SELECT a FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO OUTFILE 'union.out.file7' FROM t1 UNION SELECT a FROM t1; +--error ER_WRONG_USAGE +SELECT a INTO DUMPFILE 'union.out.file8' FROM t1 UNION SELECT a FROM t1; + +DROP TABLE t1; + --echo End of 5.0 tests diff --git a/sql/sql_class.cc b/sql/sql_class.cc index b67f63778dc..87553837641 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -931,6 +931,7 @@ void THD::rollback_item_tree_changes() select_result::select_result() { thd=current_thd; + nest_level= -1; } void select_result::send_error(uint errcode,const char *err) diff --git a/sql/sql_class.h b/sql/sql_class.h index e6d65f3133a..80665f8b4dc 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -1891,6 +1891,7 @@ class select_result :public Sql_alloc { protected: THD *thd; SELECT_LEX_UNIT *unit; + uint nest_level; public: select_result(); virtual ~select_result() {}; @@ -1927,6 +1928,12 @@ public: */ virtual void cleanup(); void set_thd(THD *thd_arg) { thd= thd_arg; } + /** + The nest level, if supported. + @return + -1 if nest level is undefined, otherwise a positive integer. + */ + int get_nest_level() { return nest_level; } #ifdef EMBEDDED_LIBRARY virtual void begin_dataset() {} #else @@ -2006,7 +2013,14 @@ class select_export :public select_to_file { bool is_unsafe_field_sep; bool fixed_row_size; public: - select_export(sql_exchange *ex) :select_to_file(ex) {} + /** + Creates a select_export to represent INTO OUTFILE with a + defined level of subquery nesting. + */ + select_export(sql_exchange *ex, uint nest_level_arg) :select_to_file(ex) + { + nest_level= nest_level_arg; + } ~select_export(); int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); @@ -2015,7 +2029,15 @@ public: class select_dump :public select_to_file { public: - select_dump(sql_exchange *ex) :select_to_file(ex) {} + /** + Creates a select_export to represent INTO DUMPFILE with a + defined level of subquery nesting. + */ + select_dump(sql_exchange *ex, uint nest_level_arg) : + select_to_file(ex) + { + nest_level= nest_level_arg; + } int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); }; @@ -2422,7 +2444,16 @@ class select_dumpvar :public select_result_interceptor { ha_rows row_count; public: List var_list; - select_dumpvar() { var_list.empty(); row_count= 0;} + /** + Creates a select_dumpvar to represent INTO with a defined + level of subquery nesting. + */ + select_dumpvar(uint nest_level_arg) + { + var_list.empty(); + row_count= 0; + nest_level= nest_level_arg; + } ~select_dumpvar() {} int prepare(List &list, SELECT_LEX_UNIT *u); bool send_data(List &items); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 82100b3d3dd..0c2c8c2bc8e 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -6356,7 +6356,8 @@ procedure_item: select_var_list_init: { LEX *lex=Lex; - if (!lex->describe && (!(lex->result= new select_dumpvar()))) + if (!lex->describe && + (!(lex->result= new select_dumpvar(lex->nest_level)))) MYSQL_YYABORT; } select_var_list @@ -6430,7 +6431,7 @@ into_destination: LEX *lex= Lex; lex->uncacheable(UNCACHEABLE_SIDEEFFECT); if (!(lex->exchange= new sql_exchange($2.str, 0)) || - !(lex->result= new select_export(lex->exchange))) + !(lex->result= new select_export(lex->exchange, lex->nest_level))) MYSQL_YYABORT; } opt_field_term opt_line_term @@ -6442,7 +6443,7 @@ into_destination: lex->uncacheable(UNCACHEABLE_SIDEEFFECT); if (!(lex->exchange= new sql_exchange($2.str,1))) MYSQL_YYABORT; - if (!(lex->result= new select_dump(lex->exchange))) + if (!(lex->result= new select_dump(lex->exchange, lex->nest_level))) MYSQL_YYABORT; } } @@ -9421,12 +9422,18 @@ union_list: UNION_SYM union_option { LEX *lex=Lex; - if (lex->result) - { - /* Only the last SELECT can have INTO...... */ - my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); - MYSQL_YYABORT; - } + if (lex->result && + (lex->result->get_nest_level() == -1 || + lex->result->get_nest_level() == lex->nest_level)) + { + /* + Only the last SELECT can have INTO unless the INTO and UNION + are at different nest levels. In version 5.1 and above, INTO + will onle be allowed at top level. + */ + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO"); + MYSQL_YYABORT; + } if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE) { my_parse_error(ER(ER_SYNTAX_ERROR));