Improve search query construction.

pull/1/head
Matthew Chen 7 years ago
parent f5a5d84edc
commit a51e9b78b0

@ -64,6 +64,10 @@ class FakeContactsManager: NSObject, ContactsManagerProtocol {
func isSystemContact(withSignalAccount recipientId: String) -> Bool {
return true
}
func compare(signalAccount left: SignalAccount, with right: SignalAccount) -> ComparisonResult {
return .orderedAscending
}
}
let bobRecipientId = "+49030183000"
@ -265,7 +269,7 @@ class ConversationSearcherTest: XCTestCase {
private func getResultSet(searchText: String) -> SearchResultSet {
var results: SearchResultSet!
self.dbConnection.read { transaction in
results = self.searcher.results(searchText: searchText, transaction: transaction)
results = self.searcher.results(searchText: searchText, transaction: transaction, contactsManager: TextSecureKitEnv.shared().contactsManager)
}
return results
}
@ -342,9 +346,23 @@ class SearcherTest: XCTestCase {
XCTAssertFalse(searcher.matches(item: regularLizaveta, query: "Liza 323"))
}
func testTextSanitization() {
func testSearchQuery() {
XCTAssertEqual(FullTextSearchFinder.query(searchText: "Liza"), "Liza*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "Liza +1-323"), "1323* Liza*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "\"\\ `~!@#$%^&*()_+-={}|[]:;'<>?,./Liza +1-323"), "1323* Liza*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "renaldo RENALDO reñaldo REÑALDO"), "RENALDO* REÑALDO* renaldo* reñaldo*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "😏"), "😏*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "alice 123 bob 456"), "123* 123456* 456* alice* bob*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "Li!za"), "Liza*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "Liza Liza"), "Liza*")
XCTAssertEqual(FullTextSearchFinder.query(searchText: "Liza liza"), "Liza* liza*")
}
func testTextNormalization() {
XCTAssertEqual(FullTextSearchFinder.normalize(text: "Liza"), "Liza")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "Liza +1-323"), "Liza 1 323")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "\"\\'!&@#$%^&*()Liza +1-323"), "Liza 1 323")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "Liza +1-323"), "Liza 1323")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "\"\\ `~!@#$%^&*()_+-={}|[]:;'<>?,./Liza +1-323"), "Liza 1323")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "renaldo RENALDO reñaldo REÑALDO"), "renaldo RENALDO reñaldo REÑALDO")
XCTAssertEqual(FullTextSearchFinder.normalize(text: "😏"), "😏")
}
}

@ -71,7 +71,6 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
* Used for sorting, respects system contacts name sort order preference.
*/
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount;
- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left withSignalAccount:(SignalAccount *)right;
// Generally we prefer the formattedProfileName over the raw profileName so as to
// distinguish a profile name apart from a name pulled from the system's contacts.

@ -39,13 +39,13 @@ public class ConversationSearchResult: Comparable {
public class ContactSearchResult: Comparable {
public let signalAccount: SignalAccount
public let contactsManager: OWSContactsManager
public let contactsManager: ContactsManagerProtocol
public var recipientId: String {
return signalAccount.recipientId
}
init(signalAccount: SignalAccount, contactsManager: OWSContactsManager) {
init(signalAccount: SignalAccount, contactsManager: ContactsManagerProtocol) {
self.signalAccount = signalAccount
self.contactsManager = contactsManager
}
@ -53,7 +53,7 @@ public class ContactSearchResult: Comparable {
// Mark: Comparable
public static func < (lhs: ContactSearchResult, rhs: ContactSearchResult) -> Bool {
return lhs.contactsManager.compareSignalAccount(lhs.signalAccount, with: rhs.signalAccount) == .orderedAscending
return lhs.contactsManager.compare(signalAccount: lhs.signalAccount, with: rhs.signalAccount) == .orderedAscending
}
// MARK: Equatable
@ -99,7 +99,7 @@ public class ConversationSearcher: NSObject {
public func results(searchText: String,
transaction: YapDatabaseReadTransaction,
contactsManager: OWSContactsManager) -> SearchResultSet {
contactsManager: ContactsManagerProtocol) -> SearchResultSet {
var conversations: [ConversationSearchResult] = []
var contacts: [ContactSearchResult] = []

@ -17,6 +17,9 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)isSystemContact:(NSString *)recipientId;
- (BOOL)isSystemContactWithSignalAccount:(NSString *)recipientId;
- (NSComparisonResult)compareSignalAccount:(SignalAccount *)left
withSignalAccount:(SignalAccount *)right NS_SWIFT_NAME(compare(signalAccount:with:));
@end
NS_ASSUME_NONNULL_END

@ -18,7 +18,7 @@ public class SearchIndexer<T> {
}
private func normalize(indexingText: String) -> String {
return FullTextSearchFinder.sanitize(text: indexingText)
return FullTextSearchFinder.normalize(text: indexingText)
}
}
@ -31,19 +31,32 @@ public class FullTextSearchFinder: NSObject {
// SQLite does not support suffix or contains matches.
public class func query(searchText: String) -> String {
// 1. Normalize the search text.
let normalizedSearchText = normalize(queryText: searchText)
let normalizedSearchText = FullTextSearchFinder.normalize(text: searchText)
// 2. Split into tokens.
let queryTerms = normalizedSearchText.split(separator: " ").filter {
// Ignore empty tokens.
// 2. Split into query terms (or tokens).
var queryTerms = normalizedSearchText.split(separator: " ")
// 3. Add an additional numeric-only query term.
let digitsOnlyScalars = normalizedSearchText.unicodeScalars.lazy.filter {
CharacterSet.decimalDigits.contains($0)
}
let digitsOnly: Substring = Substring(String(String.UnicodeScalarView(digitsOnlyScalars)))
queryTerms.append(digitsOnly)
// 4. De-duplicate and sort query terms.
queryTerms = Array(Set(queryTerms)).sorted()
// 5. Filter the query terms.
let filteredQueryTerms = queryTerms.filter {
// Ignore empty terms.
$0.count > 0
}.map {
// Allow partial match of each token.
// Allow partial match of each term.
$0 + "*"
}
// 3. Join tokens into query string.
let query = queryTerms.joined(separator: " ")
// 6. Join terms into query string.
let query = filteredQueryTerms.joined(separator: " ")
return query
}
@ -73,13 +86,13 @@ public class FullTextSearchFinder: NSObject {
}
}
// Mark: Filtering
// Mark: Normalization
fileprivate class func charactersToRemove() -> CharacterSet {
var charactersToFilter = CharacterSet.punctuationCharacters
charactersToFilter.formUnion(CharacterSet.illegalCharacters)
charactersToFilter.formUnion(CharacterSet.controlCharacters)
charactersToFilter.formUnion(CharacterSet.symbols)
charactersToFilter.formUnion(CharacterSet(charactersIn: "+~$^=|<>`"))
return charactersToFilter
}
@ -88,7 +101,7 @@ public class FullTextSearchFinder: NSObject {
return separatorCharacters
}
fileprivate class func sanitize(text: String) -> String {
public class func normalize(text: String) -> String {
// 1. Filter out invalid characters.
let filtered = text.unicodeScalars.lazy.filter({
!charactersToRemove().contains($0)
@ -115,21 +128,6 @@ public class FullTextSearchFinder: NSObject {
return result.trimmingCharacters(in: .whitespacesAndNewlines)
}
private class func normalize(queryText: String) -> String {
var normalized: String = FullTextSearchFinder.sanitize(text: queryText)
let digitsOnlyScalars = normalized.unicodeScalars.lazy.filter {
CharacterSet.decimalDigits.contains($0)
}
let normalizedDigits = String(String.UnicodeScalarView(digitsOnlyScalars))
if normalizedDigits.count > 0 {
return "\(normalized) OR \(normalizedDigits)"
} else {
return normalized
}
}
// Mark: Index Building
private class var contactsManager: ContactsManagerProtocol {

Loading…
Cancel
Save