Make progress on related objects.

This commit is contained in:
Brent Simmons
2017-09-11 06:46:32 -07:00
parent 07a44f7af0
commit 5ee58458a2
11 changed files with 288 additions and 173 deletions

View File

@@ -33,8 +33,10 @@
84419AE71B5ABD7F00C26BB2 /* RSDatabaseQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 84419ADD1B5ABD7F00C26BB2 /* RSDatabaseQueue.m */; };
84419B051B5ABFF700C26BB2 /* FMResultSet+RSExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 84419B031B5ABFF700C26BB2 /* FMResultSet+RSExtras.h */; settings = {ATTRIBUTES = (Public, ); }; };
84419B061B5ABFF700C26BB2 /* FMResultSet+RSExtras.m in Sources */ = {isa = PBXBuildFile; fileRef = 84419B041B5ABFF700C26BB2 /* FMResultSet+RSExtras.m */; };
844D97411F2D32F300CEDDEA /* DatabaseObjectCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844D97401F2D32F300CEDDEA /* DatabaseObjectCache.swift */; };
844ECFB91F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */; };
848E22541F6652990031D7C5 /* DatabaseRelatedObjectsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */; };
848E22561F6652C70031D7C5 /* RelatedObjectsLookupTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848E22551F6652C70031D7C5 /* RelatedObjectsLookupTable.swift */; };
848E22581F6653960031D7C5 /* RelatedObjectIDsLookupTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848E22571F6653960031D7C5 /* RelatedObjectIDsLookupTable.swift */; };
849BF8C61C94FB8E0071D1DA /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 849BF8C51C94FB8E0071D1DA /* libsqlite3.tbd */; };
84ABC1D11F364B07000DCC55 /* DatabaseLookupTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */; };
84ABC1D21F364B07000DCC55 /* DatabaseLookupTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */; };
@@ -73,10 +75,11 @@
84419ADD1B5ABD7F00C26BB2 /* RSDatabaseQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RSDatabaseQueue.m; path = RSDatabase/RSDatabaseQueue.m; sourceTree = "<group>"; };
84419B031B5ABFF700C26BB2 /* FMResultSet+RSExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "FMResultSet+RSExtras.h"; path = "RSDatabase/FMResultSet+RSExtras.h"; sourceTree = "<group>"; };
84419B041B5ABFF700C26BB2 /* FMResultSet+RSExtras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "FMResultSet+RSExtras.m"; path = "RSDatabase/FMResultSet+RSExtras.m"; sourceTree = "<group>"; };
844D97401F2D32F300CEDDEA /* DatabaseObjectCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatabaseObjectCache.swift; path = RSDatabase/DatabaseObjectCache.swift; sourceTree = "<group>"; };
844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatabaseRelatedObjectsTable.swift; path = RSDatabase/DatabaseRelatedObjectsTable.swift; sourceTree = "<group>"; };
844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseRelatedObjectsTable.swift; sourceTree = "<group>"; };
848E22551F6652C70031D7C5 /* RelatedObjectsLookupTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelatedObjectsLookupTable.swift; sourceTree = "<group>"; };
848E22571F6653960031D7C5 /* RelatedObjectIDsLookupTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelatedObjectIDsLookupTable.swift; sourceTree = "<group>"; };
849BF8C51C94FB8E0071D1DA /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatabaseLookupTable.swift; path = RSDatabase/DatabaseLookupTable.swift; sourceTree = "<group>"; };
84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseLookupTable.swift; sourceTree = "<group>"; };
84C6DD001F395C13009AFB47 /* DatabaseObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatabaseObject.swift; path = RSDatabase/DatabaseObject.swift; sourceTree = "<group>"; };
84DDF18B1C94FC45005E6CF5 /* FMDatabase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FMDatabase.h; sourceTree = "<group>"; };
84DDF18C1C94FC45005E6CF5 /* FMDatabase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FMDatabase.m; sourceTree = "<group>"; };
@@ -136,6 +139,17 @@
name = Frameworks;
sourceTree = "<group>";
};
848E22531F66528C0031D7C5 /* Related Objects */ = {
isa = PBXGroup;
children = (
84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */,
848E22551F6652C70031D7C5 /* RelatedObjectsLookupTable.swift */,
848E22571F6653960031D7C5 /* RelatedObjectIDsLookupTable.swift */,
844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */,
);
path = "Related Objects";
sourceTree = "<group>";
};
84DDF18A1C94FC45005E6CF5 /* FMDB */ = {
isa = PBXGroup;
children = (
@@ -164,9 +178,7 @@
84419AD91B5ABD7400C26BB2 /* NSString+RSDatabase.m */,
84C6DD001F395C13009AFB47 /* DatabaseObject.swift */,
840405DA1F1C158C00DF0296 /* DatabaseTable.swift */,
844ECFB81F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift */,
84ABC1D01F364B07000DCC55 /* DatabaseLookupTable.swift */,
844D97401F2D32F300CEDDEA /* DatabaseObjectCache.swift */,
848E22531F66528C0031D7C5 /* Related Objects */,
84DDF18A1C94FC45005E6CF5 /* FMDB */,
84F22C5A1B52E0D9000060CE /* Info.plist */,
849BF8C51C94FB8E0071D1DA /* libsqlite3.tbd */,
@@ -358,6 +370,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
848E22541F6652990031D7C5 /* DatabaseRelatedObjectsTable.swift in Sources */,
8400AC001E0CFC0700AA7C57 /* RSDatabaseQueue.m in Sources */,
8400AC061E0CFC0700AA7C57 /* NSString+RSDatabase.m in Sources */,
84ABC1D21F364B07000DCC55 /* DatabaseLookupTable.swift in Sources */,
@@ -385,7 +398,8 @@
844ECFB91F5B17F9005E405A /* DatabaseRelatedObjectsTable.swift in Sources */,
84419B061B5ABFF700C26BB2 /* FMResultSet+RSExtras.m in Sources */,
84DDF1991C94FC45005E6CF5 /* FMDatabaseAdditions.m in Sources */,
844D97411F2D32F300CEDDEA /* DatabaseObjectCache.swift in Sources */,
848E22581F6653960031D7C5 /* RelatedObjectIDsLookupTable.swift in Sources */,
848E22561F6652C70031D7C5 /* RelatedObjectsLookupTable.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@@ -1,85 +0,0 @@
//
// DatabaseObjectCache.swift
// RSDatabase
//
// Created by Brent Simmons on 7/29/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
// Not thread-safe.
public final class DatabaseObjectCache {
private var dictionary = [String: DatabaseObject]()
public init() {
// Compiler seems to want a public init method.
}
public func addObjects(_ objects: [DatabaseObject]) {
objects.forEach { add($0) }
}
public func addObjectsNotCached(_ objects: [DatabaseObject]) {
objects.forEach { addIfNotCached($0) }
}
public func add(_ object: DatabaseObject) {
self[object.databaseID] = object
}
public func addIfNotCached(_ object: DatabaseObject) {
let identifier = object.databaseID
if let _ = self[identifier] {
return
}
self[identifier] = object
}
public func removeObjects(_ objects: [DatabaseObject]) {
objects.forEach { removeObject($0) }
}
public func removeObject(_ object: DatabaseObject) {
self[object.databaseID] = nil
}
public func uniquedObjects(_ objects: [DatabaseObject]) -> [DatabaseObject] {
// Return cached version of each object.
// When an object is not already cached, cache it,
// then consider that version the unique version.
return objects.map { (object) -> DatabaseObject in
if let cachedObject = self[object.databaseID] {
return cachedObject
}
add(object)
return object
}
}
public func objectWithIDIsCached(_ identifier: String) -> Bool {
return self[identifier] != nil
}
public subscript(_ identifier: String) -> DatabaseObject? {
get {
return dictionary[identifier]
}
set {
dictionary[identifier] = newValue
}
}
}

View File

@@ -20,7 +20,8 @@ public final class DatabaseLookupTable {
private let relationshipName: String
private let relatedTable: DatabaseRelatedObjectsTable
private let cache: DatabaseLookupTableCache
private var objectIDsWithNoRelatedObjects = Set<String>()
public init(name: String, objectIDKey: String, relatedObjectIDKey: String, relatedTable: DatabaseRelatedObjectsTable, relationshipName: String) {
self.name = name
@@ -31,6 +32,31 @@ public final class DatabaseLookupTable {
self.cache = DatabaseLookupTableCache(relationshipName)
}
public func fetchRelatedObjects(for objectIDs: Set<String>, in database: FMDatabase) -> RelatedObjectsLookupTable? {
let objectIDsThatMayHaveRelatedObjects = objectIDs.subtracting(objectIDsWithNoRelatedObjects)
if objectIDsThatMayHaveRelatedObjects.isEmpty {
return nil
}
guard let lookupTable = fetchLookupTable(objectIDsThatMayHaveRelatedObjects, database) else {
objectIDsWithNoRelatedObjects.formUnion(objectIDsThatMayHaveRelatedObjects)
return nil
}
if let relatedObjects = fetchRelatedObjectsReferencedByLookupTable(LookupTable, database) {
let relatedObjectsDictionary = relatedObjectsDictionary(lookupTable, relatedObjects)
let objectIDsWithNoFetchedRelatedObjects = objectIDsThatMayHaveRelatedObjects.subtracting(Set(relatedObjectsDictionary.keys))
objectIDsWithNoRelatedObjects.formUnion(objectIDsWithNoFetchedRelatedObjects)
return relatedObjectsDictionary
}
return nil
}
public func attachRelatedObjects(to objects: [DatabaseObject], in database: FMDatabase) {
let objectsThatMayHaveRelatedObjects = cache.objectsThatMayHaveRelatedObjects(objects)
@@ -198,6 +224,16 @@ private extension DatabaseLookupTable {
attachRelatedObjectsUsingLookupTable(objects, lookupTable, database)
}
func fetchRelatedObjectsReferencedByLookupTable(_ lookupTable: LookupTable, _ database: FMDatabase) -> [DatabaseObject]? {
let relatedObjectIDs = lookupTable.relatedObjectIDs()
if (relatedObjectIDs.isEmpty) {
return nil
}
return fetchRelatedObjectsWithIDs(relatedObjectIDs)
}
func attachRelatedObjectsUsingLookupTable(_ objects: [DatabaseObject], _ lookupTable: LookupTable, _ database: FMDatabase) {
let relatedObjectIDs = lookupTable.relatedObjectIDs()
@@ -269,6 +305,17 @@ private extension DatabaseLookupTable {
}
return LookupValue(objectID: objectID, relatedObjectID: relatedObjectID)
}
func relatedObjectsDictionary(_ lookupTable: LookupTable, relatedObjects: [DatabaseObject]) -> RelatedObjectsDictionary? {
var relatedObjectsDictionary = RelatedObjectsDictionary()
let d = relatedObjects.dictionary()
return relatedObjectsDictionary.isEmpty ? nil : relatedObjectsDictionary
}
}
// MARK: -
@@ -300,6 +347,11 @@ private struct LookupTable {
self.init(dictionary: d)
}
func objectIDs() -> Set<String> {
return Set(dictionary.keys)
}
func relatedObjectIDs() -> Set<String> {
var ids = Set<String>()
@@ -376,12 +428,18 @@ private final class DatabaseLookupTableCache {
}
}
func objectsThatMayHaveRelatedObjects(_ objects: [DatabaseObject]) -> [DatabaseObject] {
func objectIDsThatMayHaveRelatedObjects(_ objectIDs: Set<String>) -> Set<String> {
// Filter out objects that are known to have no related objects
return objects.filter{ !objectIDsWithNoRelationship.contains($0.databaseID) }
return Set(objectIDs.filter{ !objectIDsWithNoRelationship.contains($0) })
}
// func objectsThatMayHaveRelatedObjects(_ objects: [DatabaseObject]) -> [DatabaseObject] {
//
// // Filter out objects that are known to have no related objects
// return objects.filter{ !objectIDsWithNoRelationship.contains($0.databaseID) }
// }
func lookupTableForObjectIDs(_ objectIDs: Set<String>) -> LookupTable {
var d = [String: Set<String>]()

View File

@@ -0,0 +1,59 @@
//
// RelatedObjectIDsLookupTable.swift
// RSDatabase
//
// Created by Brent Simmons on 9/10/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
// Maps objectIDs to Set<String> where the Strings are relatedObjectIDs.
struct RelatedObjectIDsLookupTable {
private let dictionary: [String: Set<String>] // objectID: Set<relatedObjectID>
init(dictionary: [String: Set<String>]) {
self.dictionary = dictionary
}
init(lookupValues: Set<LookupValue>) {
var d = [String: Set<String>]()
for lookupValue in lookupValues {
let objectID = lookupValue.objectID
let relatedObjectID: String = lookupValue.relatedObjectID
if d[objectID] == nil {
d[objectID] = Set([relatedObjectID])
}
else {
d[objectID]!.insert(relatedObjectID)
}
}
self.init(dictionary: d)
}
func objectIDs() -> Set<String> {
return Set(dictionary.keys)
}
func relatedObjectIDs() -> Set<String> {
var ids = Set<String>()
for (_, relatedObjectIDs) in dictionary {
ids.formUnion(relatedObjectIDs)
}
return ids
}
subscript(_ objectID: String) -> Set<String>? {
get {
return dictionary[objectID]
}
}
}

View File

@@ -0,0 +1,45 @@
//
// RelatedObjectsLookupTable.swift
// RSDatabase
//
// Created by Brent Simmons on 9/10/17.
// Copyright © 2017 Ranchero Software, LLC. All rights reserved.
//
import Foundation
public struct RelatedObjectsLookupTable {
private let dictionary: [String: [DatabaseObject]] // objectID: relatedObjects
init(relatedObjects: relatedObjects, lookupTable: LookupTable) {
var d = [String: [DatabaseObject]]()
let relatedObjectsDictionary = relatedObjects.dictionary()
let objectIDs = lookupTable.objectIDs()
for objectID in lookupTable.objectIDs() {
if let relatedObjectIDs = lookupTable[objectID] {
let relatedObjects = relatedObjectIDs.flatMap{ relatedObjectsDictionary[$0] }
if !relatedObjects.isEmpty {
d[objectID] = relatedObjects
}
}
}
self.dictionary = d
}
public func objectIDs() -> Set<String> {
return Set(dictionary.keys)
}
public subscript(_ objectID: String) -> [DatabaseObject]? {
get {
return dictionary[objectID]
}
}
}