Skip to content

Commit 4bc3115

Browse files
committed
First draft of using EclipseLink for QueryEnhancing.
Implemented `detectAlias()`, `getProjection()`, `getJoinAliases()`, `hasConstructorExpression()`. `applySorting(...)` and `createCountQueryFor(...)` is not implemented so far since I could not find a way to modify the parsed query so far. Related tickets spring-projects#2458
1 parent a70fff4 commit 4bc3115

File tree

3 files changed

+881
-1
lines changed

3 files changed

+881
-1
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>org.springframework.data</groupId>
88
<artifactId>spring-data-jpa</artifactId>
9-
<version>3.0.0-SNAPSHOT</version>
9+
<version>3.0.0-GH-2458-SNAPSHOT</version>
1010

1111
<name>Spring Data JPA</name>
1212
<description>Spring Data module for JPA repositories.</description>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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 java.util.Collection;
19+
import java.util.HashSet;
20+
import java.util.LinkedList;
21+
import java.util.Set;
22+
import java.util.stream.Collectors;
23+
import java.util.stream.StreamSupport;
24+
25+
import org.eclipse.persistence.jpa.jpql.EclipseLinkGrammarValidator;
26+
import org.eclipse.persistence.jpa.jpql.JPQLQueryProblem;
27+
import org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages;
28+
import org.eclipse.persistence.jpa.jpql.parser.*;
29+
import org.springframework.data.domain.Sort;
30+
31+
public class EclipseLinkQueryEnhancer implements QueryEnhancer {
32+
33+
// https://docs.oracle.com/javaee/6/tutorial/doc/bnbuf.html
34+
35+
private final DeclaredQuery query;
36+
private Expression memorizedRootExpression;
37+
38+
/**
39+
* @param query the query we want to enhance. Must not be {@literal null}.
40+
*/
41+
public EclipseLinkQueryEnhancer(DeclaredQuery query) {
42+
this.query = query;
43+
}
44+
45+
@Override
46+
public String applySorting(Sort sort, String alias) {
47+
// TODO: impl
48+
throw new UnsupportedOperationException("Not implemented");
49+
}
50+
51+
@Override
52+
public String detectAlias() {
53+
Expression queryStatement = parseQuery();
54+
55+
if (!(queryStatement instanceof SelectStatement selectStatement)) {
56+
// TODO: warning
57+
throw new IllegalArgumentException("Cannot work with update delete statement");
58+
}
59+
60+
FromClause fromClause = (FromClause) selectStatement.getFromClause();
61+
62+
IdentificationVariableDeclaration declaration;
63+
64+
if (fromClause.getDeclaration()instanceof IdentificationVariableDeclaration identificationVariableDeclaration) {
65+
declaration = identificationVariableDeclaration;
66+
} else if (fromClause.getDeclaration()instanceof CollectionExpression collectionExpression) {
67+
if (collectionExpression.childrenSize() == 0) {
68+
throw new IllegalStateException("Collection expression within the form clause cannot be empty!");
69+
}
70+
declaration = (IdentificationVariableDeclaration) collectionExpression.children().iterator().next();
71+
} else {
72+
throw new IllegalStateException("From clause contains object that we did not expect! %s"
73+
.formatted(fromClause.getDeclaration().getQueryBNF().toString()));
74+
}
75+
76+
RangeVariableDeclaration rangeVariableDeclaration = (RangeVariableDeclaration) declaration
77+
.getRangeVariableDeclaration();
78+
79+
return rangeVariableDeclaration.getIdentificationVariable().toActualText();
80+
}
81+
82+
@Override
83+
public String createCountQueryFor(String countProjection) {
84+
85+
Expression queryStatement = parseQuery();
86+
SelectStatement selectStatement = (SelectStatement) queryStatement;
87+
88+
if (!(queryStatement instanceof SelectStatement)) {
89+
// TODO: warning
90+
throw new IllegalArgumentException("Cannot work with update delete statement ");
91+
}
92+
93+
String alias = detectAlias();
94+
95+
// TODO: impl
96+
97+
throw new UnsupportedOperationException("Not implemented");
98+
}
99+
100+
@Override
101+
public String getProjection() {
102+
Expression queryStatement = parseQuery();
103+
104+
if (!(queryStatement instanceof SelectStatement)) {
105+
// TODO: warning
106+
throw new IllegalArgumentException("Cannot work with updaet delete statemtn ");
107+
}
108+
109+
SelectClause selectClause = (SelectClause) ((SelectStatement) queryStatement).getSelectClause();
110+
111+
return selectClause.children().toString().replaceFirst("\\[", "").replace("]", "");
112+
}
113+
114+
@Override
115+
public Set<String> getJoinAliases() {
116+
Set<String> joinAliases = new HashSet<>();
117+
118+
Expression queryStatement = parseQuery();
119+
120+
if (!(queryStatement instanceof SelectStatement selectStatement)) {
121+
// TODO: warning
122+
throw new IllegalArgumentException("Cannot work with updaet delete statemtn ");
123+
}
124+
125+
FromClause fromClause = (FromClause) selectStatement.getFromClause();
126+
127+
if (fromClause.getDeclaration()instanceof CollectionExpression collectionExpression) {
128+
joinAliases = StreamSupport.stream(collectionExpression.children().spliterator(), false) //
129+
.filter(IdentificationVariableDeclaration.class::isInstance) //
130+
.map(IdentificationVariableDeclaration.class::cast) //
131+
.flatMap(item -> this.extractJoinsAliases(item).stream())//
132+
.collect(Collectors.toSet());
133+
134+
} else if (fromClause
135+
.getDeclaration()instanceof IdentificationVariableDeclaration identificationVariableDeclaration) {
136+
joinAliases.addAll(extractJoinsAliases(identificationVariableDeclaration));
137+
}
138+
139+
return joinAliases;
140+
}
141+
142+
private Collection<String> extractJoinsAliases(IdentificationVariableDeclaration identificationVariableDeclaration) {
143+
Collection<String> joinAliases = new HashSet<>();
144+
145+
Expression joins = identificationVariableDeclaration.getJoins();
146+
147+
if (joins instanceof CollectionExpression collectionExpression) {
148+
return StreamSupport.stream(collectionExpression.children().spliterator(), false).map(Join.class::cast)
149+
.map(item -> item.getIdentificationVariable().toActualText()).collect(Collectors.toSet());
150+
} else if (joins instanceof Join join) {
151+
return Set.of(join.getIdentificationVariable().toActualText());
152+
}
153+
154+
return joinAliases;
155+
}
156+
157+
@Override
158+
public boolean hasConstructorExpression() {
159+
160+
Expression queryStatement = parseQuery();
161+
162+
if (!(queryStatement instanceof SelectStatement)) {
163+
// TODO: warning
164+
throw new IllegalArgumentException("Cannot work with updaet delete statemtn ");
165+
}
166+
167+
SelectClause selectClause = (SelectClause) ((SelectStatement) queryStatement).getSelectClause();
168+
Expression selectExpression = selectClause.getSelectExpression();
169+
170+
return selectExpression instanceof ConstructorExpression;
171+
}
172+
173+
@Override
174+
public DeclaredQuery getQuery() {
175+
return this.query;
176+
}
177+
178+
/**
179+
* Parses the query that is stored within the {@link EclipseLinkQueryEnhancer#query}. <br/>
180+
* Notice: to optimize performance this function uses memorization.
181+
*
182+
* @return root expression of the parse query
183+
*/
184+
private Expression parseQuery() {
185+
186+
if (this.memorizedRootExpression != null) {
187+
return this.memorizedRootExpression;
188+
}
189+
190+
JPQLExpression jpqlExpression = new JPQLExpression(this.query.getQueryString(),
191+
DefaultEclipseLinkJPQLGrammar.instance(), false);
192+
193+
Collection<JPQLQueryProblem> problems = new LinkedList<>();
194+
195+
// Validate the JPQL query grammatically (based on the JPQL grammar)
196+
EclipseLinkGrammarValidator grammar = new EclipseLinkGrammarValidator(DefaultEclipseLinkJPQLGrammar.instance());
197+
grammar.setProblems(problems);
198+
jpqlExpression.accept(grammar);
199+
200+
// at this state of query processing we do not have the
201+
// parameter which means we have to ignored parsing related errors
202+
problems = problems.stream()
203+
.filter(
204+
item -> !JPQLQueryProblemMessages.InputParameter_MissingParameter.equalsIgnoreCase(item.getMessageKey()))
205+
.toList();
206+
207+
if (!problems.isEmpty()) {
208+
String problemsKeys = problems.stream().map(JPQLQueryProblem::getMessageKey).collect(Collectors.joining(","));
209+
throw new IllegalStateException(
210+
"The JPQL query you provided cannot be parse with EclipseLink since it is not valid JPQL! Detailed problem keys: "
211+
+ problemsKeys);
212+
}
213+
214+
this.memorizedRootExpression = jpqlExpression.getQueryStatement();
215+
return this.memorizedRootExpression;
216+
}
217+
}

0 commit comments

Comments
 (0)