Skip to content

Commit 5bd3e65

Browse files
authored
DataStore.save() with condition (#355)
* 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
1 parent 1c2bd06 commit 5bd3e65

File tree

56 files changed

+1179
-188
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1179
-188
lines changed

Amplify.xcodeproj/project.pbxproj

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,12 @@
4444
212CE70523E9E967007D8E71 /* GraphQLQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70223E9E967007D8E71 /* GraphQLQuery.swift */; };
4545
212CE70B23E9E991007D8E71 /* ModelIdDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */; };
4646
212CE70C23E9E991007D8E71 /* ConflictResolutionDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */; };
47-
212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70823E9E990007D8E71 /* PredicateDecorator.swift */; };
47+
212CE70D23E9E991007D8E71 /* FilterDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70823E9E990007D8E71 /* FilterDecorator.swift */; };
4848
212CE70E23E9E991007D8E71 /* PaginationDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70923E9E991007D8E71 /* PaginationDecorator.swift */; };
4949
212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE70A23E9E991007D8E71 /* ModelDecorator.swift */; };
5050
212CE71123E9EA6A007D8E71 /* ModelField+GraphQL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */; };
5151
212CE71323E9F2ED007D8E71 /* DirectiveNameDecorator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 212CE71223E9F2ED007D8E71 /* DirectiveNameDecorator.swift */; };
52+
213481D9242AFA62001966DE /* AnyModelTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = 213481D8242AFA62001966DE /* AnyModelTester.swift */; };
5253
21409C552384C55D000A53C9 /* LabelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C542384C55D000A53C9 /* LabelType.swift */; };
5354
21409C5A2384C57D000A53C9 /* GraphQLMutationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C572384C57D000A53C9 /* GraphQLMutationType.swift */; };
5455
21409C5B2384C57D000A53C9 /* GraphQLQueryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21409C582384C57D000A53C9 /* GraphQLQueryType.swift */; };
@@ -568,7 +569,7 @@
568569
212CE70223E9E967007D8E71 /* GraphQLQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLQuery.swift; sourceTree = "<group>"; };
569570
212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelIdDecorator.swift; sourceTree = "<group>"; };
570571
212CE70723E9E990007D8E71 /* ConflictResolutionDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConflictResolutionDecorator.swift; sourceTree = "<group>"; };
571-
212CE70823E9E990007D8E71 /* PredicateDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PredicateDecorator.swift; sourceTree = "<group>"; };
572+
212CE70823E9E990007D8E71 /* FilterDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterDecorator.swift; sourceTree = "<group>"; };
572573
212CE70923E9E991007D8E71 /* PaginationDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaginationDecorator.swift; sourceTree = "<group>"; };
573574
212CE70A23E9E991007D8E71 /* ModelDecorator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelDecorator.swift; sourceTree = "<group>"; };
574575
212CE71023E9EA6A007D8E71 /* ModelField+GraphQL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelField+GraphQL.swift"; sourceTree = "<group>"; };
@@ -580,6 +581,7 @@
580581
212CE71B23EA1847007D8E71 /* GraphQLListQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLListQueryTests.swift; sourceTree = "<group>"; };
581582
212CE71C23EA1847007D8E71 /* GraphQLSyncQueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSyncQueryTests.swift; sourceTree = "<group>"; };
582583
212CE72023EA184F007D8E71 /* GraphQLSubscriptionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLSubscriptionTests.swift; sourceTree = "<group>"; };
584+
213481D8242AFA62001966DE /* AnyModelTester.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyModelTester.swift; sourceTree = "<group>"; };
583585
21409C4C23847E41000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = "<group>"; };
584586
21409C542384C55D000A53C9 /* LabelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelType.swift; sourceTree = "<group>"; };
585587
21409C572384C57D000A53C9 /* GraphQLMutationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphQLMutationType.swift; sourceTree = "<group>"; };
@@ -1338,11 +1340,19 @@
13381340
212CE70A23E9E991007D8E71 /* ModelDecorator.swift */,
13391341
212CE70623E9E990007D8E71 /* ModelIdDecorator.swift */,
13401342
212CE70923E9E991007D8E71 /* PaginationDecorator.swift */,
1341-
212CE70823E9E990007D8E71 /* PredicateDecorator.swift */,
1343+
212CE70823E9E990007D8E71 /* FilterDecorator.swift */,
13421344
);
13431345
path = Decorator;
13441346
sourceTree = "<group>";
13451347
};
1348+
213481D7242AFA58001966DE /* Support */ = {
1349+
isa = PBXGroup;
1350+
children = (
1351+
213481D8242AFA62001966DE /* AnyModelTester.swift */,
1352+
);
1353+
path = Support;
1354+
sourceTree = "<group>";
1355+
};
13461356
21409C562384C57D000A53C9 /* GraphQL */ = {
13471357
isa = PBXGroup;
13481358
children = (
@@ -2574,10 +2584,11 @@
25742584
FAD3937B23820CE200463F5E /* DataStore */ = {
25752585
isa = PBXGroup;
25762586
children = (
2587+
213481D7242AFA58001966DE /* Support */,
25772588
FA8EE772238621320097E4F1 /* AnyModelTests.swift */,
2578-
FAE414602399A6A500CE94C2 /* ModelRegistryTests.swift */,
25792589
FAD3937923820CDB00463F5E /* DataStoreCategoryClientAPITests.swift */,
25802590
FAD3937C23820D0200463F5E /* DataStoreCategoryConfigurationTests.swift */,
2591+
FAE414602399A6A500CE94C2 /* ModelRegistryTests.swift */,
25812592
);
25822593
path = DataStore;
25832594
sourceTree = "<group>";
@@ -3482,7 +3493,7 @@
34823493
21420A91237222A900FA140C /* AWSAuthorizationConfiguration.swift in Sources */,
34833494
212CE70F23E9E991007D8E71 /* ModelDecorator.swift in Sources */,
34843495
21420A97237222A900FA140C /* IAMCredentialProvider.swift in Sources */,
3485-
212CE70D23E9E991007D8E71 /* PredicateDecorator.swift in Sources */,
3496+
212CE70D23E9E991007D8E71 /* FilterDecorator.swift in Sources */,
34863497
219A888123EB629800BBC5F2 /* ModelBasedGraphQLDocumentDecorator.swift in Sources */,
34873498
212CE70423E9E967007D8E71 /* GraphQLSubscription.swift in Sources */,
34883499
21420A99237222A900FA140C /* APIKeyProvider.swift in Sources */,
@@ -3829,6 +3840,7 @@
38293840
FA47B8362350C2D60031A0E3 /* AutoUnsubscribeOperationTests.swift in Sources */,
38303841
FAC2356E227A056600424678 /* AnalyticsCategoryClientAPITests.swift in Sources */,
38313842
B4BD6B3323708C0000A1F0A7 /* PredictionsCategoryClientAPITests.swift in Sources */,
3843+
213481D9242AFA62001966DE /* AnyModelTester.swift in Sources */,
38323844
FA47B8382350C58B0031A0E3 /* AutoUnsubscribeHubListenToOperationTests.swift in Sources */,
38333845
FAC23574227A056B00424678 /* ConfigurationTests.swift in Sources */,
38343846
B9DCA263240F217C00075E22 /* AnyEncodableTests.swift in Sources */,

Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
//
77

88
extension DataStoreCategory: DataStoreBaseBehavior {
9-
public func save<M: Model>(_ model: M, completion: @escaping DataStoreCallback<M>) {
10-
plugin.save(model, completion: completion)
9+
public func save<M: Model>(_ model: M,
10+
where condition: QueryPredicate? = nil,
11+
completion: @escaping DataStoreCallback<M>) {
12+
plugin.save(model, where: condition, completion: completion)
1113
}
1214

1315
public func query<M: Model>(_ modelType: M.Type,

Amplify/Categories/DataStore/DataStoreCategory+HubPayloadEventName.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,8 @@ public extension HubPayload.EventName.DataStore {
1919
/// who are interested in model updates must be notified in any case of a sync received. The HubPayload will be a
2020
/// `MutationEvent` instance containing the newly mutated data from the remote API.
2121
static let syncReceived = "DataStore.syncReceived"
22+
23+
/// Dispatched when DataStore receives a sync response from the remote API via the API category. The Hub Payload
24+
/// will be a `MutationEvent` instance that caused the conditional save failed.
25+
static let conditionalSaveFailed = "DataStore.conditionalSaveFailed"
2226
}

Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public protocol DataStoreBaseBehavior {
1515

1616
/// Saves the model to storage. If sync is enabled, also initiates a sync of the mutation to the remote API
1717
func save<M: Model>(_ model: M,
18+
where condition: QueryPredicate?,
1819
completion: @escaping DataStoreCallback<M>)
1920

2021
func query<M: Model>(_ modelType: M.Type,

Amplify/Categories/DataStore/DataStoreError.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum DataStoreError: Error {
1313
case api(AmplifyError)
1414
case configuration(ErrorDescription, RecoverySuggestion, Error? = nil)
1515
case conflict(DataStoreSyncConflict)
16+
case invalidCondition(ErrorDescription, RecoverySuggestion, Error? = nil)
1617
case decodingError(ErrorDescription, RecoverySuggestion)
1718
case internalOperation(ErrorDescription, RecoverySuggestion, Error? = nil)
1819
case invalidDatabase(path: String, Error? = nil)
@@ -45,6 +46,7 @@ extension DataStoreError: AmplifyError {
4546
Only a single result was expected and the actual count was \(count).
4647
"""
4748
case .configuration(let errorDescription, _, _),
49+
.invalidCondition(let errorDescription, _, _),
4850
.decodingError(let errorDescription, _),
4951
.internalOperation(let errorDescription, _, _),
5052
.sync(let errorDescription, _, _),
@@ -72,6 +74,7 @@ extension DataStoreError: AmplifyError {
7274
as unique indexes and primary keys.
7375
"""
7476
case .configuration(_, let recoverySuggestion, _),
77+
.invalidCondition(_, let recoverySuggestion, _),
7578
.decodingError(_, let recoverySuggestion),
7679
.internalOperation(_, let recoverySuggestion, _),
7780
.sync(_, let recoverySuggestion, _),
@@ -85,6 +88,7 @@ extension DataStoreError: AmplifyError {
8588
case .api(let amplifyError):
8689
return amplifyError
8790
case .configuration(_, _, let underlyingError),
91+
.invalidCondition(_, _, let underlyingError),
8892
.internalOperation(_, _, let underlyingError),
8993
.invalidDatabase(_, let underlyingError),
9094
.invalidOperation(let underlyingError),

Amplify/Categories/DataStore/Query/QueryPredicate.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public enum QueryPredicateConstant: QueryPredicate {
3131
}
3232

3333
public class QueryPredicateGroup: QueryPredicate {
34-
3534
public internal(set) var type: QueryPredicateGroupType
3635
public internal(set) var predicates: [QueryPredicate]
3736

Amplify/Categories/DataStore/Subscribe/MutationEvent+Schema.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ extension MutationEvent {
1717
case createdAt
1818
case version
1919
case inProcess
20+
case graphQLFilterJSON
2021
}
2122

2223
public static let keys = CodingKeys.self
@@ -37,7 +38,8 @@ extension MutationEvent {
3738
.field(mutation.mutationType, is: .required, ofType: .string),
3839
.field(mutation.createdAt, is: .required, ofType: .dateTime),
3940
.field(mutation.version, is: .optional, ofType: .int),
40-
.field(mutation.inProcess, is: .optional, ofType: .bool)
41+
.field(mutation.inProcess, is: .required, ofType: .bool),
42+
.field(mutation.graphQLFilterJSON, is: .optional, ofType: .string)
4143
)
4244
}
4345
}

Amplify/Categories/DataStore/Subscribe/MutationEvent.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public struct MutationEvent: Model {
1616
public var createdAt: Date
1717
public var version: Int?
1818
public var inProcess: Bool
19+
public var graphQLFilterJSON: String?
1920

2021
public init(id: Identifier = UUID().uuidString,
2122
modelId: String,
@@ -24,7 +25,8 @@ public struct MutationEvent: Model {
2425
mutationType: MutationType,
2526
createdAt: Date = Date(),
2627
version: Int? = nil,
27-
inProcess: Bool = false) {
28+
inProcess: Bool = false,
29+
graphQLFilterJSON: String? = nil) {
2830
self.id = id
2931
self.modelId = modelId
3032
self.modelName = modelName
@@ -33,18 +35,22 @@ public struct MutationEvent: Model {
3335
self.createdAt = createdAt
3436
self.version = version
3537
self.inProcess = inProcess
38+
self.graphQLFilterJSON = graphQLFilterJSON
3639
}
3740

3841
public init<M: Model>(model: M,
3942
mutationType: MutationType,
40-
version: Int? = nil) throws {
43+
version: Int? = nil,
44+
graphQLFilterJSON: String? = nil) throws {
4145
let modelType = type(of: model)
4246
let json = try model.toJSON()
4347
self.init(modelId: model.id,
4448
modelName: modelType.schema.name,
4549
json: json,
4650
mutationType: mutationType,
47-
version: version)
51+
version: version,
52+
graphQLFilterJSON: graphQLFilterJSON)
53+
4854
}
4955

5056
public func decodeModel() throws -> Model {

AmplifyPlugins/API/AWSAPICategoryPlugin/Support/Utils/GraphQLResponseDecoder.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ struct GraphQLResponseDecoder {
5353

5454
case (.some(let data), .some(let errors)):
5555
do {
56+
if data.count == 1, let first = data.first, case .null = first.value {
57+
let responseErrors = try decodeErrors(graphQLErrors: errors)
58+
return GraphQLResponse<R>.failure(.error(responseErrors))
59+
}
60+
5661
let jsonValue = JSONValue.object(data)
5762
let responseData = try decode(graphQLData: jsonValue,
5863
into: responseType,

0 commit comments

Comments
 (0)