mirror of
https://github.com/moparisthebest/k-9
synced 2024-12-26 01:28:50 -05:00
Fix and vastly simplify the query for the threaded message list
This requires another database schema change. With this change messages at the root of a thread reference themselves in the 'threads' table, i.e. 'root' contains the value of 'id' for these messages. It makes selecting all messages in a thread much simpler.
This commit is contained in:
parent
6b3a3f7ee0
commit
658b5dbff6
@ -106,27 +106,27 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
private static final String[] UID_CHECK_PROJECTION = { "uid" };
|
private static final String[] UID_CHECK_PROJECTION = { "uid" };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of UIDs to check for existence at once.
|
* Maximum number of UIDs to check for existence at once.
|
||||||
*
|
*
|
||||||
* @see LocalFolder#extractNewMessages(List)
|
* @see LocalFolder#extractNewMessages(List)
|
||||||
*/
|
*/
|
||||||
private static final int UID_CHECK_BATCH_SIZE = 500;
|
private static final int UID_CHECK_BATCH_SIZE = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of messages to perform flag updates at once.
|
* Maximum number of messages to perform flag updates on at once.
|
||||||
*
|
*
|
||||||
* @see #setFlag(List, Flag, boolean, boolean)
|
* @see #setFlag(List, Flag, boolean, boolean)
|
||||||
*/
|
*/
|
||||||
private static final int FLAG_UPDATE_BATCH_SIZE = 500;
|
private static final int FLAG_UPDATE_BATCH_SIZE = 500;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of threads to perform flag updates on at once.
|
* Maximum number of threads to perform flag updates on at once.
|
||||||
*
|
*
|
||||||
* @see #setFlagForThreads(List, Flag, boolean)
|
* @see #setFlagForThreads(List, Flag, boolean)
|
||||||
*/
|
*/
|
||||||
private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 400;
|
private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 500;
|
||||||
|
|
||||||
public static final int DB_VERSION = 47;
|
public static final int DB_VERSION = 48;
|
||||||
|
|
||||||
|
|
||||||
public static String getColumnNameForFlag(Flag flag) {
|
public static String getColumnNameForFlag(Flag flag) {
|
||||||
@ -278,6 +278,12 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
db.execSQL("DROP INDEX IF EXISTS threads_parent");
|
db.execSQL("DROP INDEX IF EXISTS threads_parent");
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
|
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
|
||||||
|
|
||||||
|
db.execSQL("DROP TRIGGER IF EXISTS set_thread_root");
|
||||||
|
db.execSQL("CREATE TRIGGER set_thread_root " +
|
||||||
|
"AFTER INSERT ON threads " +
|
||||||
|
"BEGIN " +
|
||||||
|
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
|
||||||
|
"END");
|
||||||
|
|
||||||
db.execSQL("DROP TABLE IF EXISTS attachments");
|
db.execSQL("DROP TABLE IF EXISTS attachments");
|
||||||
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
|
||||||
@ -650,6 +656,16 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
cv.putNull("thread_parent");
|
cv.putNull("thread_parent");
|
||||||
db.update("messages", cv, null, null);
|
db.update("messages", cv, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (db.getVersion() < 48) {
|
||||||
|
db.execSQL("UPDATE threads SET root=id WHERE root IS NULL");
|
||||||
|
|
||||||
|
db.execSQL("CREATE TRIGGER set_thread_root " +
|
||||||
|
"AFTER INSERT ON threads " +
|
||||||
|
"BEGIN " +
|
||||||
|
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
|
||||||
|
"END");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
|
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0");
|
||||||
@ -790,10 +806,7 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
// sure the thread structure is in a valid state (this may destroy existing valid
|
// sure the thread structure is in a valid state (this may destroy existing valid
|
||||||
// thread trees, but is much faster than adjusting the tree by removing messages
|
// thread trees, but is much faster than adjusting the tree by removing messages
|
||||||
// one by one).
|
// one by one).
|
||||||
ContentValues cv = new ContentValues();
|
db.execSQL("UPDATE threads SET root=id, parent=NULL");
|
||||||
cv.putNull("root");
|
|
||||||
cv.putNull("parent");
|
|
||||||
db.update("threads", cv, null, null);
|
|
||||||
|
|
||||||
// Delete entries from 'messages' table
|
// Delete entries from 'messages' table
|
||||||
db.execSQL("DELETE FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%'");
|
db.execSQL("DELETE FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%'");
|
||||||
@ -4193,18 +4206,13 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
|
public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
|
||||||
throws UnavailableStorageException {
|
throws UnavailableStorageException {
|
||||||
|
|
||||||
int len = selectionArgs.length;
|
|
||||||
String[] args = new String[len * 2];
|
|
||||||
System.arraycopy(selectionArgs, 0, args, 0, len);
|
|
||||||
System.arraycopy(selectionArgs, 0, args, len, len);
|
|
||||||
|
|
||||||
db.execSQL("UPDATE messages SET " + flagColumn + " = " + ((newState) ? "1" : "0") +
|
db.execSQL("UPDATE messages SET " + flagColumn + " = " + ((newState) ? "1" : "0") +
|
||||||
" WHERE id IN (" +
|
" WHERE id IN (" +
|
||||||
"SELECT m.id FROM threads t " +
|
"SELECT m.id FROM threads t " +
|
||||||
"LEFT JOIN messages m ON (t.message_id = m.id) " +
|
"LEFT JOIN messages m ON (t.message_id = m.id) " +
|
||||||
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
||||||
"AND (t.id" + selectionSet + " OR t.root" + selectionSet + "))",
|
"AND t.root" + selectionSet + ")",
|
||||||
args);
|
selectionArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -4256,14 +4264,9 @@ public class LocalStore extends Store implements Serializable {
|
|||||||
"LEFT JOIN messages m ON (t.message_id = m.id) " +
|
"LEFT JOIN messages m ON (t.message_id = m.id) " +
|
||||||
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
"LEFT JOIN folders f ON (m.folder_id = f.id) " +
|
||||||
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
"WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
|
||||||
"AND (t.id" + selectionSet + " OR t.root" + selectionSet + ")";
|
"AND t.root" + selectionSet;
|
||||||
|
|
||||||
int len = selectionArgs.length;
|
getDataFromCursor(db.rawQuery(sql, selectionArgs));
|
||||||
String[] args = new String[len * 2];
|
|
||||||
System.arraycopy(selectionArgs, 0, args, 0, len);
|
|
||||||
System.arraycopy(selectionArgs, 0, args, len, len);
|
|
||||||
|
|
||||||
getDataFromCursor(db.rawQuery(sql, args));
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
String sql =
|
String sql =
|
||||||
|
@ -63,33 +63,6 @@ public class EmailProvider extends ContentProvider {
|
|||||||
|
|
||||||
private static final String MESSAGES_TABLE = "messages";
|
private static final String MESSAGES_TABLE = "messages";
|
||||||
|
|
||||||
private static final String[] MESSAGES_COLUMNS = {
|
|
||||||
MessageColumns.ID,
|
|
||||||
MessageColumns.UID,
|
|
||||||
MessageColumns.INTERNAL_DATE,
|
|
||||||
MessageColumns.SUBJECT,
|
|
||||||
MessageColumns.DATE,
|
|
||||||
MessageColumns.MESSAGE_ID,
|
|
||||||
MessageColumns.SENDER_LIST,
|
|
||||||
MessageColumns.TO_LIST,
|
|
||||||
MessageColumns.CC_LIST,
|
|
||||||
MessageColumns.BCC_LIST,
|
|
||||||
MessageColumns.REPLY_TO_LIST,
|
|
||||||
MessageColumns.FLAGS,
|
|
||||||
MessageColumns.ATTACHMENT_COUNT,
|
|
||||||
MessageColumns.FOLDER_ID,
|
|
||||||
MessageColumns.PREVIEW,
|
|
||||||
MessageColumns.READ,
|
|
||||||
MessageColumns.FLAGGED,
|
|
||||||
MessageColumns.ANSWERED,
|
|
||||||
MessageColumns.FORWARDED,
|
|
||||||
InternalMessageColumns.DELETED,
|
|
||||||
InternalMessageColumns.EMPTY,
|
|
||||||
InternalMessageColumns.TEXT_CONTENT,
|
|
||||||
InternalMessageColumns.HTML_CONTENT,
|
|
||||||
InternalMessageColumns.MIME_TYPE
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Map<String, String> THREAD_AGGREGATION_FUNCS = new HashMap<String, String>();
|
private static final Map<String, String> THREAD_AGGREGATION_FUNCS = new HashMap<String, String>();
|
||||||
static {
|
static {
|
||||||
THREAD_AGGREGATION_FUNCS.put(MessageColumns.DATE, "MAX");
|
THREAD_AGGREGATION_FUNCS.put(MessageColumns.DATE, "MAX");
|
||||||
@ -403,11 +376,12 @@ public class EmailProvider extends ContentProvider {
|
|||||||
final String aggregationFunc = THREAD_AGGREGATION_FUNCS.get(columnName);
|
final String aggregationFunc = THREAD_AGGREGATION_FUNCS.get(columnName);
|
||||||
|
|
||||||
if (MessageColumns.ID.equals(columnName)) {
|
if (MessageColumns.ID.equals(columnName)) {
|
||||||
query.append("u." + MessageColumns.ID + " AS " + MessageColumns.ID);
|
query.append("m." + MessageColumns.ID + " AS " + MessageColumns.ID);
|
||||||
} else if (aggregationFunc != null) {
|
} else if (aggregationFunc != null) {
|
||||||
query.append(aggregationFunc + "(" + columnName + ") AS " + columnName);
|
query.append("a.");
|
||||||
} else if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
|
query.append(columnName);
|
||||||
query.append("COUNT(g) AS " + SpecialColumns.THREAD_COUNT);
|
query.append(" AS ");
|
||||||
|
query.append(columnName);
|
||||||
} else {
|
} else {
|
||||||
query.append(columnName);
|
query.append(columnName);
|
||||||
}
|
}
|
||||||
@ -415,23 +389,29 @@ public class EmailProvider extends ContentProvider {
|
|||||||
|
|
||||||
query.append(" FROM (");
|
query.append(" FROM (");
|
||||||
|
|
||||||
createThreadedSubQuery(projection, selection, selectionArgs, "t1.id = t2.id", query);
|
createThreadedSubQuery(projection, selection, selectionArgs, query);
|
||||||
query.append(" UNION ALL ");
|
|
||||||
createThreadedSubQuery(projection, selection, selectionArgs, "t1.id = t2.root", query);
|
|
||||||
|
|
||||||
query.append(") u GROUP BY g");
|
query.append(") a ");
|
||||||
|
|
||||||
|
query.append("LEFT JOIN " + THREADS_TABLE + " t " +
|
||||||
|
"ON (t." + ThreadColumns.ROOT + " = a.thread_root) " +
|
||||||
|
"LEFT JOIN " + MESSAGES_TABLE + " m " +
|
||||||
|
"ON (m." + MessageColumns.ID + " = t." + ThreadColumns.MESSAGE_ID +
|
||||||
|
") ");
|
||||||
|
|
||||||
|
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
|
||||||
|
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
|
||||||
|
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID +
|
||||||
|
") ");
|
||||||
|
}
|
||||||
|
|
||||||
|
query.append("WHERE m." + MessageColumns.DATE + " = a." + MessageColumns.DATE);
|
||||||
if (!StringUtils.isNullOrEmpty(sortOrder)) {
|
if (!StringUtils.isNullOrEmpty(sortOrder)) {
|
||||||
query.append(" ORDER BY ");
|
query.append(" ORDER BY ");
|
||||||
query.append(sortOrder);
|
query.append(sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need the selection arguments twice. Once for each sub query.
|
return db.rawQuery(query.toString(), selectionArgs);
|
||||||
String[] args = new String[selectionArgs.length * 2];
|
|
||||||
System.arraycopy(selectionArgs, 0, args, 0, selectionArgs.length);
|
|
||||||
System.arraycopy(selectionArgs, 0, args, selectionArgs.length, selectionArgs.length);
|
|
||||||
|
|
||||||
return db.rawQuery(query.toString(), args);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (UnavailableStorageException e) {
|
} catch (UnavailableStorageException e) {
|
||||||
@ -440,53 +420,51 @@ public class EmailProvider extends ContentProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void createThreadedSubQuery(String[] projection, String selection,
|
private void createThreadedSubQuery(String[] projection, String selection,
|
||||||
String[] selectionArgs, String join, StringBuilder query) {
|
String[] selectionArgs, StringBuilder query) {
|
||||||
|
|
||||||
query.append("SELECT h." + MessageColumns.ID + " AS g");
|
query.append("SELECT t." + ThreadColumns.ROOT + " AS thread_root");
|
||||||
for (String columnName : projection) {
|
for (String columnName : projection) {
|
||||||
|
String aggregationFunc = THREAD_AGGREGATION_FUNCS.get(columnName);
|
||||||
|
|
||||||
if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
|
if (SpecialColumns.THREAD_COUNT.equals(columnName)) {
|
||||||
// Skip
|
query.append(",COUNT(t." + ThreadColumns.ROOT + ") AS " +
|
||||||
} else if (SpecialColumns.FOLDER_NAME.equals(columnName) ||
|
SpecialColumns.THREAD_COUNT);
|
||||||
SpecialColumns.INTEGRATE.equals(columnName)) {
|
} else if (aggregationFunc != null) {
|
||||||
query.append("," + columnName);
|
query.append(",");
|
||||||
} else if (ThreadColumns.ROOT.equals(columnName)) {
|
query.append(aggregationFunc);
|
||||||
// Always return the thread ID of the root message (even for the root
|
query.append("(");
|
||||||
// message itself)
|
query.append(columnName);
|
||||||
query.append(",CASE WHEN t2." + ThreadColumns.ROOT + " IS NULL THEN " +
|
query.append(") AS ");
|
||||||
"t2." + ThreadColumns.ID + " ELSE t2." + ThreadColumns.ROOT +
|
query.append(columnName);
|
||||||
" END AS " + ThreadColumns.ROOT);
|
|
||||||
} else {
|
} else {
|
||||||
query.append(",m.");
|
// Skip
|
||||||
query.append(columnName);
|
|
||||||
query.append(" AS ");
|
|
||||||
query.append(columnName);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query.append(
|
query.append(
|
||||||
" FROM messages h " +
|
" FROM " + MESSAGES_TABLE + " m " +
|
||||||
"LEFT JOIN threads t1 ON (t1.message_id = h.id) " +
|
"LEFT JOIN " + THREADS_TABLE + " t " +
|
||||||
"JOIN threads t2 ON (");
|
"ON (t." + ThreadColumns.MESSAGE_ID + " = m." + MessageColumns.ID + ")");
|
||||||
query.append(join);
|
|
||||||
query.append(") " +
|
|
||||||
"LEFT JOIN messages m ON (m.id = t2.message_id) ");
|
|
||||||
|
|
||||||
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
|
if (Utility.arrayContainsAny(projection, (Object[]) FOLDERS_COLUMNS)) {
|
||||||
query.append("LEFT JOIN folders f ON (m.folder_id = f.id) ");
|
query.append("LEFT JOIN " + FOLDERS_TABLE + " f " +
|
||||||
|
"ON (m." + MessageColumns.FOLDER_ID + " = f." + FolderColumns.ID +
|
||||||
|
")");
|
||||||
}
|
}
|
||||||
|
|
||||||
query.append(
|
query.append(" WHERE " +
|
||||||
"WHERE " +
|
"(" + InternalMessageColumns.DELETED + " = 0 AND " +
|
||||||
"(t1.root IS NULL AND " +
|
"(" + InternalMessageColumns.EMPTY + " IS NULL OR " +
|
||||||
"m.deleted = 0 AND " +
|
InternalMessageColumns.EMPTY + " != 1))");
|
||||||
"(m.empty IS NULL OR m.empty != 1))");
|
|
||||||
|
|
||||||
if (!StringUtils.isNullOrEmpty(selection)) {
|
if (!StringUtils.isNullOrEmpty(selection)) {
|
||||||
query.append(" AND (");
|
query.append(" AND (");
|
||||||
query.append(SqlQueryBuilder.addPrefixToSelection(MESSAGES_COLUMNS,
|
query.append(selection);
|
||||||
"h.", selection));
|
|
||||||
query.append(")");
|
query.append(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.append(" GROUP BY t." + ThreadColumns.ROOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Cursor getThread(String accountUuid, final String[] projection, final String threadId,
|
protected Cursor getThread(String accountUuid, final String[] projection, final String threadId,
|
||||||
@ -528,8 +506,8 @@ public class EmailProvider extends ContentProvider {
|
|||||||
") ");
|
") ");
|
||||||
}
|
}
|
||||||
|
|
||||||
query.append("WHERE (t." + ThreadColumns.ID + " = ? OR " +
|
query.append("WHERE " +
|
||||||
ThreadColumns.ROOT + " = ?) AND " +
|
ThreadColumns.ROOT + " = ? AND " +
|
||||||
InternalMessageColumns.DELETED + " = 0 AND (" +
|
InternalMessageColumns.DELETED + " = 0 AND (" +
|
||||||
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
InternalMessageColumns.EMPTY + " IS NULL OR " +
|
||||||
InternalMessageColumns.EMPTY + " != 1)");
|
InternalMessageColumns.EMPTY + " != 1)");
|
||||||
@ -538,7 +516,7 @@ public class EmailProvider extends ContentProvider {
|
|||||||
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
|
query.append(SqlQueryBuilder.addPrefixToSelection(FIXUP_MESSAGES_COLUMNS,
|
||||||
"m.", sortOrder));
|
"m.", sortOrder));
|
||||||
|
|
||||||
return db.rawQuery(query.toString(), new String[] { threadId, threadId });
|
return db.rawQuery(query.toString(), new String[] { threadId });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (UnavailableStorageException e) {
|
} catch (UnavailableStorageException e) {
|
||||||
|
@ -66,12 +66,6 @@ public class SqlQueryBuilder {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case THREAD_ID: {
|
|
||||||
query.append("threads.id = ? OR threads.root = ?");
|
|
||||||
selectionArgs.add(condition.value);
|
|
||||||
selectionArgs.add(condition.value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
default: {
|
||||||
appendCondition(condition, query, selectionArgs);
|
appendCondition(condition, query, selectionArgs);
|
||||||
}
|
}
|
||||||
@ -179,7 +173,10 @@ public class SqlQueryBuilder {
|
|||||||
columnName = "display_class";
|
columnName = "display_class";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case THREAD_ID:
|
case THREAD_ID: {
|
||||||
|
columnName = "threads.root";
|
||||||
|
break;
|
||||||
|
}
|
||||||
case FOLDER:
|
case FOLDER:
|
||||||
case SEARCHABLE: {
|
case SEARCHABLE: {
|
||||||
// Special cases handled in buildWhereClauseInternal()
|
// Special cases handled in buildWhereClauseInternal()
|
||||||
|
Loading…
Reference in New Issue
Block a user