Skip to content

Commit 444573c

Browse files
committed
Visit optional DISTINCT ON items without changing the AST
1 parent b2115ac commit 444573c

2 files changed

Lines changed: 100 additions & 11 deletions

File tree

src/main/java/net/sf/jsqlparser/statement/select/SelectVisitorAdapter.java

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

12+
import java.util.List;
1213
import net.sf.jsqlparser.expression.ExpressionVisitor;
1314
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
1415
import net.sf.jsqlparser.expression.Function;
@@ -67,11 +68,7 @@ public SelectVisitorAdapter(ExpressionVisitor<T> expressionVisitor) {
6768
@Override
6869
public <S> T visitOutputClause(OutputClause outputClause, S context) {
6970
if (outputClause != null) {
70-
if (outputClause.getSelectItemList() != null) {
71-
for (SelectItem<?> selectItem : outputClause.getSelectItemList()) {
72-
selectItem.accept(selectItemVisitor, context);
73-
}
74-
}
71+
visitSelectItems(outputClause.getSelectItemList(), context);
7572
if (outputClause.getTableVariable() != null) {
7673
outputClause.getTableVariable().accept(expressionVisitor, context);
7774
}
@@ -86,6 +83,14 @@ public <S> T visitOutputClause(OutputClause outputClause, S context) {
8683
return null;
8784
}
8885

86+
private <S> void visitSelectItems(List<SelectItem<?>> items, S context) {
87+
if (items != null) {
88+
for (SelectItem<?> item : items) {
89+
item.accept(selectItemVisitor, context);
90+
}
91+
}
92+
}
93+
8994
public ExpressionVisitor<T> getExpressionVisitor() {
9095
return expressionVisitor;
9196
}
@@ -146,18 +151,14 @@ public <S> T visit(PlainSelect plainSelect, S context) {
146151
visitWithItems(plainSelect.withItemsList, context);
147152

148153
if (plainSelect.getDistinct() != null) {
149-
for (SelectItem<?> selectItem : plainSelect.getDistinct().getOnSelectItems()) {
150-
selectItem.accept(selectItemVisitor, context);
151-
}
154+
visitSelectItems(plainSelect.getDistinct().getOnSelectItems(), context);
152155
}
153156

154157
if (plainSelect.getTop() != null) {
155158
plainSelect.getTop().getExpression().accept(expressionVisitor, context);
156159
}
157160

158-
for (SelectItem<?> selectItem : plainSelect.getSelectItems()) {
159-
selectItem.accept(selectItemVisitor, context);
160-
}
161+
visitSelectItems(plainSelect.getSelectItems(), context);
161162

162163
if (plainSelect.getMySqlSelectIntoClause() != null) {
163164
MySqlSelectIntoClause mySqlSelectIntoClause = plainSelect.getMySqlSelectIntoClause();
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import static org.assertj.core.api.Assertions.assertThat;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import net.sf.jsqlparser.JSQLParserException;
17+
import net.sf.jsqlparser.expression.Expression;
18+
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
19+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
20+
import net.sf.jsqlparser.statement.select.FromItemVisitorAdapter;
21+
import net.sf.jsqlparser.statement.select.PivotVisitorAdapter;
22+
import net.sf.jsqlparser.statement.select.PlainSelect;
23+
import net.sf.jsqlparser.statement.select.Select;
24+
import net.sf.jsqlparser.statement.select.SelectItem;
25+
import net.sf.jsqlparser.statement.select.SelectItemVisitorAdapter;
26+
import net.sf.jsqlparser.statement.select.SelectVisitorAdapter;
27+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.params.provider.ValueSource;
31+
32+
class DistinctVisitorTest {
33+
@ParameterizedTest
34+
@ValueSource(strings = {"SELECT DISTINCT id FROM foo",
35+
"SELECT * FROM (SELECT DISTINCT id FROM foo) x",
36+
"WITH x AS (SELECT DISTINCT id FROM foo) SELECT * FROM x",
37+
"SELECT DISTINCT ON (id) id FROM foo", "SELECT ALL id FROM foo"})
38+
void analysisPreservesSelectSyntax(String sql) throws JSQLParserException {
39+
Statement statement = CCJSqlParserUtil.parse(sql);
40+
String before = statement.toString();
41+
StatementFeatures features = statement.getFeatures();
42+
assertThat(features.returnsResultSet()).isTrue();
43+
assertThat(features.mayModifyData()).isFalse();
44+
assertThat(features.getUnresolvedReferences()).isEmpty();
45+
statement.accept(new StatementVisitorAdapter<>(), null);
46+
assertThat(statement.toString()).isEqualTo(before);
47+
StringBuilder deparsed = new StringBuilder();
48+
statement.accept(new StatementDeParser(deparsed), null);
49+
assertThat(CCJSqlParserUtil.parse(deparsed.toString()).toString()).isEqualTo(before);
50+
}
51+
52+
@Test
53+
void plainDistinctKeepsItsAbsentOnList() throws JSQLParserException {
54+
PlainSelect select = (PlainSelect) CCJSqlParserUtil.parse("SELECT DISTINCT id FROM foo");
55+
select.accept(new SelectVisitorAdapter<>(), null);
56+
assertThat(select.getDistinct().getOnSelectItems()).isNull();
57+
assertThat(select.toString()).isEqualTo("SELECT DISTINCT id FROM foo");
58+
}
59+
60+
@Test
61+
void selectAndOutputItemsKeepCallbackOrderAndContext() throws JSQLParserException {
62+
List<String> seen = new ArrayList<>();
63+
Object marker = new Object();
64+
SelectItemVisitorAdapter<Void> items = new SelectItemVisitorAdapter<Void>() {
65+
@Override
66+
public <S> Void visit(SelectItem<? extends Expression> item, S context) {
67+
assertThat(context).isSameAs(marker);
68+
seen.add(item.getExpression().toString());
69+
return null;
70+
}
71+
};
72+
SelectVisitorAdapter<Void> visitor = new SelectVisitorAdapter<>(
73+
new ExpressionVisitorAdapter<>(), new PivotVisitorAdapter<>(), items,
74+
new FromItemVisitorAdapter<>());
75+
Select select = (Select) CCJSqlParserUtil
76+
.parse("SELECT DISTINCT ON (key_fn(id)) value_fn(id), id FROM foo");
77+
select.accept(visitor, marker);
78+
assertThat(seen).containsExactly("key_fn(id)", "value_fn(id)", "id");
79+
assertThat(select.getFeatures().getUnresolvedReferences())
80+
.containsExactly("key_fn", "value_fn");
81+
82+
seen.clear();
83+
PlainSelect output = (PlainSelect) CCJSqlParserUtil.parse("SELECT a, b");
84+
visitor.visitOutputClause(new OutputClause(output.getSelectItems(), null, null, null),
85+
marker);
86+
assertThat(seen).containsExactly("a", "b");
87+
}
88+
}

0 commit comments

Comments
 (0)