Skip to content

Commit 87d145c

Browse files
Composite index support (Azure#18447)
* add annotation support for composite indexes * fix default value * add support for updating index on change. fix equals and NPR in getCompositeIndexes. Update tests * add missing headers * add missing package declaration * fix javadoc issues
1 parent 821f3a3 commit 87d145c

File tree

10 files changed

+358
-1
lines changed

10 files changed

+358
-1
lines changed

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/CompositePath.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
99
import com.fasterxml.jackson.databind.node.ObjectNode;
1010

11+
import java.util.Objects;
12+
1113
/**
1214
* Represents a composite path of the IndexingPolicy in the Azure Cosmos DB database service.
1315
* A composite path is used in a composite index. For example if you want to run a query like
@@ -110,4 +112,19 @@ void populatePropertyBag() {
110112
JsonSerializable getJsonSerializable() {
111113
return this.jsonSerializable;
112114
}
115+
116+
@Override
117+
public boolean equals(Object o) {
118+
if (this == o) return true;
119+
120+
if (o == null || getClass() != o.getClass()) return false;
121+
122+
CompositePath that = (CompositePath) o;
123+
return Objects.equals(getJsonSerializable(), that.getJsonSerializable());
124+
}
125+
126+
@Override
127+
public int hashCode() {
128+
return Objects.hash(getJsonSerializable());
129+
}
113130
}

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/models/IndexingPolicy.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ public List<List<CompositePath>> getCompositeIndexes() {
204204
if (this.compositeIndexes == null) {
205205
this.compositeIndexes = new ArrayList<>();
206206
ArrayNode compositeIndexes = (ArrayNode) this.jsonSerializable.get(Constants.Properties.COMPOSITE_INDEXES);
207+
if (compositeIndexes == null) {
208+
return this.compositeIndexes;
209+
}
207210
for (int i = 0; i < compositeIndexes.size(); i++) {
208211
ArrayNode compositeIndex = (ArrayNode) compositeIndexes.get(i);
209212
ArrayList<CompositePath> compositePaths = new ArrayList<CompositePath>();

sdk/cosmos/azure-spring-data-cosmos-test/src/test/java/com/azure/spring/data/cosmos/domain/ComplexIndexPolicyEntity.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,34 @@
22
// Licensed under the MIT License.
33
package com.azure.spring.data.cosmos.domain;
44

5+
import com.azure.spring.data.cosmos.core.mapping.CompositeIndex;
6+
import com.azure.spring.data.cosmos.core.mapping.CompositeIndexPath;
57
import com.azure.spring.data.cosmos.core.mapping.Container;
68
import com.azure.spring.data.cosmos.core.mapping.CosmosIndexingPolicy;
79
import org.springframework.data.annotation.Id;
810

911
@Container
10-
@CosmosIndexingPolicy(includePaths = {"/field/?"}, excludePaths = {"/*", "/\"_etag\"/?"})
12+
@CosmosIndexingPolicy(
13+
includePaths = {"/field/?"},
14+
excludePaths = {"/*", "/\"_etag\"/?"},
15+
compositeIndexes = {
16+
@CompositeIndex(paths = {
17+
@CompositeIndexPath(path = "/compositeField1"),
18+
@CompositeIndexPath(path = "/compositeField2")
19+
})
20+
}
21+
)
1122
public class ComplexIndexPolicyEntity {
1223

1324
@Id
1425
String id;
1526

1627
String field;
1728

29+
String compositeField1;
30+
31+
String compositeField2;
32+
1833
public String getId() {
1934
return id;
2035
}
@@ -31,4 +46,19 @@ public void setField(String field) {
3146
this.field = field;
3247
}
3348

49+
public String getCompositeField1() {
50+
return compositeField1;
51+
}
52+
53+
public void setCompositeField1(String compositeField1) {
54+
this.compositeField1 = compositeField1;
55+
}
56+
57+
public String getCompositeField2() {
58+
return compositeField2;
59+
}
60+
61+
public void setCompositeField2(String compositeField2) {
62+
this.compositeField2 = compositeField2;
63+
}
3464
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
package com.azure.spring.data.cosmos.domain;
4+
5+
import com.azure.cosmos.models.CompositePathSortOrder;
6+
import com.azure.spring.data.cosmos.core.mapping.CompositeIndex;
7+
import com.azure.spring.data.cosmos.core.mapping.CompositeIndexPath;
8+
import com.azure.spring.data.cosmos.core.mapping.Container;
9+
import com.azure.spring.data.cosmos.core.mapping.CosmosIndexingPolicy;
10+
import org.springframework.data.annotation.Id;
11+
12+
@Container
13+
@CosmosIndexingPolicy(compositeIndexes = {
14+
@CompositeIndex(paths = {
15+
@CompositeIndexPath(path = "/fieldOne"),
16+
@CompositeIndexPath(path = "/fieldTwo")
17+
}),
18+
@CompositeIndex(paths = {
19+
@CompositeIndexPath(path = "/fieldThree", order = CompositePathSortOrder.DESCENDING),
20+
@CompositeIndexPath(path = "/fieldFour", order = CompositePathSortOrder.DESCENDING)
21+
})
22+
})
23+
public class CompositeIndexEntity {
24+
25+
@Id
26+
String id;
27+
28+
String fieldOne;
29+
String fieldTwo;
30+
String fieldThree;
31+
String fieldFour;
32+
33+
public String getId() {
34+
return id;
35+
}
36+
37+
public void setId(String id) {
38+
this.id = id;
39+
}
40+
41+
public String getFieldOne() {
42+
return fieldOne;
43+
}
44+
45+
public void setFieldOne(String fieldOne) {
46+
this.fieldOne = fieldOne;
47+
}
48+
49+
public String getFieldTwo() {
50+
return fieldTwo;
51+
}
52+
53+
public void setFieldTwo(String fieldTwo) {
54+
this.fieldTwo = fieldTwo;
55+
}
56+
57+
public String getFieldThree() {
58+
return fieldThree;
59+
}
60+
61+
public void setFieldThree(String fieldThree) {
62+
this.fieldThree = fieldThree;
63+
}
64+
65+
public String getFieldFour() {
66+
return fieldFour;
67+
}
68+
69+
public void setFieldFour(String fieldFour) {
70+
this.fieldFour = fieldFour;
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.spring.data.cosmos.repository.integration;
5+
6+
import com.azure.cosmos.models.CompositePath;
7+
import com.azure.cosmos.models.CompositePathSortOrder;
8+
import com.azure.cosmos.models.CosmosContainerProperties;
9+
import com.azure.cosmos.models.ExcludedPath;
10+
import com.azure.cosmos.models.IncludedPath;
11+
import com.azure.cosmos.models.IndexingPolicy;
12+
import com.azure.spring.data.cosmos.core.CosmosTemplate;
13+
import com.azure.spring.data.cosmos.core.ReactiveCosmosTemplate;
14+
import com.azure.spring.data.cosmos.domain.ComplexIndexPolicyEntity;
15+
import com.azure.spring.data.cosmos.domain.CompositeIndexEntity;
16+
import com.azure.spring.data.cosmos.domain.IndexPolicyEntity;
17+
import com.azure.spring.data.cosmos.repository.TestRepositoryConfig;
18+
import com.azure.spring.data.cosmos.repository.support.CosmosEntityInformation;
19+
import com.azure.spring.data.cosmos.repository.support.SimpleCosmosRepository;
20+
import com.azure.spring.data.cosmos.repository.support.SimpleReactiveCosmosRepository;
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
import org.mockito.Mockito;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.test.context.ContextConfiguration;
26+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.List;
31+
32+
import static org.assertj.core.api.Assertions.assertThat;
33+
import static org.assertj.core.api.Assertions.in;
34+
35+
@RunWith(SpringJUnit4ClassRunner.class)
36+
@ContextConfiguration(classes = TestRepositoryConfig.class)
37+
public class CompositeIndexIT {
38+
39+
@Autowired
40+
CosmosTemplate template;
41+
42+
@Autowired
43+
ReactiveCosmosTemplate reactiveTemplate;
44+
45+
CosmosEntityInformation<CompositeIndexEntity, String> information = new CosmosEntityInformation<>(CompositeIndexEntity.class);
46+
47+
@Test
48+
public void canSetCompositeIndex() {
49+
new SimpleCosmosRepository<>(information, template);
50+
CosmosContainerProperties properties = template.getContainerProperties(information.getContainerName());
51+
List<List<CompositePath>> indexes = properties.getIndexingPolicy().getCompositeIndexes();
52+
53+
assertThat(indexes.get(0).get(0).getPath()).isEqualTo("/fieldOne");
54+
assertThat(indexes.get(0).get(0).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
55+
assertThat(indexes.get(0).get(1).getPath()).isEqualTo("/fieldTwo");
56+
assertThat(indexes.get(0).get(1).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
57+
58+
assertThat(indexes.get(1).get(0).getPath()).isEqualTo("/fieldThree");
59+
assertThat(indexes.get(1).get(0).getOrder()).isEqualTo(CompositePathSortOrder.DESCENDING);
60+
assertThat(indexes.get(1).get(1).getPath()).isEqualTo("/fieldFour");
61+
assertThat(indexes.get(1).get(1).getOrder()).isEqualTo(CompositePathSortOrder.DESCENDING);
62+
}
63+
64+
@Test
65+
public void canSetCompositeIndexReactive() {
66+
new SimpleReactiveCosmosRepository<>(information, reactiveTemplate);
67+
CosmosContainerProperties properties = reactiveTemplate.getContainerProperties(information.getContainerName()).block();
68+
List<List<CompositePath>> indexes = properties.getIndexingPolicy().getCompositeIndexes();
69+
70+
assertThat(indexes.get(0).get(0).getPath()).isEqualTo("/fieldOne");
71+
assertThat(indexes.get(0).get(0).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
72+
assertThat(indexes.get(0).get(1).getPath()).isEqualTo("/fieldTwo");
73+
assertThat(indexes.get(0).get(1).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
74+
75+
assertThat(indexes.get(1).get(0).getPath()).isEqualTo("/fieldThree");
76+
assertThat(indexes.get(1).get(0).getOrder()).isEqualTo(CompositePathSortOrder.DESCENDING);
77+
assertThat(indexes.get(1).get(1).getPath()).isEqualTo("/fieldFour");
78+
assertThat(indexes.get(1).get(1).getOrder()).isEqualTo(CompositePathSortOrder.DESCENDING);
79+
}
80+
81+
82+
@Test
83+
public void canUpdateCompositeIndex() {
84+
// initialize policy on entity
85+
new SimpleCosmosRepository<>(information, template);
86+
87+
// set new index policy
88+
IndexingPolicy newIndexPolicy = new IndexingPolicy();
89+
List<List<CompositePath>> newCompositeIndex = new ArrayList<>();
90+
List<CompositePath> innerList = new ArrayList<>();
91+
innerList.add(new CompositePath().setPath("/fieldOne"));
92+
innerList.add(new CompositePath().setPath("/fieldFour"));
93+
newCompositeIndex.add(innerList);
94+
newIndexPolicy.setCompositeIndexes(newCompositeIndex);
95+
96+
// apply new index policy
97+
CosmosEntityInformation<CompositeIndexEntity, String> spyEntityInformation = Mockito.spy(information);
98+
Mockito.doReturn(newIndexPolicy).when(spyEntityInformation).getIndexingPolicy();
99+
new SimpleCosmosRepository<>(spyEntityInformation, template);
100+
101+
// retrieve new policy
102+
CosmosContainerProperties properties = template.getContainerProperties(information.getContainerName());
103+
List<List<CompositePath>> indexes = properties.getIndexingPolicy().getCompositeIndexes();
104+
105+
// assert
106+
assertThat(indexes.size()).isEqualTo(1);
107+
assertThat(indexes.get(0).get(0).getPath()).isEqualTo("/fieldOne");
108+
assertThat(indexes.get(0).get(0).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
109+
assertThat(indexes.get(0).get(1).getPath()).isEqualTo("/fieldFour");
110+
assertThat(indexes.get(0).get(1).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
111+
}
112+
113+
@Test
114+
public void canUpdateCompositeIndexReactive() {
115+
// initialize policy on entity
116+
new SimpleReactiveCosmosRepository<>(information, reactiveTemplate);
117+
118+
// set new index policy
119+
IndexingPolicy newIndexPolicy = new IndexingPolicy();
120+
List<List<CompositePath>> newCompositeIndex = new ArrayList<>();
121+
List<CompositePath> innerList = new ArrayList<>();
122+
innerList.add(new CompositePath().setPath("/fieldOne"));
123+
innerList.add(new CompositePath().setPath("/fieldFour"));
124+
newCompositeIndex.add(innerList);
125+
newIndexPolicy.setCompositeIndexes(newCompositeIndex);
126+
127+
// apply new index policy
128+
CosmosEntityInformation<CompositeIndexEntity, String> spyEntityInformation = Mockito.spy(information);
129+
Mockito.doReturn(newIndexPolicy).when(spyEntityInformation).getIndexingPolicy();
130+
new SimpleReactiveCosmosRepository<>(spyEntityInformation, reactiveTemplate);
131+
132+
// retrieve new policy
133+
CosmosContainerProperties properties = reactiveTemplate.getContainerProperties(information.getContainerName()).block();
134+
List<List<CompositePath>> indexes = properties.getIndexingPolicy().getCompositeIndexes();
135+
136+
// assert
137+
assertThat(indexes.size()).isEqualTo(1);
138+
assertThat(indexes.get(0).get(0).getPath()).isEqualTo("/fieldOne");
139+
assertThat(indexes.get(0).get(0).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
140+
assertThat(indexes.get(0).get(1).getPath()).isEqualTo("/fieldFour");
141+
assertThat(indexes.get(0).get(1).getOrder()).isEqualTo(CompositePathSortOrder.ASCENDING);
142+
}
143+
144+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.spring.data.cosmos.core.mapping;
5+
6+
import java.lang.annotation.ElementType;
7+
import java.lang.annotation.Retention;
8+
import java.lang.annotation.RetentionPolicy;
9+
import java.lang.annotation.Target;
10+
11+
/**
12+
* Annotation for specifying a composite index on CosmosIndexPolicy
13+
*/
14+
@Retention(RetentionPolicy.RUNTIME)
15+
@Target(ElementType.ANNOTATION_TYPE)
16+
public @interface CompositeIndex {
17+
18+
/**
19+
* Array of composite index paths
20+
* @return String
21+
*/
22+
CompositeIndexPath[] paths() default {};
23+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
package com.azure.spring.data.cosmos.core.mapping;
5+
6+
import com.azure.cosmos.models.CompositePathSortOrder;
7+
8+
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/**
14+
* Annotation for specifying a composite index path
15+
*/
16+
@Retention(RetentionPolicy.RUNTIME)
17+
@Target(ElementType.ANNOTATION_TYPE)
18+
public @interface CompositeIndexPath {
19+
20+
/**
21+
* Index path
22+
* @return String
23+
*/
24+
String path() default "";
25+
26+
/**
27+
* Index order
28+
* @return CompositePathSortOrder
29+
*/
30+
CompositePathSortOrder order() default CompositePathSortOrder.ASCENDING;
31+
32+
}

sdk/cosmos/azure-spring-data-cosmos/src/main/java/com/azure/spring/data/cosmos/core/mapping/CosmosIndexingPolicy.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,11 @@
4444
* @return String[]
4545
*/
4646
String[] excludePaths() default {};
47+
48+
49+
/**
50+
* Composite Indexes
51+
* @return CompositeIndexDefinition[]
52+
*/
53+
CompositeIndex[] compositeIndexes() default {};
4754
}

0 commit comments

Comments
 (0)