Skip to content

Commit a7bdc5d

Browse files
authored
fix(DataStore): dynamic model support for cascade delete (#1296)
* fix(DataStore): dynamic model support for cascade delete * PR comments
1 parent b514670 commit a7bdc5d

File tree

13 files changed

+218
-78
lines changed

13 files changed

+218
-78
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,21 @@ extension MutationEvent {
1515
throw dataStoreError
1616
}
1717

18+
try self.init(untypedModel: model,
19+
modelName: modelType.schema.name,
20+
mutationType: mutationType,
21+
version: version)
22+
}
23+
24+
public init(untypedModel model: Model,
25+
modelName: ModelName,
26+
mutationType: MutationType,
27+
version: Int? = nil) throws {
1828
let json = try model.toJSON()
1929
self.init(modelId: model.id,
20-
modelName: modelType.schema.name,
30+
modelName: modelName,
2131
json: json,
2232
mutationType: mutationType,
2333
version: version)
2434
}
25-
2635
}

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Statement+AnyModel.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,20 @@ import SQLite
1010
import Foundation
1111

1212
extension Statement {
13-
func convert(toUntypedModel modelType: Model.Type,
14-
using statement: SelectStatement) throws -> [Model] {
13+
func convertToUntypedModel(using modelSchema: ModelSchema,
14+
statement: SelectStatement) throws -> [Model] {
1515
var models = [Model]()
1616

1717
for row in self {
18-
let modelValues = try convert(row: row, to: modelType, using: statement)
19-
let untypedModel = try convert(toAnyModel: modelType, modelDictionary: modelValues)
18+
let modelValues = try convert(row: row, withSchema: modelSchema, using: statement)
19+
let untypedModel = try convertToAnyModel(using: modelSchema, modelDictionary: modelValues)
2020
models.append(untypedModel)
2121
}
2222

2323
return models
2424
}
2525

26-
private func convert(toAnyModel modelType: Model.Type, modelDictionary: ModelValues) throws -> Model {
26+
private func convertToAnyModel(using modelSchema: ModelSchema, modelDictionary: ModelValues) throws -> Model {
2727
let data = try JSONSerialization.data(withJSONObject: modelDictionary)
2828
guard let jsonString = String(data: data, encoding: .utf8) else {
2929
let error = DataStoreError.decodingError(
@@ -36,7 +36,7 @@ extension Statement {
3636
throw error
3737
}
3838

39-
let instance = try ModelRegistry.decode(modelName: modelType.modelName, from: jsonString)
39+
let instance = try ModelRegistry.decode(modelName: modelSchema.name, from: jsonString)
4040
return instance
4141
}
4242
}

AmplifyPlugins/DataStore/AWSDataStoreCategoryPlugin/Storage/SQLite/Statement+Model.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ protocol StatementModelConvertible {
3636
///
3737
/// - Parameters:
3838
/// - modelType - the target `Model` type
39+
/// - modelSchema - the schema for `Model`
3940
/// - statement - the query executed that generated this result
4041
/// - Returns: an array of `Model` of the specified type
4142
func convert<M: Model>(to modelType: M.Type,
43+
withSchema modelSchema: ModelSchema,
4244
using statement: SelectStatement) throws -> [M]
4345

4446
}
@@ -51,12 +53,13 @@ extension Statement: StatementModelConvertible {
5153
}
5254

5355
func convert<M: Model>(to modelType: M.Type,
56+
withSchema modelSchema: ModelSchema,
5457
using statement: SelectStatement) throws -> [M] {
5558
var elements: [ModelValues] = []
5659

5760
// parse each row of the result
5861
for row in self {
59-
let modelDictionary = try convert(row: row, to: modelType, using: statement)
62+
let modelDictionary = try convert(row: row, withSchema: modelSchema, using: statement)
6063
elements.append(modelDictionary)
6164
}
6265

@@ -66,7 +69,7 @@ extension Statement: StatementModelConvertible {
6669
}
6770

6871
func convert(row: Element,
69-
to modelType: Model.Type,
72+
withSchema modelSchema: ModelSchema,
7073
using statement: SelectStatement) throws -> ModelValues {
7174
let columnMapping = statement.metadata.columnMapping
7275
let modelDictionary = ([:] as ModelValues).mutableCopy()
@@ -75,7 +78,7 @@ extension Statement: StatementModelConvertible {
7578
guard let (schema, field) = columnMapping[column] else {
7679
logger.debug("""
7780
A column named \(column) was found in the result set but no field on
78-
\(modelType.modelName) could be found with that name and it will be ignored.
81+
\(modelSchema.name) could be found with that name and it will be ignored.
7982
""")
8083
continue
8184
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
233233
sort: sort,
234234
paginationInput: paginationInput)
235235
let rows = try connection.prepare(statement.stringValue).run(statement.variables)
236-
let result: [M] = try rows.convert(to: modelType, using: statement)
236+
let result: [M] = try rows.convert(to: modelType,
237+
withSchema: modelSchema,
238+
using: statement)
237239
completion(.success(result))
238240
} catch {
239241
completion(.failure(causedBy: error))
@@ -286,6 +288,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
286288
let rows = try connection.prepare(sql).bind(ids)
287289

288290
let syncMetadataList = try rows.convert(to: MutationSyncMetadata.self,
291+
withSchema: MutationSyncMetadata.schema,
289292
using: statement)
290293
let mutationSyncList = try syncMetadataList.map { syncMetadata -> MutationSync<AnyModel> in
291294
guard let model = modelById[syncMetadata.id] else {
@@ -304,6 +307,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
304307

305308
func queryMutationSyncMetadata(for modelIds: [Model.Identifier]) throws -> [MutationSyncMetadata] {
306309
let modelType = MutationSyncMetadata.self
310+
let modelSchema = MutationSyncMetadata.schema
307311
let fields = MutationSyncMetadata.keys
308312
var results = [MutationSyncMetadata]()
309313
let chunkedModelIdsArr = modelIds.chunked(into: SQLiteStorageEngineAdapter.maxNumberOfPredicates)
@@ -313,9 +317,10 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
313317
queryPredicates.append(QueryPredicateOperation(field: fields.id.stringValue, operator: .equals(id)))
314318
}
315319
let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates)
316-
let statement = SelectStatement(from: modelType.schema, predicate: groupedQueryPredicates)
320+
let statement = SelectStatement(from: modelSchema, predicate: groupedQueryPredicates)
317321
let rows = try connection.prepare(statement.stringValue).run(statement.variables)
318322
let result = try rows.convert(to: modelType,
323+
withSchema: modelSchema,
319324
using: statement)
320325
results.append(contentsOf: result)
321326
}
@@ -327,6 +332,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter {
327332
predicate: field("id").eq(modelSchema.name))
328333
let rows = try connection.prepare(statement.stringValue).run(statement.variables)
329334
let result = try rows.convert(to: ModelSyncMetadata.self,
335+
withSchema: ModelSyncMetadata.schema,
330336
using: statement)
331337
return try result.unique()
332338
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@ extension SQLiteStorageEngineAdapter {
4343
}
4444
}
4545

46-
func query(untypedModel modelType: Model.Type,
46+
func query(modelSchema: ModelSchema,
4747
predicate: QueryPredicate? = nil,
4848
completion: DataStoreCallback<[Model]>) {
4949
do {
50-
let statement = SelectStatement(from: modelType.schema, predicate: predicate)
50+
let statement = SelectStatement(from: modelSchema, predicate: predicate)
5151
let rows = try connection.prepare(statement.stringValue).run(statement.variables)
52-
let result: [Model] = try rows.convert(toUntypedModel: modelType, using: statement)
52+
let result: [Model] = try rows.convertToUntypedModel(using: modelSchema, statement: statement)
5353
completion(.success(result))
5454
} catch {
5555
completion(.failure(causedBy: error))

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

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ extension StorageEngine {
3232

3333
func queryAndDeleteTransaction<M: Model>(_ modelType: M.Type,
3434
modelSchema: ModelSchema,
35-
deleteInput: DeleteInput) -> DataStoreResult<([M], [Model])> {
35+
deleteInput: DeleteInput) -> DataStoreResult<([M], [ModelName: [Model]])> {
3636
var queriedResult: DataStoreResult<[M]>?
3737
var deletedResult: DataStoreResult<[M]>?
38-
var associatedModels: [Model] = []
38+
var associatedModels: [(ModelName, Model)] = []
3939

4040
let queryCompletionBlock: DataStoreCallback<[M]> = { queryResult in
4141
queriedResult = queryResult
@@ -93,11 +93,11 @@ extension StorageEngine {
9393

9494
return collapseResults(queryResult: queriedResult,
9595
deleteResult: deletedResult,
96-
associatedModels: associatedModels)
96+
associatedModelsMap: getAssociatedModelsMap(associatedModels: associatedModels))
9797
}
9898

99-
func recurseQueryAssociatedModels(modelSchema: ModelSchema, ids: [Model.Identifier]) -> [Model] {
100-
var associatedModels: [Model] = []
99+
func recurseQueryAssociatedModels(modelSchema: ModelSchema, ids: [Model.Identifier]) -> [(ModelName, Model)] {
100+
var associatedModels: [(ModelName, Model)] = []
101101
for (_, modelField) in modelSchema.fields {
102102
guard modelField.hasAssociation,
103103
modelField.isOneToOne || modelField.isOneToMany,
@@ -109,57 +109,69 @@ extension StorageEngine {
109109
let queriedModels = queryAssociatedModels(modelName: associatedModelName,
110110
associatedField: associatedField,
111111
ids: ids)
112-
let associatedModelIds = queriedModels.map {$0.id}
112+
let associatedModelIds = queriedModels.map { $0.1.id }
113113
associatedModels.append(contentsOf: queriedModels)
114-
associatedModels.append(contentsOf: recurseQueryAssociatedModels(modelSchema: associatedModelSchema, ids: associatedModelIds))
114+
associatedModels.append(contentsOf: recurseQueryAssociatedModels(modelSchema: associatedModelSchema,
115+
ids: associatedModelIds))
115116
}
116117
return associatedModels
117118
}
118119

119-
func queryAssociatedModels(modelName: ModelName, associatedField: ModelField, ids: [Model.Identifier]) -> [Model] {
120-
guard let modelType = ModelRegistry.modelType(from: modelName) else {
120+
func queryAssociatedModels(modelName: ModelName,
121+
associatedField: ModelField,
122+
ids: [Model.Identifier]) -> [(ModelName, Model)] {
123+
guard let modelSchema = ModelRegistry.modelSchema(from: modelName) else {
121124
log.error("Failed to lookup \(modelName)")
122125
return []
123126
}
124127

125-
var queriedModels: [Model] = []
128+
var queriedModels: [(ModelName, Model)] = []
126129
let chunkedArrays = ids.chunked(into: SQLiteStorageEngineAdapter.maxNumberOfPredicates)
127130
for chunkedArray in chunkedArrays {
128131
// TODO: Add conveinence to queryPredicate where we have a list of items, to be all or'ed
129132
var queryPredicates: [QueryPredicateOperation] = []
130133
for id in chunkedArray {
131134
queryPredicates.append(QueryPredicateOperation(field: associatedField.name, operator: .equals(id)))
132135
}
133-
let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates)
136+
let groupedQueryPredicates = QueryPredicateGroup(type: .or, predicates: queryPredicates)
134137

135138
let sempahore = DispatchSemaphore(value: 0)
136-
storageAdapter.query(untypedModel: modelType, predicate: groupedQueryPredicates) { result in
139+
storageAdapter.query(modelSchema: modelSchema, predicate: groupedQueryPredicates) { result in
137140
defer {
138141
sempahore.signal()
139142
}
140143
switch result {
141144
case .success(let models):
142-
queriedModels.append(contentsOf: models)
143-
145+
queriedModels.append(contentsOf: models.map { model in
146+
(modelName, model)
147+
})
144148
case .failure(let error):
145-
log.error("Failed to query \(modelType) on mutation event generation: \(error)")
149+
log.error("Failed to query \(modelSchema) on mutation event generation: \(error)")
146150
}
147151
}
148152
sempahore.wait()
149153
}
150154
return queriedModels
151155
}
152156

157+
private func getAssociatedModelsMap(associatedModels: [(ModelName, Model)]) -> [ModelName: [Model]] {
158+
var associatedModelsMap = [ModelName: [Model]]()
159+
for (modelName, model) in associatedModels {
160+
associatedModelsMap[modelName, default: []].append(model)
161+
}
162+
return associatedModelsMap
163+
}
164+
153165
private func collapseResults<M: Model>(queryResult: DataStoreResult<[M]>?,
154166
deleteResult: DataStoreResult<[M]>?,
155-
associatedModels: [Model]) -> DataStoreResult<([M], [Model])> {
167+
associatedModelsMap: [ModelName: [Model]]) -> DataStoreResult<([M], [ModelName: [Model]])> {
156168
if let queryResult = queryResult {
157169
switch queryResult {
158170
case .success(let models):
159171
if let deleteResult = deleteResult {
160172
switch deleteResult {
161173
case .success:
162-
return .success((models, associatedModels))
174+
return .success((models, associatedModelsMap))
163175
case .failure(let error):
164176
return .failure(error)
165177
}
@@ -174,7 +186,7 @@ extension StorageEngine {
174186
}
175187
}
176188

177-
func collapseMResult<M: Model>(_ result: DataStoreResult<([M], [Model])>) -> DataStoreResult<[M]> {
189+
func collapseMResult<M: Model>(_ result: DataStoreResult<([M], [ModelName: [Model]])>) -> DataStoreResult<[M]> {
178190
switch result {
179191
case .success(let results):
180192
return .success(results.0)
@@ -183,12 +195,13 @@ extension StorageEngine {
183195
}
184196
}
185197

186-
func resolveAssociatedModels<M: Model>(_ result: DataStoreResult<([M], [Model])>) -> [Model] {
198+
func resolveAssociatedModels<M: Model>(
199+
_ result: DataStoreResult<([M], [ModelName: [Model]])>) -> [ModelName: [Model]] {
187200
switch result {
188201
case .success(let results):
189202
return results.1
190203
case .failure:
191-
return []
204+
return [:]
192205
}
193206
}
194207
}

0 commit comments

Comments
 (0)