From b991dad036c12e2a7bbe8249c4c00293e11b6579 Mon Sep 17 00:00:00 2001 From: tanjiro Kamado Date: Mon, 2 Nov 2026 23:52:38 +0530 Subject: [PATCH] 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. --- .../jsqlparser/parser/AbstractJSqlParser.java | 5 ++- .../jsqlparser/parser/SimpleCharStream.java | 41 +++++++++++++++---- .../sf/jsqlparser/parser/feature/Feature.java | 4 +- src/site/sphinx/usage.rst | 6 +++ .../jsqlparser/parser/MySqlCommentTest.java | 30 +++++++++++++- 5 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java index 7cee29a5e8..4984f9ae95 100644 --- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java +++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java @@ -113,8 +113,9 @@ public P withLegacyMySqlGroupBy(boolean enabled) { /** * Resolves MySQL executable comments against this server version (for example 90702 for 9.7.2). - * Requires the MYSQL dialect. Rendered SQL reflects this version, without conditional wrappers. - * Without an explicit version, conditional executable comments raise a lexical error. + * Requires the MYSQL dialect, or MARIADB with the MariaDB version (for example 110402 for + * 11.4.2). Rendered SQL reflects this version, without conditional wrappers. Without an + * explicit version, conditional executable comments raise a lexical error. * * @param version major * 10000 + minor * 100 + patch, from 0 through 999999 * @return this parser diff --git a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java index 5b6c5e8259..47fcbb90ef 100644 --- a/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java +++ b/src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java @@ -242,8 +242,7 @@ public final char BeginToken() throws java.io.IOException { buffer[bufpos] = HASH_SUBSTITUTION; c = HASH_SUBSTITUTION; } else if ((c == '-' || c == '/' || c == '*') && featureConfiguration != null - && AbstractJSqlParser.Dialect.MYSQL.name() - .equals(featureConfiguration.getValue(Feature.dialect))) { + && isMySqlCommentDialect(featureConfiguration.getValue(Feature.dialect))) { c = mySqlCommentBoundary(c); } else if (c == '\n' && featureConfiguration != null && !featureConfiguration @@ -257,6 +256,12 @@ && peekCharacters(2).equals("\n\n")) { return c; } + /** MariaDB shares the MySQL rules for {@code --} and executable comments. */ + private static boolean isMySqlCommentDialect(Object dialect) { + return AbstractJSqlParser.Dialect.MYSQL.name().equals(dialect) + || AbstractJSqlParser.Dialect.MARIADB.name().equals(dialect); + } + /** Changes only token boundaries, never characters being scanned inside strings or names. */ private char mySqlCommentBoundary(char c) throws java.io.IOException { if (c == '-') { @@ -273,23 +278,31 @@ private char mySqlCommentBoundary(char c) throws java.io.IOException { return ' '; } else if (c == '/') { String following = peekCharacters(9); - if (following.startsWith("*!")) { + boolean mariaDb = AbstractJSqlParser.Dialect.MARIADB.name() + .equals(featureConfiguration.getValue(Feature.dialect)); + // MariaDB also runs its own /*M! ... */ comments, which MySQL ignores. + int marker = following.startsWith("*!") ? 2 + : mariaDb && following.startsWith("*M!") ? 3 : 0; + if (marker > 0) { if (mySqlExecutableComment) { return mySqlCommentError("Nested MySQL executable comments are not supported"); } - int digits = mySqlCommentVersionLength(following); + int digits = mariaDb ? mariaDbCommentVersionLength(following, marker) + : mySqlCommentVersionLength(following); if (digits > 0) { long target = featureConfiguration.getAsLong(Feature.mySqlServerVersion); if (target < 0 || target > 999999) { return mySqlCommentError("MySQL conditional executable comments require " + "withMySqlServerVersion(major * 10000 + minor * 100 + patch)"); } - int minimum = Integer.parseInt(following.substring(2, 2 + digits)); - if (minimum > target) { + int minimum = Integer.parseInt(following.substring(marker, marker + digits)); + // MariaDB skips /*! comments for MySQL 5.7 and later, but not /*M! ones. + if (minimum > target + || mariaDb && marker == 2 && minimum >= 50700 && minimum <= 99999) { return c; } } - eraseCommentMarker(3 + digits); + eraseCommentMarker(marker + 1 + digits); mySqlExecutableComment = true; return ' '; } @@ -319,6 +332,20 @@ private static int mySqlCommentVersionLength(String following) { : 5; } + private static int mariaDbCommentVersionLength(String following, int marker) { + if (following.length() < marker + 5) { + return 0; + } + for (int i = marker; i < marker + 5; i++) { + if (following.charAt(i) < '0' || following.charAt(i) > '9') { + return 0; + } + } + // MariaDB takes a sixth digit whenever there is one, whatever follows it. + return following.length() > marker + 5 && following.charAt(marker + 5) >= '0' + && following.charAt(marker + 5) <= '9' ? 6 : 5; + } + private String peekCharacters(int length) { StringBuilder result = new StringBuilder(length); try { diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java index 3ce49efd6a..d73b0c6529 100644 --- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java +++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java @@ -958,8 +958,8 @@ public enum Feature { /** * Target MySQL version for executable comments, encoded as major * 10000 + minor * 100 + patch. * A negative value leaves the version unspecified: conditional comments then fail instead of - * silently discarding SQL. Only used with the MYSQL dialect; unversioned comments need no - * value. + * silently discarding SQL. Only used with the MYSQL and MARIADB dialects (with MARIADB, the + * MariaDB version); unversioned comments need no value. */ mySqlServerVersion(-1), diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst index 250dda5b6f..59073bf3b8 100644 --- a/src/site/sphinx/usage.rst +++ b/src/site/sphinx/usage.rst @@ -1646,6 +1646,12 @@ or ``FeatureConfiguration``. Other dialects retain their comment behavior. Nested executable comments are rejected; nested-comment acceptance in other dialects is not a guarantee that the same SQL is valid on MySQL. +``Dialect.MARIADB`` follows the same rules, with ``withMySqlServerVersion`` +taking the MariaDB version (``110402`` for 11.4.2). As on the server, +``/*M! ... */`` and ``/*M!###### ... */`` are executable there too, a sixth +version digit is always part of the version, and ``/*!50700`` through +``/*!99999`` (MySQL 5.7 and later) are skipped. + ALTER column names ~~~~~~~~~~~~~~~~~~ diff --git a/src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java b/src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java index e1fa6d5f93..7a4410aad8 100644 --- a/src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java +++ b/src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java @@ -28,6 +28,7 @@ import net.sf.jsqlparser.util.deparser.StatementDeParser; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; class MySqlCommentTest { @@ -242,11 +243,38 @@ void rejectsLiteralInternalMinusMarkerInEveryDialect() { } } + // Expected results checked against MariaDB 13.0.2 + @ParameterizedTest + @CsvSource(delimiter = '|', value = {"SELECT 1--2|SELECT 1 - -2", + "SELECT 1 /*! +2 */|SELECT 1 + 2", "SELECT 1 /*M! +2 */|SELECT 1 + 2", + "SELECT 1 /*!50000 +2 */|SELECT 1 + 2", "SELECT 1 /*!50700 +2 */|SELECT 1", + "SELECT 1 /*!99999 +2 */|SELECT 1", "SELECT 1 /*M!50700 +2 */|SELECT 1 + 2", + "SELECT 1 /*!130002 +2 */|SELECT 1 + 2", "SELECT 1 /*!130003 +2 */|SELECT 1", + "SELECT 1 /*M!130002 +2 */|SELECT 1 + 2", "SELECT 1 /*M!130003 +2 */|SELECT 1", + "SELECT 1 /*!100000+2 */|SELECT 1 + 2", "SELECT 1 /*M!100000+2 */|SELECT 1 + 2", + "SELECT 1 /*m! +2 */|SELECT 1", + "SELECT 1 /*M! UNION SELECT pw FROM secrets */|SELECT 1 UNION SELECT pw FROM secrets"}) + void followsMariaDbCommentRules(String sql, String expected) throws Exception { + assertEquals(expected, CCJSqlParserUtil.parse(sql, + p -> p.withDialect(Dialect.MARIADB).withMySqlServerVersion(130002)).toString()); + } + + @Test + void requiresVersionForMariaDbConditionalComments() { + JSQLParserException exception = assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT 1 /*M!100000 +2 */", + p -> p.withDialect(Dialect.MARIADB))); + assertTrue(exception.getCause().getMessage().contains("withMySqlServerVersion")); + assertThrows(JSQLParserException.class, + () -> CCJSqlParserUtil.parse("SELECT /*M! 1 /*! +99 */ + 2 */", + p -> p.withDialect(Dialect.MARIADB))); + } + @Test void preservesOtherDialectCommentsAndQuotedBodies() throws Exception { assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1--2").toString()); for (Dialect dialect : Dialect.values()) { - if (dialect != Dialect.MYSQL) { + if (dialect != Dialect.MYSQL && dialect != Dialect.MARIADB) { assertEquals("SELECT 1", CCJSqlParserUtil.parse("SELECT 1 /*!90702 +2 */ --3", p -> p.withDialect(dialect)).toString(), dialect.name()); }