Make progress on Database.framework.

This commit is contained in:
Brent Simmons 2017-08-03 21:10:01 -07:00
parent 2ace9ec0d2
commit 9d37d88c2f
9 changed files with 75 additions and 30 deletions

View File

@ -33,6 +33,8 @@ 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) {
@ -115,7 +117,7 @@ final class AttachmentsTable: DatabaseTable {
}
}
private extension AttachmentsManager {
private extension AttachmentsTable {
func deleteAttachmentsForArticles(_ articles: Set<Article>, _ database: FMDatabase) {

View File

@ -7,12 +7,14 @@
//
import Foundation
import RSDatabase
import Data
final class AuthorsTable: DatabaseTable {
let name: String
let queue: RSDatabaseQueue
private let cache = ObjectCache<Author>(keyPathForID: \Author.databaseID)
init(name: String, queue: RSDatabaseQueue) {
@ -20,30 +22,27 @@ final class AuthorsTable: DatabaseTable {
self.queue = queue
}
var cachedAuthors = [String: Author]()
func cachedAuthor(_ databaseID: String) -> Author? {
return cachedAuthors[databaseID]
}
func cacheAuthor(_ author: Author) {
cachedAuthors[author.databaseID] = author
}
func authorWithRow(_ row: FMResultSet) -> Author? {
let databaseID = row.string(forColumn: DatabaseKey.databaseID)
if let author = cachedAuthor(databaseID) {
return author
// Since:
// 1. anything to do with an FMResultSet runs inside the database serial queue, and
// 2. the cache is referenced only within this method,
// this is safe.
guard let databaseID = row.string(forColumn: DatabaseKey.databaseID) else {
return nil
}
if let cachedAuthor = cache[databaseID] {
return cachedAuthor
}
guard let author = Author(row: row) else {
return nil
}
cacheAuthor(author)
cache[databaseID] = author
return author
}
}

View File

@ -11,6 +11,7 @@ import Foundation
public struct DatabaseTableName {
static let articles = "articles"
static let authors = "authors"
static let statuses = "statuses"
static let tags = "tags"
static let attachments = "attachments"

View File

@ -2,7 +2,7 @@ CREATE TABLE if not EXISTS articles (articleID TEXT NOT NULL PRIMARY KEY, feedID
CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0, accountInfo BLOB);
CREATE TABLE if not EXISTS authors (authorID TEXT NOT NULL PRIMARY KEY, name TEXT, url TEXT, avatarURL TEXT, emailAddress TEXT);
CREATE TABLE if not EXISTS authors (databaseID TEXT NOT NULL PRIMARY KEY, name TEXT, url TEXT, avatarURL TEXT, emailAddress TEXT);
CREATE TABLE if not EXISTS authorLookup (authorID TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(authorID, articleID));
CREATE TABLE if not EXISTS tags(tagName TEXT NOT NULL, articleID TEXT NOT NULL, PRIMARY KEY(tagName, articleID));

View File

@ -26,9 +26,11 @@ final class Database {
fileprivate let queue: RSDatabaseQueue
private let databaseFile: String
private let articlesTable: ArticlesTable
private let authorsTable: AuthorsTable
private let attachmentsTable: AttachmentsTable
fileprivate let statusesManager: StatusesManager
fileprivate let articleCache = ArticlesManager()
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?
@ -38,8 +40,12 @@ final class Database {
self.delegate = delegate
self.databaseFile = databaseFile
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.statusesManager = StatusesManager(queue: self.queue)
self.statusesTable = StatusesTable(name: DatabaseTableName.statuses, queue: queue)
self.tagsTable = TagsTable(name: DatabaseTableName.tags, queue: queue)
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", ofType: "sql")!
let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue)
@ -327,7 +333,7 @@ private extension Database {
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> {
let sql = "select * from articles natural join statuses where \(whereClause);"
let sql = "select * from articles where \(whereClause);"
logSQL(sql)
if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) {
@ -344,10 +350,15 @@ private extension Database {
while (resultSet.next()) {
if let oneArticle = Article(account: self.account, row: resultSet) {
oneArticle.status = ArticleStatus(row: resultSet)
fetchedArticles.insert(oneArticle)
}
}
resultSet.close()
statusesTable.attachStatuses(fetchedArticles, database)
authorsTable.attachAuthors(fetchedArticles, database)
tagsTable.attachTags(fetchedArticles, database)
attachmentsTable.attachAttachments(fetchedArticles, database)
return fetchedArticles
}

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405CE1F1A963700DF0296 /* AttachmentsTable.swift */; };
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F20F901F1810DD00D8E682 /* Author+Database.swift */; };
844BEE411F0AB3AB004AB7CD /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE371F0AB3AA004AB7CD /* Database.framework */; };
844BEE461F0AB3AB004AB7CD /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE451F0AB3AB004AB7CD /* DatabaseTests.swift */; };
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580661F0AEBCD003CCFA1 /* Constants.swift */; };
@ -163,9 +164,9 @@
84E156E91F0AB80500F8CC05 /* Database.swift */,
845580661F0AEBCD003CCFA1 /* Constants.swift */,
84E156EB1F0AB80E00F8CC05 /* ArticlesTable.swift */,
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
84F20F8E1F180D8700D8E682 /* AuthorsTable.swift */,
840405CE1F1A963700DF0296 /* AttachmentsTable.swift */,
84E156ED1F0AB81400F8CC05 /* StatusesTable.swift */,
84BB4BA81F11A32800858766 /* TagsTable.swift */,
8461462A1F0AC44100870CB3 /* Extensions */,
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */,
@ -467,6 +468,7 @@
84F20F8F1F180D8700D8E682 /* AuthorsTable.swift in Sources */,
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */,
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */,
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,
845580721F0AEE49003CCFA1 /* AccountInfo.swift in Sources */,

View File

@ -21,7 +21,7 @@ extension Attachment {
let sizeInBytes = optionalIntForColumn(row, DatabaseKey.sizeInBytes)
let durationInSeconds = optionalIntForColumn(row, DatabaseKey.durationInSeconds)
init(databaseID: databaseID, articleID: articleID, url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
self.init(databaseID: databaseID, articleID: articleID, url: url, mimeType: mimeType, title: title, sizeInBytes: sizeInBytes, durationInSeconds: durationInSeconds)
}
private func optionalIntForColumn(_ row: FMResultSet, _ columnName: String) -> Int? {

View File

@ -29,6 +29,25 @@ final class StatusesTable: DatabaseTable {
assertNoMissingStatuses(articles)
let statuses = Set(articles.flatMap { $0.status })
markArticleStatuses(statuses, statusKey: statusKey, flag: flag)
}
func attachStatuses(_ articles: Set<Article>, _ database: FMDatabase) {
attachCachedStatuses(articles)
let articlesNeedingStatuses = articlesMissingStatuses(articles)
if articlesNeedingStatuses.isEmpty {
return
}
fetchAndCacheStatusesForArticles(Set(articlesNeedingStatuses))
attachCachedStatuses(articlesNeedingStatuses)
// It shouldnt happen that an Article in the database has no corresponding ArticleStatus,
// but the case should be handled anyway.
}
func attachCachedStatuses(_ articles: Set<Article>) {
@ -90,7 +109,7 @@ private extension StatusesTable {
// MARK: Fetching
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> Set<ArticleStatus> {
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> Set<ArticleStatus> {
if !articleIDs.isEmpty, let resultSet = selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) {
return articleStatusesWithResultSet(resultSet)
@ -161,7 +180,18 @@ private extension StatusesTable {
func articleIDsMissingStatuses(_ articleIDs: Set<String>) -> Set<String> {
return Set(articleIDs.filter { !objectWithIDIsCached[$0] })
return Set(articleIDs.filter { !cache.objectWithIDIsCached[$0] })
}
func articlesMissingStatuses(_ articles: Set<Article>) -> Set<Article> {
let missing = articles.flatMap { (article) -> Article? in
if article.status == nil {
return article
}
return nil
}
return Set(missing)
}
}

View File

@ -58,7 +58,7 @@ final class TagsTable: DatabaseTable {
}
}
private extension TagsManager {
private extension TagsTable {
func cacheTagsForArticle(_ article: Article, tags: TagNameSet) {