Hi Martin
See some comments inline below.
The patch is ok to push with the following changes:
1) Please add the test cases from Bug#30105 (SELECT [...] INTO subquery
Invalid return.),
since it's actually fixed.
2) The test cases covering a invalid SELECT ... INTO in a sub select
always use the
"INTO @var" syntax.
Please also add tests covering syntax errors when using:
- SELECT ... INTO OUTFILE
- SELECT ... INTO DUMPFILE
inside a sub select, since these should be also prevented now.
The fix itself is not trivial, but goes into the right direction by
separating the concepts
of a select statement versus a query expression, which is the root issue
causing some trouble in this area:
- see related bug#35020
- see related bug#35021
This patch will make fixing 35020 and 35021 much easier,
and should help maintenance in general.
To untangle this area of the grammar is not a trivial task,
thanks a lot for finding this fix.
Best regards,
Marc
mhansson@stripped wrote:
> Below is the list of changes that have just been committed into a local
> 5.1 repository of mhansson. When mhansson does a push these changes
> will be propagated to the main repository and, within 24 hours after the
> push, to the public repository.
> For information on how to access the public repository
> see http://dev.mysql.com/doc/mysql/en/installing-source-tree.html
>
> ChangeSet@stripped, 2008-02-26 12:52:12+01:00, mhansson@riffraff.(none) +3 -0
> Bug#33204: INTO is allowed in subselect, causing inconsistent results
>
> The parser rule for UNION in nested queries produced, by transitivity,
> top-level selects on its right-hand side. This allowed users to use
> SELECT INTO in this position, which violates the standard and has
> undefined result.
> Fixed by duplicating the intermediate rules into a subgrammar for
> nested selects where INTO is forbidden.
>
> mysql-test/r/subselect.result@stripped, 2008-02-26 12:52:10+01:00,
> mhansson@riffraff.(none) +219 -6
> Bug#33204: Test result.
>
> mysql-test/t/subselect.test@stripped, 2008-02-26 12:52:10+01:00,
> mhansson@riffraff.(none) +170 -6
> Bug#33204: Test case.
>
> sql/sql_yacc.yy@stripped, 2008-02-26 12:52:10+01:00, mhansson@riffraff.(none) +217
> -71
> Bug#33204
>
> The fix appears to introduce 6 new shift/reduce conflicts. The truth is
> - 1 conflict has disappeared (SELECT_SYM token in <simple_expr>),
>
Verified that there are no conflicts involving SELECT_SYM any more.
> - 1 conflict appears in 3 new places ('(' token in <simple_expr>),
>
Verified. These conflicts are another manifestation of an existing bug
(See Bug#25734),
which affects '(' expr ')' and '(' subselect ')' in general, in
different contexts.
After analysis, it safe to conclude that no regression is introduced here,
even with a growing number of S/R conflicts in the grammar.
> - 3 conflicts appear in 1 new place each (tokens SQL_BUFFER_RESULT,
> SQL_CACHE_SYM and SQL_NO_CACHE_SYM since the rule with the conflict,
> select_part2 has been copied) and
>
Verified. These conflicts are due to the fact that SQL_BUFFER_RESULT,
SQL_CACHE_SYM and SQL_NO_CACHE_SYM
are *not* reserved keywords.
I think however that there is more to it:
This grammar changes do separate:
- what is a SELECT *statement*, which can have an "INTO" clause
- what is a SELECT *query*, and can be nested as an expression, which
can not have an "INTO" clause.
I think that the global options like:
- HIGH_PRIORITY
- SQL_CALC_FOUND_ROWS (?)
- SQL_NO_CACHE_SYM
- SQL_CACHE_SYM
should probably appear only in select statements,
and should not be available as options in a sub select.
On the other hand, other options like "ALL", "DISTINCT", etc,
do make sense in a sub select, so there is a need to split the current
select_options rules
to clarify which options are available in which context.
This is an existing defect, similar but not identical to the present
issue affecting "INTO",
so I suggest to fix it separately (no change required for this patch).
> - 1 conflict appears in 1 new place (LIMIT token, since the rule producing it,
> select_part2 has been copied).
>
Verified.
>
> When the original conflicts are resolved, these conflicts will disappear as
> well.
>
>
Agreed.
The "%expect NNN" rule in sql_yacc.yy has been added to detect at first
glance potential issues introduced by a patch.
In this case, I agree with the analysis, and think it's safe to push
grammar change even with "more" conflicts,
since these are additional manifestations of existing conflicts only,
not really new issues.
> diff -Nrup a/mysql-test/t/subselect.test b/mysql-test/t/subselect.test
> --- a/mysql-test/t/subselect.test 2008-01-20 11:50:25 +01:00
> +++ b/mysql-test/t/subselect.test 2008-02-26 12:52:10 +01:00
> @@ -2455,8 +2455,6 @@ DROP TABLE t1, t2;
> CREATE TABLE t1 (i INT);
>
> (SELECT i FROM t1) UNION (SELECT i FROM t1);
> -#TODO:not supported
> ---error ER_PARSE_ERROR
>
Very good, resolving long standing issues in the grammar.
> SELECT sql_no_cache * FROM t1 WHERE NOT EXISTS
> (
> (SELECT i FROM t1) UNION
> @@ -2473,8 +2471,6 @@ WHERE NOT EXISTS (((SELECT i FROM t1) UN
> explain select ((select t11.i from t1 t11) union (select t12.i from t1 t12))
> from t1;
>
> -#TODO:not supported
> ---error ER_PARSE_ERROR
>
Same
> explain select * from t1 where not exists
> ((select t11.i from t1 t11) union (select t12.i from t1 t12));
>
> @@ -3128,8 +3124,6 @@ EXPLAIN EXTENDED
> SELECT 2 FROM t1 WHERE EXISTS ((SELECT 1 FROM t2 WHERE t1.a=t2.a));
>
>
> -#TODO:not supported
> ---error ER_PARSE_ERROR
>
Same
> EXPLAIN EXTENDED
> SELECT 2 FROM t1 WHERE EXISTS ((SELECT 1 FROM t2 WHERE t1.a=t2.a) UNION
> (SELECT 1 FROM t2 WHERE t1.a = t2.a));
> @@ -3249,3 +3243,173 @@ INSERT INTO t1 VALUES ('a');
> SELECT * FROM t1 WHERE _utf8'a' = ANY (SELECT s1 FROM t1);
> DROP TABLE t1;
>
> +#
> +# Bug#33204: INTO is allowed in subselect, causing inconsistent results
> +#
> +CREATE TABLE t1( a INT );
> +INSERT INTO t1 VALUES (1),(2);
> +
> +CREATE TABLE t2( a INT, b INT );
> +
> +--error ER_PARSE_ERROR
> +SELECT *
> +FROM (SELECT a INTO @var FROM t1 WHERE a = 2) t1a;
>
Ok
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM (
> + SELECT 1 a
> + UNION
> + SELECT a INTO @var FROM t1 WHERE a = 2
> +) t1a;
>
Ok
> +
> +SELECT * FROM (SELECT a FROM t1 WHERE a = 2) t1a;
> +
> +SELECT * FROM (
> + SELECT a FROM t1 WHERE a = 2
> + UNION
> + SELECT a FROM t1 WHERE a = 2
> +) t1a;
> +
> +SELECT * FROM (
> + SELECT 1 a
> + UNION
> + SELECT a FROM t1 WHERE a = 2
> + UNION
> + SELECT a FROM t1 WHERE a = 2
> +) t1a;
> +
> +# This was not allowed previously. Possibly, it should be allowed on the future.
> +# For now, the intent is to keep the fix as non-intrusive as possible.
> +--error ER_PARSE_ERROR
> +SELECT * FROM ((SELECT 1 a) UNION SELECT 1 a);
>
Ok, existing limitation, due to bug#25734
> +SELECT * FROM (SELECT 1 a UNION (SELECT 1 a)) alias;
> +SELECT * FROM (SELECT 1 UNION SELECT 1) t1a;
>
Ok
> +--error ER_PARSE_ERROR
> +SELECT * FROM ((SELECT 1 a INTO @a)) t1a;
>
Ok
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM (SELECT 1 a UNION (SELECT 1 a INTO @a)) t1a;
>
Ok
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM (SELECT 1 a UNION ((SELECT 1 a INTO @a))) t1a;
>
Ok
> +
> +SELECT * FROM (SELECT 1 a ORDER BY a) t1a;
> +SELECT * FROM (SELECT 1 a UNION SELECT 1 a ORDER BY a) t1a;
> +SELECT * FROM (SELECT 1 a UNION SELECT 1 a LIMIT 1) t1a;
> +SELECT * FROM (SELECT 1 a UNION SELECT 1 a ORDER BY a LIMIT 1) t1a;
>
Ok
> +
> +# Test of rule
> +# table_factor: '(' get_select_lex query_expression_body ')' opt_table_alias
> +# UNION should not be allowed inside the parentheses, nor should
> +# aliases after.
> +#
> +SELECT * FROM t1 JOIN (SELECT 1 UNION SELECT 1) alias ON 1;
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 JOIN ((SELECT 1 UNION SELECT 1)) ON 1;
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 JOIN (t1 t1a UNION SELECT 1) ON 1;
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 JOIN ((t1 t1a UNION SELECT 1)) ON 1;
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 JOIN (t1 t1a) t1a ON 1;
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 JOIN ((t1 t1a)) t1a ON 1;
> +
> +SELECT * FROM t1 JOIN (t1 t1a) ON 1;
> +SELECT * FROM t1 JOIN ((t1 t1a)) ON 1;
> +
> +SELECT * FROM (t1 t1a);
> +SELECT * FROM ((t1 t1a));
> +
> +SELECT * FROM t1 JOIN (SELECT 1 t1a) alias ON 1;
> +SELECT * FROM t1 JOIN ((SELECT 1 t1a)) alias ON 1;
> +
> +SELECT * FROM t1 JOIN (SELECT 1 a) a ON 1;
> +SELECT * FROM t1 JOIN ((SELECT 1 a)) a ON 1;
> +
> +# For the join, TABLE_LIST::select_lex == NULL
> +# Check that we handle this.
> +--error ER_PARSE_ERROR
> +SELECT * FROM (t1 JOIN (SELECT 1) t1a1 ON 1) t1a2;
> +
> +SELECT * FROM t1 WHERE a = ALL ( SELECT 1 );
> +SELECT * FROM t1 WHERE a = ALL ( SELECT 1 UNION SELECT 1 );
> +SELECT * FROM t1 WHERE a = ANY ( SELECT 3 UNION SELECT 1 );
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ANY ( SELECT 1 UNION SELECT 1 INTO @a);
>
Ok
> +
> +SELECT * FROM t1 WHERE a = ( SELECT 1 );
> +SELECT * FROM t1 WHERE a = ( SELECT 1 UNION SELECT 1 );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ( SELECT 1 INTO @a);
>
Ok
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ( SELECT 1 UNION SELECT 1 INTO @a);
> +
>
Ok
> +--error ER_PARSE_ERROR
> +SELECT ( SELECT 1 INTO @v );
>
Ok
> +--error ER_PARSE_ERROR
> +SELECT ( SELECT 1 UNION SELECT 1 INTO @v );
>
Ok
> +
> +# Make sure context is popped when we leave the nested select
> +SELECT ( SELECT a FROM t1 WHERE a = 1 ), a FROM t1;
> +SELECT ( SELECT a FROM t1 WHERE a = 1 UNION SELECT 1 ), a FROM t1;
> +
> +# Make sure we have feature F561 (see .yy file)
> +SELECT * FROM t2 WHERE (a, b) IN (SELECT a, b FROM t2);
> +
> +# Make sure the parser does not allow nested UNIONs anywhere
> +
> +--error ER_PARSE_ERROR
> +SELECT 1 UNION ( SELECT 1 UNION SELECT 1 );
>
> +--error ER_PARSE_ERROR
> +( SELECT 1 UNION SELECT 1 ) UNION SELECT 1;
> +
> +--error ER_PARSE_ERROR
> +SELECT ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +--error ER_PARSE_ERROR
> +SELECT ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1;
> +SELECT ( SELECT 1 UNION SELECT 1 UNION SELECT 1 );
> +SELECT ((SELECT 1 UNION SELECT 1 UNION SELECT 1));
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +--error ER_PARSE_ERROR
> +SELECT * FROM ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1 );
> +SELECT * FROM ( SELECT 1 UNION SELECT 1 UNION SELECT 1 ) a;
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ALL ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ANY ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a IN ( SELECT 1 UNION ( SELECT 1 UNION SELECT 1 ) );
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1 );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ALL ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1 );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a = ANY ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1 );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a IN ( ( SELECT 1 UNION SELECT 1 ) UNION SELECT 1 );
> +
> +SELECT * FROM t1 WHERE a = ( SELECT 1 UNION SELECT 1 UNION SELECT 1 );
> +SELECT * FROM t1 WHERE a = ALL ( SELECT 1 UNION SELECT 1 UNION SELECT 1 );
> +SELECT * FROM t1 WHERE a = ANY ( SELECT 1 UNION SELECT 1 UNION SELECT 1 );
> +SELECT * FROM t1 WHERE a IN ( SELECT 1 UNION SELECT 1 UNION SELECT 1 );
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE EXISTS ( SELECT 1 UNION SELECT 1 INTO @v );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a IN ( SELECT 1 UNION SELECT 1 INTO @v );
> +
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE EXISTS ( SELECT 1 INTO @v );
> +--error ER_PARSE_ERROR
> +SELECT * FROM t1 WHERE a IN ( SELECT 1 INTO @v );
> +
> +
> +DROP TABLE t1, t2;
> diff -Nrup a/sql/sql_yacc.yy b/sql/sql_yacc.yy
> --- a/sql/sql_yacc.yy 2008-02-01 09:26:36 +01:00
> +++ b/sql/sql_yacc.yy 2008-02-26 12:52:10 +01:00
> @@ -457,6 +457,83 @@ Item* handle_sql2003_note184_exception(T
> DBUG_RETURN(result);
> }
>
> +/**
> + @brief Creates a new SELECT_LEX for a UNION branch.
> +
> + Sets up and initializes a SELECT_LEX structure for a query once the parser
> + discovers a UNION token. The current SELECT_LEX is pushed on the stack and
> + the new SELECT_LEX becomes the current one..=
> +
> + @lex The parser state.
> +
> + @is_union_distinct True if the union preceding the new select statement
> + uses UNION DISTINCT.
> +
> + @return <code>false</code> if successful,
> <code>true</code> if an error was
> + reported. In the latter case parsing should stop.
> + */
> +bool add_select_to_union_list(LEX *lex, bool is_union_distinct)
> +{
> + if (lex->result)
> + {
> + /* Only the last SELECT can have INTO...... */
> + my_error(ER_WRONG_USAGE, MYF(0), "UNION", "INTO");
> + return TRUE;
> + }
> + if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE)
> + {
> + my_parse_error(ER(ER_SYNTAX_ERROR));
> + return TRUE;
> + }
> + /* This counter shouldn't be incremented for UNION parts */
> + lex->nest_level--;
> + if (mysql_new_select(lex, 0))
> + return TRUE;
> + mysql_init_select(lex);
> + lex->current_select->linkage=UNION_TYPE;
> + if (is_union_distinct) /* UNION DISTINCT - remember position */
> + lex->current_select->master_unit()->union_distinct=
> + lex->current_select;
> + return FALSE;
> +}
>
New explicit helper (moved existing code), good.
> +
> +/**
> + @brief Initializes a SELECT_LEX for a query within parentheses (aka
> + braces).
> +
> + @return false if successful, true if an error was reported. In the latter
> + case parsing should stop.
> + */
> +bool setup_select_in_parentheses(LEX *lex)
> +{
> + SELECT_LEX * sel= lex->current_select;
> + if (sel->set_braces(1))
> + {
> + my_parse_error(ER(ER_SYNTAX_ERROR));
> + return TRUE;
> + }
> + if (sel->linkage == UNION_TYPE &&
> + !sel->master_unit()->first_select()->braces &&
> + sel->master_unit()->first_select()->linkage ==
> + UNION_TYPE)
> + {
> + my_parse_error(ER(ER_SYNTAX_ERROR));
> + return TRUE;
> + }
> + if (sel->linkage == UNION_TYPE &&
> + sel->olap != UNSPECIFIED_OLAP_TYPE &&
> + sel->master_unit()->fake_select_lex)
> + {
> + my_error(ER_WRONG_USAGE, MYF(0), "CUBE/ROLLUP", "ORDER BY");
> + return TRUE;
> + }
> + /* select in braces, can't contain global parameters */
> + if (sel->master_unit()->fake_select_lex)
> + sel->master_unit()->global_parameters=
> + sel->master_unit()->fake_select_lex;
> + return FALSE;
> +}
>
New explicit helper (moved existing code), good.
> +
> %}
> %union {
> int num;
> @@ -508,10 +585,10 @@ bool my_yyoverflow(short **a, YYSTYPE **
>
> %pure_parser /* We have threads */
> /*
> - Currently there are 177 shift/reduce conflicts.
> + Currently there are 183 shift/reduce conflicts.
> We should not introduce new conflicts any more.
> */
> -%expect 177
> +%expect 183
>
> /*
> Comments for TOKENS.
> @@ -1195,6 +1272,7 @@ bool my_yyoverflow(short **a, YYSTYPE **
> join_table_list join_table
> table_factor table_ref
> select_derived derived_table_list
> + select_derived_union
>
> %type <date_time_type> date_time_type;
> %type <interval> interval
> @@ -1228,8 +1306,9 @@ bool my_yyoverflow(short **a, YYSTYPE **
>
> %type <variable> internal_variable_name
>
> -%type <select_lex> subselect take_first_select
> - get_select_lex
> +%type <select_lex> subselect
> + get_select_lex query_specification
> + query_expression_body
>
> %type <boolfunc2creator> comp_op
>
> @@ -6136,37 +6215,22 @@ select_init:
> select_paren:
> SELECT_SYM select_part2
> {
> - LEX *lex= Lex;
> - SELECT_LEX * sel= lex->current_select;
> - if (sel->set_braces(1))
> - {
> - my_parse_error(ER(ER_SYNTAX_ERROR));
> + if (setup_select_in_parentheses(Lex))
> MYSQL_YYABORT;
> - }
> - if (sel->linkage == UNION_TYPE &&
> - !sel->master_unit()->first_select()->braces &&
> - sel->master_unit()->first_select()->linkage ==
> - UNION_TYPE)
> - {
> - my_parse_error(ER(ER_SYNTAX_ERROR));
> - MYSQL_YYABORT;
> - }
> - if (sel->linkage == UNION_TYPE &&
> - sel->olap != UNSPECIFIED_OLAP_TYPE &&
> - sel->master_unit()->fake_select_lex)
> - {
> - my_error(ER_WRONG_USAGE, MYF(0),
> - "CUBE/ROLLUP", "ORDER BY");
> - MYSQL_YYABORT;
> - }
> - /* select in braces, can't contain global parameters */
> - if (sel->master_unit()->fake_select_lex)
> - sel->master_unit()->global_parameters=
> - sel->master_unit()->fake_select_lex;
> }
> | '(' select_paren ')'
> ;
>
> +/* The equivalent of select_paren for nested queries. */
> +select_paren_derived:
> + SELECT_SYM select_part2_derived
> + {
> + if (setup_select_in_parentheses(Lex))
> + MYSQL_YYABORT;
> + }
> + | '(' select_paren_derived ')'
> + ;
> +
> select_init2:
> select_part2
> {
> @@ -6659,7 +6723,7 @@ simple_expr:
> { $$= negate_expression(YYTHD, $2); }
> | '(' subselect ')'
> {
> - $$= new (YYTHD->mem_root) Item_singlerow_subselect($2);
> + $$= new (YYTHD->mem_root) Item_singlerow_subselect($2);
> }
> | '(' expr ')' { $$= $2; }
> | '(' expr ',' expr_list ')'
> @@ -6674,7 +6738,7 @@ simple_expr:
> }
> | EXISTS '(' subselect ')'
> {
> - $$= new (YYTHD->mem_root) Item_exists_subselect($3);
> + $$= new (YYTHD->mem_root) Item_exists_subselect($3);
> }
> | '{' ident expr '}' { $$= $3; }
> | MATCH ident_list_arg AGAINST '(' bit_expr fulltext_options ')'
> @@ -7437,6 +7501,7 @@ when_list:
> }
> ;
>
> +/* Equivalent to <from clause> in the SQL:2003 standard. */
> /* Warning - may return NULL in case of incomplete SELECT */
> table_ref:
> table_factor { $$=$1; }
> @@ -7448,10 +7513,12 @@ table_ref:
> }
> ;
>
> +/* Equivalent to <from clause> in the SQL:2003 standard. */
> join_table_list:
> derived_table_list { MYSQL_YYABORT_UNLESS($$=$1); }
> ;
>
> +/* Equivalent to <table reference list> in the SQL:2003 standard. */
> /* Warning - may return NULL in case of incomplete SELECT */
> derived_table_list:
> table_ref { $$=$1; }
> @@ -7605,6 +7672,13 @@ normal_join:
> | CROSS JOIN_SYM {}
> ;
>
> +/*
> + This is a flattening of the rules <table factor> and <table primary>
> + in the SQL:2003 standard, since we don't have <sample clause>
> +
> + I.e.
> + <table factor> ::= <table primary> [ <sample clause> ]
> +*/
> /* Warning - may return NULL in case of incomplete SELECT */
> table_factor:
> {
> @@ -7661,12 +7735,29 @@ table_factor:
> /* incomplete derived tables return NULL, we must be
> nested in select_derived rule to be here. */
> }
> - | '(' get_select_lex select_derived union_opt ')' opt_table_alias
> + /*
> + Represents a flattening of the following rules from the SQL:2003
> + standard. This sub-rule corresponds to the sub-rule
> + <table primary> ::= ... | <derived table> [ AS ]
> <correlation name>
> +
> + The following rules have been flattened into query_expression_body
> + (since we have no <with clause>).
> +
> + <derived table> ::= <table subquery>
> + <table subquery> ::= <subquery>
> + <subquery> ::= <left paren> <query expression>
> <right paren>
> + <query expression> ::= [ <with clause> ] <query
> expression body>
> +
> + For the time being we use the non-standard rule
> + select_derived_union which is a compromise between the standard
> + and our parser. Possibly this rule could be replaced by our
> + query_expression_body.
> + */
>
Thanks for the comments and pointers.
> + | '(' get_select_lex select_derived_union ')' opt_table_alias
> {
> /* Use $2 instead of Lex->current_select as derived table will
> alter value of Lex->current_select. */
> -
> - if (!($3 || $6) && $2->embedding &&
> + if (!($3 || $5) && $2->embedding &&
> !$2->embedding->nested_join->join_list.elements)
> {
> /* we have a derived table ($3 == NULL) but no alias,
> @@ -7685,14 +7776,15 @@ table_factor:
> SELECT_LEX_UNIT *unit= sel->master_unit();
> lex->current_select= sel= unit->outer_select();
> if (!($$= sel->add_table_to_list(lex->thd,
> - new Table_ident(unit), $6, 0,
> + new Table_ident(unit), $5, 0,
> TL_READ)))
>
> MYSQL_YYABORT;
> sel->add_joined_table($$);
> lex->pop_context();
> }
> - else if ($4 || $6)
> + else if ($3->select_lex &&
> + $3->select_lex->master_unit()->is_union() || $5)
> {
> /* simple nested joins cannot have aliases or unions */
> my_parse_error(ER(ER_SYNTAX_ERROR));
> @@ -7703,6 +7795,62 @@ table_factor:
> }
> ;
>
> +select_derived_union:
> + select_derived opt_order_clause opt_limit_clause
> + | select_derived_union
> + UNION_SYM
> + union_option
> + {
> + if (add_select_to_union_list(Lex, (bool)$3))
> + MYSQL_YYABORT;
> + }
> + query_specification
> + {
> + /*
> + Remove from the name resolution context stack the context of the
> + last select in the union.
> + */
> + Lex->pop_context();
> + }
> + opt_order_clause opt_limit_clause
> + ;
> +
> +/* The equivalent of select_init2 for nested queries. */
> +select_init2_derived:
> + select_part2_derived
> + {
> + LEX *lex= Lex;
> + SELECT_LEX * sel= lex->current_select;
> + if (lex->current_select->set_braces(0))
> + {
> + my_parse_error(ER(ER_SYNTAX_ERROR));
> + MYSQL_YYABORT;
> + }
> + if (sel->linkage == UNION_TYPE &&
> + sel->master_unit()->first_select()->braces)
> + {
> + my_parse_error(ER(ER_SYNTAX_ERROR));
> + MYSQL_YYABORT;
> + }
> + }
> + ;
> +
> +/* The equivalent of select_part2 for nested queries. */
> +select_part2_derived:
> + {
> + LEX *lex= Lex;
> + SELECT_LEX *sel= lex->current_select;
> + if (sel->linkage != UNION_TYPE)
> + mysql_init_select(lex);
> + lex->current_select->parsing_place= SELECT_LIST;
> + }
> + select_options select_item_list
>
See previous comment:
using select_options here makes all options available in a sub select.
Existing bug, no change required: this will be fixed with bug#35020
> + {
> + Select->parsing_place= NO_MATTER;
> + }
> + opt_select_from select_lock_type
>
And using select_lock_type here makes "FOR UPDATE" / "LOCK IN SHARED MODE"
available in sub selects also.
Existing bug, no change required: this will be fixed with bug#35021
> + ;
> +
> /* handle contents of parentheses in join expression */
> select_derived:
> get_select_lex
> @@ -11584,27 +11732,8 @@ union_clause:
> 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");
> + if (add_select_to_union_list(Lex, (bool)$2))
> MYSQL_YYABORT;
> - }
> - if (lex->current_select->linkage == GLOBAL_OPTIONS_TYPE)
> - {
> - my_parse_error(ER(ER_SYNTAX_ERROR));
> - MYSQL_YYABORT;
> - }
> - /* This counter shouldn't be incremented for UNION parts */
> - Lex->nest_level--;
> - if (mysql_new_select(lex, 0))
> - MYSQL_YYABORT;
> - mysql_init_select(lex);
> - lex->current_select->linkage=UNION_TYPE;
> - if ($2) /* UNION DISTINCT - remember position */
> - lex->current_select->master_unit()->union_distinct=
> - lex->current_select;
> }
> select_init
> {
> @@ -11657,22 +11786,39 @@ union_option:
> | ALL { $$=0; }
> ;
>
> -take_first_select: /* empty */
> - {
> - $$= Lex->current_select->master_unit()->first_select();
> - };
>
Removed "take_first_select", good.
> +query_specification:
> + SELECT_SYM select_init2_derived
> + {
> + $$= Lex->current_select->master_unit()->first_select();
> + }
> + | '(' select_paren_derived ')'
> + {
> + $$= Lex->current_select->master_unit()->first_select();
> + }
> + ;
> +
> +query_expression_body:
> + query_specification
> + | query_expression_body
> + UNION_SYM union_option
> + {
> + if (add_select_to_union_list(Lex, (bool)$3))
> + MYSQL_YYABORT;
> + }
> + query_specification
> + {
> + Lex->pop_context();
> + $$= $1;
> + }
> + ;
>
> +/* Corresponds to <query expression> in the SQL:2003 standard. */
> subselect:
> - SELECT_SYM subselect_start select_init2 take_first_select
> - subselect_end
> - {
> - $$= $4;
> - }
> - | '(' subselect_start select_paren take_first_select
> - subselect_end ')'
> - {
> - $$= $4;
> - };
> + subselect_start query_expression_body subselect_end
> + {
> + $$= $2;
> + }
> + ;
>
> subselect_start:
> {
>
>