Skip to content

Commit 3d65738

Browse files
committed
Consider query method result limiting.
We now correctly apply the limit of derived queries. Additionally, we support dynamic limiting when query methods declare a Limit parameter. Closes #586 See also: #585
1 parent c7f76b6 commit 3d65738

File tree

8 files changed

+185
-5
lines changed

8 files changed

+185
-5
lines changed

src/main/java/org/springframework/data/ldap/repository/Query.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171

7272
/**
7373
* Count limit, to be used as input to {@link org.springframework.ldap.query.LdapQueryBuilder#countLimit(int)}.
74+
* <p>
75+
* This attribute will be ignored when a query method accepts a {@link org.springframework.data.domain.Limit}
76+
* parameter
7477
*
7578
* @return the count limit.
7679
*/

src/main/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQuery.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static org.springframework.ldap.query.LdapQueryBuilder.*;
1919

20+
import org.springframework.data.domain.Limit;
2021
import org.springframework.data.expression.ValueEvaluationContext;
2122
import org.springframework.data.expression.ValueEvaluationContextProvider;
2223
import org.springframework.data.ldap.repository.Query;
@@ -93,17 +94,24 @@ protected LdapQuery createQuery(LdapParameterAccessor parameters) {
9394
String query = bind(parameters, valueContextProvider, this.query);
9495
String base = bind(parameters, valueContextProvider, this.base);
9596

97+
int countLimit = queryAnnotation.countLimit();
98+
99+
if (getQueryMethod().getParameters().hasLimitParameter()) {
100+
Limit limit = parameters.getLimit();
101+
countLimit = limit.isLimited() ? limit.max() : 0;
102+
}
103+
96104
return query().base(base) //
97105
.searchScope(queryAnnotation.searchScope()) //
98-
.countLimit(queryAnnotation.countLimit()) //
106+
.countLimit(countLimit) //
99107
.timeLimit(queryAnnotation.timeLimit()) //
100108
.filter(query, parameters.getBindableParameterValues());
101109
}
102110

103111
private String bind(LdapParameterAccessor parameters, ValueEvaluationContextProvider valueContextProvider, StringBasedQuery query) {
104112

105113
ValueEvaluationContext evaluationContext = valueContextProvider
106-
.getEvaluationContext(parameters.getBindableParameterValues(), query.getExpressionDependencies());
114+
.getEvaluationContext(parameters.getValues(), query.getExpressionDependencies());
107115

108116
return query.bindQuery(parameters,
109117
expression -> expression.evaluate(evaluationContext));

src/main/java/org/springframework/data/ldap/repository/query/LdapParameterAccessor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,13 @@ public interface LdapParameterAccessor extends ParameterAccessor {
3131
* @return
3232
*/
3333
Object[] getBindableParameterValues();
34+
35+
/**
36+
* Returns all parameter values of the underlying query method.
37+
*
38+
* @return
39+
* @since 3.5.6
40+
*/
41+
Object[] getValues();
42+
3443
}

src/main/java/org/springframework/data/ldap/repository/query/LdapParametersParameterAccessor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ public Object[] getBindableParameterValues() {
5656
return values;
5757
}
5858

59+
@Override
60+
public Object[] getValues() {
61+
return super.getValues();
62+
}
63+
5964
}

src/main/java/org/springframework/data/ldap/repository/query/LdapQueryCreator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ private ContainerCriteria appendCondition(Part part, Iterator<Object> iterator,
9999
Object next = iterator.next();
100100
value = next != null ? next.toString() : null;
101101
}
102+
102103
switch (type) {
103104
case NEGATING_SIMPLE_PROPERTY:
104105
return criteria.not().is(value);
@@ -125,10 +126,10 @@ private ContainerCriteria appendCondition(Part part, Iterator<Object> iterator,
125126
default:
126127
throw new IllegalArgumentException(String.format("%s queries are not supported for LDAP repositories", type));
127128
}
128-
129129
}
130130

131131
private String getAttribute(Part part) {
132+
132133
PropertyPath path = part.getProperty();
133134
if (path.hasNext()) {
134135
throw new IllegalArgumentException("Nested properties are not supported");
@@ -139,8 +140,8 @@ private String getAttribute(Part part) {
139140

140141
@Override
141142
protected ContainerCriteria and(Part part, ContainerCriteria base, Iterator<Object> iterator) {
142-
ConditionCriteria criteria = base.and(getAttribute(part));
143143

144+
ConditionCriteria criteria = base.and(getAttribute(part));
144145
return appendCondition(part, iterator, criteria);
145146
}
146147

src/main/java/org/springframework/data/ldap/repository/query/PartTreeLdapRepositoryQuery.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.util.Collections;
1919
import java.util.List;
2020

21+
import org.springframework.data.domain.Limit;
2122
import org.springframework.data.mapping.PersistentEntity;
2223
import org.springframework.data.mapping.PersistentProperty;
2324
import org.springframework.data.mapping.context.MappingContext;
@@ -28,6 +29,7 @@
2829
import org.springframework.ldap.core.LdapOperations;
2930
import org.springframework.ldap.odm.core.ObjectDirectoryMapper;
3031
import org.springframework.ldap.query.LdapQuery;
32+
import org.springframework.ldap.query.LdapQueryBuilder;
3133

3234
/**
3335
* {@link RepositoryQuery} implementation for LDAP.
@@ -72,6 +74,26 @@ protected LdapQuery createQuery(LdapParameterAccessor parameters) {
7274

7375
org.springframework.data.ldap.repository.query.LdapQueryCreator queryCreator = new LdapQueryCreator(partTree,
7476
getEntityClass(), objectDirectoryMapper, parameters, inputProperties);
75-
return queryCreator.createQuery();
77+
78+
LdapQuery query = queryCreator.createQuery();
79+
80+
if (getQueryMethod().getParameters().hasLimitParameter() || partTree.isLimiting()) {
81+
82+
LdapQueryBuilder builder = LdapQueryBuilder.fromQuery(query);
83+
84+
Limit limit = parameters.getLimit();
85+
86+
if (partTree.isLimiting()) {
87+
builder.countLimit(partTree.getResultLimit().max());
88+
}
89+
90+
if (getQueryMethod().getParameters().hasLimitParameter()) {
91+
builder.countLimit(limit.isLimited() ? limit.max() : 0);
92+
}
93+
94+
return builder;
95+
}
96+
97+
return query;
7698
}
7799
}

src/test/java/org/springframework/data/ldap/repository/query/AnnotatedLdapRepositoryQueryUnitTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.junit.jupiter.api.Test;
2323
import org.mockito.Mockito;
2424

25+
import org.springframework.data.domain.Limit;
2526
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
2627
import org.springframework.data.ldap.repository.LdapEncode;
2728
import org.springframework.data.ldap.repository.LdapEncoder;
@@ -93,6 +94,34 @@ void shouldEncodeWithCustomEncoder() throws NoSuchMethodException {
9394
assertThat(ldapQuery.filter().encode()).isEqualTo("(cn=Doebar)");
9495
}
9596

97+
@Test // GH-586
98+
void shouldConsiderLimit() throws NoSuchMethodException {
99+
100+
LdapQueryMethod method = queryMethod("limited", String.class);
101+
AnnotatedLdapRepositoryQuery query = repositoryQuery(method);
102+
103+
LdapQuery ldapQuery = query.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe" }));
104+
105+
assertThat(ldapQuery.countLimit()).isEqualTo(123);
106+
}
107+
108+
@Test // GH-586
109+
void shouldConsiderLimitParameter() throws NoSuchMethodException {
110+
111+
LdapQueryMethod method = queryMethod("limited", String.class, Limit.class);
112+
AnnotatedLdapRepositoryQuery query = repositoryQuery(method);
113+
114+
LdapQuery ldapQuery = query
115+
.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe", Limit.unlimited() }));
116+
117+
assertThat(ldapQuery.countLimit()).isEqualTo(0);
118+
119+
ldapQuery = query
120+
.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe", Limit.of(10) }));
121+
122+
assertThat(ldapQuery.countLimit()).isEqualTo(10);
123+
}
124+
96125
private LdapQueryMethod queryMethod(String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
97126
return new LdapQueryMethod(QueryRepository.class.getMethod(methodName, parameterTypes),
98127
new DefaultRepositoryMetadata(QueryRepository.class), new SpelAwareProxyProjectionFactory());
@@ -108,6 +137,12 @@ interface QueryRepository extends LdapRepository<SchemaEntry> {
108137
@Query(value = "(cn=:fullName)")
109138
List<SchemaEntry> namedParameters(String fullName);
110139

140+
@Query(value = "(cn=:fullName)", countLimit = 123)
141+
List<SchemaEntry> limited(String fullName);
142+
143+
@Query(value = "(cn=:fullName)", countLimit = 123)
144+
List<SchemaEntry> limited(String fullName, Limit limit);
145+
111146
@Query(value = "(cn={0})")
112147
List<SchemaEntry> messageFormatParameters(String fullName);
113148

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2025 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.ldap.repository.query;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.Mockito.*;
20+
21+
import java.util.List;
22+
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
import org.mockito.Mockito;
26+
27+
import org.springframework.data.domain.Limit;
28+
import org.springframework.data.ldap.core.mapping.LdapMappingContext;
29+
import org.springframework.data.ldap.repository.LdapRepository;
30+
import org.springframework.data.mapping.model.EntityInstantiators;
31+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
32+
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
33+
import org.springframework.ldap.core.LdapOperations;
34+
import org.springframework.ldap.odm.core.impl.DefaultObjectDirectoryMapper;
35+
import org.springframework.ldap.query.LdapQuery;
36+
37+
/**
38+
* Unit tests for {@link PartTreeLdapRepositoryQuery}.
39+
*
40+
* @author Mark Paluch
41+
*/
42+
class PartTreeLdapRepositoryQueryUnitTests {
43+
44+
LdapOperations ldapOperations = Mockito.mock();
45+
46+
@BeforeEach
47+
void setUp() {
48+
when(ldapOperations.getObjectDirectoryMapper()).thenReturn(new DefaultObjectDirectoryMapper());
49+
}
50+
51+
@Test // GH-586
52+
void shouldConsiderLimit() throws NoSuchMethodException {
53+
54+
LdapQueryMethod method = queryMethod("findTop5ByFullName", String.class);
55+
PartTreeLdapRepositoryQuery query = repositoryQuery(method);
56+
57+
LdapQuery ldapQuery = query.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe" }));
58+
59+
assertThat(ldapQuery.countLimit()).isEqualTo(5);
60+
}
61+
62+
@Test // GH-586
63+
void shouldConsiderLimitParameter() throws NoSuchMethodException {
64+
65+
LdapQueryMethod method = queryMethod("findTop5ByFullName", String.class, Limit.class);
66+
PartTreeLdapRepositoryQuery query = repositoryQuery(method);
67+
68+
LdapQuery ldapQuery = query
69+
.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe", Limit.unlimited() }));
70+
71+
assertThat(ldapQuery.countLimit()).isEqualTo(0);
72+
73+
ldapQuery = query
74+
.createQuery(new LdapParametersParameterAccessor(method, new Object[] { "John Doe", Limit.of(10) }));
75+
76+
assertThat(ldapQuery.countLimit()).isEqualTo(10);
77+
}
78+
79+
private LdapQueryMethod queryMethod(String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
80+
return new LdapQueryMethod(QueryRepository.class.getMethod(methodName, parameterTypes),
81+
new DefaultRepositoryMetadata(QueryRepository.class), new SpelAwareProxyProjectionFactory());
82+
}
83+
84+
private PartTreeLdapRepositoryQuery repositoryQuery(LdapQueryMethod method) {
85+
return new PartTreeLdapRepositoryQuery(method, SchemaEntry.class, ldapOperations, new LdapMappingContext(),
86+
new EntityInstantiators());
87+
}
88+
89+
interface QueryRepository extends LdapRepository<SchemaEntry> {
90+
91+
List<SchemaEntry> findTop5ByFullName(String fullName);
92+
93+
List<SchemaEntry> findTop5ByFullName(String fullName, Limit limit);
94+
95+
}
96+
97+
}

0 commit comments

Comments
 (0)