From a6cec8dfce08d9809233cef3ef22413e5ed939cc Mon Sep 17 00:00:00 2001 From: Michael Wyraz Date: Thu, 21 Sep 2017 15:01:55 +0200 Subject: [PATCH 1/2] Support for existsBy projection Fixes #93 --- .../query/AbstractDynamoDBQuery.java | 27 ++++++++++++++----- .../query/PartTreeDynamoDBQuery.java | 13 ++++++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java index e47b9093..5b4745ef 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java @@ -67,6 +67,7 @@ else if (method.isSliceQuery() && !isSingleEntityResultsRestriction()) { protected abstract Query doCreateQuery(Object[] values); protected abstract Query doCreateCountQuery(Object[] values,boolean pageQuery); protected abstract boolean isCountQuery(); + protected abstract boolean isExistsQuery(); protected abstract Integer getResultsRestrictionIfApplicable(); protected abstract boolean isSingleEntityResultsRestriction(); @@ -250,10 +251,18 @@ public Object execute(AbstractDynamoDBQuery dynamoDBQuery, Object[] value { return dynamoDBQuery.doCreateCountQueryWithPermissions(values,false).getSingleResult(); } - else - { - return dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult(); - } + else + { + Object result = dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult(); + if (isExistsQuery()) + { + return result==null ? Boolean.FALSE : Boolean.TRUE; + } + else + { + return result; + } + } } } @@ -269,8 +278,14 @@ public Object execute(AbstractDynamoDBQuery dynamoDBQuery, Object[] value else { List resultList = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList(); - return resultList.size() == 0 ? null : resultList.get(0); - + if (isExistsQuery()) + { + return resultList.isEmpty() ? Boolean.FALSE : Boolean.TRUE; + } + else + { + return resultList.isEmpty() ? null : resultList.get(0); + } } } diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQuery.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQuery.java index 438f21ad..9ba12403 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQuery.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQuery.java @@ -75,10 +75,15 @@ public Query doCreateCountQuery(Object[] values,boolean pageQuery) { } - @Override - protected boolean isCountQuery() { - return tree.isCountProjection(); - } + @Override + protected boolean isCountQuery() { + return tree.isCountProjection(); + } + + @Override + protected boolean isExistsQuery() { + return tree.isExistsProjection(); + } @Override protected Integer getResultsRestrictionIfApplicable() { From adb3a963f47e70fc9aeb96dbb227a583b2698523 Mon Sep 17 00:00:00 2001 From: Michael Wyraz Date: Mon, 25 Sep 2017 07:45:50 +0200 Subject: [PATCH 2/2] Add unit test. Remove unnecessary branch (limits in expressions are ignored for exists queries). --- .../query/AbstractDynamoDBQuery.java | 15 +- .../query/PartTreeDynamoDBQueryUnitTest.java | 274 ++++++++++++++++++ 2 files changed, 278 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java index 5b4745ef..530d3c25 100644 --- a/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java +++ b/src/main/java/org/socialsignin/spring/data/dynamodb/repository/query/AbstractDynamoDBQuery.java @@ -253,14 +253,13 @@ public Object execute(AbstractDynamoDBQuery dynamoDBQuery, Object[] value } else { - Object result = dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult(); if (isExistsQuery()) { - return result==null ? Boolean.FALSE : Boolean.TRUE; + return !dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList().isEmpty(); } else { - return result; + return dynamoDBQuery.doCreateQueryWithPermissions(values).getSingleResult(); } } @@ -278,14 +277,8 @@ public Object execute(AbstractDynamoDBQuery dynamoDBQuery, Object[] value else { List resultList = dynamoDBQuery.doCreateQueryWithPermissions(values).getResultList(); - if (isExistsQuery()) - { - return resultList.isEmpty() ? Boolean.FALSE : Boolean.TRUE; - } - else - { - return resultList.isEmpty() ? null : resultList.get(0); - } + return resultList.size() == 0 ? null : resultList.get(0); + } } diff --git a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQueryUnitTest.java b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQueryUnitTest.java index 03fca06c..3591b24b 100644 --- a/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQueryUnitTest.java +++ b/src/test/java/org/socialsignin/spring/data/dynamodb/repository/query/PartTreeDynamoDBQueryUnitTest.java @@ -3653,4 +3653,278 @@ public void testExecute_WhenFinderMethodIsFindingEntityList_WithSingleStringPara Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); } + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testExecute_WhenExistsQueryFindsNoEntity() { + setupCommonMocksForThisRepositoryMethod(mockUserEntityMetadata, mockDynamoDBUserQueryMethod, User.class, + "existsByName", 1, "id", null); + Mockito.when(mockUserEntityMetadata.getOverriddenAttributeName("name")).thenReturn("Name"); + + // Mock out specific DynamoDBOperations behavior expected by this method + ArgumentCaptor scanCaptor = ArgumentCaptor.forClass(DynamoDBScanExpression.class); + ArgumentCaptor classCaptor = ArgumentCaptor.forClass(Class.class); + Mockito.when(mockUserScanResults.size()).thenReturn(0); + Mockito.when(mockUserScanResults.isEmpty()).thenReturn(true); + Mockito.when(mockDynamoDBOperations.scan(classCaptor.capture(), scanCaptor.capture())).thenReturn( + mockUserScanResults); + + // Execute the query + Object[] parameters = new Object[] { "someName" }; + Object o = partTreeDynamoDBQuery.execute(parameters); + + // Assert that we obtain the expected single result + assertEquals(false, o); + + // Assert that we scanned DynamoDB for the correct class + assertEquals(User.class, classCaptor.getValue()); + + // Assert that we have only one filter condition, for the name of the + // property + Map filterConditions = scanCaptor.getValue().getScanFilter(); + assertEquals(1, filterConditions.size()); + Condition filterCondition = filterConditions.get("Name"); + assertNotNull(filterCondition); + + assertEquals(ComparisonOperator.EQ.name(), filterCondition.getComparisonOperator()); + + // Assert we only have one attribute value for this filter condition + assertEquals(1, filterCondition.getAttributeValueList().size()); + + // Assert that there the attribute value type for this attribute value + // is String, + // and its value is the parameter expected + assertEquals("someName", filterCondition.getAttributeValueList().get(0).getS()); + + // Assert that all other attribute value types other than String type + // are null + assertNull(filterCondition.getAttributeValueList().get(0).getSS()); + assertNull(filterCondition.getAttributeValueList().get(0).getN()); + assertNull(filterCondition.getAttributeValueList().get(0).getNS()); + assertNull(filterCondition.getAttributeValueList().get(0).getB()); + assertNull(filterCondition.getAttributeValueList().get(0).getBS()); + + // Verify that the expected DynamoDBOperations method was called + Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testExecute_WhenExistsQueryFindsOneEntity() { + setupCommonMocksForThisRepositoryMethod(mockUserEntityMetadata, mockDynamoDBUserQueryMethod, User.class, + "existsByName", 1, "id", null); + Mockito.when(mockUserEntityMetadata.getOverriddenAttributeName("name")).thenReturn("Name"); + + // Mock out specific DynamoDBOperations behavior expected by this method + ArgumentCaptor scanCaptor = ArgumentCaptor.forClass(DynamoDBScanExpression.class); + ArgumentCaptor classCaptor = ArgumentCaptor.forClass(Class.class); + Mockito.when(mockUserScanResults.get(0)).thenReturn(mockUser); + Mockito.when(mockUserScanResults.size()).thenReturn(1); + Mockito.when(mockUserScanResults.isEmpty()).thenReturn(false); + Mockito.when(mockDynamoDBOperations.scan(classCaptor.capture(), scanCaptor.capture())).thenReturn( + mockUserScanResults); + + // Execute the query + Object[] parameters = new Object[] { "someName" }; + Object o = partTreeDynamoDBQuery.execute(parameters); + + // Assert that we obtain the expected single result + assertEquals(true, o); + + // Assert that we scanned DynamoDB for the correct class + assertEquals(classCaptor.getValue(), User.class); + + // Assert that we have only one filter condition, for the name of the + // property + Map filterConditions = scanCaptor.getValue().getScanFilter(); + assertEquals(1, filterConditions.size()); + Condition filterCondition = filterConditions.get("Name"); + assertNotNull(filterCondition); + + assertEquals(ComparisonOperator.EQ.name(), filterCondition.getComparisonOperator()); + + // Assert we only have one attribute value for this filter condition + assertEquals(1, filterCondition.getAttributeValueList().size()); + + // Assert that there the attribute value type for this attribute value + // is String, + // and its value is the parameter expected + assertEquals("someName", filterCondition.getAttributeValueList().get(0).getS()); + + // Assert that all other attribute value types other than String type + // are null + assertNull(filterCondition.getAttributeValueList().get(0).getSS()); + assertNull(filterCondition.getAttributeValueList().get(0).getN()); + assertNull(filterCondition.getAttributeValueList().get(0).getNS()); + assertNull(filterCondition.getAttributeValueList().get(0).getB()); + assertNull(filterCondition.getAttributeValueList().get(0).getBS()); + + // Verify that the expected DynamoDBOperations method was called + Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); + } + + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testExecute_WhenExistsQueryFindsMultipleEntities() { + setupCommonMocksForThisRepositoryMethod(mockUserEntityMetadata, mockDynamoDBUserQueryMethod, User.class, + "existsByName", 1, "id", null); + Mockito.when(mockUserEntityMetadata.getOverriddenAttributeName("name")).thenReturn("Name"); + + // Mock out specific DynamoDBOperations behavior expected by this method + ArgumentCaptor scanCaptor = ArgumentCaptor.forClass(DynamoDBScanExpression.class); + ArgumentCaptor classCaptor = ArgumentCaptor.forClass(Class.class); + Mockito.when(mockUserScanResults.get(0)).thenReturn(mockUser); + Mockito.when(mockUserScanResults.get(1)).thenReturn(mockUser); + Mockito.when(mockUserScanResults.size()).thenReturn(2); + Mockito.when(mockUserScanResults.isEmpty()).thenReturn(false); + Mockito.when(mockDynamoDBOperations.scan(classCaptor.capture(), scanCaptor.capture())).thenReturn( + mockUserScanResults); + + // Execute the query + Object[] parameters = new Object[] { "someName" }; + Object o = partTreeDynamoDBQuery.execute(parameters); + + // Assert that we obtain the expected single result + assertEquals(true, o); + + // Assert that we scanned DynamoDB for the correct class + assertEquals(classCaptor.getValue(), User.class); + + // Assert that we have only one filter condition, for the name of the + // property + Map filterConditions = scanCaptor.getValue().getScanFilter(); + assertEquals(1, filterConditions.size()); + Condition filterCondition = filterConditions.get("Name"); + assertNotNull(filterCondition); + + assertEquals(ComparisonOperator.EQ.name(), filterCondition.getComparisonOperator()); + + // Assert we only have one attribute value for this filter condition + assertEquals(1, filterCondition.getAttributeValueList().size()); + + // Assert that there the attribute value type for this attribute value + // is String, + // and its value is the parameter expected + assertEquals("someName", filterCondition.getAttributeValueList().get(0).getS()); + + // Assert that all other attribute value types other than String type + // are null + assertNull(filterCondition.getAttributeValueList().get(0).getSS()); + assertNull(filterCondition.getAttributeValueList().get(0).getN()); + assertNull(filterCondition.getAttributeValueList().get(0).getNS()); + assertNull(filterCondition.getAttributeValueList().get(0).getB()); + assertNull(filterCondition.getAttributeValueList().get(0).getBS()); + + // Verify that the expected DynamoDBOperations method was called + Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testExecute_WhenExistsWithLimitQueryFindsNoEntity() { + setupCommonMocksForThisRepositoryMethod(mockUserEntityMetadata, mockDynamoDBUserQueryMethod, User.class, + "existsTop1ByName", 1, "id", null); + Mockito.when(mockUserEntityMetadata.getOverriddenAttributeName("name")).thenReturn("Name"); + + // Mock out specific DynamoDBOperations behavior expected by this method + ArgumentCaptor scanCaptor = ArgumentCaptor.forClass(DynamoDBScanExpression.class); + ArgumentCaptor classCaptor = ArgumentCaptor.forClass(Class.class); + Mockito.when(mockUserScanResults.size()).thenReturn(0); + Mockito.when(mockUserScanResults.isEmpty()).thenReturn(true); + Mockito.when(mockDynamoDBOperations.scan(classCaptor.capture(), scanCaptor.capture())).thenReturn( + mockUserScanResults); + + // Execute the query + Object[] parameters = new Object[] { "someName" }; + Object o = partTreeDynamoDBQuery.execute(parameters); + + // Assert that we obtain the expected single result + assertEquals(false, o); + + // Assert that we scanned DynamoDB for the correct class + assertEquals(User.class, classCaptor.getValue()); + + // Assert that we have only one filter condition, for the name of the + // property + Map filterConditions = scanCaptor.getValue().getScanFilter(); + assertEquals(1, filterConditions.size()); + Condition filterCondition = filterConditions.get("Name"); + assertNotNull(filterCondition); + + assertEquals(ComparisonOperator.EQ.name(), filterCondition.getComparisonOperator()); + + // Assert we only have one attribute value for this filter condition + assertEquals(1, filterCondition.getAttributeValueList().size()); + + // Assert that there the attribute value type for this attribute value + // is String, + // and its value is the parameter expected + assertEquals("someName", filterCondition.getAttributeValueList().get(0).getS()); + + // Assert that all other attribute value types other than String type + // are null + assertNull(filterCondition.getAttributeValueList().get(0).getSS()); + assertNull(filterCondition.getAttributeValueList().get(0).getN()); + assertNull(filterCondition.getAttributeValueList().get(0).getNS()); + assertNull(filterCondition.getAttributeValueList().get(0).getB()); + assertNull(filterCondition.getAttributeValueList().get(0).getBS()); + + // Verify that the expected DynamoDBOperations method was called + Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Test + public void testExecute_WhenExistsWithLimitQueryFindsOneEntity() { + setupCommonMocksForThisRepositoryMethod(mockUserEntityMetadata, mockDynamoDBUserQueryMethod, User.class, + "existsTop1ByName", 1, "id", null); + Mockito.when(mockUserEntityMetadata.getOverriddenAttributeName("name")).thenReturn("Name"); + + // Mock out specific DynamoDBOperations behavior expected by this method + ArgumentCaptor scanCaptor = ArgumentCaptor.forClass(DynamoDBScanExpression.class); + ArgumentCaptor classCaptor = ArgumentCaptor.forClass(Class.class); + Mockito.when(mockUserScanResults.get(0)).thenReturn(mockUser); + Mockito.when(mockUserScanResults.size()).thenReturn(1); + Mockito.when(mockUserScanResults.isEmpty()).thenReturn(false); + Mockito.when(mockDynamoDBOperations.scan(classCaptor.capture(), scanCaptor.capture())).thenReturn( + mockUserScanResults); + + // Execute the query + Object[] parameters = new Object[] { "someName" }; + Object o = partTreeDynamoDBQuery.execute(parameters); + + // Assert that we obtain the expected single result + assertEquals(true, o); + + // Assert that we scanned DynamoDB for the correct class + assertEquals(classCaptor.getValue(), User.class); + + // Assert that we have only one filter condition, for the name of the + // property + Map filterConditions = scanCaptor.getValue().getScanFilter(); + assertEquals(1, filterConditions.size()); + Condition filterCondition = filterConditions.get("Name"); + assertNotNull(filterCondition); + + assertEquals(ComparisonOperator.EQ.name(), filterCondition.getComparisonOperator()); + + // Assert we only have one attribute value for this filter condition + assertEquals(1, filterCondition.getAttributeValueList().size()); + + // Assert that there the attribute value type for this attribute value + // is String, + // and its value is the parameter expected + assertEquals("someName", filterCondition.getAttributeValueList().get(0).getS()); + + // Assert that all other attribute value types other than String type + // are null + assertNull(filterCondition.getAttributeValueList().get(0).getSS()); + assertNull(filterCondition.getAttributeValueList().get(0).getN()); + assertNull(filterCondition.getAttributeValueList().get(0).getNS()); + assertNull(filterCondition.getAttributeValueList().get(0).getB()); + assertNull(filterCondition.getAttributeValueList().get(0).getBS()); + + // Verify that the expected DynamoDBOperations method was called + Mockito.verify(mockDynamoDBOperations).scan(classCaptor.getValue(), scanCaptor.getValue()); + } }