Skip to content

Commit dde9e07

Browse files
gregturnmp911de
authored andcommitted
Implement EQL parser.
Implement support for EclipseLink Query Language (EQL), handling the various extensions it offers. Closes #3170 Original pull request: #3176
1 parent ae12c1c commit dde9e07

File tree

15 files changed

+7056
-45
lines changed

15 files changed

+7056
-45
lines changed

spring-data-jpa/src/main/antlr4/org/springframework/data/jpa/repository/query/Eql.g4

+897
Large diffs are not rendered by default.

spring-data-jpa/src/main/java/org/springframework/data/jpa/provider/PersistenceProvider.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
* @author Greg Turnquist
5656
* @author Yuriy Tsarkov
5757
*/
58-
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment {
58+
public enum PersistenceProvider implements QueryExtractor, ProxyIdAccessor, QueryComment, PresenceDetector {
5959

6060
/**
6161
* Hibernate persistence provider.
@@ -110,6 +110,7 @@ public CloseableIterator<Object> executeQueryWithResultStream(Query jpaQuery) {
110110
public String getCommentHintKey() {
111111
return "org.hibernate.comment";
112112
}
113+
113114
},
114115

115116
/**
@@ -203,6 +204,8 @@ public String getCommentHintKey() {
203204
private final Iterable<String> entityManagerClassNames;
204205
private final Iterable<String> metamodelClassNames;
205206

207+
private boolean present;
208+
206209
/**
207210
* Creates a new {@link PersistenceProvider}.
208211
*
@@ -214,6 +217,13 @@ public String getCommentHintKey() {
214217

215218
this.entityManagerClassNames = entityManagerClassNames;
216219
this.metamodelClassNames = metamodelClassNames;
220+
221+
this.present = false;
222+
entityManagerClassNames.forEach(entityManagerClassName -> {
223+
if (ClassUtils.isPresent(entityManagerClassName, PersistenceProvider.class.getClassLoader())) {
224+
this.present = true;
225+
}
226+
});
217227
}
218228

219229
/**
@@ -330,6 +340,11 @@ public static Object unwrapTypedParameterValue(@Nullable Object value) {
330340
: value;
331341
}
332342

343+
@Override
344+
public boolean isPresent() {
345+
return this.present;
346+
}
347+
333348
/**
334349
* Holds the PersistenceProvider specific interface names.
335350
*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2023 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+
*/
17+
package org.springframework.data.jpa.provider;
18+
19+
/**
20+
* Define operations needed to detect the presence of a given JPA provider.
21+
*
22+
* @author Greg Turnquist
23+
* @since 3.2
24+
*/
25+
interface PresenceDetector {
26+
boolean isPresent();
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2023 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 java.util.List;
19+
20+
import org.antlr.v4.runtime.CharStreams;
21+
import org.antlr.v4.runtime.CommonTokenStream;
22+
import org.antlr.v4.runtime.ParserRuleContext;
23+
import org.springframework.data.domain.Sort;
24+
import org.springframework.lang.Nullable;
25+
26+
/**
27+
* Implements the {@code EQL} parsing operations of a {@link JpaQueryParserSupport} using the ANTLR-generated
28+
* {@link EqlParser} and {@link EqlQueryTransformer}.
29+
*
30+
* @author Greg Turnquist
31+
* @since 3.2
32+
*/
33+
class EqlQueryParser extends JpaQueryParserSupport {
34+
35+
EqlQueryParser(String query) {
36+
super(query);
37+
}
38+
39+
/**
40+
* Convenience method to parse a EQL query. Will throw a {@link BadJpqlGrammarException} if the query is invalid.
41+
*
42+
* @param query
43+
* @return a parsed query, ready for postprocessing
44+
*/
45+
public static ParserRuleContext parseQuery(String query) {
46+
47+
EqlLexer lexer = new EqlLexer(CharStreams.fromString(query));
48+
EqlParser parser = new EqlParser(new CommonTokenStream(lexer));
49+
50+
configureParser(query, lexer, parser);
51+
52+
return parser.start();
53+
}
54+
55+
/**
56+
* Parse the query using {@link #parseQuery(String)}.
57+
*
58+
* @return a parsed query
59+
*/
60+
@Override
61+
protected ParserRuleContext parse(String query) {
62+
return parseQuery(query);
63+
}
64+
65+
/**
66+
* Use the {@link EqlQueryTransformer} to transform the original query into a query with the {@link Sort} applied.
67+
*
68+
* @param parsedQuery
69+
* @param sort can be {@literal null}
70+
* @return list of {@link JpaQueryParsingToken}s
71+
*/
72+
@Override
73+
protected List<JpaQueryParsingToken> applySort(ParserRuleContext parsedQuery, Sort sort) {
74+
return new EqlQueryTransformer(sort).visit(parsedQuery);
75+
}
76+
77+
/**
78+
* Use the {@link EqlQueryTransformer} to transform the original query into a count query.
79+
*
80+
* @param parsedQuery
81+
* @param countProjection
82+
* @return list of {@link JpaQueryParsingToken}s
83+
*/
84+
@Override
85+
protected List<JpaQueryParsingToken> doCreateCountQuery(ParserRuleContext parsedQuery,
86+
@Nullable String countProjection) {
87+
return new EqlQueryTransformer(true, countProjection).visit(parsedQuery);
88+
}
89+
90+
/**
91+
* Run the parsed query through {@link EqlQueryTransformer} to find the primary FROM clause's alias.
92+
*
93+
* @param parsedQuery
94+
* @return can be {@literal null}
95+
*/
96+
@Override
97+
protected String doFindAlias(ParserRuleContext parsedQuery) {
98+
99+
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
100+
transformVisitor.visit(parsedQuery);
101+
return transformVisitor.getAlias();
102+
}
103+
104+
/**
105+
* Use {@link EqlQueryTransformer} to find the projection of the query.
106+
*
107+
* @param parsedQuery
108+
* @return
109+
*/
110+
@Override
111+
protected List<JpaQueryParsingToken> doFindProjection(ParserRuleContext parsedQuery) {
112+
113+
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
114+
transformVisitor.visit(parsedQuery);
115+
return transformVisitor.getProjection();
116+
}
117+
118+
/**
119+
* Use {@link EqlQueryTransformer} to detect if the query uses a {@code new com.example.Dto()} DTO constructor in the
120+
* primary select clause.
121+
*
122+
* @param parsedQuery
123+
* @return Guaranteed to be {@literal true} or {@literal false}.
124+
*/
125+
@Override
126+
protected boolean doCheckForConstructor(ParserRuleContext parsedQuery) {
127+
128+
EqlQueryTransformer transformVisitor = new EqlQueryTransformer();
129+
transformVisitor.visit(parsedQuery);
130+
return transformVisitor.hasConstructorExpression();
131+
}
132+
}

0 commit comments

Comments
 (0)