mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-25 00:25:03 +08:00
feat(search): Fix slow queries with FTS5 on Contacts
This commit is contained in:
parent
ec7133dff1
commit
432012a6bc
6 changed files with 109 additions and 10 deletions
|
@ -0,0 +1,86 @@
|
|||
import {
|
||||
Contact,
|
||||
DatabaseStore,
|
||||
} from 'nylas-exports';
|
||||
|
||||
const INDEX_VERSION = 1;
|
||||
|
||||
class ContactSearchIndexStore {
|
||||
constructor() {
|
||||
this.unsubscribers = []
|
||||
}
|
||||
|
||||
activate() {
|
||||
this.initializeIndex();
|
||||
this.unsubscribers = [
|
||||
DatabaseStore.listen(this.onDataChanged),
|
||||
];
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.unsubscribers.forEach(unsub => unsub())
|
||||
}
|
||||
|
||||
initializeIndex() {
|
||||
if (NylasEnv.config.get('contactSearchIndexVersion') !== INDEX_VERSION) {
|
||||
DatabaseStore.dropSearchIndex(Contact)
|
||||
.then(() => DatabaseStore.createSearchIndex(Contact))
|
||||
.then(() => this.buildIndex())
|
||||
}
|
||||
}
|
||||
|
||||
buildIndex(offset = 0) {
|
||||
const indexingPageSize = 1000;
|
||||
const indexingPageDelay = 1000;
|
||||
|
||||
DatabaseStore.findAll(Contact).limit(indexingPageSize).offset(offset).then((contacts) => {
|
||||
if (contacts.length === 0) {
|
||||
NylasEnv.config.set('contactSearchIndexVersion', INDEX_VERSION)
|
||||
return;
|
||||
}
|
||||
Promise.each(contacts, (contact) => {
|
||||
return DatabaseStore.indexModel(contact, this.getIndexDataForContact(contact))
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
this.buildIndex(offset + contacts.length);
|
||||
}, indexingPageDelay);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* When a contact gets updated we will update the search index with the data
|
||||
* from that contact if the account it belongs to is not being currently
|
||||
* synced.
|
||||
*
|
||||
* When the account is successfully synced, its contacts will be added to the
|
||||
* index either via `onAccountsChanged` or via `initializeIndex` when the app
|
||||
* starts
|
||||
*/
|
||||
onDataChanged = (change) => {
|
||||
if (change.objectClass !== Contact.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
change.objects.forEach((contact) => {
|
||||
if (change.type === 'persist') {
|
||||
DatabaseStore.indexModel(contact, this.getIndexDataForContact(contact))
|
||||
} else {
|
||||
DatabaseStore.unindexModel(contact)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getIndexDataForContact(contact) {
|
||||
return {
|
||||
content: [
|
||||
contact.name ? contact.name : '',
|
||||
contact.email ? contact.email : '',
|
||||
contact.email ? contact.email.replace('@', ' ') : '',
|
||||
].join(' '),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new ContactSearchIndexStore()
|
|
@ -1,10 +1,13 @@
|
|||
import SearchIndexStore from './search-index-store'
|
||||
import ThreadSearchIndexStore from './thread-search-index-store'
|
||||
import ContactSearchIndexStore from './contact-search-index-store'
|
||||
|
||||
|
||||
export function activate() {
|
||||
SearchIndexStore.activate()
|
||||
ThreadSearchIndexStore.activate()
|
||||
ContactSearchIndexStore.activate()
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
SearchIndexStore.deactivate()
|
||||
ThreadSearchIndexStore.deactivate()
|
||||
ContactSearchIndexStore.deactivate()
|
||||
}
|
||||
|
|
|
@ -230,7 +230,11 @@ class SearchIndexStore {
|
|||
participants,
|
||||
body: messageBodies,
|
||||
subject: thread.subject,
|
||||
})
|
||||
}).then((results) => {
|
||||
return {
|
||||
content: [results.participants, results.body, results.subject].concat(' '),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
deactivate() {
|
|
@ -241,6 +241,9 @@ class NotCompositeMatcher extends AndCompositeMatcher {
|
|||
|
||||
class SearchMatcher extends Matcher {
|
||||
constructor(searchQuery) {
|
||||
if ((typeof searchQuery !== 'string') || (searchQuery.length === 0)) {
|
||||
throw new Error("You must pass a string with non-zero length to search.")
|
||||
}
|
||||
super(null, null, null);
|
||||
this.searchQuery = (
|
||||
searchQuery.trim()
|
||||
|
|
|
@ -86,6 +86,10 @@ export default class Contact extends Model {
|
|||
},
|
||||
};
|
||||
|
||||
static searchable = true;
|
||||
|
||||
static searchFields = ['content'];
|
||||
|
||||
static fromString(string, {accountId} = {}) {
|
||||
const emailRegex = RegExpUtils.emailRegex();
|
||||
const match = emailRegex.exec(string);
|
||||
|
|
|
@ -47,7 +47,8 @@ class ContactStore extends NylasStore
|
|||
search = search.toLowerCase()
|
||||
accountCount = AccountStore.accounts().length
|
||||
|
||||
return Promise.resolve([]) if not search or search.length is 0
|
||||
if not search or search.length is 0
|
||||
return Promise.resolve([])
|
||||
|
||||
# Search ranked contacts which are stored in order in memory
|
||||
results = []
|
||||
|
@ -63,11 +64,9 @@ class ContactStore extends NylasStore
|
|||
# return contacts with distinct email addresses, and the same contact
|
||||
# could exist in every account. Rather than make SQLite do a SELECT DISTINCT
|
||||
# (which is very slow), we just ask for more items.
|
||||
query = DatabaseStore.findAll(Contact).whereAny([
|
||||
Contact.attributes.name.like(search),
|
||||
Contact.attributes.email.like(search)
|
||||
])
|
||||
query.limit(limit * accountCount)
|
||||
query = DatabaseStore.findAll(Contact)
|
||||
.search(search)
|
||||
.limit(limit * accountCount)
|
||||
query.then (queryResults) =>
|
||||
existingEmails = _.pluck(results, 'email')
|
||||
|
||||
|
|
Loading…
Reference in a new issue