Make progress saving relationships in DatabaseLookupTable.

This commit is contained in:
Brent Simmons 2017-08-20 12:41:33 -07:00
parent e76beee988
commit dccc44c920
5 changed files with 106 additions and 37 deletions

View File

@ -12,7 +12,8 @@ public protocol DatabaseTable: class {
var name: String {get}
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, _ database: FMDatabase) -> [DatabaseObject]
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]
func save(_ objects: [DatabaseObject], in database: FMDatabase)
}
public extension DatabaseTable {

View File

@ -565,7 +565,7 @@
INFOPLIST_FILE = RSDatabase/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=75";
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=125";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSDatabase;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -586,7 +586,7 @@
INFOPLIST_FILE = RSDatabase/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=75";
OTHER_SWIFT_FLAGS = "-Xfrontend -warn-long-function-bodies=125";
PRODUCT_BUNDLE_IDENTIFIER = com.ranchero.RSDatabase;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;

View File

@ -90,34 +90,105 @@ private extension DatabaseLookupTable {
database.rs_deleteRowsWhereKey(objectIDKey, inValues: Array(objectIDsToRemove), tableName: name)
}
func deleteLookups(for objectID: String, _ relatedObjectIDs: Set<String>, _ 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)
}
// MARK: Saving/Updating
func updateRelationships(for objects: [DatabaseObject], _ database: FMDatabase) {
// let objectsNeedingUpdate = objects.filter { (object) -> Bool in
// return !relationshipsMatchCache(object)
// }
let objectsNeedingUpdate = objects.filter { !relatedObjectIDsMatchesCache($0) }
if objectsNeedingUpdate.isEmpty {
return
}
if let lookupTable = fetchLookupTable(objectsNeedingUpdate.databaseIDs(), database) {
for object in objectsNeedingUpdate {
syncRelatedObjectsAndLookupTable(object, lookupTable, database)
}
}
// Save the actual related objects.
guard let relatedTable = relatedTable else {
assertionFailure("updateRelationships: relatedTable unexpectedly disappeared.")
return
}
let relatedObjectsToSave = uniqueArrayOfRelatedObjects(with: objectsNeedingUpdate)
if relatedObjectsToSave.isEmpty {
assertionFailure("updateRelationships: expected related objects to save. This should be unreachable.")
return
}
relatedTable.save(relatedObjectsToSave, in: database)
}
func relationshipsMatchCache(_ object: DatabaseObject) -> Bool {
func uniqueArrayOfRelatedObjects(with objects: [DatabaseObject]) -> [DatabaseObject] {
// Cant create a Set, because we cant make a Set<DatabaseObject>, because protocol-conforming objects cant 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.
let relationships = object.relatedObjectsWithName(relationshipName)
let cachedRelationshipIDs = cache[object.databaseID]
var relatedObjectsUniqueArray = [DatabaseObject]()
for object in objects {
guard let relatedObjects = object.relatedObjectsWithName(relationshipName) else {
assertionFailure("uniqueArrayOfRelatedObjects: expected every object to have related objects.")
continue
}
for relatedObject in relatedObjects {
if !relatedObjectsUniqueArray.includesObjectWithDatabaseID(relatedObject.databaseID) {
relatedObjectsUniqueArray += [relatedObject]
}
}
}
return relatedObjectsUniqueArray
}
func relatedObjectIDsMatchesCache(_ object: DatabaseObject) -> Bool {
if let relationships = relationships {
if let cachedRelationshipIDs = cachedRelationshipIDs {
return relationships.databaseIDs() == cachedRelationshipIDs
}
return false // cachedRelationshipIDs == nil, relationships != nil
}
else { // relationships == nil
if let cachedRelationshipIDs = cachedRelationshipIDs {
return !cachedRelationshipIDs.isEmpty
}
return true // both nil
}
let relatedObjects = object.relatedObjectsWithName(relationshipName) ?? [DatabaseObject]()
let cachedRelationshipIDs = cache[object.databaseID] ?? Set<String>()
return relatedObjects.databaseIDs() == cachedRelationshipIDs
}
func syncRelatedObjectsAndLookupTable(_ object: DatabaseObject, _ lookupTable: LookupTable, _ 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<String>()
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<String>, _ database: FMDatabase) {
}
// MARK: Attaching
func attachRelatedObjectsUsingCache(_ objects: [DatabaseObject], _ database: FMDatabase) {
@ -159,7 +230,7 @@ private extension DatabaseLookupTable {
func fetchRelatedObjectsWithIDs(_ relatedObjectIDs: Set<String>, _ database: FMDatabase) -> [DatabaseObject]? {
guard let relatedObjects = relatedTable?.fetchObjectsWithIDs(relatedObjectIDs, database), !relatedObjects.isEmpty else {
guard let relatedObjects = relatedTable?.fetchObjectsWithIDs(relatedObjectIDs, in: database), !relatedObjects.isEmpty else {
return nil
}
return relatedObjects
@ -198,7 +269,7 @@ private extension DatabaseLookupTable {
}
}
struct LookupTable {
private struct LookupTable {
private let dictionary: [String: Set<String>] // objectID: Set<relatedObjectID>
@ -241,7 +312,7 @@ struct LookupTable {
}
}
struct LookupValue: Hashable {
private struct LookupValue: Hashable {
let objectID: String
let relatedObjectID: String
@ -319,16 +390,3 @@ private final class DatabaseLookupTableCache {
}
}
private extension Set where Element == LookupValue {
func objectIDs() -> Set<String> {
return Set<String>(self.map { $0.objectID })
}
func relatedObjectIDs() -> Set<String> {
return Set<String>(self.map { $0.relatedObjectID })
}
}

View File

@ -31,4 +31,14 @@ extension Array where Element == DatabaseObject {
return Set(self.map { $0.databaseID })
}
func includesObjectWithDatabaseID(_ databaseID: String) -> Bool {
for object in self {
if object.databaseID == databaseID {
return true
}
}
return false
}
}

Binary file not shown.