amplify-swift
amplify-swift copied to clipboard
API QueryPredicate `Foo.keys.bar == nil` unexpected behavior
Describe the bug
Given a schema:
type Post @model {
id: ID!
title: String!
status: PostStatus!
rating: Int
content: String
}
enum PostStatus {
DRAFT
PUBLISHED
}
let predicate = Post.keys.content == nil
Amplify.DataStore.query(Post.self, where: predicate) { _ in ... }
will return Post
s where content is nil
.
let predicate = Post.keys.content == nil
Amplify.API.query(request: .list(Post.self, where: predicate)) { _ in ... }
returns no results, despite Post
s existing where content is nil
.
This inconsistent behavior is confusing.
Steps To Reproduce
Steps to reproduce the behavior:
1. Setup project with API (and DataStore for comparison in behavior)
2. Use schema defined above or any schema with optional properties.
3. Create and save models with `nil` for that optional property.
4. Query with a `QueryPredicate` looking for models where that property is `nil`
Expected behavior
Querying with the predicatePost.keys.rating == nil
with API
should return Post
s where the rating is nil
/ doesn't exist.
Amplify Framework Version
1.24.0
Amplify Categories
API
Dependency manager
Swift PM
Swift version
5.6
CLI version
8.1.0
Xcode version
13.3.1
Relevant log output
No response
Is this a regression?
No
Regression additional context
No response
Device
n/a
iOS Version
n/a
Specific to simulators
No response
Additional context
No response
DataStore APIs interact with SQLite database directly so the predicate is translated to the SQL expression, while the predicate for APIPlugin APIs interact with the Transformer provisioned AppSync service. We can do two things next in the investigation on AppSync use cases:
- Construct a request that adds a filter on
null
using AppSync console's Queries tab, is it possible? does it work as expected? - Write a unit test that generates the GraphQL documennt and variables based on the predicate
let predicate = Post.keys.content == nil
and see if that matches a valid request
It appears as though AppSync supports this through attributeExists
.
query MyQuery {
listPosts(filter: {content: {attributeExists: false}}) {
items {
content
createdAt
id
rating
status
title
updatedAt
}
}
}
Looks related: https://github.com/aws-amplify/amplify-js/issues/5179
I believe the implementation lives in the translation from the prediate to GraphQL input, we need to map the equal operation to attributeExists : false
when the value is null
I'm not certain we should add a mapping. In JS a predicate builder is not used and the filter is written out manually. Since the filter on JS is the exact same that is sent over the GraphQL query I don't think it makes sense to add the mapping for JS. So it may be confusing when seemingly the same filter will have different functionality across different platforms.
let predicate = Post.keys.content == nil
Amplify.API.query(request: .list(Post.self, where: predicate))
// maps to =>
{ content: { attributeExists: false } }
const filter = { content: { eq: null } };
await API.graphql({ query: listPosts, variables: { filter }});
// isn't modified
{ content: { eq: null } }
We have discussed this issue some and the solution will likely be to update the VTL in AppSync. AppSync API VTL will modify { eq: null }
filters to use { attributeExists: false }
. This will provide identical functionality across platforms.
Thanks @dpilch, where can this change be requested and tracked?
I haven't created a ticket. I'll create one soon in amplify-category-api and link back here.
Closing this issue, the fix will be in the api category and can be tracked here - https://github.com/aws-amplify/amplify-category-api/issues/943