Skip to content

Commit 3869ec5

Browse files
authored
chore(datastore): add logging to cascadeDelete (#2553)
1 parent 18ea29e commit 3869ec5

File tree

2 files changed

+134
-83
lines changed

2 files changed

+134
-83
lines changed

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/CascadeDeleteOperation.swift

Lines changed: 133 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -29,57 +29,75 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
2929
let completionForWithId: ((DataStoreResult<M?>) -> Void)?
3030
let completionForWithFilter: ((DataStoreResult<[M]>) -> Void)?
3131

32-
private let serialQueueSyncDeletions: DispatchQueue
33-
34-
init(storageAdapter: StorageEngineAdapter,
35-
syncEngine: RemoteSyncEngineBehavior?,
36-
modelType: M.Type,
37-
modelSchema: ModelSchema,
38-
withIdentifier identifier: ModelIdentifierProtocol,
39-
condition: QueryPredicate? = nil,
40-
completion: @escaping (DataStoreResult<M?>) -> Void) {
41-
self.storageAdapter = storageAdapter
42-
self.syncEngine = syncEngine
43-
self.modelType = modelType
44-
self.modelSchema = modelSchema
45-
var deleteInput = DeleteInput.withIdentifier(id: identifier)
46-
if let condition = condition {
47-
deleteInput = .withIdentifierAndCondition(id: identifier,
48-
condition: condition)
32+
private let serialQueueSyncDeletions = DispatchQueue(label: "com.amazoncom.Storage.CascadeDeleteOperation.concurrency")
33+
private let isDeveloperDefinedModel: Bool
34+
35+
convenience init(
36+
storageAdapter: StorageEngineAdapter,
37+
syncEngine: RemoteSyncEngineBehavior?,
38+
modelType: M.Type,
39+
modelSchema: ModelSchema,
40+
withIdentifier identifier: ModelIdentifierProtocol,
41+
condition: QueryPredicate? = nil,
42+
completion: @escaping (DataStoreResult<M?>) -> Void) {
43+
44+
var deleteInput = DeleteInput.withIdentifier(id: identifier)
45+
if let condition = condition {
46+
deleteInput = .withIdentifierAndCondition(id: identifier, condition: condition)
47+
}
48+
self.init(
49+
storageAdapter: storageAdapter,
50+
syncEngine: syncEngine,
51+
modelType: modelType,
52+
modelSchema: modelSchema,
53+
deleteInput: deleteInput,
54+
completionForWithId: completion,
55+
completionForWithFilter: nil)
4956
}
50-
self.deleteInput = deleteInput
51-
self.completionForWithId = completion
52-
self.completionForWithFilter = nil
53-
self.serialQueueSyncDeletions = DispatchQueue(label: "com.amazoncom.Storage.CascadeDeleteOperation.concurrency")
54-
super.init()
55-
}
5657

57-
init(storageAdapter: StorageEngineAdapter,
58-
syncEngine: RemoteSyncEngineBehavior?,
59-
modelType: M.Type,
60-
modelSchema: ModelSchema,
61-
filter: QueryPredicate,
62-
completion: @escaping (DataStoreResult<[M]>) -> Void) {
58+
convenience init(
59+
storageAdapter: StorageEngineAdapter,
60+
syncEngine: RemoteSyncEngineBehavior?,
61+
modelType: M.Type,
62+
modelSchema: ModelSchema,
63+
filter: QueryPredicate,
64+
completion: @escaping (DataStoreResult<[M]>) -> Void) {
65+
self.init(
66+
storageAdapter: storageAdapter,
67+
syncEngine: syncEngine,
68+
modelType: modelType,
69+
modelSchema: modelSchema,
70+
deleteInput: .withFilter(filter),
71+
completionForWithId: nil,
72+
completionForWithFilter: completion
73+
)
74+
}
75+
76+
private init(storageAdapter: StorageEngineAdapter,
77+
syncEngine: RemoteSyncEngineBehavior?,
78+
modelType: M.Type,
79+
modelSchema: ModelSchema,
80+
deleteInput: DeleteInput,
81+
completionForWithId: ((DataStoreResult<M?>) -> Void)?,
82+
completionForWithFilter: ((DataStoreResult<[M]>) -> Void)?
83+
) {
6384
self.storageAdapter = storageAdapter
6485
self.syncEngine = syncEngine
6586
self.modelType = modelType
6687
self.modelSchema = modelSchema
67-
self.deleteInput = .withFilter(filter)
68-
self.completionForWithId = nil
69-
self.completionForWithFilter = completion
70-
self.serialQueueSyncDeletions = DispatchQueue(label: "com.amazoncom.Storage.CascadeDeleteOperation.concurrency")
88+
self.deleteInput = deleteInput
89+
self.completionForWithId = completionForWithId
90+
self.completionForWithFilter = completionForWithFilter
91+
self.isDeveloperDefinedModel = StorageEngine
92+
.systemModelSchemas
93+
.contains { $0.name != modelSchema.name }
7194
super.init()
7295
}
7396

7497
override public func main() {
7598
queryAndDelete()
7699
}
77100

78-
struct QueryAndDeleteResult<M: Model> {
79-
let deletedModels: [M]
80-
let associatedModels: [(ModelName, Model)]
81-
}
82-
83101
func queryAndDelete() {
84102
do {
85103
try storageAdapter.transaction {
@@ -118,8 +136,8 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
118136
// Query did not return any results, treat this as a successful no-op delete.
119137
deletedResult = .success([M]())
120138
return collapseResults(queryResult: queriedResult,
121-
deleteResult: deletedResult,
122-
associatedModels: associatedModels)
139+
deleteResult: deletedResult,
140+
associatedModels: associatedModels)
123141
}
124142

125143
// Query using the computed predicate did not return any results, check if model actually exists.
@@ -137,12 +155,13 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
137155
}
138156

139157
return collapseResults(queryResult: queriedResult,
140-
deleteResult: deletedResult,
141-
associatedModels: associatedModels)
158+
deleteResult: deletedResult,
159+
associatedModels: associatedModels)
142160
}
143161

144162
let modelIds = queriedModels.map { $0.identifier(schema: self.modelSchema).stringValue }
145-
163+
logMessage("[CascadeDelete.1] Deleting \(modelSchema.name) with identifiers: \(modelIds)")
164+
146165
associatedModels = await self.recurseQueryAssociatedModels(modelSchema: self.modelSchema, ids: modelIds)
147166

148167
deletedResult = await withCheckedContinuation { continuation in
@@ -160,28 +179,36 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
160179
func recurseQueryAssociatedModels(modelSchema: ModelSchema, ids: [String]) async -> [(ModelName, Model)] {
161180
var associatedModels: [(ModelName, Model)] = []
162181
for (_, modelField) in modelSchema.fields {
163-
guard modelField.hasAssociation,
164-
modelField.isOneToOne || modelField.isOneToMany,
165-
let associatedModelName = modelField.associatedModelName,
166-
let associatedField = modelField.associatedField,
167-
let associatedModelSchema = ModelRegistry.modelSchema(from: associatedModelName) else {
182+
guard
183+
modelField.hasAssociation,
184+
modelField.isOneToOne || modelField.isOneToMany,
185+
let associatedModelName = modelField.associatedModelName,
186+
let associatedField = modelField.associatedField,
187+
let associatedModelSchema = ModelRegistry.modelSchema(from: associatedModelName)
188+
else {
168189
continue
169190
}
170-
171-
guard let modelSchema = ModelRegistry.modelSchema(from: associatedModelName) else {
172-
log.error("Failed to lookup associate model \(associatedModelName)")
173-
return []
191+
logMessage(
192+
"[CascadeDelete.2] Querying for \(modelSchema.name)'s associated model \(associatedModelSchema.name)."
193+
)
194+
let queriedModels = await queryAssociatedModels(
195+
associatedModelSchema: associatedModelSchema,
196+
associatedField: associatedField,
197+
ids: ids)
198+
199+
let associatedModelIds = queriedModels.map {
200+
$0.1.identifier(schema: associatedModelSchema).stringValue
174201
}
175-
176-
let queriedModels = await queryAssociatedModels(associatedModelSchema: modelSchema,
177-
associatedField: associatedField,
178-
ids: ids)
179-
180-
let associatedModelIds = queriedModels.map { $0.1.identifier(schema: modelSchema).stringValue }
202+
203+
logMessage("[CascadeDelete.2] Queried for \(associatedModelSchema.name), retrieved ids for deletion: \(associatedModelIds)")
204+
181205
associatedModels.append(contentsOf: queriedModels)
182-
associatedModels.append(contentsOf: await recurseQueryAssociatedModels(modelSchema: associatedModelSchema,
183-
ids: associatedModelIds))
206+
associatedModels.append(contentsOf: await recurseQueryAssociatedModels(
207+
modelSchema: associatedModelSchema,
208+
ids: associatedModelIds)
209+
)
184210
}
211+
185212
return associatedModels
186213
}
187214

@@ -214,34 +241,42 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
214241
return queriedModels
215242
}
216243

217-
private func collapseResults<M: Model>(queryResult: DataStoreResult<[M]>?,
218-
deleteResult: DataStoreResult<[M]>?,
219-
associatedModels: [(ModelName, Model)]) -> DataStoreResult<QueryAndDeleteResult<M>> {
220-
if let queryResult = queryResult {
221-
switch queryResult {
222-
case .success(let models):
223-
if let deleteResult = deleteResult {
224-
switch deleteResult {
225-
case .success:
226-
return .success(QueryAndDeleteResult(deletedModels: models,
227-
associatedModels: associatedModels))
228-
case .failure(let error):
229-
return .failure(error)
230-
}
231-
} else {
232-
return .failure(.unknown("deleteResult not set during transaction", "coding error", nil))
233-
}
244+
private func collapseResults<M: Model>(
245+
queryResult: DataStoreResult<[M]>?,
246+
deleteResult: DataStoreResult<[M]>?,
247+
associatedModels: [(ModelName, Model)]
248+
) -> DataStoreResult<QueryAndDeleteResult<M>> {
249+
250+
guard let queryResult = queryResult else {
251+
return .failure(.unknown("queryResult not set during transaction", "coding error", nil))
252+
}
253+
254+
switch queryResult {
255+
case .success(let models):
256+
guard let deleteResult = deleteResult else {
257+
return .failure(.unknown("deleteResult not set during transaction", "coding error", nil))
258+
}
259+
260+
switch deleteResult {
261+
case .success:
262+
logMessage("[CascadeDelete.3] Local cascade delete of \(modelSchema.name) successful!")
263+
return .success(QueryAndDeleteResult(deletedModels: models,
264+
associatedModels: associatedModels))
234265
case .failure(let error):
235266
return .failure(error)
236267
}
237-
} else {
238-
return .failure(.unknown("queryResult not set during transaction", "coding error", nil))
268+
269+
case .failure(let error):
270+
return .failure(error)
239271
}
240272
}
241273

242274
func syncIfNeededAndFinish(_ transactionResult: DataStoreResult<QueryAndDeleteResult<M>>) {
243275
switch transactionResult {
244276
case .success(let queryAndDeleteResult):
277+
logMessage(
278+
"[CascadeDelete.4] sending a total of \(queryAndDeleteResult.associatedModels.count + queryAndDeleteResult.deletedModels.count) delete mutations"
279+
)
245280
switch deleteInput {
246281
case .withIdentifier, .withIdentifierAndCondition:
247282
guard queryAndDeleteResult.deletedModels.count <= 1 else {
@@ -331,7 +366,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
331366
}
332367

333368
// `syncDeletions` will first sync all associated models in reversed order so the lowest level of children models
334-
// are synced first, before the its parent models. See `recurseQueryAssociatedModels()` for more details on the
369+
// are synced first, before its parent models. See `recurseQueryAssociatedModels()` for more details on the
335370
// ordering of the results in `associatedModels`. Once all the associated models are synced, sync the `models`,
336371
// finishing the sequence of deletions from children to parent.
337372
//
@@ -358,6 +393,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
358393
completion: completion)
359394
return
360395
}
396+
self.log.debug("[CascadeDelete.4] Begin syncing \(associatedModels.count) associated models for deletion. ")
361397

362398
var mutationEventsSubmitCompleted = 0
363399
for (modelName, associatedModel) in associatedModels.reversed() {
@@ -382,7 +418,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
382418
savedDataStoreError = dataStoreError
383419
}
384420
case .success(let mutationEvent):
385-
self.log.verbose("\(#function) successfully submitted to sync engine \(mutationEvent)")
421+
self.log.verbose("\(#function) successfully submitted \(mutationEvent.modelName) to sync engine \(mutationEvent)")
386422
}
387423

388424
if mutationEventsSubmitCompleted == associatedModels.count {
@@ -400,12 +436,14 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
400436

401437
}
402438
}
439+
403440
@available(iOS 13.0, *)
404441
private func syncDeletions(withModels models: [M],
405442
predicate: QueryPredicate? = nil,
406443
syncEngine: RemoteSyncEngineBehavior,
407444
dataStoreError: DataStoreError?,
408445
completion: @escaping DataStoreCallback<Void>) {
446+
logMessage("[CascadeDelete.4] Begin syncing \(models.count) \(modelSchema.name) model for deletion")
409447
var graphQLFilterJSON: String?
410448
if let predicate = predicate {
411449
do {
@@ -465,10 +503,23 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
465503
completion: @escaping DataStoreCallback<MutationEvent>) {
466504
syncEngine.submit(mutationEvent, completion: completion)
467505
}
506+
507+
private func logMessage(
508+
_ message: @escaping @autoclosure () -> String,
509+
level log: (() -> String) -> Void = log.debug) {
510+
guard isDeveloperDefinedModel else { return }
511+
log(message)
512+
}
468513
}
469514

470-
// MARK: - CascadeDeleteOperation.DeleteInput
515+
// MARK: - Supporting types
471516
extension CascadeDeleteOperation {
517+
518+
struct QueryAndDeleteResult<M: Model> {
519+
let deletedModels: [M]
520+
let associatedModels: [(ModelName, Model)]
521+
}
522+
472523
enum DeleteInput {
473524
case withIdentifier(id: ModelIdentifierProtocol)
474525
case withIdentifierAndCondition(id: ModelIdentifierProtocol, condition: QueryPredicate)

AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ final class StorageEngine: StorageEngineBehavior {
356356
case .failure(let dataStoreError):
357357
completion(.failure(dataStoreError))
358358
case .success(let mutationEvent):
359-
self.log.verbose("\(#function) successfully submitted to sync engine \(mutationEvent)")
359+
self.log.verbose("\(#function) successfully submitted \(mutationEvent.modelName) to sync engine \(mutationEvent)")
360360
completion(.success(savedModel))
361361
}
362362
}

0 commit comments

Comments
 (0)