Move thread counts logic to c++

This commit is contained in:
Ben Gotow 2017-06-24 18:40:41 -07:00
parent f37aa13780
commit f038c97144
17 changed files with 13 additions and 161 deletions

View file

@ -94,7 +94,7 @@ c3 = new ListTabular.Column
attachment = false
messages = thread.__messages || []
hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files)
hasAttachments = thread.attachmentCount > 0 and messages.find (m) -> Utils.showIconForAttachments(m.files)
if hasAttachments
attachment = <div className="thread-icon thread-icon-attachment"></div>
@ -144,7 +144,7 @@ cNarrow = new ListTabular.Column
attachment = false
messages = thread.__messages || []
hasAttachments = thread.hasAttachments and messages.find (m) -> Utils.showIconForAttachments(m.files)
hasAttachments = thread.attachmentCount > 0 and messages.find (m) -> Utils.showIconForAttachments(m.files)
if hasAttachments
attachment = <div className="thread-icon thread-icon-attachment"></div>

View file

@ -16,6 +16,8 @@ classnames = require 'classnames'
CanvasUtils,
TaskFactory,
ChangeStarredTask,
ChangeFolderTask,
ChangeLabelsTask,
WorkspaceStore,
AccountStore,
CategoryStore,
@ -154,8 +156,10 @@ class ThreadList extends React.Component
task = tasks[0]
name = if task instanceof ChangeStarredTask
'unstar'
else if task.categoriesToAdd().length is 1
task.categoriesToAdd()[0].name
else if task instanceof ChangeFolderTask
task.folder.name
else if task instanceof ChangeLabelsTask and task.labelsToAdd.length is 1
task.labelsToAdd[0].name
else
'remove'

View file

@ -19,7 +19,6 @@ class TestModel extends Model
jsonKey: 'server_id'
TestModel.configureBasic = ->
TestModel.additionalSQLiteConfig = undefined
TestModel.attributes =
'id': Attributes.String
queryable: true
@ -34,7 +33,6 @@ TestModel.configureBasic = ->
jsonKey: 'server_id'
TestModel.configureWithAllAttributes = ->
TestModel.additionalSQLiteConfig = undefined
TestModel.attributes =
'datetime': Attributes.DateTime
queryable: true
@ -53,7 +51,6 @@ TestModel.configureWithAllAttributes = ->
modelKey: 'other'
TestModel.configureWithCollectionAttribute = ->
TestModel.additionalSQLiteConfig = undefined
TestModel.attributes =
'id': Attributes.String
queryable: true
@ -77,7 +74,6 @@ TestModel.configureWithCollectionAttribute = ->
joinQueryableBy: ['other'],
TestModel.configureWithJoinedDataAttribute = ->
TestModel.additionalSQLiteConfig = undefined
TestModel.attributes =
'id': Attributes.String
queryable: true
@ -95,7 +91,6 @@ TestModel.configureWithJoinedDataAttribute = ->
modelKey: 'body'
TestModel.configureWithAdditionalSQLiteConfig = ->
TestModel.attributes =
'id': Attributes.String
queryable: true
@ -109,8 +104,5 @@ TestModel.configureWithAdditionalSQLiteConfig = ->
'body': Attributes.JoinedData
modelTable: 'TestModelBody'
modelKey: 'body'
TestModel.additionalSQLiteConfig =
setup: ->
['CREATE INDEX IF NOT EXISTS ThreadListIndex ON Thread(last_message_received_timestamp DESC, account_id, id)']
module.exports = TestModel

View file

@ -118,15 +118,6 @@ export default class Category extends Model {
return name;
}
static additionalSQLiteConfig = {
setup: () => {
return [
// 'CREATE INDEX IF NOT EXISTS FolderNameIndex ON Folder(accountId,name)',
// 'CREATE UNIQUE INDEX IF NOT EXISTS FolderClientIndex ON Folder(id)',
];
},
};
displayType() {
throw new Error("Base class");
}

View file

@ -90,16 +90,6 @@ export default class Contact extends Model {
}),
});
static additionalSQLiteConfig = {
setup: () => {
return [
'CREATE INDEX IF NOT EXISTS ContactEmailIndex ON Contact(email)',
'CREATE INDEX IF NOT EXISTS ContactAccountEmailIndex ON Contact(accountId, email)',
'CREATE INDEX IF NOT EXISTS ContactIsSearchIndexedIndex ON `Contact` (isSearchIndexed, id)',
];
},
};
static searchable = true;
static searchFields = ['content'];

View file

@ -127,15 +127,6 @@ export default class Event extends Model {
}),
});
static additionalSQLiteConfig = {
setup: () => {
return [
'CREATE UNIQUE INDEX IF NOT EXISTS EventClientIndex ON Event(id)',
'CREATE INDEX IF NOT EXISTS EventIsSearchIndexedIndex ON `Event` (is_search_indexed, id)',
];
},
};
static searchable = true
static searchFields = ['title', 'description', 'location', 'participants']

View file

@ -29,25 +29,19 @@ export default class File extends Model {
static attributes = Object.assign({}, Model.attributes, {
filename: Attributes.String({
modelKey: 'filename',
jsonKey: 'filename',
queryable: true,
}),
size: Attributes.Number({
modelKey: 'size',
jsonKey: 'size',
}),
contentType: Attributes.String({
modelKey: 'contentType',
jsonKey: 'content_type',
}),
messageIds: Attributes.Collection({
modelKey: 'messageIds',
jsonKey: 'message_ids',
itemClass: String,
messageId: Attributes.ServerId({
modelKey: 'messageId',
}),
contentId: Attributes.String({
modelKey: 'contentId',
jsonKey: 'content_id',
}),
});

View file

@ -181,18 +181,6 @@ export default class Message extends ModelWithMetadata {
return Message.attributes.date.ascending()
}
static additionalSQLiteConfig = {
setup: () => [
`CREATE INDEX IF NOT EXISTS MessageListThreadIndex ON Message(threadId, date ASC)`,
`CREATE UNIQUE INDEX IF NOT EXISTS MessageDraftIndex ON Message(id)`,
`CREATE INDEX IF NOT EXISTS MessageListDraftIndex ON \
Message(accountId, date DESC) WHERE draft = 1`,
`CREATE INDEX IF NOT EXISTS MessageListUnifiedDraftIndex ON \
Message(date DESC) WHERE draft = 1`,
`CREATE UNIQUE INDEX IF NOT EXISTS MessageBodyIndex ON MessageBody(id)`,
],
}
constructor(args) {
super(args);
this.subject = this.subject || ""

View file

@ -94,8 +94,8 @@ class Thread extends ModelWithMetadata {
itemClass: Contact,
}),
hasAttachments: Attributes.Boolean({
modelKey: 'hasAttachments',
attachmentCount: Attributes.Number({
modelKey: 'attachmentCount',
}),
lastMessageReceivedTimestamp: Attributes.DateTime({
@ -136,36 +136,6 @@ class Thread extends ModelWithMetadata {
return Thread.sortOrderAttribute().descending()
}
static additionalSQLiteConfig = {
setup: () => [
// ThreadCounts
'CREATE TABLE IF NOT EXISTS `ThreadCounts` (`category_id` TEXT PRIMARY KEY, `unread` INTEGER, `total` INTEGER)',
'CREATE UNIQUE INDEX IF NOT EXISTS ThreadCountsIndex ON `ThreadCounts` (category_id DESC)',
// ThreadContact
'CREATE INDEX IF NOT EXISTS ThreadContactDateIndex ON `ThreadContact` (lastMessageReceivedTimestamp DESC, value, id)',
// ThreadCategory
'CREATE INDEX IF NOT EXISTS ThreadListCategoryIndex ON `ThreadCategory` (lastMessageReceivedTimestamp DESC, value, inAllMail, unread, id)',
'CREATE INDEX IF NOT EXISTS ThreadListCategorySentIndex ON `ThreadCategory` (lastMessageSentTimestamp DESC, value, inAllMail, unread, id)',
// Thread: General index
'CREATE INDEX IF NOT EXISTS ThreadDateIndex ON `Thread` (lastMessageReceivedTimestamp DESC)',
'CREATE INDEX IF NOT EXISTS ThreadClientIdIndex ON `Thread` (id)',
// Thread: Partial indexes for specific views
'CREATE INDEX IF NOT EXISTS ThreadUnreadIndex ON `Thread` (accountId, lastMessageReceivedTimestamp DESC) WHERE unread = 1 AND inAllMail = 1',
'CREATE INDEX IF NOT EXISTS ThreadUnifiedUnreadIndex ON `Thread` (lastMessageReceivedTimestamp DESC) WHERE unread = 1 AND inAllMail = 1',
'DROP INDEX IF EXISTS `Thread`.ThreadStarIndex',
'CREATE INDEX IF NOT EXISTS ThreadStarredIndex ON `Thread` (accountId, lastMessageReceivedTimestamp DESC) WHERE starred = 1 AND inAllMail = 1',
'CREATE INDEX IF NOT EXISTS ThreadUnifiedStarredIndex ON `Thread` (lastMessageReceivedTimestamp DESC) WHERE starred = 1 AND inAllMail = 1',
'CREATE INDEX IF NOT EXISTS ThreadIsSearchIndexedIndex ON `Thread` (isSearchIndexed, id)',
'CREATE INDEX IF NOT EXISTS ThreadIsSearchIndexedLastMessageReceivedIndex ON `Thread` (isSearchIndexed, lastMessageReceivedTimestamp)',
],
}
static searchable = true
static searchFields = ['subject', 'to_', 'from_', 'categories', 'body']

View file

@ -3,65 +3,9 @@ NylasStore = require 'nylas-store'
DatabaseStore = require('./database-store').default
Thread = require('../models/thread').default
###
Are running two nested SELECT statements really the best option? Yup.
For a performance assessment of these queries and other options, see:
https://gist.github.com/bengotow/c8b5cd8989c9149ded56
Note: SUM(unread) works because unread is represented as an int: 0 or 1.
###
ReadCountsQuery = ->
"SELECT * FROM `ThreadCounts`"
SetCountsQuery = ->
"""
REPLACE INTO `ThreadCounts` (categoryId, unread, total)
SELECT
`ThreadCategory`.`value` as categoryId,
SUM(`ThreadCategory`.`unread`) as unread,
COUNT(*) as total
FROM `ThreadCategory`
WHERE
`ThreadCategory`.inAllMail = 1
GROUP BY `ThreadCategory`.`value`;
"""
UpdateCountsQuery = (objectIds, operator) ->
objectIdsString = "'" + objectIds.join("','") + "'"
"""
REPLACE INTO `ThreadCounts` (categoryId, unread, total)
SELECT
`ThreadCategory`.`value` as categoryId,
COALESCE((SELECT unread FROM `ThreadCounts` WHERE categoryId = `ThreadCategory`.`value`), 0) #{operator} SUM(`ThreadCategory`.`unread`) as unread,
COALESCE((SELECT total FROM `ThreadCounts` WHERE categoryId = `ThreadCategory`.`value`), 0) #{operator} COUNT(*) as total
FROM `ThreadCategory`
WHERE
`ThreadCategory`.id IN (#{objectIdsString}) AND
`ThreadCategory`.inAllMail = 1
GROUP BY `ThreadCategory`.`value`
"""
class CategoryDatabaseMutationObserver
beforeDatabaseChange: (query, {type, objects, objectIds, objectClass}) =>
if objectClass is Thread.name
query(UpdateCountsQuery(objectIds, '-'))
else
Promise.resolve()
afterDatabaseChange: (query, {type, objects, objectIds, objectClass}, beforeResolveValue) =>
if objectClass is Thread.name
query(UpdateCountsQuery(objectIds, '+'))
else
Promise.resolve()
class ThreadCountsStore extends NylasStore
CategoryDatabaseMutationObserver: CategoryDatabaseMutationObserver
constructor: ->
@_counts = {}
@_observer = new CategoryDatabaseMutationObserver()
DatabaseStore.addMutationHook(@_observer)
if NylasEnv.isMainWindow()
# For now, unread counts are only retrieved in the main window.
@ -71,20 +15,8 @@ class ThreadCountsStore extends NylasStore
@_onCountsChangedDebounced()
@_onCountsChangedDebounced()
if NylasEnv.isWorkWindow() and not NylasEnv.config.get('nylas.threadCountsValid')
@reset()
reset: =>
countsStartTime = null
DatabaseStore.inTransaction (t) =>
countsStartTime = Date.now()
DatabaseStore._query(SetCountsQuery())
.then =>
NylasEnv.config.set('nylas.threadCountsValid', true)
console.log("Recomputed all thread counts in #{Date.now() - countsStartTime}ms")
_onCountsChanged: =>
DatabaseStore._query(ReadCountsQuery()).then (results) =>
DatabaseStore._query("SELECT * FROM `ThreadCounts`").then (results) =>
nextCounts = {}
foundNegative = false