Skip to content

Commit

Permalink
Reintroduced delete by index (#242)
Browse files Browse the repository at this point in the history
* Reintroduced delete by index

* license header
  • Loading branch information
musketyr authored Jun 17, 2024
1 parent 07e9fb0 commit 84e18c9
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.DetachedScan;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.DetachedUpdate;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.UpdateBuilder;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.util.ItemArgument;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.util.QueryArguments;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.context.annotation.Replaces;
Expand All @@ -43,6 +44,7 @@

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

/**
* Introduction for {@link Service} annotation.
Expand Down Expand Up @@ -97,8 +99,7 @@ public <T> Object doIntercept(MethodInvocationContext<Object, Object> context, C
try {
return doIntercept(context, service);
} catch (ResourceNotFoundException ignored) {
service.createTable();
return doIntercept(context, service);
return unwrapIfRequired(Flux.from(service.createTable()).map(t -> doIntercept(context, service)), context.getReturnType().getType());
}
}

Expand Down Expand Up @@ -160,17 +161,27 @@ private <T> Object doIntercept(MethodInvocationContext<Object, Object> context,
}

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

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")) {
QueryArguments partitionAndSort = QueryArguments.create(context, service.getTable().tableSchema().tableMetadata());
if (methodName.startsWith("count")) {
if (partitionAndSort.isCustomized()) {
return unwrapIfRequired(service.countUsingQuery(partitionAndSort.generateQuery(context)), context.getReturnType().getType());
}
return unwrapIfRequired(service.count(partitionAndSort.getPartitionValue(context.getParameters()), partitionAndSort.getSortValue(context.getParameters())), context.getReturnType().getType());
}
if (methodName.startsWith("delete")) {
if (partitionAndSort.isCustomized()) {
return unwrapIfRequired(service.deleteAll(service.query(partitionAndSort.generateQuery(context))), context.getReturnType().getType());
}
Optional<ItemArgument> maybeItemArgument = ItemArgument.findItemArgument(service.getItemType(), context);
return unwrapIfRequired(handleDelete(service, context, maybeItemArgument), context.getReturnType().getType());
}
if (partitionAndSort.isCustomized()) {
return unwrapIfRequired(service.query(partitionAndSort.generateQuery(context)), context.getReturnType().getType());
}
Expand Down Expand Up @@ -236,23 +247,23 @@ private <T> Publisher<T> handleSave(AsyncDynamoDbService<T> service, MethodInvoc
return service.save((T) params.get(itemArgument.getName()).getValue());
}

private <T> Publisher<?> handleDelete(AsyncDynamoDbService<T> service, MethodInvocationContext<Object, Object> context) {
private <T> Publisher<?> handleDelete(AsyncDynamoDbService<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 (maybeItemArgument.isPresent()) {
ItemArgument itemArgument = maybeItemArgument.get();
Publisher<T> items = QueryArguments.toPublisher(conversionService, service.getItemType(), itemArgument.getArgument(), params);

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

if (service.getItemType().isAssignableFrom(itemArgument.getType())) {
return service.delete(Flux.from(items).blockFirst());
if (service.getItemType().isAssignableFrom(itemArgument.getArgument().getType())) {
return Mono.from(items).flatMap(item -> Mono.from(service.delete(item)));
}
}

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
Original file line number Diff line number Diff line change
Expand Up @@ -173,20 +173,20 @@ public Publisher<T> delete(Object partitionKey, @Nullable Object sortKey) {
@Override
public Publisher<T> delete(T item) {
publisher.publishEvent(DynamoDbEvent.preRemove(item));
return Mono.fromFuture(table.deleteItem(table.keyFrom(item))).then(Mono.fromCallable(() -> {
publisher.publishEvent(DynamoDbEvent.postRemove(item));
return item;
}));
return Mono.fromFuture(table.deleteItem(table.keyFrom(item))).map(deletedItem -> {
publisher.publishEvent(DynamoDbEvent.postRemove(deletedItem));
return deletedItem;
});
}

@Override
public Publisher<T> delete(Key key) {
T item = table.tableSchema().mapToItem(key.primaryKeyMap(table.tableSchema()));
publisher.publishEvent(DynamoDbEvent.preRemove(item));
return Mono.fromFuture(table.deleteItem(key)).then(Mono.fromCallable(() -> {
publisher.publishEvent(DynamoDbEvent.postRemove(item));
return item;
}));
return Mono.fromFuture(table.deleteItem(key)).map(deletedItem -> {
publisher.publishEvent(DynamoDbEvent.postRemove(deletedItem));
return deletedItem;
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,17 +169,17 @@ public T delete(Object partitionKey, @Nullable Object sortKey) {
@Override
public T delete(T item) {
publisher.publishEvent(DynamoDbEvent.preRemove(item));
table.deleteItem(table.keyFrom(item));
publisher.publishEvent(DynamoDbEvent.postRemove(item));
T deleted = table.deleteItem(table.keyFrom(item));
publisher.publishEvent(DynamoDbEvent.postRemove(deleted));
return item;
}

@Override
public T delete(Key key) {
T item = table.tableSchema().mapToItem(key.primaryKeyMap(table.tableSchema()));
publisher.publishEvent(DynamoDbEvent.preRemove(item));
table.deleteItem(key);
publisher.publishEvent(DynamoDbEvent.postRemove(item));
T deleted = table.deleteItem(key);
publisher.publishEvent(DynamoDbEvent.postRemove(deleted));
return item;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.DetachedScan;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.DetachedUpdate;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.builder.UpdateBuilder;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.util.ItemArgument;
import com.agorapulse.micronaut.amazon.awssdk.dynamodb.util.QueryArguments;
import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.async.publisher.Publishers;
Expand All @@ -38,6 +39,7 @@

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

/**
* Introduction for {@link Service} annotation.
Expand Down Expand Up @@ -133,17 +135,27 @@ private <T> Object doIntercept(MethodInvocationContext<Object, Object> context,
}

if (methodName.startsWith("delete")) {
return handleDelete(service, context);
Optional<ItemArgument> maybeItemArgument = ItemArgument.findItemArgument(service.getItemType(), 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")) {
QueryArguments partitionAndSort = QueryArguments.create(context, service.getTable().tableSchema().tableMetadata());
if (methodName.startsWith("count")) {
if (partitionAndSort.isCustomized()) {
return service.countUsingQuery(partitionAndSort.generateQuery(context));
}
return service.count(partitionAndSort.getPartitionValue(context.getParameters()), partitionAndSort.getSortValue(context.getParameters()));
}
if (methodName.startsWith("delete")) {
if (partitionAndSort.isCustomized()) {
return service.deleteAll(service.query(partitionAndSort.generateQuery(context)));
}
Optional<ItemArgument> maybeItemArgument = ItemArgument.findItemArgument(service.getItemType(), context);
return handleDelete(service, context, maybeItemArgument);
}
if (partitionAndSort.isCustomized()) {
return publisherOrIterable(service.query(partitionAndSort.generateQuery(context)), context.getReturnType().getType());
}
Expand Down Expand Up @@ -182,23 +194,23 @@ private <T> Object handleSave(DynamoDbService<T> service, MethodInvocationContex
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 = QueryArguments.toPublisher(conversionService, service.getItemType(), itemArgument, params);
if (maybeItemArgument.isPresent()) {
ItemArgument itemArgument = maybeItemArgument.get();
Publisher<T> items = QueryArguments.toPublisher(conversionService, service.getItemType(), itemArgument.getArgument(), params);

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

if (service.getItemType().isAssignableFrom(itemArgument.getType())) {
if (service.getItemType().isAssignableFrom(itemArgument.getArgument().getType())) {
return service.delete(Flux.from(items).blockFirst());
}
}

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2018-2024 Agorapulse.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.agorapulse.micronaut.amazon.awssdk.dynamodb.util;

import io.micronaut.aop.MethodInvocationContext;
import io.micronaut.core.type.Argument;
import org.reactivestreams.Publisher;

import java.util.Optional;

public class ItemArgument {

public static <T> Optional<ItemArgument> findItemArgument(Class<T> itemType, MethodInvocationContext<Object, Object> context) {
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 (itemType.isAssignableFrom(itemArgument.getType())) {
ItemArgument item = new ItemArgument();
item.argument = itemArgument;
item.single = true;
return Optional.of(item);
}
}

return Optional.empty();
}


Argument<?> argument;
boolean single;

public Argument<?> getArgument() {
return argument;
}

public boolean isSingle() {
return single;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ public static QueryArguments create(MethodInvocationContext<Object, Object> cont
|| argument.getName().equals(tableMetadata.primarySortKey().orElse(SORT))
) {
if (queryArguments.sortKey == null) {
queryArguments.sortKey = new FilterArgument().fill(argument);
queryArguments.sortKey = new FilterArgument();
}
queryArguments.sortKey.fill(argument);
} else if (
argument.isAnnotationPresent(PartitionKey.class)
|| argument.isAnnotationPresent(HashKey.class)
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 Expand Up @@ -397,7 +398,7 @@ class DefaultDynamoDBServiceSpec extends Specification {
then:
playbook.verifyAndForget(
'PRE_REMOVE:1003:1:null:0',
'POST_REMOVE:1003:1:null:0'
'POST_REMOVE:1003:1:foo:11'
)
service.count('1003', '1') == 0

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

Please sign in to comment.