Skip to content

Fixed DynamoDbEnhancedClient TableSchema::itemToMap to handle null fl… #6137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"type": "bugfix",
"category": "Amazon DynamoDB Enhanced Client",
"contributor": "",
"description": "Fixed DynamoDbEnhancedClient TableSchema::itemToMap to return a map that contains a consistent representation of null top-level (non-flattened) attributes and flattened attributes when their enclosing member is null and ignoreNulls is set to false."
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private B mapToItem(B thisBuilder,
private Map<String, AttributeValue> itemToMap(T item, boolean ignoreNulls) {
T1 otherItem = this.otherItemGetter.apply(item);

if (otherItem == null) {
if (otherItem == null && ignoreNulls) {
return Collections.emptyMap();
}

Expand Down Expand Up @@ -515,15 +515,21 @@ public Map<String, AttributeValue> itemToMap(T item, boolean ignoreNulls) {

attributeMappers.forEach(attributeMapper -> {
String attributeKey = attributeMapper.attributeName();
AttributeValue attributeValue = attributeMapper.attributeGetterMethod().apply(item);
AttributeValue attributeValue = item == null ?
AttributeValue.fromNul(true) :
attributeMapper.attributeGetterMethod().apply(item);

if (!ignoreNulls || !isNullAttributeValue(attributeValue)) {
attributeValueMap.put(attributeKey, attributeValue);
}
});

indexedFlattenedMappers.forEach((name, flattenedMapper) -> {
attributeValueMap.putAll(flattenedMapper.itemToMap(item, ignoreNulls));
if (item != null) {
attributeValueMap.putAll(flattenedMapper.itemToMap(item, ignoreNulls));
} else if (!ignoreNulls) {
attributeValueMap.put(name, AttributeValue.fromNul(true));
}
});

return unmodifiableMap(attributeValueMap);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import software.amazon.awssdk.enhanced.dynamodb.internal.AttributeValues;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AbstractBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AbstractImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AbstractNestedImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AttributeConverterBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.AttributeConverterNoConstructorBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.CommonTypesBean;
Expand All @@ -54,7 +55,10 @@
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.EnumBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.ExtendedBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedBeanBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedFirstNestedBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedFirstNestedBean.FlattenedSecondNestedBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedImmutableBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FlattenedNestedImmutableBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.FluentSetterBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.IgnoredAttributeBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testbeans.InvalidBean;
Expand Down Expand Up @@ -257,6 +261,128 @@ public void dynamoDbFlatten_correctlyFlattensImmutableAttributes() {
assertThat(itemMap, hasEntry("attribute2", stringValue("two")));
}

@Test
public void dynamoDbFlatten_correctlyFlattensNullImmutableAttributes() {
BeanTableSchema<FlattenedImmutableBean> beanTableSchema = BeanTableSchema.create(FlattenedImmutableBean.class);
AbstractImmutable abstractImmutable = AbstractImmutable.builder().build();
FlattenedImmutableBean flattenedImmutableBean = new FlattenedImmutableBean();
flattenedImmutableBean.setId("id-value");
flattenedImmutableBean.setAbstractImmutable(abstractImmutable);

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedImmutableBean, false);
assertThat(itemMap.size(), is(3));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("attribute1", AttributeValue.fromNul(true)));
assertThat(itemMap, hasEntry("attribute2", AttributeValue.fromNul(true)));
}

@Test
public void dynamoDbFlatten_correctlyFlattensNestedImmutableAttributes() {
BeanTableSchema<FlattenedNestedImmutableBean> beanTableSchema =
BeanTableSchema.create(FlattenedNestedImmutableBean.class);
AbstractNestedImmutable abstractNestedImmutable2 =
AbstractNestedImmutable.builder().attribute2("nested-two").build();
AbstractNestedImmutable abstractNestedImmutable1 =
AbstractNestedImmutable.builder().attribute2("two").abstractNestedImmutableOne(abstractNestedImmutable2).build();
FlattenedNestedImmutableBean flattenedNestedImmutableBean = new FlattenedNestedImmutableBean();
flattenedNestedImmutableBean.setId("id-value");
flattenedNestedImmutableBean.setAttribute1("one");
flattenedNestedImmutableBean.setAbstractNestedImmutable(abstractNestedImmutable1);

Map<String, AttributeValue> nestedAttributesMap = new HashMap<>();
nestedAttributesMap.put("abstractNestedImmutableOne", AttributeValue.fromNul(true));
nestedAttributesMap.put("attribute2", stringValue("nested-two"));

AttributeValue expectedNestedAttribute =
AttributeValue.builder().m(Collections.unmodifiableMap(nestedAttributesMap)).build();

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedNestedImmutableBean, false);
assertThat(itemMap.size(), is(4));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
assertThat(itemMap, hasEntry("attribute2", stringValue("two")));
assertThat(itemMap, hasEntry("abstractNestedImmutableOne", expectedNestedAttribute));
}

@Test
public void dynamoDbFlatten_correctlyFlattensNullNestedImmutableAttributes() {
BeanTableSchema<FlattenedNestedImmutableBean> beanTableSchema =
BeanTableSchema.create(FlattenedNestedImmutableBean.class);
AbstractNestedImmutable abstractNestedImmutable = AbstractNestedImmutable.builder().build();
FlattenedNestedImmutableBean flattenedNestedImmutableBean = new FlattenedNestedImmutableBean();
flattenedNestedImmutableBean.setId("id-value");
flattenedNestedImmutableBean.setAttribute1("one");
flattenedNestedImmutableBean.setAbstractNestedImmutable(abstractNestedImmutable);

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedNestedImmutableBean, false);
assertThat(itemMap.size(), is(4));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("attribute1", stringValue("one")));
assertThat(itemMap, hasEntry("attribute2", AttributeValue.fromNul(true)));
assertThat(itemMap, hasEntry("abstractNestedImmutableOne", AttributeValue.fromNul(true)));
}

@Test
public void dynamoDbFlatten_correctlyFlattensSecondNullNestedAttributes_IgnoreNullsFalse() {
BeanTableSchema<FlattenedFirstNestedBean> beanTableSchema =
BeanTableSchema.create(FlattenedFirstNestedBean.class);
FlattenedFirstNestedBean flattenedFirstNestedBean = new FlattenedFirstNestedBean();
flattenedFirstNestedBean.setId("id-value");

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedFirstNestedBean, false);
assertThat(itemMap.size(), is(4));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("secondId", AttributeValue.fromNul(true)));
assertThat(itemMap, hasEntry("thirdId", AttributeValue.fromNul(true)));
assertThat(itemMap, hasEntry("flattenedFourthBean", AttributeValue.fromNul(true)));
}

@Test
public void dynamoDbFlatten_correctlyFlattensSecondNullNestedAttributes_IgnoreNullsTrue() {
BeanTableSchema<FlattenedFirstNestedBean> beanTableSchema =
BeanTableSchema.create(FlattenedFirstNestedBean.class);
FlattenedFirstNestedBean flattenedFirstNestedBean = new FlattenedFirstNestedBean();
flattenedFirstNestedBean.setId("id-value");

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedFirstNestedBean, true);
assertThat(itemMap.size(), is(1));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
}

@Test
public void dynamoDbFlatten_correctlyFlattensThirdNullNestedAttributes_IgnoreNullsFalse() {
BeanTableSchema<FlattenedFirstNestedBean> beanTableSchema =
BeanTableSchema.create(FlattenedFirstNestedBean.class);
FlattenedSecondNestedBean flattenedSecondNestedBean = new FlattenedSecondNestedBean();
flattenedSecondNestedBean.setSecondId("second-id-value");
FlattenedFirstNestedBean flattenedFirstNestedBean = new FlattenedFirstNestedBean();
flattenedFirstNestedBean.setId("id-value");
flattenedFirstNestedBean.setFlattenedSecondNestedBean(flattenedSecondNestedBean);

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedFirstNestedBean, false);
assertThat(itemMap.size(), is(4));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("secondId", stringValue("second-id-value")));
assertThat(itemMap, hasEntry("thirdId", AttributeValue.fromNul(true)));
assertThat(itemMap, hasEntry("flattenedFourthBean", AttributeValue.fromNul(true)));
}

@Test
public void dynamoDbFlatten_correctlyFlattensThirdNullNestedAttributes_IgnoreNullsTrue() {
BeanTableSchema<FlattenedFirstNestedBean> beanTableSchema =
BeanTableSchema.create(FlattenedFirstNestedBean.class);
FlattenedSecondNestedBean flattenedSecondNestedBean = new FlattenedSecondNestedBean();
flattenedSecondNestedBean.setSecondId("second-id-value");
FlattenedFirstNestedBean flattenedFirstNestedBean = new FlattenedFirstNestedBean();
flattenedFirstNestedBean.setId("id-value");
flattenedFirstNestedBean.setFlattenedSecondNestedBean(flattenedSecondNestedBean);

Map<String, AttributeValue> itemMap = beanTableSchema.itemToMap(flattenedFirstNestedBean, true);
assertThat(itemMap.size(), is(2));
assertThat(itemMap, hasEntry("id", stringValue("id-value")));
assertThat(itemMap, hasEntry("secondId", stringValue("second-id-value")));
}

@Test
public void documentBean_correctlyMapsBeanAttributes() {
BeanTableSchema<DocumentBean> beanTableSchema = BeanTableSchema.create(DocumentBean.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItem;
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemComposedClass;
import software.amazon.awssdk.enhanced.dynamodb.functionaltests.models.FakeItemWithSort;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbFlatten;
import software.amazon.awssdk.enhanced.dynamodb.mapper.testimmutables.EntityEnvelopeImmutable;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;

Expand Down Expand Up @@ -782,6 +783,62 @@ public Consumer<StaticTableMetadata.Builder> modifyMetadata() {
}
}

public static final class FakeMappedItemWithDeep {
private String documentString;
private FakeDocumentWithDeep aFakeDocument;

public String getDocumentString() {
return documentString;
}

public void setDocumentString(String documentString) {
this.documentString = documentString;
}

@DynamoDbFlatten
public FakeDocumentWithDeep getAFakeDocument() {
return aFakeDocument;
}

public void setAFakeDocument(FakeDocumentWithDeep aFakeDocument) {
this.aFakeDocument = aFakeDocument;
}
}

public static final class FakeDocumentWithDeep {
private Integer documentInteger;
private DeepFakeDocument deepFakeDocument;

public Integer getDocumentInteger() {
return documentInteger;
}

public void setDocumentInteger(Integer documentInteger) {
this.documentInteger = documentInteger;
}

@DynamoDbFlatten
public DeepFakeDocument getDeepFakeDocument() {
return deepFakeDocument;
}

public void setDeepFakeDocument(DeepFakeDocument deepFakeDocument) {
this.deepFakeDocument = deepFakeDocument;
}
}

public static final class DeepFakeDocument {
private String deepString;

public String getDeepString() {
return deepString;
}

public void setDeepString(String deepString) {
this.deepString = deepString;
}
}

@Mock
private AttributeConverterProvider provider1;

Expand Down Expand Up @@ -1388,6 +1445,67 @@ public void buildAbstractWithFlatten() {
is(singletonMap("documentString", AttributeValue.builder().s("test-string").build())));
}

@Test
public void buildAbstractWithFlattenAndIgnoreNullAsFalse() {
StaticTableSchema<FakeMappedItem> tableSchema =
StaticTableSchema.builder(FakeMappedItem.class)
.flatten(FAKE_DOCUMENT_TABLE_SCHEMA,
FakeMappedItem::getAFakeDocument,
FakeMappedItem::setAFakeDocument)
.build();

FakeDocument document = FakeDocument.of("test-string", null);
FakeMappedItem item = FakeMappedItem.builder().aFakeDocument(document).build();

Map<String, AttributeValue> attributeMapWithNulls = tableSchema.itemToMap(item, false);
assertThat(attributeMapWithNulls.size(), is(2));
assertThat(attributeMapWithNulls, hasEntry("documentString", AttributeValue.builder().s("test-string").build()));
assertThat(attributeMapWithNulls, hasEntry("documentInteger", AttributeValue.fromNul(true)));
}

@Test
public void buildAbstractWithNestedFlattenAndIgnoreNullAsFalse() {
StaticTableSchema<DeepFakeDocument> deepSchema =
StaticTableSchema.builder(DeepFakeDocument.class)
.newItemSupplier(DeepFakeDocument::new)
.addAttribute(String.class, a -> a.name("deepString")
.getter(DeepFakeDocument::getDeepString)
.setter(DeepFakeDocument::setDeepString))
.build();

StaticTableSchema<FakeDocumentWithDeep> nestedSchema =
StaticTableSchema.builder(FakeDocumentWithDeep.class)
.newItemSupplier(FakeDocumentWithDeep::new)
.addAttribute(Integer.class, a -> a.name("documentInteger")
.getter(FakeDocumentWithDeep::getDocumentInteger)
.setter(FakeDocumentWithDeep::setDocumentInteger))
.flatten(deepSchema,
FakeDocumentWithDeep::getDeepFakeDocument,
FakeDocumentWithDeep::setDeepFakeDocument)
.build();

StaticTableSchema<FakeMappedItemWithDeep> tableSchema =
StaticTableSchema.builder(FakeMappedItemWithDeep.class)
.newItemSupplier(FakeMappedItemWithDeep::new)
.addAttribute(String.class, a -> a.name("documentString")
.getter(FakeMappedItemWithDeep::getDocumentString)
.setter(FakeMappedItemWithDeep::setDocumentString))
.flatten(nestedSchema,
FakeMappedItemWithDeep::getAFakeDocument,
FakeMappedItemWithDeep::setAFakeDocument)
.build();

FakeMappedItemWithDeep item = new FakeMappedItemWithDeep();
item.setDocumentString("top-level-string");

Map<String, AttributeValue> attributeMap = tableSchema.itemToMap(item, false);

assertThat(attributeMap.size(), is(3));
assertThat(attributeMap, hasEntry("documentString", AttributeValue.builder().s("top-level-string").build()));
assertThat(attributeMap, hasEntry("documentInteger", AttributeValue.fromNul(true)));
assertThat(attributeMap, hasEntry("deepString", AttributeValue.fromNul(true)));
}

@Test
public void buildAbstractExtends() {
StaticTableSchema<FakeAbstractSuperclass> superclassTableSchema =
Expand Down
Loading