diff --git a/mysql-test/r/parser.result b/mysql-test/r/parser.result index 002fbd02c2a868a6cf1d9981a25ba942cd6f9f7b..097f6dc69fff328c791b7524e206499eee669ff6 100644 --- a/mysql-test/r/parser.result +++ b/mysql-test/r/parser.result @@ -615,3 +615,55 @@ UPDATE t3 SET a4={d '1789-07-14'} WHERE a1=0; SELECT a1, a4 FROM t2 WHERE a4 LIKE {fn UCASE('1789-07-14')}; a1 a4 DROP TABLE t1, t2, t3; +# +# Bug#39559: dump of stored procedures / functions with C-style +# comment can't be read back +# ++----------+--------+ +| expected | result | ++----------+--------+ +| 2 | 2 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 1 | 1 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 3 | 3 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 2 | 2 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 7 | 7 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 8 | 8 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 7 | 7 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 4 | 4 | ++----------+--------+ ++----------+--------+ +| expected | result | ++----------+--------+ +| 4 | 4 | ++----------+--------+ +# +# End of 5.1 tests +# diff --git a/mysql-test/t/parser.test b/mysql-test/t/parser.test index 7efc519e44185d0bf60af1281f7879799ef64677..e44fc85bc5e296b204030d33bd244ea38e9c24f0 100644 --- a/mysql-test/t/parser.test +++ b/mysql-test/t/parser.test @@ -724,3 +724,31 @@ SELECT {fn CONCAT(a1,a2)} FROM t1; UPDATE t3 SET a4={d '1789-07-14'} WHERE a1=0; SELECT a1, a4 FROM t2 WHERE a4 LIKE {fn UCASE('1789-07-14')}; DROP TABLE t1, t2, t3; + +########################################################################### +--echo # +--echo # Bug#39559: dump of stored procedures / functions with C-style +--echo # comment can't be read back +--echo # + +--write_file $MYSQLTEST_VARDIR/tmp/bug39559.sql +select 2 as expected, /*!01000/**/*/ 2 as result; +select 1 as expected, /*!99998/**/*/ 1 as result; +select 3 as expected, /*!01000 1 + */ 2 as result; +select 2 as expected, /*!99990 1 + */ 2 as result; +select 7 as expected, /*!01000 1 + /* 8 + */ 2 + */ 4 as result; +select 8 as expected, /*!99998 1 + /* 2 + */ 4 + */ 8 as result; +select 7 as expected, /*!01000 1 + /*!01000 8 + */ 2 + */ 4 as result; +select 7 as expected, /*!01000 1 + /*!99998 8 + */ 2 + */ 4 as result; +select 4 as expected, /*!99998 1 + /*!99998 8 + */ 2 + */ 4 as result; +select 4 as expected, /*!99998 1 + /*!01000 8 + */ 2 + */ 4 as result; +select 7 as expected, /*!01000 1 + /*!01000 8 + /*!01000 error */ 16 + */ 2 + */ 4 as result; +select 4 as expected, /* 1 + /*!01000 8 + */ 2 + */ 4; +EOF + +--exec $MYSQL --comment --force --table test <$MYSQLTEST_VARDIR/tmp/bug39559.sql +--remove_file $MYSQLTEST_VARDIR/tmp/bug39559.sql + +--echo # +--echo # End of 5.1 tests +--echo # diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 19a19f2b6fbf035c41708c941674bd3046663f9c..5e24a18bea36b2d95890c1c9f61fa84b106cacf0 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -712,6 +712,53 @@ static inline uint int_token(const char *str,uint length) return ((uchar) str[-1] <= (uchar) cmp[-1]) ? smaller : bigger; } + +/** + Given a stream that is advanced to the first contained character in + an open comment, consume the comment. Optionally, if we are allowed, + recurse so that we understand comments within this current comment. + + At this level, we do not support version-condition comments. We might + have been called with having just passed one in the stream, though. In + that case, we probably want to tolerate mundane comments inside. Thus, + the case for recursion. + + @retval Whether EOF reached before comment is closed. +*/ +bool consume_comment(Lex_input_stream *lip, int remaining_recursions_permitted) +{ + reg1 uchar c; + while (! lip->eof()) + { + c= lip->yyGet(); + + if (remaining_recursions_permitted > 0) + { + if ((c == '/') && (lip->yyPeek() == '*')) + { + lip->yySkip(); /* Eat asterisk */ + consume_comment(lip, remaining_recursions_permitted-1); + continue; + } + } + + if (c == '*') + { + if (lip->yyPeek() == '/') + { + lip->yySkip(); /* Eat slash */ + return FALSE; + } + } + + if (c == '\n') + lip->yylineno++; + } + + return TRUE; +} + + /* MYSQLlex remember the following states from the following MYSQLlex() @@ -1204,6 +1251,8 @@ int MYSQLlex(void *arg, void *yythd) /* Reject '/' '*', since we might need to turn off the echo */ lip->yyUnget(); + lip->save_in_comment_state(); + if (lip->yyPeekn(2) == '!') { lip->in_comment= DISCARD_COMMENT; @@ -1246,11 +1295,17 @@ int MYSQLlex(void *arg, void *yythd) /* Expand the content of the special comment as real code */ lip->set_echo(TRUE); state=MY_LEX_START; - break; + break; /* Do not treat contents as a comment. */ + } + else + { + comment_closed= ! consume_comment(lip, 1); + /* version allowed to have one level of comment inside. */ } } else { + /* Not a version comment. */ state=MY_LEX_START; lip->set_echo(TRUE); break; @@ -1261,38 +1316,30 @@ int MYSQLlex(void *arg, void *yythd) lip->in_comment= PRESERVE_COMMENT; lip->yySkip(); // Accept / lip->yySkip(); // Accept * + comment_closed= ! consume_comment(lip, 0); + /* regular comments can have zero comments inside. */ } /* Discard: - regular '/' '*' comments, - special comments '/' '*' '!' for a future version, by scanning until we find a closing '*' '/' marker. - Note: There is no such thing as nesting comments, - the first '*' '/' sequence seen will mark the end. + + Nesting regular comments isn't allowed. The first + '*' '/' returns the parser to the previous state. + + /#!VERSI oned containing /# regular #/ is allowed #/ + + Inside one versioned comment, another versioned comment + is treated as a regular discardable comment. It gets + no special parsing. */ - comment_closed= FALSE; - while (! lip->eof()) - { - c= lip->yyGet(); - if (c == '*') - { - if (lip->yyPeek() == '/') - { - lip->yySkip(); - comment_closed= TRUE; - state = MY_LEX_START; - break; - } - } - else if (c == '\n') - lip->yylineno++; - } + /* Unbalanced comments with a missing '*' '/' are a syntax error */ if (! comment_closed) return (ABORT_SYM); state = MY_LEX_START; // Try again - lip->in_comment= NO_COMMENT; - lip->set_echo(TRUE); + lip->restore_in_comment_state(); break; case MY_LEX_END_LONG_COMMENT: if ((lip->in_comment != NO_COMMENT) && lip->yyPeek() == '/') diff --git a/sql/sql_lex.h b/sql/sql_lex.h index a48b99d07c791cbf24d4efb4b41f215e851860d0..cf825a57fd5df5c8700311cc8cfb310103e50f3d 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1160,6 +1160,18 @@ class Lex_input_stream m_echo= echo; } + void save_in_comment_state() + { + m_echo_saved= m_echo; + in_comment_saved= in_comment; + } + + void restore_in_comment_state() + { + m_echo= m_echo_saved; + in_comment= in_comment_saved; + } + /** Skip binary from the input stream. @param n number of bytes to accept. @@ -1417,6 +1429,7 @@ class Lex_input_stream /** Echo the parsed stream to the pre-processed buffer. */ bool m_echo; + bool m_echo_saved; /** Pre-processed buffer. */ char *m_cpp_buf; @@ -1479,6 +1492,7 @@ class Lex_input_stream /** State of the lexical analyser for comments. */ enum_comment_state in_comment; + enum_comment_state in_comment_saved; /** Starting position of the TEXT_STRING or IDENT in the pre-processed