Skip to content

Commit 5796b90

Browse files
fix: end block comments at the first */ for dialects that do not nest
consumeBlockCommentBody honoured nested block comments under every dialect, so a /* inside a comment opened another level and the comment ran on to a later */. MySQL, MariaDB, SQLite, Oracle, BigQuery and Spanner end a block comment at the first */ and run what follows it, so with Dialect.MYSQL the parser returned SELECT 1 for SELECT 1 /* a /* b */ + 2 -- */, which the server evaluates as SELECT 1 + 2. Dialect gains isNestedBlockComments(), false for those six dialects, and the token manager nests only when the configured dialect does. Without a dialect, and for PostgreSQL, SQL Server, H2 and the other dialects, nesting is unchanged.
1 parent 7123d25 commit 5796b90

3 files changed

Lines changed: 67 additions & 4 deletions

File tree

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public enum Dialect {
5151
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
5252
Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA, SQLITE;
5353

54+
// MySQL, MariaDB, SQLite, Oracle, BigQuery and Spanner document that block comments do
55+
// not nest: the first closing delimiter ends the comment.
56+
private static final Set<Dialect> FLAT_BLOCK_COMMENT_DIALECTS =
57+
EnumSet.of(MYSQL, MARIADB, ORACLE, SQLITE, BIGQUERY, SPANNER);
58+
5459
private final Set<Feature> lexerFeatures;
5560
private final AdjacentStringLiterals adjacentStringLiterals;
5661

@@ -72,6 +77,15 @@ public Set<Feature> getLexerFeatures() {
7277
public AdjacentStringLiterals getAdjacentStringLiterals() {
7378
return adjacentStringLiterals;
7479
}
80+
81+
/**
82+
* Whether a {@code /*} inside a block comment opens a nested comment that needs its own
83+
* closing {@code *}{@code /}, as in the SQL standard, PostgreSQL, SQL Server or H2. The
84+
* other dialects end the comment at the first closing delimiter.
85+
*/
86+
public boolean isNestedBlockComments() {
87+
return !FLAT_BLOCK_COMMENT_DIALECTS.contains(this);
88+
}
7589
}
7690

7791
public P withSquareBracketQuotation() {

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3918,17 +3918,18 @@ TOKEN_MGR_DECLS : {
39183918

39193919
/**
39203920
* Consumes the body of a block comment after the opening delimiter has been matched,
3921-
* honouring nesting, up to and including the outermost closing delimiter. Then backs
3922-
* up 2 characters so the MULTI_LINE_COMMENT rule matches the closing delimiter and
3923-
* terminates the MORE chain.
3921+
* honouring nesting where the dialect nests, up to and including the outermost closing
3922+
* delimiter. Then backs up 2 characters so the MULTI_LINE_COMMENT rule matches the
3923+
* closing delimiter and terminates the MORE chain.
39243924
*/
39253925
void consumeBlockCommentBody() {
3926+
boolean nested = isNestedBlockCommentDialect();
39263927
int nesting = 1;
39273928
char prev = 0;
39283929
try {
39293930
while (nesting > 0) {
39303931
char c = input_stream.readChar();
3931-
if (prev == '/' && c == '*') {
3932+
if (nested && prev == '/' && c == '*') {
39323933
nesting++;
39333934
prev = 0;
39343935
} else if (prev == '*' && c == '/') {
@@ -3944,6 +3945,18 @@ TOKEN_MGR_DECLS : {
39443945
// manager raise the lexical error from IN_BLOCK_COMMENT.
39453946
}
39463947
}
3948+
3949+
// Without a dialect block comments nest as in the SQL standard. A dialect whose server ends
3950+
// the comment at the first closing delimiter must not hide the SQL that follows it.
3951+
private boolean isNestedBlockCommentDialect() {
3952+
Object dialect = configuration.getValue(Feature.dialect);
3953+
for (AbstractJSqlParser.Dialect candidate : AbstractJSqlParser.Dialect.values()) {
3954+
if (candidate.name().equals(dialect)) {
3955+
return candidate.isNestedBlockComments();
3956+
}
3957+
}
3958+
return true;
3959+
}
39473960
}
39483961

39493962
SKIP:

‎src/test/java/net/sf/jsqlparser/statement/select/NestedCommentTest.java‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,13 @@
99
*/
1010
package net.sf.jsqlparser.statement.select;
1111

12+
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
1214
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
1315
import org.junit.jupiter.api.Disabled;
1416
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.EnumSource;
1519

1620
import static org.junit.jupiter.api.Assertions.*;
1721

@@ -102,4 +106,36 @@ void testMultilineNestedComment() {
102106
void testOracleHintPreserved() {
103107
assertParses("SELECT /*+ FULL(t) */ * FROM t");
104108
}
109+
110+
private static String parse(String sql, Dialect dialect) throws JSQLParserException {
111+
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect)).toString();
112+
}
113+
114+
@ParameterizedTest
115+
@EnumSource(value = Dialect.class,
116+
names = {"MYSQL", "MARIADB", "ORACLE", "SQLITE", "BIGQUERY", "SPANNER"})
117+
void testBlockCommentEndsAtFirstDelimiterWhereTheServerDoes(Dialect dialect)
118+
throws JSQLParserException {
119+
assertFalse(dialect.isNestedBlockComments());
120+
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ + 2 -- */", dialect));
121+
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ + 2", dialect));
122+
assertEquals("SELECT 1", parse("SELECT 1 /* a /* b */ /* c */", dialect));
123+
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b */ */", dialect));
124+
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b", dialect));
125+
}
126+
127+
@ParameterizedTest
128+
@EnumSource(value = Dialect.class, names = {"ANSI_SQL", "POSTGRESQL", "SQLSERVER", "H2"})
129+
void testBlockCommentNestsWhereTheServerDoes(Dialect dialect) throws JSQLParserException {
130+
assertTrue(dialect.isNestedBlockComments());
131+
assertEquals("SELECT 1", parse("SELECT 1 /* a /* b */ + 2 -- */", dialect));
132+
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ c */ + 2", dialect));
133+
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b */", dialect));
134+
}
135+
136+
@Test
137+
void testBlockCommentNestsWithoutDialect() throws JSQLParserException {
138+
assertEquals("SELECT 1",
139+
CCJSqlParserUtil.parse("SELECT 1 /* a /* b */ + 2 -- */").toString());
140+
}
105141
}

0 commit comments

Comments
 (0)