Skip to content

Commit 1fe4cf5

Browse files
DiegoKrupitzagregturn
authored andcommitted
Introduce JSqlParser as option for parsing native queries.
Introduce a library that makes it possible to parse native SQL statements. See #2409.
1 parent 8c127f6 commit 1fe4cf5

29 files changed

+2391
-284
lines changed

Diff for: pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
<postgresql>42.2.19</postgresql>
2929
<springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
3030
<vavr>0.10.3</vavr>
31+
<jsqlparser.version>4.3</jsqlparser.version>
3132

3233
<hibernate.groupId>org.hibernate</hibernate.groupId>
3334

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

+8
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,14 @@
226226
<scope>test</scope>
227227
</dependency>
228228

229+
<dependency>
230+
<groupId>com.github.jsqlparser</groupId>
231+
<artifactId>jsqlparser</artifactId>
232+
<version>${jsqlparser.version}</version>
233+
<scope>provided</scope>
234+
<optional>true</optional>
235+
</dependency>
236+
229237
</dependencies>
230238

231239
<build>

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

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

18-
import java.util.Arrays;
19-
import java.util.Collection;
20-
import java.util.HashMap;
21-
import java.util.List;
22-
import java.util.Map;
23-
import java.util.Set;
24-
import java.util.stream.Collectors;
25-
2618
import jakarta.persistence.EntityManager;
2719
import jakarta.persistence.LockModeType;
2820
import jakarta.persistence.Query;
@@ -31,6 +23,14 @@
3123
import jakarta.persistence.TupleElement;
3224
import jakarta.persistence.TypedQuery;
3325

26+
import java.util.Arrays;
27+
import java.util.Collection;
28+
import java.util.HashMap;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.Set;
32+
import java.util.stream.Collectors;
33+
3434
import org.springframework.core.convert.converter.Converter;
3535
import org.springframework.data.jpa.provider.PersistenceProvider;
3636
import org.springframework.data.jpa.repository.EntityGraph;

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
* @author Tom Hombergs
3535
* @author David Madden
3636
* @author Mark Paluch
37+
* @author Diego Krupitza
3738
*/
3839
abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
3940

@@ -55,8 +56,8 @@ abstract class AbstractStringBasedJpaQuery extends AbstractJpaQuery {
5556
* @param parser must not be {@literal null}.
5657
*/
5758
public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, String queryString,
58-
@Nullable String countQueryString,
59-
QueryMethodEvaluationContextProvider evaluationContextProvider, SpelExpressionParser parser) {
59+
@Nullable String countQueryString, QueryMethodEvaluationContextProvider evaluationContextProvider,
60+
SpelExpressionParser parser) {
6061

6162
super(method, em);
6263

@@ -65,10 +66,12 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
6566
Assert.notNull(parser, "Parser must not be null!");
6667

6768
this.evaluationContextProvider = evaluationContextProvider;
68-
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser);
69+
this.query = new ExpressionBasedStringQuery(queryString, method.getEntityInformation(), parser,
70+
method.isNativeQuery());
6971

7072
DeclaredQuery countQuery = query.deriveCountQuery(countQueryString, method.getCountQueryProjection());
71-
this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser);
73+
this.countQuery = ExpressionBasedStringQuery.from(countQuery, method.getEntityInformation(), parser,
74+
method.isNativeQuery());
7275

7376
this.parser = parser;
7477

@@ -79,7 +82,8 @@ public AbstractStringBasedJpaQuery(JpaQueryMethod method, EntityManager em, Stri
7982
@Override
8083
public Query doCreateQuery(JpaParametersParameterAccessor accessor) {
8184

82-
String sortedQueryString = QueryUtils.applySorting(query.getQueryString(), accessor.getSort(), query.getAlias());
85+
String sortedQueryString = QueryEnhancerFactory.forQuery(query) //
86+
.applySorting(accessor.getSort(), query.getAlias());
8387
ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);
8488

8589
Query query = createJpaQuery(sortedQueryString, processor.getReturnedType());

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

+13-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* A wrapper for a String representation of a query offering information about the query.
2525
*
2626
* @author Jens Schauder
27+
* @author Diego Krupitza
2728
* @since 2.0.3
2829
*/
2930
interface DeclaredQuery {
@@ -32,10 +33,11 @@ interface DeclaredQuery {
3233
* Creates a {@literal DeclaredQuery} from a query {@literal String}.
3334
*
3435
* @param query might be {@literal null} or empty.
36+
* @param nativeQuery is a given query is native or not
3537
* @return a {@literal DeclaredQuery} instance even for a {@literal null} or empty argument.
3638
*/
37-
static DeclaredQuery of(@Nullable String query) {
38-
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query);
39+
static DeclaredQuery of(@Nullable String query, boolean nativeQuery) {
40+
return ObjectUtils.isEmpty(query) ? EmptyDeclaredQuery.EMPTY_QUERY : new StringQuery(query, nativeQuery);
3941
}
4042

4143
/**
@@ -100,4 +102,13 @@ default boolean usesPaging() {
100102
* @since 2.0.6
101103
*/
102104
boolean usesJdbcStyleParameters();
105+
106+
/**
107+
* Return whether the query is a native query of not.
108+
*
109+
* @return <code>true</code> if native query otherwise <code>false</code>
110+
*/
111+
default boolean isNativeQuery() {
112+
return false;
113+
}
103114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.jpa.repository.query;
17+
18+
import org.springframework.data.domain.Sort;
19+
20+
import java.util.Set;
21+
22+
/**
23+
* The implementation of {@link QueryEnhancer} using {@link QueryUtils}.
24+
*
25+
* @author Diego Krupitza
26+
*/
27+
public class DefaultQueryEnhancer implements QueryEnhancer {
28+
29+
private final DeclaredQuery query;
30+
31+
public DefaultQueryEnhancer(DeclaredQuery query) {
32+
this.query = query;
33+
}
34+
35+
@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) {
47+
return QueryUtils.applySorting(this.query.getQueryString(), sort, alias);
48+
}
49+
50+
@Override
51+
public String detectAlias() {
52+
return QueryUtils.detectAlias(this.query.getQueryString());
53+
}
54+
55+
@Override
56+
public String createCountQueryFor(String countProjection) {
57+
return QueryUtils.createCountQueryFor(this.query.getQueryString(), countProjection);
58+
}
59+
60+
@Override
61+
public String getProjection() {
62+
return QueryUtils.getProjection(this.query.getQueryString());
63+
}
64+
65+
@Override
66+
public Set<String> getJoinAliases() {
67+
return QueryUtils.getOuterJoinAliases(this.query.getQueryString());
68+
}
69+
70+
@Override
71+
public DeclaredQuery getQuery() {
72+
return this.query;
73+
}
74+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public DeclaredQuery deriveCountQuery(@Nullable String countQuery, @Nullable Str
6969

7070
Assert.hasText(countQuery, "CountQuery must not be empty!");
7171

72-
return DeclaredQuery.of(countQuery);
72+
return DeclaredQuery.of(countQuery, false);
7373
}
7474

7575
@Override

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* @author Oliver Gierke
3737
* @author Tom Hombergs
3838
* @author Michael J. Simons
39+
* @author Diego Krupitza
3940
*/
4041
class ExpressionBasedStringQuery extends StringQuery {
4142

@@ -55,9 +56,11 @@ class ExpressionBasedStringQuery extends StringQuery {
5556
* @param query must not be {@literal null} or empty.
5657
* @param metadata must not be {@literal null}.
5758
* @param parser must not be {@literal null}.
59+
* @param nativeQuery is a given query is native or not
5860
*/
59-
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser) {
60-
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser));
61+
public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, SpelExpressionParser parser,
62+
boolean nativeQuery) {
63+
super(renderQueryIfExpressionOrReturnQuery(query, metadata, parser), nativeQuery && !containsExpression(query));
6164
}
6265

6366
/**
@@ -66,11 +69,12 @@ public ExpressionBasedStringQuery(String query, JpaEntityMetadata<?> metadata, S
6669
* @param query the original query. Must not be {@literal null}.
6770
* @param metadata the {@link JpaEntityMetadata} for the given entity. Must not be {@literal null}.
6871
* @param parser Parser for resolving SpEL expressions. Must not be {@literal null}.
72+
* @param nativeQuery
6973
* @return A query supporting SpEL expressions.
7074
*/
71-
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata,
72-
SpelExpressionParser parser) {
73-
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser);
75+
static ExpressionBasedStringQuery from(DeclaredQuery query, JpaEntityMetadata metadata, SpelExpressionParser parser,
76+
boolean nativeQuery) {
77+
return new ExpressionBasedStringQuery(query.getQueryString(), metadata, parser, nativeQuery);
7478
}
7579

7680
/**

0 commit comments

Comments
 (0)