Browse Source

Support delete methods

labs/generated-repositories
Christoph Strobl 11 months ago
parent
commit
7c34b5b136
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 86
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java
  2. 20
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java
  3. 37
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java
  4. 18
      spring-data-mongodb/src/test/java/example/aot/UserRepository.java
  5. 44
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributorTests.java

86
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoBlocks.java

@ -24,17 +24,22 @@ import java.util.stream.Collectors; @@ -24,17 +24,22 @@ import java.util.stream.Collectors;
import org.bson.Document;
import org.springframework.data.mongodb.BindableMongoExpression;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableRemoveOperation.ExecutableRemove;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.Hint;
import org.springframework.data.mongodb.repository.ReadPreference;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecutionX;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.DeleteExecutionX.Type;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.PagedExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution;
import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.TypeName;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
@ -45,23 +50,84 @@ public class MongoBlocks { @@ -45,23 +50,84 @@ public class MongoBlocks {
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)");
public static QueryBlockBuilder queryBlockBuilder(AotRepositoryMethodGenerationContext context) {
static QueryBlockBuilder queryBlockBuilder(AotRepositoryMethodGenerationContext context) {
return new QueryBlockBuilder(context);
}
public static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
return new QueryExecutionBlockBuilder(context);
}
static DeleteExecutionBuilder deleteExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
return new DeleteExecutionBuilder(context);
}
static class DeleteExecutionBuilder {
AotRepositoryMethodGenerationContext context;
String queryVariableName;
public DeleteExecutionBuilder(AotRepositoryMethodGenerationContext context) {
this.context = context;
}
public DeleteExecutionBuilder referencing(String queryVariableName) {
this.queryVariableName = queryVariableName;
return this;
}
public CodeBlock build() {
String mongoOpsRef = context.fieldNameOf(MongoOperations.class);
Builder builder = CodeBlock.builder();
boolean isProjecting = context.getActualReturnType() != null
&& !ObjectUtils.nullSafeEquals(TypeName.get(context.getRepositoryInformation().getDomainType()),
context.getActualReturnType());
Object actualReturnType = isProjecting ? context.getActualReturnType()
: context.getRepositoryInformation().getDomainType();
builder.add("\n");
builder.addStatement("$T<$T> remover = $L.remove($T.class)", ExecutableRemove.class, actualReturnType,
mongoOpsRef, context.getRepositoryInformation().getDomainType());
Type type = Type.FIND_AND_REMOVE_ALL;
if (context.returnsSingleValue()) {
if (!ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())) {
type = Type.FIND_AND_REMOVE_ONE;
} else {
type = Type.ALL;
}
}
actualReturnType = ClassUtils.isPrimitiveOrWrapper(context.getMethod().getReturnType())
? ClassName.get(context.getMethod().getReturnType())
: context.returnsSingleValue() ? actualReturnType : context.getReturnType();
builder.addStatement("return ($T) new $T(remover, $T.$L).execute($L)", actualReturnType, DeleteExecutionX.class,
DeleteExecutionX.Type.class, type.name(), queryVariableName);
return builder.build();
}
}
static class QueryExecutionBlockBuilder {
AotRepositoryMethodGenerationContext context;
private String queryVariableName;
public QueryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
this.context = context;
}
CodeBlock build(String queryVariableName) {
QueryExecutionBlockBuilder referencing(String queryVariableName) {
this.queryVariableName = queryVariableName;
return this;
}
CodeBlock build() {
String mongoOpsRef = context.fieldNameOf(MongoOperations.class);
@ -74,10 +140,12 @@ public class MongoBlocks { @@ -74,10 +140,12 @@ public class MongoBlocks {
: context.getRepositoryInformation().getDomainType();
builder.add("\n");
if (isProjecting) {
builder.addStatement("$T<$T> finder = $L.query($T.class).as($T.class)", FindWithQuery.class, actualReturnType,
mongoOpsRef, context.getRepositoryInformation().getDomainType(), actualReturnType);
} else {
builder.addStatement("$T<$T> finder = $L.query($T.class)", FindWithQuery.class, actualReturnType, mongoOpsRef,
context.getRepositoryInformation().getDomainType());
}
@ -97,7 +165,6 @@ public class MongoBlocks { @@ -97,7 +165,6 @@ public class MongoBlocks {
}
if (context.returnsPage()) {
// builder.addStatement("return finder.$L", terminatingMethod);
builder.addStatement("return new $T(finder, $L).execute($L)", PagedExecution.class,
context.getPageableParameterName(), queryVariableName);
} else if (context.returnsSlice()) {
@ -110,7 +177,6 @@ public class MongoBlocks { @@ -110,7 +177,6 @@ public class MongoBlocks {
return builder.build();
}
}
static class QueryBlockBuilder {
@ -118,7 +184,8 @@ public class MongoBlocks { @@ -118,7 +184,8 @@ public class MongoBlocks {
AotRepositoryMethodGenerationContext context;
StringQuery source;
List<String> arguments;
private String queryVariableName;
public QueryBlockBuilder(AotRepositoryMethodGenerationContext context) {
this.context = context;
this.arguments = Arrays.stream(context.getMethod().getParameters()).map(Parameter::getName)
@ -134,7 +201,12 @@ public class MongoBlocks { @@ -134,7 +201,12 @@ public class MongoBlocks {
return this;
}
CodeBlock build(String queryVariableName) {
public QueryBlockBuilder usingQueryVariableName(String queryVariableName) {
this.queryVariableName = queryVariableName;
return this;
}
CodeBlock build() {
CodeBlock.Builder builder = CodeBlock.builder();

20
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributor.java

@ -18,6 +18,7 @@ package org.springframework.data.mongodb.aot.generated; @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.aot.generated;
import java.util.regex.Pattern;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.mongodb.aot.generated.MongoBlocks.QueryBlockBuilder;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.Query;
@ -55,10 +56,6 @@ public class MongoRepositoryContributor extends RepositoryContributor { @@ -55,10 +56,6 @@ public class MongoRepositoryContributor extends RepositoryContributor {
// TODO: do not generate stuff for spel expressions
// skip currently unsupported Stuff.
if (generationContext.isDeleteMethod()) {
return null;
}
if (AnnotatedElementUtils.hasAnnotation(generationContext.getMethod(), Aggregation.class)) {
return null;
}
@ -100,8 +97,19 @@ public class MongoRepositoryContributor extends RepositoryContributor { @@ -100,8 +97,19 @@ public class MongoRepositoryContributor extends RepositoryContributor {
private static void writeStringQuery(AotRepositoryMethodGenerationContext context, Builder body, StringQuery query) {
body.addCode(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName())));
body.addCode(MongoBlocks.queryBlockBuilder(context).filter(query).build("query"));
body.addCode(MongoBlocks.queryExecutionBlockBuilder(context).build("query"));
QueryBlockBuilder queryBlockBuilder = MongoBlocks.queryBlockBuilder(context).filter(query);
if (context.isDeleteMethod()) {
String deleteQueryVariableName = "deleteQuery";
body.addCode(queryBlockBuilder.usingQueryVariableName(deleteQueryVariableName).build());
body.addCode(MongoBlocks.deleteExecutionBlockBuilder(context).referencing(deleteQueryVariableName).build());
} else {
String filterQueryVariableName = "filterQuery";
body.addCode(queryBlockBuilder.usingQueryVariableName(filterQueryVariableName).build());
body.addCode(MongoBlocks.queryExecutionBlockBuilder(context).referencing(filterQueryVariableName).build());
}
}
private static void userAnnotatedQuery(AotRepositoryMethodGenerationContext context, Builder body, Query query) {

37
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/query/MongoQueryExecution.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.data.mongodb.repository.query;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
@ -31,6 +32,9 @@ import org.springframework.data.geo.Point; @@ -31,6 +32,9 @@ import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind;
import org.springframework.data.mongodb.core.ExecutableRemoveOperation;
import org.springframework.data.mongodb.core.ExecutableRemoveOperation.ExecutableRemove;
import org.springframework.data.mongodb.core.ExecutableRemoveOperation.TerminatingRemove;
import org.springframework.data.mongodb.core.ExecutableUpdateOperation.ExecutableUpdate;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.NearQuery;
@ -242,6 +246,39 @@ public interface MongoQueryExecution { @@ -242,6 +246,39 @@ public interface MongoQueryExecution {
}
}
final class DeleteExecutionX<T> implements MongoQueryExecution {
ExecutableRemoveOperation.ExecutableRemove<T> remove;
Type type;
public DeleteExecutionX(ExecutableRemove<T> remove, Type type) {
this.remove = remove;
this.type = type;
}
@Nullable
@Override
public Object execute(Query query) {
TerminatingRemove<T> doRemove = remove.matching(query);
if (Type.ALL.equals(type)) {
DeleteResult result = doRemove.all();
return result.wasAcknowledged() ? Long.valueOf(result.getDeletedCount()) : Long.valueOf(0);
} else if (Type.FIND_AND_REMOVE_ALL.equals(type)) {
return doRemove.findAndRemove();
} else if (Type.FIND_AND_REMOVE_ONE.equals(type)) {
Iterator<T> removed = doRemove.findAndRemove().iterator();
return removed.hasNext() ? removed.next() : null;
}
throw new RuntimeException();
}
public enum Type {
FIND_AND_REMOVE_ONE, FIND_AND_REMOVE_ALL, ALL
}
}
/**
* {@link MongoQueryExecution} removing documents matching the query.
*

18
spring-data-mongodb/src/test/java/example/aot/UserRepository.java

@ -104,7 +104,23 @@ public interface UserRepository extends CrudRepository<User, String> { @@ -104,7 +104,23 @@ public interface UserRepository extends CrudRepository<User, String> {
@Query("{ 'lastname' : { '$regex' : '^?0' } }")
Slice<User> findAnnotatedQuerySliceOfUsersByLastname(String lastname, Pageable pageable);
// TODO: deletes
/* deletes */
User deleteByUsername(String username);
@Query(value = "{ 'username' : ?0 }", delete = true)
User deleteAnnotatedQueryByUsername(String username);
Long deleteByLastnameStartingWith(String lastname);
@Query(value = "{ 'lastname' : { '$regex' : '^?0' } }", delete = true)
Long deleteAnnotatedQueryByLastnameStartingWith(String lastname);
List<User> deleteUsersByLastnameStartingWith(String lastname);
@Query(value = "{ 'lastname' : { '$regex' : '^?0' } }", delete = true)
List<User> deleteUsersAnnotatedQueryByLastnameStartingWith(String lastname);
// TODO: updates
// TODO: Aggregations

44
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/aot/generated/MongoRepositoryContributorTests.java

@ -51,6 +51,8 @@ import org.junit.jupiter.api.BeforeAll; @@ -51,6 +51,8 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -388,6 +390,48 @@ public class MongoRepositoryContributorTests { @@ -388,6 +390,48 @@ public class MongoRepositoryContributorTests {
});
}
@ParameterizedTest
@ValueSource(strings = { "deleteByUsername", "deleteAnnotatedQueryByUsername" })
void testDeleteSingle(String methodName) {
generated.verify(methodInvoker -> {
User result = methodInvoker.invoke(methodName, "yoda").onBean("aotUserRepository");
assertThat(result).isNotNull().extracting(User::getUsername).isEqualTo("yoda");
});
assertThat(client.getDatabase(DB_NAME).getCollection("user").countDocuments()).isEqualTo(6L);
}
@ParameterizedTest
@ValueSource(strings = { "deleteByLastnameStartingWith", "deleteAnnotatedQueryByLastnameStartingWith" })
void testDerivedDeleteMultipleReturningDeleteCount(String methodName) {
generated.verify(methodInvoker -> {
Long result = methodInvoker.invoke(methodName, "S").onBean("aotUserRepository");
assertThat(result).isEqualTo(4L);
});
assertThat(client.getDatabase(DB_NAME).getCollection("user").countDocuments()).isEqualTo(3L);
}
@ParameterizedTest
@ValueSource(strings = { "deleteUsersByLastnameStartingWith", "deleteUsersAnnotatedQueryByLastnameStartingWith" })
void testDerivedDeleteMultipleReturningDeleted(String methodName) {
generated.verify(methodInvoker -> {
List<User> result = methodInvoker.invoke(methodName, "S").onBean("aotUserRepository");
assertThat(result).extracting(User::getUsername).containsExactlyInAnyOrder("han", "kylo", "luke", "vader");
});
assertThat(client.getDatabase(DB_NAME).getCollection("user").countDocuments()).isEqualTo(3L);
}
@Test
void testDerivedFinderWithAnnotatedSort() {

Loading…
Cancel
Save