Skip to content

Commit be52b8f

Browse files
akieslerAndy Kiesler
andauthored
Include flattened mappers in converter resolution (#3877)
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 Co-authored-by: Andy Kiesler <[email protected]>
1 parent d126935 commit be52b8f

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 Enhanced Client",
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)