More work on relationships.
This commit is contained in:
parent
7f5277f3e0
commit
a4b27075a7
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol DatabaseTable {
|
||||
public protocol DatabaseTable: class {
|
||||
|
||||
var name: String {get}
|
||||
|
||||
|
@ -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<String>()
|
||||
private var cache = LookupTable(Set<LookupValue>())
|
||||
|
||||
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<String>, _ 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<String>, _ database: FMDatabase) -> LookupTable? {
|
||||
|
||||
let foreignIDsToLookup = foreignIDs.subtracting(foreignIDsWithNoRelationship)
|
||||
if foreignIDsToLookup.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
var lookupValues = Set<LookupValue>()
|
||||
var foreignIDsToFetchFromDatabase = Set<String>()
|
||||
|
||||
|
||||
// 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<String> {
|
||||
|
||||
var primaryIDs = Set<String>()
|
||||
|
||||
for (_, lookupValues) in lookupTableDictionary {
|
||||
primaryIDs.formUnion(lookupValues.primaryIDs())
|
||||
}
|
||||
|
||||
return primaryIDs
|
||||
}
|
||||
|
||||
public func removeLookupsForForeignIDs(_ foreignIDs: Set<String>, _ 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<String>, _ database: FMDatabase) -> Set<LookupValue>? {
|
||||
|
||||
guard !foreignIDs.isEmpty, let resultSet = database.rs_selectRowsWhereKey(foreignKey, inValues: Array(foreignIDsToLookup), tableName: name) else {
|
||||
return nil
|
||||
}
|
||||
return lookupValuesWithResultSet(resultSet)
|
||||
}
|
||||
|
||||
func addToLookupTableDictionary(_ lookupValues: Set<LookupValue>, _ 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<LookupValue>) -> LookupTableDictionary {
|
||||
|
||||
var d = LookupTableDictionary()
|
||||
addToLookupTableDictionary(lookupValues, &d)
|
||||
return d
|
||||
}
|
||||
|
||||
func cacheLookupValues(_ lookupValues: Set<LookupValue>) {
|
||||
|
||||
addToLookupTableDictionary(lookupValues, &lookupsByForeignID)
|
||||
return LookupTable(lookupValues)
|
||||
}
|
||||
|
||||
func cacheNotFoundForeignIDs(_ lookupValues: Set<LookupValue>, _ foreignIDs: Set<String>) {
|
||||
|
||||
|
||||
// Note where nothing was found, and cache the foreignID in foreignIDsWithNoRelationship.
|
||||
|
||||
|
||||
let foundForeignIDs = lookupValues.foreignIDs()
|
||||
var foreignIDsToRemove = Set<String>()
|
||||
for foreignID in foreignIDs {
|
||||
@ -163,7 +126,28 @@ private extension DatabaseLookupTable {
|
||||
|
||||
cache.removeLookupValuesForForeignIDs(foreignIDsToRemove)
|
||||
}
|
||||
|
||||
|
||||
func removeLookupsForForeignIDs(_ foreignIDs: Set<String>, _ 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<String>, _ database: FMDatabase) -> Set<LookupValue>? {
|
||||
|
||||
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<LookupValue> {
|
||||
|
||||
return resultSet.mapToSet(lookupValueWithRow)
|
||||
@ -182,42 +166,26 @@ private extension DatabaseLookupTable {
|
||||
|
||||
}
|
||||
|
||||
final class LookupTable {
|
||||
private class LookupTable {
|
||||
|
||||
var lookupValues: Set<LookupValue>
|
||||
var dictionary = [String: Set<LookupValues>]()
|
||||
var dictionary = [String: Set<LookupValue>]()
|
||||
|
||||
init(lookupValues: Set<LookupValue>) {
|
||||
init(_ lookupValues: Set<LookupValue>) {
|
||||
|
||||
self.lookupValues = lookupValues
|
||||
addLookupValuesToDictionary()
|
||||
addLookupValues(lookupValues)
|
||||
}
|
||||
|
||||
func primaryIDs() -> Set<String> {
|
||||
|
||||
return lookupValues.primaryIDs()
|
||||
var ids = Set<String>()
|
||||
for (_, lookupValues) in dictionary {
|
||||
ids.formUnion(lookupValues.primaryIDs())
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func addLookupValues(_ values: Set<LookupValue>) {
|
||||
|
||||
lookupValues.formUnion(values)
|
||||
addLookupValuesToDictionary(values)
|
||||
}
|
||||
|
||||
func removeLookupValuesForForeignIDs(_ foreignIDs: Set<String>) {
|
||||
|
||||
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<LookupValue>) {
|
||||
|
||||
for lookupValue in values {
|
||||
let foreignID = lookupValue.foreignID
|
||||
if self[foreignID] == nil {
|
||||
@ -229,6 +197,13 @@ final class LookupTable {
|
||||
}
|
||||
}
|
||||
|
||||
func removeLookupValuesForForeignIDs(_ foreignIDs: Set<String>) {
|
||||
|
||||
for foreignID in foreignIDs {
|
||||
self[foreignID] = nil
|
||||
}
|
||||
}
|
||||
|
||||
subscript(_ foreignID: String) -> Set<LookupValue>? {
|
||||
get {
|
||||
return dictionary[foreignID]
|
||||
@ -239,7 +214,7 @@ final class LookupTable {
|
||||
}
|
||||
}
|
||||
|
||||
struct LookupValue: Hashable {
|
||||
private struct LookupValue: Hashable {
|
||||
|
||||
let primaryID: String
|
||||
let foreignID: String
|
||||
|
@ -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<String> {
|
||||
|
||||
return Set(self.map { $0.databaseID })
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user