Skip to content

Commit b991dad

Browse files
fix: apply the MySQL comment rules to Dialect.MARIADB
The -- whitespace rule and executable comments were only lexed the MySQL way under Dialect.MYSQL. MariaDB follows the same rules and also runs /*M! ... */, so under MARIADB SQL the server executes was dropped as a comment: SELECT 1 /*M! UNION SELECT pw FROM secrets */ parsed as SELECT 1. The rules now apply to MARIADB as well, with MariaDB's own version handling: a sixth digit always belongs to the version, and /*!50700 through /*!99999 are skipped while /*M! ones are not.
1 parent 7123d25 commit b991dad

5 files changed

Lines changed: 74 additions & 12 deletions

File tree

‎src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ public P withLegacyMySqlGroupBy(boolean enabled) {
113113

114114
/**
115115
* Resolves MySQL executable comments against this server version (for example 90702 for 9.7.2).
116-
* Requires the MYSQL dialect. Rendered SQL reflects this version, without conditional wrappers.
117-
* Without an explicit version, conditional executable comments raise a lexical error.
116+
* Requires the MYSQL dialect, or MARIADB with the MariaDB version (for example 110402 for
117+
* 11.4.2). Rendered SQL reflects this version, without conditional wrappers. Without an
118+
* explicit version, conditional executable comments raise a lexical error.
118119
*
119120
* @param version major * 10000 + minor * 100 + patch, from 0 through 999999
120121
* @return this parser

‎src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java‎

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,7 @@ public final char BeginToken() throws java.io.IOException {
242242
buffer[bufpos] = HASH_SUBSTITUTION;
243243
c = HASH_SUBSTITUTION;
244244
} else if ((c == '-' || c == '/' || c == '*') && featureConfiguration != null
245-
&& AbstractJSqlParser.Dialect.MYSQL.name()
246-
.equals(featureConfiguration.getValue(Feature.dialect))) {
245+
&& isMySqlCommentDialect(featureConfiguration.getValue(Feature.dialect))) {
247246
c = mySqlCommentBoundary(c);
248247
} else if (c == '\n' && featureConfiguration != null
249248
&& !featureConfiguration
@@ -257,6 +256,12 @@ && peekCharacters(2).equals("\n\n")) {
257256
return c;
258257
}
259258

259+
/** MariaDB shares the MySQL rules for {@code --} and executable comments. */
260+
private static boolean isMySqlCommentDialect(Object dialect) {
261+
return AbstractJSqlParser.Dialect.MYSQL.name().equals(dialect)
262+
|| AbstractJSqlParser.Dialect.MARIADB.name().equals(dialect);
263+
}
264+
260265
/** Changes only token boundaries, never characters being scanned inside strings or names. */
261266
private char mySqlCommentBoundary(char c) throws java.io.IOException {
262267
if (c == '-') {
@@ -273,23 +278,31 @@ private char mySqlCommentBoundary(char c) throws java.io.IOException {
273278
return ' ';
274279
} else if (c == '/') {
275280
String following = peekCharacters(9);
276-
if (following.startsWith("*!")) {
281+
boolean mariaDb = AbstractJSqlParser.Dialect.MARIADB.name()
282+
.equals(featureConfiguration.getValue(Feature.dialect));
283+
// MariaDB also runs its own /*M! ... */ comments, which MySQL ignores.
284+
int marker = following.startsWith("*!") ? 2
285+
: mariaDb && following.startsWith("*M!") ? 3 : 0;
286+
if (marker > 0) {
277287
if (mySqlExecutableComment) {
278288
return mySqlCommentError("Nested MySQL executable comments are not supported");
279289
}
280-
int digits = mySqlCommentVersionLength(following);
290+
int digits = mariaDb ? mariaDbCommentVersionLength(following, marker)
291+
: mySqlCommentVersionLength(following);
281292
if (digits > 0) {
282293
long target = featureConfiguration.getAsLong(Feature.mySqlServerVersion);
283294
if (target < 0 || target > 999999) {
284295
return mySqlCommentError("MySQL conditional executable comments require "
285296
+ "withMySqlServerVersion(major * 10000 + minor * 100 + patch)");
286297
}
287-
int minimum = Integer.parseInt(following.substring(2, 2 + digits));
288-
if (minimum > target) {
298+
int minimum = Integer.parseInt(following.substring(marker, marker + digits));
299+
// MariaDB skips /*! comments for MySQL 5.7 and later, but not /*M! ones.
300+
if (minimum > target
301+
|| mariaDb && marker == 2 && minimum >= 50700 && minimum <= 99999) {
289302
return c;
290303
}
291304
}
292-
eraseCommentMarker(3 + digits);
305+
eraseCommentMarker(marker + 1 + digits);
293306
mySqlExecutableComment = true;
294307
return ' ';
295308
}
@@ -319,6 +332,20 @@ private static int mySqlCommentVersionLength(String following) {
319332
: 5;
320333
}
321334

335+
private static int mariaDbCommentVersionLength(String following, int marker) {
336+
if (following.length() < marker + 5) {
337+
return 0;
338+
}
339+
for (int i = marker; i < marker + 5; i++) {
340+
if (following.charAt(i) < '0' || following.charAt(i) > '9') {
341+
return 0;
342+
}
343+
}
344+
// MariaDB takes a sixth digit whenever there is one, whatever follows it.
345+
return following.length() > marker + 5 && following.charAt(marker + 5) >= '0'
346+
&& following.charAt(marker + 5) <= '9' ? 6 : 5;
347+
}
348+
322349
private String peekCharacters(int length) {
323350
StringBuilder result = new StringBuilder(length);
324351
try {

‎src/main/java/net/sf/jsqlparser/parser/feature/Feature.java‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -958,8 +958,8 @@ public enum Feature {
958958
/**
959959
* Target MySQL version for executable comments, encoded as major * 10000 + minor * 100 + patch.
960960
* A negative value leaves the version unspecified: conditional comments then fail instead of
961-
* silently discarding SQL. Only used with the MYSQL dialect; unversioned comments need no
962-
* value.
961+
* silently discarding SQL. Only used with the MYSQL and MARIADB dialects (with MARIADB, the
962+
* MariaDB version); unversioned comments need no value.
963963
*/
964964
mySqlServerVersion(-1),
965965

‎src/site/sphinx/usage.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1646,6 +1646,12 @@ or ``FeatureConfiguration``. Other dialects retain their comment behavior.
16461646
Nested executable comments are rejected; nested-comment acceptance in other
16471647
dialects is not a guarantee that the same SQL is valid on MySQL.
16481648

1649+
``Dialect.MARIADB`` follows the same rules, with ``withMySqlServerVersion``
1650+
taking the MariaDB version (``110402`` for 11.4.2). As on the server,
1651+
``/*M! ... */`` and ``/*M!###### ... */`` are executable there too, a sixth
1652+
version digit is always part of the version, and ``/*!50700`` through
1653+
``/*!99999`` (MySQL 5.7 and later) are skipped.
1654+
16491655
ALTER column names
16501656
~~~~~~~~~~~~~~~~~~
16511657

‎src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java‎

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import net.sf.jsqlparser.util.deparser.StatementDeParser;
2929
import org.junit.jupiter.api.Test;
3030
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.CsvSource;
3132
import org.junit.jupiter.params.provider.ValueSource;
3233

3334
class MySqlCommentTest {
@@ -242,11 +243,38 @@ void rejectsLiteralInternalMinusMarkerInEveryDialect() {
242243
}
243244
}
244245

246+
// Expected results checked against MariaDB 13.0.2
247+
@ParameterizedTest
248+
@CsvSource(delimiter = '|', value = {"SELECT 1--2|SELECT 1 - -2",
249+
"SELECT 1 /*! +2 */|SELECT 1 + 2", "SELECT 1 /*M! +2 */|SELECT 1 + 2",
250+
"SELECT 1 /*!50000 +2 */|SELECT 1 + 2", "SELECT 1 /*!50700 +2 */|SELECT 1",
251+
"SELECT 1 /*!99999 +2 */|SELECT 1", "SELECT 1 /*M!50700 +2 */|SELECT 1 + 2",
252+
"SELECT 1 /*!130002 +2 */|SELECT 1 + 2", "SELECT 1 /*!130003 +2 */|SELECT 1",
253+
"SELECT 1 /*M!130002 +2 */|SELECT 1 + 2", "SELECT 1 /*M!130003 +2 */|SELECT 1",
254+
"SELECT 1 /*!100000+2 */|SELECT 1 + 2", "SELECT 1 /*M!100000+2 */|SELECT 1 + 2",
255+
"SELECT 1 /*m! +2 */|SELECT 1",
256+
"SELECT 1 /*M! UNION SELECT pw FROM secrets */|SELECT 1 UNION SELECT pw FROM secrets"})
257+
void followsMariaDbCommentRules(String sql, String expected) throws Exception {
258+
assertEquals(expected, CCJSqlParserUtil.parse(sql,
259+
p -> p.withDialect(Dialect.MARIADB).withMySqlServerVersion(130002)).toString());
260+
}
261+
262+
@Test
263+
void requiresVersionForMariaDbConditionalComments() {
264+
JSQLParserException exception = assertThrows(JSQLParserException.class,
265+
() -> CCJSqlParserUtil.parse("SELECT 1 /*M!100000 +2 */",
266+
p -> p.withDialect(Dialect.MARIADB)));
267+
assertTrue(exception.getCause().getMessage().contains("withMySqlServerVersion"));
268+
assertThrows(JSQLParserException.class,
269+
() -> CCJSqlParserUtil.parse("SELECT /*M! 1 /*! +99 */ + 2 */",
270+
p -> p.withDialect(Dialect.MARIADB)));
271+
}
272+
245273
@Test
246274
void preservesOtherDialectCommentsAndQuotedBodies() throws Exception {
247275
assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1--2").toString());
248276
for (Dialect dialect : Dialect.values()) {
249-
if (dialect != Dialect.MYSQL) {
277+
if (dialect != Dialect.MYSQL && dialect != Dialect.MARIADB) {
250278
assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1 /*!90702 +2 */ --3",
251279
p -> p.withDialect(dialect)).toString(), dialect.name());
252280
}

0 commit comments

Comments
 (0)