Skip to content

Commit 7a1d42d

Browse files
committed
DDB mapper filter expressions codegen
1 parent 4240741 commit 7a1d42d

File tree

40 files changed

+1719
-191
lines changed

40 files changed

+1719
-191
lines changed

hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperPkg.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public object MapperPkg {
2020
public val CollectionValues: String = "$Values.collections"
2121
public val ScalarValues: String = "$Values.scalars"
2222
public val SmithyTypeValues: String = "$Values.smithytypes"
23+
24+
@InternalSdkApi
25+
public object Expressions {
26+
public val Base: String = "${Hl.Base}.expressions"
27+
public val Internal: String = "$Base.internal"
28+
}
2329
}
2430

2531
@InternalSdkApi

hll/dynamodb-mapper/dynamodb-mapper-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/model/MapperTypes.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ public object MapperTypes {
2222
public val ManualPagination: TypeRef = TypeRef(MapperPkg.Hl.Annotations, "ManualPagination")
2323
}
2424

25+
public object Expressions {
26+
public val BooleanExpr: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Base, "BooleanExpr")
27+
public val Filter: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Base, "Filter")
28+
public val KeyFilter: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Base, "KeyFilter")
29+
30+
public object Internal {
31+
public val FilterImpl: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Internal, "FilterImpl")
32+
public val ParameterizingExpressionVisitor: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Internal, "ParameterizingExpressionVisitor")
33+
public val toExpression: TypeRef = TypeRef(MapperPkg.Hl.Expressions.Internal, "toExpression")
34+
}
35+
}
36+
2537
public object Items {
2638
public fun itemSchema(typeVar: String): TypeRef =
2739
TypeRef(MapperPkg.Hl.Items, "ItemSchema", genericArgs = listOf(TypeVar(typeVar)))
Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,12 @@
55
package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model
66

77
import aws.sdk.kotlin.hll.codegen.model.Operation
8-
import aws.sdk.kotlin.hll.codegen.model.Structure
98
import aws.smithy.kotlin.runtime.collections.AttributeKey
109

1110
/**
1211
* Defines [AttributeKey] instances that relate to the data model of low-level to high-level codegen
1312
*/
14-
internal object ModelAttributes {
15-
/**
16-
* For a given high-level [Operation], this attribute key identifies the associated low-level [Operation]
17-
*/
18-
val LowLevelOperation: AttributeKey<Operation> = AttributeKey("aws.sdk.kotlin.ddbmapper#LowLevelOperation")
19-
20-
/**
21-
* For a given high-level [Structure], this attribute key identifies the associated low-level [Structure]
22-
*/
23-
val LowLevelStructure: AttributeKey<Structure> = AttributeKey("aws.sdk.kotlin.ddbmapper#LowLevelStructure")
24-
13+
internal object MapperAttributes {
2514
/**
2615
* For a given [Operation], this attribute key contains relevant pagination members (if applicable) in the request
2716
* and response

hll/dynamodb-mapper/dynamodb-mapper-ops-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/MemberCodegenBehavior.kt

Lines changed: 96 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,12 @@
44
*/
55
package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model
66

7-
import aws.sdk.kotlin.hll.codegen.model.Member
8-
import aws.sdk.kotlin.hll.codegen.model.TypeRef
9-
import aws.sdk.kotlin.hll.codegen.model.Types
10-
import aws.sdk.kotlin.hll.codegen.model.nullable
7+
import aws.sdk.kotlin.hll.codegen.model.*
118
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperPkg
129
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes
13-
14-
private val attrMapTypes = setOf(MapperTypes.AttributeMap, MapperTypes.AttributeMap.nullable())
15-
private val attrMapListTypes = Types.Kotlin.list(MapperTypes.AttributeMap).let { setOf(it, it.nullable()) }
10+
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ExpressionArgumentsType.*
11+
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ExpressionLiteralType.*
12+
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.MemberCodegenBehavior.*
1613

1714
/**
1815
* Describes a behavior to apply for a given [Member] in a low-level structure when generating code for an equivalent
@@ -60,56 +57,105 @@ internal sealed interface MemberCodegenBehavior {
6057
* structure).
6158
*/
6259
data object Hoist : MemberCodegenBehavior
60+
61+
/**
62+
* Indicates that a member is a string expression parameter which should be replaced by an expression DSL
63+
* @param type The type of expression this member models
64+
*/
65+
data class ExpressionLiteral(val type: ExpressionLiteralType) : MemberCodegenBehavior
66+
67+
/**
68+
* Indicates that a member is a map of expression arguments which should be automatically handled by an expression
69+
* DSL
70+
* @param type The type of expression arguments this member models
71+
*/
72+
data class ExpressionArguments(val type: ExpressionArgumentsType) : MemberCodegenBehavior
73+
}
74+
75+
/**
76+
* Identifies a type of expression literal supported by DynamoDB APIs
77+
*/
78+
internal enum class ExpressionLiteralType {
79+
Condition,
80+
Filter,
81+
KeyCondition,
82+
Projection,
83+
Update,
84+
}
85+
86+
/**
87+
* Identifies a type of expression arguments supported by DynamoDB APIs
88+
*/
89+
internal enum class ExpressionArgumentsType {
90+
AttributeNames,
91+
AttributeValues,
6392
}
6493

6594
/**
6695
* Identifies a [MemberCodegenBehavior] for this [Member] by way of various heuristics
6796
*/
6897
internal val Member.codegenBehavior: MemberCodegenBehavior
69-
get() = when {
70-
this in unsupportedMembers -> MemberCodegenBehavior.Drop
71-
type in attrMapTypes -> if (name == "key") MemberCodegenBehavior.MapKeys else MemberCodegenBehavior.MapAll
72-
type in attrMapListTypes -> MemberCodegenBehavior.ListMapAll
73-
isTableName || isIndexName -> MemberCodegenBehavior.Hoist
74-
else -> MemberCodegenBehavior.PassThrough
75-
}
98+
get() = rules.firstNotNullOfOrNull { it.matchedBehaviorOrNull(this) } ?: PassThrough
7699

77-
private val Member.isTableName: Boolean
78-
get() = name == "tableName" && type == Types.Kotlin.StringNullable
100+
private fun llType(name: String) = TypeRef(MapperPkg.Ll.Model, name)
79101

80-
private val Member.isIndexName: Boolean
81-
get() = name == "indexName" && type == Types.Kotlin.StringNullable
102+
private data class Rule(
103+
val namePredicate: (String) -> Boolean,
104+
val typePredicate: (TypeRef) -> Boolean,
105+
val behavior: MemberCodegenBehavior,
106+
) {
107+
constructor(name: String, type: TypeRef, behavior: MemberCodegenBehavior) :
108+
this(name::equals, type::isEquivalentTo, behavior)
82109

83-
private fun llType(name: String) = TypeRef(MapperPkg.Ll.Model, name)
110+
constructor(name: Regex, type: TypeRef, behavior: MemberCodegenBehavior) :
111+
this(name::matches, type::isEquivalentTo, behavior)
84112

85-
private val unsupportedMembers = listOf(
86-
// superseded by ConditionExpression
87-
Member("conditionalOperator", llType("ConditionalOperator")),
88-
Member("expected", Types.Kotlin.stringMap(llType("ExpectedAttributeValue"))),
89-
90-
// superseded by FilterExpression
91-
Member("queryFilter", Types.Kotlin.stringMap(llType("Condition"))),
92-
Member("scanFilter", Types.Kotlin.stringMap(llType("Condition"))),
93-
94-
// superseded by KeyConditionExpression
95-
Member("keyConditions", Types.Kotlin.stringMap(llType("Condition"))),
96-
97-
// superseded by ProjectionExpression
98-
Member("attributesToGet", Types.Kotlin.list(Types.Kotlin.String)),
99-
100-
// superseded by UpdateExpression
101-
Member("attributeUpdates", Types.Kotlin.stringMap(llType("AttributeValueUpdate"))),
102-
103-
// TODO add support for expressions
104-
Member("expressionAttributeNames", Types.Kotlin.stringMap(Types.Kotlin.String)),
105-
Member("expressionAttributeValues", MapperTypes.AttributeMap),
106-
Member("conditionExpression", Types.Kotlin.String),
107-
Member("projectionExpression", Types.Kotlin.String),
108-
Member("updateExpression", Types.Kotlin.String),
109-
).map { member ->
110-
if (member.type is TypeRef) {
111-
member.copy(type = member.type.nullable())
112-
} else {
113-
member
114-
}
115-
}.toSet()
113+
fun matchedBehaviorOrNull(member: Member) = if (matches(member)) behavior else null
114+
fun matches(member: Member) = namePredicate(member.name) && typePredicate(member.type as TypeRef)
115+
}
116+
117+
private fun Type.isEquivalentTo(other: Type): Boolean = when (this) {
118+
is TypeVar -> other is TypeVar && shortName == other.shortName
119+
is TypeRef ->
120+
other is TypeRef &&
121+
fullName == other.fullName &&
122+
genericArgs.size == other.genericArgs.size &&
123+
genericArgs.zip(other.genericArgs).all { (thisArg, otherArg) -> thisArg.isEquivalentTo(otherArg) }
124+
}
125+
126+
/**
127+
* Priority-ordered list of dispositions to apply to members found in structures. The first element from this list that
128+
* successfully matches with a member will be chosen.
129+
*/
130+
private val rules = listOf(
131+
// Deprecated expression members not to be carried forward into HLL
132+
Rule("conditionalOperator", llType("ConditionalOperator"), Drop),
133+
Rule("expected", Types.Kotlin.stringMap(llType("ExpectedAttributeValue")), Drop),
134+
Rule("queryFilter", Types.Kotlin.stringMap(llType("Condition")), Drop),
135+
Rule("scanFilter", Types.Kotlin.stringMap(llType("Condition")), Drop),
136+
Rule("keyConditions", Types.Kotlin.stringMap(llType("Condition")), Drop),
137+
Rule("attributesToGet", Types.Kotlin.list(Types.Kotlin.String), Drop),
138+
Rule("attributeUpdates", Types.Kotlin.stringMap(llType("AttributeValueUpdate")), Drop),
139+
140+
// Hoisted members
141+
Rule("tableName", Types.Kotlin.String, Hoist),
142+
Rule("indexName", Types.Kotlin.String, Hoist),
143+
144+
// Expression literals
145+
Rule("keyConditionExpression", Types.Kotlin.String, ExpressionLiteral(KeyCondition)),
146+
Rule("filterExpression", Types.Kotlin.String, ExpressionLiteral(Filter)),
147+
148+
// TODO add support for remaining expression types
149+
Rule("conditionExpression", Types.Kotlin.String, Drop),
150+
Rule("projectionExpression", Types.Kotlin.String, Drop),
151+
Rule("updateExpression", Types.Kotlin.String, Drop),
152+
153+
// Expression arguments
154+
Rule("expressionAttributeNames", Types.Kotlin.stringMap(Types.Kotlin.String), ExpressionArguments(AttributeNames)),
155+
Rule("expressionAttributeValues", MapperTypes.AttributeMap, ExpressionArguments(AttributeValues)),
156+
157+
// Mappable members
158+
Rule(".*".toRegex(), Types.Kotlin.list(MapperTypes.AttributeMap), ListMapAll),
159+
Rule("key", MapperTypes.AttributeMap, MapKeys),
160+
Rule(".*".toRegex(), MapperTypes.AttributeMap, MapAll),
161+
)

hll/dynamodb-mapper/dynamodb-mapper-ops-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Operation.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,9 @@
44
*/
55
package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model
66

7+
import aws.sdk.kotlin.hll.codegen.model.ModelAttributes
78
import aws.sdk.kotlin.hll.codegen.model.Operation
89
import aws.sdk.kotlin.hll.codegen.util.plus
9-
import aws.smithy.kotlin.runtime.collections.get
10-
11-
/**
12-
* Gets the low-level [Operation] equivalent for this high-level operation
13-
*/
14-
internal val Operation.lowLevel: Operation
15-
get() = attributes[ModelAttributes.LowLevelOperation]
1610

1711
/**
1812
* Derives a high-level [Operation] equivalent for this low-level operation

hll/dynamodb-mapper/dynamodb-mapper-ops-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Pagination.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ internal data class PaginationMembers(
3939
* property returns `null`.
4040
*/
4141
internal val Operation.paginationInfo: PaginationMembers?
42-
get() = attributes.getOrNull(ModelAttributes.PaginationInfo)
42+
get() = attributes.getOrNull(MapperAttributes.PaginationInfo)
4343

4444
/**
4545
* A codegen plugin that adds DDB-specific pagination info to operations
4646
*/
4747
internal class DdbPaginationPlugin : ModelParsingPlugin {
4848
override fun postProcessOperation(operation: Operation): Operation {
4949
val paginationMembers = PaginationMembers.forOperationOrNull(operation) ?: return operation
50-
val newAttributes = operation.attributes + (ModelAttributes.PaginationInfo to paginationMembers)
50+
val newAttributes = operation.attributes + (MapperAttributes.PaginationInfo to paginationMembers)
5151
return operation.copy(attributes = newAttributes)
5252
}
5353
}

hll/dynamodb-mapper/dynamodb-mapper-ops-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/model/Structure.kt

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model
66

77
import aws.sdk.kotlin.hll.codegen.model.*
88
import aws.sdk.kotlin.hll.codegen.util.plus
9-
import aws.smithy.kotlin.runtime.collections.get
10-
11-
/**
12-
* Gets the low-level [Structure] equivalent for this high-level structure
13-
*/
14-
internal val Structure.lowLevel: Structure
15-
get() = attributes[ModelAttributes.LowLevelStructure]
9+
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes
1610

1711
/**
1812
* Derives a high-level [Structure] equivalent for this low-level structure
@@ -24,20 +18,54 @@ internal fun Structure.toHighLevel(pkg: String): Structure {
2418
val hlType = TypeRef(pkg, llStructure.type.shortName, listOf(TypeVar("T")))
2519

2620
val hlMembers = llStructure.members.mapNotNull { llMember ->
27-
when (llMember.codegenBehavior) {
28-
MemberCodegenBehavior.PassThrough -> llMember
21+
val nullable = llMember.type.nullable
22+
23+
val hlMember = when (val behavior = llMember.codegenBehavior) {
24+
MemberCodegenBehavior.PassThrough, is MemberCodegenBehavior.ExpressionArguments -> llMember
2925

3026
MemberCodegenBehavior.MapAll, MemberCodegenBehavior.MapKeys ->
31-
llMember.copy(type = TypeVar("T", llMember.type.nullable))
27+
llMember.copy(type = TypeVar("T", nullable))
3228

3329
MemberCodegenBehavior.ListMapAll -> {
3430
val llListType = llMember.type as? TypeRef ?: error("`ListMapAll` member is required to be a TypeRef")
35-
val hlListType = llListType.copy(genericArgs = listOf(TypeVar("T")), nullable = llListType.nullable)
31+
val hlListType = llListType.copy(genericArgs = listOf(TypeVar("T")))
3632
llMember.copy(type = hlListType)
3733
}
3834

35+
is MemberCodegenBehavior.ExpressionLiteral -> {
36+
val expressionType = when (behavior.type) {
37+
ExpressionLiteralType.Filter -> MapperTypes.Expressions.BooleanExpr
38+
ExpressionLiteralType.KeyCondition -> MapperTypes.Expressions.KeyFilter
39+
40+
// TODO add support for other expression types
41+
else -> return@mapNotNull null
42+
}.nullable(nullable)
43+
44+
val dslInfo = when (behavior.type) {
45+
ExpressionLiteralType.Filter -> DslInfo(
46+
interfaceType = MapperTypes.Expressions.Filter,
47+
implType = MapperTypes.Expressions.Internal.FilterImpl,
48+
implSingleton = true,
49+
)
50+
51+
// KeyCondition doesn't use a top-level DSL (SortKeyCondition is nested)
52+
ExpressionLiteralType.KeyCondition -> null
53+
54+
// TODO add support for other expression types
55+
else -> return@mapNotNull null
56+
}
57+
58+
llMember.copy(
59+
name = llMember.name.removeSuffix("Expression"),
60+
type = expressionType,
61+
attributes = llMember.attributes + (ModelAttributes.DslInfo to dslInfo),
62+
)
63+
}
64+
3965
else -> null
4066
}
67+
68+
hlMember?.copy(attributes = hlMember.attributes + (ModelAttributes.LowLevelMember to llMember))
4169
}.toSet()
4270

4371
val hlAttributes = llStructure.attributes + (ModelAttributes.LowLevelStructure to llStructure)

hll/dynamodb-mapper/dynamodb-mapper-ops-codegen/src/main/kotlin/aws/sdk/kotlin/hll/dynamodbmapper/codegen/operations/rendering/DataTypeGenerator.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.rendering
66

77
import aws.sdk.kotlin.hll.codegen.core.CodeGenerator
88
import aws.sdk.kotlin.hll.codegen.model.*
9-
import aws.sdk.kotlin.hll.codegen.rendering.BuilderRenderer
10-
import aws.sdk.kotlin.hll.codegen.rendering.RenderContext
11-
import aws.sdk.kotlin.hll.codegen.rendering.RenderOptions
12-
import aws.sdk.kotlin.hll.codegen.rendering.Visibility
9+
import aws.sdk.kotlin.hll.codegen.rendering.*
1310
import aws.sdk.kotlin.hll.codegen.util.plus
1411

1512
/**

0 commit comments

Comments
 (0)