Skip to content

Commit 19f12ea

Browse files
authored
feat: expose leading comments via getLeadingComments() (#2779)
* feat(parser): expose leading comments via getLeadingComments() Downstream SQL rewriters currently recover statement-leading comments from the JavaCC special-token chain via getASTNode(), whose erasure changed within 5.x and broke compiled downstream adapters. Signed-off-by: 付典 <fudianchn@gmail.com> * feat(parser): expose leading comments via getLeadingComments() Keep the serial UID from 066f581 so existing serialized models remain readable. Return the original comment token images without line terminators. Signed-off-by: 付典 <fudianchn@gmail.com> * refactor(parser): remove the fixed serialVersionUID Adding getLeadingComments changes the computed UID; previously serialized AST objects may no longer be readable. Signed-off-by: 付典 <fudianchn@gmail.com> --------- Signed-off-by: 付典 <fudianchn@gmail.com>
1 parent 4ecc8bc commit 19f12ea

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

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

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

12+
import java.util.ArrayList;
13+
import java.util.Collections;
14+
import java.util.List;
1215
import java.util.Set;
1316
import java.util.TreeSet;
1417

@@ -26,6 +29,26 @@ public void setASTNode(Node node) {
2629
this.node = node;
2730
}
2831

32+
/**
33+
* The comments preceding this node's first token, in source order, with the original token
34+
* images. Line comments do not include their line terminator; callers must add a line break
35+
* when prepending them to SQL. Empty for nodes built programmatically, which carry no AST.
36+
*/
37+
public List<String> getLeadingComments() {
38+
Node node = getASTNode();
39+
if (node == null) {
40+
return new ArrayList<>();
41+
}
42+
List<String> comments = new ArrayList<>();
43+
for (Token specialToken =
44+
node.jjtGetFirstToken().specialToken; specialToken != null; specialToken =
45+
specialToken.specialToken) {
46+
comments.add(specialToken.image);
47+
}
48+
Collections.reverse(comments);
49+
return comments;
50+
}
51+
2952
public StringBuilder appendTo(StringBuilder builder) {
3053
// don't add spaces around the following punctuation
3154
final Set<String> punctuation = new TreeSet<>(Set.of(".", "[", "]"));
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2019 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.parser;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
import static org.junit.jupiter.api.Assertions.assertFalse;
14+
15+
import java.util.List;
16+
import net.sf.jsqlparser.JSQLParserException;
17+
import net.sf.jsqlparser.statement.Statements;
18+
import net.sf.jsqlparser.statement.select.PlainSelect;
19+
import net.sf.jsqlparser.statement.select.Select;
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.ValueSource;
23+
24+
public class LeadingCommentsTest {
25+
26+
@Test
27+
public void blockCommentsBeforeStatementInSourceOrder() throws JSQLParserException {
28+
Select select = (Select) CCJSqlParserUtil
29+
.parse("/* keep orderby */ /*+ FULL(t) */ SELECT * FROM mytable");
30+
assertEquals(List.of("/* keep orderby */", "/*+ FULL(t) */"), select.getLeadingComments());
31+
}
32+
33+
@ParameterizedTest
34+
@ValueSource(strings = {"\n", "\r\n", "\r"})
35+
public void lineCommentsExcludeLineTerminators(String lineTerminator)
36+
throws JSQLParserException {
37+
Select select =
38+
(Select) CCJSqlParserUtil.parse("-- paging hint" + lineTerminator + "SELECT 1");
39+
assertEquals(List.of("-- paging hint"), select.getLeadingComments());
40+
}
41+
42+
@Test
43+
public void mixedCommentsRetainExactImagesAndSourceOrder() throws JSQLParserException {
44+
Select select = (Select) CCJSqlParserUtil
45+
.parse("/* a */ -- b\r\n// c\r/* d */ SELECT 1");
46+
assertEquals(List.of("/* a */", "-- b", "// c", "/* d */"), select.getLeadingComments());
47+
}
48+
49+
@Test
50+
public void commentInsideStatementIsNotLeading() throws JSQLParserException {
51+
Select select = (Select) CCJSqlParserUtil.parse("SELECT /* mid comment */ 1");
52+
assertEquals(List.of(), select.getLeadingComments());
53+
}
54+
55+
@Test
56+
public void statementWithoutComments() throws JSQLParserException {
57+
Select select = (Select) CCJSqlParserUtil.parse("SELECT * FROM mytable");
58+
assertEquals(List.of(), select.getLeadingComments());
59+
}
60+
61+
@Test
62+
public void programmaticStatementHasNoLeadingComments() {
63+
assertEquals(List.of(), new PlainSelect().getLeadingComments());
64+
}
65+
66+
@Test
67+
public void toStringDropsLeadingCommentsAsBefore() throws JSQLParserException {
68+
Select select = (Select) CCJSqlParserUtil
69+
.parse("/* keep orderby */ SELECT * FROM mytable");
70+
assertFalse(select.toString().contains("/*"));
71+
}
72+
73+
@Test
74+
public void commentsSplitPerStatement() throws JSQLParserException {
75+
Statements statements = CCJSqlParserUtil
76+
.parseStatements("/* first */ SELECT 1; /* second */ SELECT 2");
77+
assertEquals(List.of("/* first */"), ((Select) statements.get(0)).getLeadingComments());
78+
assertEquals(List.of("/* second */"), ((Select) statements.get(1)).getLeadingComments());
79+
}
80+
}

‎src/test/java/net/sf/jsqlparser/statement/SerializationTest.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.io.ObjectInputStream;
1616
import java.io.ObjectOutputStream;
1717
import java.math.BigDecimal;
18+
import java.util.List;
1819
import java.util.stream.Stream;
1920
import net.sf.jsqlparser.JSQLParserException;
2021
import net.sf.jsqlparser.expression.SpannerInterleaveIn;
@@ -34,6 +35,17 @@
3435
import org.junit.jupiter.params.provider.ValueSource;
3536

3637
public class SerializationTest {
38+
@Test
39+
void serializeSelectWithLeadingComments() throws Exception {
40+
PlainSelect original = (PlainSelect) CCJSqlParserUtil
41+
.parse("/* keep orderby */ SELECT id FROM mytable");
42+
Assertions.assertEquals(List.of("/* keep orderby */"), original.getLeadingComments());
43+
PlainSelect copy = roundTrip(original);
44+
Assertions.assertEquals("SELECT id FROM mytable", copy.toString());
45+
Assertions.assertNull(copy.getASTNode());
46+
Assertions.assertEquals(List.of(), copy.getLeadingComments());
47+
}
48+
3749
@Test
3850
void serializeWithItem() throws JSQLParserException, IOException, ClassNotFoundException {
3951
String sqlStr =

0 commit comments

Comments
 (0)