-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DDB Mapper filter expressions (codegen components) #1424
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,12 @@ | |
*/ | ||
package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model | ||
|
||
import aws.sdk.kotlin.hll.codegen.model.Member | ||
import aws.sdk.kotlin.hll.codegen.model.TypeRef | ||
import aws.sdk.kotlin.hll.codegen.model.Types | ||
import aws.sdk.kotlin.hll.codegen.model.nullable | ||
import aws.sdk.kotlin.hll.codegen.model.* | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperPkg | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes | ||
|
||
private val attrMapTypes = setOf(MapperTypes.AttributeMap, MapperTypes.AttributeMap.nullable()) | ||
private val attrMapListTypes = Types.Kotlin.list(MapperTypes.AttributeMap).let { setOf(it, it.nullable()) } | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ExpressionArgumentsType.* | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.ExpressionLiteralType.* | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model.MemberCodegenBehavior.* | ||
|
||
/** | ||
* 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 { | |
* structure). | ||
*/ | ||
data object Hoist : MemberCodegenBehavior | ||
|
||
/** | ||
* Indicates that a member is a string expression parameter which should be replaced by an expression DSL | ||
* @param type The type of expression this member models | ||
*/ | ||
data class ExpressionLiteral(val type: ExpressionLiteralType) : MemberCodegenBehavior | ||
|
||
/** | ||
* Indicates that a member is a map of expression arguments which should be automatically handled by an expression | ||
* DSL | ||
* @param type The type of expression arguments this member models | ||
*/ | ||
data class ExpressionArguments(val type: ExpressionArgumentsType) : MemberCodegenBehavior | ||
} | ||
|
||
/** | ||
* Identifies a type of expression literal supported by DynamoDB APIs | ||
*/ | ||
internal enum class ExpressionLiteralType { | ||
Condition, | ||
Filter, | ||
KeyCondition, | ||
Projection, | ||
Update, | ||
} | ||
|
||
/** | ||
* Identifies a type of expression arguments supported by DynamoDB APIs | ||
*/ | ||
internal enum class ExpressionArgumentsType { | ||
AttributeNames, | ||
AttributeValues, | ||
} | ||
|
||
/** | ||
* Identifies a [MemberCodegenBehavior] for this [Member] by way of various heuristics | ||
*/ | ||
internal val Member.codegenBehavior: MemberCodegenBehavior | ||
get() = when { | ||
this in unsupportedMembers -> MemberCodegenBehavior.Drop | ||
type in attrMapTypes -> if (name == "key") MemberCodegenBehavior.MapKeys else MemberCodegenBehavior.MapAll | ||
type in attrMapListTypes -> MemberCodegenBehavior.ListMapAll | ||
isTableName || isIndexName -> MemberCodegenBehavior.Hoist | ||
else -> MemberCodegenBehavior.PassThrough | ||
} | ||
get() = rules.firstNotNullOfOrNull { it.matchedBehaviorOrNull(this) } ?: PassThrough | ||
|
||
private val Member.isTableName: Boolean | ||
get() = name == "tableName" && type == Types.Kotlin.StringNullable | ||
private fun llType(name: String) = TypeRef(MapperPkg.Ll.Model, name) | ||
|
||
private val Member.isIndexName: Boolean | ||
get() = name == "indexName" && type == Types.Kotlin.StringNullable | ||
private data class Rule( | ||
val namePredicate: (String) -> Boolean, | ||
val typePredicate: (TypeRef) -> Boolean, | ||
val behavior: MemberCodegenBehavior, | ||
) { | ||
constructor(name: String, type: TypeRef, behavior: MemberCodegenBehavior) : | ||
this(name::equals, type::isEquivalentTo, behavior) | ||
|
||
private fun llType(name: String) = TypeRef(MapperPkg.Ll.Model, name) | ||
constructor(name: Regex, type: TypeRef, behavior: MemberCodegenBehavior) : | ||
this(name::matches, type::isEquivalentTo, behavior) | ||
|
||
private val unsupportedMembers = listOf( | ||
// superseded by ConditionExpression | ||
Member("conditionalOperator", llType("ConditionalOperator")), | ||
Member("expected", Types.Kotlin.stringMap(llType("ExpectedAttributeValue"))), | ||
|
||
// superseded by FilterExpression | ||
Member("queryFilter", Types.Kotlin.stringMap(llType("Condition"))), | ||
Member("scanFilter", Types.Kotlin.stringMap(llType("Condition"))), | ||
|
||
// superseded by KeyConditionExpression | ||
Member("keyConditions", Types.Kotlin.stringMap(llType("Condition"))), | ||
|
||
// superseded by ProjectionExpression | ||
Member("attributesToGet", Types.Kotlin.list(Types.Kotlin.String)), | ||
|
||
// superseded by UpdateExpression | ||
Member("attributeUpdates", Types.Kotlin.stringMap(llType("AttributeValueUpdate"))), | ||
|
||
// TODO add support for expressions | ||
Member("expressionAttributeNames", Types.Kotlin.stringMap(Types.Kotlin.String)), | ||
Member("expressionAttributeValues", MapperTypes.AttributeMap), | ||
Member("conditionExpression", Types.Kotlin.String), | ||
Member("projectionExpression", Types.Kotlin.String), | ||
Member("updateExpression", Types.Kotlin.String), | ||
).map { member -> | ||
if (member.type is TypeRef) { | ||
member.copy(type = member.type.nullable()) | ||
} else { | ||
member | ||
} | ||
}.toSet() | ||
fun matchedBehaviorOrNull(member: Member) = if (matches(member)) behavior else null | ||
fun matches(member: Member) = namePredicate(member.name) && typePredicate(member.type as TypeRef) | ||
} | ||
|
||
private fun Type.isEquivalentTo(other: Type): Boolean = when (this) { | ||
is TypeVar -> other is TypeVar && shortName == other.shortName | ||
is TypeRef -> | ||
other is TypeRef && | ||
fullName == other.fullName && | ||
genericArgs.size == other.genericArgs.size && | ||
genericArgs.zip(other.genericArgs).all { (thisArg, otherArg) -> thisArg.isEquivalentTo(otherArg) } | ||
} | ||
|
||
/** | ||
* Priority-ordered list of dispositions to apply to members found in structures. The first element from this list that | ||
* successfully matches with a member will be chosen. | ||
*/ | ||
private val rules = listOf( | ||
// Deprecated expression members not to be carried forward into HLL | ||
Rule("conditionalOperator", llType("ConditionalOperator"), Drop), | ||
Rule("expected", Types.Kotlin.stringMap(llType("ExpectedAttributeValue")), Drop), | ||
Rule("queryFilter", Types.Kotlin.stringMap(llType("Condition")), Drop), | ||
Rule("scanFilter", Types.Kotlin.stringMap(llType("Condition")), Drop), | ||
Rule("keyConditions", Types.Kotlin.stringMap(llType("Condition")), Drop), | ||
Rule("attributesToGet", Types.Kotlin.list(Types.Kotlin.String), Drop), | ||
Rule("attributeUpdates", Types.Kotlin.stringMap(llType("AttributeValueUpdate")), Drop), | ||
|
||
// Hoisted members | ||
Rule("tableName", Types.Kotlin.String, Hoist), | ||
Rule("indexName", Types.Kotlin.String, Hoist), | ||
|
||
// Expression literals | ||
Rule("keyConditionExpression", Types.Kotlin.String, ExpressionLiteral(KeyCondition)), | ||
Rule("filterExpression", Types.Kotlin.String, ExpressionLiteral(Filter)), | ||
|
||
// TODO add support for remaining expression types | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: // TODO add support for remaining expression types
// Expression types There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The expression types which follow the |
||
Rule("conditionExpression", Types.Kotlin.String, Drop), | ||
Rule("projectionExpression", Types.Kotlin.String, Drop), | ||
Rule("updateExpression", Types.Kotlin.String, Drop), | ||
|
||
// Expression arguments | ||
Rule("expressionAttributeNames", Types.Kotlin.stringMap(Types.Kotlin.String), ExpressionArguments(AttributeNames)), | ||
Rule("expressionAttributeValues", MapperTypes.AttributeMap, ExpressionArguments(AttributeValues)), | ||
|
||
// Mappable members | ||
Rule(".*".toRegex(), Types.Kotlin.list(MapperTypes.AttributeMap), ListMapAll), | ||
Rule("key", MapperTypes.AttributeMap, MapKeys), | ||
Rule(".*".toRegex(), MapperTypes.AttributeMap, MapAll), | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,13 +6,7 @@ package aws.sdk.kotlin.hll.dynamodbmapper.codegen.operations.model | |
|
||
import aws.sdk.kotlin.hll.codegen.model.* | ||
import aws.sdk.kotlin.hll.codegen.util.plus | ||
import aws.smithy.kotlin.runtime.collections.get | ||
|
||
/** | ||
* Gets the low-level [Structure] equivalent for this high-level structure | ||
*/ | ||
internal val Structure.lowLevel: Structure | ||
get() = attributes[ModelAttributes.LowLevelStructure] | ||
import aws.sdk.kotlin.hll.dynamodbmapper.codegen.model.MapperTypes | ||
|
||
/** | ||
* Derives a high-level [Structure] equivalent for this low-level structure | ||
|
@@ -24,20 +18,54 @@ internal fun Structure.toHighLevel(pkg: String): Structure { | |
val hlType = TypeRef(pkg, llStructure.type.shortName, listOf(TypeVar("T"))) | ||
|
||
val hlMembers = llStructure.members.mapNotNull { llMember -> | ||
when (llMember.codegenBehavior) { | ||
MemberCodegenBehavior.PassThrough -> llMember | ||
val nullable = llMember.type.nullable | ||
|
||
val hlMember = when (val behavior = llMember.codegenBehavior) { | ||
MemberCodegenBehavior.PassThrough, is MemberCodegenBehavior.ExpressionArguments -> llMember | ||
|
||
MemberCodegenBehavior.MapAll, MemberCodegenBehavior.MapKeys -> | ||
llMember.copy(type = TypeVar("T", llMember.type.nullable)) | ||
llMember.copy(type = TypeVar("T", nullable)) | ||
|
||
MemberCodegenBehavior.ListMapAll -> { | ||
val llListType = llMember.type as? TypeRef ?: error("`ListMapAll` member is required to be a TypeRef") | ||
val hlListType = llListType.copy(genericArgs = listOf(TypeVar("T")), nullable = llListType.nullable) | ||
val hlListType = llListType.copy(genericArgs = listOf(TypeVar("T"))) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: Just double checking that this doesn't require nullable anymore There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It never did require setting |
||
llMember.copy(type = hlListType) | ||
} | ||
|
||
is MemberCodegenBehavior.ExpressionLiteral -> { | ||
val expressionType = when (behavior.type) { | ||
ExpressionLiteralType.Filter -> MapperTypes.Expressions.BooleanExpr | ||
ExpressionLiteralType.KeyCondition -> MapperTypes.Expressions.KeyFilter | ||
|
||
// TODO add support for other expression types | ||
else -> return@mapNotNull null | ||
}.nullable(nullable) | ||
|
||
val dslInfo = when (behavior.type) { | ||
ExpressionLiteralType.Filter -> DslInfo( | ||
interfaceType = MapperTypes.Expressions.Filter, | ||
implType = MapperTypes.Expressions.Internal.FilterImpl, | ||
implSingleton = true, | ||
) | ||
|
||
// KeyCondition doesn't use a top-level DSL (SortKeyCondition is nested) | ||
ExpressionLiteralType.KeyCondition -> null | ||
|
||
// TODO add support for other expression types | ||
else -> return@mapNotNull null | ||
} | ||
|
||
llMember.copy( | ||
name = llMember.name.removeSuffix("Expression"), | ||
type = expressionType, | ||
attributes = llMember.attributes + (ModelAttributes.DslInfo to dslInfo), | ||
) | ||
} | ||
|
||
else -> null | ||
} | ||
|
||
hlMember?.copy(attributes = hlMember.attributes + (ModelAttributes.LowLevelMember to llMember)) | ||
}.toSet() | ||
|
||
val hlAttributes = llStructure.attributes + (ModelAttributes.LowLevelStructure to llStructure) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is there a difference here between
Hl.Base
andBase
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes there is,
Base
refers to the value defined on L26.Hl.Base
is defined on L13