diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift index ae3549bf81..527e673f67 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/SQLite/StorageEngineAdapter+SQLite.swift @@ -148,9 +148,18 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { condition: QueryPredicate? = nil, eagerLoad: Bool = true, completion: DataStoreCallback) { + completion(save(model, + modelSchema: modelSchema, + condition: condition, + eagerLoad: eagerLoad)) + } + + func save(_ model: M, + modelSchema: ModelSchema, + condition: QueryPredicate? = nil, + eagerLoad: Bool = true) -> DataStoreResult { guard let connection = connection else { - completion(.failure(DataStoreError.nilSQLiteConnection())) - return + return .failure(DataStoreError.nilSQLiteConnection()) } do { let modelType = type(of: model) @@ -162,8 +171,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { let dataStoreError = DataStoreError.invalidCondition( "Cannot apply a condition on model which does not exist.", "Save the model instance without a condition first.") - completion(.failure(causedBy: dataStoreError)) - return + return .failure(causedBy: dataStoreError) } let statement = InsertStatement(model: model, modelSchema: modelSchema) @@ -179,9 +187,7 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { let dataStoreError = DataStoreError.invalidCondition( "Save failed due to condition did not match existing model instance.", "The save will continue to fail until the model instance is updated.") - completion(.failure(causedBy: dataStoreError)) - - return + return .failure(causedBy: dataStoreError) } } @@ -192,23 +198,22 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { } // load the recent saved instance and pass it back to the callback - query(modelType, modelSchema: modelSchema, - predicate: model.identifier(schema: modelSchema).predicate, - eagerLoad: eagerLoad) { - switch $0 { - case .success(let result): - if let saved = result.first { - completion(.success(saved)) - } else { - completion(.failure(.nonUniqueResult(model: modelType.modelName, - count: result.count))) - } - case .failure(let error): - completion(.failure(error)) + let queryResult = query(modelType, modelSchema: modelSchema, + predicate: model.identifier(schema: modelSchema).predicate, + eagerLoad: eagerLoad) + switch queryResult { + case .success(let result): + if let saved = result.first { + return .success(saved) + } else { + return .failure(.nonUniqueResult(model: modelType.modelName, + count: result.count)) } + case .failure(let error): + return .failure(error) } } catch { - completion(.failure(causedBy: error)) + return .failure(causedBy: error) } } @@ -321,9 +326,22 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { paginationInput: QueryPaginationInput? = nil, eagerLoad: Bool = true, completion: DataStoreCallback<[M]>) { + completion(query(modelType, + modelSchema: modelSchema, + predicate: predicate, + sort: sort, + paginationInput: paginationInput, + eagerLoad: eagerLoad)) + } + + func query(_ modelType: M.Type, + modelSchema: ModelSchema, + predicate: QueryPredicate? = nil, + sort: [QuerySortDescriptor]? = nil, + paginationInput: QueryPaginationInput? = nil, + eagerLoad: Bool = true) -> DataStoreResult<[M]> { guard let connection = connection else { - completion(.failure(DataStoreError.nilSQLiteConnection())) - return + return .failure(DataStoreError.nilSQLiteConnection()) } do { let statement = SelectStatement(from: modelSchema, @@ -336,9 +354,9 @@ final class SQLiteStorageEngineAdapter: StorageEngineAdapter { withSchema: modelSchema, using: statement, eagerLoad: eagerLoad) - completion(.success(result)) + return .success(result) } catch { - completion(.failure(causedBy: error)) + return .failure(causedBy: error) } } diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine.swift index 3b556c31ae..55c4c752a5 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngine.swift @@ -210,36 +210,39 @@ final class StorageEngine: StorageEngineBehavior { do { try storageAdapter.transaction { - let wrappedCompletion: DataStoreCallback = { result in - guard modelSchema.isSyncable, let syncEngine = self.syncEngine else { - completion(result) - return - } - - guard case .success(let savedModel) = result else { - completion(result) - return - } - - self.log.verbose("\(#function) syncing mutation for \(savedModel)") - self.syncMutation(of: savedModel, - modelSchema: modelSchema, - mutationType: mutationType, - predicate: condition, - syncEngine: syncEngine, - completion: completion) + let result = self.storageAdapter.save(model, + modelSchema: modelSchema, + condition: condition, + eagerLoad: eagerLoad) + guard modelSchema.isSyncable else { + completion(result) + return } - storageAdapter.save(model, - modelSchema: modelSchema, - condition: condition, - eagerLoad: eagerLoad, - completion: wrappedCompletion) + guard case .success(let savedModel) = result else { + completion(result) + return + } + + guard let syncEngine else { + let message = "No SyncEngine available to sync mutation event, rollback save." + self.log.verbose("\(#function) \(message) : \(savedModel)") + throw DataStoreError.internalOperation( + message, + "Save was interrupted. `DataStore.stop()` may have been called.", + nil) + } + self.log.verbose("\(#function) syncing mutation for \(savedModel)") + self.syncMutation(of: savedModel, + modelSchema: modelSchema, + mutationType: mutationType, + predicate: condition, + syncEngine: syncEngine, + completion: completion) } } catch { completion(.failure(causedBy: error)) } - } func save(_ model: M, @@ -346,7 +349,7 @@ final class StorageEngine: StorageEngineBehavior { } } - @available(iOS 13.0, *) +@available(iOS 13.0, *) private func syncMutation(of savedModel: M, modelSchema: ModelSchema, mutationType: MutationEvent.MutationType, diff --git a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngineAdapter.swift b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngineAdapter.swift index b911f0a1c3..191df26ebc 100644 --- a/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngineAdapter.swift +++ b/AmplifyPlugins/DataStore/Sources/AWSDataStorePlugin/Storage/StorageEngineAdapter.swift @@ -34,6 +34,11 @@ protocol StorageEngineAdapter: AnyObject, ModelStorageBehavior, ModelStorageErro // MARK: - Synchronous APIs + func save(_ model: M, + modelSchema: ModelSchema, + condition: QueryPredicate?, + eagerLoad: Bool) -> DataStoreResult + func exists(_ modelSchema: ModelSchema, withIdentifier id: ModelIdentifierProtocol, predicate: QueryPredicate?) throws -> Bool diff --git a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift index 5b5fb2d46a..dbdfdbf2a2 100644 --- a/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift +++ b/AmplifyPlugins/DataStore/Tests/AWSDataStorePluginTests/Sync/SubscriptionSync/Support/MockSQLiteStorageEngineAdapter.swift @@ -177,6 +177,14 @@ class MockSQLiteStorageEngineAdapter: StorageEngineAdapter { : completion(.success(model)) } + func save(_ model: M, + modelSchema: ModelSchema, + condition: QueryPredicate?, + eagerLoad: Bool) -> DataStoreResult { + XCTFail("Not yet implemented") + return .failure(.internalOperation("", "", nil)) + } + func save(_ model: M, modelSchema: ModelSchema, condition where: QueryPredicate?,