@@ -29,57 +29,75 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
29
29
let completionForWithId : ( ( DataStoreResult < M ? > ) -> Void ) ?
30
30
let completionForWithFilter : ( ( DataStoreResult < [ M ] > ) -> Void ) ?
31
31
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 )
49
56
}
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
- }
56
57
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
+ ) {
63
84
self . storageAdapter = storageAdapter
64
85
self . syncEngine = syncEngine
65
86
self . modelType = modelType
66
87
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 }
71
94
super. init ( )
72
95
}
73
96
74
97
override public func main( ) {
75
98
queryAndDelete ( )
76
99
}
77
100
78
- struct QueryAndDeleteResult < M: Model > {
79
- let deletedModels : [ M ]
80
- let associatedModels : [ ( ModelName , Model ) ]
81
- }
82
-
83
101
func queryAndDelete( ) {
84
102
do {
85
103
try storageAdapter. transaction {
@@ -118,8 +136,8 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
118
136
// Query did not return any results, treat this as a successful no-op delete.
119
137
deletedResult = . success( [ M] ( ) )
120
138
return collapseResults ( queryResult: queriedResult,
121
- deleteResult: deletedResult,
122
- associatedModels: associatedModels)
139
+ deleteResult: deletedResult,
140
+ associatedModels: associatedModels)
123
141
}
124
142
125
143
// 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 {
137
155
}
138
156
139
157
return collapseResults ( queryResult: queriedResult,
140
- deleteResult: deletedResult,
141
- associatedModels: associatedModels)
158
+ deleteResult: deletedResult,
159
+ associatedModels: associatedModels)
142
160
}
143
161
144
162
let modelIds = queriedModels. map { $0. identifier ( schema: self . modelSchema) . stringValue }
145
-
163
+ logMessage ( " [CascadeDelete.1] Deleting \( modelSchema. name) with identifiers: \( modelIds) " )
164
+
146
165
associatedModels = await self . recurseQueryAssociatedModels ( modelSchema: self . modelSchema, ids: modelIds)
147
166
148
167
deletedResult = await withCheckedContinuation { continuation in
@@ -160,28 +179,36 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
160
179
func recurseQueryAssociatedModels( modelSchema: ModelSchema , ids: [ String ] ) async -> [ ( ModelName , Model ) ] {
161
180
var associatedModels : [ ( ModelName , Model ) ] = [ ]
162
181
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 {
168
189
continue
169
190
}
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
174
201
}
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
+
181
205
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
+ )
184
210
}
211
+
185
212
return associatedModels
186
213
}
187
214
@@ -214,34 +241,42 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
214
241
return queriedModels
215
242
}
216
243
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) )
234
265
case . failure( let error) :
235
266
return . failure( error)
236
267
}
237
- } else {
238
- return . failure( . unknown( " queryResult not set during transaction " , " coding error " , nil ) )
268
+
269
+ case . failure( let error) :
270
+ return . failure( error)
239
271
}
240
272
}
241
273
242
274
func syncIfNeededAndFinish( _ transactionResult: DataStoreResult < QueryAndDeleteResult < M > > ) {
243
275
switch transactionResult {
244
276
case . success( let queryAndDeleteResult) :
277
+ logMessage (
278
+ " [CascadeDelete.4] sending a total of \( queryAndDeleteResult. associatedModels. count + queryAndDeleteResult. deletedModels. count) delete mutations "
279
+ )
245
280
switch deleteInput {
246
281
case . withIdentifier, . withIdentifierAndCondition:
247
282
guard queryAndDeleteResult. deletedModels. count <= 1 else {
@@ -331,7 +366,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
331
366
}
332
367
333
368
// `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
335
370
// ordering of the results in `associatedModels`. Once all the associated models are synced, sync the `models`,
336
371
// finishing the sequence of deletions from children to parent.
337
372
//
@@ -358,6 +393,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
358
393
completion: completion)
359
394
return
360
395
}
396
+ self . log. debug ( " [CascadeDelete.4] Begin syncing \( associatedModels. count) associated models for deletion. " )
361
397
362
398
var mutationEventsSubmitCompleted = 0
363
399
for (modelName, associatedModel) in associatedModels. reversed ( ) {
@@ -382,7 +418,7 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
382
418
savedDataStoreError = dataStoreError
383
419
}
384
420
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) " )
386
422
}
387
423
388
424
if mutationEventsSubmitCompleted == associatedModels. count {
@@ -400,12 +436,14 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
400
436
401
437
}
402
438
}
439
+
403
440
@available ( iOS 13 . 0 , * )
404
441
private func syncDeletions( withModels models: [ M ] ,
405
442
predicate: QueryPredicate ? = nil ,
406
443
syncEngine: RemoteSyncEngineBehavior ,
407
444
dataStoreError: DataStoreError ? ,
408
445
completion: @escaping DataStoreCallback < Void > ) {
446
+ logMessage ( " [CascadeDelete.4] Begin syncing \( models. count) \( modelSchema. name) model for deletion " )
409
447
var graphQLFilterJSON : String ?
410
448
if let predicate = predicate {
411
449
do {
@@ -465,10 +503,23 @@ public class CascadeDeleteOperation<M: Model>: AsynchronousOperation {
465
503
completion: @escaping DataStoreCallback < MutationEvent > ) {
466
504
syncEngine. submit ( mutationEvent, completion: completion)
467
505
}
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
+ }
468
513
}
469
514
470
- // MARK: - CascadeDeleteOperation.DeleteInput
515
+ // MARK: - Supporting types
471
516
extension CascadeDeleteOperation {
517
+
518
+ struct QueryAndDeleteResult < M: Model > {
519
+ let deletedModels : [ M ]
520
+ let associatedModels : [ ( ModelName , Model ) ]
521
+ }
522
+
472
523
enum DeleteInput {
473
524
case withIdentifier( id: ModelIdentifierProtocol )
474
525
case withIdentifierAndCondition( id: ModelIdentifierProtocol , condition: QueryPredicate )
0 commit comments