Merge pull request #2802 from banaslee/pr/1695

Adds author name as a searchable field
This commit is contained in:
Maurice Parker 2021-02-10 16:43:28 -06:00 committed by GitHub
commit 6d72bc0c19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 13 deletions

View File

@ -63,6 +63,7 @@ public final class ArticlesDatabase {
} }
private let articlesTable: ArticlesTable private let articlesTable: ArticlesTable
private let searchTable: SearchTable
private let queue: DatabaseQueue private let queue: DatabaseQueue
private let operationQueue = MainThreadOperationQueue() private let operationQueue = MainThreadOperationQueue()
private let retentionStyle: RetentionStyle private let retentionStyle: RetentionStyle
@ -71,9 +72,11 @@ public final class ArticlesDatabase {
let queue = DatabaseQueue(databasePath: databaseFilePath) let queue = DatabaseQueue(databasePath: databaseFilePath)
self.queue = queue self.queue = queue
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, accountID: accountID, queue: queue, retentionStyle: retentionStyle) self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, accountID: accountID, queue: queue, retentionStyle: retentionStyle)
self.searchTable = SearchTable(queue: queue, articlesTable: self.articlesTable)
self.retentionStyle = retentionStyle self.retentionStyle = retentionStyle
try! queue.runCreateStatements(ArticlesDatabase.tableCreationStatements) try! queue.runCreateStatements(ArticlesDatabase.tableCreationStatements)
try! queue.runCreateStatements(ArticlesDatabase.searchTableCreationStatements)
queue.runInDatabase { databaseResult in queue.runInDatabase { databaseResult in
let database = databaseResult.database! let database = databaseResult.database!
if !self.articlesTable.containsColumn("searchRowID", in: database) { if !self.articlesTable.containsColumn("searchRowID", in: database) {
@ -81,6 +84,10 @@ public final class ArticlesDatabase {
} }
database.executeStatements("CREATE INDEX if not EXISTS articles_searchRowID on articles(searchRowID);") database.executeStatements("CREATE INDEX if not EXISTS articles_searchRowID on articles(searchRowID);")
database.executeStatements("DROP TABLE if EXISTS tags;DROP INDEX if EXISTS tags_tagName_index;DROP INDEX if EXISTS articles_feedID_index;DROP INDEX if EXISTS statuses_read_index;DROP TABLE if EXISTS attachments;DROP TABLE if EXISTS attachmentsLookup;") database.executeStatements("DROP TABLE if EXISTS tags;DROP INDEX if EXISTS tags_tagName_index;DROP INDEX if EXISTS articles_feedID_index;DROP INDEX if EXISTS statuses_read_index;DROP TABLE if EXISTS attachments;DROP TABLE if EXISTS attachmentsLookup;")
if !self.searchTable.containsColumn("authors", in: database) {
database.executeStatements("DROP TABLE if EXISTS search;UPDATE articles SET searchRowID = null;")
database.executeStatements(ArticlesDatabase.searchTableCreationStatements)
}
} }
DispatchQueue.main.async { DispatchQueue.main.async {
@ -331,12 +338,14 @@ private extension ArticlesDatabase {
CREATE INDEX if not EXISTS articles_feedID_datePublished_articleID on articles (feedID, datePublished, articleID); CREATE INDEX if not EXISTS articles_feedID_datePublished_articleID on articles (feedID, datePublished, articleID);
CREATE INDEX if not EXISTS statuses_starred_index on statuses (starred); CREATE INDEX if not EXISTS statuses_starred_index on statuses (starred);
CREATE VIRTUAL TABLE if not EXISTS search using fts4(title, body);
CREATE TRIGGER if not EXISTS articles_after_delete_trigger_delete_search_text after delete on articles begin delete from search where rowid = OLD.searchRowID; end;
""" """
static let searchTableCreationStatements = """
CREATE VIRTUAL TABLE if not EXISTS search using fts4(title, body, authors);
CREATE TRIGGER if not EXISTS articles_after_delete_trigger_delete_search_text after delete on articles begin delete from search where rowid = OLD.searchRowID; end;
"""
func todayCutoffDate() -> Date { func todayCutoffDate() -> Date {
// 24 hours previous. This is used by the Today smart feed, which should not actually empty out at midnight. // 24 hours previous. This is used by the Today smart feed, which should not actually empty out at midnight.
return Date(timeIntervalSinceNow: -(60 * 60 * 24)) // This does not need to be more precise. return Date(timeIntervalSinceNow: -(60 * 60 * 24)) // This does not need to be more precise.

View File

@ -145,19 +145,36 @@ final class ArticlesTable: DatabaseTable {
} }
// MARK: - Fetching Articles for Indexer // MARK: - Fetching Articles for Indexer
private func articleSearchInfosQuery(with placeholders: String) -> String {
return """
SELECT
art.articleID,
art.title,
art.contentHTML,
art.contentText,
art.summary,
art.searchRowID,
(SELECT GROUP_CONCAT(name, ' ')
FROM authorsLookup as autL
JOIN authors as aut ON autL.authorID = aut.authorID
WHERE art.articleID = autL.articleID
GROUP BY autl.articleID) as authors
FROM articles as art
WHERE articleID in \(placeholders);
"""
}
func fetchArticleSearchInfos(_ articleIDs: Set<String>, in database: FMDatabase) -> Set<ArticleSearchInfo>? { func fetchArticleSearchInfos(_ articleIDs: Set<String>, in database: FMDatabase) -> Set<ArticleSearchInfo>? {
let parameters = articleIDs.map { $0 as AnyObject } let parameters = articleIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
let sql = "select articleID, title, contentHTML, contentText, summary, searchRowID from articles where articleID in \(placeholders);"; if let resultSet = database.executeQuery(self.articleSearchInfosQuery(with: placeholders), withArgumentsIn: parameters) {
if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) {
return resultSet.mapToSet { (row) -> ArticleSearchInfo? in return resultSet.mapToSet { (row) -> ArticleSearchInfo? in
let articleID = row.string(forColumn: DatabaseKey.articleID)! let articleID = row.string(forColumn: DatabaseKey.articleID)!
let title = row.string(forColumn: DatabaseKey.title) let title = row.string(forColumn: DatabaseKey.title)
let contentHTML = row.string(forColumn: DatabaseKey.contentHTML) let contentHTML = row.string(forColumn: DatabaseKey.contentHTML)
let contentText = row.string(forColumn: DatabaseKey.contentText) let contentText = row.string(forColumn: DatabaseKey.contentText)
let summary = row.string(forColumn: DatabaseKey.summary) let summary = row.string(forColumn: DatabaseKey.summary)
let authorsNames = row.string(forColumn: DatabaseKey.authors)
let searchRowIDObject = row.object(forColumnName: DatabaseKey.searchRowID) let searchRowIDObject = row.object(forColumnName: DatabaseKey.searchRowID)
var searchRowID: Int? = nil var searchRowID: Int? = nil
@ -165,7 +182,7 @@ final class ArticlesTable: DatabaseTable {
searchRowID = Int(row.longLongInt(forColumn: DatabaseKey.searchRowID)) searchRowID = Int(row.longLongInt(forColumn: DatabaseKey.searchRowID))
} }
return ArticleSearchInfo(articleID: articleID, title: title, contentHTML: contentHTML, contentText: contentText, summary: summary, searchRowID: searchRowID) return ArticleSearchInfo(articleID: articleID, title: title, authorsNames: authorsNames, contentHTML: contentHTML, contentText: contentText, summary: summary, searchRowID: searchRowID)
} }
} }
return nil return nil

View File

@ -17,6 +17,7 @@ final class ArticleSearchInfo: Hashable {
let articleID: String let articleID: String
let title: String? let title: String?
let authorsNames: String?
let contentHTML: String? let contentHTML: String?
let contentText: String? let contentText: String?
let summary: String? let summary: String?
@ -37,9 +38,10 @@ final class ArticleSearchInfo: Hashable {
return s.strippingHTML().collapsingWhitespace return s.strippingHTML().collapsingWhitespace
}() }()
init(articleID: String, title: String?, contentHTML: String?, contentText: String?, summary: String?, searchRowID: Int?) { init(articleID: String, title: String?, authorsNames: String?, contentHTML: String?, contentText: String?, summary: String?, searchRowID: Int?) {
self.articleID = articleID self.articleID = articleID
self.title = title self.title = title
self.authorsNames = authorsNames
self.contentHTML = contentHTML self.contentHTML = contentHTML
self.contentText = contentText self.contentText = contentText
self.summary = summary self.summary = summary
@ -47,7 +49,8 @@ final class ArticleSearchInfo: Hashable {
} }
convenience init(article: Article) { convenience init(article: Article) {
self.init(articleID: article.articleID, title: article.title, contentHTML: article.contentHTML, contentText: article.contentText, summary: article.summary, searchRowID: nil) let authorsNames = article.authors?.map({ $0.name }).reduce("", { $0.appending("").appending($1 ?? "") })
self.init(articleID: article.articleID, title: article.title, authorsNames: authorsNames, contentHTML: article.contentHTML, contentText: article.contentText, summary: article.summary, searchRowID: nil)
} }
// MARK: Hashable // MARK: Hashable
@ -59,7 +62,7 @@ final class ArticleSearchInfo: Hashable {
// MARK: Equatable // MARK: Equatable
static func == (lhs: ArticleSearchInfo, rhs: ArticleSearchInfo) -> Bool { static func == (lhs: ArticleSearchInfo, rhs: ArticleSearchInfo) -> Bool {
return lhs.articleID == rhs.articleID && lhs.title == rhs.title && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.summary == rhs.summary && lhs.searchRowID == rhs.searchRowID return lhs.articleID == rhs.articleID && lhs.title == rhs.title && lhs.authorsNames == rhs.authorsNames && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.summary == rhs.summary && lhs.searchRowID == rhs.searchRowID
} }
} }
@ -127,7 +130,7 @@ private extension SearchTable {
} }
func insert(_ article: ArticleSearchInfo, _ database: FMDatabase) -> Int { func insert(_ article: ArticleSearchInfo, _ database: FMDatabase) -> Int {
let rowDictionary: DatabaseDictionary = [DatabaseKey.body: article.bodyForIndex, DatabaseKey.title: article.title ?? ""] let rowDictionary: DatabaseDictionary = [DatabaseKey.body: article.bodyForIndex, DatabaseKey.title: article.title ?? "", DatabaseKey.authors: article.authorsNames ?? ""]
insertRow(rowDictionary, insertType: .normal, in: database) insertRow(rowDictionary, insertType: .normal, in: database)
return Int(database.lastInsertRowId()) return Int(database.lastInsertRowId())
} }
@ -201,7 +204,7 @@ private extension SearchTable {
return nil return nil
} }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(searchRowIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(searchRowIDs.count))!
let sql = "select rowid, title, body from \(name) where rowid in \(placeholders);" let sql = "select rowid, title, body, authors from \(name) where rowid in \(placeholders);"
guard let resultSet = database.executeQuery(sql, withArgumentsIn: searchRowIDs) else { guard let resultSet = database.executeQuery(sql, withArgumentsIn: searchRowIDs) else {
return nil return nil
} }