Progress on lookup tables.

This commit is contained in:
Brent Simmons
2017-08-06 21:16:13 -07:00
parent 4503f771da
commit 570d70d8fe
6 changed files with 305 additions and 209 deletions

View File

@@ -11,12 +11,19 @@ import Foundation
// Implement a lookup table for a many-to-many relationship.
// 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.
//
// foreignIDsWithNoRelationship: caches the foreignIDs where its known that theres no relationship.
// lookupsByForeignID: caches the LookupValues for a foreignID.
public struct LookupTable {
typealias LookupTableDictionary = [String: Set<LookupValue>] // key is foreignID
public final class LookupTable {
let name: String
let primaryKey: String
let foreignKey: String
private var foreignIDsWithNoRelationship = Set<String>()
private var lookupsByForeignID = LookupTableDictionary()
public init(name: String, primaryKey: String, foreignKey: String) {
@@ -25,17 +32,94 @@ public struct LookupTable {
self.foreignKey = foreignKey
}
public func fetchLookupValues(_ foreignIDs: Set<String>, database: FMDatabase) -> Set<LookupValue> {
public func fetchLookupTableDictionary(_ foreignIDs: Set<String>, _ database: FMDatabase) -> LookupTableDictionary? {
guard let resultSet = database.rs_selectRowsWhereKey(foreignKey, inValues: Array(foreignIDs), tableName: name) else {
return Set<LookupValue>()
let foreignIDsToLookup = foreignIDs.subtracting(foreignIDsWithNoRelationship)
if foreignIDsToLookup.isEmpty {
return nil
}
return lookupValuesWithResultSet(resultSet)
var lookupValues = Set<LookupValue>()
var foreignIDsToFetchFromDatabase = Set<String>()
// Pull from cache.
for oneForeignID in foreignIDsToLookup {
if let cachedLookups = lookupsByForeignID[oneForeignID] {
lookupValues.formUnion(cachedLookups)
}
else {
foreignIDsToFetchFromDatabase.insert(oneForeignID)
}
}
if !foreignIDsToFetchFromDatabase.isEmpty {
if let resultSet = database.rs_selectRowsWhereKey(foreignKey, inValues: Array(foreignIDsToLookup), tableName: name) {
lookupValues.formUnion(lookupValuesWithResultSet(resultSet))
}
}
cacheNotFoundForeignIDs(lookupValues, foreignIDsToFetchFromDatabase)
cacheLookupValues(lookupValues)
return lookupTableDictionary(with: lookupValues)
}
public func removeLookupsForForeignIDs(_ foreignIDs: Set<String>, _ database: FMDatabase) {
let foreignIDsToRemove = foreignIDs.subtracting(foreignIDsWithNoRelationship)
if foreignIDsToRemove.isEmpty {
return
}
for oneForeignID in foreignIDsToRemove {
lookupsByForeignID[oneForeignID] = nil
}
foreignIDsWithNoRelationship.formUnion(foreignIDsToRemove)
database.rs_deleteRowsWhereKey(foreignKey, inValues: Array(foreignIDsToRemove), tableName: name)
}
}
private extension LookupTable {
func addToLookupTableDictionary(_ lookupValues: Set<LookupValue>, _ table: inout LookupTableDictionary) {
for lookupValue in lookupValues {
let foreignID = lookupValue.foreignID
let primaryID = lookupValue.primaryID
if table[foreignID] == nil {
table[foreignID] = Set([primaryID])
}
else {
table[foreignID]!.insert(primaryID)
}
}
}
func lookupTableDictionary(with lookupValues: Set<LookupValue>) -> LookupTableDictionary {
var d = LookupTableDictionary()
addToLookupTableDictionary(lookupValues, &d)
return d
}
func cacheLookupValues(_ lookupValues: Set<LookupValue>) {
addToLookupTableDictionary(lookupValues, &lookupsByForeignID)
}
func cacheNotFoundForeignIDs(_ lookupValues: Set<LookupValue>, _ foreignIDs: Set<String>) {
// Note where nothing was found, and cache the foreignID in foreignIDsWithNoRelationship.
let foundForeignIDs = Set(lookupValues.map { $0.foreignID })
for foreignID in foreignIDs {
if !foundForeignIDs.contains(foreignID) {
foreignIDsWithNoRelationship.insert(foreignID)
}
}
}
func lookupValuesWithResultSet(_ resultSet: FMResultSet) -> Set<LookupValue> {
return resultSet.mapToSet(lookupValueWithRow)
@@ -71,3 +155,4 @@ public struct LookupValue: Hashable {
return lhs.primaryID == rhs.primaryID && lhs.foreignID == rhs.foreignID
}
}