Skip to content

Commit dba98bd

Browse files
authored
feat: add support for DDB Mapper getItem overloads that specify primary key(s) (#1583)
1 parent 80a49de commit dba98bd

File tree

4 files changed

+103
-17
lines changed

4 files changed

+103
-17
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "af8e4342-f541-4d83-88a1-2570461d7e7e",
3+
"type": "feature",
4+
"description": "Add support for DynamoDB Mapper `getItem` overloads that specify primary key(s)",
5+
"issues": [
6+
"awslabs/aws-sdk-kotlin#1577"
7+
]
8+
}

hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/model/internal/TableImpl.kt

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,13 @@ import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema
99
import aws.sdk.kotlin.hll.dynamodbmapper.model.Index
1010
import aws.sdk.kotlin.hll.dynamodbmapper.model.Table
1111
import aws.sdk.kotlin.hll.dynamodbmapper.model.TableSpec
12-
import aws.sdk.kotlin.hll.dynamodbmapper.operations.TableOperations
13-
import aws.sdk.kotlin.hll.dynamodbmapper.operations.TableOperationsImpl
12+
import aws.sdk.kotlin.hll.dynamodbmapper.model.itemOf
13+
import aws.sdk.kotlin.hll.dynamodbmapper.operations.*
14+
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.Interceptor
15+
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.LReqContext
16+
import aws.sdk.kotlin.services.dynamodb.model.AttributeValue
17+
import aws.sdk.kotlin.services.dynamodb.model.GetItemRequest as LowLevelGetItemRequest
18+
import aws.sdk.kotlin.services.dynamodb.model.GetItemResponse as LowLevelGetItemResponse
1419

1520
internal fun <T, PK> tableImpl(
1621
mapper: DynamoDbMapper,
@@ -35,7 +40,17 @@ internal fun <T, PK> tableImpl(
3540
schema: ItemSchema.CompositeKey<T, PK, SK>,
3641
): Index.CompositeKey<T, PK, SK> = indexImpl(mapper, tableName, name, schema)
3742

38-
override suspend fun getItem(partitionKey: PK) = TODO("not yet implemented")
43+
override suspend fun getItem(partitionKey: PK): T? {
44+
val keyItem = itemOf(schema.partitionKey.toField(partitionKey))
45+
val interceptor = KeyInsertionInterceptor<T>(keyItem)
46+
val op = getItemOperation(specImpl).let {
47+
it.copy(
48+
interceptors = it.interceptors.prepend(interceptor),
49+
)
50+
}
51+
val hRes = op.execute(GetItemRequest { })
52+
return hRes.item
53+
}
3954
}
4055
}
4156

@@ -61,6 +76,36 @@ internal fun <T, PK, SK> tableImpl(
6176
schema: ItemSchema.CompositeKey<T, PK, SK>,
6277
): Index.CompositeKey<T, PK, SK> = indexImpl(mapper, tableName, name, schema)
6378

64-
override suspend fun getItem(partitionKey: PK, sortKey: SK) = TODO("Not yet implemented")
79+
override suspend fun getItem(partitionKey: PK, sortKey: SK): T? {
80+
val keyItem = itemOf(
81+
schema.partitionKey.toField(partitionKey),
82+
schema.sortKey.toField(sortKey),
83+
)
84+
val interceptor = KeyInsertionInterceptor<T>(keyItem)
85+
val op = getItemOperation(specImpl).let {
86+
it.copy(
87+
interceptors = it.interceptors.prepend(interceptor),
88+
)
89+
}
90+
val hRes = op.execute(GetItemRequest { })
91+
return hRes.item
92+
}
6593
}
6694
}
95+
96+
private typealias GetItemInterceptor<T> =
97+
Interceptor<T, GetItemRequest<T>, LowLevelGetItemRequest, LowLevelGetItemResponse, GetItemResponse<T>>
98+
99+
private class KeyInsertionInterceptor<T>(private val newKey: Map<String, AttributeValue>) : GetItemInterceptor<T> {
100+
override fun modifyBeforeInvocation(ctx: LReqContext<T, GetItemRequest<T>, LowLevelGetItemRequest>) =
101+
ctx.lowLevelRequest.copy {
102+
if (key == null) {
103+
key = newKey
104+
}
105+
}
106+
}
107+
108+
private fun <T> List<T>.prepend(element: T): List<T> = buildList(size + 1) {
109+
add(element)
110+
addAll(this)
111+
}

hll/dynamodb-mapper/dynamodb-mapper/common/src/aws/sdk/kotlin/hll/dynamodbmapper/pipeline/internal/Operation.kt

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,33 @@
55
package aws.sdk.kotlin.hll.dynamodbmapper.pipeline.internal
66

77
import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema
8-
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.*
9-
10-
internal class Operation<T, HReq, LReq, LRes, HRes>(
11-
private val initialize: (HReq) -> HReqContextImpl<T, HReq>,
12-
private val serialize: (HReq, ItemSchema<T>) -> LReq,
13-
private val lowLevelInvoke: suspend (LReq) -> LRes,
14-
private val deserialize: (LRes, ItemSchema<T>) -> HRes,
15-
interceptors: Collection<InterceptorAny>,
8+
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.Interceptor
9+
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.InterceptorAny
10+
11+
internal data class Operation<T, HReq, LReq, LRes, HRes>(
12+
val initialize: (HReq) -> HReqContextImpl<T, HReq>,
13+
val serialize: (HReq, ItemSchema<T>) -> LReq,
14+
val lowLevelInvoke: suspend (LReq) -> LRes,
15+
val deserialize: (LRes, ItemSchema<T>) -> HRes,
16+
val interceptors: List<Interceptor<T, HReq, LReq, LRes, HRes>>,
1617
) {
17-
private val interceptors = interceptors.map {
18-
// Will cause runtime ClassCastExceptions during interceptor invocation if the types don't match. Is that ok?
19-
@Suppress("UNCHECKED_CAST")
20-
it as Interceptor<T, HReq, LReq, LRes, HRes>
21-
}
18+
constructor(
19+
initialize: (HReq) -> HReqContextImpl<T, HReq>,
20+
serialize: (HReq, ItemSchema<T>) -> LReq,
21+
lowLevelInvoke: suspend (LReq) -> LRes,
22+
deserialize: (LRes, ItemSchema<T>) -> HRes,
23+
interceptors: Collection<InterceptorAny>,
24+
) : this(
25+
initialize,
26+
serialize,
27+
lowLevelInvoke,
28+
deserialize,
29+
interceptors.map {
30+
// Will cause runtime ClassCastExceptions during interceptor invocation if the types don't match. Is that ok?
31+
@Suppress("UNCHECKED_CAST")
32+
it as Interceptor<T, HReq, LReq, LRes, HRes>
33+
},
34+
)
2235

2336
suspend fun execute(hReq: HReq): HRes {
2437
val hReqContext = doInitialize(hReq)

hll/dynamodb-mapper/dynamodb-mapper/jvm/test/aws/sdk/kotlin/hll/dynamodbmapper/operations/GetItemTest.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,24 @@ class GetItemTest : DdbLocalTest() {
125125
val tableCapacity = assertNotNull(cc.table)
126126
assertEquals(0.5, tableCapacity.capacityUnits)
127127
}
128+
129+
@Test
130+
fun testPkGetItemByScalarKey() = runTest {
131+
val table = mapper().getTable(PK_TABLE_NAME, pkSchema)
132+
133+
val item = assertNotNull(table.getItem(1))
134+
assertEquals("foo", item.value)
135+
136+
assertNull(table.getItem(2))
137+
}
138+
139+
@Test
140+
fun testCkGetItemByScalarKeys() = runTest {
141+
val table = mapper().getTable(CK_TABLE_NAME, ckSchema)
142+
143+
val item = assertNotNull(table.getItem("abcd", 42))
144+
assertEquals("foo", item.value)
145+
146+
assertNull(table.getItem("abcd", 43))
147+
}
128148
}

0 commit comments

Comments
 (0)