Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 34 additions & 7 deletions src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 == '-') {
Expand All @@ -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 ' ';
}
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
6 changes: 6 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~~~~

Expand Down
30 changes: 29 additions & 1 deletion src/test/java/net/sf/jsqlparser/parser/MySqlCommentTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
}
Expand Down
Loading