-
Notifications
You must be signed in to change notification settings - Fork 199
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
DataStore.save() with condition #355
Conversation
8d38c08
to
ae7e816
Compare
AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift
Outdated
Show resolved
Hide resolved
ae7e816
to
723ae70
Compare
723ae70
to
d631c28
Compare
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.
Did an first pass and left a couple of comments.
I believe the missing part here is since the condition needs to be sent to the Sync Engine later on, we will need to change the MutationEvent
to save the condition as well. This will require a bit of work since now the conditions will need to be Codable
and converted to/from JSON when interacting with the MutationEvent
. See the existing model
in the MutationEvent
for reference.
AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift
Show resolved
Hide resolved
AmplifyTests/CategoryTests/DataStore/DataStoreCategoryConfigurationTests.swift
Show resolved
Hide resolved
...Plugins/DataStore/AWSDataStoreCategoryPluginTests/Core/SQLiteStorageEngineAdapterTests.swift
Outdated
Show resolved
Hide resolved
…uccessful responnse with graphql errors. If the graphQL error is conditional failed, then retrieve the latest remote model, and apply it to local storage.
…after possibly exeuction of ProcessMutationErrorFromCloudOperation. - Added unit test for ProcessMutationErrorFromCloudOperation
if data.count == 1, let first = data.first, case .null = first.value { | ||
let responseErrors = try decodeErrors(graphQLErrors: errors) | ||
return GraphQLResponse<R>.failure(.error(responseErrors)) | ||
} |
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.
this was added due to graphql response that looks like
{
"data": {
"updatePost": null
},
"errors": {
...
}
}
not too happy with this class, needs another pass on refactoring and better unit tests
AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift
Outdated
Show resolved
Hide resolved
- AnyModelTester class rename - unit test parallelization fix - add correct error from StorageEngine for save()
…version of QueryPredicate into JSON string
} | ||
|
||
public init<M: Model>(model: M, | ||
mutationType: MutationType, | ||
version: Int? = nil) throws { | ||
version: Int? = nil, | ||
queryPredicate: QueryPredicate? = nil) throws { |
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.
I believe the model should stay a pure and simple value holding data structure. Therefore I don't think we need this initializer nor any initializer in models should throw
. The encoding/decoding logic should happen outside, where the model is initialized (just like with the json
property)
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.
this code looks a bit outdated, it should be taking in queryPredicateJson: String? = nil
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.
it is now updated to graphQLFilterJSON
Amplify/Categories/DataStore/Subscribe/MutationEvent+Schema.swift
Outdated
Show resolved
Hide resolved
extension String { | ||
|
||
/// Deserialize the JSON string converted with `QueryPredicate.toGraphQLFilterString()` to `[String: Any]` JSON | ||
public func toGraphQLFilterJson() throws -> [String: Any] { |
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.
This should not be a public extension.
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.
I believe we've been using JSON
instead of Json
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.
This should not be a public extension.
isn't this public for DataStore to turn it back into GraphQLFilter? that is unless we change the GraphQLRequest extension methods to take in graphQLFilterJSON String and then internally do graphQLFilterJSON.toGraphQLFiilter()
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.
My point is that this is a generic String -> JSON function, added as an extension to String. Also the semantics are not entire correct. For example: "{a:1}". toGraphQLFilterJson()
would return a valid JSON as [String: Any]
but it's not a valid GraphQL filter.
Therefore, if the extension is toJSON()
, that could be valid. Then the discussion is if we should expose that extension to customers or not (it seems outside of the scope of Amplify to add such extension).
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.
taking a look at this to see if we can reduce the access level or at least move it under a static function of a different class rather than extension on String
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.
i agree, the string extension is incorrect in this case, and is prone to errors, and don't think we want to take on the toJSON()
implementation. Updated to GraphQLFilterConverter
with static methods for turning into a GraphQLFilter JSON and back to a GraphQLFilter
func toGraphQLFilterJSON() throws -> String { | ||
func toGraphQLFilterPrettyPrintedJson() throws -> String { |
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.
minor: if you have only one toJSON
function, I think if it's pretty printed or not can be omitted from the function name
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.
this was to differentiate between the code's toGraphQLFilterJSON and test code's toGraphQLFilterJSON which does pretty print for test case assertion, i think what we can do here is in actual code, have optional options and allow test to leverage the parameter by passing in options = [.prettyPrinted]
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.
not sure I understand in actual code, have optional options
. If in tests it's always pretty printed, then no options should be needed. Do the extensions clash? Is that why you need another toJSON function?
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 the reason for the different naming in test code is the clashing of method names. i wanted toGraphQLFilterJSON with pretty print options set because our test relies on asserting against a hand crafted pretty printed version of the graphql filter json. "actual code" i meant the production code
@@ -15,6 +15,7 @@ public protocol DataStoreBaseBehavior { | |||
|
|||
/// Saves the model to storage. If sync is enabled, also initiates a sync of the mutation to the remote API |
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.
this method just got a new layer of complexity, not need to address it right now but I believe we need to add proper API documentation to DataStore in general. I'll create an issue to track it.
AmplifyPlugins/Core/AWSPluginsCore/Model/Decorator/PredicateDecorator.swift
Outdated
Show resolved
Hide resolved
0357ad7
to
27a75e8
Compare
27a75e8
to
8977e88
Compare
… error naming, removed QueryPredicateInput, updated MutationEvent's field as graphQLFiilterJSON
8977e88
to
f09e606
Compare
AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift
Outdated
Show resolved
Hide resolved
AmplifyPlugins/Core/AWSPluginsCore/Model/GraphQLRequest/GraphQLRequest+AnyModelWithSync.swift
Outdated
Show resolved
Hide resolved
Tracking issue regarding dead code: #370 |
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.
great job man, thanks for the thoughtful work and follow up :)
/\/\
/\/\
O O O
O
__|__
|| ||_____
|| || |
--------------------------
\ O O O O /
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Description of changes:
This adds the
condition
argument to the DataStore.save() for local storage.Scenario 1: the local store does not contain model, and there is a condition pased in, fail with conditional save failed.
Scenario 2: the local store contains the model, but the condition does not match the existing instance, fail with conditional save failed
Scenario 3: the local storage contains the model, and the condition matches the existing instance, update the model with the provided fields with the
condition
in the where clause, preventing any race condition between checkiing that the condition matches existing instance and actually updating it.Details
AnyQueryPredicate
for encoding array of predicates, and custom Codable forQueryOperator
.queryPredicateJson
in MutationEvent to allow decoding toQueryPredicate
for 1. sending API requests with condition input on create/update mutations, and 2. handle conditional mutations in theAWSMutationDatabaseAdapter: MutationEventIngester
.AWSMutationDatabaseAdapter
resolves conflicts for incoming candidate mutation events with conditions by saving the candidate. (does not drop/replace local with candidate, etc)ProcessMutationErrorFromCloudOperation
to check errors for "conditional failed event", query for latest, and storage it back to local.TODO:
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.