[geary/cherry-pick-82f0ca8d] Merge branch 'mjog/db-result-timing' into 'mainline'




commit 1b8c9349c626d215ef219296f1b35af393b2bc3f
Author: Michael Gratton <mike vee net>
Date:   Thu Sep 24 22:54:30 2020 +0000

    Merge branch 'mjog/db-result-timing' into 'mainline'
    
    Database contention and locking redux
    
    See merge request GNOME/geary!578
    
    (cherry picked from commit 82f0ca8d0f38e2323449bd8fb26c9ee88afd176b)
    
    3b6dd303 Geary.Db.Context: Update access to DatabaseConnections
    a1d31847 Geary.Db.Result: Log large elapsed query times as a warning
    485868d5 Geary.Db.DatabaseConnection: Check elapsed time for exec statements
    0fa0d0ea Geary.Db.Statement: Minor code cleanup
    940ca831 Geary.Db.Context: Remove separate `logging_parent` property
    e39853db Geary.ImapEngine.GenericAccount: Set database logging parent per account
    0f60c285 Geary.Db: Update SQL logging
    915a38fa Geary.ImapDb.Account: Slice up search table population work better
    3d8b86dd Geary.ImapDB.Folder: Drop create/merge batch size down

 src/engine/db/db-connection.vala                   |   5 +-
 src/engine/db/db-context.vala                      |  46 +++--
 src/engine/db/db-database-connection.vala          |  37 ++--
 src/engine/db/db-database.vala                     |  16 +-
 src/engine/db/db-result.vala                       |  53 +++---
 src/engine/db/db-statement.vala                    |  39 ++--
 src/engine/db/db-transaction-connection.vala       |  11 +-
 src/engine/imap-db/imap-db-account.vala            | 205 ++++++++++++---------
 src/engine/imap-db/imap-db-attachment.vala         |   2 +-
 src/engine/imap-db/imap-db-folder.vala             |   2 +-
 .../imap-engine/imap-engine-generic-account.vala   |   2 +
 11 files changed, 245 insertions(+), 173 deletions(-)
---
diff --git a/src/engine/db/db-connection.vala b/src/engine/db/db-connection.vala
index 4f0859e11..ebce27dc3 100644
--- a/src/engine/db/db-connection.vala
+++ b/src/engine/db/db-connection.vala
@@ -18,7 +18,7 @@
  * A connection will be automatically closed when its last reference
  * is dropped.
  */
-public interface Geary.Db.Connection : Context {
+public interface Geary.Db.Connection : BaseObject {
 
     private const string PRAGMA_FOREIGN_KEYS = "foreign_keys";
     private const string PRAGMA_RECURSIVE_TRIGGERS = "recursive_triggers";
@@ -278,7 +278,8 @@ public interface Geary.Db.Connection : Context {
      *
      * @see exec
      */
-    public abstract Result query(string sql, GLib.Cancellable? cancellable = null)
+    public abstract Result query(string sql,
+                                 GLib.Cancellable? cancellable = null)
         throws GLib.Error;
 
     /**
diff --git a/src/engine/db/db-context.vala b/src/engine/db/db-context.vala
index 9bbb85038..7b8b9d654 100644
--- a/src/engine/db/db-context.vala
+++ b/src/engine/db/db-context.vala
@@ -16,16 +16,25 @@
 public abstract class Geary.Db.Context : BaseObject, Logging.Source {
 
 
+    /** The GLib logging domain used by this class. */
+    public const string LOGGING_DOMAIN = Logging.DOMAIN + ".Db";
+
+
     /**
-     * Determines if SQL queries and results will be logged.
+     * Determines if SQL queries will be logged.
      *
-     * This will cause extremely verbose logging, so enable with care.
+     * This will cause verbose logging, so enable with care.
      */
     public static bool enable_sql_logging = false;
 
 
-    /** The GLib logging domain used by this class. */
-    public const string LOGGING_DOMAIN = Logging.DOMAIN + ".Db";
+    /**
+     * Determines if SQL results will be logged.
+     *
+     * This will cause extremely verbose logging, so enable with extra care.
+     */
+    public static bool enable_result_logging = false;
+
 
     /** {@inheritDoc} */
     public string logging_domain {
@@ -33,34 +42,41 @@ public abstract class Geary.Db.Context : BaseObject, Logging.Source {
     }
 
     /** {@inheritDoc} */
-    public Logging.Source? logging_parent { get { return _logging_parent; } }
-    private weak Logging.Source? _logging_parent = null;
+    public abstract Logging.Source? logging_parent { get; }
 
 
-    public virtual Database? get_database() {
+    internal virtual Database? get_database() {
         return get_connection() != null ? get_connection().database : null;
     }
 
-    public virtual Connection? get_connection() {
+    internal virtual DatabaseConnection? get_connection() {
         return get_statement() != null ? get_statement().connection : null;
     }
 
-    public virtual Statement? get_statement() {
+    internal virtual Statement? get_statement() {
         return get_result() != null ? get_result().statement : null;
     }
 
-    public virtual Result? get_result() {
+    internal virtual Result? get_result() {
         return null;
     }
 
-    /** {@inheritDoc} */
-    public void set_logging_parent(Logging.Source parent) {
-        this._logging_parent = parent;
-    }
-
     /** {@inheritDoc} */
     public abstract Logging.State to_logging_state();
 
+
+    protected inline void check_elapsed(string message,
+                                        GLib.Timer timer)
+        throws DatabaseError {
+        var elapsed = timer.elapsed();
+        var threshold = (get_connection().busy_timeout * 1000.0) / 2.0;
+        if (threshold > 0 && elapsed > threshold) {
+            warning("%s: elapsed time: %lfs (>50%)", message, elapsed);
+        } else if (elapsed > 1.0) {
+            debug("%s: elapsed time: %lfs (>1s)", message, elapsed);
+        }
+    }
+
     protected inline int throw_on_error(string? method, int result, string? raw = null) throws DatabaseError 
{
         return Db.throw_on_error(this, method, result, raw);
     }
diff --git a/src/engine/db/db-database-connection.vala b/src/engine/db/db-database-connection.vala
index 4e7ceb788..29d52fd16 100644
--- a/src/engine/db/db-database-connection.vala
+++ b/src/engine/db/db-database-connection.vala
@@ -66,6 +66,11 @@ public class Geary.Db.DatabaseConnection : Context, Connection {
     public Database database { get { return this._database; } }
     private weak Database _database;
 
+    /** {@inheritDoc} */
+    public override Logging.Source? logging_parent {
+        get { return this._database; }
+    }
+
     /** {@inheritDoc} */
     internal Sqlite.Database db { get { return this._db; } }
     private Sqlite.Database _db;
@@ -118,9 +123,10 @@ public class Geary.Db.DatabaseConnection : Context, Connection {
 
     /** {@inheritDoc} */
     public Statement prepare(string sql) throws DatabaseError {
-        var prepared = new Statement(this, sql);
-        prepared.set_logging_parent(this);
-        return prepared;
+        if (Db.Context.enable_sql_logging) {
+            debug(sql);
+        }
+        return new Statement(this, sql);
     }
 
     /** {@inheritDoc} */
@@ -132,23 +138,28 @@ public class Geary.Db.DatabaseConnection : Context, Connection {
     /** {@inheritDoc} */
     public void exec(string sql, GLib.Cancellable? cancellable = null)
         throws GLib.Error {
+        check_cancelled("Connection.exec", cancellable);
         if (Db.Context.enable_sql_logging) {
-            debug("exec:\n\t%s", sql);
+            debug(sql);
         }
-
-        check_cancelled("Connection.exec", cancellable);
-        throw_on_error("Connection.exec", db.exec(sql), sql);
+        var timer = new GLib.Timer();
+        throw_on_error("Connection.exec_file", this.db.exec(sql), sql);
+        check_elapsed("Query \"%s\"".printf(sql), timer);
     }
 
     /** {@inheritDoc} */
     public void exec_file(GLib.File file, GLib.Cancellable? cancellable = null)
         throws GLib.Error {
         check_cancelled("Connection.exec_file", cancellable);
+        if (Db.Context.enable_sql_logging) {
+            debug(file.get_path());
+        }
 
         string sql;
         FileUtils.get_contents(file.get_path(), out sql);
-
-        exec(sql, cancellable);
+        var timer = new GLib.Timer();
+        throw_on_error("Connection.exec_file", this.db.exec(sql), sql);
+        check_elapsed(file.get_path(), timer);
     }
 
     /**
@@ -255,13 +266,13 @@ public class Geary.Db.DatabaseConnection : Context, Connection {
         return yield job.wait_for_completion_async();
     }
 
-    public override Connection? get_connection() {
-        return this;
-    }
-
     /** {@inheritDoc} */
     public override Logging.State to_logging_state() {
         return new Logging.State(this, "%u", this.cx_number);
     }
 
+    internal override DatabaseConnection? get_connection() {
+        return this;
+    }
+
 }
diff --git a/src/engine/db/db-database.vala b/src/engine/db/db-database.vala
index 592bd3066..df5bed216 100644
--- a/src/engine/db/db-database.vala
+++ b/src/engine/db/db-database.vala
@@ -57,6 +57,10 @@ public class Geary.Db.Database : Context {
         }
     }
 
+    /** {@inheritDoc} */
+    public override Logging.Source? logging_parent { get { return _logging_parent; } }
+    private weak Logging.Source? _logging_parent = null;
+
     private DatabaseConnection? primary = null;
     private int outstanding_async_jobs = 0;
     private ThreadPool<TransactionAsyncJob>? thread_pool = null;
@@ -143,7 +147,6 @@ public class Geary.Db.Database : Context {
             var cx = new DatabaseConnection(
                 this, Sqlite.OPEN_READWRITE, cancellable
             );
-            cx.set_logging_parent(this);
 
             try {
                 // drop existing test table (in case created in prior failed open)
@@ -233,7 +236,6 @@ public class Geary.Db.Database : Context {
         DatabaseConnection cx = new DatabaseConnection(
             this, sqlite_flags, cancellable
         );
-        cx.set_logging_parent(this);
         prepare_connection(cx);
         return cx;
     }
@@ -357,9 +359,9 @@ public class Geary.Db.Database : Context {
         return yield job.wait_for_completion_async();
     }
 
-
-    public override Database? get_database() {
-        return this;
+    /** Sets the logging parent context object for this database. */
+    public void set_logging_parent(Logging.Source parent) {
+        this._logging_parent = parent;
     }
 
     /** {@inheritDoc} */
@@ -386,6 +388,10 @@ public class Geary.Db.Database : Context {
         this.thread_pool.add(new_job);
     }
 
+    internal override Database? get_database() {
+        return this;
+    }
+
     /**
      * Hook for subclasses to modify a new SQLite connection before use.
      *
diff --git a/src/engine/db/db-result.vala b/src/engine/db/db-result.vala
index 1ec3ed551..14dc71a96 100644
--- a/src/engine/db/db-result.vala
+++ b/src/engine/db/db-result.vala
@@ -8,14 +8,20 @@ public class Geary.Db.Result : Geary.Db.Context {
     public bool finished { get; private set; default = false; }
 
 
+    /** The statement this result was generated from. */
     public Statement statement { get; private set; }
 
+    /** The current row represented by this result. */
+    public uint64 row { get; private set; default = 0; }
+
+    /** {@inheritDoc} */
+    public override Logging.Source? logging_parent {
+        get { return this.statement; }
+    }
 
     // This results in an automatic first next().
     internal Result(Statement statement, Cancellable? cancellable) throws Error {
         this.statement = statement;
-        set_logging_parent(statement);
-
         statement.was_reset.connect(on_query_finished);
         statement.bindings_cleared.connect(on_query_finished);
 
@@ -37,13 +43,14 @@ public class Geary.Db.Result : Geary.Db.Context {
     public bool next(Cancellable? cancellable = null) throws Error {
         check_cancelled("Result.next", cancellable);
 
-        if (!finished) {
-            Timer timer = new Timer();
-            finished = throw_on_error("Result.next", statement.stmt.step(), statement.sql) != Sqlite.ROW;
-            if (timer.elapsed() > 1.0)
-                debug("\n\nDB QUERY STEP \"%s\"\nelapsed=%lf\n\n", statement.sql, timer.elapsed());
-
-            log_result(finished ? "NO ROW" : "ROW");
+        if (!this.finished) {
+            this.row++;
+            var timer = new GLib.Timer();
+            this.finished = throw_on_error(
+                "Result.next", statement.stmt.step(), statement.sql
+            ) != Sqlite.ROW;
+            check_elapsed("Result.next", timer);
+            log_result(this.finished ? "NO ROW" : "ROW");
         }
 
         return !finished;
@@ -294,26 +301,24 @@ public class Geary.Db.Result : Geary.Db.Context {
         return column;
     }
 
-    public override Result? get_result() {
-        return this;
-    }
-
     /** {@inheritDoc} */
     public override Logging.State to_logging_state() {
-        return new Logging.State(this, this.finished ? "finished" : "not finished");
+        return new Logging.State(
+            this,
+            "%llu, %s",
+            this.row,
+            this.finished ? "finished" : "!finished"
+        );
+    }
+
+    internal override Result? get_result() {
+        return this;
     }
 
     [PrintfFormat]
-    private void log_result(string fmt, ...) {
-        if (Db.Context.enable_sql_logging) {
-            Statement? stmt = get_statement();
-            if (stmt != null) {
-                debug("%s\n\t<%s>",
-                      fmt.vprintf(va_list()),
-                      (stmt != null) ? "%.100s".printf(stmt.sql) : "no sql");
-            } else {
-                debug(fmt.vprintf(va_list()));
-            }
+    private inline void log_result(string fmt, ...) {
+        if (Db.Context.enable_result_logging) {
+            debug(fmt.vprintf(va_list()));
         }
     }
 
diff --git a/src/engine/db/db-statement.vala b/src/engine/db/db-statement.vala
index 0a36dfb1e..072692ffb 100644
--- a/src/engine/db/db-statement.vala
+++ b/src/engine/db/db-statement.vala
@@ -9,15 +9,21 @@ private extern string? sqlite3_expanded_sql(Sqlite.Statement stmt);
 
 public class Geary.Db.Statement : Context {
 
-    public string sql {
-        get { return this.stmt.sql(); }
+
+    public string sql { get; private set; }
+
+    /** {@inheritDoc} */
+    public override Logging.Source? logging_parent {
+        get { return this.connection; }
     }
 
-    public Connection connection { get; private set; }
+    internal DatabaseConnection connection { get; private set; }
 
     internal Sqlite.Statement stmt;
 
     private Gee.HashMap<string, int>? column_map = null;
+    private Gee.HashSet<Memory.Buffer> held_buffers = new Gee.HashSet<Memory.Buffer>();
+
 
     /**
      * Fired when the Statement is executed the first time (after creation or after a reset).
@@ -34,22 +40,21 @@ public class Geary.Db.Statement : Context {
      */
     public signal void bindings_cleared();
 
-    private Gee.HashSet<Memory.Buffer> held_buffers = new Gee.HashSet<Memory.Buffer>();
 
-    internal Statement(Connection connection, string sql) throws DatabaseError {
+    internal Statement(DatabaseConnection connection, string sql)
+        throws DatabaseError {
         this.connection = connection;
-        throw_on_error("Statement.ctor", connection.db.prepare_v2(sql, -1, out stmt, null), sql);
+        this.sql = sql;
+        throw_on_error(
+            "Statement.ctor",
+            connection.db.prepare_v2(sql, -1, out stmt, null),
+            sql
+        );
     }
 
     /** Returns SQL for the statement with bound parameters expanded. */
     public string? get_expanded_sql() {
-        // Replace all this with `Sqlite.Statement.expanded_sql` is
-        // readily available. See:
-        // https://gitlab.gnome.org/GNOME/vala/merge_requests/74
-        string* sqlite = sqlite3_expanded_sql(this.stmt);
-        string? sql = sqlite;
-        Sqlite.Memory.free((void*) sqlite);
-        return sql;
+        return this.stmt.expanded_sql();
     }
 
     /**
@@ -271,13 +276,13 @@ public class Geary.Db.Statement : Context {
         return this;
     }
 
-    public override Statement? get_statement() {
-        return this;
-    }
-
     /** {@inheritDoc} */
     public override Logging.State to_logging_state() {
         return new Logging.State(this, this.sql);
     }
 
+    internal override Statement? get_statement() {
+        return this;
+    }
+
 }
diff --git a/src/engine/db/db-transaction-connection.vala b/src/engine/db/db-transaction-connection.vala
index 48244dbc7..ebdd18b42 100644
--- a/src/engine/db/db-transaction-connection.vala
+++ b/src/engine/db/db-transaction-connection.vala
@@ -9,7 +9,7 @@
 /**
  * A connection to the database for transactions.
  */
-internal class Geary.Db.TransactionConnection : Context, Connection {
+internal class Geary.Db.TransactionConnection : BaseObject, Connection {
 
 
     /** {@inheritDoc} */
@@ -54,13 +54,4 @@ internal class Geary.Db.TransactionConnection : Context, Connection {
         this.db_cx.exec_file(file, cancellable);
     }
 
-    public override Connection? get_connection() {
-        return this;
-    }
-
-    /** {@inheritDoc} */
-    public override Logging.State to_logging_state() {
-        return new Logging.State(this, "");
-    }
-
 }
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index 99244dc26..54522b90e 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -955,8 +955,78 @@ private class Geary.ImapDB.Account : BaseObject {
 
     public async void populate_search_table(Cancellable? cancellable) {
         debug("%s: Populating search table", account_information.id);
+        // Since all queries involved can be quite extensive and this
+        // is not a time-critical operation, split them up
+
+        var search_ids = new Gee.HashSet<int64?>(
+            Collection.int64_hash_func,
+            Collection.int64_equal_func
+        );
+        var message_ids = new Gee.HashSet<int64?>(
+            Collection.int64_hash_func,
+            Collection.int64_equal_func
+        );
+        var unindexed_message_ids = new Gee.HashSet<int64?>(
+            Collection.int64_hash_func,
+            Collection.int64_equal_func
+        );
+
         try {
-            while (!yield populate_search_table_batch_async(50, cancellable)) {
+            yield this.db.exec_transaction_async(
+                RO,
+                (cx, cancellable) => {
+                    // Embedding a SELECT within a SELECT is painfully slow
+                    // with SQLite, and a LEFT OUTER JOIN will still take in
+                    // the order of seconds, so manually perform the operation
+
+                    var result = cx.prepare(
+                        "SELECT docid FROM MessageSearchTable"
+                    ).exec(cancellable);
+                    while (!result.finished) {
+                        search_ids.add(result.rowid_at(0));
+                        result.next(cancellable);
+                    }
+
+                    var stmt = cx.prepare(
+                        "SELECT id FROM MessageTable WHERE (fields & ?) = ?"
+                    );
+                    stmt.bind_uint(0, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
+                    stmt.bind_uint(1, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
+                    result = stmt.exec(cancellable);
+                    while (!result.finished) {
+                        message_ids.add(result.rowid_at(0));
+                        result.next(cancellable);
+                    }
+
+                    return DONE;
+                },
+                cancellable
+            );
+
+            // Run this in a separate thread since it could be quite a
+            // substantial process for large accounts
+            yield Nonblocking.Concurrent.global.schedule_async(
+                () => {
+                    foreach (int64 message_id in message_ids) {
+                        if (!search_ids.contains(message_id)) {
+                            unindexed_message_ids.add(message_id);
+                        }
+                    }
+                },
+                cancellable
+            );
+
+            debug("%s: Found %d missing messages to populate",
+                  this.account_information.id,
+                  unindexed_message_ids.size
+            );
+
+            // Do the actual updating in batches since these require
+            // RW transactions
+            while (!unindexed_message_ids.is_empty) {
+                yield populate_search_table_batch_async(
+                    50, unindexed_message_ids, cancellable
+                );
                 // With multiple accounts, meaning multiple background threads
                 // doing such CPU- and disk-heavy work, this process can cause
                 // the main thread to slow to a crawl.  This delay means the
@@ -965,105 +1035,70 @@ private class Geary.ImapDB.Account : BaseObject {
                 yield Geary.Scheduler.sleep_ms_async(50);
             }
         } catch (Error e) {
-            debug("Error populating %s search table: %s", account_information.id, e.message);
+            debug("%s: Error populating search table: %s", account_information.id, e.message);
         }
 
         debug("%s: Done populating search table", account_information.id);
     }
 
-    private static Gee.HashSet<int64?> do_build_rowid_set(Db.Result result, Cancellable? cancellable)
-        throws Error {
-        Gee.HashSet<int64?> rowid_set = new Gee.HashSet<int64?>(Collection.int64_hash_func,
-            Collection.int64_equal_func);
-        while (!result.finished) {
-            rowid_set.add(result.rowid_at(0));
-            result.next(cancellable);
-        }
-
-        return rowid_set;
-    }
-
-    private async bool populate_search_table_batch_async(int limit, Cancellable? cancellable)
-        throws Error {
+    private async void populate_search_table_batch_async(
+        int limit,
+        Gee.HashSet<int64?> unindexed_message_ids,
+        GLib.Cancellable? cancellable
+    ) throws GLib.Error {
         check_open();
-        debug("%s: Searching for up to %d missing indexed messages...", account_information.id,
-            limit);
-
-        int count = 0, total_unindexed = 0;
-        yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => {
-            // Embedding a SELECT within a SELECT is painfully slow
-            // with SQLite, and a LEFT OUTER JOIN will still take in
-            // the order of seconds, so manually perform the operation
 
-            Db.Statement stmt = cx.prepare("""
-                SELECT docid FROM MessageSearchTable
-            """);
-            Gee.HashSet<int64?> search_ids = do_build_rowid_set(stmt.exec(cancellable), cancellable);
-
-            stmt = cx.prepare("""
-                SELECT id FROM MessageTable WHERE (fields & ?) = ?
-            """);
-            stmt.bind_uint(0, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
-            stmt.bind_uint(1, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
-            Gee.HashSet<int64?> message_ids = do_build_rowid_set(stmt.exec(cancellable), cancellable);
-
-            // This is hard to calculate correctly without doing a
-            // join (which we should above, but is currently too
-            // slow), and if we do get it wrong the progress monitor
-            // will crash and burn, so just something too big to fail
-            // for now. See Bug 776383.
-            total_unindexed = message_ids.size;
-
-            // chaff out any MessageTable entries not present in the MessageSearchTable ... since
-            // we're given a limit, stuff messages req'ing search into separate set and stop when limit
-            // reached
-            Gee.HashSet<int64?> unindexed_message_ids = new Gee.HashSet<int64?>(Collection.int64_hash_func,
-                Collection.int64_equal_func);
-            foreach (int64 message_id in message_ids) {
-                if (search_ids.contains(message_id))
-                    continue;
-
-                unindexed_message_ids.add(message_id);
-                if (unindexed_message_ids.size >= limit)
-                    break;
-            }
-
-            // For all remaining MessageTable rowid's, generate search table entry
-            foreach (int64 message_id in unindexed_message_ids) {
-                try {
-                    Geary.Email.Field search_fields = Geary.Email.REQUIRED_FOR_MESSAGE |
-                        Geary.Email.Field.ORIGINATORS | Geary.Email.Field.RECEIVERS |
-                        Geary.Email.Field.SUBJECT;
+        uint count = 0;
+        var iter = unindexed_message_ids.iterator();
+        yield this.db.exec_transaction_async(
+            RW,
+            (cx, cancellable) => {
+                while (iter.has_next() && count < limit) {
+                    iter.next();
+                    int64 message_id = iter.get();
+                    try {
+                        Email.Field search_fields = (
+                            Email.REQUIRED_FOR_MESSAGE |
+                            Email.Field.ORIGINATORS |
+                            Email.Field.RECEIVERS |
+                            Email.Field.SUBJECT
+                        );
 
-                    Geary.Email.Field db_fields;
-                    MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row(
-                        cx, message_id, search_fields, out db_fields, cancellable);
-                    Geary.Email email = row.to_email(new Geary.ImapDB.EmailIdentifier(message_id, null));
-                    Attachment.add_attachments(
-                        cx, this.db.attachments_path, email, message_id, cancellable
-                    );
+                        Email.Field db_fields;
+                        MessageRow row = Geary.ImapDB.Folder.do_fetch_message_row(
+                            cx, message_id, search_fields, out db_fields, cancellable
+                        );
+                        Email email = row.to_email(
+                            new Geary.ImapDB.EmailIdentifier(message_id, null)
+                        );
+                        Attachment.add_attachments(
+                            cx, this.db.attachments_path, email, message_id, cancellable
+                        );
+                        Geary.ImapDB.Folder.do_add_email_to_search_table(
+                            cx, message_id, email, cancellable
+                        );
+                    } catch (GLib.Error e) {
+                        // This is a somewhat serious issue since we rely on
+                        // there always being a row in the search table for
+                        // every message.
+                        warning(
+                            "Error populating message %s for indexing: %s",
+                            message_id.to_string(),
+                            e.message
+                        );
+                    }
 
-                    Geary.ImapDB.Folder.do_add_email_to_search_table(cx, message_id, email, cancellable);
-                } catch (Error e) {
-                    // This is a somewhat serious issue since we rely on
-                    // there always being a row in the search table for
-                    // every message.
-                    warning("Error adding message %s to the search table: %s", message_id.to_string(),
-                        e.message);
+                    iter.remove();
+                    ++count;
                 }
 
-                ++count;
-            }
-
-            return Db.TransactionOutcome.DONE;
+            return COMMIT;
         }, cancellable);
 
         if (count > 0) {
-            debug("%s: Found %d/%d missing indexed messages, %d remaining...",
-                account_information.id, count, limit, total_unindexed);
+            debug("%s: Populated %u missing indexed messages...",
+                account_information.id, count);
         }
-
-        return (count < limit);
     }
 
     //
diff --git a/src/engine/imap-db/imap-db-attachment.vala b/src/engine/imap-db/imap-db-attachment.vala
index d8e8f9dbc..fa94b6300 100644
--- a/src/engine/imap-db/imap-db-attachment.vala
+++ b/src/engine/imap-db/imap-db-attachment.vala
@@ -245,7 +245,7 @@ private class Geary.ImapDB.Attachment : Geary.Attachment {
         }
 
         // Ensure they're dead, Jim.
-        Db.Statement stmt = new Db.Statement(cx, """
+        Db.Statement stmt = cx.prepare("""
             DELETE FROM MessageAttachmentTable WHERE message_id = ?
         """);
         stmt.bind_rowid(0, message_id);
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index 4b015cd17..2c2d4015b 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -40,7 +40,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
     private const int LIST_EMAIL_METADATA_COUNT = 100;
     private const int LIST_EMAIL_FIELDS_CHUNK_COUNT = 500;
     private const int REMOVE_COMPLETE_LOCATIONS_CHUNK_COUNT = 500;
-    private const int CREATE_MERGE_EMAIL_CHUNK_COUNT = 25;
+    private const int CREATE_MERGE_EMAIL_CHUNK_COUNT = 10;
     private const int OLD_MSG_DETACH_BATCH_SIZE = 1000;
 
     // When old messages beyond the period set in the account preferences are removed this number 
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 2bd087b17..ef1ba7b4d 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -79,6 +79,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
         base(config, imap, smtp);
 
         this.local = local;
+        this.local.db.set_logging_parent(this);
+
         this.contact_store = new ContactStoreImpl(local.db);
 
         imap.min_pool_size = IMAP_MIN_POOL_SIZE;


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