Skip to content

Commit ed16e42

Browse files
authored
fix(datastore): Query nested model causes column not found sql error (#761)
* Reformat source code * fix(datastore): Query nested model causes column not found sql error * Optimize method interface to clarify the purpose * Optimize method interface * Added relationship check ensure the field conversion happens only for BelongsTo
1 parent 784581d commit ed16e42

File tree

5 files changed

+141
-60
lines changed

5 files changed

+141
-60
lines changed

packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/AmplifyDataStorePlugin.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ class AmplifyDataStorePlugin : FlutterPlugin, MethodCallHandler {
214214
var queryOptions: QueryOptions
215215
try {
216216
modelName = request["modelName"] as String
217-
queryOptions = QueryOptionsBuilder.fromSerializedMap(request)
217+
val modelSchema = modelProvider.modelSchemas().getValue(modelName)
218+
queryOptions = QueryOptionsBuilder.fromSerializedMap(request, modelSchema)
218219
} catch (e: Exception) {
219220
handler.post {
220221
postExceptionToFlutterChannel(

packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/query/QueryOptionsBuilder.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package com.amazonaws.amplify.amplify_datastore.types.query
1717

1818
import com.amazonaws.amplify.amplify_datastore.util.safeCastToList
1919
import com.amazonaws.amplify.amplify_datastore.util.safeCastToMap
20+
import com.amplifyframework.core.model.ModelSchema
2021
import com.amplifyframework.core.model.query.QueryOptions
2122
import com.amplifyframework.core.model.query.QueryPaginationInput
2223
import com.amplifyframework.core.model.query.QuerySortBy
@@ -27,17 +28,21 @@ class QueryOptionsBuilder {
2728

2829
companion object {
2930
@JvmStatic
30-
fun fromSerializedMap(request: Map<String, Any>?): QueryOptions {
31+
fun fromSerializedMap(request: Map<String, Any>?, modelSchema: ModelSchema): QueryOptions {
3132
var queryOptions: QueryOptions = Where.matchesAll()
3233
if (request == null) {
3334
return queryOptions
3435
}
3536
var queryPredicate: QueryPredicate? = QueryPredicateBuilder.fromSerializedMap(
36-
request["queryPredicate"].safeCastToMap())
37+
request["queryPredicate"].safeCastToMap(),
38+
modelSchema
39+
)
3740
var querySortInput: List<QuerySortBy>? = QuerySortBuilder.fromSerializedList(
38-
request["querySort"].safeCastToList())
41+
request["querySort"].safeCastToList()
42+
)
3943
var queryPagination: QueryPaginationInput? = QueryPaginationBuilder.fromSerializedMap(
40-
request["queryPagination"].safeCastToMap())
44+
request["queryPagination"].safeCastToMap()
45+
)
4146

4247
if (queryPredicate != null) {
4348
queryOptions = queryOptions.matches(queryPredicate)

packages/amplify_datastore/android/src/main/kotlin/com/amazonaws/amplify/amplify_datastore/types/query/QueryPredicateBuilder.kt

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ package com.amazonaws.amplify.amplify_datastore.types.query
1717

1818
import com.amazonaws.amplify.amplify_datastore.util.safeCastToList
1919
import com.amazonaws.amplify.amplify_datastore.util.safeCastToMap
20+
import com.amplifyframework.core.model.ModelSchema
21+
import com.amplifyframework.core.model.annotations.BelongsTo
2022
import com.amplifyframework.core.model.query.predicate.QueryField
2123
import com.amplifyframework.core.model.query.predicate.QueryPredicate
2224
import com.amplifyframework.core.model.query.predicate.QueryPredicateGroup
@@ -25,17 +27,31 @@ import com.amplifyframework.core.model.query.predicate.QueryPredicateOperation.n
2527

2628
class QueryPredicateBuilder {
2729
companion object {
30+
/**
31+
* Use only within [QueryOptionsBuilder]
32+
*/
2833
@JvmStatic
29-
fun fromSerializedMap(serializedMap: Map<String, Any>?): QueryPredicate? {
34+
fun fromSerializedMap(serializedMap: Map<String, Any>?, modelSchema: ModelSchema?): QueryPredicate? {
3035
if (serializedMap == null) {
3136
return null
3237
}
3338

3439
if (serializedMap.containsKey("queryPredicateOperation")) {
35-
val queryPredicateOperationMap: Map<String, Any> = serializedMap["queryPredicateOperation"].safeCastToMap()!!
36-
val field = queryPredicateOperationMap["field"] as String
40+
val queryPredicateOperationMap: Map<String, Any> =
41+
serializedMap["queryPredicateOperation"].safeCastToMap()!!
42+
var field = queryPredicateOperationMap["field"] as String
43+
44+
if (modelSchema?.associations?.containsKey(field) == true) {
45+
val association = modelSchema.associations.getValue(field);
46+
47+
if (BelongsTo::class.java.simpleName.equals(association.name)) {
48+
field = modelSchema.associations.getValue(field).targetName
49+
}
50+
}
51+
3752
val queryField: QueryField = QueryField.field(field)
38-
val queryFieldOperatorMap: Map<String, Any> = queryPredicateOperationMap["fieldOperator"].safeCastToMap()!!
53+
val queryFieldOperatorMap: Map<String, Any> =
54+
queryPredicateOperationMap["fieldOperator"].safeCastToMap()!!
3955
val operand: Any? = queryFieldOperatorMap["value"]
4056
when (queryFieldOperatorMap["operatorName"]) {
4157
"equal" -> return queryField.eq(operand)
@@ -46,33 +62,34 @@ class QueryPredicateBuilder {
4662
"greater_than" -> return queryField.gt(operand as Comparable<Any?>?)
4763
"contains" -> return queryField.contains(operand as String)
4864
"between" -> return queryField.between(
49-
queryFieldOperatorMap["start"] as Comparable<Any?>?,
50-
queryFieldOperatorMap["end"] as Comparable<Any?>?)
65+
queryFieldOperatorMap["start"] as Comparable<Any?>?,
66+
queryFieldOperatorMap["end"] as Comparable<Any?>?
67+
)
5168
"begins_with" -> return queryField.beginsWith(operand as String)
5269
}
5370
}
5471

5572
if (serializedMap.containsKey("queryPredicateGroup")) {
56-
val queryPredicateGroupMap : Map<String, Any> =
57-
serializedMap["queryPredicateGroup"].safeCastToMap()!!
73+
val queryPredicateGroupMap: Map<String, Any> =
74+
serializedMap["queryPredicateGroup"].safeCastToMap()!!
5875
var predicates: List<QueryPredicate> =
59-
queryPredicateGroupMap["predicates"].safeCastToList<Map<String, Any>>()
60-
?.map { queryPredicate ->
61-
fromSerializedMap(queryPredicate)!!
62-
}!!
76+
queryPredicateGroupMap["predicates"].safeCastToList<Map<String, Any>>()
77+
?.map { queryPredicate ->
78+
fromSerializedMap(queryPredicate, modelSchema)!!
79+
}!!
6380
var resultQueryPredicate: QueryPredicateGroup? = null
6481
// The first predicate in the list is either predicateOperation or predicateGroup. We need
6582
// to know which one so that we can cast it appropriately and call the `and` or `not` method
6683
if (predicates[0] is QueryPredicateOperation<*>) {
6784
when (queryPredicateGroupMap["type"]) {
6885
"and" -> {
6986
resultQueryPredicate =
70-
(predicates[0] as QueryPredicateOperation<*>).and(predicates[1])
87+
(predicates[0] as QueryPredicateOperation<*>).and(predicates[1])
7188
predicates = predicates.drop(2)
7289
}
7390
"or" -> {
7491
resultQueryPredicate =
75-
(predicates[0] as QueryPredicateOperation<*>).or(predicates[1])
92+
(predicates[0] as QueryPredicateOperation<*>).or(predicates[1])
7693
predicates = predicates.drop(2)
7794
}
7895
"not" -> {
@@ -100,11 +117,12 @@ class QueryPredicateBuilder {
100117
// Not operator cannot contain a list of predicates, but just one.
101118
if (predicates.isNotEmpty()) {
102119
throw IllegalArgumentException(
103-
"More than one predicates added in the `not` queryPredicate operation." +
104-
" Predicates Size: " + predicates.size)
120+
"More than one predicates added in the `not` queryPredicate operation." +
121+
" Predicates Size: " + predicates.size
122+
)
105123
}
106124
resultQueryPredicate =
107-
QueryPredicateGroup.not(resultQueryPredicate as QueryPredicateGroup)
125+
QueryPredicateGroup.not(resultQueryPredicate as QueryPredicateGroup)
108126
}
109127
}
110128

@@ -113,5 +131,10 @@ class QueryPredicateBuilder {
113131

114132
return null
115133
}
134+
135+
@JvmStatic
136+
fun fromSerializedMap(serializedMap: Map<String, Any>?): QueryPredicate? {
137+
return fromSerializedMap(serializedMap, null)
138+
}
116139
}
117140
}

packages/amplify_datastore/android/src/test/kotlin/com/amazonaws/amplify/amplify_datastore/types/query/QueryPredicateBuilderTest.kt

Lines changed: 84 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
package com.amazonaws.amplify.amplify_datastore.types.query
1717

18+
import com.amazonaws.amplify.amplify_datastore.postSchema
1819
import com.amazonaws.amplify.amplify_datastore.readMapFromFile
1920
import com.amplifyframework.core.model.query.predicate.QueryField
2021
import com.amplifyframework.core.model.query.predicate.QueryPredicateGroup
@@ -27,77 +28,122 @@ class QueryPredicateBuilderTest {
2728
private val title: QueryField = QueryField.field("title")
2829
private val rating: QueryField = QueryField.field("rating")
2930
private val created: QueryField = QueryField.field("created")
31+
private val blogID: QueryField = QueryField.field("blogID")
3032

3133
@Test
3234
fun test_when_id_not_equals() {
3335
Assert.assertEquals(
34-
id.ne("123"),
35-
QueryPredicateBuilder.fromSerializedMap(
36-
readMapFromFile("query_predicate", "id_not_equals.json",
37-
HashMap::class.java) as HashMap<String, Any>))
36+
id.ne("123"),
37+
QueryPredicateBuilder.fromSerializedMap(
38+
readMapFromFile(
39+
"query_predicate", "id_not_equals.json",
40+
HashMap::class.java
41+
) as HashMap<String, Any>
42+
)
43+
)
3844
}
3945

4046
@Test
4147
fun test_when_rating_greater_or_equal() {
4248
Assert.assertEquals(
43-
rating.ge(4),
44-
QueryPredicateBuilder.fromSerializedMap(
45-
readMapFromFile("query_predicate",
46-
"rating_greater_or_equal.json",
47-
HashMap::class.java) as HashMap<String, Any>))
49+
rating.ge(4),
50+
QueryPredicateBuilder.fromSerializedMap(
51+
readMapFromFile(
52+
"query_predicate",
53+
"rating_greater_or_equal.json",
54+
HashMap::class.java
55+
) as HashMap<String, Any>
56+
)
57+
)
4858
}
4959

5060
@Test
5161
fun test_complex_nested_and_or() {
5262
Assert.assertEquals(
53-
rating.le(4).and(id.contains("abc").or(title.beginsWith("def"))),
54-
QueryPredicateBuilder.fromSerializedMap(
55-
readMapFromFile("query_predicate",
56-
"complex_nested.json",
57-
HashMap::class.java) as HashMap<String, Any>))
63+
rating.le(4).and(id.contains("abc").or(title.beginsWith("def"))),
64+
QueryPredicateBuilder.fromSerializedMap(
65+
readMapFromFile(
66+
"query_predicate",
67+
"complex_nested.json",
68+
HashMap::class.java
69+
) as HashMap<String, Any>
70+
)
71+
)
5872
}
5973

6074
@Test
6175
fun test_when_group_with_only_and() {
6276
Assert.assertEquals(
63-
rating.between(1, 4)
64-
.and(id.contains("abc"))
65-
.and(title.beginsWith("def"))
66-
.and(created.eq("2020-02-20T20:20:20-08:00")),
67-
QueryPredicateBuilder.fromSerializedMap(
68-
readMapFromFile("query_predicate",
69-
"group_with_only_and.json",
70-
HashMap::class.java) as HashMap<String, Any>))
77+
rating.between(1, 4)
78+
.and(id.contains("abc"))
79+
.and(title.beginsWith("def"))
80+
.and(created.eq("2020-02-20T20:20:20-08:00")),
81+
QueryPredicateBuilder.fromSerializedMap(
82+
readMapFromFile(
83+
"query_predicate",
84+
"group_with_only_and.json",
85+
HashMap::class.java
86+
) as HashMap<String, Any>
87+
)
88+
)
7189
}
7290

7391
@Test
7492
fun test_when_mixed_and_or() {
7593
Assert.assertEquals(
76-
rating.lt(4).and(id.contains("abc")).or(title.contains("def")),
77-
QueryPredicateBuilder.fromSerializedMap(
78-
readMapFromFile("query_predicate",
79-
"group_mixed_and_or.json",
80-
HashMap::class.java) as HashMap<String, Any>))
94+
rating.lt(4).and(id.contains("abc")).or(title.contains("def")),
95+
QueryPredicateBuilder.fromSerializedMap(
96+
readMapFromFile(
97+
"query_predicate",
98+
"group_mixed_and_or.json",
99+
HashMap::class.java
100+
) as HashMap<String, Any>
101+
)
102+
)
81103
}
82104

83105
@Test
84106
fun test_when_rating_gt_but_not_eq() {
85107
Assert.assertEquals(
86-
rating.gt(4).and(not(rating.eq(1))),
87-
QueryPredicateBuilder.fromSerializedMap(
88-
readMapFromFile("query_predicate",
89-
"mixed_with_not.json",
90-
HashMap::class.java) as HashMap<String, Any>))
108+
rating.gt(4).and(not(rating.eq(1))),
109+
QueryPredicateBuilder.fromSerializedMap(
110+
readMapFromFile(
111+
"query_predicate",
112+
"mixed_with_not.json",
113+
HashMap::class.java
114+
) as HashMap<String, Any>
115+
)
116+
)
91117
}
92118

93119
@Test
94120
fun test_when_negate_complex_predicate() {
95121
Assert.assertEquals(
96-
QueryPredicateGroup.not(
97-
rating.eq(1).and(rating.eq(4).or(title.contains("crap")))),
98-
QueryPredicateBuilder.fromSerializedMap(
99-
readMapFromFile("query_predicate",
100-
"negate_complex_predicate.json",
101-
HashMap::class.java) as HashMap<String, Any>))
122+
QueryPredicateGroup.not(
123+
rating.eq(1).and(rating.eq(4).or(title.contains("crap")))
124+
),
125+
QueryPredicateBuilder.fromSerializedMap(
126+
readMapFromFile(
127+
"query_predicate",
128+
"negate_complex_predicate.json",
129+
HashMap::class.java
130+
) as HashMap<String, Any>
131+
)
132+
)
133+
}
134+
135+
@Test
136+
fun test_lookup_query_field_from_relation() {
137+
Assert.assertEquals(
138+
blogID.eq("123"),
139+
QueryPredicateBuilder.fromSerializedMap(
140+
readMapFromFile(
141+
"query_predicate",
142+
"relation_field_lookup.json",
143+
HashMap::class.java
144+
) as HashMap<String, Any>,
145+
postSchema
146+
)
147+
)
102148
}
103149
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"queryPredicateOperation": {
3+
"field": "blog",
4+
"fieldOperator": { "operatorName": "equal", "value": "123" }
5+
}
6+
}

0 commit comments

Comments
 (0)