Skip to content

Commit 1f29463

Browse files
committed
Polishing.
See #2409.
1 parent 1fe4cf5 commit 1f29463

21 files changed

+329
-393
lines changed

Diff for: pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@
2424

2525
<eclipselink>3.0.2</eclipselink>
2626
<hibernate>5.6.0.Final</hibernate>
27+
<jsqlparser>4.3</jsqlparser>
2728
<mysql-connector-java>8.0.23</mysql-connector-java>
2829
<postgresql>42.2.19</postgresql>
2930
<springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
3031
<vavr>0.10.3</vavr>
31-
<jsqlparser.version>4.3</jsqlparser.version>
3232

3333
<hibernate.groupId>org.hibernate</hibernate.groupId>
3434

Diff for: spring-data-jpa/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@
229229
<dependency>
230230
<groupId>com.github.jsqlparser</groupId>
231231
<artifactId>jsqlparser</artifactId>
232-
<version>${jsqlparser.version}</version>
232+
<version>${jsqlparser}</version>
233233
<scope>provided</scope>
234234
<optional>true</optional>
235235
</dependency>

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/DefaultQueryEnhancer.java

+6-14
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import org.springframework.data.domain.Sort;
19-
2018
import java.util.Set;
2119

20+
import org.springframework.data.domain.Sort;
21+
import org.springframework.lang.Nullable;
22+
2223
/**
2324
* The implementation of {@link QueryEnhancer} using {@link QueryUtils}.
2425
*
2526
* @author Diego Krupitza
27+
* @since 2.7.0
2628
*/
2729
public class DefaultQueryEnhancer implements QueryEnhancer {
2830

@@ -33,17 +35,7 @@ public DefaultQueryEnhancer(DeclaredQuery query) {
3335
}
3436

3537
@Override
36-
public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable<String> idAttributes) {
37-
return QueryUtils.getExistsQueryString(entityName, countQueryPlaceHolder, idAttributes);
38-
}
39-
40-
@Override
41-
public String getQueryString(String template, String entityName) {
42-
return QueryUtils.getQueryString(template, entityName);
43-
}
44-
45-
@Override
46-
public String applySorting(Sort sort, String alias) {
38+
public String applySorting(Sort sort, @Nullable String alias) {
4739
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
4840
}
4941

@@ -53,7 +45,7 @@ public String detectAlias() {
5345
}
5446

5547
@Override
56-
public String createCountQueryFor(String countProjection) {
48+
public String createCountQueryFor(@Nullable String countProjection) {
5749
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
5850
}
5951

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/ExpressionBasedStringQuery.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -69,19 +69,18 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, S
6969
* @param query the original query. Must not be {@literal null}.
7070
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
7171
* @param parser Parser for resolving SpEL expressions. Must not be {@literal null}.
72-
* @param nativeQuery
72+
* @param nativeQuery is a given query native or not
7373
* @return A query supporting SpEL expressions.
7474
*/
75-
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, SpelExpressionParser parser,
76-
boolean nativeQuery) {
75+
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata<?> metadata,
76+
SpelExpressionParser parser, boolean nativeQuery) {
7777
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
7878
}
7979

8080
/**
81-
* @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.}
81+
* @param query, the query expression potentially containing a SpEL expression. Must not be {@literal null}.
8282
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
8383
* @param parser Must not be {@literal null}.
84-
* @return
8584
*/
8685
private static String renderQueryIfExpressionOrReturnQuery(String query, JpaEntityMetadata<?> metadata,
8786
SpelExpressionParser parser) {

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserQueryEnhancer.java

+32-50
Original file line numberDiff line numberDiff line change
@@ -15,38 +15,44 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18+
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
19+
import static org.springframework.data.jpa.repository.query.QueryUtils.*;
20+
1821
import net.sf.jsqlparser.JSQLParserException;
1922
import net.sf.jsqlparser.expression.Alias;
2023
import net.sf.jsqlparser.expression.Expression;
2124
import net.sf.jsqlparser.expression.Function;
22-
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
23-
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
2425
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
2526
import net.sf.jsqlparser.schema.Column;
26-
import net.sf.jsqlparser.schema.Table;
27-
import net.sf.jsqlparser.statement.select.*;
28-
import net.sf.jsqlparser.util.SelectUtils;
27+
import net.sf.jsqlparser.statement.select.OrderByElement;
28+
import net.sf.jsqlparser.statement.select.PlainSelect;
29+
import net.sf.jsqlparser.statement.select.Select;
30+
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
31+
import net.sf.jsqlparser.statement.select.SelectItem;
32+
33+
import java.util.ArrayList;
34+
import java.util.Collections;
35+
import java.util.HashSet;
36+
import java.util.List;
37+
import java.util.Objects;
38+
import java.util.Set;
39+
import java.util.stream.Collectors;
40+
2941
import org.springframework.data.domain.Sort;
30-
import org.springframework.data.util.Streamable;
42+
import org.springframework.lang.Nullable;
3143
import org.springframework.util.Assert;
3244
import org.springframework.util.CollectionUtils;
3345
import org.springframework.util.StringUtils;
3446

35-
import java.util.*;
36-
import java.util.stream.Collectors;
37-
38-
import static org.springframework.data.jpa.repository.query.JSqlParserUtils.*;
39-
import static org.springframework.data.jpa.repository.query.QueryUtils.checkSortExpression;
40-
4147
/**
4248
* The implementation of {@link QueryEnhancer} using JSqlParser.
4349
*
4450
* @author Diego Krupitza
51+
* @author Greg Turnquist
52+
* @since 2.7.0
4553
*/
4654
public class JSqlParserQueryEnhancer implements QueryEnhancer {
4755

48-
private static final String DEFAULT_TABLE_ALIAS = "x";
49-
5056
private final DeclaredQuery query;
5157

5258
/**
@@ -57,40 +63,8 @@ public JSqlParserQueryEnhancer(DeclaredQuery query) {
5763
}
5864

5965
@Override
60-
public String getExistsQueryString(String entityName, String countQueryPlaceHolder, Iterable<String> idAttributes) {
61-
final Table tableNameWithAlias = getTableWithAlias(entityName, DEFAULT_TABLE_ALIAS);
62-
Function jSqlCount = getJSqlCount(Collections.singletonList(countQueryPlaceHolder), false);
63-
64-
Select select = SelectUtils.buildSelectFromTableAndSelectItems(tableNameWithAlias,
65-
new SelectExpressionItem(jSqlCount));
66-
67-
PlainSelect selectBody = (PlainSelect) select.getSelectBody();
68-
69-
List<Expression> equalityExpressions = Streamable.of(idAttributes).stream() //
70-
.map(field -> {
71-
Expression tableNameField = new Column().withTable(tableNameWithAlias).withColumnName(field);
72-
Expression inputField = new Column(":".concat(field));
73-
return new EqualsTo(tableNameField, inputField);
74-
}).collect(Collectors.toList());
75-
76-
if (equalityExpressions.size() > 1) {
77-
AndExpression rootOfWhereClause = concatenateWithAndExpression(equalityExpressions);
78-
selectBody.setWhere(rootOfWhereClause);
79-
} else if (equalityExpressions.size() == 1) {
80-
selectBody.setWhere(equalityExpressions.get(0));
81-
}
66+
public String applySorting(Sort sort, @Nullable String alias) {
8267

83-
return selectBody.toString();
84-
}
85-
86-
@Override
87-
public String getQueryString(String template, String entityName) {
88-
Assert.hasText(entityName, "Entity name must not be null or empty!");
89-
return String.format(template, entityName);
90-
}
91-
92-
@Override
93-
public String applySorting(Sort sort, String alias) {
9468
String queryString = query.getQueryString();
9569
Assert.hasText(queryString, "Query must not be null or empty!");
9670

@@ -145,6 +119,7 @@ private Set<String> getSelectionAliases(PlainSelect selectBody) {
145119
* @return a {@literal Set} containing all found aliases. Guaranteed to be not {@literal null}.
146120
*/
147121
Set<String> getSelectionAliases() {
122+
148123
Select selectStatement = parseSelectStatement(this.query.getQueryString());
149124
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
150125
return this.getSelectionAliases(selectBody);
@@ -189,7 +164,7 @@ private Set<String> getJoinAliases(PlainSelect selectBody) {
189164
* @return a {@link OrderByElement} containing an order clause. Guaranteed to be not {@literal null}.
190165
*/
191166
private OrderByElement getOrderClause(final Set<String> joinAliases, final Set<String> selectionAliases,
192-
final String alias, final Sort.Order order) {
167+
@Nullable final String alias, final Sort.Order order) {
193168

194169
final OrderByElement orderByElement = new OrderByElement();
195170
orderByElement.setAsc(order.getDirection().isAscending());
@@ -233,7 +208,9 @@ public String detectAlias() {
233208
* @param query must not be {@literal null}.
234209
* @return Might return {@literal null}.
235210
*/
211+
@Nullable
236212
private String detectAlias(String query) {
213+
237214
Select selectStatement = parseSelectStatement(query);
238215
PlainSelect selectBody = (PlainSelect) selectStatement.getSelectBody();
239216
return detectAlias(selectBody);
@@ -246,13 +223,15 @@ private String detectAlias(String query) {
246223
* @param selectBody must not be {@literal null}.
247224
* @return Might return {@literal null}.
248225
*/
226+
@Nullable
249227
private static String detectAlias(PlainSelect selectBody) {
228+
250229
Alias alias = selectBody.getFromItem().getAlias();
251230
return alias == null ? null : alias.getName();
252231
}
253232

254233
@Override
255-
public String createCountQueryFor(String countProjection) {
234+
public String createCountQueryFor(@Nullable String countProjection) {
256235

257236
Assert.hasText(this.query.getQueryString(), "OriginalQuery must not be null or empty!");
258237

@@ -298,6 +277,7 @@ public String createCountQueryFor(String countProjection) {
298277

299278
@Override
300279
public String getProjection() {
280+
301281
Assert.hasText(query.getQueryString(), "Query must not be null or empty!");
302282

303283
Select selectStatement = parseSelectStatement(query.getQueryString());
@@ -321,6 +301,7 @@ public Set<String> getJoinAliases() {
321301
* @return the parsed query
322302
*/
323303
private static Select parseSelectStatement(String query) {
304+
324305
try {
325306
return (Select) CCJSqlParserUtil.parse(query);
326307
} catch (JSQLParserException e) {
@@ -329,12 +310,13 @@ private static Select parseSelectStatement(String query) {
329310
}
330311

331312
/**
332-
* Checks whether a given projection only contains a single column definition (aka without functions, etc)
313+
* Checks whether a given projection only contains a single column definition (aka without functions, etc.)
333314
*
334315
* @param projection the projection to analyse
335316
* @return <code>true</code> when the projection only contains a single column definition otherwise <code>false</code>
336317
*/
337318
private boolean onlyASingleColumnProjection(List<SelectItem> projection) {
319+
338320
// this is unfortunately the only way to check without any hacky & hard string regex magic
339321
return projection.size() == 1 && projection.get(0) instanceof SelectExpressionItem
340322
&& (((SelectExpressionItem) projection.get(0)).getExpression()) instanceof Column;

Diff for: spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JSqlParserUtils.java

+7-55
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,10 @@
1515
*/
1616
package org.springframework.data.jpa.repository.query;
1717

18-
import net.sf.jsqlparser.expression.Alias;
1918
import net.sf.jsqlparser.expression.Expression;
2019
import net.sf.jsqlparser.expression.Function;
21-
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
2220
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
2321
import net.sf.jsqlparser.schema.Column;
24-
import net.sf.jsqlparser.schema.Table;
25-
import org.springframework.util.CollectionUtils;
26-
import org.springframework.util.StringUtils;
2722

2823
import java.util.Collections;
2924
import java.util.List;
@@ -33,58 +28,12 @@
3328
* A utility class for JSqlParser.
3429
*
3530
* @author Diego Krupitza
31+
* @author Greg Turnquist
32+
* @since 2.7.0
3633
*/
3734
public final class JSqlParserUtils {
3835

39-
private JSqlParserUtils() {
40-
}
41-
42-
/**
43-
* Generates a JSqlParser table from an entity name and an optional alias name
44-
*
45-
* @param entityName the name of the table
46-
* @param alias the optional alias. Might be {@literal null} or empty
47-
* @return the newly generated table
48-
*/
49-
public static Table getTableWithAlias(String entityName, String alias) {
50-
Table table = new Table(entityName);
51-
return StringUtils.hasText(alias) ? table.withAlias(new Alias(alias)) : table;
52-
}
53-
54-
/**
55-
* Concatenates a list of expression with <code>AND</code>.
56-
*
57-
* @param expressions the list of expressions to concatenate. Has to be non empty and with size >= 2
58-
* @return the root of the concatenated expression
59-
*/
60-
public static AndExpression concatenateWithAndExpression(List<Expression> expressions) {
61-
62-
if (CollectionUtils.isEmpty(expressions) || expressions.size() == 1) {
63-
throw new IllegalArgumentException(
64-
"The list of expression has to be at least of length 2! Otherwise it is not possible to concatinate with an");
65-
}
66-
67-
AndExpression rootAndExpression = new AndExpression();
68-
AndExpression currentLocation = rootAndExpression;
69-
70-
// traverse the list with looking 1 element ahead
71-
for (int i = 0; i < expressions.size(); i++) {
72-
Expression currentExpression = expressions.get(i);
73-
if (currentLocation.getLeftExpression() == null) {
74-
currentLocation.setLeftExpression(currentExpression);
75-
} else if (currentLocation.getRightExpression() == null && i == expressions.size() - 1) {
76-
currentLocation.setRightExpression(currentExpression);
77-
} else {
78-
AndExpression nextAndExpression = new AndExpression();
79-
nextAndExpression.setLeftExpression(currentExpression);
80-
81-
currentLocation.setRightExpression(nextAndExpression);
82-
currentLocation = (AndExpression) currentLocation.getRightExpression();
83-
}
84-
}
85-
86-
return rootAndExpression;
87-
}
36+
private JSqlParserUtils() {}
8837

8938
/**
9039
* Generates a count function call, based on the {@code countFields}.
@@ -94,12 +43,14 @@ public static AndExpression concatenateWithAndExpression(List<Expression> expres
9443
* @return the generated count function call
9544
*/
9645
public static Function getJSqlCount(final List<String> countFields, final boolean distinct) {
46+
9747
List<Expression> countColumns = countFields //
9848
.stream() //
9949
.map(Column::new) //
10050
.collect(Collectors.toList());
10151

10252
ExpressionList countExpression = new ExpressionList(countColumns);
53+
10354
return new Function() //
10455
.withName("count") //
10556
.withParameters(countExpression) //
@@ -113,11 +64,12 @@ public static Function getJSqlCount(final List<String> countFields, final boolea
11364
* @return the generated lower function call
11465
*/
11566
public static Function getJSqlLower(String column) {
67+
11668
List<Expression> expressions = Collections.singletonList(new Column(column));
11769
ExpressionList lowerParamExpression = new ExpressionList(expressions);
70+
11871
return new Function() //
11972
.withName("lower") //
12073
.withParameters(lowerParamExpression);
12174
}
122-
12375
}

0 commit comments

Comments
 (0)