Skip to content

Commit 1f2da5e

Browse files
peteraishermp911de
authored andcommitted
Constistent unrestricted() behaviour for all *Specification types.
Closes #4203 Original pull request: #4024 Signed-off-by: Peter Aisher <[email protected]>
1 parent a5b5c2e commit 1f2da5e

File tree

8 files changed

+89
-46
lines changed

8 files changed

+89
-46
lines changed

spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/DeleteSpecification.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,18 +35,30 @@
3535
* <p>
3636
* Specifications can be composed into higher order functions from other specifications using
3737
* {@link #and(DeleteSpecification)}, {@link #or(DeleteSpecification)} or factory methods such as
38-
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
39-
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
40-
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
38+
* {@link #allOf(Iterable)}.
39+
* <p>
40+
* Composition considers whether one or more specifications contribute to the overall predicate by returning a
41+
* {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@link #unrestricted()}, are
42+
* considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
4143
*
4244
* @author Mark Paluch
45+
* @author Peter Aisher
4346
* @since 4.0
4447
*/
4548
@FunctionalInterface
4649
public interface DeleteSpecification<T> extends Serializable {
4750

4851
/**
49-
* Simple static factory method to create a specification deleting all objects.
52+
* Simple static factory method to create a specification which does not participate in matching. The specification
53+
* returned is {@code null}-like, and is elided in all operations.
54+
*
55+
* <pre>
56+
* {@code
57+
* unrestricted().and(other) // consider only `other`
58+
* unrestricted().or(other) // consider only `other`
59+
* not(unrestricted()) // equivalent to `unrestricted()`
60+
* }
61+
* </pre>
5062
*
5163
* @param <T> the type of the {@link Root} the resulting {@literal DeleteSpecification} operates on.
5264
* @return guaranteed to be not {@literal null}.
@@ -159,7 +171,7 @@ static <T> DeleteSpecification<T> not(DeleteSpecification<T> spec) {
159171
return (root, delete, builder) -> {
160172

161173
Predicate predicate = spec.toPredicate(root, delete, builder);
162-
return predicate != null ? builder.not(predicate) : builder.disjunction();
174+
return predicate != null ? builder.not(predicate) : null;
163175
};
164176
}
165177

spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/PredicateSpecification.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,18 +34,30 @@
3434
* <p>
3535
* Specifications can be composed into higher order functions from other specifications using
3636
* {@link #and(PredicateSpecification)}, {@link #or(PredicateSpecification)} or factory methods such as
37-
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
38-
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
39-
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
37+
* {@link #allOf(Iterable)}.
38+
* <p>
39+
* Composition considers whether one or more specifications contribute to the overall predicate by returning a
40+
* {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@link #unrestricted()}, are
41+
* considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
4042
*
4143
* @author Mark Paluch
44+
* @author Peter Aisher
4245
* @since 4.0
4346
*/
4447
@FunctionalInterface
4548
public interface PredicateSpecification<T> extends Serializable {
4649

4750
/**
48-
* Simple static factory method to create a specification matching all objects.
51+
* Simple static factory method to create a specification which does not participate in matching. The specification
52+
* returned is {@code null}-like, and is elided in all operations.
53+
*
54+
* <pre>
55+
* {@code
56+
* unrestricted().and(other) // consider only `other`
57+
* unrestricted().or(other) // consider only `other`
58+
* not(unrestricted()) // equivalent to `unrestricted()`
59+
* }
60+
* </pre>
4961
*
5062
* @param <T> the type of the {@link Root} the resulting {@literal PredicateSpecification} operates on.
5163
* @return guaranteed to be not {@literal null}.
@@ -113,7 +125,7 @@ static <T> PredicateSpecification<T> not(PredicateSpecification<T> spec) {
113125
return (root, builder) -> {
114126

115127
Predicate predicate = spec.toPredicate(root, builder);
116-
return predicate != null ? builder.not(predicate) : builder.disjunction();
128+
return predicate != null ? builder.not(predicate) : null;
117129
};
118130
}
119131

spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/Specification.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
* <p>
3737
* Specifications can be composed into higher order functions from other specifications using
3838
* {@link #and(Specification)}, {@link #or(Specification)} or factory methods such as {@link #allOf(Iterable)}.
39+
* <p>
3940
* Composition considers whether one or more specifications contribute to the overall predicate by returning a
40-
* {@link Predicate} or {@literal null}. Specifications returning {@literal null} are considered to not contribute to
41-
* the overall predicate and their result is not considered in the final predicate.
41+
* {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@link #unrestricted()}, are
42+
* considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
4243
*
4344
* @author Oliver Gierke
4445
* @author Thomas Darimont
@@ -49,12 +50,22 @@
4950
* @author Daniel Shuy
5051
* @author Sergey Rukin
5152
* @author Heeeun Cho
53+
* @author Peter Aisher
5254
*/
5355
@FunctionalInterface
5456
public interface Specification<T> extends Serializable {
5557

5658
/**
57-
* Simple static factory method to create a specification matching all objects.
59+
* Simple static factory method to create a specification which does not participate in matching. The specification
60+
* returned is {@code null}-like, and is elided in all operations.
61+
*
62+
* <pre>
63+
* {@code
64+
* unrestricted().and(other) // consider only `other`
65+
* unrestricted().or(other) // consider only `other`
66+
* not(unrestricted()) // equivalent to `unrestricted()`
67+
* }
68+
* </pre>
5869
*
5970
* @param <T> the type of the {@link Root} the resulting {@literal Specification} operates on.
6071
* @return guaranteed to be not {@literal null}.
@@ -175,7 +186,7 @@ static <T> Specification<T> not(Specification<T> spec) {
175186
return (root, query, builder) -> {
176187

177188
Predicate predicate = spec.toPredicate(root, query, builder);
178-
return predicate != null ? builder.not(predicate) : builder.disjunction();
189+
return predicate != null ? builder.not(predicate) : null;
179190
};
180191
}
181192

spring-data-jpa/src/main/java/org/springframework/data/jpa/domain/UpdateSpecification.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,18 +35,30 @@
3535
* <p>
3636
* Specifications can be composed into higher order functions from other specifications using
3737
* {@link #and(UpdateSpecification)}, {@link #or(UpdateSpecification)} or factory methods such as
38-
* {@link #allOf(Iterable)}. Composition considers whether one or more specifications contribute to the overall
39-
* predicate by returning a {@link Predicate} or {@literal null}. Specifications returning {@literal null} are
40-
* considered to not contribute to the overall predicate and their result is not considered in the final predicate.
38+
* {@link #allOf(Iterable)}.
39+
* <p>
40+
* Composition considers whether one or more specifications contribute to the overall predicate by returning a
41+
* {@link Predicate} or {@literal null}. Specifications returning {@literal null}, such as {@link #unrestricted()}, are
42+
* considered to not contribute to the overall predicate, and their result is not considered in the final predicate.
4143
*
4244
* @author Mark Paluch
45+
* @author Peter Aisher
4346
* @since 4.0
4447
*/
4548
@FunctionalInterface
4649
public interface UpdateSpecification<T> extends Serializable {
4750

4851
/**
49-
* Simple static factory method to create a specification updating all objects.
52+
* Simple static factory method to create a specification which does not participate in matching. The specification
53+
* returned is {@code null}-like, and is elided in all operations.
54+
*
55+
* <pre>
56+
* {@code
57+
* unrestricted().and(other) // consider only `other`
58+
* unrestricted().or(other) // consider only `other`
59+
* not(unrestricted()) // equivalent to `unrestricted()`
60+
* }
61+
* </pre>
5062
*
5163
* @param <T> the type of the {@link Root} the resulting {@literal UpdateSpecification} operates on.
5264
* @return guaranteed to be not {@literal null}.
@@ -180,7 +192,7 @@ static <T> UpdateSpecification<T> not(UpdateSpecification<T> spec) {
180192
return (root, update, builder) -> {
181193

182194
Predicate predicate = spec.toPredicate(root, update, builder);
183-
return predicate != null ? builder.not(predicate) : builder.disjunction();
195+
return predicate != null ? builder.not(predicate) : null;
184196
};
185197
}
186198

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/DeleteSpecificationUnitTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
* Unit tests for {@link DeleteSpecification}.
3939
*
4040
* @author Mark Paluch
41+
* @author Peter Aisher
4142
*/
4243
@SuppressWarnings({ "unchecked", "deprecation" })
4344
@ExtendWith(MockitoExtension.class)
@@ -158,15 +159,13 @@ void orCombinesSpecificationsInOrder() {
158159
verify(builder).or(firstPredicate, secondPredicate);
159160
}
160161

161-
@Test // GH-3849
162+
@Test // GH-3849, GH-4023
162163
void notWithNullPredicate() {
163164

164-
when(builder.disjunction()).thenReturn(mock(Predicate.class));
165-
166165
DeleteSpecification<Object> notSpec = DeleteSpecification.not((r, q, cb) -> null);
167166

168-
assertThat(notSpec.toPredicate(root, delete, builder)).isNotNull();
169-
verify(builder).disjunction();
167+
assertThat(notSpec.toPredicate(root, delete, builder)).isNull();
168+
verifyNoInteractions(builder);
170169
}
171170

172171
static class SerializableSpecification implements Serializable, DeleteSpecification<Object> {

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/PredicateSpecificationUnitTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@
3737
* Unit tests for {@link PredicateSpecification}.
3838
*
3939
* @author Mark Paluch
40+
* @author Peter Aisher
4041
*/
4142
@SuppressWarnings({ "unchecked", "deprecation" })
4243
@ExtendWith(MockitoExtension.class)
@@ -156,15 +157,13 @@ void orCombinesSpecificationsInOrder() {
156157
verify(builder).or(firstPredicate, secondPredicate);
157158
}
158159

159-
@Test // GH-3849
160+
@Test // GH-3849, GH-4023
160161
void notWithNullPredicate() {
161162

162-
when(builder.disjunction()).thenReturn(mock(Predicate.class));
163-
164163
PredicateSpecification<Object> notSpec = PredicateSpecification.not((r, cb) -> null);
165164

166-
assertThat(notSpec.toPredicate(root, builder)).isNotNull();
167-
verify(builder).disjunction();
165+
assertThat(notSpec.toPredicate(root, builder)).isNull();
166+
verifyNoInteractions(builder);
168167
}
169168

170169
static class SerializableSpecification implements Serializable, PredicateSpecification<Object> {

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/SpecificationUnitTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
* @author Mark Paluch
4444
* @author Daniel Shuy
4545
* @author Heeeun Cho
46+
* @author Peter Aisher
4647
*/
4748
@SuppressWarnings({ "unchecked", "deprecation" })
4849
@ExtendWith(MockitoExtension.class)
@@ -127,15 +128,13 @@ void orCombinesSpecificationsInOrder() {
127128
verify(builder).or(firstPredicate, secondPredicate);
128129
}
129130

130-
@Test // GH-3849
131+
@Test // GH-3849, GH-4023
131132
void notWithNullPredicate() {
132133

133-
when(builder.disjunction()).thenReturn(mock(Predicate.class));
134+
Specification<Object> notSpec = Specification.not(Specification.unrestricted());
134135

135-
Specification<Object> notSpec = Specification.not((r, q, cb) -> null);
136-
137-
assertThat(notSpec.toPredicate(root, query, builder)).isNotNull();
138-
verify(builder).disjunction();
136+
assertThat(notSpec.toPredicate(root, query, builder)).isNull();
137+
verifyNoInteractions(builder);
139138
}
140139

141140
@Test // GH-3992

spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/UpdateSpecificationUnitTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024 the original author or authors.
2+
* Copyright 2024-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -38,6 +38,7 @@
3838
* Unit tests for {@link UpdateSpecification}.
3939
*
4040
* @author Mark Paluch
41+
* @author Peter Aisher
4142
*/
4243
@SuppressWarnings({ "unchecked", "deprecation" })
4344
@ExtendWith(MockitoExtension.class)
@@ -158,15 +159,13 @@ void orCombinesSpecificationsInOrder() {
158159
verify(builder).or(firstPredicate, secondPredicate);
159160
}
160161

161-
@Test // GH-3849
162+
@Test // GH-3849, GH-4023
162163
void notWithNullPredicate() {
163164

164-
when(builder.disjunction()).thenReturn(mock(Predicate.class));
165-
166165
UpdateSpecification<Object> notSpec = UpdateSpecification.not((r, q, cb) -> null);
167166

168-
assertThat(notSpec.toPredicate(root, update, builder)).isNotNull();
169-
verify(builder).disjunction();
167+
assertThat(notSpec.toPredicate(root, update, builder)).isNull();
168+
verifyNoInteractions(builder);
170169
}
171170

172171
static class SerializableSpecification implements Serializable, UpdateSpecification<Object> {

0 commit comments

Comments
 (0)