diff --git a/Modules/RSDatabase/Package.swift b/Modules/RSDatabase/Package.swift index b4b419ac2..81bfa5358 100644 --- a/Modules/RSDatabase/Package.swift +++ b/Modules/RSDatabase/Package.swift @@ -13,7 +13,7 @@ let package = Package( .library( name: "RSDatabaseObjC", type: .dynamic, - targets: ["RSDatabaseObjC"]), + targets: ["RSDatabaseObjC"]) ], dependencies: [ ], @@ -29,6 +29,6 @@ let package = Package( ), .testTarget( name: "RSDatabaseTests", - dependencies: ["RSDatabase"]), + dependencies: ["RSDatabase"]) ] ) diff --git a/Modules/RSDatabase/Sources/RSDatabase/DatabaseObject.swift b/Modules/RSDatabase/Sources/RSDatabase/DatabaseObject.swift index cc86201d3..f4d59e3ee 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/DatabaseObject.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/DatabaseObject.swift @@ -20,9 +20,9 @@ public protocol DatabaseObject { } public extension DatabaseObject { - + func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? { - + return nil } } @@ -37,14 +37,14 @@ extension Array where Element == DatabaseObject { } return d } - + func databaseIDs() -> Set { - + return Set(self.map { $0.databaseID }) } - + func includesObjectWithDatabaseID(_ databaseID: String) -> Bool { - + for object in self { if object.databaseID == databaseID { return true @@ -55,7 +55,7 @@ extension Array where Element == DatabaseObject { func databaseDictionaries() -> [DatabaseDictionary]? { - let dictionaries = self.compactMap{ $0.databaseDictionary() } + let dictionaries = self.compactMap { $0.databaseDictionary() } return dictionaries.isEmpty ? nil : dictionaries } } diff --git a/Modules/RSDatabase/Sources/RSDatabase/DatabaseQueue.swift b/Modules/RSDatabase/Sources/RSDatabase/DatabaseQueue.swift index 6dd544520..5146372db 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/DatabaseQueue.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/DatabaseQueue.swift @@ -152,7 +152,7 @@ public final class DatabaseQueue { /// Use this to create tables, indexes, etc. public func runCreateStatements(_ statements: String) throws { precondition(Thread.isMainThread) - var error: DatabaseError? = nil + var error: DatabaseError? runInDatabaseSync { result in switch result { case .success(let database): @@ -236,8 +236,7 @@ private extension DatabaseQueue { autoreleasepool { if _isSuspended { databaseBlock(.failure(.isSuspended)) - } - else { + } else { if useTransaction { database.beginTransaction() } @@ -256,4 +255,3 @@ private extension DatabaseQueue { database.setShouldCacheStatements(true) } } - diff --git a/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift b/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift index 3dc4a5707..e8d3a2cc0 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/DatabaseTable.swift @@ -15,11 +15,11 @@ public protocol DatabaseTable { } public extension DatabaseTable { - + // MARK: Fetching func selectRowsWhere(key: String, equals value: Any, in database: FMDatabase) -> FMResultSet? { - + return database.rs_selectRowsWhereKey(key, equalsValue: value, tableName: name) } @@ -39,7 +39,7 @@ public extension DatabaseTable { // MARK: Deleting func deleteRowsWhere(key: String, equalsAnyValue values: [Any], in database: FMDatabase) { - + if values.isEmpty { return } @@ -49,21 +49,21 @@ public extension DatabaseTable { // MARK: Updating func updateRowsWithValue(_ value: Any, valueKey: String, whereKey: String, matches: [Any], database: FMDatabase) { - - let _ = database.rs_updateRows(withValue: value, valueKey: valueKey, whereKey: whereKey, inValues: matches, tableName: self.name) + + _ = database.rs_updateRows(withValue: value, valueKey: valueKey, whereKey: whereKey, inValues: matches, tableName: self.name) } - + func updateRowsWithDictionary(_ dictionary: DatabaseDictionary, whereKey: String, matches: Any, database: FMDatabase) { - - let _ = database.rs_updateRows(with: dictionary, whereKey: whereKey, equalsValue: matches, tableName: self.name) + + _ = database.rs_updateRows(with: dictionary, whereKey: whereKey, equalsValue: matches, tableName: self.name) } - + // MARK: Saving func insertRows(_ dictionaries: [DatabaseDictionary], insertType: RSDatabaseInsertType, in database: FMDatabase) { for oneDictionary in dictionaries { - let _ = database.rs_insertRow(with: oneDictionary, insertType: insertType, tableName: self.name) + _ = database.rs_insertRow(with: oneDictionary, insertType: insertType, tableName: self.name) } } @@ -136,4 +136,3 @@ public extension FMResultSet { return Set(compactMap(completion)) } } - diff --git a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseLookupTable.swift b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseLookupTable.swift index defd66fcb..e85a9b293 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseLookupTable.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseLookupTable.swift @@ -21,7 +21,7 @@ public final class DatabaseLookupTable { private let relationshipName: String private let relatedTable: DatabaseRelatedObjectsTable private var objectIDsWithNoRelatedObjects = Set() - + public init(name: String, objectIDKey: String, relatedObjectIDKey: String, relatedTable: DatabaseRelatedObjectsTable, relationshipName: String) { self.name = name @@ -32,47 +32,46 @@ public final class DatabaseLookupTable { } public func fetchRelatedObjects(for objectIDs: Set, in database: FMDatabase) -> RelatedObjectsMap? { - + let objectIDsThatMayHaveRelatedObjects = objectIDs.subtracting(objectIDsWithNoRelatedObjects) if objectIDsThatMayHaveRelatedObjects.isEmpty { return nil } - + guard let relatedObjectIDsMap = fetchRelatedObjectIDsMap(objectIDsThatMayHaveRelatedObjects, database) else { objectIDsWithNoRelatedObjects.formUnion(objectIDsThatMayHaveRelatedObjects) return nil } - + if let relatedObjects = fetchRelatedObjectsWithIDs(relatedObjectIDsMap.relatedObjectIDs(), database) { - + let relatedObjectsMap = RelatedObjectsMap(relatedObjects: relatedObjects, relatedObjectIDsMap: relatedObjectIDsMap) - + let objectIDsWithNoFetchedRelatedObjects = objectIDsThatMayHaveRelatedObjects.subtracting(relatedObjectsMap.objectIDs()) objectIDsWithNoRelatedObjects.formUnion(objectIDsWithNoFetchedRelatedObjects) - + return relatedObjectsMap } - + return nil } - + public func saveRelatedObjects(for objects: [DatabaseObject], in database: FMDatabase) { - + var objectsWithNoRelationships = [DatabaseObject]() var objectsWithRelationships = [DatabaseObject]() for object in objects { if let relatedObjects = object.relatedObjectsWithName(relationshipName), !relatedObjects.isEmpty { objectsWithRelationships += [object] - } - else { + } else { objectsWithNoRelationships += [object] } } - + removeRelationships(for: objectsWithNoRelationships, database) updateRelationships(for: objectsWithRelationships, database) - + objectIDsWithNoRelatedObjects.formUnion(objectsWithNoRelationships.databaseIDs()) objectIDsWithNoRelatedObjects.subtract(objectsWithRelationships.databaseIDs()) } @@ -83,7 +82,7 @@ public final class DatabaseLookupTable { private extension DatabaseLookupTable { // MARK: Removing - + func removeRelationships(for objects: [DatabaseObject], _ database: FMDatabase) { let objectIDs = objects.databaseIDs() @@ -91,52 +90,52 @@ private extension DatabaseLookupTable { if objectIDsToRemove.isEmpty { return } - + database.rs_deleteRowsWhereKey(objectIDKey, inValues: Array(objectIDsToRemove), tableName: name) } - + func deleteLookups(for objectID: String, _ relatedObjectIDs: Set, _ database: FMDatabase) { - + guard !relatedObjectIDs.isEmpty else { assertionFailure("deleteLookups: expected non-empty relatedObjectIDs") return } - + // delete from authorLookup where articleID=? and authorID in (?,?,?) let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(relatedObjectIDs.count))! let sql = "delete from \(name) where \(objectIDKey)=? and \(relatedObjectIDKey) in \(placeholders)" - + let parameters: [Any] = [objectID] + Array(relatedObjectIDs) - let _ = database.executeUpdate(sql, withArgumentsIn: parameters) + _ = database.executeUpdate(sql, withArgumentsIn: parameters) } - + // MARK: Saving/Updating - + func updateRelationships(for objects: [DatabaseObject], _ database: FMDatabase) { if objects.isEmpty { return } - + if let lookupTable = fetchRelatedObjectIDsMap(objects.databaseIDs(), database) { for object in objects { syncRelatedObjectsAndLookupTable(object, lookupTable, database) } } - + // Save the actual related objects. - + let relatedObjectsToSave = uniqueArrayOfRelatedObjects(with: objects) if relatedObjectsToSave.isEmpty { assertionFailure("updateRelationships: expected relatedObjectsToSave would not be empty. This should be unreachable.") return } - + relatedTable.save(relatedObjectsToSave, in: database) } func uniqueArrayOfRelatedObjects(with objects: [DatabaseObject]) -> [DatabaseObject] { - + // Can’t create a Set, because we can’t make a Set, because protocol-conforming objects can’t be made Hashable or even Equatable. // We still want the array to include only one copy of each object, but we have to do it the slow way. Instruments will tell us if this is a performance problem. @@ -154,48 +153,48 @@ private extension DatabaseLookupTable { } return relatedObjectsUniqueArray } - + func syncRelatedObjectsAndLookupTable(_ object: DatabaseObject, _ lookupTable: RelatedObjectIDsMap, _ database: FMDatabase) { - + guard let relatedObjects = object.relatedObjectsWithName(relationshipName) else { assertionFailure("syncRelatedObjectsAndLookupTable should be called only on objects with related objects.") return } - + let relatedObjectIDs = relatedObjects.databaseIDs() let lookupTableRelatedObjectIDs = lookupTable[object.databaseID] ?? Set() - + let relatedObjectIDsToDelete = lookupTableRelatedObjectIDs.subtracting(relatedObjectIDs) if !relatedObjectIDsToDelete.isEmpty { deleteLookups(for: object.databaseID, relatedObjectIDsToDelete, database) } - + let relatedObjectIDsToSave = relatedObjectIDs.subtracting(lookupTableRelatedObjectIDs) if !relatedObjectIDsToSave.isEmpty { saveLookups(for: object.databaseID, relatedObjectIDsToSave, database) } } - + func saveLookups(for objectID: String, _ relatedObjectIDs: Set, _ database: FMDatabase) { - + for relatedObjectID in relatedObjectIDs { let d: [NSObject: Any] = [(objectIDKey as NSString): objectID, (relatedObjectIDKey as NSString): relatedObjectID] - let _ = database.rs_insertRow(with: d, insertType: .orIgnore, tableName: name) + _ = database.rs_insertRow(with: d, insertType: .orIgnore, tableName: name) } } - + // MARK: Fetching - + func fetchRelatedObjectsWithIDs(_ relatedObjectIDs: Set, _ database: FMDatabase) -> [DatabaseObject]? { - + guard let relatedObjects = relatedTable.fetchObjectsWithIDs(relatedObjectIDs, in: database), !relatedObjects.isEmpty else { return nil } return relatedObjects } - + func fetchRelatedObjectIDsMap(_ objectIDs: Set, _ database: FMDatabase) -> RelatedObjectIDsMap? { - + guard let lookupValues = fetchLookupValues(objectIDs, database) else { return nil } @@ -203,20 +202,20 @@ private extension DatabaseLookupTable { } func fetchLookupValues(_ objectIDs: Set, _ database: FMDatabase) -> Set? { - + guard !objectIDs.isEmpty, let resultSet = database.rs_selectRowsWhereKey(objectIDKey, inValues: Array(objectIDs), tableName: name) else { return nil } return lookupValuesWithResultSet(resultSet) } - + func lookupValuesWithResultSet(_ resultSet: FMResultSet) -> Set { - + return resultSet.mapToSet(lookupValueWithRow) } - + func lookupValueWithRow(_ row: FMResultSet) -> LookupValue? { - + guard let objectID = row.string(forColumn: objectIDKey) else { return nil } @@ -226,4 +225,3 @@ private extension DatabaseLookupTable { return LookupValue(objectID: objectID, relatedObjectID: relatedObjectID) } } - diff --git a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift index 18e84ac75..ec97ac95c 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/DatabaseRelatedObjectsTable.swift @@ -15,7 +15,7 @@ public protocol DatabaseRelatedObjectsTable: DatabaseTable { var databaseIDKey: String { get} var cache: DatabaseObjectCache { get } - + func fetchObjectsWithIDs(_ databaseIDs: Set, in database: FMDatabase) -> [DatabaseObject]? func objectsWithResultSet(_ resultSet: FMResultSet) -> [DatabaseObject] func objectWithRow(_ row: FMResultSet) -> DatabaseObject? @@ -39,8 +39,7 @@ public extension DatabaseRelatedObjectsTable { for databaseID in databaseIDs { if let cachedObject = cache[databaseID] { cachedObjects += [cachedObject] - } - else { + } else { databaseIDsToFetch.insert(databaseID) } } diff --git a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectIDsMap.swift b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectIDsMap.swift index c15e4bde8..51f03e227 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectIDsMap.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectIDsMap.swift @@ -11,46 +11,45 @@ import Foundation // Maps objectIDs to Set where the Strings are relatedObjectIDs. struct RelatedObjectIDsMap { - + private let dictionary: [String: Set] // objectID: Set - + init(dictionary: [String: Set]) { - + self.dictionary = dictionary } - + init(lookupValues: Set) { - + var d = [String: Set]() - + for lookupValue in lookupValues { let objectID = lookupValue.objectID let relatedObjectID: String = lookupValue.relatedObjectID if d[objectID] == nil { d[objectID] = Set([relatedObjectID]) - } - else { + } else { d[objectID]!.insert(relatedObjectID) } } - + self.init(dictionary: d) } - + func objectIDs() -> Set { - + return Set(dictionary.keys) } - + func relatedObjectIDs() -> Set { - + var ids = Set() for (_, relatedObjectIDs) in dictionary { ids.formUnion(relatedObjectIDs) } return ids } - + subscript(_ objectID: String) -> Set? { return dictionary[objectID] } diff --git a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectsMap.swift b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectsMap.swift index 5afd9b54c..309661eb9 100644 --- a/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectsMap.swift +++ b/Modules/RSDatabase/Sources/RSDatabase/Related Objects/RelatedObjectsMap.swift @@ -12,32 +12,32 @@ import Foundation // It’s used as the return value for DatabaseLookupTable.fetchRelatedObjects. public struct RelatedObjectsMap { - + private let dictionary: [String: [DatabaseObject]] // objectID: relatedObjects - + init(relatedObjects: [DatabaseObject], relatedObjectIDsMap: RelatedObjectIDsMap) { - + var d = [String: [DatabaseObject]]() let relatedObjectsDictionary = relatedObjects.dictionary() - + for objectID in relatedObjectIDsMap.objectIDs() { - + if let relatedObjectIDs = relatedObjectIDsMap[objectID] { - let relatedObjects = relatedObjectIDs.compactMap{ relatedObjectsDictionary[$0] } + let relatedObjects = relatedObjectIDs.compactMap { relatedObjectsDictionary[$0] } if !relatedObjects.isEmpty { d[objectID] = relatedObjects } } } - + self.dictionary = d } - + public func objectIDs() -> Set { - + return Set(dictionary.keys) } - + public subscript(_ objectID: String) -> [DatabaseObject]? { return dictionary[objectID] }