Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# These are supported funding model platforms

github: #boostchicken
github: boostchicken
custom: ["paypal.me/boostchicken"]
5 changes: 5 additions & 0 deletions CHANELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# 5.2.5

## New Features
1. [Allow single object as parameter to query a set/list](https://github.com/boostchicken/spring-data-dynamodb/issues/33)

# 5.2.4

## Housekeeping
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Download the JAR though [Maven Central](http://mvnrepository.com/artifact/io.git
<dependency>
<groupId>io.github.boostchicken</groupId>
<artifactId>spring-data-dynamodb</artifactId>
<version>5.2.4</version>
<version>5.2.5</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.boostchicken</groupId>
<artifactId>spring-data-dynamodb</artifactId>
<version>5.2.5-SNAPSHOT</version>
<version>5.2.6-SNAPSHOT</version>
<name>Spring Data DynamoDB</name>
<inceptionYear>2018</inceptionYear>

Expand Down
5 changes: 5 additions & 0 deletions src/changes/changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
<author>boostchicken</author>
</properties>
<body>
<release version="5.2.5" description="Feature Release">
<action dev="hannes-angst" type="add" issue="33" date="2020-06-16">
Allow single object as parameter to query a set/list
</action>
</release>
<release version="5.2.4" description="Feature Release">
<action dev="boostchicken" type="add" issue="29" date="2020-05-24">
Added Filter Expression Support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;

Expand Down Expand Up @@ -139,18 +140,12 @@ protected DynamoDBQueryCriteria<T, ID> addCriteria(DynamoDBQueryCriteria<T, ID>

switch (part.getType()) {
case IN :
Object in = iterator.next();
Assert.notNull(in, "Creating conditions on null parameters not supported: please specify a value for '"
+ leafNodePropertyName + "'");
boolean isIterable = ClassUtils.isAssignable(Iterable.class, in.getClass());
boolean isArray = ObjectUtils.isArray(in);
Assert.isTrue(isIterable || isArray, "In criteria can only operate with Iterable or Array parameters");
Iterable<?> iterable = isIterable ? ((Iterable<?>) in) : Arrays.asList(ObjectUtils.toObjectArray(in));
return criteria.withPropertyIn(leafNodePropertyName, iterable, leafNodePropertyType);
case CONTAINING :
return criteria.withSingleValueCriteria(leafNodePropertyName, ComparisonOperator.CONTAINS,
iterator.next(), leafNodePropertyType);
case STARTING_WITH :
return getInProperty(criteria, iterator, leafNodePropertyType, leafNodePropertyName);
case CONTAINING :
return getItemsProperty(criteria, ComparisonOperator.CONTAINS, iterator, leafNodePropertyType, leafNodePropertyName);
case NOT_CONTAINING:
return getItemsProperty(criteria, ComparisonOperator.NOT_CONTAINS, iterator, leafNodePropertyType, leafNodePropertyName);
case STARTING_WITH :
return criteria.withSingleValueCriteria(leafNodePropertyName, ComparisonOperator.BEGINS_WITH,
iterator.next(), leafNodePropertyType);
case BETWEEN :
Expand Down Expand Up @@ -192,7 +187,38 @@ protected DynamoDBQueryCriteria<T, ID> addCriteria(DynamoDBQueryCriteria<T, ID>

}

@Override
private DynamoDBQueryCriteria<T, ID> getItemsProperty(DynamoDBQueryCriteria<T, ID> criteria, ComparisonOperator comparisonOperator, Iterator<Object> iterator, Class<?> leafNodePropertyType, String leafNodePropertyName) {
Object in = iterator.next();
Assert.notNull(in, "Creating conditions on null parameters not supported: please specify a value for '" + leafNodePropertyName + "'");

if(ObjectUtils.isArray(in)) {
List<?> list = Arrays.asList(ObjectUtils.toObjectArray(in));
Assert.isTrue(list.size()==1, "Only one value is supported: please specify a value for '\" + leafNodePropertyName + \"'\"");
Object value = list.get(0);
return criteria.withSingleValueCriteria(leafNodePropertyName, comparisonOperator, value, leafNodePropertyType);
} else if(ClassUtils.isAssignable(Iterable.class, in.getClass())) {
Iterator<?> iter = ((Iterable<?>) in).iterator();
Assert.isTrue(iter.hasNext(), "Creating conditions on empty parameters not supported: please specify a value for '\" + leafNodePropertyName + \"'\"");
Object value = iter.next();
Assert.isTrue(!iter.hasNext(), "Only one value is supported: please specify a value for '\" + leafNodePropertyName + \"'\"");
return criteria.withSingleValueCriteria(leafNodePropertyName, comparisonOperator, value, leafNodePropertyType);
} else {
return criteria.withSingleValueCriteria(leafNodePropertyName, comparisonOperator, in, leafNodePropertyType);
}
}

private DynamoDBQueryCriteria<T, ID> getInProperty(DynamoDBQueryCriteria<T, ID> criteria, Iterator<Object> iterator, Class<?> leafNodePropertyType, String leafNodePropertyName) {
Object in = iterator.next();
Assert.notNull(in, "Creating conditions on null parameters not supported: please specify a value for '"
+ leafNodePropertyName + "'");
boolean isIterable = ClassUtils.isAssignable(Iterable.class, in.getClass());
boolean isArray = ObjectUtils.isArray(in);
Assert.isTrue(isIterable || isArray, "In criteria can only operate with Iterable or Array parameters");
Iterable<?> iterable = isIterable ? ((Iterable<?>) in) : Arrays.asList(ObjectUtils.toObjectArray(in));
return criteria.withPropertyIn(leafNodePropertyName, iterable, leafNodePropertyType);
}

@Override
protected DynamoDBQueryCriteria<T, ID> and(Part part, DynamoDBQueryCriteria<T, ID> base,
Iterator<Object> iterator) {
return addCriteria(base, part, iterator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ public DynamoDBQueryCriteria<T, ID> withPropertyIn(String propertyName, Iterable
return withCondition(propertyName, condition);
}

@Override
@Override
public DynamoDBQueryCriteria<T, ID> withSingleValueCriteria(String propertyName,
ComparisonOperator comparisonOperator, Object value, Class<?> propertyType) {
if (comparisonOperator.equals(ComparisonOperator.EQ)) {
Expand Down Expand Up @@ -554,8 +554,15 @@ protected <V extends Object> Object getPropertyAttributeValue(final String prope

DynamoDBMapperFieldModel<T, Object> fieldModel = tableModel.field(attributeName);
if (fieldModel != null) {
return fieldModel.convert(value);
}
if (fieldModel.attributeType() == DynamoDBMapperFieldModel.DynamoDBAttributeType.SS) {
if(value instanceof Collection) {
return fieldModel.convert(value);
} else {
return new AttributeValue(value.toString());
}
}
return fieldModel.convert(value);
}
}

return value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import org.junit.Before;
import org.junit.Rule;

import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
Expand All @@ -36,8 +35,10 @@
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -263,14 +264,163 @@ public void testFilterAndPagination() {

@Test
public void testDeleteNonExistentIdWithCondition() {
// Delete conditional
userRepository.deleteByIdAndName("non-existent", "non-existent");
}
// Delete conditional
userRepository.deleteByIdAndName("non-existent", "non-existent");
}

@Test
public void testDeleteNonExistingGsiWithCondition() {
// Delete via GSI
userRepository.deleteByPostCodeAndNumberOfPlaylists("non-existing", 23);

}


@Test
public void testFilterWithCollections() {
// Prepare
User u1 = new User();
String name1 = "name1" + ThreadLocalRandom.current().nextLong();
u1.setId("u1");
u1.setName(name1);
u1.setPostCode("1234");
Set<String> u1Tags = new HashSet<>();
u1Tags.add("tag-a");
u1Tags.add("tag-b");
u1Tags.add("tag-c");
u1.setTags(u1Tags);

User u2 = new User();
String name2 = "name1" + ThreadLocalRandom.current().nextLong();
u2.setId("u2");
u2.setName(name2);
u2.setPostCode("1234");
Set<String> u2Tags = new HashSet<>();
u2Tags.add("tag-a");
u2Tags.add("tag-b");
u2.setTags(u2Tags);

User u3 = new User();
String name3 = "name1" + ThreadLocalRandom.current().nextLong();
u3.setId("u3");
u3.setName(name3);
u3.setPostCode("1234");
Set<String> u3Tags = new HashSet<>();
u3Tags.add("tag-a");
u3Tags.add("tag-c");
u3.setTags(u3Tags);


u1 = userRepository.save(u1);
u2 = userRepository.save(u2);
u3 = userRepository.save(u3);

Set<User> tagA = setOf(u1, u2, u3);
Set<User> tagB = setOf(u1,u2);
Set<User> tagC = setOf(u1, u3);

Set<User> notTagA = setOf();
Set<User> notTagB = setOf(u3);
Set<User> notTagC = setOf(u2);


// Single value
assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContaining("tag-a")));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContaining("tag-b")));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContaining("tag-c")));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContains("tag-a")));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContains("tag-b")));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContains("tag-c")));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsIsContaining("tag-a")));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsIsContaining("tag-b")));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsIsContaining("tag-c")));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContaining("tag-a")));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContaining("tag-b")));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContaining("tag-c")));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContains("tag-a")));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContains("tag-b")));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContains("tag-c")));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsIsNotContaining("tag-a")));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsIsNotContaining("tag-b")));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsIsNotContaining("tag-c")));


//Sets
assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContaining(setOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContaining(setOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContaining(setOf("tag-c"))));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContains(setOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContains(setOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContains(setOf("tag-c"))));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsIsContaining(setOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsIsContaining(setOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsIsContaining(setOf("tag-c"))));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContaining(setOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContaining(setOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContaining(setOf("tag-c"))));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContains(setOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContains(setOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContains(setOf("tag-c"))));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsIsNotContaining(setOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsIsNotContaining(setOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsIsNotContaining(setOf("tag-c"))));

//Lists
assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContaining(listOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContaining(listOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContaining(listOf("tag-c"))));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsContains(listOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsContains(listOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsContains(listOf("tag-c"))));

assertEquals(tagA, new HashSet<>(userRepository.findAllByTagsIsContaining(listOf("tag-a"))));
assertEquals(tagB, new HashSet<>(userRepository.findAllByTagsIsContaining(listOf("tag-b"))));
assertEquals(tagC, new HashSet<>(userRepository.findAllByTagsIsContaining(listOf("tag-c"))));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContaining(listOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContaining(listOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContaining(listOf("tag-c"))));

assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsNotContains(listOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsNotContains(listOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsNotContains(listOf("tag-c"))));

@Test
public void testDeleteNonExistingGsiWithConditoin() {
// Delete via GSI
userRepository.deleteByPostCodeAndNumberOfPlaylists("non-existing", 23);
assertEquals(notTagA, new HashSet<>(userRepository.findAllByTagsIsNotContaining(listOf("tag-a"))));
assertEquals(notTagB, new HashSet<>(userRepository.findAllByTagsIsNotContaining(listOf("tag-b"))));
assertEquals(notTagC, new HashSet<>(userRepository.findAllByTagsIsNotContaining(listOf("tag-c"))));

}

}


@SafeVarargs
private final <E> Set<E> setOf(E... values) {
Set<E> result = new HashSet<>();

if (values != null) {
result.addAll(Arrays.asList(values));
}
return result;
}

@SafeVarargs
private final <E> List<E> listOf(E... values) {
List<E> result = new ArrayList<>();

if (values != null) {
result.addAll(Arrays.asList(values));
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class User {

private Date joinDate;

private Set<String> tags;

@SuppressWarnings("deprecation")
@DynamoDBMarshalling(marshallerClass = DynamoDBYearMarshaller.class)
private Date joinYear;
Expand Down Expand Up @@ -72,7 +74,15 @@ public void setJoinYear(Date joinYear) {
this.joinYear = joinYear;
}

@SuppressWarnings("deprecation")
public Set<String> getTags() {
return tags;
}

public void setTags(Set<String> tags) {
this.tags = tags;
}

@SuppressWarnings("deprecation")
@DynamoDBMarshalling(marshallerClass = Instant2IsoDynamoDBMarshaller.class)
public Instant getLeaveDate() {
return leaveDate;
Expand Down Expand Up @@ -184,4 +194,8 @@ public boolean equals(Object obj) {
return true;
}

@Override
public String toString() {
return "<User "+ id +">";
}
}
Loading