@@ -60,7 +60,12 @@ public class SimpleCharStream {
6060 // the `#` in the token image. Wired (with the token manager's
6161 // configuration) before parsing, null keeps this inert.
6262 static final char HASH_SUBSTITUTION = '\u0001' ;
63+ static final char MYSQL_MINUS_SUBSTITUTION = '\u0002' ;
64+ static final char MYSQL_COMMENT_ERROR = '\u0003' ;
6365 FeatureConfiguration featureConfiguration ;
66+ private boolean mySqlExecutableComment ;
67+ String mySqlCommentError ;
68+ boolean mySqlMinusSubstitution ;
6469
6570 /**
6671 * Constructor.
@@ -173,8 +178,24 @@ protected void FillBuff() throws java.io.IOException {
173178 * Start.
174179 */
175180 public final char BeginToken () throws java .io .IOException {
181+ mySqlMinusSubstitution = false ;
182+ if (mySqlCommentError != null ) {
183+ // Parser lookahead may catch lexical errors. Keep this failure sticky until ReInit
184+ // rather than allowing a later token request to continue after the rejected marker.
185+ return MYSQL_COMMENT_ERROR ;
186+ }
176187 tokenBegin = -1 ;
177- char c = readChar ();
188+ char c ;
189+ try {
190+ c = readChar ();
191+ } catch (java .io .IOException e ) {
192+ if (!mySqlExecutableComment ) {
193+ throw e ;
194+ }
195+ // JavaCC treats exceptions from BeginToken as EOF. Emit a real token whose lexical
196+ // action reports the error, so an unclosed executable comment cannot succeed as SQL.
197+ c = mySqlCommentError ("Unterminated MySQL executable comment" );
198+ }
178199 tokenBegin = bufpos ;
179200
180201 absoluteTokenBegin = totalCharsRead ;
@@ -183,10 +204,103 @@ public final char BeginToken() throws java.io.IOException {
183204 && featureConfiguration .getAsBoolean (Feature .allowHashLineComments )) {
184205 buffer [bufpos ] = HASH_SUBSTITUTION ;
185206 c = HASH_SUBSTITUTION ;
207+ } else if ((c == '-' || c == '/' || c == '*' ) && featureConfiguration != null
208+ && AbstractJSqlParser .Dialect .MYSQL .name ()
209+ .equals (featureConfiguration .getValue (Feature .dialect ))) {
210+ c = mySqlCommentBoundary (c );
186211 }
187212 return c ;
188213 }
189214
215+ /** Changes only token boundaries, never characters being scanned inside strings or names. */
216+ private char mySqlCommentBoundary (char c ) throws java .io .IOException {
217+ if (c == '-' ) {
218+ String following = peekCharacters (2 );
219+ if (following .length () == 2 && following .charAt (0 ) == '-'
220+ && !isMySqlCommentSpace (following .charAt (1 ))) {
221+ mySqlMinusSubstitution = true ;
222+ buffer [bufpos ] = MYSQL_MINUS_SUBSTITUTION ;
223+ return MYSQL_MINUS_SUBSTITUTION ;
224+ }
225+ } else if (c == '*' && mySqlExecutableComment && peekCharacters (1 ).equals ("/" )) {
226+ eraseCommentMarker (2 );
227+ mySqlExecutableComment = false ;
228+ return ' ' ;
229+ } else if (c == '/' ) {
230+ String following = peekCharacters (9 );
231+ if (following .startsWith ("*!" )) {
232+ if (mySqlExecutableComment ) {
233+ return mySqlCommentError ("Nested MySQL executable comments are not supported" );
234+ }
235+ int digits = mySqlCommentVersionLength (following );
236+ if (digits > 0 ) {
237+ long target = featureConfiguration .getAsLong (Feature .mySqlServerVersion );
238+ if (target < 0 || target > 999999 ) {
239+ return mySqlCommentError ("MySQL conditional executable comments require "
240+ + "withMySqlServerVersion(major * 10000 + minor * 100 + patch)" );
241+ }
242+ int minimum = Integer .parseInt (following .substring (2 , 2 + digits ));
243+ if (minimum > target ) {
244+ return c ;
245+ }
246+ }
247+ eraseCommentMarker (3 + digits );
248+ mySqlExecutableComment = true ;
249+ return ' ' ;
250+ }
251+ }
252+ return c ;
253+ }
254+
255+ private static boolean isMySqlCommentSpace (char c ) {
256+ return c <= ' ' || c == '\u007f' ;
257+ }
258+
259+ private static int mySqlCommentVersionLength (String following ) {
260+ if (following .length () < 7 ) {
261+ return 0 ;
262+ }
263+ for (int i = 2 ; i < 7 ; i ++) {
264+ if (following .charAt (i ) < '0' || following .charAt (i ) > '9' ) {
265+ return 0 ;
266+ }
267+ }
268+ // MySQL recognizes a sixth digit only when followed by whitespace. Otherwise the
269+ // five-digit prefix is the version and the remaining characters belong to the SQL body.
270+ return following .length () >= 9 && following .charAt (7 ) >= '0'
271+ && following .charAt (7 ) <= '9' && (following .charAt (8 ) == ' '
272+ || following .charAt (8 ) >= '\t' && following .charAt (8 ) <= '\r' )
273+ ? 6
274+ : 5 ;
275+ }
276+
277+ private String peekCharacters (int length ) {
278+ StringBuilder result = new StringBuilder (length );
279+ try {
280+ for (int i = 0 ; i < length ; i ++) {
281+ result .append (readChar ());
282+ }
283+ } catch (java .io .IOException ignored ) {
284+ // Only the successfully read characters need to be rewound, including at EOF.
285+ }
286+ backup (result .length ());
287+ return result .toString ();
288+ }
289+
290+ private void eraseCommentMarker (int length ) throws java .io .IOException {
291+ buffer [bufpos ] = ' ' ;
292+ for (int i = 1 ; i < length ; i ++) {
293+ readChar ();
294+ buffer [bufpos ] = ' ' ;
295+ }
296+ backup (length - 1 );
297+ }
298+
299+ private char mySqlCommentError (String message ) {
300+ mySqlCommentError = message ;
301+ return MYSQL_COMMENT_ERROR ;
302+ }
303+
190304 protected final void UpdateLineColumn (char c ) {
191305 column ++;
192306
@@ -330,6 +444,8 @@ public void ReInit(Provider dstream, int startline, int startcolumn, int buffers
330444 prevCharIsLF = prevCharIsCR = false ;
331445 tokenBegin = inBuf = maxNextCharInd = 0 ;
332446 bufpos = -1 ;
447+ mySqlExecutableComment = false ;
448+ mySqlCommentError = null ;
333449 }
334450
335451 /**
@@ -362,6 +478,8 @@ public String GetImage() {
362478 String image = doGetImage ();
363479 if (!image .isEmpty () && image .charAt (0 ) == HASH_SUBSTITUTION ) {
364480 image = "#" + image .substring (1 );
481+ } else if (!image .isEmpty () && image .charAt (0 ) == MYSQL_MINUS_SUBSTITUTION ) {
482+ image = "-" + image .substring (1 );
365483 }
366484 return image ;
367485 }
0 commit comments