Bug#26030 (Parsing fails for stored routine w/multi-statement execution
enabled) Before this fix, the lexer and parser would treat the ';' character as a different token (either ';' or END_OF_INPUT), based on convoluted logic, which failed in simple cases where a stored procedure is implemented as a single statement, and used in a multi query. With this fix: - the character ';' is always parsed as a ';' token in the lexer, - parsing multi queries is implemented in the parser, in the 'query:' rules, - the value of thd->client_capabilities, which is the capabilities negotiated between the client and the server during bootstrap, is immutable and not arbitrarily modified during parsing (which was the root cause of the bug)
This commit is contained in:
parent
5647bce366
commit
c7724872d8
@ -35,7 +35,7 @@ ERROR 42000: You have an error in your SQL syntax; check the manual that corresp
|
||||
prepare bar from "DELETE FROM table_28779 WHERE a = 7 OR 1=1/*! AND 2=2;";
|
||||
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 '' at line 1
|
||||
prepare bar from "DELETE FROM table_28779 WHERE a = 7 OR 1=1/*! AND 2=2;*";
|
||||
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 ';*' at line 1
|
||||
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 '*' at line 1
|
||||
prepare bar from "DELETE FROM table_28779 WHERE a = 7 OR 1=1/*!98765' AND b = 'bar';";
|
||||
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 '/*!98765' AND b = 'bar'' at line 1
|
||||
prepare bar from "DELETE FROM table_28779 WHERE a = 7 OR 1=1/*!98765' AND b = 'bar';*";
|
||||
|
68
mysql-test/r/parser.result
Normal file
68
mysql-test/r/parser.result
Normal file
@ -0,0 +1,68 @@
|
||||
DROP PROCEDURE IF EXISTS p26030;
|
||||
select "non terminated"$$
|
||||
non terminated
|
||||
non terminated
|
||||
select "terminated";$$
|
||||
terminated
|
||||
terminated
|
||||
select "non terminated, space" $$
|
||||
non terminated, space
|
||||
non terminated, space
|
||||
select "terminated, space"; $$
|
||||
terminated, space
|
||||
terminated, space
|
||||
select "non terminated, comment" /* comment */$$
|
||||
non terminated, comment
|
||||
non terminated, comment
|
||||
select "terminated, comment"; /* comment */$$
|
||||
terminated, comment
|
||||
terminated, comment
|
||||
select "stmt 1";select "stmt 2 non terminated"$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 non terminated
|
||||
stmt 2 non terminated
|
||||
select "stmt 1";select "stmt 2 terminated";$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 terminated
|
||||
stmt 2 terminated
|
||||
select "stmt 1";select "stmt 2 non terminated, space" $$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 non terminated, space
|
||||
stmt 2 non terminated, space
|
||||
select "stmt 1";select "stmt 2 terminated, space"; $$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 terminated, space
|
||||
stmt 2 terminated, space
|
||||
select "stmt 1";select "stmt 2 non terminated, comment" /* comment */$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 non terminated, comment
|
||||
stmt 2 non terminated, comment
|
||||
select "stmt 1";select "stmt 2 terminated, comment"; /* comment */$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
stmt 2 terminated, comment
|
||||
stmt 2 terminated, comment
|
||||
select "stmt 1"; select "space, stmt 2"$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
space, stmt 2
|
||||
space, stmt 2
|
||||
select "stmt 1";/* comment */select "comment, stmt 2"$$
|
||||
stmt 1
|
||||
stmt 1
|
||||
comment, stmt 2
|
||||
comment, stmt 2
|
||||
DROP PROCEDURE IF EXISTS p26030; CREATE PROCEDURE p26030() BEGIN SELECT 1; END; CALL p26030()
|
||||
$$
|
||||
1
|
||||
1
|
||||
DROP PROCEDURE IF EXISTS p26030; CREATE PROCEDURE p26030() SELECT 1; CALL p26030()
|
||||
$$
|
||||
1
|
||||
1
|
||||
DROP PROCEDURE p26030;
|
@ -85,9 +85,9 @@ NULL
|
||||
NULL
|
||||
NULL
|
||||
prepare stmt6 from 'select 1; select2';
|
||||
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 '; select2' at line 1
|
||||
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 'select2' at line 1
|
||||
prepare stmt6 from 'insert into t1 values (5,"five"); select2';
|
||||
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 '; select2' at line 1
|
||||
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 'select2' at line 1
|
||||
explain prepare stmt6 from 'insert into t1 values (5,"five"); select2';
|
||||
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 'from 'insert into t1 values (5,"five"); select2'' at line 1
|
||||
create table t2
|
||||
|
59
mysql-test/t/parser.test
Normal file
59
mysql-test/t/parser.test
Normal file
@ -0,0 +1,59 @@
|
||||
#
|
||||
# This file contains tests covering the parser
|
||||
#
|
||||
|
||||
#=============================================================================
|
||||
# LEXICAL PARSER (lex)
|
||||
#=============================================================================
|
||||
|
||||
#
|
||||
# Maintainer: these tests are for the lexical parser, so every character,
|
||||
# even whitespace or comments, is significant here.
|
||||
#
|
||||
|
||||
#
|
||||
# Bug#26030 (Parsing fails for stored routine w/multi-statement execution
|
||||
# enabled)
|
||||
#
|
||||
|
||||
--disable_warnings
|
||||
DROP PROCEDURE IF EXISTS p26030;
|
||||
--enable_warnings
|
||||
|
||||
delimiter $$;
|
||||
|
||||
select "non terminated"$$
|
||||
select "terminated";$$
|
||||
select "non terminated, space" $$
|
||||
select "terminated, space"; $$
|
||||
select "non terminated, comment" /* comment */$$
|
||||
select "terminated, comment"; /* comment */$$
|
||||
|
||||
# Multi queries can not be used in --ps-protocol test mode
|
||||
--disable_ps_protocol
|
||||
|
||||
select "stmt 1";select "stmt 2 non terminated"$$
|
||||
select "stmt 1";select "stmt 2 terminated";$$
|
||||
select "stmt 1";select "stmt 2 non terminated, space" $$
|
||||
select "stmt 1";select "stmt 2 terminated, space"; $$
|
||||
select "stmt 1";select "stmt 2 non terminated, comment" /* comment */$$
|
||||
select "stmt 1";select "stmt 2 terminated, comment"; /* comment */$$
|
||||
|
||||
select "stmt 1"; select "space, stmt 2"$$
|
||||
select "stmt 1";/* comment */select "comment, stmt 2"$$
|
||||
|
||||
DROP PROCEDURE IF EXISTS p26030; CREATE PROCEDURE p26030() BEGIN SELECT 1; END; CALL p26030()
|
||||
$$
|
||||
|
||||
DROP PROCEDURE IF EXISTS p26030; CREATE PROCEDURE p26030() SELECT 1; CALL p26030()
|
||||
$$
|
||||
|
||||
--enable_ps_protocol
|
||||
|
||||
delimiter ;$$
|
||||
DROP PROCEDURE p26030;
|
||||
|
||||
#============================================================================r
|
||||
# SYNTACTIC PARSER (bison)
|
||||
#=============================================================================
|
||||
|
@ -117,7 +117,6 @@ public:
|
||||
create_field m_return_field_def; /* This is used for FUNCTIONs only. */
|
||||
|
||||
const char *m_tmp_query; // Temporary pointer to sub query string
|
||||
uint m_old_cmq; // Old CLIENT_MULTI_QUERIES value
|
||||
st_sp_chistics *m_chistics;
|
||||
ulong m_sql_mode; // For SHOW CREATE and execution
|
||||
LEX_STRING m_qname; // db.name
|
||||
|
@ -1010,21 +1010,8 @@ int MYSQLlex(void *arg, void *yythd)
|
||||
yySkip();
|
||||
return (SET_VAR);
|
||||
case MY_LEX_SEMICOLON: // optional line terminator
|
||||
if (yyPeek())
|
||||
{
|
||||
if ((thd->client_capabilities & CLIENT_MULTI_STATEMENTS) &&
|
||||
!lip->stmt_prepare_mode)
|
||||
{
|
||||
lex->safe_to_cache_query= 0;
|
||||
lip->found_semicolon= lip->ptr;
|
||||
thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
|
||||
lip->next_state= MY_LEX_END;
|
||||
return (END_OF_INPUT);
|
||||
}
|
||||
state= MY_LEX_CHAR; // Return ';'
|
||||
break;
|
||||
}
|
||||
/* fall true */
|
||||
state= MY_LEX_CHAR; // Return ';'
|
||||
break;
|
||||
case MY_LEX_EOL:
|
||||
if (lip->ptr >= lip->end_of_query)
|
||||
{
|
||||
@ -1039,7 +1026,7 @@ int MYSQLlex(void *arg, void *yythd)
|
||||
case MY_LEX_END:
|
||||
lip->next_state=MY_LEX_END;
|
||||
return(0); // We found end of input last time
|
||||
|
||||
|
||||
/* Actually real shouldn't start with . but allow them anyhow */
|
||||
case MY_LEX_REAL_OR_POINT:
|
||||
if (my_isdigit(cs,yyPeek()))
|
||||
|
@ -6169,6 +6169,11 @@ void mysql_parse(THD *thd, const char *inBuf, uint length,
|
||||
(thd->query_length= (ulong)(lip.found_semicolon - thd->query)))
|
||||
thd->query_length--;
|
||||
/* Actually execute the query */
|
||||
if (*found_semicolon)
|
||||
{
|
||||
lex->safe_to_cache_query= 0;
|
||||
thd->server_status|= SERVER_MORE_RESULTS_EXISTS;
|
||||
}
|
||||
lex->set_trg_event_type_for_tables();
|
||||
mysql_execute_command(thd);
|
||||
query_cache_end_of_result(thd);
|
||||
|
@ -1203,21 +1203,54 @@ END_OF_INPUT
|
||||
|
||||
|
||||
query:
|
||||
END_OF_INPUT
|
||||
{
|
||||
THD *thd= YYTHD;
|
||||
if (!thd->bootstrap &&
|
||||
(!(thd->lex->select_lex.options & OPTION_FOUND_COMMENT)))
|
||||
{
|
||||
my_message(ER_EMPTY_QUERY, ER(ER_EMPTY_QUERY), MYF(0));
|
||||
MYSQL_YYABORT;
|
||||
}
|
||||
else
|
||||
{
|
||||
thd->lex->sql_command= SQLCOM_EMPTY_QUERY;
|
||||
}
|
||||
}
|
||||
| verb_clause END_OF_INPUT {};
|
||||
END_OF_INPUT
|
||||
{
|
||||
THD *thd= YYTHD;
|
||||
if (!thd->bootstrap &&
|
||||
(!(thd->lex->select_lex.options & OPTION_FOUND_COMMENT)))
|
||||
{
|
||||
my_message(ER_EMPTY_QUERY, ER(ER_EMPTY_QUERY), MYF(0));
|
||||
MYSQL_YYABORT;
|
||||
}
|
||||
thd->lex->sql_command= SQLCOM_EMPTY_QUERY;
|
||||
thd->m_lip->found_semicolon= NULL;
|
||||
}
|
||||
| verb_clause
|
||||
{
|
||||
Lex_input_stream *lip = YYTHD->m_lip;
|
||||
|
||||
if ((YYTHD->client_capabilities & CLIENT_MULTI_QUERIES) &&
|
||||
! lip->stmt_prepare_mode &&
|
||||
! (lip->ptr >= lip->end_of_query))
|
||||
{
|
||||
/*
|
||||
We found a well formed query, and multi queries are allowed:
|
||||
- force the parser to stop after the ';'
|
||||
- mark the start of the next query for the next invocation
|
||||
of the parser.
|
||||
*/
|
||||
lip->next_state= MY_LEX_END;
|
||||
lip->found_semicolon= lip->ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Single query, terminated. */
|
||||
lip->found_semicolon= NULL;
|
||||
}
|
||||
}
|
||||
';'
|
||||
opt_end_of_input
|
||||
| verb_clause END_OF_INPUT
|
||||
{
|
||||
/* Single query, not terminated. */
|
||||
YYTHD->m_lip->found_semicolon= NULL;
|
||||
}
|
||||
;
|
||||
|
||||
opt_end_of_input:
|
||||
/* empty */
|
||||
| END_OF_INPUT
|
||||
;
|
||||
|
||||
verb_clause:
|
||||
statement
|
||||
@ -9867,13 +9900,6 @@ trigger_tail:
|
||||
|
||||
lex->sphead= sp;
|
||||
lex->spname= $3;
|
||||
/*
|
||||
We have to turn of CLIENT_MULTI_QUERIES while parsing a
|
||||
stored procedure, otherwise yylex will chop it into pieces
|
||||
at each ';'.
|
||||
*/
|
||||
sp->m_old_cmq= thd->client_capabilities & CLIENT_MULTI_QUERIES;
|
||||
thd->client_capabilities &= ~CLIENT_MULTI_QUERIES;
|
||||
|
||||
bzero((char *)&lex->sp_chistics, sizeof(st_sp_chistics));
|
||||
lex->sphead->m_chistics= &lex->sp_chistics;
|
||||
@ -9888,9 +9914,6 @@ trigger_tail:
|
||||
|
||||
lex->sql_command= SQLCOM_CREATE_TRIGGER;
|
||||
sp->init_strings(YYTHD, lex);
|
||||
/* Restore flag if it was cleared above */
|
||||
if (sp->m_old_cmq)
|
||||
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
|
||||
sp->restore_thd_mem_root(YYTHD);
|
||||
|
||||
if (sp->is_not_allowed_in_function("trigger"))
|
||||
@ -9968,13 +9991,6 @@ sf_tail:
|
||||
|
||||
sp->m_type= TYPE_ENUM_FUNCTION;
|
||||
lex->sphead= sp;
|
||||
/*
|
||||
* We have to turn of CLIENT_MULTI_QUERIES while parsing a
|
||||
* stored procedure, otherwise yylex will chop it into pieces
|
||||
* at each ';'.
|
||||
*/
|
||||
sp->m_old_cmq= thd->client_capabilities & CLIENT_MULTI_QUERIES;
|
||||
thd->client_capabilities &= ~CLIENT_MULTI_QUERIES;
|
||||
lex->sphead->m_param_begin= lip->tok_start+1;
|
||||
}
|
||||
sp_fdparam_list /* $6 */
|
||||
@ -10030,9 +10046,6 @@ sf_tail:
|
||||
my_error(ER_SP_NORETURN, MYF(0), sp->m_qname.str);
|
||||
MYSQL_YYABORT;
|
||||
}
|
||||
/* Restore flag if it was cleared above */
|
||||
if (sp->m_old_cmq)
|
||||
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
|
||||
sp->restore_thd_mem_root(YYTHD);
|
||||
}
|
||||
;
|
||||
@ -10062,13 +10075,6 @@ sp_tail:
|
||||
sp->init_sp_name(YYTHD, $3);
|
||||
|
||||
lex->sphead= sp;
|
||||
/*
|
||||
* We have to turn of CLIENT_MULTI_QUERIES while parsing a
|
||||
* stored procedure, otherwise yylex will chop it into pieces
|
||||
* at each ';'.
|
||||
*/
|
||||
sp->m_old_cmq= YYTHD->client_capabilities & CLIENT_MULTI_QUERIES;
|
||||
YYTHD->client_capabilities &= (~CLIENT_MULTI_QUERIES);
|
||||
}
|
||||
'('
|
||||
{
|
||||
@ -10104,9 +10110,6 @@ sp_tail:
|
||||
|
||||
sp->init_strings(YYTHD, lex);
|
||||
lex->sql_command= SQLCOM_CREATE_PROCEDURE;
|
||||
/* Restore flag if it was cleared above */
|
||||
if (sp->m_old_cmq)
|
||||
YYTHD->client_capabilities |= CLIENT_MULTI_QUERIES;
|
||||
sp->restore_thd_mem_root(YYTHD);
|
||||
}
|
||||
;
|
||||
|
Loading…
x
Reference in New Issue
Block a user