Skip to content

Commit

Permalink
Fixing bug with ARRAY_CONTAINS in Reactive Spring. (#34274)
Browse files Browse the repository at this point in the history
* Fixing bug with ARRAY_CONTAINS in Reactive Spring. This was already fixed in Spring in the past.

* Updating the changelog.
  • Loading branch information
trande4884 authored Mar 30, 2023
1 parent 9024bcc commit 570ac85
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,41 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestRepositoryConfig.class)
public class ReactiveTeacherRepositoryIT {

private static final String TEACHER_ID_1 = "1";

private static final String TEACHER_ID_2 = "2";

private static final String TEACHER_ID_3 = "3";

private static final String TEACHER_FIRST_NAME_1 = "FirstName1";

private static final String TEACHER_FIRST_NAME_2 = "FirstName2";

private static final String DEPARTMENT_LAST_NAME_1 = "LastName1";

private static final String DEPARTMENT_LAST_NAME_2 = "LastName2";

private static final ReactiveTeacher TEACHER_1 = new ReactiveTeacher(TEACHER_ID_1, TEACHER_FIRST_NAME_1, DEPARTMENT_LAST_NAME_1);

private static final ReactiveTeacher TEACHER_2 = new ReactiveTeacher(TEACHER_ID_2, TEACHER_FIRST_NAME_1, DEPARTMENT_LAST_NAME_2);

private static final ReactiveTeacher TEACHER_3 = new ReactiveTeacher(TEACHER_ID_3, TEACHER_FIRST_NAME_2, DEPARTMENT_LAST_NAME_1);

@ClassRule
public static final ReactiveIntegrationTestCollectionManager collectionManager = new ReactiveIntegrationTestCollectionManager();

Expand Down Expand Up @@ -67,4 +82,58 @@ public void testSaveWithSuppressedNullValue() {
final Mono<Boolean> existLastNameMono = repository.existsByLastNameIsNull();
StepVerifier.create(existLastNameMono).expectNext(false).expectComplete().verify();
}

@Test
public void testAnnotatedQueryWithArrayContains() {
final Mono<Void> deletedMono = repository.deleteAll();
StepVerifier.create(deletedMono).thenAwait().verifyComplete();
final Flux<ReactiveTeacher> savedFlux = repository.saveAll(Arrays.asList(TEACHER_1, TEACHER_2, TEACHER_3));
StepVerifier.create(savedFlux).thenConsumeWhile(ReactiveTeacher -> true).expectComplete().verify();

List<String> firstNames = new ArrayList<>();
firstNames.add(TEACHER_FIRST_NAME_1);
final Flux<ReactiveTeacher> resultsAsc = repository.annotatedFindByFirstNames(firstNames);
StepVerifier.create(resultsAsc)
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_1))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_2))
.verifyComplete();

List<String> firstNames2 = new ArrayList<>();
firstNames2.add(TEACHER_FIRST_NAME_1);
firstNames2.add(TEACHER_FIRST_NAME_2);
final Flux<ReactiveTeacher> resultsAsc2 = repository.annotatedFindByFirstNames(firstNames2);
StepVerifier.create(resultsAsc2)
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_1))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_2))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_3))
.verifyComplete();

}

@Test
public void testAnnotatedQueryWithArrayContainsAndSort() {
final Mono<Void> deletedMono = repository.deleteAll();
StepVerifier.create(deletedMono).thenAwait().verifyComplete();
final Flux<ReactiveTeacher> savedFlux = repository.saveAll(Arrays.asList(TEACHER_1, TEACHER_2, TEACHER_3));
StepVerifier.create(savedFlux).thenConsumeWhile(ReactiveTeacher -> true).expectComplete().verify();

List<String> firstNames = new ArrayList<>();
firstNames.add(TEACHER_FIRST_NAME_1);
final Flux<ReactiveTeacher> resultsAsc = repository.annotatedFindByFirstNamesWithSort(firstNames, Sort.by(Sort.Direction.DESC, "id"));
StepVerifier.create(resultsAsc)
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_2))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_1))
.verifyComplete();

List<String> firstNames2 = new ArrayList<>();
firstNames2.add(TEACHER_FIRST_NAME_1);
firstNames2.add(TEACHER_FIRST_NAME_2);
final Flux<ReactiveTeacher> resultsAsc2 = repository.annotatedFindByFirstNamesWithSort(firstNames2, Sort.by(Sort.Direction.DESC, "id"));
StepVerifier.create(resultsAsc2)
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_3))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_2))
.expectNextMatches(teacher -> teacher.getId().equals(TEACHER_ID_1))
.verifyComplete();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,25 @@
package com.azure.spring.data.cosmos.repository.repository;

import com.azure.spring.data.cosmos.domain.ReactiveTeacher;
import com.azure.spring.data.cosmos.repository.Query;
import com.azure.spring.data.cosmos.repository.ReactiveCosmosRepository;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.Param;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;


public interface ReactiveTeacherRepository extends ReactiveCosmosRepository<ReactiveTeacher, String> {

Mono<Boolean> existsByFirstNameIsNotNull();

Mono<Boolean> existsByLastNameIsNull();

@Query(value = "SELECT * FROM a WHERE ARRAY_CONTAINS(@firstNames, a.firstName) ")
Flux<ReactiveTeacher> annotatedFindByFirstNames(@Param("firstNames") List<String> firstNames);

@Query(value = "SELECT * FROM a WHERE ARRAY_CONTAINS(@firstNames, a.firstName) ")
Flux<ReactiveTeacher> annotatedFindByFirstNamesWithSort(@Param("firstNames") List<String> firstNames, Sort sort);
}
2 changes: 1 addition & 1 deletion sdk/cosmos/azure-spring-data-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Added a new flag `overwritePolicy` to `CosmosIndexingPolicy` that when set to true (by default it is false) will allow the user to overwrite the Indexing policy in Portal using the Indexing Policy defined in the SDK. This will affect users who change the Indexing Policy they have defined on the container and want that to overwrite what is in portal, you will now need to set the flag `overwritePolicy` to true for this to happen. The reason we have added this breaking change is that allowing overwrite of an existing indexing policy is considered too risky to be a default behavior. The risk is that you may be removing indexes through multiple indexing policy changes, and in that case the query engine may not provide consistent or complete results until all index transformations are complete. So we are changing the default behavior so that users must opt in to overwriting the indexing policy that exists. - See [PR 33171](https://github.com/Azure/azure-sdk-for-java/pull/33171)

#### Bugs Fixed

* Fixing ARRAY_CONTAINS annotated query bug in Reactive Spring introduced by fixing to IN annotated queries. - See [PR 34274](https://github.com/Azure/azure-sdk-for-java/pull/34274)
#### Other Changes

### 3.33.0 (2023-03-17)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.azure.spring.data.cosmos.repository.query.ReactiveCosmosParameterParameterAccessor;
import com.azure.spring.data.cosmos.repository.query.ReactiveCosmosQueryMethod;
import com.azure.spring.data.cosmos.repository.query.SimpleReactiveCosmosEntityMetadata;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ResultProcessor;
Expand All @@ -20,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;

import static com.azure.spring.data.cosmos.core.convert.MappingCosmosConverter.toCosmosDbValue;
Expand Down Expand Up @@ -53,23 +55,36 @@ public Object execute(final Object[] parameters) {
parameters);
final ResultProcessor processor = getQueryMethod().getResultProcessor().withDynamicProjection(accessor);

/*
* The below for loop is used to handle two unique use cases with annotated queries.
* Annotated queries are defined as strings so there is no way to know the clauses
* being used in advance. Some clauses expect an array and others expect just a list of values.
* (1) IN clauses expect the syntax 'IN (a, b, c) which is generated from the if statement.
* (2) ARRAY_CONTAINS expects the syntax 'ARRAY_CONTAINS(["a", "b", "c"], table.param) which
* is generated from the else statement.
*/
String expandedQuery = query;
List<SqlParameter> sqlParameters = new ArrayList<>();
String modifiedExpandedQuery = expandedQuery.toLowerCase(Locale.US).replaceAll("\\s+", "");
for (int paramIndex = 0; paramIndex < parameters.length; paramIndex++) {
Parameter queryParam = getQueryMethod().getParameters().getParameter(paramIndex);
if (parameters[paramIndex] instanceof Collection) {
ArrayList<String> expandParam = (ArrayList<String>) ((Collection<?>) parameters[paramIndex]).stream()
.map(Object::toString).collect(Collectors.toList());
List<String> expandedParamKeys = new ArrayList<>();
for (int arrayIndex = 0; arrayIndex < expandParam.size(); arrayIndex++) {
String paramName = "@" + queryParam.getName().orElse("") + arrayIndex;
expandedParamKeys.add(paramName);
sqlParameters.add(new SqlParameter(paramName, toCosmosDbValue(expandParam.get(arrayIndex))));
}
expandedQuery = expandedQuery.replaceAll("@" + queryParam.getName().orElse(""), String.join(",", expandedParamKeys));
} else {
if (!Sort.class.isAssignableFrom(queryParam.getType())) {
sqlParameters.add(new SqlParameter("@" + queryParam.getName().orElse(""), toCosmosDbValue(parameters[paramIndex])));
String paramName = queryParam.getName().orElse("");
if (!("").equals(paramName)) {
String inParamCheck = "array_contains(@" + paramName.toLowerCase(Locale.US);
if (parameters[paramIndex] instanceof Collection && !modifiedExpandedQuery.contains(inParamCheck)) {
ArrayList<String> expandParam = (ArrayList<String>) ((Collection<?>) parameters[paramIndex]).stream()
.map(Object::toString).collect(Collectors.toList());
List<String> expandedParamKeys = new ArrayList<>();
for (int arrayIndex = 0; arrayIndex < expandParam.size(); arrayIndex++) {
expandedParamKeys.add("@" + paramName + arrayIndex);
sqlParameters.add(new SqlParameter("@" + paramName + arrayIndex, toCosmosDbValue(expandParam.get(arrayIndex))));
}
expandedQuery = expandedQuery.replaceAll("@" + queryParam.getName().orElse(""), String.join(",", expandedParamKeys));
} else {
if (!Pageable.class.isAssignableFrom(queryParam.getType())
&& !Sort.class.isAssignableFrom(queryParam.getType())) {
sqlParameters.add(new SqlParameter("@" + queryParam.getName().orElse(""), toCosmosDbValue(parameters[paramIndex])));
}
}
}
}
Expand Down

0 comments on commit 570ac85

Please sign in to comment.