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. 84
      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

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

@ -24,17 +24,22 @@ import java.util.stream.Collectors;
import org.bson.Document; import org.bson.Document;
import org.springframework.data.mongodb.BindableMongoExpression; import org.springframework.data.mongodb.BindableMongoExpression;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; 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.MongoOperations;
import org.springframework.data.mongodb.core.query.BasicQuery; import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.repository.Hint; import org.springframework.data.mongodb.repository.Hint;
import org.springframework.data.mongodb.repository.ReadPreference; 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.PagedExecution;
import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution; import org.springframework.data.mongodb.repository.query.MongoQueryExecution.SlicedExecution;
import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext; import org.springframework.data.repository.aot.generate.AotRepositoryMethodGenerationContext;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.CodeBlock.Builder; import org.springframework.javapoet.CodeBlock.Builder;
import org.springframework.javapoet.TypeName; import org.springframework.javapoet.TypeName;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils; import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -45,23 +50,84 @@ public class MongoBlocks {
private static final Pattern PARAMETER_BINDING_PATTERN = Pattern.compile("\\?(\\d+)"); 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); return new QueryBlockBuilder(context);
} }
public static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) { static QueryExecutionBlockBuilder queryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
return new QueryExecutionBlockBuilder(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 { static class QueryExecutionBlockBuilder {
AotRepositoryMethodGenerationContext context; AotRepositoryMethodGenerationContext context;
private String queryVariableName;
public QueryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) { public QueryExecutionBlockBuilder(AotRepositoryMethodGenerationContext context) {
this.context = 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); String mongoOpsRef = context.fieldNameOf(MongoOperations.class);
@ -74,10 +140,12 @@ public class MongoBlocks {
: context.getRepositoryInformation().getDomainType(); : context.getRepositoryInformation().getDomainType();
builder.add("\n"); builder.add("\n");
if (isProjecting) { if (isProjecting) {
builder.addStatement("$T<$T> finder = $L.query($T.class).as($T.class)", FindWithQuery.class, actualReturnType, builder.addStatement("$T<$T> finder = $L.query($T.class).as($T.class)", FindWithQuery.class, actualReturnType,
mongoOpsRef, context.getRepositoryInformation().getDomainType(), actualReturnType); mongoOpsRef, context.getRepositoryInformation().getDomainType(), actualReturnType);
} else { } else {
builder.addStatement("$T<$T> finder = $L.query($T.class)", FindWithQuery.class, actualReturnType, mongoOpsRef, builder.addStatement("$T<$T> finder = $L.query($T.class)", FindWithQuery.class, actualReturnType, mongoOpsRef,
context.getRepositoryInformation().getDomainType()); context.getRepositoryInformation().getDomainType());
} }
@ -97,7 +165,6 @@ public class MongoBlocks {
} }
if (context.returnsPage()) { if (context.returnsPage()) {
// builder.addStatement("return finder.$L", terminatingMethod);
builder.addStatement("return new $T(finder, $L).execute($L)", PagedExecution.class, builder.addStatement("return new $T(finder, $L).execute($L)", PagedExecution.class,
context.getPageableParameterName(), queryVariableName); context.getPageableParameterName(), queryVariableName);
} else if (context.returnsSlice()) { } else if (context.returnsSlice()) {
@ -110,7 +177,6 @@ public class MongoBlocks {
return builder.build(); return builder.build();
} }
} }
static class QueryBlockBuilder { static class QueryBlockBuilder {
@ -118,6 +184,7 @@ public class MongoBlocks {
AotRepositoryMethodGenerationContext context; AotRepositoryMethodGenerationContext context;
StringQuery source; StringQuery source;
List<String> arguments; List<String> arguments;
private String queryVariableName;
public QueryBlockBuilder(AotRepositoryMethodGenerationContext context) { public QueryBlockBuilder(AotRepositoryMethodGenerationContext context) {
this.context = context; this.context = context;
@ -134,7 +201,12 @@ public class MongoBlocks {
return this; return this;
} }
CodeBlock build(String queryVariableName) { public QueryBlockBuilder usingQueryVariableName(String queryVariableName) {
this.queryVariableName = queryVariableName;
return this;
}
CodeBlock build() {
CodeBlock.Builder builder = CodeBlock.builder(); 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;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.springframework.core.annotation.AnnotatedElementUtils; 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.core.MongoOperations;
import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.Aggregation;
import org.springframework.data.mongodb.repository.Query; import org.springframework.data.mongodb.repository.Query;
@ -55,10 +56,6 @@ public class MongoRepositoryContributor extends RepositoryContributor {
// TODO: do not generate stuff for spel expressions // TODO: do not generate stuff for spel expressions
// skip currently unsupported Stuff.
if (generationContext.isDeleteMethod()) {
return null;
}
if (AnnotatedElementUtils.hasAnnotation(generationContext.getMethod(), Aggregation.class)) { if (AnnotatedElementUtils.hasAnnotation(generationContext.getMethod(), Aggregation.class)) {
return null; return null;
} }
@ -100,8 +97,19 @@ public class MongoRepositoryContributor extends RepositoryContributor {
private static void writeStringQuery(AotRepositoryMethodGenerationContext context, Builder body, StringQuery query) { private static void writeStringQuery(AotRepositoryMethodGenerationContext context, Builder body, StringQuery query) {
body.addCode(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName()))); body.addCode(context.codeBlocks().logDebug("invoking [%s]".formatted(context.getMethod().getName())));
body.addCode(MongoBlocks.queryBlockBuilder(context).filter(query).build("query")); QueryBlockBuilder queryBlockBuilder = MongoBlocks.queryBlockBuilder(context).filter(query);
body.addCode(MongoBlocks.queryExecutionBlockBuilder(context).build("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) { 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 @@
*/ */
package org.springframework.data.mongodb.repository.query; package org.springframework.data.mongodb.repository.query;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -31,6 +32,9 @@ import org.springframework.data.geo.Point;
import org.springframework.data.mongodb.core.ExecutableFindOperation; import org.springframework.data.mongodb.core.ExecutableFindOperation;
import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery;
import org.springframework.data.mongodb.core.ExecutableFindOperation.TerminatingFind; 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.ExecutableUpdateOperation.ExecutableUpdate;
import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.NearQuery;
@ -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. * {@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> {
@Query("{ 'lastname' : { '$regex' : '^?0' } }") @Query("{ 'lastname' : { '$regex' : '^?0' } }")
Slice<User> findAnnotatedQuerySliceOfUsersByLastname(String lastname, Pageable pageable); 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: updates
// TODO: Aggregations // 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;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; 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.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
@ -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 @Test
void testDerivedFinderWithAnnotatedSort() { void testDerivedFinderWithAnnotatedSort() {

Loading…
Cancel
Save