Skip to content

Commit 73ede6e

Browse files
authored
fix(datastore): delete model with predicate (#1030)
* fix(datastore): delete model with predicate * refactor DeleteInput
1 parent d0674f0 commit 73ede6e

File tree

41 files changed

+780
-111
lines changed

Some content is hidden

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

41 files changed

+780
-111
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ public extension DataStoreBaseBehavior {
3333
/// - Returns: A DataStorePublisher with the results of the operation
3434
func delete<M: Model>(
3535
_ modelType: M.Type,
36-
withId id: String
36+
withId id: String,
37+
where predicate: QueryPredicate? = nil
3738
) -> DataStorePublisher<Void> {
3839
Future { promise in
39-
self.delete(modelType, withId: id) { promise($0) }
40+
self.delete(modelType, withId: id, where: predicate) { promise($0) }
4041
}.eraseToAnyPublisher()
4142
}
4243

Amplify/Categories/DataStore/DataStoreCategory+Behavior.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ extension DataStoreCategory: DataStoreBaseBehavior {
3434

3535
public func delete<M: Model>(_ modelType: M.Type,
3636
withId id: String,
37+
where predicate: QueryPredicate? = nil,
3738
completion: @escaping DataStoreCallback<Void>) {
38-
plugin.delete(modelType, withId: id, completion: completion)
39+
plugin.delete(modelType, withId: id, where: predicate, completion: completion)
3940
}
4041

4142
public func start(completion: @escaping DataStoreCallback<Void>) {

Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift

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

3333
func delete<M: Model>(_ modelType: M.Type,
3434
withId id: String,
35+
where predicate: QueryPredicate?,
3536
completion: @escaping DataStoreCallback<Void>)
3637
/**
3738
Synchronization starts automatically whenever you run any DataStore operation (query(), save(), delete())

AmplifyPlugins/API/Podfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,4 @@ SPEC CHECKSUMS:
108108

109109
PODFILE CHECKSUM: 857e4ea8246683593b8a7db77014aeb7340c15a5
110110

111-
COCOAPODS: 1.9.3
111+
COCOAPODS: 1.10.0

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/AWSDataStorePlugin+DataStoreBaseBehavior.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,18 @@ extension AWSDataStorePlugin: DataStoreBaseBehavior {
110110

111111
public func delete<M: Model>(_ modelType: M.Type,
112112
withId id: String,
113+
where predicate: QueryPredicate? = nil,
113114
completion: @escaping DataStoreCallback<Void>) {
114-
delete(modelType, modelSchema: modelType.schema, withId: id, completion: completion)
115+
delete(modelType, modelSchema: modelType.schema, withId: id, where: predicate, completion: completion)
115116
}
116117

117118
public func delete<M: Model>(_ modelType: M.Type,
118119
modelSchema: ModelSchema,
119120
withId id: String,
121+
where predicate: QueryPredicate? = nil,
120122
completion: @escaping DataStoreCallback<Void>) {
121123
reinitStorageEngineIfNeeded()
122-
storageEngine.delete(modelType, modelSchema: modelSchema, withId: id) { result in
124+
storageEngine.delete(modelType, modelSchema: modelSchema, withId: id, predicate: predicate) { result in
123125
self.onDeleteCompletion(result: result, modelSchema: modelSchema, completion: completion)
124126
}
125127
}
@@ -135,8 +137,10 @@ extension AWSDataStorePlugin: DataStoreBaseBehavior {
135137
where predicate: QueryPredicate? = nil,
136138
completion: @escaping DataStoreCallback<Void>) {
137139
reinitStorageEngineIfNeeded()
138-
// TODO: handle query predicate like in the update flow
139-
storageEngine.delete(type(of: model), modelSchema: modelSchema, withId: model.id) { result in
140+
storageEngine.delete(type(of: model),
141+
modelSchema: modelSchema,
142+
withId: model.id,
143+
predicate: predicate) { result in
140144
self.onDeleteCompletion(result: result, modelSchema: modelSchema, completion: completion)
141145
}
142146
}

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/ModelStorageBehavior.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ protocol ModelStorageBehavior {
2222
func delete<M: Model>(_ modelType: M.Type,
2323
modelSchema: ModelSchema,
2424
withId id: Model.Identifier,
25+
predicate: QueryPredicate?,
2526
completion: @escaping DataStoreCallback<M?>)
2627

2728
func delete<M: Model>(_ modelType: M.Type,

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/SQLStatement+Delete.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ struct DeleteStatement: SQLStatement {
2929
self.conditionStatement = conditionStatement
3030
}
3131

32-
init(modelSchema: ModelSchema, withId id: Model.Identifier) {
33-
self.init(modelSchema: modelSchema, predicate: field("id") == id)
32+
init(modelSchema: ModelSchema, withId id: Model.Identifier, predicate: QueryPredicate? = nil) {
33+
var queryPredicate: QueryPredicate = field("id").eq(id)
34+
if let predicate = predicate {
35+
queryPredicate = field("id").eq(id).and(predicate)
36+
}
37+
self.init(modelSchema: modelSchema, predicate: queryPredicate)
3438
}
3539

3640
init(model: Model) {

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,9 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
173173
func delete<M: Model>(_ modelType: M.Type,
174174
modelSchema: ModelSchema,
175175
withId id: Model.Identifier,
176+
predicate: QueryPredicate? = nil,
176177
completion: (DataStoreResult<M?>) -> Void) {
177-
delete(untypedModelType: modelType, modelSchema: modelSchema, withId: id) { result in
178+
delete(untypedModelType: modelType, modelSchema: modelSchema, withId: id, predicate: predicate) { result in
178179
switch result {
179180
case .success:
180181
completion(.success(nil))
@@ -187,9 +188,10 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
187188
func delete(untypedModelType modelType: Model.Type,
188189
modelSchema: ModelSchema,
189190
withId id: Model.Identifier,
191+
predicate: QueryPredicate? = nil,
190192
completion: DataStoreCallback<Void>) {
191193
do {
192-
let statement = DeleteStatement(modelSchema: modelSchema, withId: id)
194+
let statement = DeleteStatement(modelSchema: modelSchema, withId: id, predicate: predicate)
193195
_ = try connection.prepare(statement.stringValue).run(statement.variables)
194196
completion(.emptyResult)
195197
} catch {

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine+DeleteTransaction.swift

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,28 @@ import Foundation
1111
import AWSPluginsCore
1212

1313
extension StorageEngine {
14+
15+
enum DeleteInput {
16+
case withId(id: Model.Identifier)
17+
case withIdAndPredicate(id: Model.Identifier, predicate: QueryPredicate)
18+
case withPredicate(predicate: QueryPredicate)
19+
20+
/// Returns a computed predicate based on the type of delete scenario it is.
21+
var predicate: QueryPredicate {
22+
switch self {
23+
case .withId(let id):
24+
return field("id").eq(id)
25+
case .withIdAndPredicate(let id, let predicate):
26+
return field("id").eq(id).and(predicate)
27+
case .withPredicate(let predicate):
28+
return predicate
29+
}
30+
}
31+
}
32+
1433
func queryAndDeleteTransaction<M: Model>(_ modelType: M.Type,
1534
modelSchema: ModelSchema,
16-
predicate: QueryPredicate) -> DataStoreResult<([M], [Model])> {
35+
deleteInput: DeleteInput) -> DataStoreResult<([M], [Model])> {
1736
var queriedResult: DataStoreResult<[M]>?
1837
var deletedResult: DataStoreResult<[M]>?
1938
var associatedModels: [Model] = []
@@ -23,23 +42,47 @@ extension StorageEngine {
2342
guard case .success(let queriedModels) = queryResult else {
2443
return
2544
}
45+
46+
guard !queriedModels.isEmpty else {
47+
guard case .withIdAndPredicate(let id, _) = deleteInput else {
48+
// Query did not return any results, treat this as a successful no-op delete.
49+
deletedResult = .success([M]())
50+
return
51+
}
52+
53+
// Query using the computed predicate did not return any results, check if model actually exists.
54+
do {
55+
if try self.storageAdapter.exists(modelSchema, withId: id, predicate: nil) {
56+
queriedResult = .failure(
57+
DataStoreError.invalidCondition(
58+
"Delete failed due to condition did not match existing model instance.",
59+
"Subsequent deletes will continue to fail until the model instance is updated."))
60+
} else {
61+
deletedResult = .success([M]())
62+
}
63+
} catch {
64+
queriedResult = .failure(DataStoreError.invalidOperation(causedBy: error))
65+
}
66+
67+
return
68+
}
69+
2670
let modelIds = queriedModels.map {$0.id}
2771
associatedModels = self.recurseQueryAssociatedModels(modelSchema: modelSchema, ids: modelIds)
28-
2972
let deleteCompletionWrapper: DataStoreCallback<[M]> = { deleteResult in
3073
deletedResult = deleteResult
3174
}
3275
self.storageAdapter.delete(modelType,
3376
modelSchema: modelSchema,
34-
predicate: predicate,
77+
predicate: deleteInput.predicate,
3578
completion: deleteCompletionWrapper)
3679
}
3780

3881
do {
3982
try storageAdapter.transaction {
4083
storageAdapter.query(modelType,
4184
modelSchema: modelSchema,
42-
predicate: predicate,
85+
predicate: deleteInput.predicate,
4386
sort: nil,
4487
paginationInput: nil,
4588
completion: queryCompletionBlock)
@@ -48,7 +91,9 @@ extension StorageEngine {
4891
return .failure(causedBy: error)
4992
}
5093

51-
return collapseResults(queryResult: queriedResult, deleteResult: deletedResult, associatedModels: associatedModels)
94+
return collapseResults(queryResult: queriedResult,
95+
deleteResult: deletedResult,
96+
associatedModels: associatedModels)
5297
}
5398

5499
func recurseQueryAssociatedModels(modelSchema: ModelSchema, ids: [Model.Identifier]) -> [Model] {

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/StorageEngine.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,15 @@ final class StorageEngine: StorageEngineBehavior {
204204
func delete<M: Model>(_ modelType: M.Type,
205205
modelSchema: ModelSchema,
206206
withId id: Model.Identifier,
207+
predicate: QueryPredicate? = nil,
207208
completion: @escaping (DataStoreResult<M?>) -> Void) {
209+
var deleteInput = DeleteInput.withId(id: id)
210+
if let predicate = predicate {
211+
deleteInput = .withIdAndPredicate(id: id, predicate: predicate)
212+
}
208213
let transactionResult = queryAndDeleteTransaction(modelType,
209214
modelSchema: modelSchema,
210-
predicate: field("id").eq(id))
215+
deleteInput: deleteInput)
211216
let modelsFromTransactionResult = collapseMResult(transactionResult)
212217
let associatedModelsFromTransactionResult = resolveAssociatedModels(transactionResult)
213218

@@ -264,7 +269,9 @@ final class StorageEngine: StorageEngineBehavior {
264269
modelSchema: ModelSchema,
265270
predicate: QueryPredicate,
266271
completion: @escaping DataStoreCallback<[M]>) {
267-
let transactionResult = queryAndDeleteTransaction(modelType, modelSchema: modelSchema, predicate: predicate)
272+
let transactionResult = queryAndDeleteTransaction(modelType,
273+
modelSchema: modelSchema,
274+
deleteInput: .withPredicate(predicate: predicate))
268275
let modelsFromTransactionResult = collapseMResult(transactionResult)
269276
let associatedModelsFromTransactionResult = resolveAssociatedModels(transactionResult)
270277

0 commit comments

Comments
 (0)