Skip to content

Commit f8c2206

Browse files
committed
Introduce explicit index field matching methods.
Closes #5187 Signed-off-by: dragonfsky <dragonfsky@gmail.com>
1 parent cbc9273 commit f8c2206

3 files changed

Lines changed: 111 additions & 5 deletions

File tree

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/index/IndexInfo.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Collections;
2525
import java.util.List;
2626
import java.util.Optional;
27+
import java.util.Set;
2728
import java.util.stream.Collectors;
2829

2930
import org.bson.Document;
@@ -41,6 +42,7 @@
4142
* @author Oliver Gierke
4243
* @author Christoph Strobl
4344
* @author Mark Paluch
45+
* @author dragonfsky
4446
*/
4547
public class IndexInfo {
4648

@@ -182,16 +184,80 @@ public List<IndexField> getIndexFields() {
182184
}
183185

184186
/**
185-
* Returns whether the index is covering exactly the fields given independently of the order.
187+
* Returns whether the index contains all given field keys independently of their position. Field keys are compared as
188+
* a set and therefore repeated input keys are ignored.
186189
*
187190
* @param keys must not be {@literal null}.
188191
* @return
192+
* @since 5.1
189193
*/
194+
public boolean containsAllFields(Collection<String> keys) {
195+
196+
Assert.notNull(keys, "Collection of keys must not be null");
197+
198+
return getIndexFieldKeys().containsAll(keys);
199+
}
200+
201+
/**
202+
* Returns whether the index contains all given field keys independently of their position. Field keys are compared as
203+
* a set and therefore repeated input keys are ignored.
204+
*
205+
* @param keys must not be {@literal null}.
206+
* @return
207+
* @deprecated since 5.1. Use {@link #containsAllFields(Collection)}, {@link #isIndexForFieldsExactly(Collection)}, or
208+
* {@link #coversFields(Collection)} to express the intended index field matching semantics.
209+
*/
210+
@Deprecated(since = "5.1")
190211
public boolean isIndexForFields(Collection<String> keys) {
212+
return containsAllFields(keys);
213+
}
214+
215+
/**
216+
* Returns whether the index matches exactly the given field keys independently of their order. Field keys are compared
217+
* as a set and therefore repeated input keys are ignored.
218+
*
219+
* @param keys must not be {@literal null}.
220+
* @return
221+
* @since 5.1
222+
*/
223+
public boolean isIndexForFieldsExactly(Collection<String> keys) {
191224

192225
Assert.notNull(keys, "Collection of keys must not be null");
193226

194-
return this.indexFields.stream().map(IndexField::getKey).collect(Collectors.toSet()).containsAll(keys);
227+
Set<String> indexFieldKeys = getIndexFieldKeys();
228+
Set<String> keysToCheck = keys.stream().collect(Collectors.toSet());
229+
230+
return indexFieldKeys.size() == keysToCheck.size() && indexFieldKeys.containsAll(keysToCheck);
231+
}
232+
233+
/**
234+
* Returns whether the given field keys are covered by this index according to compound-index prefix field matching.
235+
* Field keys are compared as a set and therefore repeated input keys are ignored. This method matches
236+
* {@link IndexField#getKey() index field keys} only and does not evaluate special query semantics of text, geo, or
237+
* wildcard indexes.
238+
*
239+
* @param keys must not be {@literal null}.
240+
* @return
241+
* @since 5.1
242+
*/
243+
public boolean coversFields(Collection<String> keys) {
244+
245+
Assert.notNull(keys, "Collection of keys must not be null");
246+
247+
Set<String> keysToCheck = keys.stream().collect(Collectors.toSet());
248+
249+
if (keysToCheck.size() > indexFields.size()) {
250+
return false;
251+
}
252+
253+
Set<String> indexFieldPrefix = indexFields.stream().limit(keysToCheck.size()).map(IndexField::getKey)
254+
.collect(Collectors.toSet());
255+
256+
return indexFieldPrefix.containsAll(keysToCheck);
257+
}
258+
259+
private Set<String> getIndexFieldKeys() {
260+
return this.indexFields.stream().map(IndexField::getKey).collect(Collectors.toSet());
195261
}
196262

197263
public String getName() {

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/index/IndexInfoUnitTests.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* @author Oliver Gierke
3131
* @author Christoph Strobl
3232
* @author Stefan Tirea
33+
* @author dragonfsky
3334
*/
3435
class IndexInfoUnitTests {
3536

@@ -50,14 +51,52 @@ class IndexInfoUnitTests {
5051
}
5152
""";
5253

53-
@Test
54-
void isIndexForFieldsCorrectly() {
54+
@Test // GH-5187
55+
@SuppressWarnings("deprecation")
56+
void isIndexForFieldsRetainsContainsAllBehavior() {
5557

5658
IndexField fooField = IndexField.create("foo", Direction.ASC);
5759
IndexField barField = IndexField.create("bar", Direction.DESC);
5860

5961
IndexInfo info = new IndexInfo(Arrays.asList(fooField, barField), "myIndex", false, false, "");
6062
assertThat(info.isIndexForFields(Arrays.asList("foo", "bar"))).isTrue();
63+
assertThat(info.isIndexForFields(Arrays.asList("foo"))).isTrue();
64+
}
65+
66+
@Test // GH-5187
67+
void containsAllFieldsReturnsTrueForFieldSubset() {
68+
69+
IndexInfo info = new IndexInfo(Arrays.asList(IndexField.create("foo", Direction.ASC),
70+
IndexField.create("bar", Direction.DESC)), "myIndex", false, false, "");
71+
72+
assertThat(info.containsAllFields(Arrays.asList("foo"))).isTrue();
73+
assertThat(info.containsAllFields(Arrays.asList("bar", "foo"))).isTrue();
74+
assertThat(info.containsAllFields(Arrays.asList("foo", "baz"))).isFalse();
75+
}
76+
77+
@Test // GH-5187
78+
void isIndexForFieldsExactlyRequiresSameFields() {
79+
80+
IndexInfo info = new IndexInfo(Arrays.asList(IndexField.create("foo", Direction.ASC),
81+
IndexField.create("bar", Direction.DESC)), "myIndex", false, false, "");
82+
83+
assertThat(info.isIndexForFieldsExactly(Arrays.asList("foo", "bar"))).isTrue();
84+
assertThat(info.isIndexForFieldsExactly(Arrays.asList("bar", "foo"))).isTrue();
85+
assertThat(info.isIndexForFieldsExactly(Arrays.asList("foo"))).isFalse();
86+
assertThat(info.isIndexForFieldsExactly(Arrays.asList("foo", "bar", "baz"))).isFalse();
87+
}
88+
89+
@Test // GH-5187
90+
void coversFieldsOnlyMatchesIndexPrefixes() {
91+
92+
IndexInfo info = new IndexInfo(Arrays.asList(IndexField.create("foo", Direction.ASC),
93+
IndexField.create("bar", Direction.DESC), IndexField.create("baz", Direction.ASC)), "myIndex", false,
94+
false, "");
95+
96+
assertThat(info.coversFields(Arrays.asList("foo"))).isTrue();
97+
assertThat(info.coversFields(Arrays.asList("bar", "foo"))).isTrue();
98+
assertThat(info.coversFields(Arrays.asList("bar"))).isFalse();
99+
assertThat(info.coversFields(Arrays.asList("foo", "baz"))).isFalse();
61100
}
62101

63102
@Test // DATAMONGO-2170

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/RepositoryIndexCreationIntegrationTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
* Integration test for index creation for query methods.
4242
*
4343
* @author Oliver Gierke
44+
* @author dragonfsky
4445
*/
4546
@RunWith(SpringRunner.class)
4647
@ContextConfiguration
@@ -84,7 +85,7 @@ public void testname() {
8485
private static void assertHasIndexForField(List<IndexInfo> indexInfo, String... fields) {
8586

8687
for (IndexInfo info : indexInfo) {
87-
if (info.isIndexForFields(Arrays.asList(fields))) {
88+
if (info.containsAllFields(Arrays.asList(fields))) {
8889
return;
8990
}
9091
}

0 commit comments

Comments
 (0)