Skip to content

Commit e8debb9

Browse files
author
Andy Kiesler
committed
Include flattened mappers in converter resolution
This ensures that flattened fields are correctly resolved for conversion. This mostly impacts extensions such as the AutoGeneratedTimestampRecordExtension which could not handle a flattend timestamp field. #3150
1 parent 6cfa6fc commit e8debb9

File tree

4 files changed

+117
-17
lines changed

4 files changed

+117
-17
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Amazon DynamoDB",
4+
"contributor": "akiesler",
5+
"description": "Include flattened mappers in attribute converter resolution"
6+
}

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -589,8 +589,15 @@ private B constructNewBuilder() {
589589
@Override
590590
public AttributeConverter<T> converterForAttribute(Object key) {
591591
ResolvedImmutableAttribute<T, B> resolvedImmutableAttribute = indexedMappers.get(key);
592-
return resolvedImmutableAttribute != null
593-
? resolvedImmutableAttribute.attributeConverter()
594-
: null;
592+
if (resolvedImmutableAttribute != null) {
593+
return resolvedImmutableAttribute.attributeConverter();
594+
}
595+
596+
// If no resolvedAttribute is found look through flattened attributes
597+
FlattenedMapper<T, B, ?> flattenedMapper = indexedFlattenedMappers.get(key);
598+
if (flattenedMapper != null) {
599+
return (AttributeConverter) flattenedMapper.getOtherItemTableSchema().converterForAttribute(key);
600+
}
601+
return null;
595602
}
596603
}

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/functionaltests/AutoGeneratedTimestampRecordTest.java

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ public class AutoGeneratedTimestampRecordTest extends LocalDynamoDbSyncTestBase
7373
private static final String TABLE_NAME = "table-name";
7474
private static final OperationContext PRIMARY_CONTEXT =
7575
DefaultOperationContext.create(TABLE_NAME, TableMetadata.primaryIndexName());
76+
77+
private static final TableSchema<FlattenedRecord> FLATTENED_TABLE_SCHEMA =
78+
StaticTableSchema.builder(FlattenedRecord.class)
79+
.newItemSupplier(FlattenedRecord::new)
80+
.addAttribute(Instant.class, a -> a.name("generated")
81+
.getter(FlattenedRecord::getGenerated)
82+
.setter(FlattenedRecord::setGenerated)
83+
.tags(autoGeneratedTimestampAttribute()))
84+
.build();
85+
7686
private static final TableSchema<Record> TABLE_SCHEMA =
7787
StaticTableSchema.builder(Record.class)
7888
.newItemSupplier(Record::new)
@@ -102,6 +112,7 @@ public class AutoGeneratedTimestampRecordTest extends LocalDynamoDbSyncTestBase
102112
.setter(Record::setConvertedLastUpdatedDate)
103113
.attributeConverter(TimeFormatUpdateTestConverter.create())
104114
.tags(autoGeneratedTimestampAttribute()))
115+
.flatten(FLATTENED_TABLE_SCHEMA, Record::getFlattenedRecord, Record::setFlattenedRecord)
105116
.build();
106117

107118
private final List<Map<String, AttributeValue>> fakeItems =
@@ -154,12 +165,14 @@ public void putNewRecordSetsInitialAutoGeneratedTimestamp() {
154165
mappedTable.putItem(r -> r.item(item));
155166
Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
156167
GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB();
168+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW);
157169
Record expectedRecord = new Record().setId("id")
158170
.setAttribute("one")
159171
.setLastUpdatedDate(MOCKED_INSTANT_NOW)
160172
.setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW)
161173
.setCreatedDate(MOCKED_INSTANT_NOW)
162-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW);
174+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW)
175+
.setFlattenedRecord(flattenedRecord);
163176
assertThat(result, is(expectedRecord));
164177
// The data in DDB is stored in converted time format
165178
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00"));
@@ -169,12 +182,14 @@ public void putNewRecordSetsInitialAutoGeneratedTimestamp() {
169182
public void updateNewRecordSetsAutoFormattedDate() {
170183
Record result = mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one")));
171184
GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB();
185+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW);
172186
Record expectedRecord = new Record().setId("id")
173187
.setAttribute("one")
174188
.setLastUpdatedDate(MOCKED_INSTANT_NOW)
175189
.setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW)
176190
.setCreatedDate(MOCKED_INSTANT_NOW)
177-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW);
191+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW)
192+
.setFlattenedRecord(flattenedRecord);
178193
assertThat(result, is(expectedRecord));
179194
// The data in DDB is stored in converted time format
180195
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00"));
@@ -185,12 +200,14 @@ public void putExistingRecordUpdatedWithAutoFormattedTimestamps() {
185200
mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one")));
186201
Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
187202
GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB();
203+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW);
188204
Record expectedRecord = new Record().setId("id")
189205
.setAttribute("one")
190206
.setLastUpdatedDate(MOCKED_INSTANT_NOW)
191207
.setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW)
192208
.setCreatedDate(MOCKED_INSTANT_NOW)
193-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW);
209+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW)
210+
.setFlattenedRecord(flattenedRecord);
194211
assertThat(result, is(expectedRecord));
195212
// The data in DDB is stored in converted time format
196213
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00"));
@@ -199,13 +216,15 @@ public void putExistingRecordUpdatedWithAutoFormattedTimestamps() {
199216
mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one")));
200217
result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
201218
itemAsStoredInDDB = getItemAsStoredFromDDB();
219+
flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE);
202220
expectedRecord = new Record().setId("id")
203221
.setAttribute("one")
204222
.setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
205223
.setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
206224
// Note : Since we are doing PutItem second time, the createDate gets updated,
207225
.setCreatedDate(MOCKED_INSTANT_UPDATE_ONE)
208-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE);
226+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE)
227+
.setFlattenedRecord(flattenedRecord);
209228

210229
System.out.println("result "+result);
211230
assertThat(result, is(expectedRecord));
@@ -218,12 +237,14 @@ public void putItemFollowedByUpdates() {
218237
mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one")));
219238
Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
220239
GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB();
240+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW);
221241
Record expectedRecord = new Record().setId("id")
222242
.setAttribute("one")
223243
.setLastUpdatedDate(MOCKED_INSTANT_NOW)
224244
.setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW)
225245
.setCreatedDate(MOCKED_INSTANT_NOW)
226-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW);
246+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW)
247+
.setFlattenedRecord(flattenedRecord);
227248
assertThat(result, is(expectedRecord));
228249
// The data in DDB is stored in converted time format
229250
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00"));
@@ -233,12 +254,14 @@ public void putItemFollowedByUpdates() {
233254

234255
result = mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one")));
235256
itemAsStoredInDDB = getItemAsStoredFromDDB();
257+
flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE);
236258
expectedRecord = new Record().setId("id")
237259
.setAttribute("one")
238260
.setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
239261
.setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
240262
.setCreatedDate(MOCKED_INSTANT_NOW)
241-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE);
263+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE)
264+
.setFlattenedRecord(flattenedRecord);
242265
assertThat(result, is(expectedRecord));
243266
// The data in DDB is stored in converted time format
244267
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("14 01 2019 14:00:00"));
@@ -247,12 +270,14 @@ public void putItemFollowedByUpdates() {
247270
Mockito.when(mockCLock.instant()).thenReturn(MOCKED_INSTANT_UPDATE_TWO);
248271
result = mappedTable.updateItem(r -> r.item(new Record().setId("id").setAttribute("one")));
249272
itemAsStoredInDDB = getItemAsStoredFromDDB();
273+
flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_TWO);
250274
expectedRecord = new Record().setId("id")
251275
.setAttribute("one")
252276
.setLastUpdatedDate(MOCKED_INSTANT_UPDATE_TWO)
253277
.setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_TWO)
254278
.setCreatedDate(MOCKED_INSTANT_NOW)
255-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_TWO);
279+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_TWO)
280+
.setFlattenedRecord(flattenedRecord);
256281
assertThat(result, is(expectedRecord));
257282
// The data in DDB is stored in converted time format
258283
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("15 01 2019 14:00:00"));
@@ -267,12 +292,14 @@ public void putExistingRecordWithConditionExpressions() {
267292
mappedTable.putItem(r -> r.item(new Record().setId("id").setAttribute("one")));
268293
Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
269294
GetItemResponse itemAsStoredInDDB = getItemAsStoredFromDDB();
295+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_NOW);
270296
Record expectedRecord = new Record().setId("id")
271297
.setAttribute("one")
272298
.setLastUpdatedDate(MOCKED_INSTANT_NOW)
273299
.setConvertedLastUpdatedDate(MOCKED_INSTANT_NOW)
274300
.setCreatedDate(MOCKED_INSTANT_NOW)
275-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW);
301+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_NOW)
302+
.setFlattenedRecord(flattenedRecord);
276303
assertThat(result, is(expectedRecord));
277304
// The data in DDB is stored in converted time format
278305
assertThat(itemAsStoredInDDB.item().get("convertedLastUpdatedDate").s(), is("13 01 2019 14:00:00"));
@@ -291,13 +318,15 @@ public void putExistingRecordWithConditionExpressions() {
291318
.build());
292319

293320
result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
321+
flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE);
294322
expectedRecord = new Record().setId("id")
295323
.setAttribute("one")
296324
.setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
297325
.setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
298326
// Note that this is a second putItem call so create date is updated.
299327
.setCreatedDate(MOCKED_INSTANT_UPDATE_ONE)
300-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE);
328+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE)
329+
.setFlattenedRecord(flattenedRecord);
301330
assertThat(result, is(expectedRecord));
302331
}
303332

@@ -319,12 +348,14 @@ public void updateExistingRecordWithConditionExpressions() {
319348
.conditionExpression(conditionExpression));
320349

321350
Record result = mappedTable.getItem(r -> r.key(k -> k.partitionValue("id")));
351+
FlattenedRecord flattenedRecord = new FlattenedRecord().setGenerated(MOCKED_INSTANT_UPDATE_ONE);
322352
Record expectedRecord = new Record().setId("id")
323353
.setAttribute("one")
324354
.setLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
325355
.setConvertedLastUpdatedDate(MOCKED_INSTANT_UPDATE_ONE)
326356
.setCreatedDate(MOCKED_INSTANT_NOW)
327-
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE);
357+
.setLastUpdatedDateInEpochMillis(MOCKED_INSTANT_UPDATE_ONE)
358+
.setFlattenedRecord(flattenedRecord);
328359
assertThat(result, is(expectedRecord));
329360
}
330361

@@ -408,6 +439,7 @@ private static class Record {
408439
private Instant lastUpdatedDate;
409440
private Instant convertedLastUpdatedDate;
410441
private Instant lastUpdatedDateInEpochMillis;
442+
private FlattenedRecord flattenedRecord;
411443

412444
private String getId() {
413445
return id;
@@ -463,6 +495,15 @@ private Record setLastUpdatedDateInEpochMillis(Instant lastUpdatedDateInEpochMil
463495
return this;
464496
}
465497

498+
public FlattenedRecord getFlattenedRecord() {
499+
return flattenedRecord;
500+
}
501+
502+
public Record setFlattenedRecord(FlattenedRecord flattenedRecord) {
503+
this.flattenedRecord = flattenedRecord;
504+
return this;
505+
}
506+
466507
@Override
467508
public boolean equals(Object o) {
468509
if (this == o) {
@@ -477,13 +518,14 @@ public boolean equals(Object o) {
477518
Objects.equals(lastUpdatedDate, record.lastUpdatedDate) &&
478519
Objects.equals(createdDate, record.createdDate) &&
479520
Objects.equals(lastUpdatedDateInEpochMillis, record.lastUpdatedDateInEpochMillis) &&
480-
Objects.equals(convertedLastUpdatedDate, record.convertedLastUpdatedDate);
521+
Objects.equals(convertedLastUpdatedDate, record.convertedLastUpdatedDate) &&
522+
Objects.equals(flattenedRecord, record.flattenedRecord);
481523
}
482524

483525
@Override
484526
public int hashCode() {
485-
return Objects.hash(id, attribute,
486-
lastUpdatedDate, createdDate, lastUpdatedDateInEpochMillis, convertedLastUpdatedDate);
527+
return Objects.hash(id, attribute, lastUpdatedDate, createdDate, lastUpdatedDateInEpochMillis,
528+
convertedLastUpdatedDate, flattenedRecord);
487529
}
488530

489531
@Override
@@ -495,6 +537,44 @@ public String toString() {
495537
", lastUpdatedDate=" + lastUpdatedDate +
496538
", convertedLastUpdatedDate=" + convertedLastUpdatedDate +
497539
", lastUpdatedDateInEpochMillis=" + lastUpdatedDateInEpochMillis +
540+
", flattenedRecord=" + flattenedRecord +
541+
'}';
542+
}
543+
}
544+
545+
private static class FlattenedRecord {
546+
private Instant generated;
547+
548+
public Instant getGenerated() {
549+
return generated;
550+
}
551+
552+
public FlattenedRecord setGenerated(Instant generated) {
553+
this.generated = generated;
554+
return this;
555+
}
556+
557+
@Override
558+
public boolean equals(Object o) {
559+
if (this == o) {
560+
return true;
561+
}
562+
if (o == null || getClass() != o.getClass()) {
563+
return false;
564+
}
565+
FlattenedRecord that = (FlattenedRecord) o;
566+
return Objects.equals(generated, that.generated);
567+
}
568+
569+
@Override
570+
public int hashCode() {
571+
return Objects.hash(generated);
572+
}
573+
574+
@Override
575+
public String toString() {
576+
return "FlattenedRecord{" +
577+
"generated=" + generated +
498578
'}';
499579
}
500580
}

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchemaFlattenTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ public void attributeNames() {
186186
assertThat(result).containsExactlyInAnyOrder(ITEM_MAP.keySet().toArray(new String[]{}));
187187
}
188188

189+
@Test
190+
public void converterForAttribute() {
191+
ITEM_MAP.forEach((key, attributeValue) -> {
192+
assertThat(immutableTableSchema.converterForAttribute(key)).isNotNull();
193+
});
194+
}
195+
189196
public static class ImmutableRecord {
190197
private final String id;
191198
private final String attribute1;
@@ -272,4 +279,4 @@ public ImmutableRecord build() {
272279
}
273280
}
274281
}
275-
}
282+
}

0 commit comments

Comments
 (0)