Skip to content
Merged
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
15 changes: 15 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,21 @@ public P withLegacyMySqlGroupBy(boolean enabled) {
return withFeature(Feature.allowLegacyMySqlGroupBy, 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.
*
* @param version major * 10000 + minor * 100 + patch, from 0 through 999999
* @return this parser
*/
public P withMySqlServerVersion(int version) {
if (version < 0 || version > 999999) {
throw new IllegalArgumentException("MySQL server version must be between 0 and 999999");
}
return withFeature(Feature.mySqlServerVersion, (long) version);
}

public P withTimeOut(long timeOutMillSeconds) {
return withFeature(Feature.timeOut, timeOutMillSeconds);
}
Expand Down
120 changes: 119 additions & 1 deletion src/main/java/net/sf/jsqlparser/parser/SimpleCharStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ public class SimpleCharStream {
// the `#` in the token image. Wired (with the token manager's
// configuration) before parsing, null keeps this inert.
static final char HASH_SUBSTITUTION = '\u0001';
static final char MYSQL_MINUS_SUBSTITUTION = '\u0002';
static final char MYSQL_COMMENT_ERROR = '\u0003';
FeatureConfiguration featureConfiguration;
private boolean mySqlExecutableComment;
String mySqlCommentError;
boolean mySqlMinusSubstitution;

/**
* Constructor.
Expand Down Expand Up @@ -173,8 +178,24 @@ protected void FillBuff() throws java.io.IOException {
* Start.
*/
public final char BeginToken() throws java.io.IOException {
mySqlMinusSubstitution = false;
if (mySqlCommentError != null) {
// Parser lookahead may catch lexical errors. Keep this failure sticky until ReInit
// rather than allowing a later token request to continue after the rejected marker.
return MYSQL_COMMENT_ERROR;
}
tokenBegin = -1;
char c = readChar();
char c;
try {
c = readChar();
} catch (java.io.IOException e) {
if (!mySqlExecutableComment) {
throw e;
}
// JavaCC treats exceptions from BeginToken as EOF. Emit a real token whose lexical
// action reports the error, so an unclosed executable comment cannot succeed as SQL.
c = mySqlCommentError("Unterminated MySQL executable comment");
}
tokenBegin = bufpos;

absoluteTokenBegin = totalCharsRead;
Expand All @@ -183,10 +204,103 @@ public final char BeginToken() throws java.io.IOException {
&& featureConfiguration.getAsBoolean(Feature.allowHashLineComments)) {
buffer[bufpos] = HASH_SUBSTITUTION;
c = HASH_SUBSTITUTION;
} else if ((c == '-' || c == '/' || c == '*') && featureConfiguration != null
&& AbstractJSqlParser.Dialect.MYSQL.name()
.equals(featureConfiguration.getValue(Feature.dialect))) {
c = mySqlCommentBoundary(c);
}
return c;
}

/** Changes only token boundaries, never characters being scanned inside strings or names. */
private char mySqlCommentBoundary(char c) throws java.io.IOException {
if (c == '-') {
String following = peekCharacters(2);
if (following.length() == 2 && following.charAt(0) == '-'
&& !isMySqlCommentSpace(following.charAt(1))) {
mySqlMinusSubstitution = true;
buffer[bufpos] = MYSQL_MINUS_SUBSTITUTION;
return MYSQL_MINUS_SUBSTITUTION;
}
} else if (c == '*' && mySqlExecutableComment && peekCharacters(1).equals("/")) {
eraseCommentMarker(2);
mySqlExecutableComment = false;
return ' ';
} else if (c == '/') {
String following = peekCharacters(9);
if (following.startsWith("*!")) {
if (mySqlExecutableComment) {
return mySqlCommentError("Nested MySQL executable comments are not supported");
}
int digits = 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) {
return c;
}
}
eraseCommentMarker(3 + digits);
mySqlExecutableComment = true;
return ' ';
}
}
return c;
}

private static boolean isMySqlCommentSpace(char c) {
return c <= ' ' || c == '\u007f';
}

private static int mySqlCommentVersionLength(String following) {
if (following.length() < 7) {
return 0;
}
for (int i = 2; i < 7; i++) {
if (following.charAt(i) < '0' || following.charAt(i) > '9') {
return 0;
}
}
// MySQL recognizes a sixth digit only when followed by whitespace. Otherwise the
// five-digit prefix is the version and the remaining characters belong to the SQL body.
return following.length() >= 9 && following.charAt(7) >= '0'
&& following.charAt(7) <= '9' && (following.charAt(8) == ' '
|| following.charAt(8) >= '\t' && following.charAt(8) <= '\r')
? 6
: 5;
}

private String peekCharacters(int length) {
StringBuilder result = new StringBuilder(length);
try {
for (int i = 0; i < length; i++) {
result.append(readChar());
}
} catch (java.io.IOException ignored) {
// Only the successfully read characters need to be rewound, including at EOF.
}
backup(result.length());
return result.toString();
}

private void eraseCommentMarker(int length) throws java.io.IOException {
buffer[bufpos] = ' ';
for (int i = 1; i < length; i++) {
readChar();
buffer[bufpos] = ' ';
}
backup(length - 1);
}

private char mySqlCommentError(String message) {
mySqlCommentError = message;
return MYSQL_COMMENT_ERROR;
}

protected final void UpdateLineColumn(char c) {
column++;

Expand Down Expand Up @@ -330,6 +444,8 @@ public void ReInit(Provider dstream, int startline, int startcolumn, int buffers
prevCharIsLF = prevCharIsCR = false;
tokenBegin = inBuf = maxNextCharInd = 0;
bufpos = -1;
mySqlExecutableComment = false;
mySqlCommentError = null;
}

/**
Expand Down Expand Up @@ -362,6 +478,8 @@ public String GetImage() {
String image = doGetImage();
if (!image.isEmpty() && image.charAt(0) == HASH_SUBSTITUTION) {
image = "#" + image.substring(1);
} else if (!image.isEmpty() && image.charAt(0) == MYSQL_MINUS_SUBSTITUTION) {
image = "-" + image.substring(1);
}
return image;
}
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,14 @@ public enum Feature {
/** Enables legacy GROUP BY ordering with the MYSQL dialect; disabled by default. */
allowLegacyMySqlGroupBy(false),

/**
* 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.
*/
mySqlServerVersion(-1),

// PERFORMANCE

/**
Expand Down
18 changes: 18 additions & 0 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -2199,6 +2199,12 @@ TOKEN_MGR_DECLS : {

private RowPatternTokenContext rowPatternContext;

private void reportMySqlCommentError() {
throw new TokenMgrException(input_stream.mySqlCommentError == null
? "Unexpected control character" : input_stream.mySqlCommentError,
TokenMgrException.LEXICAL_ERROR);
}

public void CommonTokenAction(Token t)
{
t.absoluteBegin = getCurrentTokenAbsolutePosition();
Expand Down Expand Up @@ -2889,6 +2895,18 @@ TOKEN : /* Operators */
| <OP_RSHIFT: ">>">
| <OP_JSON_PATH_DELETE: "#-">
| <OP_JSON_PATH_EXISTS: "@?">
| <OP_MINUS: "-">
| <MYSQL_MINUS: "\u0002"> {
if (!input_stream.mySqlMinusSubstitution) {
input_stream.mySqlCommentError = "Unexpected control character";
reportMySqlCommentError();
}
matchedToken.kind = OP_MINUS;
matchedToken.image = "-";
}
| <MYSQL_EXECUTABLE_COMMENT_ERROR: "\u0003"> {
reportMySqlCommentError();
}
| <OP_DOUBLEAND: "&&">
| <OP_CONTAINS: "&>">
| <OP_CONTAINEDBY: "<&">
Expand Down
30 changes: 30 additions & 0 deletions src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,36 @@ One grammar covers every supported RDBMS, but a few pieces of syntax mean differ

Features set explicitly *after* the preset win over it.

MySQL comments
~~~~~~~~~~~~~~

With ``Dialect.MYSQL``, ``--`` starts a line comment only when followed by
whitespace or a control character. Thus ``SELECT 1--2`` contains subtraction
and a negative operand. SQL inside an unversioned executable comment is parsed
normally: ``SELECT 1 /*! + 2 */`` becomes ``SELECT 1 + 2``. Optimizer hints
(``/*+ ... */``) remain separate from executable comments.

Conditional executable comments require an explicit target server version:

.. code-block:: java

Statement stmt = CCJSqlParserUtil.parse(
"SELECT 1 /*!90702 + 2 */",
parser -> parser.withDialect(Dialect.MYSQL).withMySqlServerVersion(90702));

The value is ``major * 10000 + minor * 100 + patch``; for example, ``90702``
means 9.7.2 and ``260700`` means 26.7.0. Five-digit and six-digit comment version
prefixes follow MySQL's whitespace rules. SQL for a newer server is ignored;
SQL for the configured version or an older one participates in the AST.
Both SQL renderers produce the resolved SQL for that target, without the
conditional wrappers. Without a target, a conditional executable comment
raises an error instead of silently discarding its SQL. This replaces the
previous treatment of all executable comments as ordinary discarded comments.
The same configuration applies to parsers constructed with a ``StreamProvider``
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.

ALTER column names
~~~~~~~~~~~~~~~~~~

Expand Down
Loading
Loading