[geary/wip/725929-db-opt: 1/2] First pass at issue #1, translating identifiers into rowids



commit f720d3fc3922a4c189231d631a32895a1a7a6725
Author: Jim Nelson <jim yorba org>
Date:   Fri Mar 7 18:56:39 2014 -0800

    First pass at issue #1, translating identifiers into rowids
    
    Mostly "loop unrolling" by combining multiple DB reads into a single
    long request.

 src/engine/imap-db/imap-db-folder.vala             |  212 ++++++++++++++------
 .../imap-engine/imap-engine-replay-queue.vala      |    2 +-
 .../replay-ops/imap-engine-remove-email.vala       |    2 +-
 3 files changed, 148 insertions(+), 68 deletions(-)
---
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index bc9370c..759228c 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -18,7 +18,8 @@
 private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     public const Geary.Email.Field REQUIRED_FIELDS = Geary.Email.Field.PROPERTIES;
     
-    private const int LIST_EMAIL_CHUNK_COUNT = 5;
+    private const int LIST_EMAIL_WITH_MESSAGE_CHUNK_COUNT = 10;
+    private const int LIST_EMAIL_METADATA_COUNT = 100;
     private const int LIST_EMAIL_FIELDS_CHUNK_COUNT = 500;
     
     [Flags]
@@ -424,6 +425,12 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         // database ... first, gather locations of all emails in database
         Gee.List<LocationIdentifier> locations = new Gee.ArrayList<LocationIdentifier>();
         yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
+            // convert ids into LocationIdentifiers
+            Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids, flags,
+                cancellable);
+            if (locs == null || locs.size == 0)
+                return Db.TransactionOutcome.DONE;
+            
             StringBuilder sql = new StringBuilder("""
                 SELECT MessageLocationTable.message_id, ordering, remove_marker
                 FROM MessageLocationTable
@@ -436,24 +443,25 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 """);
             }
             
-            sql.append("WHERE folder_id = ? ");
+            if (locs.size != 1) {
+                sql.append("WHERE ordering IN (");
+                bool first = true;
+                foreach (LocationIdentifier location in locs) {
+                    if (!first)
+                        sql.append(",");
+                    
+                    sql.append(location.uid.to_string());
+                    first = false;
+                }
+                sql.append(")");
+            } else {
+                sql.append_printf("WHERE ordering = '%s' ", locs[0].uid.to_string());
+            }
+            
             if (only_incomplete)
                 sql.append_printf(" AND fields != %d ", Geary.Email.Field.ALL);
             
-            sql.append("AND ordering IN (");
-            bool first = true;
-            foreach (ImapDB.EmailIdentifier id in ids) {
-                LocationIdentifier? location = do_get_location_for_id(cx, id, flags, cancellable);
-                if (location == null)
-                    continue;
-                
-                if (!first)
-                    sql.append(", ");
-                
-                sql.append(location.uid.to_string());
-                first = false;
-            }
-            sql.append(")");
+            sql.append("AND folder_id = ? ");
             
             Db.Statement stmt = cx.prepare(sql.str);
             stmt.bind_rowid(0, folder_id);
@@ -472,12 +480,16 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         if (ids == null || ids.size == 0)
             return null;
         
-        int length_rounded_up = Numeric.int_round_up(ids.size, LIST_EMAIL_CHUNK_COUNT);
+        // chunk count depends on whether or not the message -- body + headers -- is being fetched
+        int chunk_count = required_fields.requires_any(Email.Field.BODY | Email.Field.HEADER)
+            ? LIST_EMAIL_WITH_MESSAGE_CHUNK_COUNT : LIST_EMAIL_METADATA_COUNT;
+        
+        int length_rounded_up = Numeric.int_round_up(ids.size, chunk_count);
         
         Gee.List<Geary.Email> results = new Gee.ArrayList<Geary.Email>();
-        for (int start = 0; start < length_rounded_up; start += LIST_EMAIL_CHUNK_COUNT) {
+        for (int start = 0; start < length_rounded_up; start += chunk_count) {
             // stop is the index *after* the end of the slice
-            int stop = Numeric.int_ceiling((start + LIST_EMAIL_CHUNK_COUNT), ids.size);
+            int stop = Numeric.int_ceiling((start + chunk_count), ids.size);
             
             Gee.List<LocationIdentifier>? slice = ids.slice(start, stop);
             assert(slice != null && slice.size > 0);
@@ -608,9 +620,10 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         // for another Folder
         Gee.Set<Imap.UID> uids = new Gee.HashSet<Imap.UID>();
         yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
-            foreach (ImapDB.EmailIdentifier id in ids) {
-                LocationIdentifier? location = do_get_location_for_id(cx, id, flags, cancellable);
-                if (location != null)
+            Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids, flags,
+                cancellable);
+            if (locs != null) {
+                foreach (LocationIdentifier location in locs)
                     uids.add(location.uid);
             }
             
@@ -640,10 +653,10 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         ListFlags flags, Cancellable? cancellable) throws Error {
         Gee.Set<ImapDB.EmailIdentifier> ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
         yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
-            foreach (Imap.UID uid in uids) {
-                LocationIdentifier? location = do_get_location_for_uid(cx, uid, flags,
-                    cancellable);
-                if (location != null)
+            Gee.List<LocationIdentifier>? locs = do_get_locations_for_uids(cx, uids, flags,
+                cancellable);
+            if (locs != null) {
+                foreach (LocationIdentifier location in locs)
                     ids.add(location.email_id);
             }
             
@@ -692,30 +705,31 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         // (since it may be located in multiple folders).  This means at some point in the future
         // a vacuum will be required to remove emails that are completely unassociated with the
         // account.
-        yield db.exec_transaction_async(Db.TransactionType.WO, (cx) => {
+        yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => {
+            Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids,
+                ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
+            if (locs == null || locs.size == 0)
+                return Db.TransactionOutcome.DONE;
+            
             unread_count = do_get_unread_count_for_ids(cx, ids, cancellable);
             do_add_to_unread_count(cx, -unread_count, cancellable);
             
-            // prepare DELETE Statement and invariants
-            Db.Statement delete_stmt = cx.prepare(
-                "DELETE FROM MessageLocationTable WHERE folder_id=? AND message_id=?");
-            delete_stmt.bind_rowid(0, folder_id);
-            
-            // remove one at a time, gather UIDs
-            Gee.HashSet<Imap.UID> uids = new Gee.HashSet<Imap.UID>();
-            foreach (ImapDB.EmailIdentifier id in ids) {
-                LocationIdentifier? location = do_get_location_for_id(cx, id,
-                    ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
-                if (location == null)
-                    continue;
-                
-                delete_stmt.reset(Db.ResetScope.SAVE_BINDINGS);
-                delete_stmt.bind_rowid(1, location.message_id);
-                
-                delete_stmt.exec(cancellable);
-                
-                uids.add(location.uid);
+            StringBuilder sql = new StringBuilder("""
+                DELETE FROM MessageLocationTable WHERE (
+            """);
+            bool first = true;
+            foreach (LocationIdentifier location in locs) {
+                if (!first)
+                    sql.append(" OR ");
+                sql.append_printf(" message_id='%s' ", location.message_id.to_string());
+                first = false;
             }
+            sql.append(") AND folder_id=?");
+            
+            Db.Statement stmt = cx.prepare(sql.str);
+            stmt.bind_rowid(0, folder_id);
+            
+            stmt.exec(cancellable);
             
             return Db.TransactionOutcome.COMMIT;
         }, cancellable);
@@ -907,16 +921,17 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         int unread_count = 0;
         Gee.Set<ImapDB.EmailIdentifier> removed_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
         yield db.exec_transaction_async(Db.TransactionType.RW, (cx) => {
+            Gee.List<LocationIdentifier?> locs = do_get_locations_for_ids(cx, ids,
+                ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
+            if (locs == null || locs.size == 0)
+                return Db.TransactionOutcome.DONE;
+            
             unread_count = do_get_unread_count_for_ids(cx, ids, cancellable);
             
             Gee.HashSet<Imap.UID> uids = new Gee.HashSet<Imap.UID>();
-            foreach (ImapDB.EmailIdentifier id in ids) {
-                LocationIdentifier? location = do_get_location_for_id(cx, id,
-                    ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
-                if (location != null) {
-                    uids.add(location.uid);
-                    removed_ids.add(location.email_id);
-                }
+            foreach (LocationIdentifier location in locs) {
+                uids.add(location.uid);
+                removed_ids.add(location.email_id);
             }
             
             if (uids.size > 0)
@@ -1023,21 +1038,22 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
                 continue;
             
             yield db.exec_transaction_async(Db.TransactionType.RO, (cx, cancellable) => {
+                Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids, flags,
+                    cancellable);
+                if (locs == null || locs.size == 0)
+                    return Db.TransactionOutcome.DONE;
+                
                 Db.Statement fetch_stmt = cx.prepare(
                     "SELECT fields FROM MessageTable WHERE id = ?");
                 
-                foreach (ImapDB.EmailIdentifier id in list) {
-                    LocationIdentifier? location_id = do_get_location_for_id(cx, id, flags,
-                        cancellable);
-                    if (location_id == null)
-                        continue;
-                    
+                // TODO: Unroll loop
+                foreach (LocationIdentifier location in locs) {
                     fetch_stmt.reset(Db.ResetScope.CLEAR_BINDINGS);
-                    fetch_stmt.bind_rowid(0, location_id.message_id);
+                    fetch_stmt.bind_rowid(0, location.message_id);
                     
                     Db.Result results = fetch_stmt.exec(cancellable);
                     if (!results.finished)
-                        map.set(id, (Geary.Email.Field) results.int_at(0));
+                        map.set(location.email_id, (Geary.Email.Field) results.int_at(0));
                 }
                 
                 return Db.TransactionOutcome.SUCCESS;
@@ -1085,6 +1101,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return !results.finished ? results.int_at(0) : 0;
     }
     
+    // TODO: Unroll loop
     private void do_mark_unmark_removed(Db.Connection cx, Gee.Collection<Imap.UID> uids,
         bool mark_removed, Cancellable? cancellable) throws Error {
         // prepare Statement for reuse
@@ -1491,16 +1508,18 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     
     private Gee.Map<ImapDB.EmailIdentifier, Geary.EmailFlags>? do_get_email_flags(Db.Connection cx,
         Gee.Collection<ImapDB.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
+        Gee.List<LocationIdentifier>? locs = do_get_locations_for_ids(cx, ids, ListFlags.NONE,
+            cancellable);
+        if (locs == null || locs.size == 0)
+            return null;
+        
         // prepare Statement for reuse
         Db.Statement fetch_stmt = cx.prepare("SELECT flags FROM MessageTable WHERE id=?");
         
         Gee.Map<ImapDB.EmailIdentifier, Geary.EmailFlags> map = new Gee.HashMap<
             Geary.EmailIdentifier, Geary.EmailFlags>();
-        foreach (ImapDB.EmailIdentifier id in ids) {
-            LocationIdentifier? location = do_get_location_for_id(cx, id, ListFlags.NONE, cancellable);
-            if (location == null)
-                continue;
-            
+        // TODO: Unroll this loop
+        foreach (LocationIdentifier location in locs) {
             fetch_stmt.reset(Db.ResetScope.CLEAR_BINDINGS);
             fetch_stmt.bind_rowid(0, location.message_id);
             
@@ -1528,6 +1547,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return new Geary.Imap.EmailFlags(Geary.Imap.MessageFlags.deserialize(results.string_at(0)));
     }
     
+    // TODO: Unroll loop
     private void do_set_email_flags(Db.Connection cx, Gee.Map<ImapDB.EmailIdentifier, Geary.EmailFlags> map,
         Cancellable? cancellable) throws Error {
         Db.Statement update_stmt = cx.prepare(
@@ -2049,6 +2069,36 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return (!flags.include_marked_for_remove() && location.marked_removed) ? null : location;
     }
     
+    private Gee.List<LocationIdentifier>? do_get_locations_for_ids(Db.Connection cx,
+        Gee.Collection<ImapDB.EmailIdentifier>? ids, ListFlags flags, Cancellable? cancellable)
+        throws Error {
+        if (ids == null || ids.size == 0)
+            return null;
+        
+        StringBuilder sql = new StringBuilder("""
+            SELECT message_id, ordering, remove_marker
+            FROM MessageLocationTable
+            WHERE (
+        """);
+        bool first = true;
+        foreach (ImapDB.EmailIdentifier id in ids) {
+            if (!first)
+                sql.append(" OR ");
+            sql.append_printf("message_id = '%s' ", id.message_id.to_string());
+            
+            first = false;
+        }
+        sql.append(") AND folder_id = ?");
+        
+        Db.Statement stmt = cx.prepare(sql.str);
+        stmt.bind_rowid(0, folder_id);
+        
+        Gee.List<LocationIdentifier> locs = do_results_to_locations(stmt.exec(cancellable), flags,
+            cancellable);
+        
+        return (locs.size > 0) ? locs : null;
+    }
+    
     private LocationIdentifier? do_get_location_for_uid(Db.Connection cx, Imap.UID uid,
         ListFlags flags, Cancellable? cancellable) throws Error {
         Db.Statement stmt = cx.prepare("""
@@ -2068,6 +2118,36 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         return (!flags.include_marked_for_remove() && location.marked_removed) ? null : location;
     }
     
+    private Gee.List<LocationIdentifier>? do_get_locations_for_uids(Db.Connection cx,
+        Gee.Collection<Imap.UID>? uids, ListFlags flags, Cancellable? cancellable)
+        throws Error {
+        if (uids == null || uids.size == 0)
+            return null;
+        
+        StringBuilder sql = new StringBuilder("""
+            SELECT message_id, ordering, remove_marker
+            FROM MessageLocationTable
+            WHERE (
+        """);
+        bool first = true;
+        foreach (Imap.UID uid in uids) {
+            if (!first)
+                sql.append(" OR ");
+            sql.append_printf("ordering = '%s' ", uid.to_string());
+            
+            first = false;
+        }
+        sql.append(") AND folder_id = ?");
+        
+        Db.Statement stmt = cx.prepare(sql.str);
+        stmt.bind_rowid(0, folder_id);
+        
+        Gee.List<LocationIdentifier> locs = do_results_to_locations(stmt.exec(cancellable), flags,
+            cancellable);
+        
+        return (locs.size > 0) ? locs : null;
+    }
+    
     private int do_get_unread_count_for_ids(Db.Connection cx,
         Gee.Collection<ImapDB.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
         // Fetch flags for each email and update this folder's unread count.
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala 
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index 9b78bd1..b01f1d8 100644
--- a/src/engine/imap-engine/imap-engine-replay-queue.vala
+++ b/src/engine/imap-engine/imap-engine-replay-queue.vala
@@ -499,7 +499,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
                     remote_err = replay_err;
                 }
             } else if (!is_close_op) {
-                remote_err = new EngineError.SERVER_UNAVAILABLE("Folder %s not available", to_string());
+                remote_err = new EngineError.SERVER_UNAVAILABLE("Folder %s not available", 
owner.to_string());
             }
             
             bool has_failed = !is_close_op && (status == ReplayOperation.Status.FAILED);
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
index b2a5ab3..99bc2d4 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
@@ -79,7 +79,7 @@ private class Geary.ImapEngine.RemoveEmail : Geary.ImapEngine.SendReplayOperatio
     }
     
     public override string describe_state() {
-        return "to_remove=%d removed_ids=%d".printf(to_remove.size,
+        return "to_remove.size=%d removed_ids.size=%d".printf(to_remove.size,
             (removed_ids != null) ? removed_ids.size : 0);
     }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]