Skip to content

Commit

Permalink
delete using index (#233)
Browse files Browse the repository at this point in the history
* delete using index

* fixed different handling of for delete methods, added docs
  • Loading branch information
musketyr authored May 28, 2024
1 parent 834b5c1 commit 4fbac1a
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 30 deletions.
1 change: 1 addition & 0 deletions docs/guide/src/docs/asciidoc/dynamodb.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ include::{root-dir}/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy
<8> You can combine `@SortKey` and `@Filter` annotations to specify the sort key condition
<9> Only`EQ`, `LE`, `LT`, `GE`, `GT`, `BETWEEN` and `BEGINS_WITH` operators are supported
<10> You can also use `@Filter` annotation to specify the sort key name
<11> If you use any customization annotations on delete method, then the method will be used as batch delete method

TIP: The operator `EQ` is used by default if `@Filter` annotation is not present. This makes it special and the service introduction tries to find the appropriate operation based on the actual value. For collections or arrays, `inList` operation is actually used. If the actual value is `null` then `isNull` operation is used. For other types, `eq` operation is used. For sort keys, `eq` operation is always used.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;

/**
Expand All @@ -62,6 +63,11 @@ public class ServiceIntroduction implements MethodInterceptor<Object, Object> {
private static final String HASH = "hash";
private static final String RANGE = "range";

private static class ItemArgument {
Argument<?> argument;
boolean single;
}

private static class FilterArgument {
Argument<?> firstArgument;
Argument<?> secondArgument;
Expand Down Expand Up @@ -156,7 +162,7 @@ private <T> Object doIntercept(MethodInvocationContext<Object, Object> context,
private <T> Object doIntercept(MethodInvocationContext<Object, Object> context, DynamoDbService<T> service) {
String methodName = context.getMethodName();
if (methodName.startsWith("save")) {
return handleSave(service, context);
return handleSave(service, context, findItemArgument(service, context));
}

if (methodName.startsWith("get") || methodName.startsWith("load")) {
Expand Down Expand Up @@ -211,22 +217,36 @@ private <T> Object doIntercept(MethodInvocationContext<Object, Object> context,
}

if (methodName.startsWith("delete")) {
return handleDelete(service, context);
Optional<ItemArgument> maybeItemArgument = findItemArgument(service, context);
if (maybeItemArgument.isPresent()) {
return handleDelete(service, context, maybeItemArgument);
}
}

if (methodName.startsWith("query") || methodName.startsWith("findAll") || methodName.startsWith("list") || methodName.startsWith("count")) {
if (methodName.startsWith("query") || methodName.startsWith("findAll") || methodName.startsWith("list") || methodName.startsWith("count") || methodName.startsWith("delete")) {
String index = context.getTargetMethod().isAnnotationPresent(Index.class) ? context.getTargetMethod().getAnnotation(Index.class).value() : null;
boolean consistent = context.getTargetMethod().isAnnotationPresent(Consistent.class) && context.getTargetMethod().getAnnotation(Consistent.class).value();
boolean descending = context.getTargetMethod().isAnnotationPresent(Descending.class) && context.getTargetMethod().getAnnotation(Descending.class).value();

QueryArguments partitionAndSort = findHashAndRange(context.getArguments(), service);
boolean customized = index != null || consistent || descending || !partitionAndSort.filters.isEmpty() || partitionAndSort.sortKey != null && partitionAndSort.sortKey.operator != Filter.Operator.EQ;

if (methodName.startsWith("count")) {
if (index != null || consistent || descending || !partitionAndSort.filters.isEmpty() || partitionAndSort.sortKey != null && partitionAndSort.sortKey.operator != Filter.Operator.EQ) {
if (customized) {
return service.countUsingQuery(generateQuery(context, partitionAndSort, index, consistent, descending));
}
return service.count(partitionAndSort.getPartitionValue(context.getParameters()), partitionAndSort.getSortValue(context.getParameters()));
}
if (index != null || consistent || descending || !partitionAndSort.filters.isEmpty() || partitionAndSort.sortKey != null && partitionAndSort.sortKey.operator != Filter.Operator.EQ) {

if (methodName.startsWith("delete")) {
Optional<ItemArgument> maybeItemArgument = findItemArgument(service, context);
if (customized) {
return service.deleteAll(service.query(generateQuery(context, partitionAndSort, index, consistent, descending)));
}
return handleDelete(service, context, maybeItemArgument);
}

if (customized) {
return publisherOrIterable(service.query(generateQuery(context, partitionAndSort, index, consistent, descending)), context.getReturnType().getType());
}
return publisherOrIterable(
Expand All @@ -246,41 +266,35 @@ private Object publisherOrIterable(Publisher<?> result, Class<?> type) {
return Flux.from(result).collectList().blockOptional().orElse(Collections.emptyList());
}

private <T> Object handleSave(DynamoDbService<T> service, MethodInvocationContext<Object, Object> context) {
@SuppressWarnings("unchecked")
private <T> Object handleSave(DynamoDbService<T> service, MethodInvocationContext<Object, Object> context, Optional<ItemArgument> maybeItemArgument) {
Map<String, MutableArgumentValue<?>> params = context.getParameters();
Argument<?>[] args = context.getArguments();

if (args.length != 1) {
throw new UnsupportedOperationException("Method expects 1 parameter - item, iterable of items or array of items");
}

Argument<?> itemArgument = args[0];
Publisher<T> items = toPublisher(service.getItemType(), itemArgument, params);

if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) {
if (maybeItemArgument.isPresent()) {
ItemArgument itemArgument = maybeItemArgument.get();
Publisher<T> items = toPublisher(service.getItemType(), itemArgument.argument, params);
if (itemArgument.single) {
return service.save((T) params.get(itemArgument.argument.getName()).getValue());
}
return publisherOrIterable(service.saveAll(items), context.getReturnType().getType());
} else {
throw new UnsupportedOperationException("Method expects 1 parameter - item, iterable of items or array of items");
}

return service.save((T) params.get(itemArgument.getName()).getValue());
}

private <T> Object handleDelete(DynamoDbService<T> service, MethodInvocationContext<Object, Object> context) {
private <T> Object handleDelete(DynamoDbService<T> service, MethodInvocationContext<Object, Object> context, Optional<ItemArgument> maybeItemArgument) {
Map<String, MutableArgumentValue<?>> params = context.getParameters();
Argument<?>[] args = context.getArguments();

if (args.length == 1) {
Argument<?> itemArgument = args[0];
Publisher<T> items = toPublisher(service.getItemType(), itemArgument, params);

if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) {
return service.deleteAll(items);
}

if (service.getItemType().isAssignableFrom(itemArgument.getType())) {
if (maybeItemArgument.isPresent()) {
ItemArgument itemArgument = maybeItemArgument.get();
Publisher<T> items = toPublisher(service.getItemType(), itemArgument.argument, params);
if (itemArgument.single) {
return service.delete(Flux.from(items).blockFirst());
}
return service.deleteAll(items);
}

Argument<?>[] args = context.getArguments();
if (args.length > 2) {
throw new UnsupportedOperationException("Method expects at most 2 parameters - partition key and sort key, an item or items");
}
Expand Down Expand Up @@ -317,6 +331,30 @@ private <T> Object handleGet(DynamoDbService<T> service, MethodInvocationContext
return service.get(partitionValue, partitionAndSort.getSortValue(params));
}

private <T> Optional<ItemArgument> findItemArgument(DynamoDbService<T> service, MethodInvocationContext<Object, Object> context) {
Map<String, MutableArgumentValue<?>> params = context.getParameters();
Argument<?>[] args = context.getArguments();

if (args.length == 1) {
Argument<?> itemArgument = args[0];
if (itemArgument.getType().isArray() || Iterable.class.isAssignableFrom(itemArgument.getType()) || Publisher.class.isAssignableFrom(itemArgument.getType())) {
ItemArgument item = new ItemArgument();
item.argument = itemArgument;
item.single = false;
return Optional.of(item);
}

if (service.getItemType().isAssignableFrom(itemArgument.getType())) {
ItemArgument item = new ItemArgument();
item.argument = itemArgument;
item.single = true;
return Optional.of(item);
}
}

return Optional.empty();
}

private QueryArguments findHashAndRange(Argument<?>[] arguments, DynamoDbService<?> table) {
QueryArguments names = new QueryArguments();
for (Argument<?> argument : arguments) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ public void testJavaService() {
assertEquals(4, s.findAllByNumberInExplicit("1", List.of(1, 2)).size());
assertEquals(4, s.findAllByNumberBetween("1", 1, 2).size());
assertEquals(4, s.findAllByRangeBeginsWith("1", "f").size());
assertEquals(4, s.deleteAllByRangeBeginsWith("1", "f"));
assertEquals(0, s.findAllByRangeBeginsWith("1", "f").size());
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class DefaultDynamoDBServiceSpec extends Specification {
}
// end::setup[]

@SuppressWarnings('LineLength')
void 'unsupported methods throws meaningful messages'() {
when:
unknownMethodsService.doSomething()
Expand All @@ -87,8 +88,8 @@ class DefaultDynamoDBServiceSpec extends Specification {
when:
unknownMethodsService.delete('1', '1', '1')
then:
UnsupportedOperationException e3 = thrown(UnsupportedOperationException)
e3.message == 'Method expects at most 2 parameters - partition key and sort key, an item or items'
IllegalArgumentException e3 = thrown(IllegalArgumentException)
e3.message == '''Unknown property somethingElse for DynamoDBEntity{parentId='null', id='null', rangeIndex='null', date=null, number=0, mapProperty={}}'''

when:
unknownMethodsService.get('1', '1', '1')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ List<DynamoDBEntity> findAllByRangeBeginsWith(
)
String rangeIndexPrefix
);

@Index(DynamoDBEntity.RANGE_INDEX)
int deleteAllByRangeBeginsWith( // <11>
@PartitionKey String parentId,
@SortKey
@Filter(
value = Filter.Operator.BEGINS_WITH,
name = "rangeIndex"
)
String rangeIndexPrefix
);
// end::advanced-query-methods[]
// CHECKSTYLE:ON

Expand Down

0 comments on commit 4fbac1a

Please sign in to comment.