Skip to content

Commit a995b2b

Browse files
authored
feat: support increment (#63)
* feat: support increment of patch api * docs: add increment in readme * test: fix tests * chore: bump version to 0.5.10
1 parent 10ede54 commit a995b2b

11 files changed

Lines changed: 339 additions & 58 deletions

File tree

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,23 @@ db.updatePartial("Collection", user1.id, Map.of("lastName", "Hanks", "status", "
227227
*/
228228
```
229229

230+
### Increment
231+
232+
```java
233+
// support incrementing a number field
234+
// see https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update-getting-started?tabs=java
235+
236+
// increment age by 1
237+
var result = db.increment("Collection1", "id1", "/age", 1, "Users");
238+
239+
// increment age by -5
240+
var result = db.increment("Collection1", "id1", "/age", -5, "Users");
241+
242+
// 400 Bad Request Exception will be throw if the field is not an integer
243+
var result = db.increment("Collection1", "id1", "/name", 1, "Users");
244+
245+
```
246+
230247
### Cross-partition queries
231248

232249
```java

pom.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<groupId>com.github.thunderz99</groupId>
55
<artifactId>java-cosmos</artifactId>
66
<packaging>jar</packaging>
7-
<version>0.5.9</version>
7+
<version>0.5.10</version>
88
<name>${project.groupId}:${project.artifactId}$</name>
99
<description>A lightweight Azure CosmosDB client for Java</description>
1010
<url>https://github.com/thunderz99/java-cosmos</url>
@@ -88,7 +88,13 @@
8888
<artifactId>azure-documentdb</artifactId>
8989
<version>2.6.4</version>
9090
</dependency>
91-
91+
92+
<dependency>
93+
<groupId>com.azure</groupId>
94+
<artifactId>azure-cosmos</artifactId>
95+
<version>4.29.1</version>
96+
</dependency>
97+
9298
<dependency>
9399
<groupId>io.github.cdimascio</groupId>
94100
<artifactId>java-dotenv</artifactId>

src/main/java/io/github/thunderz99/cosmos/Cosmos.java

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
import java.util.List;
44
import java.util.Objects;
55
import java.util.Set;
6-
import java.util.regex.Pattern;
76
import java.util.stream.Collectors;
87

8+
import com.azure.cosmos.CosmosClient;
9+
import com.azure.cosmos.CosmosClientBuilder;
910
import com.microsoft.azure.documentdb.*;
1011
import io.github.thunderz99.cosmos.util.Checker;
12+
import io.github.thunderz99.cosmos.util.ConnectionStringUtil;
13+
import io.github.thunderz99.cosmos.util.EnvUtil;
1114
import org.apache.commons.collections4.CollectionUtils;
1215
import org.apache.commons.lang3.StringUtils;
1316
import org.apache.commons.lang3.tuple.Pair;
@@ -32,11 +35,11 @@ public class Cosmos {
3235

3336
DocumentClient client;
3437

35-
String account;
38+
CosmosClient clientV4;
3639

37-
static Pattern connectionStringPattern = Pattern.compile("AccountEndpoint=(?<endpoint>.+);AccountKey=(?<key>[^;]+);?");
40+
String account;
3841

39-
public static final String JC_SDK_V4_ENABLE = "JC_SDK_V4_ENABLE";
42+
public static final String COSMOS_SDK_V4_ENABLE = "COSMOS_SDK_V4_ENABLE";
4043

4144
public static final String ETAG = "_etag";
4245

@@ -46,13 +49,30 @@ public Cosmos(String connectionString) {
4649

4750
public Cosmos(String connectionString, List<String> preferredRegions) {
4851

49-
Pair<String, String> pair = parseConnectionString(connectionString);
52+
Pair<String, String> pair = ConnectionStringUtil.parseConnectionString(connectionString);
5053
var endpoint = pair.getLeft();
5154
var key = pair.getRight();
5255

5356
this.client = new DocumentClient(endpoint, key, ConnectionPolicy.GetDefault(), ConsistencyLevel.Session);
5457

55-
//var v4Enable = Boolean.parseBoolean(EnvUtil.getOrDefault(Cosmos.JC_SDK_V4_ENABLE, "false"));
58+
// default to true
59+
var v4Enable = Boolean.parseBoolean(EnvUtil.getOrDefault(Cosmos.COSMOS_SDK_V4_ENABLE, "true"));
60+
61+
if (v4Enable) {
62+
log.info("COSMOS_SDK_V4_ENABLE is enabled for endpoint:{}", endpoint);
63+
this.clientV4 = new CosmosClientBuilder()
64+
.endpoint(endpoint)
65+
.key(key)
66+
.preferredRegions(preferredRegions)
67+
.consistencyLevel(com.azure.cosmos.ConsistencyLevel.SESSION)
68+
.contentResponseOnWriteEnabled(true)
69+
.buildClient();
70+
71+
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
72+
this.clientV4.close();
73+
}));
74+
}
75+
5676

5777
}
5878

@@ -332,15 +352,36 @@ static UniqueKey toUniqueKey(String key) {
332352
}
333353

334354

335-
static String getDatabaseLink(String db) {
355+
/**
356+
* Generate database link format used in cosmosdb
357+
*
358+
* @param db db name
359+
* @return databaseLink
360+
*/
361+
public static String getDatabaseLink(String db) {
336362
return String.format("/dbs/%s", db);
337363
}
338364

339-
static String getCollectionLink(String db, String coll) {
365+
/**
366+
* Generate database link format used in cosmosdb
367+
*
368+
* @param db db name
369+
* @param coll collection name
370+
* @return collection link
371+
*/
372+
public static String getCollectionLink(String db, String coll) {
340373
return String.format("/dbs/%s/colls/%s", db, coll);
341374
}
342375

343-
static String getDocumentLink(String db, String coll, String id) {
376+
/**
377+
* Generate document link format used in cosmosdb
378+
*
379+
* @param db db name
380+
* @param coll collection name
381+
* @param id document id
382+
* @return document link
383+
*/
384+
public static String getDocumentLink(String db, String coll, String id) {
344385
return String.format("/dbs/%s/colls/%s/docs/%s", db, coll, id);
345386
}
346387

@@ -383,24 +424,4 @@ static String getAccount(DocumentClient client) throws DocumentClientException {
383424
return client.getDatabaseAccount().get("id").toString();
384425
}
385426

386-
static Pair<String, String> parseConnectionString(String connectionString) {
387-
388-
var matcher = connectionStringPattern.matcher(connectionString);
389-
if (!matcher.find()) {
390-
throw new IllegalStateException(
391-
"Make sure connectionString contains 'AccountEndpoint=' and 'AccountKey=' ");
392-
}
393-
String endpoint = matcher.group("endpoint");
394-
String key = matcher.group("key");
395-
396-
Checker.check(StringUtils.isNotBlank(endpoint), "Make sure connectionString contains 'AccountEndpoint=' ");
397-
Checker.check(StringUtils.isNotBlank(key), "Make sure connectionString contains 'AccountKey='");
398-
399-
if (log.isInfoEnabled()) {
400-
log.info("endpoint:{}", endpoint);
401-
log.info("key:{}...", key.substring(0, 3));
402-
}
403-
404-
return Pair.of(endpoint, key);
405-
}
406427
}

src/main/java/io/github/thunderz99/cosmos/CosmosDatabase.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.github.thunderz99.cosmos;
22

3+
import java.util.LinkedHashMap;
34
import java.util.Map;
45
import java.util.stream.Collectors;
56

7+
import com.azure.cosmos.CosmosClient;
8+
import com.azure.cosmos.models.CosmosPatchOperations;
69
import com.microsoft.azure.documentdb.*;
710
import io.github.thunderz99.cosmos.condition.Aggregate;
811
import io.github.thunderz99.cosmos.condition.Condition;
@@ -33,12 +36,15 @@ public class CosmosDatabase {
3336

3437
DocumentClient client;
3538

39+
CosmosClient clientV4;
40+
3641
Cosmos cosmosAccount;
3742

3843
CosmosDatabase(Cosmos cosmosAccount, String db) {
3944
this.cosmosAccount = cosmosAccount;
4045
this.db = db;
4146
this.client = cosmosAccount.client;
47+
this.clientV4 = cosmosAccount.clientV4;
4248
}
4349

4450

@@ -767,6 +773,46 @@ public int count(String coll, Condition cond, String partition) throws Exception
767773

768774
}
769775

776+
/**
777+
* Increment a number field of a document using json path format(e.g. "/count")
778+
*
779+
* <p>
780+
* see json patch format: <a href="http://jsonpatch.com/">json path</a>
781+
* <br>
782+
* see details of increment: <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/partial-document-update#supported-operations">supported operations: increment</a>
783+
* </p>
784+
*
785+
* @param coll
786+
* @param id
787+
* @param path
788+
* @param value
789+
* @param partition
790+
* @return
791+
* @throws Exception
792+
*/
793+
public CosmosDocument increment(String coll, String id, String path, int value, String partition) throws Exception {
794+
795+
var documentLink = Cosmos.getDocumentLink(db, coll, id);
796+
797+
Checker.checkNotNull(this.clientV4, String.format("SDK v4 must be enabled to use increment method. docLink:%s", documentLink));
798+
799+
var container = this.clientV4.getDatabase(db).getContainer(coll);
800+
801+
var response = RetryUtil.executeWithRetry(() -> container.patchItem(
802+
id,
803+
new com.azure.cosmos.models.PartitionKey(partition),
804+
CosmosPatchOperations
805+
.create()
806+
.increment(path, value),
807+
LinkedHashMap.class
808+
));
809+
810+
var item = response.getItem();
811+
log.info("increment Document:{}, partition:{}, account:{}", documentLink, partition, getAccount());
812+
813+
return new CosmosDocument(item);
814+
}
815+
770816
RequestOptions requestOptions(String partition) {
771817
var options = new RequestOptions();
772818
options.setPartitionKey(new PartitionKey(partition));

src/main/java/io/github/thunderz99/cosmos/CosmosException.java

Lines changed: 81 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.github.thunderz99.cosmos;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnore;
34
import com.microsoft.azure.documentdb.DocumentClientException;
45

56
/**
@@ -9,28 +10,104 @@ public class CosmosException extends RuntimeException {
910

1011
static final long serialVersionUID = 1L;
1112

13+
@JsonIgnore
1214
private DocumentClientException dce;
1315

16+
@JsonIgnore
17+
private com.azure.cosmos.CosmosException ce;
18+
19+
/**
20+
* http status code
21+
*/
22+
int statusCode;
23+
24+
/**
25+
* String error code. e.g. Unauthorized / NotFound / etc
26+
*/
27+
String code;
28+
29+
/**
30+
* T time to retry suggested by the 429 exception in milliseconds. 0 if not 429 exception.
31+
*/
32+
long retryAfterInMilliseconds;
33+
34+
35+
/**
36+
* Constructor using DocumentClientException;
37+
*
38+
* @param dce document client exception
39+
*/
1440
public CosmosException(DocumentClientException dce) {
1541
super(dce.getMessage(), dce);
1642
this.dce = dce;
43+
this.statusCode = dce.getStatusCode();
44+
this.code = dce.getError() == null ? "" : dce.getError().getCode();
45+
this.retryAfterInMilliseconds = dce.getRetryAfterInMilliseconds();
46+
}
47+
48+
public CosmosException(com.azure.cosmos.CosmosException ce) {
49+
super(ce.getMessage(), ce);
50+
this.ce = ce;
51+
this.statusCode = ce.getStatusCode();
52+
// can not get CosmosException's code yet.
53+
this.code = "";
54+
this.retryAfterInMilliseconds = ce.getRetryAfterDuration().toMillis();
55+
56+
}
57+
58+
/**
59+
* Constructor using statusCode and message
60+
*
61+
* @param statusCode cosmos exception's httpStatusCode
62+
* @param code string error code
63+
* @param message detail message
64+
*/
65+
public CosmosException(int statusCode, String code, String message) {
66+
super(message);
67+
this.statusCode = statusCode;
68+
this.code = code;
69+
}
70+
71+
/**
72+
* Constructor using statusCode and message
73+
*
74+
* @param statusCode cosmos exception's httpStatusCode
75+
* @param code string error code
76+
* @param message detail message
77+
* @param retryAfterInMilliseconds amount of time should retry after in milliseconds
78+
*/
79+
public CosmosException(int statusCode, String code, String message, long retryAfterInMilliseconds) {
80+
super(message);
81+
this.statusCode = statusCode;
82+
this.code = code;
83+
this.retryAfterInMilliseconds = retryAfterInMilliseconds;
1784
}
1885

1986
/**
2087
* Get the exception's status code. e.g. 404 / 429 / 403
88+
*
2189
* @return status code of exception.
2290
*/
23-
public int getStatusCode(){
24-
return dce.getStatusCode();
91+
public int getStatusCode() {
92+
return statusCode;
93+
}
94+
95+
/**
96+
* Get the string code e.g. Unauthorized / NotFound / etc
97+
*
98+
* @return
99+
*/
100+
public String getCode() {
101+
return code;
25102
}
26103

27104
/**
28105
* Get the time to retry suggested by the 429 exception in milliseconds. Return 0 if not 429 exception.
29106
*
30107
* @return time to retry in milliseconds
31108
*/
32-
public long getRetryAfterInMilliseconds(){
33-
return dce.getRetryAfterInMilliseconds();
109+
public long getRetryAfterInMilliseconds() {
110+
return retryAfterInMilliseconds;
34111
}
35112

36113

0 commit comments

Comments
 (0)