Skip to content

Commit

Permalink
Adding sum support for annotated queries (Azure#43239)
Browse files Browse the repository at this point in the history
* Adding sum support for annotated queries

* Adding to reactive.

* Updating the changelog

* Removing unused import

* Fixing comments on PR.

* Fixing javadoc issues.

* Fixing javadoc issues.
  • Loading branch information
trande4884 authored Jan 8, 2025
1 parent 8f7b796 commit 32092fb
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 17 deletions.
10 changes: 10 additions & 0 deletions eng/code-quality-reports/src/main/resources/revapi/revapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@
"new": "method <S extends T, T> java.lang.Iterable<S> com.azure.spring.data.cosmos.core.CosmosOperations::insertAll(com.azure.spring.data.cosmos.repository.support.CosmosEntityInformation<T, ?>, java.lang.Iterable<S>)",
"justification": "Spring interfaces are allowed to add methods."
},
{
"code": "java.method.addedToInterface",
"new" : "method <T> long com.azure.spring.data.cosmos.core.CosmosOperations::sum(com.azure.cosmos.models.SqlQuerySpec, java.lang.String)",
"justification": "Spring interfaces are allowed to add methods."
},
{
"code": "java.method.addedToInterface",
"new": "method <S extends T, T> reactor.core.publisher.Mono<java.lang.Void> com.azure.spring.data.cosmos.core.ReactiveCosmosOperations::deleteEntities(com.azure.spring.data.cosmos.repository.support.CosmosEntityInformation<T, ?>, reactor.core.publisher.Flux<S>)",
Expand All @@ -107,6 +112,11 @@
"new": "method <S extends T, T> reactor.core.publisher.Flux<S> com.azure.spring.data.cosmos.core.ReactiveCosmosOperations::insertAll(com.azure.spring.data.cosmos.repository.support.CosmosEntityInformation<T, ?>, java.lang.Iterable<S>)",
"justification": "Spring interfaces are allowed to add methods."
},
{
"code": "java.method.addedToInterface",
"new" : "method reactor.core.publisher.Mono<java.lang.Long> com.azure.spring.data.cosmos.core.ReactiveCosmosOperations::sum(com.azure.cosmos.models.SqlQuerySpec, java.lang.String)",
"justification": "Spring interfaces are allowed to add methods."
},
{
"regex": true,
"code": "java\\.annotation\\.(added|attributeValueChanged)",
Expand Down
1 change: 1 addition & 0 deletions sdk/spring/azure-spring-data-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#### Features Added
* Improved the Exception Handling of 'azure-spring-data-cosmos' to throw more detailed exceptions and not always the same exception - See [PR 42902](https://github.com/Azure/azure-sdk-for-java/pull/42902).
* Implemented sum() support for annotated queries - See [PR 43239](https://github.com/Azure/azure-sdk-for-java/pull/43239).

#### Breaking Changes

Expand Down
1 change: 1 addition & 0 deletions sdk/spring/azure-spring-data-cosmos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ String[] includePaths() default {};
// Excluded paths for indexing
String[] excludePaths() default {};
```
- If using a column for summation in a sum() query that column should be indexed or else it will lead to performance issues.
#### Unique Key Policy
- Azure Spring Data Cosmos supports setting `UniqueKeyPolicy` on container by adding the annotation `@CosmosUniqueKeyPolicy` to domain class. This annotation has the following attributes:
```java readme-sample-CosmosUniqueKeyPolicyCodeSnippet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,16 @@ public interface CosmosOperations {
*/
<T> long count(SqlQuerySpec querySpec, String containerName);

/**
* Sum
*
* @param querySpec the document query spec
* @param containerName the container name
* @param <T> type class of domainType
* @return sum result
*/
<T> long sum(SqlQuerySpec querySpec, String containerName);

/**
* To get converter
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1008,7 +1008,7 @@ private <T> Page<T> paginationQuery(SqlQuerySpec querySpec, SqlQuerySpec countQu
Optional<Object> partitionKeyValue) {
containerName = getContainerNameOverride(containerName);
Slice<T> response = sliceQuery(querySpec, pageable, sort, returnType, containerName, partitionKeyValue);
final long total = getCountValue(countQuerySpec, containerName);
final long total = getNumericValue(countQuerySpec, containerName);
return new CosmosPageImpl<>(response.getContent(), response.getPageable(), total);
}

Expand Down Expand Up @@ -1132,12 +1132,21 @@ public <T> long count(CosmosQuery query, String containerName) {

@Override
public <T> long count(SqlQuerySpec querySpec, String containerName) {
return this.numeric(querySpec, containerName);
}

@Override
public <T> long sum(SqlQuerySpec querySpec, String containerName) {
return this.numeric(querySpec, containerName);
}

private <T> long numeric(SqlQuerySpec querySpec, String containerName) {
containerName = getContainerNameOverride(containerName);
Assert.hasText(containerName, "container name should not be empty");

final Long count = getCountValue(querySpec, containerName);
assert count != null;
return count;
final Long numericResult = getNumericValue(querySpec, containerName);
assert numericResult != null;
return numericResult;
}

@Override
Expand Down Expand Up @@ -1167,10 +1176,10 @@ private void markAuditedIfConfigured(Object object) {

private Long getCountValue(CosmosQuery query, String containerName) {
final SqlQuerySpec querySpec = new CountQueryGenerator().generateCosmos(query);
return getCountValue(querySpec, containerName);
return getNumericValue(querySpec, containerName);
}

private Long getCountValue(SqlQuerySpec querySpec, String containerName) {
private Long getNumericValue(SqlQuerySpec querySpec, String containerName) {
final CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setQueryMetricsEnabled(this.queryMetricsEnabled);
options.setIndexMetricsEnabled(this.indexMetricsEnabled);
Expand All @@ -1182,7 +1191,7 @@ private Long getCountValue(SqlQuerySpec querySpec, String containerName) {
return executeQuery(querySpec, containerName, options)
.publishOn(CosmosSchedulers.SPRING_DATA_COSMOS_PARALLEL)
.onErrorResume(throwable ->
CosmosExceptionUtils.exceptionHandler("Failed to get count value", throwable,
CosmosExceptionUtils.exceptionHandler("Failed to get numeric value", throwable,
this.responseDiagnosticsProcessor))
.doOnNext(response -> CosmosUtils.fillAndProcessResponseDiagnostics(this.responseDiagnosticsProcessor,
response.getCosmosDiagnostics(), response))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,15 @@ Mono<CosmosContainerProperties> replaceContainerProperties(String containerName,
*/
Mono<Long> count(SqlQuerySpec querySpec, String containerName);

/**
* Sum
*
* @param querySpec the document query spec
* @param containerName the container name
* @return sum result
*/
Mono<Long> sum(SqlQuerySpec querySpec, String containerName);

/**
* To get converter
* @return MappingCosmosConverter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -920,7 +920,7 @@ public Mono<Long> count(String containerName) {
@Override
public Mono<Long> count(CosmosQuery query, String containerName) {
final SqlQuerySpec querySpec = new CountQueryGenerator().generateCosmos(query);
return getCountValue(querySpec, containerName);
return getNumericValue(querySpec, containerName);
}

/**
Expand All @@ -932,7 +932,19 @@ public Mono<Long> count(CosmosQuery query, String containerName) {
*/
@Override
public Mono<Long> count(SqlQuerySpec querySpec, String containerName) {
return getCountValue(querySpec, containerName);
return getNumericValue(querySpec, containerName);
}

/**
* Sum
*
* @param querySpec the document query spec
* @param containerName the container name
* @return Mono with sum or error
*/
@Override
public Mono<Long> sum(SqlQuerySpec querySpec, String containerName) {
return getNumericValue(querySpec, containerName);
}

@Override
Expand Down Expand Up @@ -975,7 +987,7 @@ private Flux<JsonNode> runQuery(SqlQuerySpec querySpec, Class<?> domainType) {
this.responseDiagnosticsProcessor));
}

private Mono<Long> getCountValue(SqlQuerySpec querySpec, String containerName) {
private Mono<Long> getNumericValue(SqlQuerySpec querySpec, String containerName) {
final CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setQueryMetricsEnabled(this.queryMetricsEnabled);
options.setIndexMetricsEnabled(this.indexMetricsEnabled);
Expand All @@ -987,7 +999,7 @@ private Mono<Long> getCountValue(SqlQuerySpec querySpec, String containerName) {
.doOnNext(feedResponse -> CosmosUtils.fillAndProcessResponseDiagnostics(this.responseDiagnosticsProcessor,
feedResponse.getCosmosDiagnostics(), feedResponse))
.onErrorResume(throwable ->
CosmosExceptionUtils.exceptionHandler("Failed to get count value", throwable,
CosmosExceptionUtils.exceptionHandler("Failed to get numeric value", throwable,
this.responseDiagnosticsProcessor))
.next()
.map(r -> r.getResults().get(0).asLong());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*/
public class StringBasedCosmosQuery extends AbstractCosmosQuery {
private static final Pattern COUNT_QUERY_PATTERN = Pattern.compile("^\\s*select\\s+value\\s+count.*", Pattern.CASE_INSENSITIVE);
private static final Pattern SUM_QUERY_PATTERN = Pattern.compile("^\\s*select\\s+value\\s+sum.*", Pattern.CASE_INSENSITIVE);

private final String query;

Expand Down Expand Up @@ -104,9 +105,12 @@ public Object execute(final Object[] parameters) {
} else if (isCountQuery()) {
final String container = ((CosmosEntityMetadata<?>) getQueryMethod().getEntityInformation()).getContainerName();
return this.operations.count(querySpec, container);
} else if (isSumQuery()) {
final String container = ((CosmosEntityMetadata<?>) getQueryMethod().getEntityInformation()).getContainerName();
return this.operations.sum(querySpec, container);
} else {
return this.operations.runQuery(querySpec, accessor.getSort(), processor.getReturnedType().getDomainType(),
processor.getReturnedType().getReturnedType());
processor.getReturnedType().getReturnedType());
}
}

Expand All @@ -129,15 +133,31 @@ protected boolean isCountQuery() {
return isCountQuery(query, getQueryMethod().getReturnedObjectType());
}

/**
* This method is used to determine if the query is a sum query.
* @return boolean if the query is a sum query
*/
protected boolean isSumQuery() {
return isSumQuery(query, getQueryMethod().getReturnedObjectType());
}

static boolean isCountQuery(String query, Class<?> returnedType) {
if (isCountQueryReturnType(returnedType)) {
if (isNumericQueryReturnType(returnedType)) {
return COUNT_QUERY_PATTERN.matcher(query).matches();
} else {
return false;
}
}

private static boolean isCountQueryReturnType(Class<?> returnedType) {
static boolean isSumQuery(String query, Class<?> returnedType) {
if (isNumericQueryReturnType(returnedType)) {
return SUM_QUERY_PATTERN.matcher(query).matches();
} else {
return false;
}
}

private static boolean isNumericQueryReturnType(Class<?> returnedType) {
return returnedType == Long.class
|| returnedType == long.class
|| returnedType == Integer.class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ResultProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -96,8 +95,10 @@ public Object execute(final Object[] parameters) {
SqlQuerySpec querySpec = new SqlQuerySpec(stripExtraWhitespaceFromString(expandedQuery), sqlParameters);
if (isCountQuery()) {
final String container = ((SimpleReactiveCosmosEntityMetadata<?>) getQueryMethod().getEntityInformation()).getContainerName();
final Mono<Long> mono = this.operations.count(querySpec, container);
return mono;
return this.operations.count(querySpec, container);
} else if (isSumQuery()) {
final String container = ((SimpleReactiveCosmosEntityMetadata<?>) getQueryMethod().getEntityInformation()).getContainerName();
return this.operations.sum(querySpec, container);
} else {
Flux<?> flux = this.operations.runQuery(querySpec, accessor.getSort(), processor.getReturnedType().getDomainType(),
processor.getReturnedType().getReturnedType());
Expand All @@ -123,4 +124,12 @@ protected boolean isCountQuery() {
return StringBasedCosmosQuery.isCountQuery(query, getQueryMethod().getReturnedObjectType());
}

/**
* This method is used to determine if the query is a sum query.
* @return boolean if the query is a sum query
*/
protected boolean isSumQuery() {
return StringBasedCosmosQuery.isSumQuery(query, getQueryMethod().getReturnedObjectType());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,18 @@ public void testAnnotatedQueryWithValueAsList() {
assertAddressPostalCodesUnordered(postalCodes, addresses);
}

@Test
public void testAnnotatedQueryWithSum() {
Address.TEST_ADDRESS1_PARTITION1.setLongId(1L);
Address.TEST_ADDRESS2_PARTITION1.setLongId(2L);
final List<Address> addresses = Arrays.asList(Address.TEST_ADDRESS1_PARTITION1, Address.TEST_ADDRESS2_PARTITION1);
addressRepository.saveAll(addresses);

final Long sumResult = addressRepository.annotatedSumLongIdValuesByCity(TestConstants.CITY);

assertThat(sumResult).isEqualTo(3);
}

@Test
public void testAnnotatedQueryWithJsonNodeAsPage() {
final List<Address> addresses = Arrays.asList(Address.TEST_ADDRESS1_PARTITION1, Address.TEST_ADDRESS2_PARTITION1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ public class ReactiveLongIdDomainPartitionPartitionRepositoryIT {
private static final Long ID_2 = 67890L;
private static final String NAME_2 = "camille";

private static final Long ID_3 = 98765L;

private static final LongIdDomainPartition DOMAIN_1 = new LongIdDomainPartition(ID_1, NAME_1);
private static final LongIdDomainPartition DOMAIN_2 = new LongIdDomainPartition(ID_2, NAME_2);
private static final LongIdDomainPartition DOMAIN_3 = new LongIdDomainPartition(ID_3, NAME_1);

@ClassRule
public static final ReactiveIntegrationTestCollectionManager collectionManager = new ReactiveIntegrationTestCollectionManager();
Expand Down Expand Up @@ -192,6 +195,18 @@ public void testFindAllSort() {
StepVerifier.create(descAllFlux).expectNext(DOMAIN_2, other, DOMAIN_1).verifyComplete();
}

@Test
public void testSum() {
Mono<Long> sum1 = this.repository.annotatedSumLongIdValuesByName(NAME_1);
StepVerifier.create(sum1).expectNext(12345L).verifyComplete();

Mono<LongIdDomainPartition> saveMono = this.repository.save(DOMAIN_3);
StepVerifier.create(saveMono).expectNext(DOMAIN_3).expectComplete().verify();

Mono<Long> sum2 = this.repository.annotatedSumLongIdValuesByName(NAME_1);
StepVerifier.create(sum2).expectNext(111110L).verifyComplete();
}

private static class InvalidDomain {

private long count;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ public interface AddressRepository extends CosmosRepository<Address, String> {
@Query("select DISTINCT value a.postalCode from a where a.city = @city")
List<String> annotatedFindPostalCodeValuesByCity(@Param("city") String city);

@Query("SELECT VALUE SUM(a.longId) from a where a.city = @city")
Long annotatedSumLongIdValuesByCity(@Param("city") String city);

@Query(value = "select * from a where a.city IN (@cities)")
List<Address> annotatedFindByCityIn(@Param("cities") List<String> cities, Sort sort);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@
package com.azure.spring.data.cosmos.repository.repository;

import com.azure.spring.data.cosmos.domain.LongIdDomainPartition;
import com.azure.spring.data.cosmos.repository.Query;
import com.azure.spring.data.cosmos.repository.ReactiveCosmosRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Mono;

@Repository
public interface ReactiveLongIdDomainPartitionRepository extends ReactiveCosmosRepository<LongIdDomainPartition, Long> {

@Query("SELECT VALUE SUM(a.number) from a where a.name = @name")
Mono<Long> annotatedSumLongIdValuesByName(@Param("name") String name);

}

0 comments on commit 32092fb

Please sign in to comment.