Skip to content

Commit

Permalink
Native query support for reactive repositories (#42)
Browse files Browse the repository at this point in the history
o. Added support for running native queries on reactive repos.
   o. Added new unit tests for reactive native queries.
  • Loading branch information
Akshay-Sundarraj authored Jul 3, 2023
1 parent 9614e0c commit df8bc9f
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*-
* Copyright (c) 2020, 2023 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/

package com.oracle.nosql.spring.data.repository.query;

import com.oracle.nosql.spring.data.core.ReactiveNosqlOperations;
import com.oracle.nosql.spring.data.core.query.NosqlQuery;
import com.oracle.nosql.spring.data.core.query.StringQuery;
import com.oracle.nosql.spring.data.repository.Query;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;

import static com.oracle.nosql.spring.data.repository.query.StringBasedNosqlQuery.hasAmbiguousProjectionFlags;

public class ReactiveStringBasedNosqlQuery extends AbstractReactiveNosqlQuery {
private final String query;

private final boolean isCountQuery;
private final boolean isExistsQuery;
private final boolean isDeleteQuery;

public ReactiveStringBasedNosqlQuery(NosqlQueryMethod method,
ReactiveNosqlOperations operations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this(method.getAnnotatedQuery(), method, operations,
evaluationContextProvider);
}

public ReactiveStringBasedNosqlQuery(String query,
NosqlQueryMethod method,
ReactiveNosqlOperations operations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
super(method, operations);
this.query = query;
if (method.hasAnnotatedQuery()) {
Query queryAnnotation = method.getQueryAnnotation();
isCountQuery = queryAnnotation.count();
isExistsQuery = queryAnnotation.exists();
isDeleteQuery = queryAnnotation.delete();
if (hasAmbiguousProjectionFlags(isCountQuery, isExistsQuery,
isDeleteQuery)) {
throw new IllegalArgumentException(
String.format("Manually defined query for %s cannot be a " +
"count and exists or delete query at the same time!",
method));
}
} else {
isCountQuery = false;
isExistsQuery = false;
isDeleteQuery = false;
}
}

@Override
protected NosqlQuery createQuery(NosqlParameterAccessor accessor) {
return new StringQuery(getQueryMethod(), query, accessor);
}

@Override
protected boolean isDeleteQuery() {
return isDeleteQuery;
}

@Override
protected boolean isExistsQuery() {
return isExistsQuery;
}

@Override
protected boolean isCountQuery() {
return isCountQuery;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ protected boolean isCountQuery() {
return isCountQuery;
}

private static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
static boolean hasAmbiguousProjectionFlags(boolean isCountQuery, boolean isExistsQuery,
boolean isDeleteQuery) {
return countBooleanTrueValues(isCountQuery, isExistsQuery, isDeleteQuery) > 1;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.oracle.nosql.spring.data.repository.query.NosqlQueryMethod;
import com.oracle.nosql.spring.data.repository.query.PartTreeReactiveNosqlQuery;

import com.oracle.nosql.spring.data.repository.query.ReactiveStringBasedNosqlQuery;
import org.springframework.context.ApplicationContext;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.EntityInformation;
Expand Down Expand Up @@ -69,13 +70,16 @@ protected Optional<QueryLookupStrategy> getQueryLookupStrategy(
private static class ReactiveNosqlQueryLookupStrategy implements QueryLookupStrategy {
private final ApplicationContext applicationContext;
private final ReactiveNosqlOperations nosqlOperations;
private final QueryMethodEvaluationContextProvider
evaluationContextProvider;

public ReactiveNosqlQueryLookupStrategy(
ApplicationContext applicationContext,
ReactiveNosqlOperations operations,
QueryMethodEvaluationContextProvider provider) {
this.applicationContext = applicationContext;
this.nosqlOperations = operations;
this.evaluationContextProvider = provider;
}

@Override
Expand All @@ -87,7 +91,18 @@ public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata,

Assert.notNull(queryMethod, "queryMethod must not be null!");
Assert.notNull(nosqlOperations, "dbOperations must not be null!");
return new PartTreeReactiveNosqlQuery(queryMethod, nosqlOperations);

String namedQueryName = queryMethod.getNamedQueryName();
if (namedQueries.hasQuery(namedQueryName)) {
String namedQuery = namedQueries.getQuery(namedQueryName);
return new ReactiveStringBasedNosqlQuery(namedQuery, queryMethod,
nosqlOperations, evaluationContextProvider);
} else if (queryMethod.hasAnnotatedQuery()) {
return new ReactiveStringBasedNosqlQuery(queryMethod, nosqlOperations,
evaluationContextProvider);
} else {
return new PartTreeReactiveNosqlQuery(queryMethod, nosqlOperations);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,20 @@ public void testIgnoreCase() {
machines.forEach(m -> TestCase.assertEquals(machineCache.get(m.getMachineId())
, m));
}

@Test
public void testNative() {
List<Machine> machines = repo.
findAllByLocationNative().collectList().block();
TestCase.assertEquals(8, machines.size());
machines.forEach(m -> {
TestCase.assertNotNull(m.getMachineId());
TestCase.assertNotNull(m.getMachineId().getName());
TestCase.assertNotNull(m.getMachineId().getVersion());
});

machines = repo.findByMachineIdNameNative("name3").collectList().block();
TestCase.assertEquals(4, machines.size());
machines.forEach(m -> TestCase.assertEquals(machineCache.get(m.getMachineId()), m));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.oracle.nosql.spring.data.repository.Query;
import com.oracle.nosql.spring.data.repository.ReactiveNosqlRepository;
import org.springframework.data.repository.query.Param;
import reactor.core.publisher.Flux;

public interface ReactiveMachineRepository extends ReactiveNosqlRepository<Machine, MachineId> {
Expand All @@ -33,4 +34,13 @@ Flux<Machine> findByMachineIdNameOrMachineIdVersion(String name,

//Ignore case
Flux<Machine> findByMachineIdNameIgnoreCase(String name);

//native
@Query("SELECT * FROM Machine m WHERE m" +
".kv_json_.location='newyork'")
Flux<Machine> findAllByLocationNative();

@Query(value = "DECLARE $name STRING; SELECT * FROM Machine AS m " +
"WHERE m.name = $name")
Flux<Machine> findByMachineIdNameNative(@Param("$name") String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
*/
package com.oracle.nosql.spring.data.test.reactive;

import com.oracle.nosql.spring.data.repository.Query;
import com.oracle.nosql.spring.data.repository.ReactiveNosqlRepository;
import com.oracle.nosql.spring.data.test.app.Customer;

import org.springframework.data.repository.query.Param;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

Expand All @@ -24,4 +26,20 @@ public interface CustomerReactiveRepository
Mono<Long> countByLastName(String last);

Flux<Customer> deleteByLastName(String last);

@Query("SELECT * FROM Customer AS c WHERE c.kv_json_.firstName = 'John'")
Flux<Customer> findCustomersByFirstNameJohn();

@Query(value = "DECLARE $firstName STRING; SELECT * FROM Customer AS c " +
"WHERE c.kv_json_.firstName = $firstName")
Flux<Customer> findCustomersByFirstName(@Param("$firstName") String firstName);

@Query("DECLARE $firstName STRING; $last STRING; " +
"SELECT * FROM Customer AS c " +
"WHERE c.kv_json_.firstName = $firstName AND " +
"c.kv_json_.lastName = $last")
Flux<Customer> findCustomersWithLastAndFirstNames(
@Param("$last") String paramLast,
@Param("$firstName") String firstName
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ public void testRepo() {
Mono<Long> count = repo.count();
StepVerifier.create(count).expectNext(7L).verifyComplete();

//native queries
List<Customer> johns =
repo.findCustomersByFirstNameJohn().collectList().block();
Assert.assertTrue(johns.contains(c3) && johns.contains(c4));

johns = repo.findCustomersByFirstName("John").collectList().block();
Assert.assertTrue(johns.size() == 2 &&
johns.contains(c3) && johns.contains(c4));

johns = repo.findCustomersWithLastAndFirstNames("Doe", "John").
collectList().block();
Assert.assertTrue(johns.size() == 1 && johns.contains(c4));

// deleteById
repo.deleteById(c7.customerId).subscribe();
exists = repo.existsById(c7.customerId);
Expand Down

0 comments on commit df8bc9f

Please sign in to comment.