From a9bb9a2872e4eb26be1aabbabb4aa7a2ecb4221e Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 12 Aug 2017 16:16:22 -0700 Subject: [PATCH] Yet more progress on relationships. --- Frameworks/RSDatabase/DatabaseTable.swift | 2 +- .../RSDatabase/DatabaseLookupTable.swift | 235 ++++++++---------- .../RSDatabase/DatabaseObject.swift | 8 +- 3 files changed, 113 insertions(+), 132 deletions(-) diff --git a/Frameworks/RSDatabase/DatabaseTable.swift b/Frameworks/RSDatabase/DatabaseTable.swift index e49053c1d..cf128a913 100644 --- a/Frameworks/RSDatabase/DatabaseTable.swift +++ b/Frameworks/RSDatabase/DatabaseTable.swift @@ -8,7 +8,7 @@ import Foundation -public protocol DatabaseTable { +public protocol DatabaseTable: class { var name: String {get} diff --git a/Frameworks/RSDatabase/RSDatabase/DatabaseLookupTable.swift b/Frameworks/RSDatabase/RSDatabase/DatabaseLookupTable.swift index b907d9b5b..6306cf0f8 100644 --- a/Frameworks/RSDatabase/RSDatabase/DatabaseLookupTable.swift +++ b/Frameworks/RSDatabase/RSDatabase/DatabaseLookupTable.swift @@ -12,32 +12,82 @@ import Foundation // Example: CREATE TABLE if not EXISTS authorLookup (authorID TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(authorID, articleID)); // authorID is primaryKey; articleID is foreignKey. - public final class DatabaseLookupTable { - public let name: String - let primaryKey: String - let foreignKey: String + private let name: String + private let primaryKey: String + private let foreignKey: String + private let relationshipName: String + private weak var relatedTable: DatabaseTable? private var foreignIDsWithNoRelationship = Set() private var cache = LookupTable(Set()) - public init(name: String, primaryKey: String, foreignKey: String) { + public init(name: String, primaryKey: String, foreignKey: String, relatedTable: DatabaseTable, relationshipName: String) { self.name = name self.primaryKey = primaryKey self.foreignKey = foreignKey + self.relatedTable = relatedTable + self.relationshipName = relationshipName } - public func fetchLookupTable(_ foreignIDs: Set, _ database: FMDatabase) -> LookupTable? { + public func attachRelationships(to objects: [DatabaseObject], database: FMDatabase) { + + guard !objects.isEmpty, let lookupTable = fetchLookupTable(objects.databaseIDs(), database) else { + return; + } + attachRelationshipsUsingLookupTable(to: objects, lookupTable: lookupTable, database: database) + } + + public func saveRelationships(for objects: [DatabaseObject], relationshipName: String, database: FMDatabase) { + + + } + + public func removeRelationships(for objects: [DatabaseObject], relationshipName: String, database: FMDatabase) { + + removeLookupsForForeignIDs(objects.databaseIDs(), database) + } + +} +private extension DatabaseLookupTable { + + func attachRelationshipsUsingLookupTable(to objects: [DatabaseObject], lookupTable: LookupTable, database: FMDatabase) { + + let primaryIDs = lookupTable.primaryIDs() + if (primaryIDs.isEmpty) { + return + } + + guard let relatedObjects: [DatabaseObject] = relatedTable?.fetchObjectsWithIDs(primaryIDs, database), !relatedObjects.isEmpty else { + return + } + + let relatedObjectsDictionary = relatedObjects.dictionary() + + for object in objects { + let identifier = object.databaseID + if let lookupValues = lookupTable[identifier], !lookupValues.isEmpty { + let primaryIDs = lookupValues.primaryIDs() + let oneObjectRelatedObjects = primaryIDs.flatMap{ (primaryID) -> DatabaseObject? in + return relatedObjectsDictionary[primaryID] + } + object.setRelatedObjects(oneObjectRelatedObjects, name: relationshipName) + } + } + } + + func fetchLookupTable(_ foreignIDs: Set, _ database: FMDatabase) -> LookupTable? { + let foreignIDsToLookup = foreignIDs.subtracting(foreignIDsWithNoRelationship) if foreignIDsToLookup.isEmpty { return nil } - + var lookupValues = Set() var foreignIDsToFetchFromDatabase = Set() - + // Pull from cache. for foreignID in foreignIDsToLookup { if let cachedLookups = cache[foreignID] { @@ -47,111 +97,24 @@ public final class DatabaseLookupTable { foreignIDsToFetchFromDatabase.insert(foreignID) } } - + // Fetch from database. - if let fetchedLookupValues = fetchLookupValues(foreignIDsToFetchFromDatabase, database) { + let fetchedLookupValues = fetchLookupValues(foreignIDsToFetchFromDatabase, database) + if let fetchedLookupValues = fetchedLookupValues { lookupValues.formUnion(fetchedLookupValues) + cache.addLookupValues(fetchedLookupValues) } - + // Maintain cache. cacheNotFoundForeignIDs(lookupValues, foreignIDsToFetchFromDatabase) - cache.addLookupValues(fetchedLookupValues) - - return LookupTable(lookupValues: lookupValues) - } - - public func attachRelationships(to objects: [DatabaseObject], table: DatabaseTable, lookupTableDictionary: LookupTableDictionary, relationshipName: String, database: FMDatabase) { - let primaryIDs = primaryIDsInLookupTableDictionary(lookupTableDictionary) - if (primaryIDs.isEmpty) { - return - } - - let relatedObjects: [DatabaseObject] = table.fetchObjectsWithIDs(primaryIDs, database) - if relatedObjects.isEmpty { - return - } - - let relatedObjectsDictionary = relatedObjects.dictionary() - - for object in objects { - let identifier = object.databaseID - if let lookupValues = lookupTableDictionary[identifier], !lookupValues.isEmpty { - let primaryIDs = lookupValues.primaryIDs() - let oneObjectRelatedObjects = primaryIDs.flatMap{ (primaryID) -> DatabaseObject? in - return relatedObjectsDictionary[primaryID] - } - object.attachRelationshipWithObjects(oneObjectRelatedObjects, name: relationshipName) - } - } - } - - func primaryIDsInLookupTableDictionary(_ lookupTableDictionary: LookupTableDictionary) -> Set { - - var primaryIDs = Set() - - for (_, lookupValues) in lookupTableDictionary { - primaryIDs.formUnion(lookupValues.primaryIDs()) - } - - return primaryIDs - } - - public func removeLookupsForForeignIDs(_ foreignIDs: Set, _ database: FMDatabase) { - - let foreignIDsToRemove = foreignIDs.subtracting(foreignIDsWithNoRelationship) - if foreignIDsToRemove.isEmpty { - return - } - - for oneForeignID in foreignIDsToRemove { - cache[oneForeignID] = nil - } - foreignIDsWithNoRelationship.formUnion(foreignIDsToRemove) - - database.rs_deleteRowsWhereKey(foreignKey, inValues: Array(foreignIDsToRemove), tableName: name) - } -} - -private extension DatabaseLookupTable { - - func fetchLookupValues(_ foreignIDs: Set, _ database: FMDatabase) -> Set? { - - guard !foreignIDs.isEmpty, let resultSet = database.rs_selectRowsWhereKey(foreignKey, inValues: Array(foreignIDsToLookup), tableName: name) else { - return nil - } - return lookupValuesWithResultSet(resultSet) - } - - func addToLookupTableDictionary(_ lookupValues: Set, _ table: inout LookupTableDictionary) { - - for lookupValue in lookupValues { - let foreignID = lookupValue.foreignID - if table[foreignID] == nil { - table[foreignID] = Set([lookupValue]) - } - else { - table[foreignID]!.insert(lookupValue) - } - } - } - - func lookupTableDictionary(with lookupValues: Set) -> LookupTableDictionary { - - var d = LookupTableDictionary() - addToLookupTableDictionary(lookupValues, &d) - return d - } - - func cacheLookupValues(_ lookupValues: Set) { - - addToLookupTableDictionary(lookupValues, &lookupsByForeignID) + return LookupTable(lookupValues) } func cacheNotFoundForeignIDs(_ lookupValues: Set, _ foreignIDs: Set) { - + // Note where nothing was found, and cache the foreignID in foreignIDsWithNoRelationship. - + let foundForeignIDs = lookupValues.foreignIDs() var foreignIDsToRemove = Set() for foreignID in foreignIDs { @@ -163,7 +126,28 @@ private extension DatabaseLookupTable { cache.removeLookupValuesForForeignIDs(foreignIDsToRemove) } - + + func removeLookupsForForeignIDs(_ foreignIDs: Set, _ database: FMDatabase) { + + let foreignIDsToRemove = foreignIDs.subtracting(foreignIDsWithNoRelationship) + if foreignIDsToRemove.isEmpty { + return + } + + cache.removeLookupValuesForForeignIDs(foreignIDsToRemove) + foreignIDsWithNoRelationship.formUnion(foreignIDsToRemove) + + database.rs_deleteRowsWhereKey(foreignKey, inValues: Array(foreignIDsToRemove), tableName: name) + } + + func fetchLookupValues(_ foreignIDs: Set, _ database: FMDatabase) -> Set? { + + guard !foreignIDs.isEmpty, let resultSet = database.rs_selectRowsWhereKey(foreignKey, inValues: Array(foreignIDs), tableName: name) else { + return nil + } + return lookupValuesWithResultSet(resultSet) + } + func lookupValuesWithResultSet(_ resultSet: FMResultSet) -> Set { return resultSet.mapToSet(lookupValueWithRow) @@ -182,42 +166,26 @@ private extension DatabaseLookupTable { } -final class LookupTable { +private class LookupTable { - var lookupValues: Set - var dictionary = [String: Set]() + var dictionary = [String: Set]() - init(lookupValues: Set) { + init(_ lookupValues: Set) { - self.lookupValues = lookupValues - addLookupValuesToDictionary() + addLookupValues(lookupValues) } func primaryIDs() -> Set { - return lookupValues.primaryIDs() + var ids = Set() + for (_, lookupValues) in dictionary { + ids.formUnion(lookupValues.primaryIDs()) + } + return ids } func addLookupValues(_ values: Set) { - lookupValues.formUnion(values) - addLookupValuesToDictionary(values) - } - - func removeLookupValuesForForeignIDs(_ foreignIDs: Set) { - - for foreignID in foreignIDs { - self[foreignID] = nil - } - - let lookupValuesToRemove = lookupValues.filter { (lookupValue) -> Bool in - foreignIDs.contains(lookupValue.foreignID) - } - lookupValues.subtract(lookupValuesToRemove) - } - - func addLookupValuesToDictionary(_ values: Set) { - for lookupValue in values { let foreignID = lookupValue.foreignID if self[foreignID] == nil { @@ -229,6 +197,13 @@ final class LookupTable { } } + func removeLookupValuesForForeignIDs(_ foreignIDs: Set) { + + for foreignID in foreignIDs { + self[foreignID] = nil + } + } + subscript(_ foreignID: String) -> Set? { get { return dictionary[foreignID] @@ -239,7 +214,7 @@ final class LookupTable { } } -struct LookupValue: Hashable { +private struct LookupValue: Hashable { let primaryID: String let foreignID: String diff --git a/Frameworks/RSDatabase/RSDatabase/DatabaseObject.swift b/Frameworks/RSDatabase/RSDatabase/DatabaseObject.swift index cb1ff203e..45d338286 100644 --- a/Frameworks/RSDatabase/RSDatabase/DatabaseObject.swift +++ b/Frameworks/RSDatabase/RSDatabase/DatabaseObject.swift @@ -12,7 +12,8 @@ public protocol DatabaseObject { var databaseID: String { get } - func attachRelationshipWithObjects(_ objects: [DatabaseObject], name: String) + func setRelatedObjects(_ objects: [DatabaseObject], name: String) + func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? } extension Array where Element == DatabaseObject { @@ -25,4 +26,9 @@ extension Array where Element == DatabaseObject { } return d } + + func databaseIDs() -> Set { + + return Set(self.map { $0.databaseID }) + } }