From 011872bcd7dcbcdb7ee8da4544b851265d314529 Mon Sep 17 00:00:00 2001 From: musketyr Date: Mon, 17 Jun 2024 14:57:52 +0200 Subject: [PATCH 1/2] Reintroduced delete by index --- .../AsyncDynamoDbServiceIntroduction.java | 35 ++++++++++----- .../dynamodb/DefaultAsyncDynamoDbService.java | 16 +++---- .../dynamodb/DefaultDynamoDbService.java | 8 ++-- .../SyncDynamoDbServiceIntroduction.java | 30 +++++++++---- .../awssdk/dynamodb/util/ItemArgument.java | 45 +++++++++++++++++++ .../awssdk/dynamodb/util/QueryArguments.java | 3 +- ...AdvancedQueryOnDeclarativeServiceTest.java | 2 + .../DefaultDynamoDBServiceSpec.groovy | 7 +-- .../dynamodb/DynamoDBEntityService.java | 11 +++++ 9 files changed, 120 insertions(+), 37 deletions(-) create mode 100644 subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AsyncDynamoDbServiceIntroduction.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AsyncDynamoDbServiceIntroduction.java index 025a9bbb4..32830db4f 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AsyncDynamoDbServiceIntroduction.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AsyncDynamoDbServiceIntroduction.java @@ -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; @@ -43,6 +44,7 @@ import java.util.Collections; import java.util.Map; +import java.util.Optional; /** * Introduction for {@link Service} annotation. @@ -97,8 +99,7 @@ public Object doIntercept(MethodInvocationContext 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()); } } @@ -160,10 +161,13 @@ private Object doIntercept(MethodInvocationContext context, } if (methodName.startsWith("delete")) { - return unwrapIfRequired(handleDelete(service, context), context.getReturnType().getType()); + Optional 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()) { @@ -171,6 +175,13 @@ private Object doIntercept(MethodInvocationContext context, } 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 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()); } @@ -236,23 +247,23 @@ private Publisher handleSave(AsyncDynamoDbService service, MethodInvoc return service.save((T) params.get(itemArgument.getName()).getValue()); } - private Publisher handleDelete(AsyncDynamoDbService service, MethodInvocationContext context) { + private Publisher handleDelete(AsyncDynamoDbService service, MethodInvocationContext context, Optional maybeItemArgument) { Map> params = context.getParameters(); - Argument[] args = context.getArguments(); - if (args.length == 1) { - Argument itemArgument = args[0]; - Publisher items = toPublisher(service.getItemType(), itemArgument, params); + if (maybeItemArgument.isPresent()) { + ItemArgument itemArgument = maybeItemArgument.get(); + Publisher 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"); } diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultAsyncDynamoDbService.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultAsyncDynamoDbService.java index 1e6571409..e6c877cb4 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultAsyncDynamoDbService.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultAsyncDynamoDbService.java @@ -173,20 +173,20 @@ public Publisher delete(Object partitionKey, @Nullable Object sortKey) { @Override public Publisher 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 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 diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDbService.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDbService.java index e960dce08..d87af0314 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDbService.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDbService.java @@ -169,8 +169,8 @@ 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; } @@ -178,8 +178,8 @@ public T delete(T item) { 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; } diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/SyncDynamoDbServiceIntroduction.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/SyncDynamoDbServiceIntroduction.java index b1e8bf603..58de09078 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/SyncDynamoDbServiceIntroduction.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/SyncDynamoDbServiceIntroduction.java @@ -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; @@ -38,6 +39,7 @@ import java.util.Collections; import java.util.Map; +import java.util.Optional; /** * Introduction for {@link Service} annotation. @@ -133,10 +135,13 @@ private Object doIntercept(MethodInvocationContext context, } if (methodName.startsWith("delete")) { - return handleDelete(service, context); + Optional 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()) { @@ -144,6 +149,13 @@ private Object doIntercept(MethodInvocationContext 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 maybeItemArgument = ItemArgument.findItemArgument(service.getItemType(), context); + return handleDelete(service, context, maybeItemArgument); + } if (partitionAndSort.isCustomized()) { return publisherOrIterable(service.query(partitionAndSort.generateQuery(context)), context.getReturnType().getType()); } @@ -182,23 +194,23 @@ private Object handleSave(DynamoDbService service, MethodInvocationContex return service.save((T) params.get(itemArgument.getName()).getValue()); } - private Object handleDelete(DynamoDbService service, MethodInvocationContext context) { + private Object handleDelete(DynamoDbService service, MethodInvocationContext context, Optional maybeItemArgument) { Map> params = context.getParameters(); - Argument[] args = context.getArguments(); - if (args.length == 1) { - Argument itemArgument = args[0]; - Publisher items = QueryArguments.toPublisher(conversionService, service.getItemType(), itemArgument, params); + if (maybeItemArgument.isPresent()) { + ItemArgument itemArgument = maybeItemArgument.get(); + Publisher 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"); } diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java new file mode 100644 index 000000000..1ffac0689 --- /dev/null +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java @@ -0,0 +1,45 @@ +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 Optional findItemArgument(Class itemType, MethodInvocationContext 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; + } +} diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/QueryArguments.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/QueryArguments.java index 8c7c7b763..39d26f639 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/QueryArguments.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/QueryArguments.java @@ -66,8 +66,9 @@ public static QueryArguments create(MethodInvocationContext 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) diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AdvancedQueryOnDeclarativeServiceTest.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AdvancedQueryOnDeclarativeServiceTest.java index e61cb283a..d19919f6c 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AdvancedQueryOnDeclarativeServiceTest.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/AdvancedQueryOnDeclarativeServiceTest.java @@ -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()); } diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy index c72df4e55..b27ea7954 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DefaultDynamoDBServiceSpec.groovy @@ -71,6 +71,7 @@ class DefaultDynamoDBServiceSpec extends Specification { } // end::setup[] + @SuppressWarnings('LineLength') void 'unsupported methods throws meaningful messages'() { when: unknownMethodsService.doSomething() @@ -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') @@ -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 diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DynamoDBEntityService.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DynamoDBEntityService.java index e45367ec8..6ad0bb83d 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DynamoDBEntityService.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/test/groovy/com/agorapulse/micronaut/amazon/awssdk/dynamodb/DynamoDBEntityService.java @@ -215,6 +215,17 @@ List 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 From 32e038cc2fedb5231660c074411d5da4928d884b Mon Sep 17 00:00:00 2001 From: musketyr Date: Mon, 17 Jun 2024 15:08:46 +0200 Subject: [PATCH 2/2] license header --- .../awssdk/dynamodb/util/ItemArgument.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java index 1ffac0689..4b571287b 100644 --- a/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java +++ b/subprojects/micronaut-amazon-awssdk-dynamodb/src/main/java/com/agorapulse/micronaut/amazon/awssdk/dynamodb/util/ItemArgument.java @@ -1,3 +1,20 @@ +/* + * 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;