Skip to content

Commit a7b737b

Browse files
committed
Hacking on Keyset queries.
1 parent e58f1cb commit a7b737b

13 files changed

+347
-120
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaCountQueryCreator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public JpaCountQueryCreator(PartTree tree, ReturnedType returnedType, ParameterM
5454
}
5555

5656
@Override
57-
protected JpqlQueryBuilder.AbstractJpqlQuery buildQuery(Sort sort) {
57+
protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
5858

5959
JpqlQueryBuilder.SelectStep selectStep = JpqlQueryBuilder.selectFrom(returnedType.getDomainType());
6060
if (this.distinct) {

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaKeysetScrollQueryCreator.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@
1616
package org.springframework.data.jpa.repository.query;
1717

1818
import jakarta.persistence.EntityManager;
19-
import jakarta.persistence.criteria.CriteriaBuilder;
20-
import jakarta.persistence.criteria.CriteriaQuery;
21-
import jakarta.persistence.criteria.Predicate;
22-
import jakarta.persistence.criteria.Root;
2319

2420
import java.util.Collection;
2521
import java.util.LinkedHashSet;
@@ -43,6 +39,7 @@ class JpaKeysetScrollQueryCreator extends JpaQueryCreator {
4339

4440
private final JpaEntityInformation<?, ?> entityInformation;
4541
private final KeysetScrollPosition scrollPosition;
42+
private final ParameterMetadataProvider provider;
4643

4744
public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvider provider,
4845
JpqlQueryTemplates templates, JpaEntityInformation<?, ?> entityInformation, KeysetScrollPosition scrollPosition,
@@ -52,29 +49,42 @@ public JpaKeysetScrollQueryCreator(PartTree tree, ReturnedType type, ParameterMe
5249

5350
this.entityInformation = entityInformation;
5451
this.scrollPosition = scrollPosition;
52+
this.provider = provider;
5553
}
5654

57-
// TODO
58-
protected CriteriaQuery<?> complete(@Nullable Predicate predicate, Sort sort, CriteriaQuery<?> query,
59-
CriteriaBuilder builder, Root<?> root) {
55+
@Override
56+
protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) {
6057

61-
/*KeysetScrollSpecification<Object> keysetSpec = new KeysetScrollSpecification<>(scrollPosition, sort,
58+
KeysetScrollSpecification<Object> keysetSpec = new KeysetScrollSpecification<>(scrollPosition, sort,
6259
entityInformation);
63-
Predicate keysetPredicate = keysetSpec.createPredicate(root, builder);
60+
JpqlQueryBuilder.Predicate keysetPredicate = keysetSpec.createJpqlPredicate(getFrom(), getEntity(), value -> {
61+
return JpqlQueryBuilder.expression(render(provider.synthetic(value)));
62+
});
63+
64+
JpqlQueryBuilder.Select query = buildQuery(keysetSpec.sort());
65+
66+
JpqlQueryBuilder.Predicate predicateToUse = getPredicate(predicate, keysetPredicate);
67+
68+
if (predicateToUse != null) {
69+
return query.where(predicateToUse);
70+
}
71+
72+
return query;
73+
}
6474

65-
CriteriaQuery<?> queryToUse = super.complete(predicate, keysetSpec.sort(), query, builder, root);
75+
@Nullable
76+
private static JpqlQueryBuilder.Predicate getPredicate(@Nullable JpqlQueryBuilder.Predicate predicate,
77+
@Nullable JpqlQueryBuilder.Predicate keysetPredicate) {
6678

6779
if (keysetPredicate != null) {
68-
if (queryToUse.getRestriction() != null) {
69-
return queryToUse.where(builder.and(queryToUse.getRestriction(), keysetPredicate));
80+
if (predicate != null) {
81+
return predicate.nest().and(keysetPredicate.nest());
82+
} else {
83+
return keysetPredicate;
7084
}
71-
return queryToUse.where(keysetPredicate);
7285
}
7386

74-
return queryToUse;*/
75-
76-
// TODO
77-
return null;
87+
return predicate;
7888
}
7989

8090
@Override

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java

Lines changed: 21 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
import jakarta.persistence.criteria.CriteriaQuery;
2222
import jakarta.persistence.criteria.Expression;
2323
import jakarta.persistence.criteria.From;
24-
import jakarta.persistence.criteria.Join;
25-
import jakarta.persistence.criteria.JoinType;
2624
import jakarta.persistence.criteria.Predicate;
2725
import jakarta.persistence.metamodel.Attribute;
2826
import jakarta.persistence.metamodel.EntityType;
@@ -31,7 +29,6 @@
3129
import java.util.Collection;
3230
import java.util.Iterator;
3331
import java.util.List;
34-
import java.util.Objects;
3532

3633
import org.springframework.data.domain.Sort;
3734
import org.springframework.data.jpa.domain.JpaSort;
@@ -94,6 +91,14 @@ public JpaQueryCreator(PartTree tree, ReturnedType type, ParameterMetadataProvid
9491
this.entity = JpqlQueryBuilder.entity(returnedType.getDomainType());
9592
}
9693

94+
From<?, ?> getFrom() {
95+
return from;
96+
}
97+
98+
JpqlQueryBuilder.Entity getEntity() {
99+
return entity;
100+
}
101+
97102
/**
98103
* Returns all {@link jakarta.persistence.criteria.ParameterExpression} created when creating the query.
99104
*
@@ -125,12 +130,13 @@ protected JpqlQueryBuilder.Predicate or(JpqlQueryBuilder.Predicate base, JpqlQue
125130
@Override
126131
protected final String complete(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) {
127132

128-
JpqlQueryBuilder.AbstractJpqlQuery query = applyPredicate(predicate, buildQuery(sort));
133+
JpqlQueryBuilder.AbstractJpqlQuery query = createQuery(predicate, sort);
129134
return query.render();
130135
}
131136

132-
protected JpqlQueryBuilder.AbstractJpqlQuery applyPredicate(@Nullable JpqlQueryBuilder.Predicate predicate,
133-
JpqlQueryBuilder.AbstractJpqlQuery query) {
137+
protected JpqlQueryBuilder.AbstractJpqlQuery createQuery(@Nullable JpqlQueryBuilder.Predicate predicate, Sort sort) {
138+
139+
JpqlQueryBuilder.Select query = buildQuery(sort);
134140

135141
if (predicate != null) {
136142
return query.where(predicate);
@@ -145,7 +151,7 @@ protected JpqlQueryBuilder.AbstractJpqlQuery applyPredicate(@Nullable JpqlQueryB
145151
* @param sort
146152
* @return
147153
*/
148-
protected JpqlQueryBuilder.AbstractJpqlQuery buildQuery(Sort sort) {
154+
protected JpqlQueryBuilder.Select buildQuery(Sort sort) {
149155

150156
JpqlQueryBuilder.Select select = doSelect(sort);
151157

@@ -159,8 +165,8 @@ protected JpqlQueryBuilder.AbstractJpqlQuery buildQuery(Sort sort) {
159165
QueryUtils.checkSortExpression(order);
160166

161167
try {
162-
expression = JpqlQueryBuilder.expression(
163-
toExpressionRecursively(entity, from, PropertyPath.from(order.getProperty(), entityType.getJavaType())));
168+
expression = JpqlQueryBuilder.expression(JpqlUtils.toExpressionRecursively(entity, from,
169+
PropertyPath.from(order.getProperty(), entityType.getJavaType())));
164170
} catch (PropertyReferenceException e) {
165171

166172
if (order instanceof JpaSort.JpaOrder jpaOrder && jpaOrder.isUnsafe()) {
@@ -227,6 +233,10 @@ Collection<String> getRequiredSelection(Sort sort, ReturnedType returnedType) {
227233
return returnedType.getInputProperties();
228234
}
229235

236+
String render(ParameterMetadata<?> metadata) {
237+
return "?" + (metadata.getPosition() + 1);
238+
}
239+
230240
/**
231241
* Creates a {@link Predicate} from the given {@link Part}.
232242
*
@@ -270,7 +280,7 @@ public JpqlQueryBuilder.Predicate build() {
270280
PropertyPath property = part.getProperty();
271281
Type type = part.getType();
272282

273-
PathAndOrigin pas = toExpressionRecursively(entity, from, property);
283+
PathAndOrigin pas = JpqlUtils.toExpressionRecursively(entity, from, property);
274284
JpqlQueryBuilder.WhereStep where = JpqlQueryBuilder.where(pas);
275285
JpqlQueryBuilder.WhereStep whereIgnoreCase = JpqlQueryBuilder.where(potentiallyIgnoreCase(pas));
276286

@@ -352,19 +362,14 @@ public JpqlQueryBuilder.Predicate build() {
352362
}
353363
}
354364

355-
private String render(ParameterMetadata<?> metadata) {
356-
return "?" + (metadata.getPosition() + 1);
357-
}
358-
359365
/**
360366
* Applies an {@code UPPERCASE} conversion to the given {@link Expression} in case the underlying {@link Part}
361367
* requires ignoring case.
362368
*
363369
* @param path must not be {@literal null}.
364370
* @return
365371
*/
366-
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(JpqlQueryBuilder.Origin source,
367-
PropertyPath path) {
372+
private <T> JpqlQueryBuilder.Expression potentiallyIgnoreCase(JpqlQueryBuilder.Origin source, PropertyPath path) {
368373
return potentiallyIgnoreCase(path, JpqlQueryBuilder.expression(source, path));
369374
}
370375

@@ -414,56 +419,4 @@ private boolean canUpperCase(PropertyPath path) {
414419
}
415420
}
416421

417-
static PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
418-
PropertyPath property) {
419-
return toExpressionRecursively(source, from, property, false);
420-
}
421-
422-
static PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
423-
PropertyPath property, boolean isForSelection) {
424-
return toExpressionRecursively(source, from, property, isForSelection, false);
425-
}
426-
427-
/**
428-
* Creates an expression with proper inner and left joins by recursively navigating the path
429-
*
430-
* @param from the {@link From}
431-
* @param property the property path
432-
* @param isForSelection is the property navigated for the selection or ordering part of the query?
433-
* @param hasRequiredOuterJoin has a parent already required an outer join?
434-
* @param <T> the type of the expression
435-
* @return the expression
436-
*/
437-
@SuppressWarnings("unchecked")
438-
static PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
439-
PropertyPath property, boolean isForSelection, boolean hasRequiredOuterJoin) {
440-
441-
String segment = property.getSegment();
442-
443-
boolean isLeafProperty = !property.hasNext();
444-
445-
boolean requiresOuterJoin = QueryUtils.requiresOuterJoin(from, property, isForSelection, hasRequiredOuterJoin);
446-
447-
// if it does not require an outer join and is a leaf, simply get the segment
448-
if (!requiresOuterJoin && isLeafProperty) {
449-
return new PathAndOrigin(property, source, false);
450-
}
451-
452-
// get or create the join
453-
JpqlQueryBuilder.Join joinSource = requiresOuterJoin ? JpqlQueryBuilder.leftJoin(source, segment)
454-
: JpqlQueryBuilder.innerJoin(source, segment);
455-
JoinType joinType = requiresOuterJoin ? JoinType.LEFT : JoinType.INNER;
456-
Join<?, ?> join = QueryUtils.getOrCreateJoin(from, segment, joinType);
457-
458-
// if it's a leaf, return the join
459-
if (isLeafProperty) {
460-
return new PathAndOrigin(property, joinSource, true);
461-
}
462-
463-
PropertyPath nextProperty = Objects.requireNonNull(property.next(), "An element of the property path is null");
464-
465-
// recurse with the next property
466-
return toExpressionRecursively(joinSource, join, nextProperty, isForSelection, requiresOuterJoin);
467-
}
468-
469422
}

spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/JpqlQueryBuilder.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,40 @@ public Predicate neq(Expression value) {
349349
};
350350
}
351351

352+
@Nullable
353+
public static Predicate and(List<Predicate> intermediate) {
354+
355+
Predicate predicate = null;
356+
357+
for (Predicate other : intermediate) {
358+
359+
if (predicate == null) {
360+
predicate = other;
361+
} else {
362+
predicate = predicate.and(other);
363+
}
364+
}
365+
366+
return predicate;
367+
}
368+
369+
@Nullable
370+
public static Predicate or(List<Predicate> intermediate) {
371+
372+
Predicate predicate = null;
373+
374+
for (Predicate other : intermediate) {
375+
376+
if (predicate == null) {
377+
predicate = other;
378+
} else {
379+
predicate = predicate.or(other);
380+
}
381+
}
382+
383+
return predicate;
384+
}
385+
352386
/**
353387
* Fluent interface to build a {@link Select}.
354388
*/
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2024 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 jakarta.persistence.criteria.From;
19+
import jakarta.persistence.criteria.Join;
20+
import jakarta.persistence.criteria.JoinType;
21+
22+
import java.util.Objects;
23+
24+
import org.springframework.data.mapping.PropertyPath;
25+
26+
/**
27+
* @author Mark Paluch
28+
*/
29+
class JpqlUtils {
30+
31+
static JpqlQueryBuilder.PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
32+
PropertyPath property) {
33+
return toExpressionRecursively(source, from, property, false);
34+
}
35+
36+
static JpqlQueryBuilder.PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
37+
PropertyPath property, boolean isForSelection) {
38+
return toExpressionRecursively(source, from, property, isForSelection, false);
39+
}
40+
41+
/**
42+
* Creates an expression with proper inner and left joins by recursively navigating the path
43+
*
44+
* @param from the {@link From}
45+
* @param property the property path
46+
* @param isForSelection is the property navigated for the selection or ordering part of the query?
47+
* @param hasRequiredOuterJoin has a parent already required an outer join?
48+
* @param <T> the type of the expression
49+
* @return the expression
50+
*/
51+
@SuppressWarnings("unchecked")
52+
static JpqlQueryBuilder.PathAndOrigin toExpressionRecursively(JpqlQueryBuilder.Origin source, From<?, ?> from,
53+
PropertyPath property, boolean isForSelection, boolean hasRequiredOuterJoin) {
54+
55+
String segment = property.getSegment();
56+
57+
boolean isLeafProperty = !property.hasNext();
58+
59+
boolean requiresOuterJoin = QueryUtils.requiresOuterJoin(from, property, isForSelection, hasRequiredOuterJoin);
60+
61+
// if it does not require an outer join and is a leaf, simply get the segment
62+
if (!requiresOuterJoin && isLeafProperty) {
63+
return new JpqlQueryBuilder.PathAndOrigin(property, source, false);
64+
}
65+
66+
// get or create the join
67+
JpqlQueryBuilder.Join joinSource = requiresOuterJoin ? JpqlQueryBuilder.leftJoin(source, segment)
68+
: JpqlQueryBuilder.innerJoin(source, segment);
69+
JoinType joinType = requiresOuterJoin ? JoinType.LEFT : JoinType.INNER;
70+
Join<?, ?> join = QueryUtils.getOrCreateJoin(from, segment, joinType);
71+
72+
// if it's a leaf, return the join
73+
if (isLeafProperty) {
74+
return new JpqlQueryBuilder.PathAndOrigin(property, joinSource, true);
75+
}
76+
77+
PropertyPath nextProperty = Objects.requireNonNull(property.next(), "An element of the property path is null");
78+
79+
// recurse with the next property
80+
return toExpressionRecursively(joinSource, join, nextProperty, isForSelection, requiresOuterJoin);
81+
}
82+
}

0 commit comments

Comments
 (0)