Skip to content

Commit 0b1f97e

Browse files
authored
feat(DataStore): DataStore.delete(modelType:where:) API (#1723)
* feat(DataStore): DataStore.delete(modelType:where:) API * update integration test
1 parent c373a99 commit 0b1f97e

File tree

8 files changed

+210
-3
lines changed

8 files changed

+210
-3
lines changed

Amplify/Categories/DataStore/DataStoreCategory+Behavior+Combine.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,22 @@ public extension DataStoreBaseBehavior {
4141
}.eraseToAnyPublisher()
4242
}
4343

44+
/// Deletes models matching the `predicate` from the DataStore. If sync is enabled, this will delete the
45+
/// model from the remote store as well.
46+
///
47+
/// - Parameters:
48+
/// - modelType: The type of the model to delete
49+
/// - predicate: The models that match this predicate to be deleted
50+
/// - Returns: A DataStorePublisher with the results of the operation
51+
func delete<M: Model>(
52+
_ modelType: M.Type,
53+
where predicate: QueryPredicate
54+
) -> DataStorePublisher<Void> {
55+
Future { promise in
56+
self.delete(modelType, where: predicate) { promise($0) }
57+
}.eraseToAnyPublisher()
58+
}
59+
4460
/// Deletes the specified model instance from the DataStore. If sync is enabled, this will delete the
4561
/// model from the remote store as well.
4662
///

Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ extension DataStoreCategory: DataStoreBaseBehavior {
3939
plugin.delete(modelType, withId: id, where: predicate, completion: completion)
4040
}
4141

42+
public func delete<M: Model>(_ modelType: M.Type,
43+
where predicate: QueryPredicate,
44+
completion: @escaping DataStoreCallback<Void>) {
45+
plugin.delete(modelType, where: predicate, completion: completion)
46+
}
47+
4248
public func start(completion: @escaping DataStoreCallback<Void>) {
4349
plugin.start(completion: completion)
4450
}

Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public protocol DataStoreBaseBehavior {
3434
withId id: String,
3535
where predicate: QueryPredicate?,
3636
completion: @escaping DataStoreCallback<Void>)
37+
38+
func delete<M: Model>(_ modelType: M.Type,
39+
where predicate: QueryPredicate,
40+
completion: @escaping DataStoreCallback<Void>)
41+
3742
/**
3843
Synchronization starts automatically whenever you run any DataStore operation (query(), save(), delete())
3944
however, you can explicitly begin the process with DatasStore.start()

AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/Connection/DataStoreConnectionScenario6Tests.swift

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import XCTest
99
import AmplifyPlugins
10-
import AWSMobileClient
10+
import Combine
1111

1212
@testable import Amplify
1313
@testable import AmplifyTestCommon
@@ -39,6 +39,7 @@ import AWSMobileClient
3939
```
4040
*/
4141

42+
@available(iOS 13.0, *)
4243
class DataStoreConnectionScenario6Tests: SyncEngineIntegrationTestBase {
4344

4445
func testGetBlogThenFetchPostsThenFetchComments() throws {
@@ -203,6 +204,103 @@ class DataStoreConnectionScenario6Tests: SyncEngineIntegrationTestBase {
203204
}
204205
}
205206

207+
func testDeleteAll() throws {
208+
try startAmplifyAndWaitForReady()
209+
var cancellables = Set<AnyCancellable>()
210+
let remoteEventReceived = expectation(description: "received mutation event with version 1")
211+
let commentId = UUID().uuidString
212+
Amplify.DataStore.publisher(for: Comment6.self).sink { completion in
213+
switch completion {
214+
case .finished:
215+
break
216+
case .failure(let error):
217+
XCTFail("Failed \(error)")
218+
}
219+
} receiveValue: { mutationEvent in
220+
if mutationEvent.modelId == commentId && mutationEvent.version == 1 {
221+
remoteEventReceived.fulfill()
222+
}
223+
}.store(in: &cancellables)
224+
guard let blog = saveBlog(name: "name"),
225+
let post = savePost(title: "title", blog: blog),
226+
saveComment(id: commentId, post: post, content: "content") != nil else {
227+
XCTFail("Could not create blog, post, and comment")
228+
return
229+
}
230+
wait(for: [remoteEventReceived], timeout: 5)
231+
232+
var blogCount = 0
233+
let retrievedBlogCount = expectation(description: "retrieved blog count")
234+
Amplify.DataStore.query(Blog6.self) { result in
235+
switch result {
236+
case .success(let blogs):
237+
blogCount = blogs.count
238+
retrievedBlogCount.fulfill()
239+
case .failure(let error):
240+
XCTFail("\(error)")
241+
}
242+
}
243+
wait(for: [retrievedBlogCount], timeout: 10)
244+
var postCount = 0
245+
let retrievedPostCount = expectation(description: "retrieved post count")
246+
Amplify.DataStore.query(Post6.self) { result in
247+
switch result {
248+
case .success(let posts):
249+
postCount = posts.count
250+
retrievedPostCount.fulfill()
251+
case .failure(let error):
252+
XCTFail("\(error)")
253+
}
254+
}
255+
wait(for: [retrievedPostCount], timeout: 10)
256+
257+
var commentCount = 0
258+
let retrievedCommentCount = expectation(description: "retrieved comment count")
259+
Amplify.DataStore.query(Comment6.self) { result in
260+
switch result {
261+
case .success(let comments):
262+
commentCount = comments.count
263+
retrievedCommentCount.fulfill()
264+
case .failure(let error):
265+
XCTFail("\(error)")
266+
}
267+
}
268+
269+
wait(for: [retrievedCommentCount], timeout: 10)
270+
271+
let totalCount = blogCount + postCount + commentCount
272+
Amplify.Logging.verbose("Retrieved blog \(blogCount) post \(postCount) comment \(commentCount)")
273+
274+
let outboxMutationProcessed = expectation(description: "received outboxMutationProcessed")
275+
var processedSoFar = 0
276+
Amplify.Hub.publisher(for: .dataStore)
277+
.sink { payload in
278+
let event = DataStoreHubEvent(payload: payload)
279+
switch event {
280+
case .outboxMutationProcessed:
281+
processedSoFar += 1
282+
print("Processed so far \(processedSoFar)")
283+
if processedSoFar == totalCount {
284+
outboxMutationProcessed.fulfill()
285+
}
286+
default:
287+
break
288+
}
289+
}.store(in: &cancellables)
290+
291+
let deleteSuccess = expectation(description: "Delete all successful")
292+
Amplify.DataStore.delete(Blog6.self, where: QueryPredicateConstant.all) { result in
293+
switch result {
294+
case .success:
295+
deleteSuccess.fulfill()
296+
case .failure(let error):
297+
XCTFail("\(error)")
298+
}
299+
}
300+
301+
wait(for: [deleteSuccess, outboxMutationProcessed], timeout: 10)
302+
}
303+
206304
func saveBlog(id: String = UUID().uuidString, name: String) -> Blog6? {
207305
let blog = Blog6(id: id, name: name)
208306
var result: Blog6?

AmplifyPlugins/DataStore/AWSDataStoreCategoryPluginIntegrationTests/DataStoreLocalStoreTests.swift

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,78 @@ class DataStoreLocalStoreTests: LocalStoreIntegrationTestBase {
421421
sink.cancel()
422422
}
423423

424+
func testDeleteModelTypeWithPredicate() {
425+
_ = setUpLocalStore(numberOfPosts: 5)
426+
let queryOnSetUpSuccess = expectation(description: "query returns non-empty result")
427+
Amplify.DataStore.query(Post.self, where: Post.keys.status.eq(PostStatus.draft)) { result in
428+
switch result {
429+
case .success(let posts):
430+
XCTAssertFalse(posts.isEmpty)
431+
queryOnSetUpSuccess.fulfill()
432+
case .failure(let error):
433+
XCTFail("\(error)")
434+
}
435+
}
436+
wait(for: [queryOnSetUpSuccess], timeout: 1)
437+
let deleteSuccess = expectation(description: "Delete all successful")
438+
Amplify.DataStore.delete(Post.self, where: Post.keys.status.eq(PostStatus.draft)) { result in
439+
switch result {
440+
case .success:
441+
deleteSuccess.fulfill()
442+
case .failure(let error):
443+
XCTFail("\(error)")
444+
}
445+
}
446+
wait(for: [deleteSuccess], timeout: 1)
447+
448+
let queryComplete = expectation(description: "query returns empty result")
449+
Amplify.DataStore.query(Post.self, where: Post.keys.status.eq(PostStatus.draft)) { result in
450+
switch result {
451+
case .success(let posts):
452+
XCTAssertEqual(posts.count, 0)
453+
queryComplete.fulfill()
454+
case .failure(let error):
455+
XCTFail("\(error)")
456+
}
457+
}
458+
wait(for: [queryComplete], timeout: 1)
459+
}
460+
461+
func testDeleteAll() {
462+
_ = setUpLocalStore(numberOfPosts: 5)
463+
let deleteSuccess = expectation(description: "Delete all successful")
464+
Amplify.DataStore.delete(Post.self, where: QueryPredicateConstant.all) { result in
465+
switch result {
466+
case .success:
467+
deleteSuccess.fulfill()
468+
case .failure(let error):
469+
XCTFail("\(error)")
470+
}
471+
}
472+
wait(for: [deleteSuccess], timeout: 1)
473+
474+
let queryComplete = expectation(description: "query returns empty result")
475+
Amplify.DataStore.query(Post.self) { result in
476+
switch result {
477+
case .success(let posts):
478+
XCTAssertEqual(posts.count, 0)
479+
queryComplete.fulfill()
480+
case .failure(let error):
481+
XCTFail("\(error)")
482+
}
483+
}
484+
wait(for: [queryComplete], timeout: 1)
485+
}
486+
424487
func setUpLocalStore(numberOfPosts: Int) -> [Post] {
425488
var savedPosts = [Post]()
426489
for id in 0 ..< numberOfPosts {
427490
let saveSuccess = expectation(description: "Save post completed")
428491
let post = Post(title: "title\(Int.random(in: 0 ... 5))",
429492
content: "content",
430493
createdAt: .now(),
431-
rating: Double(Int.random(in: 0 ... 5)))
494+
rating: Double(Int.random(in: 0 ... 5)),
495+
status: .draft)
432496
savedPosts.append(post)
433497
print("\(id) \(post.id)")
434498
Amplify.DataStore.save(post) { result in

AmplifyTestCommon/Mocks/MockAuthCategoryPlugin.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class MockAuthCategoryPlugin: MessageReporter, AuthCategoryPlugin {
6969
listener: AuthSignOutOperation.ResultListener?) -> AuthSignOutOperation {
7070
fatalError()
7171
}
72-
72+
7373
public func deleteUser(listener: AuthDeleteUserOperation.ResultListener?) -> AuthDeleteUserOperation {
7474
fatalError()
7575
}

AmplifyTestCommon/Mocks/MockDataStoreCategoryPlugin.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,18 @@ class MockDataStoreCategoryPlugin: MessageReporter, DataStoreCategoryPlugin {
8080
}
8181
}
8282

83+
func delete<M: Model>(_ modelType: M.Type,
84+
where predicate: QueryPredicate,
85+
completion: (DataStoreResult<Void>) -> Void) {
86+
notify("deleteModelTypeByPredicate")
87+
88+
if let responder = responders[.deleteModelTypeListener] as? DeleteModelTypeResponder<M> {
89+
if let callback = responder.callback((modelType: modelType, where: predicate)) {
90+
completion(callback)
91+
}
92+
}
93+
}
94+
8395
func delete<M: Model>(_ model: M,
8496
where predicate: QueryPredicate? = nil,
8597
completion: @escaping DataStoreCallback<Void>) {

AmplifyTestCommon/Mocks/MockDataStoreResponders.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ extension MockDataStoreCategoryPlugin {
1313
case queryByIdListener
1414
case queryModelsListener
1515
case deleteByIdListener
16+
case deleteModelTypeListener
1617
case deleteModelListener
1718
case clearListener
1819
case startListener
@@ -39,6 +40,11 @@ typealias DeleteByIdResponder<M: Model> = MockResponder<
3940
DataStoreResult<Void>?
4041
>
4142

43+
typealias DeleteModelTypeResponder<M: Model> = MockResponder<
44+
(modelType: M.Type, where: QueryPredicate),
45+
DataStoreResult<Void>?
46+
>
47+
4248
typealias DeleteModelResponder<M: Model> = MockResponder<
4349
(model: M, where: QueryPredicate?),
4450
DataStoreResult<Void>?

0 commit comments

Comments
 (0)