Make progress on Database surgery.
This commit is contained in:
parent
54cfaefd81
commit
2ce577e9d4
@ -11,7 +11,7 @@ import RSCore
|
||||
|
||||
public struct Author: Hashable {
|
||||
|
||||
public let databaseID: String // calculated
|
||||
public let authorID: String // calculated
|
||||
public let name: String?
|
||||
public let url: String?
|
||||
public let avatarURL: String?
|
||||
|
@ -34,14 +34,12 @@ import Data
|
||||
final class AttachmentsTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
private let cacheByArticleID = ObjectCache<Attachment>(keyPathForID: \Attachment.articleID)
|
||||
private let cacheByDatabaseID = ObjectCache<Attachment>(keyPathForID: \Attachment.databaseID)
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
init(name: String) {
|
||||
|
||||
self.name = name
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
private var cachedAttachments = [String: Attachment]() // Attachment.databaseID key
|
||||
|
@ -17,45 +17,27 @@ import Data
|
||||
// CREATE TABLE if not EXISTS authorLookup (authorID TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(authorID, articleID));
|
||||
|
||||
|
||||
final class AuthorsTable: DatabaseTable {
|
||||
|
||||
struct AuthorsTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
private let cache = ObjectCache<Author>(keyPathForID: \Author.databaseID)
|
||||
private var articleIDToAuthorsCache = [String: Set<Author>]()
|
||||
private let authorsLookupTable = LookupTable(name: DatabaseTableName.authorsLookup, primaryKey: DatabaseKey.authorID, foreignKey: DatabaseKey.articleID)
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
init(name: String) {
|
||||
|
||||
self.name = name
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
// MARK: DatabaseTable Methods
|
||||
|
||||
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject] {
|
||||
|
||||
|
||||
}
|
||||
|
||||
func save(_ objects: [DatabaseObject], in database: FMDatabase) {
|
||||
<#code#>
|
||||
}
|
||||
|
||||
func attachAuthors(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
attachCachedAuthors(articles)
|
||||
|
||||
let articlesMissingAuthors = articlesNeedingAuthors(articles)
|
||||
if articlesMissingAuthors.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
let articleIDs = Set(articlesMissingAuthors.map { $0.databaseID })
|
||||
let authorTable = fetchAuthorsForArticleIDs(articleIDs, database)
|
||||
|
||||
for article in articlesMissingAuthors {
|
||||
|
||||
let articleID = article.databaseID
|
||||
|
||||
if let authors = authorTable?[articleID] {
|
||||
articleIDsWithNoAuthors.remove(articleID)
|
||||
article.authors = Array(authors)
|
||||
}
|
||||
else {
|
||||
articleIDsWithNoAuthors.insert(articleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension AuthorsTable {
|
||||
|
@ -63,3 +63,8 @@ public struct DatabaseKey {
|
||||
static let emailAddress = "emailAddress"
|
||||
}
|
||||
|
||||
public struct RelationshipName {
|
||||
|
||||
static let authors = "authors"
|
||||
static let tags = "tags"
|
||||
}
|
||||
|
@ -24,16 +24,17 @@ typealias ArticleResultBlock = (Set<Article>) -> Void
|
||||
|
||||
final class Database {
|
||||
|
||||
fileprivate let queue: RSDatabaseQueue
|
||||
private let queue: RSDatabaseQueue
|
||||
private let databaseFile: String
|
||||
private let articlesTable: ArticlesTable
|
||||
private let authorsTable: AuthorsTable
|
||||
private let authorsLookupTable: DatabaseLookupTable
|
||||
private let attachmentsTable: AttachmentsTable
|
||||
private let statusesTable: StatusesTable
|
||||
private let tagsTable: TagsTable
|
||||
fileprivate var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
||||
fileprivate let minimumNumberOfArticles = 10
|
||||
fileprivate weak var delegate: AccountDelegate?
|
||||
private let tagsLookupTable: DatabaseLookupTable
|
||||
private var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
||||
private let minimumNumberOfArticles = 10
|
||||
private weak var delegate: AccountDelegate?
|
||||
|
||||
init(databaseFile: String, delegate: AccountDelegate) {
|
||||
|
||||
@ -42,11 +43,15 @@ final class Database {
|
||||
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
|
||||
|
||||
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, queue: queue)
|
||||
self.authorsTable = AuthorsTable(name: DatabaseTableName.authors, queue: queue)
|
||||
self.attachmentsTable = AttachmentsTable(name: DatabaseTableName.attachments, queue: queue)
|
||||
self.statusesTable = StatusesTable(name: DatabaseTableName.statuses, queue: queue)
|
||||
self.tagsTable = TagsTable(name: DatabaseTableName.tags, queue: queue)
|
||||
self.attachmentsTable = AttachmentsTable(name: DatabaseTableName.attachments)
|
||||
self.statusesTable = StatusesTable(name: DatabaseTableName.statuses)
|
||||
|
||||
self.authorsTable = AuthorsTable(name: DatabaseTableName.authors)
|
||||
self.authorsLookupTable = DatabaseLookupTable(name: DatabaseTableName.authorsLookup, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.authorID, relatedTable: authorsTable, relationshipName: RelationshipName.authors)
|
||||
|
||||
let tagsTable = TagsTable(name: DatabaseTableName.tags)
|
||||
self.tagsLookupTable = DatabaseLookupTable(name: DatabaseTableName.tags, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.tagName, relatedTable: tagsTable, relationshipName: RelationshipName.tags)
|
||||
|
||||
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", ofType: "sql")!
|
||||
let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue)
|
||||
queue.createTables(usingStatements: createStatements as String)
|
||||
|
@ -20,6 +20,7 @@
|
||||
846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846146241F0ABC7400870CB3 /* RSParser.framework */; };
|
||||
84BB4BA21F119C5400858766 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B981F119C4900858766 /* RSCore.framework */; };
|
||||
84BB4BA91F11A32800858766 /* TagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB4BA81F11A32800858766 /* TagsTable.swift */; };
|
||||
84D0DEA11F4A429800073503 /* String+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D0DEA01F4A429800073503 /* String+Database.swift */; };
|
||||
84E156EA1F0AB80500F8CC05 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156E91F0AB80500F8CC05 /* Database.swift */; };
|
||||
84E156EC1F0AB80E00F8CC05 /* ArticlesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */; };
|
||||
84E156EE1F0AB81400F8CC05 /* StatusesTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */; };
|
||||
@ -124,6 +125,7 @@
|
||||
8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = "<group>"; };
|
||||
84BB4B8F1F119C4900858766 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = "<group>"; };
|
||||
84BB4BA81F11A32800858766 /* TagsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsTable.swift; sourceTree = "<group>"; };
|
||||
84D0DEA01F4A429800073503 /* String+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "String+Database.swift"; path = "Extensions/String+Database.swift"; sourceTree = "<group>"; };
|
||||
84E156E81F0AB75600F8CC05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84E156E91F0AB80500F8CC05 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticlesTable.swift; sourceTree = "<group>"; };
|
||||
@ -212,6 +214,7 @@
|
||||
845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */,
|
||||
84F20F901F1810DD00D8E682 /* Author+Database.swift */,
|
||||
8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */,
|
||||
84D0DEA01F4A429800073503 /* String+Database.swift */,
|
||||
845580711F0AEE49003CCFA1 /* AccountInfo.swift */,
|
||||
);
|
||||
name = Extensions;
|
||||
@ -468,6 +471,7 @@
|
||||
84F20F8F1F180D8700D8E682 /* AuthorsTable.swift in Sources */,
|
||||
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
|
||||
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */,
|
||||
84D0DEA11F4A429800073503 /* String+Database.swift in Sources */,
|
||||
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */,
|
||||
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
|
||||
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,
|
||||
|
@ -12,13 +12,22 @@ import RSDatabase
|
||||
|
||||
extension Author {
|
||||
|
||||
init?(databaseID: String, row: FMResultSet) {
|
||||
init?(authorID: String, row: FMResultSet) {
|
||||
|
||||
let name = row.string(forColumn: DatabaseKey.name)
|
||||
let url = row.string(forColumn: DatabaseKey.url)
|
||||
let avatarURL = row.string(forColumn: DatabaseKey.avatarURL)
|
||||
let emailAddress = row.string(forColumn: DatabaseKey.emailAddress)
|
||||
|
||||
self.init(databaseID: databaseID, name: name, url: url, avatarURL: avatarURL, emailAddress: emailAddress)
|
||||
self.init(authorID: authorID, name: name, url: url, avatarURL: avatarURL, emailAddress: emailAddress)
|
||||
}
|
||||
}
|
||||
|
||||
extension Author: DatabaseObject {
|
||||
|
||||
var databaseID: String {
|
||||
get {
|
||||
return authorID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
Frameworks/Database/Extensions/String+Database.swift
Normal file
22
Frameworks/Database/Extensions/String+Database.swift
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// String+Database.swift
|
||||
// Database
|
||||
//
|
||||
// Created by Brent Simmons on 8/20/17.
|
||||
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RSDatabase
|
||||
|
||||
// A tag is a String.
|
||||
// Extending tag to conform to DatabaseObject means extending String to conform to DatabaseObject.
|
||||
|
||||
extension String: DatabaseObject {
|
||||
|
||||
var databaseID: String {
|
||||
get {
|
||||
return self
|
||||
}
|
||||
}
|
||||
}
|
@ -18,13 +18,11 @@ import Data
|
||||
final class StatusesTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
private let cache = ObjectCache<ArticleStatus>(keyPathForID: \ArticleStatus.articleID)
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
init(name: String) {
|
||||
|
||||
self.name = name
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
||||
|
@ -11,225 +11,33 @@ import RSDatabase
|
||||
import Data
|
||||
|
||||
// Article->tags is a many-to-many relationship.
|
||||
// Since a tag is just a simple string, the tags table and the lookup table are the same table.
|
||||
// Since a tag is just a String, the tags table and the lookup table are the same table.
|
||||
// All the heavy lifting is done in DatabaseLookupTable.
|
||||
//
|
||||
// CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(tagName, articleID));
|
||||
// CREATE INDEX if not EXISTS tags_tagName_index on tags (tagName COLLATE NOCASE);
|
||||
|
||||
typealias TagNameSet = Set<String>
|
||||
|
||||
final class TagsTable: DatabaseTable {
|
||||
|
||||
struct TagsTable: DatabaseTable {
|
||||
|
||||
let name: String
|
||||
let queue: RSDatabaseQueue
|
||||
let lookupTable: LookupTable
|
||||
|
||||
init(name: String, queue: RSDatabaseQueue) {
|
||||
init(name: String) {
|
||||
|
||||
self.name = name
|
||||
self.queue = queue
|
||||
self.lookupTable = LookupTable(name: DatabaseTableName.tags, primaryKey: DatabaseKey.tagName, foreignKey: DatabaseKey.articleID)
|
||||
}
|
||||
|
||||
func attachTags(_ articles: Set<Article>, _ database: FMDatabase) {
|
||||
|
||||
guard let lookupTableDictionary = lookupTable.fetchLookupTableDictionary(articleIDs, database) else {
|
||||
return
|
||||
}
|
||||
|
||||
for article in articles {
|
||||
if let lookupValues = lookupTableDictionary[article.databaseID] {
|
||||
article.tags = lookupValues.tags()
|
||||
}
|
||||
}
|
||||
// MARK: DatabaseTable Methods
|
||||
|
||||
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject] {
|
||||
|
||||
// A tag is a string, and it is its own databaseID.
|
||||
return databaseIDs.map{ $0 as DatabaseObject }
|
||||
}
|
||||
|
||||
func save(_ objects: [DatabaseObject], in database: FMDatabase) {
|
||||
|
||||
// Nothing to do, since tags are saved in the lookup table, not in a separate table.
|
||||
}
|
||||
|
||||
// func saveTagsForArticles(_ articles: Set<Article>) {
|
||||
//
|
||||
// var articlesToSaveTags = Set<Article>()
|
||||
// var articlesToRemoveTags = Set<Article>()
|
||||
//
|
||||
// articles.forEach { (oneArticle) in
|
||||
//
|
||||
// if articleTagsMatchCache(oneArticle) {
|
||||
// return
|
||||
// }
|
||||
// if let tags = oneArticle.tags {
|
||||
// articlesToSaveTags.insert(oneArticle)
|
||||
// }
|
||||
// else {
|
||||
// articlesToRemoveTags.insert(oneArticle)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !articlesToSaveTags.isEmpty {
|
||||
// updateTagsForArticles(articlesToSaveTags)
|
||||
// }
|
||||
//
|
||||
// if !articlesToRemoveTags.isEmpty {
|
||||
// removeArticleFromTags(articlesToRemoveTags)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private extension TagsTable {
|
||||
|
||||
// func cacheTagsForArticle(_ article: Article, tags: TagNameSet) {
|
||||
//
|
||||
// articleIDsWithNoTags.remove(article.articleID)
|
||||
// articleIDCache[article.articleID] = tags
|
||||
// }
|
||||
//
|
||||
// func cachedTagsForArticleID(_ articleID: String) -> TagNameSet? {
|
||||
//
|
||||
// return articleIDsCache[articleID]
|
||||
// }
|
||||
//
|
||||
// func articleTagsMatchCache(_ article: Article) -> Bool {
|
||||
//
|
||||
// if let tags = article.tags {
|
||||
// return tags == articleIDCache[article.articleID]
|
||||
// }
|
||||
// return articleIDIsKnowToHaveNoTags(article.articleID)
|
||||
// }
|
||||
//
|
||||
// func articleIDIsKnownToHaveNoTags(_ articleID: String) -> Bool {
|
||||
//
|
||||
// return articleIDsWithNoTags.contains(articleID)
|
||||
// }
|
||||
//
|
||||
// func removeTagsFromCacheForArticleID(_ articleID: String) {
|
||||
//
|
||||
// articleIDsCache[oneArticleID] = nil
|
||||
// articleIDsWithNoTags.insert(oneArticleID)
|
||||
// }
|
||||
//
|
||||
// func removeArticleFromTags(_ articles: Set<Article>) {
|
||||
//
|
||||
// var articleIDsToRemove = [String]()
|
||||
//
|
||||
// articles.forEach { (oneArticle) in
|
||||
// let oneArticleID = oneArticle.articleID
|
||||
// if articleIDIsKnownToHaveNoTags(oneArticle) {
|
||||
// return
|
||||
// }
|
||||
// articleIDsToRemove += oneArticleID
|
||||
// removeTagsFromCacheForArticleID(oneArticleID)
|
||||
// }
|
||||
//
|
||||
// if !articleIDsToRemove.isEmpty {
|
||||
// queue.update { (database) in
|
||||
// database.rs_deleteRowsWhereKey(DatabaseKey.articleID, inValues: articleIDsToRemove, tableName: DatabaseTableName.tags)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// typealias TagsTable = [String: TagNameSet] // [articleID: Set<tagName>]
|
||||
//
|
||||
// func updateTagsForArticles(_ articles: Set<Article>) {
|
||||
//
|
||||
// var tagsForArticleIDs = TagsTable()
|
||||
// articles.forEach { (oneArticle)
|
||||
// if let tags = oneArticle.tags {
|
||||
// cacheTagsForArticle(oneArticle, tags)
|
||||
// tagsForArticleIDs[oneArticle.articleID] = oneArticle.tags
|
||||
// }
|
||||
// else {
|
||||
// assertionFailure("article must have tags")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if tagsForArticleIDs.isEmpty { // Shouldn’t be empty
|
||||
// return
|
||||
// }
|
||||
// let articleIDs = tagsForArticleIDs.keys
|
||||
//
|
||||
// queue.update { (database) in
|
||||
//
|
||||
// let existingTags = self.fetchTagsForArticleIDs(articleIDs, database: database)
|
||||
// self.syncIncomingAndExistingTags(incomingTags: tagsForArticleIDs, existingTags: existingTags, database: database)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func syncIncomingAndExistingTags(incomingTags: TagsTable, existingTags: TagsTable, database: database) {
|
||||
//
|
||||
// for (oneArticleID, oneTagNames) in incomingTags {
|
||||
// if let existingTagNames = existingTags[oneArticleID] {
|
||||
// syncIncomingAndExistingTagsForArticleID(oneArticleID, incomingTagNames: oneTagNames, existingTagNames: existingTagNames, database: database)
|
||||
// }
|
||||
// else {
|
||||
// saveIncomingTagsForArticleID(oneArticleID, tagNames: oneTagNames, database: database)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func saveIncomingTagsForArticleID(_ articleID: String, tagNames: TagNameSet, database: FMDatabase) {
|
||||
//
|
||||
// // No existing tags in database. Simple save.
|
||||
//
|
||||
// for oneTagName in tagNames {
|
||||
// let oneDictionary = [DatabaseTableName.articleID: articleID, DatabaseTableName.tagName: oneTagName]
|
||||
// database.rs_insertRow(with: oneDictionary, insertType: .OrIgnore, tableName: DatabaseTableName.tags)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func syncingIncomingAndExistingTagsForArticleID(_ articleID: String, incomingTagNames: TagNameSet, existingTagNames: TagNameSet, database: FMDatabase) {
|
||||
//
|
||||
// if incomingTagNames == existingTagNames {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var tagsToRemove = TagNameSet()
|
||||
// for oneExistingTagName in existingTagNames {
|
||||
// if !incomingTagNames.contains(oneExistingTagName) {
|
||||
// tagsToRemove.insert(oneExistingTagName)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var tagsToAdd = TagNameSet()
|
||||
// for oneIncomingTagName in incomingTagNames {
|
||||
// if !existingTagNames.contains(oneIncomingTagName) {
|
||||
// tagsToAdd.insert(oneIncomingTagName)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !tagsToRemove.isEmpty {
|
||||
// let placeholders = NSString.rs_SQLValueListWithPlaceholders
|
||||
// let sql = "delete from \(DatabaseTableName.tags) where \(DatabaseKey.articleID) = ? and \(DatabaseKey.tagName) in "
|
||||
// database.executeUpdate(sql, withArgumentsIn: [articleID, ])
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func fetchTagsForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> TagsTable {
|
||||
//
|
||||
// var tagSpecifiers = TagsTable()
|
||||
//
|
||||
// guard let rs = database.rs_selectRowsWhereKey(DatabaseKey.articleID, inValues: Array(articleIDs), tableName: DatabaseTableName.tags) else {
|
||||
// return tagSpecifiers
|
||||
// }
|
||||
//
|
||||
// while rs.next() {
|
||||
//
|
||||
// guard let oneTagName = rs.string(forColumn: DatabaseKey.tagName), let oneArticleID = rs.string(forColumn: DatabaseKey.articleID) else {
|
||||
// continue
|
||||
// }
|
||||
// if tagSpecifiers[oneArticleID] == nil {
|
||||
// tagSpecifiers[oneArticleID] = Set([oneTagName])
|
||||
// }
|
||||
// else {
|
||||
// tagSpecifiers[oneArticleID]!.insert(oneTagName)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return tagSpecifiers
|
||||
// }
|
||||
}
|
||||
|
||||
private extension Set where Element == LookupValue {
|
||||
|
||||
func tags() -> Set<String> {
|
||||
|
||||
return Set(flatMap{ $0.primaryID })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,9 +8,9 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol DatabaseTable: class {
|
||||
public protocol DatabaseTable {
|
||||
|
||||
var name: String {get}
|
||||
var name: String { get }
|
||||
|
||||
func fetchObjectsWithIDs(_ databaseIDs: Set<String>, in database: FMDatabase) -> [DatabaseObject]
|
||||
func save(_ objects: [DatabaseObject], in database: FMDatabase)
|
||||
|
@ -18,7 +18,7 @@ public final class DatabaseLookupTable {
|
||||
private let objectIDKey: String
|
||||
private let relatedObjectIDKey: String
|
||||
private let relationshipName: String
|
||||
private weak var relatedTable: DatabaseTable?
|
||||
private let relatedTable: DatabaseTable
|
||||
private let cache: DatabaseLookupTableCache
|
||||
|
||||
public init(name: String, objectIDKey: String, relatedObjectIDKey: String, relatedTable: DatabaseTable, relationshipName: String) {
|
||||
@ -124,11 +124,6 @@ private extension DatabaseLookupTable {
|
||||
|
||||
// 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.")
|
||||
|
@ -16,6 +16,18 @@ public protocol DatabaseObject {
|
||||
func relatedObjectsWithName(_ name: String) -> [DatabaseObject]?
|
||||
}
|
||||
|
||||
public extension DatabaseObject {
|
||||
|
||||
func setRelatedObjects(_ objects: [DatabaseObject], name: String) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
func relatedObjectsWithName(_ name: String) -> [DatabaseObject]? {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == DatabaseObject {
|
||||
|
||||
func dictionary() -> [String: DatabaseObject] {
|
||||
|
BIN
ToDo.ooutline
BIN
ToDo.ooutline
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user