Skip to content

Commit

Permalink
DataStore.save() with condition (#355)
Browse files Browse the repository at this point in the history
* Adding condition to save()

* Adding condition to save()

* Add predicate to mutation event

* QueryPredicate Codable conformance

* add ingestor disposition logic

* test for conditional request failed

* Add ProcessMutationErrorFromCloudOperation to check when there is a successful responnse with graphql errors. If the graphQL error is conditional failed, then retrieve the latest remote model, and apply it to local storage.

* Adding simple AnyQueryPredicate test

* - Moved delete mutation event out of SyncMutationToCloudOperation to after possibly exeuction of ProcessMutationErrorFromCloudOperation.
- Added unit test for ProcessMutationErrorFromCloudOperation

* Add E2E test for conditional save failed

* minor changes
- AnyModelTester class rename
- unit test parallelization fix
- add correct error from StorageEngine for save()

* Removed Codable conformance for QueryPredicate and serialize graphQL version of QueryPredicate into JSON string

* remove fetch and save when processing conditional save failed

* clean up, api tests, plugin core tests

* minor comment update

* Code clean up - use GraphQLFilter typealias, 'JSON' casing, DataStore error naming, removed QueryPredicateInput, updated MutationEvent's field as graphQLFiilterJSON

* rename parameter to `filter`

* Move GraphQLFilter serialization under GraphQLFilterConverter
  • Loading branch information
lawmicha authored Apr 3, 2020
1 parent 1c2bd06 commit 5bd3e65
Show file tree
Hide file tree
Showing 56 changed files with 1,179 additions and 188 deletions.
22 changes: 17 additions & 5 deletions Amplify.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@
212CE70523E9E967007D8E71 /* GraphQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70223E9E967007D8E71 /* GraphQLQuery.swift */; };
212CE70B23E9E991007D8E71 /* ModelIdDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */; };
212CE70C23E9E991007D8E71 /* ConflictResolutionDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */; };
212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70823E9E990007D8E71 /* PredicateDecorator.swift */; };
212CE70D23E9E991007D8E71 /* FilterDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70823E9E990007D8E71 /* FilterDecorator.swift */; };
212CE70E23E9E991007D8E71 /* PaginationDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70923E9E991007D8E71 /* PaginationDecorator.swift */; };
212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70A23E9E991007D8E71 /* ModelDecorator.swift */; };
212CE71123E9EA6A007D8E71 /* ModelField+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */; };
212CE71323E9F2ED007D8E71 /* DirectiveNameDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71223E9F2ED007D8E71 /* DirectiveNameDecorator.swift */; };
213481D9242AFA62001966DE /* AnyModelTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213481D8242AFA62001966DE /* AnyModelTester.swift */; };
21409C552384C55D000A53C9 /* LabelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C542384C55D000A53C9 /* LabelType.swift */; };
21409C5A2384C57D000A53C9 /* GraphQLMutationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C572384C57D000A53C9 /* GraphQLMutationType.swift */; };
21409C5B2384C57D000A53C9 /* GraphQLQueryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C582384C57D000A53C9 /* GraphQLQueryType.swift */; };
Expand Down Expand Up @@ -568,7 +569,7 @@
212CE70223E9E967007D8E71 /* GraphQLQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLQuery.swift; sourceTree = "<group>"; };
212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelIdDecorator.swift; sourceTree = "<group>"; };
212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConflictResolutionDecorator.swift; sourceTree = "<group>"; };
212CE70823E9E990007D8E71 /* PredicateDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredicateDecorator.swift; sourceTree = "<group>"; };
212CE70823E9E990007D8E71 /* FilterDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDecorator.swift; sourceTree = "<group>"; };
212CE70923E9E991007D8E71 /* PaginationDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationDecorator.swift; sourceTree = "<group>"; };
212CE70A23E9E991007D8E71 /* ModelDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelDecorator.swift; sourceTree = "<group>"; };
212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelField+GraphQL.swift"; sourceTree = "<group>"; };
Expand All @@ -580,6 +581,7 @@
212CE71B23EA1847007D8E71 /* GraphQLListQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLListQueryTests.swift; sourceTree = "<group>"; };
212CE71C23EA1847007D8E71 /* GraphQLSyncQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSyncQueryTests.swift; sourceTree = "<group>"; };
212CE72023EA184F007D8E71 /* GraphQLSubscriptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSubscriptionTests.swift; sourceTree = "<group>"; };
213481D8242AFA62001966DE /* AnyModelTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyModelTester.swift; sourceTree = "<group>"; };
21409C4C23847E41000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = "<group>"; };
21409C542384C55D000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = "<group>"; };
21409C572384C57D000A53C9 /* GraphQLMutationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLMutationType.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1338,11 +1340,19 @@
212CE70A23E9E991007D8E71 /* ModelDecorator.swift */,
212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */,
212CE70923E9E991007D8E71 /* PaginationDecorator.swift */,
212CE70823E9E990007D8E71 /* PredicateDecorator.swift */,
212CE70823E9E990007D8E71 /* FilterDecorator.swift */,
);
path = Decorator;
sourceTree = "<group>";
};
213481D7242AFA58001966DE /* Support */ = {
isa = PBXGroup;
children = (
213481D8242AFA62001966DE /* AnyModelTester.swift */,
);
path = Support;
sourceTree = "<group>";
};
21409C562384C57D000A53C9 /* GraphQL */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2574,10 +2584,11 @@
FAD3937B23820CE200463F5E /* DataStore */ = {
isa = PBXGroup;
children = (
213481D7242AFA58001966DE /* Support */,
FA8EE772238621320097E4F1 /* AnyModelTests.swift */,
FAE414602399A6A500CE94C2 /* ModelRegistryTests.swift */,
FAD3937923820CDB00463F5E /* DataStoreCategoryClientAPITests.swift */,
FAD3937C23820D0200463F5E /* DataStoreCategoryConfigurationTests.swift */,
FAE414602399A6A500CE94C2 /* ModelRegistryTests.swift */,
);
path = DataStore;
sourceTree = "<group>";
Expand Down Expand Up @@ -3482,7 +3493,7 @@
21420A91237222A900FA140C /* AWSAuthorizationConfiguration.swift in Sources */,
212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */,
21420A97237222A900FA140C /* IAMCredentialProvider.swift in Sources */,
212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */,
212CE70D23E9E991007D8E71 /* FilterDecorator.swift in Sources */,
219A888123EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift in Sources */,
212CE70423E9E967007D8E71 /* GraphQLSubscription.swift in Sources */,
21420A99237222A900FA140C /* APIKeyProvider.swift in Sources */,
Expand Down Expand Up @@ -3829,6 +3840,7 @@
FA47B8362350C2D60031A0E3 /* AutoUnsubscribeOperationTests.swift in Sources */,
FAC2356E227A056600424678 /* AnalyticsCategoryClientAPITests.swift in Sources */,
B4BD6B3323708C0000A1F0A7 /* PredictionsCategoryClientAPITests.swift in Sources */,
213481D9242AFA62001966DE /* AnyModelTester.swift in Sources */,
FA47B8382350C58B0031A0E3 /* AutoUnsubscribeHubListenToOperationTests.swift in Sources */,
FAC23574227A056B00424678 /* ConfigurationTests.swift in Sources */,
B9DCA263240F217C00075E22 /* AnyEncodableTests.swift in Sources */,
Expand Down
6 changes: 4 additions & 2 deletions Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
//

extension DataStoreCategory: DataStoreBaseBehavior {
public func save<M: Model>(_ model: M, completion: @escaping DataStoreCallback<M>) {
plugin.save(model, completion: completion)
public func save<M: Model>(_ model: M,
where condition: QueryPredicate? = nil,
completion: @escaping DataStoreCallback<M>) {
plugin.save(model, where: condition, completion: completion)
}

public func query<M: Model>(_ modelType: M.Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public extension HubPayload.EventName.DataStore {
/// who are interested in model updates must be notified in any case of a sync received. The HubPayload will be a
/// `MutationEvent` instance containing the newly mutated data from the remote API.
static let syncReceived = "DataStore.syncReceived"

/// Dispatched when DataStore receives a sync response from the remote API via the API category. The Hub Payload
/// will be a `MutationEvent` instance that caused the conditional save failed.
static let conditionalSaveFailed = "DataStore.conditionalSaveFailed"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
func save<M: Model>(_ model: M,
where condition: QueryPredicate?,
completion: @escaping DataStoreCallback<M>)

func query<M: Model>(_ modelType: M.Type,
Expand Down
4 changes: 4 additions & 0 deletions Amplify/Categories/DataStore/DataStoreError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum DataStoreError: Error {
case api(AmplifyError)
case configuration(ErrorDescription, RecoverySuggestion, Error? = nil)
case conflict(DataStoreSyncConflict)
case invalidCondition(ErrorDescription, RecoverySuggestion, Error? = nil)
case decodingError(ErrorDescription, RecoverySuggestion)
case internalOperation(ErrorDescription, RecoverySuggestion, Error? = nil)
case invalidDatabase(path: String, Error? = nil)
Expand Down Expand Up @@ -45,6 +46,7 @@ extension DataStoreError: AmplifyError {
Only a single result was expected and the actual count was \(count).
"""
case .configuration(let errorDescription, _, _),
.invalidCondition(let errorDescription, _, _),
.decodingError(let errorDescription, _),
.internalOperation(let errorDescription, _, _),
.sync(let errorDescription, _, _),
Expand Down Expand Up @@ -72,6 +74,7 @@ extension DataStoreError: AmplifyError {
as unique indexes and primary keys.
"""
case .configuration(_, let recoverySuggestion, _),
.invalidCondition(_, let recoverySuggestion, _),
.decodingError(_, let recoverySuggestion),
.internalOperation(_, let recoverySuggestion, _),
.sync(_, let recoverySuggestion, _),
Expand All @@ -85,6 +88,7 @@ extension DataStoreError: AmplifyError {
case .api(let amplifyError):
return amplifyError
case .configuration(_, _, let underlyingError),
.invalidCondition(_, _, let underlyingError),
.internalOperation(_, _, let underlyingError),
.invalidDatabase(_, let underlyingError),
.invalidOperation(let underlyingError),
Expand Down
1 change: 0 additions & 1 deletion Amplify/Categories/DataStore/Query/QueryPredicate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public enum QueryPredicateConstant: QueryPredicate {
}

public class QueryPredicateGroup: QueryPredicate {

public internal(set) var type: QueryPredicateGroupType
public internal(set) var predicates: [QueryPredicate]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension MutationEvent {
case createdAt
case version
case inProcess
case graphQLFilterJSON
}

public static let keys = CodingKeys.self
Expand All @@ -37,7 +38,8 @@ extension MutationEvent {
.field(mutation.mutationType, is: .required, ofType: .string),
.field(mutation.createdAt, is: .required, ofType: .dateTime),
.field(mutation.version, is: .optional, ofType: .int),
.field(mutation.inProcess, is: .optional, ofType: .bool)
.field(mutation.inProcess, is: .required, ofType: .bool),
.field(mutation.graphQLFilterJSON, is: .optional, ofType: .string)
)
}
}
12 changes: 9 additions & 3 deletions Amplify/Categories/DataStore/Subscribe/MutationEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public struct MutationEvent: Model {
public var createdAt: Date
public var version: Int?
public var inProcess: Bool
public var graphQLFilterJSON: String?

public init(id: Identifier = UUID().uuidString,
modelId: String,
Expand All @@ -24,7 +25,8 @@ public struct MutationEvent: Model {
mutationType: MutationType,
createdAt: Date = Date(),
version: Int? = nil,
inProcess: Bool = false) {
inProcess: Bool = false,
graphQLFilterJSON: String? = nil) {
self.id = id
self.modelId = modelId
self.modelName = modelName
Expand All @@ -33,18 +35,22 @@ public struct MutationEvent: Model {
self.createdAt = createdAt
self.version = version
self.inProcess = inProcess
self.graphQLFilterJSON = graphQLFilterJSON
}

public init<M: Model>(model: M,
mutationType: MutationType,
version: Int? = nil) throws {
version: Int? = nil,
graphQLFilterJSON: String? = nil) throws {
let modelType = type(of: model)
let json = try model.toJSON()
self.init(modelId: model.id,
modelName: modelType.schema.name,
json: json,
mutationType: mutationType,
version: version)
version: version,
graphQLFilterJSON: graphQLFilterJSON)

}

public func decodeModel() throws -> Model {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ struct GraphQLResponseDecoder {

case (.some(let data), .some(let errors)):
do {
if data.count == 1, let first = data.first, case .null = first.value {
let responseErrors = try decodeErrors(graphQLErrors: errors)
return GraphQLResponse<R>.failure(.error(responseErrors))
}

let jsonValue = JSONValue.object(data)
let responseData = try decode(graphQLData: jsonValue,
into: responseType,
Expand Down
Loading

0 comments on commit 5bd3e65

Please sign in to comment.