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
8 changes: 8 additions & 0 deletions .changes/af8e4342-f541-4d83-88a1-2570461d7e7e.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "af8e4342-f541-4d83-88a1-2570461d7e7e",
"type": "feature",
"description": "Add support for DynamoDB Mapper `getItem` overloads that specify primary key(s)",
"issues": [
"awslabs/aws-sdk-kotlin#1577"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema
import aws.sdk.kotlin.hll.dynamodbmapper.model.Index
import aws.sdk.kotlin.hll.dynamodbmapper.model.Table
import aws.sdk.kotlin.hll.dynamodbmapper.model.TableSpec
import aws.sdk.kotlin.hll.dynamodbmapper.operations.TableOperations
import aws.sdk.kotlin.hll.dynamodbmapper.operations.TableOperationsImpl
import aws.sdk.kotlin.hll.dynamodbmapper.model.itemOf
import aws.sdk.kotlin.hll.dynamodbmapper.operations.*
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.Interceptor
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.LReqContext
import aws.sdk.kotlin.services.dynamodb.model.AttributeValue
import aws.sdk.kotlin.services.dynamodb.model.GetItemRequest as LowLevelGetItemRequest
import aws.sdk.kotlin.services.dynamodb.model.GetItemResponse as LowLevelGetItemResponse

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

override suspend fun getItem(partitionKey: PK) = TODO("not yet implemented")
override suspend fun getItem(partitionKey: PK): T? {
val keyItem = itemOf(schema.partitionKey.toField(partitionKey))
val interceptor = KeyInsertionInterceptor<T>(keyItem)
val op = getItemOperation(specImpl).let {
it.copy(
interceptors = it.interceptors.prepend(interceptor),
)
}
val hRes = op.execute(GetItemRequest { })
return hRes.item
}
}
}

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

override suspend fun getItem(partitionKey: PK, sortKey: SK) = TODO("Not yet implemented")
override suspend fun getItem(partitionKey: PK, sortKey: SK): T? {
val keyItem = itemOf(
schema.partitionKey.toField(partitionKey),
schema.sortKey.toField(sortKey),
)
val interceptor = KeyInsertionInterceptor<T>(keyItem)
val op = getItemOperation(specImpl).let {
it.copy(
interceptors = it.interceptors.prepend(interceptor),
)
}
val hRes = op.execute(GetItemRequest { })
return hRes.item
}
}
}

private typealias GetItemInterceptor<T> =
Interceptor<T, GetItemRequest<T>, LowLevelGetItemRequest, LowLevelGetItemResponse, GetItemResponse<T>>

private class KeyInsertionInterceptor<T>(private val newKey: Map<String, AttributeValue>) : GetItemInterceptor<T> {
override fun modifyBeforeInvocation(ctx: LReqContext<T, GetItemRequest<T>, LowLevelGetItemRequest>) =
ctx.lowLevelRequest.copy {
if (key == null) {
key = newKey
}
}
}

private fun <T> List<T>.prepend(element: T): List<T> = buildList(size + 1) {
add(element)
addAll(this)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,33 @@
package aws.sdk.kotlin.hll.dynamodbmapper.pipeline.internal

import aws.sdk.kotlin.hll.dynamodbmapper.items.ItemSchema
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.*

internal class Operation<T, HReq, LReq, LRes, HRes>(
private val initialize: (HReq) -> HReqContextImpl<T, HReq>,
private val serialize: (HReq, ItemSchema<T>) -> LReq,
private val lowLevelInvoke: suspend (LReq) -> LRes,
private val deserialize: (LRes, ItemSchema<T>) -> HRes,
interceptors: Collection<InterceptorAny>,
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.Interceptor
import aws.sdk.kotlin.hll.dynamodbmapper.pipeline.InterceptorAny

internal data class Operation<T, HReq, LReq, LRes, HRes>(
val initialize: (HReq) -> HReqContextImpl<T, HReq>,
val serialize: (HReq, ItemSchema<T>) -> LReq,
val lowLevelInvoke: suspend (LReq) -> LRes,
val deserialize: (LRes, ItemSchema<T>) -> HRes,
val interceptors: List<Interceptor<T, HReq, LReq, LRes, HRes>>,
) {
private val interceptors = interceptors.map {
// Will cause runtime ClassCastExceptions during interceptor invocation if the types don't match. Is that ok?
@Suppress("UNCHECKED_CAST")
it as Interceptor<T, HReq, LReq, LRes, HRes>
}
constructor(
initialize: (HReq) -> HReqContextImpl<T, HReq>,
serialize: (HReq, ItemSchema<T>) -> LReq,
lowLevelInvoke: suspend (LReq) -> LRes,
deserialize: (LRes, ItemSchema<T>) -> HRes,
interceptors: Collection<InterceptorAny>,
) : this(
initialize,
serialize,
lowLevelInvoke,
deserialize,
interceptors.map {
// Will cause runtime ClassCastExceptions during interceptor invocation if the types don't match. Is that ok?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The comment is useful but Is that ok? seems like it could be removed

@Suppress("UNCHECKED_CAST")
it as Interceptor<T, HReq, LReq, LRes, HRes>
},
)

suspend fun execute(hReq: HReq): HRes {
val hReqContext = doInitialize(hReq)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,24 @@ class GetItemTest : DdbLocalTest() {
val tableCapacity = assertNotNull(cc.table)
assertEquals(0.5, tableCapacity.capacityUnits)
}

@Test
fun testPkGetItemByScalarKey() = runTest {
val table = mapper().getTable(PK_TABLE_NAME, pkSchema)

val item = assertNotNull(table.getItem(1))
assertEquals("foo", item.value)

assertNull(table.getItem(2))
}

@Test
fun testCkGetItemByScalarKeys() = runTest {
val table = mapper().getTable(CK_TABLE_NAME, ckSchema)

val item = assertNotNull(table.getItem("abcd", 42))
assertEquals("foo", item.value)

assertNull(table.getItem("abcd", 43))
}
}
Loading