Skip to content
Closed
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
14 changes: 14 additions & 0 deletions src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public enum Dialect {
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
Feature.allowBackslashEscapeCharacter), INFORMIX, SPANNER, DORIS, COCKROACHDB, TERADATA, SQLITE;

// MySQL, MariaDB, SQLite, Oracle, BigQuery and Spanner document that block comments do
// not nest: the first closing delimiter ends the comment.
private static final Set<Dialect> FLAT_BLOCK_COMMENT_DIALECTS =
EnumSet.of(MYSQL, MARIADB, ORACLE, SQLITE, BIGQUERY, SPANNER);

private final Set<Feature> lexerFeatures;
private final AdjacentStringLiterals adjacentStringLiterals;

Expand All @@ -72,6 +77,15 @@ public Set<Feature> getLexerFeatures() {
public AdjacentStringLiterals getAdjacentStringLiterals() {
return adjacentStringLiterals;
}

/**
* Whether a {@code /*} inside a block comment opens a nested comment that needs its own
* closing {@code *}{@code /}, as in the SQL standard, PostgreSQL, SQL Server or H2. The
* other dialects end the comment at the first closing delimiter.
*/
public boolean isNestedBlockComments() {
return !FLAT_BLOCK_COMMENT_DIALECTS.contains(this);
}
}

public P withSquareBracketQuotation() {
Expand Down
21 changes: 17 additions & 4 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -3918,17 +3918,18 @@ TOKEN_MGR_DECLS : {

/**
* Consumes the body of a block comment after the opening delimiter has been matched,
* honouring nesting, up to and including the outermost closing delimiter. Then backs
* up 2 characters so the MULTI_LINE_COMMENT rule matches the closing delimiter and
* terminates the MORE chain.
* honouring nesting where the dialect nests, up to and including the outermost closing
* delimiter. Then backs up 2 characters so the MULTI_LINE_COMMENT rule matches the
* closing delimiter and terminates the MORE chain.
*/
void consumeBlockCommentBody() {
boolean nested = isNestedBlockCommentDialect();
int nesting = 1;
char prev = 0;
try {
while (nesting > 0) {
char c = input_stream.readChar();
if (prev == '/' && c == '*') {
if (nested && prev == '/' && c == '*') {
nesting++;
prev = 0;
} else if (prev == '*' && c == '/') {
Expand All @@ -3944,6 +3945,18 @@ TOKEN_MGR_DECLS : {
// manager raise the lexical error from IN_BLOCK_COMMENT.
}
}

// Without a dialect block comments nest as in the SQL standard. A dialect whose server ends
// the comment at the first closing delimiter must not hide the SQL that follows it.
private boolean isNestedBlockCommentDialect() {
Object dialect = configuration.getValue(Feature.dialect);
for (AbstractJSqlParser.Dialect candidate : AbstractJSqlParser.Dialect.values()) {
if (candidate.name().equals(dialect)) {
return candidate.isNestedBlockComments();
}
}
return true;
}
}

SKIP:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@
*/
package net.sf.jsqlparser.statement.select;

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

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

Expand Down Expand Up @@ -102,4 +106,36 @@ void testMultilineNestedComment() {
void testOracleHintPreserved() {
assertParses("SELECT /*+ FULL(t) */ * FROM t");
}

private static String parse(String sql, Dialect dialect) throws JSQLParserException {
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(dialect)).toString();
}

@ParameterizedTest
@EnumSource(value = Dialect.class,
names = {"MYSQL", "MARIADB", "ORACLE", "SQLITE", "BIGQUERY", "SPANNER"})
void testBlockCommentEndsAtFirstDelimiterWhereTheServerDoes(Dialect dialect)
throws JSQLParserException {
assertFalse(dialect.isNestedBlockComments());
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ + 2 -- */", dialect));
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ + 2", dialect));
assertEquals("SELECT 1", parse("SELECT 1 /* a /* b */ /* c */", dialect));
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b */ */", dialect));
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b", dialect));
}

@ParameterizedTest
@EnumSource(value = Dialect.class, names = {"ANSI_SQL", "POSTGRESQL", "SQLSERVER", "H2"})
void testBlockCommentNestsWhereTheServerDoes(Dialect dialect) throws JSQLParserException {
assertTrue(dialect.isNestedBlockComments());
assertEquals("SELECT 1", parse("SELECT 1 /* a /* b */ + 2 -- */", dialect));
assertEquals("SELECT 1 + 2", parse("SELECT 1 /* a /* b */ c */ + 2", dialect));
assertThrows(JSQLParserException.class, () -> parse("SELECT 1 /* a /* b */", dialect));
}

@Test
void testBlockCommentNestsWithoutDialect() throws JSQLParserException {
assertEquals("SELECT 1",
CCJSqlParserUtil.parse("SELECT 1 /* a /* b */ + 2 -- */").toString());
}
}
Loading