← Back to team overview

zeitgeist team mailing list archive

[Branch ~zeitgeist/zeitgeist/bluebird] Rev 382: Merge lp:~mhr3/zeitgeist/refactoring

 

Merge authors:
  Michal Hruby (mhr3)
Related merge proposals:
  https://code.launchpad.net/~mhr3/zeitgeist/refactoring/+merge/91334
  proposed by: Michal Hruby (mhr3)
  review: Approve - Siegfried Gevatter (rainct)
------------------------------------------------------------
revno: 382 [merge]
committer: Michal Hruby <michal.mhr@xxxxxxxxx>
branch nick: zeitgeist
timestamp: Mon 2012-02-06 11:52:13 +0100
message:
  Merge lp:~mhr3/zeitgeist/refactoring
added:
  src/db-reader.vala
modified:
  extensions/fts.vala
  extensions/storage-monitor.vala
  src/Makefile.am
  src/datamodel.vala
  src/engine.vala
  src/extension-store.vala
  src/remote.vala
  src/sql-schema.vala
  src/sql.vala
  src/table-lookup.vala
  src/utils.vala
  src/zeitgeist-daemon.vala
  test/direct/Makefile.am
  test/direct/query-operators-test.vala
  test/direct/table-lookup-test.vala


--
lp:zeitgeist
https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird

Your team Zeitgeist Framework Team is subscribed to branch lp:zeitgeist.
To unsubscribe from this branch go to https://code.launchpad.net/~zeitgeist/zeitgeist/bluebird/+edit-subscription
=== modified file 'extensions/fts.vala'
--- extensions/fts.vala	2012-01-02 19:30:04 +0000
+++ extensions/fts.vala	2012-02-05 18:05:11 +0000
@@ -24,13 +24,13 @@
     [DBus (name = "org.gnome.zeitgeist.Index")]
     public interface RemoteSearchEngine: Object
     {
-        [DBus (signature = "a(asaasay)u")]
-        public abstract async Variant search (
+        public abstract async void search (
             string query_string,
             [DBus (signature = "(xx)")] Variant time_range,
             [DBus (signature = "a(asaasay)")] Variant filter_templates,
             uint offset, uint count, uint result_type,
-            [DBus (signature = "a(asaasay)")] out Variant events) throws Error;
+            [DBus (signature = "a(asaasay)")] out Variant events,
+            out uint matches) throws Error;
     }
 
     /* Because of a Vala bug we have to define the proxy interface outside of
@@ -39,12 +39,13 @@
     [DBus (name = "org.gnome.zeitgeist.SimpleIndexer")]
     public interface RemoteSimpleIndexer : Object
     {
-        [DBus (signature = "a(asaasay)u")]
-        public abstract async Variant search (
+        public abstract async void search (
             string query_string,
             [DBus (signature = "(xx)")] Variant time_range,
             [DBus (signature = "a(asaasay)")] Variant filter_templates,
-            uint offset, uint count, uint result_type) throws Error;
+            uint offset, uint count, uint result_type,
+            [DBus (signature = "a(asaasay)")] out Variant events,
+            out uint matches) throws Error;
     }
     */
 
@@ -103,11 +104,9 @@
             }
         }
 
-        /* This whole method is one huge workaround for an issue with Vala
-         * enclosing all out/return parameters in a TUPLE variant */
-        public async Variant search (string query_string, Variant time_range,
+        public async void search (string query_string, Variant time_range,
             Variant filter_templates, uint offset, uint count, uint result_type,
-            out Variant events) throws Error
+            out Variant events, out uint matches) throws Error
         {
             if (siin == null || !(siin is DBusProxy))
             {
@@ -116,26 +115,11 @@
                     "Not connected to SimpleIndexer");
             }
             var timer = new Timer ();
-            DBusProxy proxy = (DBusProxy) siin;
-            var b = new VariantBuilder (new VariantType ("(s(xx)a(asaasay)uuu)"));
-            b.add ("s", query_string);
-            b.add_value (time_range);
-            b.add_value (filter_templates);
-            b.add ("u", offset);
-            b.add ("u", count);
-            b.add ("u", result_type);
-            var result = yield proxy.call ("Search", b.end (), 0, -1, null);
-            events = result.get_child_value (0);
-            /* FIXME: this somehow doesn't work :(
-             *   but it's fixable in a similar way as this method's signature
-             *   is done */
-            /*
-            var result = yield siin.search (query_string, time_range,
-                filter_templates, offset, count, result_type);
-            */
-            debug ("Got %u results from indexer (in %f seconds)",
-                (uint) events.n_children (), timer.elapsed ());
-            return result.get_child_value (1);
+            yield siin.search (query_string, time_range, filter_templates,
+                               offset, count, result_type,
+                               out events, out matches);
+            debug ("Got %u[/%u] results from indexer (in %f seconds)",
+                (uint) events.n_children (), matches, timer.elapsed ());
         }
 
     }

=== modified file 'extensions/storage-monitor.vala'
--- extensions/storage-monitor.vala	2012-01-27 13:34:18 +0000
+++ extensions/storage-monitor.vala	2012-02-02 18:57:35 +0000
@@ -106,7 +106,7 @@
             "dav", "davs", "ftp", "http", "https", "mailto",
             "sftp", "smb", "ssh" };
 
-        private Zeitgeist.SQLite.ZeitgeistDatabase database;
+        private Zeitgeist.SQLite.Database database;
         private unowned Sqlite.Database db;
         private uint registration_id;
 

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2011-12-31 18:15:06 +0000
+++ src/Makefile.am	2012-02-02 18:57:35 +0000
@@ -31,6 +31,7 @@
 zeitgeist_daemon_VALASOURCES = \
 	zeitgeist-daemon.vala \
 	datamodel.vala \
+	db-reader.vala \
 	engine.vala \
 	remote.vala \
 	extension.vala \

=== modified file 'src/datamodel.vala'
--- src/datamodel.vala	2012-01-25 17:37:55 +0000
+++ src/datamodel.vala	2012-02-02 18:57:35 +0000
@@ -268,7 +268,7 @@
     {
         var matches = false;
         var parsed = template_property;
-        var is_negated = Engine.parse_negation (ref parsed);
+        var is_negated = Utils.parse_negation (ref parsed);
 
         if (parsed == "")
         {
@@ -283,7 +283,7 @@
         {
             matches = true;
         }
-        else if (can_wildcard && Engine.parse_wildcard (ref parsed))
+        else if (can_wildcard && Utils.parse_wildcard (ref parsed))
         {
             if (property.has_prefix (parsed)) matches = true;
         }

=== added file 'src/db-reader.vala'
--- src/db-reader.vala	1970-01-01 00:00:00 +0000
+++ src/db-reader.vala	2012-02-05 14:48:50 +0000
@@ -0,0 +1,894 @@
+/* db-reader.vala
+ *
+ * Copyright © 2011 Collabora Ltd.
+ *             By Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *             By Seif Lotfy <seif@xxxxxxxxx>
+ * Copyright © 2011 Canonical Ltd.
+ *             By Michal Hruby <michal.hruby@xxxxxxxxxxxxx>
+ *
+ * Based upon a Python implementation (2009-2011) by:
+ *  Markus Korn <thekorn@xxxxxxx>
+ *  Mikkel Kamstrup Erlandsen <mikkel.kamstrup@xxxxxxxxx>
+ *  Seif Lotfy <seif@xxxxxxxxx>
+ *  Siegfried-Angel Gevatter Pujals <siegfried@xxxxxxxxxxxx>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+using Zeitgeist;
+using Zeitgeist.SQLite;
+using Zeitgeist.Utils;
+
+namespace Zeitgeist
+{
+
+public class DbReader : Object
+{
+
+    public Zeitgeist.SQLite.Database database { get; construct; }
+    protected unowned Sqlite.Database db;
+
+    protected TableLookup interpretations_table;
+    protected TableLookup manifestations_table;
+    protected TableLookup mimetypes_table;
+    protected TableLookup actors_table;
+
+    public DbReader () throws EngineError
+    {
+        Object (database: new Zeitgeist.SQLite.Database.read_only ());
+    }
+
+    construct
+    {
+        database.set_deletion_callback (delete_from_cache);
+        db = database.database;
+
+        interpretations_table = new TableLookup (database, "interpretation");
+        manifestations_table = new TableLookup (database, "manifestation");
+        mimetypes_table = new TableLookup (database, "mimetype");
+        actors_table = new TableLookup (database, "actor");
+    }
+
+    protected Event get_event_from_row (Sqlite.Statement stmt, uint32 event_id)
+    {
+        Event event = new Event ();
+        event.id = event_id;
+        event.timestamp = stmt.column_int64 (EventViewRows.TIMESTAMP);
+        event.interpretation = interpretations_table.get_value (
+            stmt.column_int (EventViewRows.INTERPRETATION));
+        event.manifestation = manifestations_table.get_value (
+            stmt.column_int (EventViewRows.MANIFESTATION));
+        event.actor = actors_table.get_value (
+            stmt.column_int (EventViewRows.ACTOR));
+        event.origin = stmt.column_text (
+            EventViewRows.EVENT_ORIGIN_URI);
+
+        // Load payload
+        unowned uint8[] data = (uint8[]) stmt.column_blob(
+            EventViewRows.PAYLOAD);
+        data.length = stmt.column_bytes(EventViewRows.PAYLOAD);
+        if (data != null)
+        {
+            event.payload = new ByteArray();
+            event.payload.append(data);
+        }
+        return event;
+    }
+
+    protected Subject get_subject_from_row (Sqlite.Statement stmt)
+    {
+        Subject subject = new Subject ();
+        subject.uri = stmt.column_text (EventViewRows.SUBJECT_URI);
+        subject.text = stmt.column_text (EventViewRows.SUBJECT_TEXT);
+        subject.storage = stmt.column_text (EventViewRows.SUBJECT_STORAGE);
+        subject.origin = stmt.column_text (EventViewRows.SUBJECT_ORIGIN_URI);
+        subject.current_uri = stmt.column_text (
+            EventViewRows.SUBJECT_CURRENT_URI);
+        subject.interpretation = interpretations_table.get_value (
+            stmt.column_int (EventViewRows.SUBJECT_INTERPRETATION));
+        subject.manifestation = manifestations_table.get_value (
+            stmt.column_int (EventViewRows.SUBJECT_MANIFESTATION));
+        subject.mimetype = mimetypes_table.get_value (
+            stmt.column_int (EventViewRows.SUBJECT_MIMETYPE));
+        return subject;
+    }
+
+    public GenericArray<Event?> get_events(uint32[] event_ids,
+            BusName? sender=null) throws EngineError
+    {
+        // TODO: Consider if we still want the cache. This should be done
+        //  once everything is working, since it adds unneeded complexity.
+        //  It'd also benchmark it again first, we may have better options
+        //  to enhance the performance of SQLite now, and event processing
+        //  will be faster now being C.
+
+        if (event_ids.length == 0)
+            return new GenericArray<Event?> ();
+
+        var sql_event_ids = database.get_sql_string_from_event_ids (event_ids);
+        string sql = """
+            SELECT * FROM event_view
+            WHERE id IN (%s)
+            """.printf (sql_event_ids);
+
+        Sqlite.Statement stmt;
+        int rc = db.prepare_v2 (sql, -1, out stmt);
+        database.assert_query_success (rc, "SQL error");
+
+        var events = new HashTable<uint32, Event?> (direct_hash, direct_equal);
+
+        // Create Events and Subjects from rows
+        while ((rc = stmt.step ()) == Sqlite.ROW)
+        {
+            uint32 event_id = (uint32) stmt.column_int64 (EventViewRows.ID);
+            Event? event = events.lookup (event_id);
+            if (event == null)
+            {
+                event = get_event_from_row(stmt, event_id);
+                events.insert (event_id, event);
+            }
+            Subject subject = get_subject_from_row(stmt);
+            event.add_subject(subject);
+        }
+        if (rc != Sqlite.DONE)
+        {
+            throw new EngineError.DATABASE_ERROR ("Error: %d, %s\n",
+                rc, db.errmsg ());
+        }
+
+        // Sort events according to the sequence of event_ids
+        var results = new GenericArray<Event?> ();
+        results.length = event_ids.length;
+        int i = 0;
+        foreach (var id in event_ids)
+        {
+            results.set(i++, events.lookup (id));
+        }
+
+        return results;
+    }
+
+    public uint32[] find_event_ids (TimeRange time_range,
+        GenericArray<Event> event_templates,
+        uint storage_state, uint max_events, uint result_type,
+        BusName? sender=null) throws EngineError
+    {
+
+        WhereClause where = new WhereClause (WhereClause.Type.AND);
+
+        /**
+         * We are using the unary operator here to tell SQLite to not use
+         * the index on the timestamp column at the first place. This is a
+         * "fix" for (LP: #672965) based on some benchmarks, which suggest
+         * a performance win, but we might not oversee all implications.
+         * (See http://www.sqlite.org/optoverview.html, section 6.0).
+         *    -- Markus Korn, 29/11/2010
+         */
+        if (time_range.start != 0)
+            where.add (("+timestamp >= %" + int64.FORMAT).printf(
+                time_range.start));
+        if (time_range.end != 0)
+            where.add (("+timestamp <= %" + int64.FORMAT).printf(
+                time_range.end));
+
+        if (storage_state == StorageState.AVAILABLE ||
+            storage_state == StorageState.NOT_AVAILABLE)
+        {
+            where.add ("(subj_storage_state=? OR subj_storage_state IS NULL)",
+                storage_state.to_string ());
+        }
+        else if (storage_state != StorageState.ANY)
+        {
+            throw new EngineError.INVALID_ARGUMENT(
+                "Unknown storage state '%u'".printf(storage_state));
+        }
+
+        WhereClause tpl_conditions = get_where_clause_from_event_templates (
+            event_templates);
+        where.extend (tpl_conditions);
+        //if (!where.may_have_results ())
+        //    return new uint32[0];
+
+        string sql = "SELECT id FROM event_view ";
+        string where_sql = "";
+        if (!where.is_empty ())
+        {
+            where_sql = "WHERE " + where.get_sql_conditions ();
+        }
+
+        switch (result_type)
+        {
+            case ResultType.MOST_RECENT_EVENTS:
+                sql += where_sql + " ORDER BY timestamp DESC";
+                break;
+            case ResultType.LEAST_RECENT_EVENTS:
+                sql += where_sql + " ORDER BY timestamp ASC";
+                break;
+            case ResultType.MOST_RECENT_EVENT_ORIGIN:
+                sql += group_and_sort ("origin", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_EVENT_ORIGIN:
+                sql += group_and_sort ("origin", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_EVENT_ORIGIN:
+                sql += group_and_sort ("origin", where_sql, false, false);
+                break;
+            case ResultType.LEAST_POPULAR_EVENT_ORIGIN:
+                sql += group_and_sort ("origin", where_sql, true, true);
+                break;
+            case ResultType.MOST_RECENT_SUBJECTS:
+                sql += group_and_sort ("subj_id", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_SUBJECTS:
+                sql += group_and_sort ("subj_id", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_SUBJECTS:
+                sql += group_and_sort ("subj_id", where_sql, false, false);
+                break;
+            case ResultType.LEAST_POPULAR_SUBJECTS:
+                sql += group_and_sort ("subj_id", where_sql, true, true);
+                break;
+            case ResultType.MOST_RECENT_CURRENT_URI:
+                sql += group_and_sort ("subj_id_current", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_CURRENT_URI:
+                sql += group_and_sort ("subj_id_current", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_CURRENT_URI:
+                sql += group_and_sort ("subj_id_current", where_sql,
+                    false, false);
+                break;
+            case ResultType.LEAST_POPULAR_CURRENT_URI:
+                sql += group_and_sort ("subj_id_current", where_sql,
+                    true, true);
+                break;
+            case ResultType.MOST_RECENT_ACTOR:
+                sql += group_and_sort ("actor", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_ACTOR:
+                sql += group_and_sort ("actor", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_ACTOR:
+                sql += group_and_sort ("actor", where_sql, false, false);
+                break;
+            case ResultType.LEAST_POPULAR_ACTOR:
+                sql += group_and_sort ("actor", where_sql, true, true);
+                break;
+            case ResultType.OLDEST_ACTOR:
+                sql += group_and_sort ("actor", where_sql, true, null, "min");
+                break;
+            case ResultType.MOST_RECENT_ORIGIN:
+                sql += group_and_sort ("subj_origin", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_ORIGIN:
+                sql += group_and_sort ("subj_origin", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_ORIGIN:
+                sql += group_and_sort ("subj_origin", where_sql, false, false);
+                break;
+            case ResultType.LEAST_POPULAR_ORIGIN:
+                sql += group_and_sort ("subj_origin", where_sql, true, true);
+                break;
+            case ResultType.MOST_RECENT_SUBJECT_INTERPRETATION:
+                sql += group_and_sort ("subj_interpretation", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_SUBJECT_INTERPRETATION:
+                sql += group_and_sort ("subj_interpretation", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_SUBJECT_INTERPRETATION:
+                sql += group_and_sort ("subj_interpretation", where_sql,
+                    false, false);
+                break;
+            case ResultType.LEAST_POPULAR_SUBJECT_INTERPRETATION:
+                sql += group_and_sort ("subj_interpretation", where_sql,
+                    true, true);
+                break;
+            case ResultType.MOST_RECENT_MIMETYPE:
+                sql += group_and_sort ("subj_mimetype", where_sql, false);
+                break;
+            case ResultType.LEAST_RECENT_MIMETYPE:
+                sql += group_and_sort ("subj_mimetype", where_sql, true);
+                break;
+            case ResultType.MOST_POPULAR_MIMETYPE:
+                sql += group_and_sort ("subj_mimetype", where_sql,
+                    false, false);
+                break;
+            case ResultType.LEAST_POPULAR_MIMETYPE:
+                sql += group_and_sort ("subj_mimetype", where_sql,
+                    true, true);
+                break;
+            default:
+                string error_message = "Invalid ResultType.";
+                warning (error_message);
+                throw new EngineError.INVALID_ARGUMENT (error_message);
+        }
+
+        int rc;
+        Sqlite.Statement stmt;
+
+        rc = db.prepare_v2 (sql, -1, out stmt);
+        database.assert_query_success(rc, "SQL error");
+
+        var arguments = where.get_bind_arguments ();
+        for (int i = 0; i < arguments.length; ++i)
+            stmt.bind_text (i + 1, arguments[i]);
+
+#if EXPLAIN_QUERIES
+        database.explain_query (stmt);
+#endif
+
+        uint32[] event_ids = {};
+
+        while ((rc = stmt.step()) == Sqlite.ROW)
+        {
+            var event_id = (uint32) uint64.parse(
+                stmt.column_text (EventViewRows.ID));
+            // Events are supposed to be contiguous in the database
+            if (event_ids.length == 0 || event_ids[event_ids.length-1] != event_id) {
+                event_ids += event_id;
+                if (event_ids.length == max_events) break;
+            }
+        }
+        if (rc != Sqlite.DONE && rc != Sqlite.ROW)
+        {
+            string error_message = "Error in find_event_ids: %d, %s".printf (
+                rc, db.errmsg ());
+            warning (error_message);
+            throw new EngineError.DATABASE_ERROR (error_message);
+        }
+
+        return event_ids;
+    }
+
+    public GenericArray<Event?> find_events (TimeRange time_range,
+        GenericArray<Event> event_templates,
+        uint storage_state, uint max_events, uint result_type,
+        BusName? sender=null) throws EngineError
+    {
+        return get_events (find_event_ids (time_range, event_templates,
+            storage_state, max_events, result_type));
+    }
+
+    private struct RelatedUri {
+        public uint32 id;
+        public int64 timestamp;
+        public string uri;
+        public int32 counter;
+    }
+
+    public string[] find_related_uris (TimeRange time_range,
+        GenericArray<Event> event_templates,
+        GenericArray<Event> result_event_templates,
+        uint storage_state, uint max_results, uint result_type,
+        BusName? sender=null) throws EngineError
+    {
+        /**
+        * Return a list of subject URIs commonly used together with events
+        * matching the given template, considering data from within the
+        * indicated timerange.
+        * Only URIs for subjects matching the indicated `result_event_templates`
+        * and `result_storage_state` are returned.
+        */
+        if (result_type == ResultType.MOST_RECENT_EVENTS ||
+            result_type == ResultType.LEAST_RECENT_EVENTS)
+        {
+
+            // We pick out the ids for relational event so we can set them as
+            // roots the ids are taken from the events that match the
+            // events_templates
+            uint32[] ids = find_event_ids (time_range, event_templates,
+                storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
+
+            if (event_templates.length > 0 && ids.length == 0)
+            {
+                throw new EngineError.INVALID_ARGUMENT (
+                    "No results found for the event_templates");
+            }
+
+            // Pick out the result_ids for the filtered results we would like to
+            // take into account the ids are taken from the events that match
+            // the result_event_templates if no result_event_templates are set we
+            // consider all results as allowed
+            uint32[] result_ids;
+            result_ids = find_event_ids (time_range, result_event_templates,
+                storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
+
+            // From here we create several graphs with the maximum depth of 2
+            // and push all the nodes and vertices (events) in one pot together
+
+            uint32[] pot = new uint32[ids.length + result_ids.length];
+
+            for (uint32 i=0; i < ids.length; i++)
+                pot[i] = ids[i];
+            for (uint32 i=0; i < result_ids.length; i++)
+                pot[ids.length + i] = result_ids[ids.length + i];
+
+            Sqlite.Statement stmt;
+
+            var sql_event_ids = database.get_sql_string_from_event_ids (pot);
+            string sql = """
+               SELECT id, timestamp, subj_uri FROM event_view 
+               WHERE id IN (%s) ORDER BY timestamp ASC
+               """.printf (sql_event_ids);
+
+            int rc = db.prepare_v2 (sql, -1, out stmt);
+
+            database.assert_query_success(rc, "SQL error");
+
+            // FIXME: fix this ugly code
+            var temp_related_uris = new GenericArray<RelatedUri?>();
+
+            while ((rc = stmt.step()) == Sqlite.ROW)
+            {
+                RelatedUri ruri = RelatedUri(){
+                    id = (uint32) uint64.parse(stmt.column_text (0)),
+                    timestamp = stmt.column_int64 (1),
+                    uri = stmt.column_text (2),
+                    counter = 0
+                };
+                temp_related_uris.add (ruri);
+            }
+
+            // RelatedUri[] related_uris = new RelatedUri[temp_related_uris.length];
+            // for (int i=0; i<related_uris.length; i++)
+            //    related_uris[i] = temp_related_uris[i];
+
+            if (rc != Sqlite.DONE)
+            {
+                string error_message =
+                    "Error in find_related_uris: %d, %s".printf (
+                    rc, db.errmsg ());
+                warning (error_message);
+                throw new EngineError.DATABASE_ERROR (error_message);
+            }
+
+            var uri_counter = new HashTable<string, RelatedUri?>(
+                str_hash, str_equal);
+
+            for (int i = 0; i < temp_related_uris.length; i++)
+            {
+                var window = new GenericArray<unowned RelatedUri?>();
+
+                bool count_in_window = false;
+                for (int j = int.max (0, i - 5);
+                    j < int.min (i, temp_related_uris.length);
+                    j++)
+                {
+                    window.add(temp_related_uris[j]);
+                    if (temp_related_uris[j].id in ids)
+                        count_in_window = true;
+                }
+
+                if (count_in_window)
+                {
+                    for (int j = 0; j < window.length; j++)
+                    {
+                        if (uri_counter.lookup (window[j].uri) == null)
+                        {
+                            RelatedUri ruri = RelatedUri ()
+                            {
+                                id = window[j].id,
+                                timestamp = window[j].timestamp,
+                                uri = window[j].uri,
+                                counter = 0
+                            };
+                            uri_counter.insert (window[j].uri, ruri);
+                        }
+                        uri_counter.lookup (window[j].uri).counter++;
+                        if (uri_counter.lookup (window[j].uri).timestamp
+                                < window[j].timestamp)
+                        {
+                            uri_counter.lookup (window[j].uri).timestamp =
+                                window[j].timestamp;
+                        }
+                    }
+                }
+            }
+
+
+            // We have the big hashtable with the structs, now we sort them by
+            // most used and limit the result then sort again
+            List<RelatedUri?> temp_ruris = new  List<RelatedUri?>();
+            List<RelatedUri?> values = new List<RelatedUri?>();
+
+            foreach (var uri in uri_counter.get_values())
+                values.append(uri);
+
+            values.sort ((a, b) => a.counter - b.counter);
+            values.sort ((a, b) => {
+                    int64 delta = a.timestamp - b.timestamp;
+                    if (delta < 0) return 1;
+                    else if (delta > 0) return -1;
+                    else return 0;
+                });
+
+            foreach (RelatedUri ruri in values)
+            {
+                if (temp_ruris.length() < max_results)
+                    temp_ruris.append(ruri);
+                else
+                    break;
+            }
+
+            // Sort by recency
+            if (result_type == 1)
+                temp_ruris.sort ((a, b) => {
+                    int64 delta = a.timestamp - b.timestamp;
+                    if (delta < 0) return 1;
+                    else if (delta > 0) return -1;
+                    else return 0;});
+
+            string[] results = new string[temp_ruris.length()];
+
+            int i = 0;
+            foreach (var uri in temp_ruris)
+            {
+                results[i] = uri.uri;
+                stdout.printf("%i %lld %s\n", uri.counter,
+                    uri.timestamp,
+                    uri.uri);
+                i++;
+            }
+
+            return results;
+        }
+        else
+        {
+            throw new EngineError.DATABASE_ERROR ("Unsupported ResultType.");
+        }
+    }
+
+    /**
+     * Clear all resources Engine is using (close database connection, etc.).
+     *
+     * After executing this method on an instance, no other function
+     * may be called.
+     */
+    public virtual void close ()
+    {
+        database.close ();
+    }
+
+    // Used by find_event_ids
+    private string group_and_sort (string field, string where_sql,
+        bool time_asc=false, bool? count_asc=null,
+        string aggregation_type="max")
+    {
+        string time_sorting = (time_asc) ? "ASC" : "DESC";
+        string aggregation_sql = "";
+        string order_sql = "";
+
+        if (count_asc != null)
+        {
+            aggregation_sql = ", COUNT(%s) AS num_events".printf (field);
+            order_sql = "num_events %s,".printf ((count_asc) ? "ASC" : "DESC");
+        }
+
+        return """
+            NATURAL JOIN (
+                SELECT %s,
+                %s(timestamp) AS timestamp
+                %s
+                FROM event_view %s
+                GROUP BY %s)
+            GROUP BY %s
+            ORDER BY %s timestamp %s
+            """.printf (
+                field,
+                aggregation_type,
+                aggregation_sql,
+                where_sql,
+                field,
+                field,
+                order_sql, time_sorting);
+    }
+
+    // Used by find_event_ids
+    protected WhereClause get_where_clause_from_event_templates (
+        GenericArray<Event> templates) throws EngineError
+    {
+        WhereClause where = new WhereClause (WhereClause.Type.OR);
+        for (int i = 0; i < templates.length; ++i)
+        {
+            Event event_template = templates[i];
+            where.extend (
+                get_where_clause_from_event_template (event_template));
+        }
+        return where;
+    }
+
+    // Used by get_where_clause_from_event_templates
+    private WhereClause get_where_clause_from_event_template (Event template)
+        throws EngineError
+    {
+            WhereClause where = new WhereClause (WhereClause.Type.AND);
+
+            // Event ID
+            if (template.id != 0)
+                where.add ("id=?", template.id.to_string());
+
+            // Interpretation
+            if (!is_empty_string (template.interpretation))
+            {
+                assert_no_wildcard ("interpretation", template.interpretation);
+                WhereClause subwhere = get_where_clause_for_symbol (
+                    "interpretation", template.interpretation,
+                    interpretations_table);
+                if (!subwhere.is_empty ())
+                    where.extend (subwhere);
+            }
+
+            // Manifestation
+            if (!is_empty_string (template.manifestation))
+            {
+                assert_no_wildcard ("manifestation", template.interpretation);
+                WhereClause subwhere = get_where_clause_for_symbol (
+                    "manifestation", template.manifestation,
+                    manifestations_table);
+                if (!subwhere.is_empty ())
+                   where.extend (subwhere);
+            }
+
+            // Actor
+            if (!is_empty_string (template.actor))
+            {
+                string val = template.actor;
+                bool like = parse_wildcard (ref val);
+                bool negated = parse_negation (ref val);
+
+                if (like)
+                    where.add_wildcard_condition ("actor", val, negated);
+                else
+                    where.add_match_condition ("actor",
+                        actors_table.get_id (val), negated);
+            }
+
+            // Origin
+            if (!is_empty_string (template.origin))
+            {
+                string val = template.origin;
+                bool like = parse_wildcard (ref val);
+                bool negated = parse_negation (ref val);
+                assert_no_noexpand (val, "origin");
+
+                if (like)
+                    where.add_wildcard_condition ("origin", val, negated);
+                else
+                    where.add_text_condition_subquery ("origin", val, negated);
+            }
+
+            // Subject templates within the same event template are AND'd
+            // See LP bug #592599.
+            for (int i = 0; i < template.num_subjects(); ++i)
+            {
+                Subject subject_template = template.subjects[i];
+
+                // Subject interpretation
+                if (!is_empty_string (subject_template.interpretation))
+                {
+                    assert_no_wildcard ("subject interpretation",
+                        template.interpretation);
+                    WhereClause subwhere = get_where_clause_for_symbol (
+                        "subj_interpretation", subject_template.interpretation,
+                        interpretations_table);
+                    if (!subwhere.is_empty ())
+                        where.extend (subwhere);
+                }
+
+                // Subject manifestation
+                if (!is_empty_string (subject_template.manifestation))
+                {
+                    assert_no_wildcard ("subject manifestation",
+                        subject_template.manifestation);
+                    WhereClause subwhere = get_where_clause_for_symbol (
+                        "subj_manifestation", subject_template.manifestation,
+                        manifestations_table);
+                    if (!subwhere.is_empty ())
+                        where.extend (subwhere);
+                }
+
+                // Mime-Type
+                if (!is_empty_string (subject_template.mimetype))
+                {
+                    string val = subject_template.mimetype;
+                    bool like = parse_wildcard (ref val);
+                    bool negated = parse_negation (ref val);
+                    assert_no_noexpand (val, "mime-type");
+
+                    if (like)
+                        where.add_wildcard_condition (
+                            "subj_mimetype", val, negated);
+                    else
+                        where.add_match_condition ("subj_mimetype",
+                            mimetypes_table.get_id (val), negated);
+                }
+
+                // URI
+                if (!is_empty_string (subject_template.uri))
+                {
+                    string val = subject_template.uri;
+                    bool like = parse_wildcard (ref val);
+                    bool negated = parse_negation (ref val);
+                    assert_no_noexpand (val, "uri");
+
+                    if (like)
+                        where.add_wildcard_condition ("subj_id", val, negated);
+                    else
+                        where.add_text_condition_subquery ("subj_id", val, negated);
+                }
+
+                // Origin
+                if (!is_empty_string (subject_template.origin))
+                {
+                    string val = subject_template.origin;
+                    bool like = parse_wildcard (ref val);
+                    bool negated = parse_negation (ref val);
+                    assert_no_noexpand (val, "subject origin");
+
+                    if (like)
+                        where.add_wildcard_condition (
+                            "subj_origin", val, negated);
+                    else
+                        where.add_text_condition_subquery (
+                            "subj_origin", val, negated);
+                }
+
+                // Text
+                if (!is_empty_string (subject_template.text))
+                {
+                    // Negation, noexpand and prefix search aren't supported
+                    // for subject texts, but "!", "+" and "*" are valid as
+                    // plain text characters.
+                    where.add_text_condition_subquery ("subj_text_id",
+                        subject_template.text, false);
+                }
+
+                // Current URI
+                if (!is_empty_string (subject_template.current_uri))
+                {
+                    string val = subject_template.current_uri;
+                    bool like = parse_wildcard (ref val);
+                    bool negated = parse_negation (ref val);
+                    assert_no_noexpand (val, "current_uri");
+
+                    if (like)
+                        where.add_wildcard_condition (
+                            "subj_id_current", val, negated);
+                    else
+                        where.add_text_condition_subquery (
+                            "subj_id_current", val, negated);
+                }
+
+                // Subject storage
+                if (!is_empty_string (subject_template.storage))
+                {
+                    string val = subject_template.storage;
+                    assert_no_negation ("subject storage", val);
+                    assert_no_wildcard ("subject storage", val);
+                    assert_no_noexpand (val, "subject storage");
+                    where.add_text_condition_subquery ("subj_storage_id", val);
+                }
+            }
+
+            return where;
+    }
+
+    // Used by get_where_clause_from_event_templates
+    /**
+     * If the value starts with the negation operator, throw an
+     * error.
+     */
+    protected void assert_no_negation (string field, string val)
+        throws EngineError
+    {
+        if (!val.has_prefix ("!"))
+            return;
+        string error_message =
+            "Field '%s' doesn't support negation".printf (field);
+        warning (error_message);
+        throw new EngineError.INVALID_ARGUMENT (error_message);
+    }
+
+    // Used by get_where_clause_from_event_templates
+    /**
+     * If the value starts with the negation operator, throw an
+     * error.
+     */
+    protected void assert_no_noexpand (string field, string val)
+        throws EngineError
+    {
+        if (!val.has_prefix ("+"))
+            return;
+        string error_message =
+            "Field '%s' doesn't support the no-expand operator".printf (field);
+        warning (error_message);
+        throw new EngineError.INVALID_ARGUMENT (error_message);
+    }
+
+    // Used by get_where_clause_from_event_templates
+    /**
+     * If the value ends with the wildcard character, throw an error.
+     */
+    protected void assert_no_wildcard (string field, string val)
+        throws EngineError
+    {
+        if (!val.has_suffix ("*"))
+            return;
+        string error_message =
+            "Field '%s' doesn't support prefix search".printf (field);
+        warning (error_message);
+        throw new EngineError.INVALID_ARGUMENT (error_message);
+    }
+
+    protected WhereClause get_where_clause_for_symbol (string table_name,
+        string symbol, TableLookup lookup_table) throws EngineError
+    {
+        string _symbol = symbol;
+        bool negated = parse_negation (ref _symbol);
+        bool noexpand = parse_noexpand (ref _symbol);
+        List<unowned string> symbols;
+        if (noexpand)
+            symbols = new List<unowned string> ();
+        else
+            symbols = Symbol.get_all_children (_symbol);
+        symbols.prepend (_symbol);
+
+        WhereClause subwhere = new WhereClause(
+            WhereClause.Type.OR, negated);
+
+        if (symbols.length () == 1)
+        {
+            subwhere.add_match_condition (table_name,
+                lookup_table.get_id (_symbol));
+        }
+        else
+        {
+            var sb = new StringBuilder ();
+            foreach (unowned string uri in symbols)
+            {
+                sb.append_printf ("%d,", lookup_table.get_id (uri));
+            }
+            sb.truncate (sb.len - 1);
+
+            string sql = "%s IN (%s)".printf(table_name, sb.str);
+            subwhere.add(sql);
+        }
+
+        return subwhere;
+    }
+
+    private void delete_from_cache (string table, int64 rowid)
+    {
+        TableLookup table_lookup;
+
+        if (table == "interpretation")
+            table_lookup = interpretations_table;
+        else if (table == "manifestation")
+            table_lookup = manifestations_table;
+        else if (table == "mimetype")
+            table_lookup = mimetypes_table;
+        else if (table == "actor")
+            table_lookup = actors_table;
+        else
+            return;
+
+        table_lookup.remove((int) rowid);
+    }
+
+}
+
+}
+
+// vim:expandtab:ts=4:sw=4

=== modified file 'src/engine.vala'
--- src/engine.vala	2012-01-25 17:37:55 +0000
+++ src/engine.vala	2012-02-02 20:58:01 +0000
@@ -29,37 +29,28 @@
 using Zeitgeist.SQLite;
 
 namespace Zeitgeist
-{ // FIXME: increase indentation once we're ok with breaking 'bzr diff'
-
-public class Engine : Object
-{
-
-    public Zeitgeist.SQLite.ZeitgeistDatabase database { get; private set; }
+{
+
+public class Engine : DbReader
+{
+
     public ExtensionStore extension_store;
     private ExtensionCollection extension_collection;
-    private unowned Sqlite.Database db;
-
-    protected TableLookup interpretations_table;
-    protected TableLookup manifestations_table;
-    protected TableLookup mimetypes_table;
-    protected TableLookup actors_table;
 
     private uint32 last_id;
 
     public Engine () throws EngineError
     {
-        database = new Zeitgeist.SQLite.ZeitgeistDatabase ();
-        database.set_deletion_callback (delete_from_cache);
-        db = database.database;
+        Object (database: new Zeitgeist.SQLite.Database ());
+
+        // TODO: take care of this if we decide to subclass Engine
         last_id = database.get_last_id ();
-
-        interpretations_table = new TableLookup (database, "interpretation");
-        manifestations_table = new TableLookup (database, "manifestation");
-        mimetypes_table = new TableLookup (database, "mimetype");
-        actors_table = new TableLookup (database, "actor");
-
+        extension_collection = new ExtensionCollection (this);
+    }
+
+    construct
+    {
         extension_store = new ExtensionStore (this);
-        extension_collection = new ExtensionCollection (this);
     }
 
     public string[] get_extension_names ()
@@ -67,495 +58,6 @@
         return extension_collection.get_extension_names ();
     }
 
-    private Event get_event_from_row (Sqlite.Statement stmt, uint32 event_id)
-    {
-        Event event = new Event ();
-        event.id = event_id;
-        event.timestamp = stmt.column_int64 (EventViewRows.TIMESTAMP);
-        event.interpretation = interpretations_table.get_value (
-            stmt.column_int (EventViewRows.INTERPRETATION));
-        event.manifestation = manifestations_table.get_value (
-            stmt.column_int (EventViewRows.MANIFESTATION));
-        event.actor = actors_table.get_value (
-            stmt.column_int (EventViewRows.ACTOR));
-        event.origin = stmt.column_text (
-            EventViewRows.EVENT_ORIGIN_URI);
-
-        // Load payload
-        unowned uint8[] data = (uint8[]) stmt.column_blob(
-            EventViewRows.PAYLOAD);
-        data.length = stmt.column_bytes(EventViewRows.PAYLOAD);
-        if (data != null)
-        {
-            event.payload = new ByteArray();
-            event.payload.append(data);
-        }
-        return event;
-    }
-
-    private Subject get_subject_from_row (Sqlite.Statement stmt)
-    {
-        Subject subject = new Subject ();
-        subject.uri = stmt.column_text (EventViewRows.SUBJECT_URI);
-        subject.text = stmt.column_text (EventViewRows.SUBJECT_TEXT);
-        subject.storage = stmt.column_text (EventViewRows.SUBJECT_STORAGE);
-        subject.origin = stmt.column_text (EventViewRows.SUBJECT_ORIGIN_URI);
-        subject.current_uri = stmt.column_text (
-            EventViewRows.SUBJECT_CURRENT_URI);
-        subject.interpretation = interpretations_table.get_value (
-            stmt.column_int (EventViewRows.SUBJECT_INTERPRETATION));
-        subject.manifestation = manifestations_table.get_value (
-            stmt.column_int (EventViewRows.SUBJECT_MANIFESTATION));
-        subject.mimetype = mimetypes_table.get_value (
-            stmt.column_int (EventViewRows.SUBJECT_MIMETYPE));
-        return subject;
-    }
-
-    public GenericArray<Event?> get_events(uint32[] event_ids,
-            BusName? sender=null) throws EngineError
-    {
-        // TODO: Consider if we still want the cache. This should be done
-        //  once everything is working, since it adds unneeded complexity.
-        //  It'd also benchmark it again first, we may have better options
-        //  to enhance the performance of SQLite now, and event processing
-        //  will be faster now being C.
-
-        if (event_ids.length == 0)
-            return new GenericArray<Event?> ();
-
-        var sql_event_ids = database.get_sql_string_from_event_ids (event_ids);
-        string sql = """
-            SELECT * FROM event_view
-            WHERE id IN (%s)
-            """.printf (sql_event_ids);
-
-        Sqlite.Statement stmt;
-        int rc = db.prepare_v2 (sql, -1, out stmt);
-        database.assert_query_success (rc, "SQL error");
-
-        var events = new HashTable<uint32, Event?> (direct_hash, direct_equal);
-
-        // Create Events and Subjects from rows
-        while ((rc = stmt.step ()) == Sqlite.ROW)
-        {
-            uint32 event_id = (uint32) stmt.column_int64 (EventViewRows.ID);
-            Event? event = events.lookup (event_id);
-            if (event == null)
-            {
-                event = get_event_from_row(stmt, event_id);
-                events.insert (event_id, event);
-            }
-            Subject subject = get_subject_from_row(stmt);
-            event.add_subject(subject);
-        }
-        if (rc != Sqlite.DONE)
-        {
-            throw new EngineError.DATABASE_ERROR ("Error: %d, %s\n",
-                rc, db.errmsg ());
-        }
-
-        // Sort events according to the sequence of event_ids
-        var results = new GenericArray<Event?> ();
-        results.length = event_ids.length;
-        int i = 0;
-        foreach (var id in event_ids)
-        {
-            results.set(i++, events.lookup (id));
-        }
-
-        return results;
-    }
-
-    public uint32[] find_event_ids (TimeRange time_range,
-        GenericArray<Event> event_templates,
-        uint storage_state, uint max_events, uint result_type,
-        BusName? sender=null) throws EngineError
-    {
-
-        WhereClause where = new WhereClause (WhereClause.Type.AND);
-
-        /**
-         * We are using the unary operator here to tell SQLite to not use
-         * the index on the timestamp column at the first place. This is a
-         * "fix" for (LP: #672965) based on some benchmarks, which suggest
-         * a performance win, but we might not oversee all implications.
-         * (See http://www.sqlite.org/optoverview.html, section 6.0).
-         *    -- Markus Korn, 29/11/2010
-         */
-        if (time_range.start != 0)
-            where.add (("+timestamp >= %" + int64.FORMAT).printf(
-                time_range.start));
-        if (time_range.end != 0)
-            where.add (("+timestamp <= %" + int64.FORMAT).printf(
-                time_range.end));
-
-        if (storage_state == StorageState.AVAILABLE ||
-            storage_state == StorageState.NOT_AVAILABLE)
-        {
-            where.add ("(subj_storage_state=? OR subj_storage_state IS NULL)",
-                storage_state.to_string ());
-        }
-        else if (storage_state != StorageState.ANY)
-        {
-            throw new EngineError.INVALID_ARGUMENT(
-                "Unknown storage state '%u'".printf(storage_state));
-        }
-
-        WhereClause tpl_conditions = get_where_clause_from_event_templates (
-            event_templates);
-        where.extend (tpl_conditions);
-        //if (!where.may_have_results ())
-        //    return new uint32[0];
-
-        string sql = "SELECT id FROM event_view ";
-        string where_sql = "";
-        if (!where.is_empty ())
-        {
-            where_sql = "WHERE " + where.get_sql_conditions ();
-        }
-
-        switch (result_type)
-        {
-            case ResultType.MOST_RECENT_EVENTS:
-                sql += where_sql + " ORDER BY timestamp DESC";
-                break;
-            case ResultType.LEAST_RECENT_EVENTS:
-                sql += where_sql + " ORDER BY timestamp ASC";
-                break;
-            case ResultType.MOST_RECENT_EVENT_ORIGIN:
-                sql += group_and_sort ("origin", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_EVENT_ORIGIN:
-                sql += group_and_sort ("origin", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_EVENT_ORIGIN:
-                sql += group_and_sort ("origin", where_sql, false, false);
-                break;
-            case ResultType.LEAST_POPULAR_EVENT_ORIGIN:
-                sql += group_and_sort ("origin", where_sql, true, true);
-                break;
-            case ResultType.MOST_RECENT_SUBJECTS:
-                sql += group_and_sort ("subj_id", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_SUBJECTS:
-                sql += group_and_sort ("subj_id", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_SUBJECTS:
-                sql += group_and_sort ("subj_id", where_sql, false, false);
-                break;
-            case ResultType.LEAST_POPULAR_SUBJECTS:
-                sql += group_and_sort ("subj_id", where_sql, true, true);
-                break;
-            case ResultType.MOST_RECENT_CURRENT_URI:
-                sql += group_and_sort ("subj_id_current", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_CURRENT_URI:
-                sql += group_and_sort ("subj_id_current", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_CURRENT_URI:
-                sql += group_and_sort ("subj_id_current", where_sql,
-                    false, false);
-                break;
-            case ResultType.LEAST_POPULAR_CURRENT_URI:
-                sql += group_and_sort ("subj_id_current", where_sql,
-                    true, true);
-                break;
-            case ResultType.MOST_RECENT_ACTOR:
-                sql += group_and_sort ("actor", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_ACTOR:
-                sql += group_and_sort ("actor", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_ACTOR:
-                sql += group_and_sort ("actor", where_sql, false, false);
-                break;
-            case ResultType.LEAST_POPULAR_ACTOR:
-                sql += group_and_sort ("actor", where_sql, true, true);
-                break;
-            case ResultType.OLDEST_ACTOR:
-                sql += group_and_sort ("actor", where_sql, true, null, "min");
-                break;
-            case ResultType.MOST_RECENT_ORIGIN:
-                sql += group_and_sort ("subj_origin", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_ORIGIN:
-                sql += group_and_sort ("subj_origin", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_ORIGIN:
-                sql += group_and_sort ("subj_origin", where_sql, false, false);
-                break;
-            case ResultType.LEAST_POPULAR_ORIGIN:
-                sql += group_and_sort ("subj_origin", where_sql, true, true);
-                break;
-            case ResultType.MOST_RECENT_SUBJECT_INTERPRETATION:
-                sql += group_and_sort ("subj_interpretation", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_SUBJECT_INTERPRETATION:
-                sql += group_and_sort ("subj_interpretation", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_SUBJECT_INTERPRETATION:
-                sql += group_and_sort ("subj_interpretation", where_sql,
-                    false, false);
-                break;
-            case ResultType.LEAST_POPULAR_SUBJECT_INTERPRETATION:
-                sql += group_and_sort ("subj_interpretation", where_sql,
-                    true, true);
-                break;
-            case ResultType.MOST_RECENT_MIMETYPE:
-                sql += group_and_sort ("subj_mimetype", where_sql, false);
-                break;
-            case ResultType.LEAST_RECENT_MIMETYPE:
-                sql += group_and_sort ("subj_mimetype", where_sql, true);
-                break;
-            case ResultType.MOST_POPULAR_MIMETYPE:
-                sql += group_and_sort ("subj_mimetype", where_sql,
-                    false, false);
-                break;
-            case ResultType.LEAST_POPULAR_MIMETYPE:
-                sql += group_and_sort ("subj_mimetype", where_sql,
-                    true, true);
-                break;
-            default:
-                string error_message = "Invalid ResultType.";
-                warning (error_message);
-                throw new EngineError.INVALID_ARGUMENT (error_message);
-        }
-
-        int rc;
-        Sqlite.Statement stmt;
-
-        rc = db.prepare_v2 (sql, -1, out stmt);
-        database.assert_query_success(rc, "SQL error");
-
-        var arguments = where.get_bind_arguments ();
-        for (int i = 0; i < arguments.length; ++i)
-            stmt.bind_text (i + 1, arguments[i]);
-
-#if EXPLAIN_QUERIES
-        database.explain_query (stmt);
-#endif
-
-        uint32[] event_ids = {};
-
-        while ((rc = stmt.step()) == Sqlite.ROW)
-        {
-            var event_id = (uint32) uint64.parse(
-                stmt.column_text (EventViewRows.ID));
-            // Events are supposed to be contiguous in the database
-            if (event_ids.length == 0 || event_ids[event_ids.length-1] != event_id) {
-                event_ids += event_id;
-                if (event_ids.length == max_events) break;
-            }
-        }
-        if (rc != Sqlite.DONE && rc != Sqlite.ROW)
-        {
-            string error_message = "Error in find_event_ids: %d, %s".printf (
-                rc, db.errmsg ());
-            warning (error_message);
-            throw new EngineError.DATABASE_ERROR (error_message);
-        }
-
-        return event_ids;
-    }
-
-    public GenericArray<Event?> find_events (TimeRange time_range,
-        GenericArray<Event> event_templates,
-        uint storage_state, uint max_events, uint result_type,
-        BusName? sender=null) throws EngineError
-    {
-        return get_events (find_event_ids (time_range, event_templates,
-            storage_state, max_events, result_type));
-    }
-
-    private struct RelatedUri {
-        public uint32 id;
-        public int64 timestamp;
-        public string uri;
-        public int32 counter;
-    }
-
-    public string[] find_related_uris (TimeRange time_range,
-        GenericArray<Event> event_templates,
-        GenericArray<Event> result_event_templates,
-        uint storage_state, uint max_results, uint result_type,
-        BusName? sender=null) throws EngineError
-    {
-        /**
-        * Return a list of subject URIs commonly used together with events
-        * matching the given template, considering data from within the
-        * indicated timerange.
-        * Only URIs for subjects matching the indicated `result_event_templates`
-        * and `result_storage_state` are returned.
-        */
-        if (result_type == ResultType.MOST_RECENT_EVENTS ||
-            result_type == ResultType.LEAST_RECENT_EVENTS)
-        {
-
-            // We pick out the ids for relational event so we can set them as
-            // roots the ids are taken from the events that match the
-            // events_templates
-            uint32[] ids = find_event_ids (time_range, event_templates,
-                storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
-
-            if (event_templates.length > 0 && ids.length == 0)
-            {
-                throw new EngineError.INVALID_ARGUMENT (
-                    "No results found for the event_templates");
-            }
-
-            // Pick out the result_ids for the filtered results we would like to
-            // take into account the ids are taken from the events that match
-            // the result_event_templates if no result_event_templates are set we
-            // consider all results as allowed
-            uint32[] result_ids;
-            result_ids = find_event_ids (time_range, result_event_templates,
-                storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
-
-            // From here we create several graphs with the maximum depth of 2
-            // and push all the nodes and vertices (events) in one pot together
-
-            uint32[] pot = new uint32[ids.length + result_ids.length];
-
-            for (uint32 i=0; i < ids.length; i++)
-                pot[i] = ids[i];
-            for (uint32 i=0; i < result_ids.length; i++)
-                pot[ids.length + i] = result_ids[ids.length + i];
-
-            Sqlite.Statement stmt;
-
-            var sql_event_ids = database.get_sql_string_from_event_ids (pot);
-            string sql = """
-               SELECT id, timestamp, subj_uri FROM event_view 
-               WHERE id IN (%s) ORDER BY timestamp ASC
-               """.printf (sql_event_ids);
-
-            int rc = db.prepare_v2 (sql, -1, out stmt);
-
-            database.assert_query_success(rc, "SQL error");
-
-            // FIXME: fix this ugly code
-            var temp_related_uris = new GenericArray<RelatedUri?>();
-
-            while ((rc = stmt.step()) == Sqlite.ROW)
-            {
-                RelatedUri ruri = RelatedUri(){
-                    id = (uint32) uint64.parse(stmt.column_text (0)),
-                    timestamp = stmt.column_int64 (1),
-                    uri = stmt.column_text (2),
-                    counter = 0
-                };
-                temp_related_uris.add (ruri);
-            }
-
-            // RelatedUri[] related_uris = new RelatedUri[temp_related_uris.length];
-            // for (int i=0; i<related_uris.length; i++)
-            //    related_uris[i] = temp_related_uris[i];
-
-            if (rc != Sqlite.DONE)
-            {
-                string error_message =
-                    "Error in find_related_uris: %d, %s".printf (
-                    rc, db.errmsg ());
-                warning (error_message);
-                throw new EngineError.DATABASE_ERROR (error_message);
-            }
-
-            var uri_counter = new HashTable<string, RelatedUri?>(
-                str_hash, str_equal);
-
-            for (int i = 0; i < temp_related_uris.length; i++)
-            {
-                var window = new GenericArray<unowned RelatedUri?>();
-
-                bool count_in_window = false;
-                for (int j = int.max (0, i - 5);
-                    j < int.min (i, temp_related_uris.length);
-                    j++)
-                {
-                    window.add(temp_related_uris[j]);
-                    if (temp_related_uris[j].id in ids)
-                        count_in_window = true;
-                }
-
-                if (count_in_window)
-                {
-                    for (int j = 0; j < window.length; j++)
-                    {
-                        if (uri_counter.lookup (window[j].uri) == null)
-                        {
-                            RelatedUri ruri = RelatedUri ()
-                            {
-                                id = window[j].id,
-                                timestamp = window[j].timestamp,
-                                uri = window[j].uri,
-                                counter = 0
-                            };
-                            uri_counter.insert (window[j].uri, ruri);
-                        }
-                        uri_counter.lookup (window[j].uri).counter++;
-                        if (uri_counter.lookup (window[j].uri).timestamp
-                                < window[j].timestamp)
-                        {
-                            uri_counter.lookup (window[j].uri).timestamp =
-                                window[j].timestamp;
-                        }
-                    }
-                }
-            }
-
-
-            // We have the big hashtable with the structs, now we sort them by
-            // most used and limit the result then sort again
-            List<RelatedUri?> temp_ruris = new  List<RelatedUri?>();
-            List<RelatedUri?> values = new List<RelatedUri?>();
-
-            foreach (var uri in uri_counter.get_values())
-                values.append(uri);
-
-            values.sort ((a, b) => a.counter - b.counter);
-            values.sort ((a, b) => {
-                    int64 delta = a.timestamp - b.timestamp;
-                    if (delta < 0) return 1;
-                    else if (delta > 0) return -1;
-                    else return 0;
-                });
-
-            foreach (RelatedUri ruri in values)
-            {
-                if (temp_ruris.length() < max_results)
-                    temp_ruris.append(ruri);
-                else
-                    break;
-            }
-
-            // Sort by recency
-            if (result_type == 1)
-                temp_ruris.sort ((a, b) => {
-                    int64 delta = a.timestamp - b.timestamp;
-                    if (delta < 0) return 1;
-                    else if (delta > 0) return -1;
-                    else return 0;});
-
-            string[] results = new string[temp_ruris.length()];
-
-            int i = 0;
-            foreach (var uri in temp_ruris)
-            {
-                results[i] = uri.uri;
-                stdout.printf("%i %lld %s\n", uri.counter,
-                    uri.timestamp,
-                    uri.uri);
-                i++;
-            }
-
-            return results;
-        }
-        else
-        {
-            throw new EngineError.DATABASE_ERROR ("Unsupported ResultType.");
-        }
-    }
-
     public uint32[] insert_events (GenericArray<Event> events,
         BusName? sender=null) throws EngineError
     {
@@ -786,366 +288,14 @@
      * After executing this method on an Engine instance, no other function
      * of said instance may be called.
      */
-    public void close ()
+    public override void close ()
     {
         // We delete the ExtensionCollection here so that it unloads
         // all extensions and they get a chance to access the database
         // (including through ExtensionStore) before it's closed.
         extension_collection = null;
-        database.close ();
-    }
-
-    // Used by find_event_ids
-    private string group_and_sort (string field, string where_sql,
-        bool time_asc=false, bool? count_asc=null,
-        string aggregation_type="max")
-    {
-        string time_sorting = (time_asc) ? "ASC" : "DESC";
-        string aggregation_sql = "";
-        string order_sql = "";
-
-        if (count_asc != null)
-        {
-            aggregation_sql = ", COUNT(%s) AS num_events".printf (field);
-            order_sql = "num_events %s,".printf ((count_asc) ? "ASC" : "DESC");
-        }
-
-        return """
-            NATURAL JOIN (
-                SELECT %s,
-                %s(timestamp) AS timestamp
-                %s
-                FROM event_view %s
-                GROUP BY %s)
-            GROUP BY %s
-            ORDER BY %s timestamp %s
-            """.printf (
-                field,
-                aggregation_type,
-                aggregation_sql,
-                where_sql,
-                field,
-                field,
-                order_sql, time_sorting);
-    }
-
-    // Used by find_event_ids
-    private WhereClause get_where_clause_from_event_templates (
-        GenericArray<Event> templates) throws EngineError
-    {
-        WhereClause where = new WhereClause (WhereClause.Type.OR);
-        for (int i = 0; i < templates.length; ++i)
-        {
-            Event event_template = templates[i];
-            where.extend (
-                get_where_clause_from_event_template (event_template));
-        }
-        return where;
-    }
-
-    // Used by get_where_clause_from_event_templates
-    private WhereClause get_where_clause_from_event_template (Event template)
-        throws EngineError
-    {
-            WhereClause where = new WhereClause (WhereClause.Type.AND);
-
-            // Event ID
-            if (template.id != 0)
-                where.add ("id=?", template.id.to_string());
-
-            // Interpretation
-            if (template.interpretation != "")
-            {
-                assert_no_wildcard ("interpretation", template.interpretation);
-                WhereClause subwhere = get_where_clause_for_symbol (
-                    "interpretation", template.interpretation,
-                    interpretations_table);
-                if (!subwhere.is_empty ())
-                    where.extend (subwhere);
-            }
-
-            // Manifestation
-            if (template.manifestation != "")
-            {
-                assert_no_wildcard ("manifestation", template.interpretation);
-                WhereClause subwhere = get_where_clause_for_symbol (
-                    "manifestation", template.manifestation,
-                    manifestations_table);
-                if (!subwhere.is_empty ())
-                   where.extend (subwhere);
-            }
-
-            // Actor
-            if (template.actor != "")
-            {
-                string val = template.actor;
-                bool like = parse_wildcard (ref val);
-                bool negated = parse_negation (ref val);
-
-                if (like)
-                    where.add_wildcard_condition ("actor", val, negated);
-                else
-                    where.add_match_condition ("actor",
-                        actors_table.get_id (val), negated);
-            }
-
-            // Origin
-            if (template.origin != "")
-            {
-                string val = template.origin;
-                bool like = parse_wildcard (ref val);
-                bool negated = parse_negation (ref val);
-                assert_no_noexpand (val, "origin");
-
-                if (like)
-                    where.add_wildcard_condition ("origin", val, negated);
-                else
-                    where.add_text_condition_subquery ("origin", val, negated);
-            }
-
-            // Subject templates within the same event template are AND'd
-            // See LP bug #592599.
-            for (int i = 0; i < template.num_subjects(); ++i)
-            {
-                Subject subject_template = template.subjects[i];
-
-                // Subject interpretation
-                if (subject_template.interpretation != "")
-                {
-                    assert_no_wildcard ("subject interpretation",
-                        template.interpretation);
-                    WhereClause subwhere = get_where_clause_for_symbol (
-                        "subj_interpretation", subject_template.interpretation,
-                        interpretations_table);
-                    if (!subwhere.is_empty ())
-                        where.extend (subwhere);
-                }
-
-                // Subject manifestation
-                if (subject_template.manifestation != "")
-                {
-                    assert_no_wildcard ("subject manifestation",
-                        subject_template.manifestation);
-                    WhereClause subwhere = get_where_clause_for_symbol (
-                        "subj_manifestation", subject_template.manifestation,
-                        manifestations_table);
-                    if (!subwhere.is_empty ())
-                        where.extend (subwhere);
-                }
-
-                // Mime-Type
-                if (subject_template.mimetype != "")
-                {
-                    string val = subject_template.mimetype;
-                    bool like = parse_wildcard (ref val);
-                    bool negated = parse_negation (ref val);
-                    assert_no_noexpand (val, "mime-type");
-
-                    if (like)
-                        where.add_wildcard_condition (
-                            "subj_mimetype", val, negated);
-                    else
-                        where.add_match_condition ("subj_mimetype",
-                            mimetypes_table.get_id (val), negated);
-                }
-
-                // URI
-                if (subject_template.uri != "")
-                {
-                    string val = subject_template.uri;
-                    bool like = parse_wildcard (ref val);
-                    bool negated = parse_negation (ref val);
-                    assert_no_noexpand (val, "uri");
-
-                    if (like)
-                        where.add_wildcard_condition ("subj_id", val, negated);
-                    else
-                        where.add_text_condition_subquery ("subj_id", val, negated);
-                }
-
-                // Origin
-                if (subject_template.origin != "")
-                {
-                    string val = subject_template.origin;
-                    bool like = parse_wildcard (ref val);
-                    bool negated = parse_negation (ref val);
-                    assert_no_noexpand (val, "subject origin");
-
-                    if (like)
-                        where.add_wildcard_condition (
-                            "subj_origin", val, negated);
-                    else
-                        where.add_text_condition_subquery (
-                            "subj_origin", val, negated);
-                }
-
-                // Text
-                if (subject_template.text != "")
-                {
-                    // Negation, noexpand and prefix search aren't supported
-                    // for subject texts, but "!", "+" and "*" are valid as
-                    // plain text characters.
-                    where.add_text_condition_subquery ("subj_text_id",
-                        subject_template.text, false);
-                }
-
-                // Current URI
-                if (subject_template.current_uri != "")
-                {
-                    string val = subject_template.current_uri;
-                    bool like = parse_wildcard (ref val);
-                    bool negated = parse_negation (ref val);
-                    assert_no_noexpand (val, "current_uri");
-
-                    if (like)
-                        where.add_wildcard_condition (
-                            "subj_id_current", val, negated);
-                    else
-                        where.add_text_condition_subquery (
-                            "subj_id_current", val, negated);
-                }
-
-                // Subject storage
-                if (subject_template.storage != "")
-                {
-                    string val = subject_template.storage;
-                    assert_no_negation ("subject storage", val);
-                    assert_no_wildcard ("subject storage", val);
-                    assert_no_noexpand (val, "subject storage");
-                    where.add_text_condition_subquery ("subj_storage_id", val);
-                }
-            }
-
-            return where;
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * Check if the value starts with the negation operator. If it does,
-     * remove the operator from the value and return true. Otherwise,
-     * return false.
-     */
-    public static bool parse_negation (ref string val)
-    {
-        if (!val.has_prefix ("!"))
-            return false;
-        val = val.substring (1);
-        return true;
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * If the value starts with the negation operator, throw an
-     * error.
-     */
-    protected void assert_no_negation (string field, string val)
-        throws EngineError
-    {
-        if (!val.has_prefix ("!"))
-            return;
-        string error_message =
-            "Field '%s' doesn't support negation".printf (field);
-        warning (error_message);
-        throw new EngineError.INVALID_ARGUMENT (error_message);
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * Check if the value starts with the noexpand operator. If it does,
-     * remove the operator from the value and return true. Otherwise,
-     * return false.
-     *
-     * Check for the negation operator before calling this function.
-     */
-    public static bool parse_noexpand (ref string val)
-    {
-        if (!val.has_prefix ("+"))
-            return false;
-        val = val.substring (1);
-        return true;
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * If the value starts with the negation operator, throw an
-     * error.
-     */
-    protected void assert_no_noexpand (string field, string val)
-        throws EngineError
-    {
-        if (!val.has_prefix ("+"))
-            return;
-        string error_message =
-            "Field '%s' doesn't support the no-expand operator".printf (field);
-        warning (error_message);
-        throw new EngineError.INVALID_ARGUMENT (error_message);
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * Check if the value ends with the wildcard character. If it does,
-     * remove the wildcard character from the value and return true.
-     * Otherwise, return false.
-     */
-    public static bool parse_wildcard (ref string val)
-    {
-        if (!val.has_suffix ("*"))
-            return false;
-        unowned uint8[] val_data = val.data;
-        val_data[val_data.length-1] = '\0';
-        return true;
-    }
-
-    // Used by get_where_clause_from_event_templates
-    /**
-     * If the value ends with the wildcard character, throw an error.
-     */
-    protected void assert_no_wildcard (string field, string val)
-        throws EngineError
-    {
-        if (!val.has_suffix ("*"))
-            return;
-        string error_message =
-            "Field '%s' doesn't support prefix search".printf (field);
-        warning (error_message);
-        throw new EngineError.INVALID_ARGUMENT (error_message);
-    }
-
-    protected WhereClause get_where_clause_for_symbol (string table_name,
-        string symbol, TableLookup lookup_table) throws EngineError
-    {
-        string _symbol = symbol;
-        bool negated = parse_negation (ref _symbol);
-        bool noexpand = parse_noexpand (ref _symbol);
-        List<unowned string> symbols;
-        if (noexpand)
-            symbols = new List<unowned string> ();
-        else
-            symbols = Symbol.get_all_children (_symbol);
-        symbols.prepend (_symbol);
-
-        WhereClause subwhere = new WhereClause(
-            WhereClause.Type.OR, negated);
-
-        if (symbols.length () == 1)
-        {
-            subwhere.add_match_condition (table_name,
-                lookup_table.get_id (_symbol));
-        }
-        else
-        {
-            var sb = new StringBuilder ();
-            foreach (unowned string uri in symbols)
-            {
-                sb.append_printf ("%d,", lookup_table.get_id (uri));
-            }
-            sb.truncate (sb.len - 1);
-
-            string sql = "%s IN (%s)".printf(table_name, sb.str);
-            subwhere.add(sql);
-        }
-
-        return subwhere;
+
+        base.close ();
     }
 
     private void handle_move_event (Event event)
@@ -1194,24 +344,6 @@
         return 0;
     }
 
-    private void delete_from_cache (string table, int64 rowid)
-    {
-        TableLookup table_lookup;
-
-        if (table == "interpretation")
-            table_lookup = interpretations_table;
-        else if (table == "manifestation")
-            table_lookup = manifestations_table;
-        else if (table == "mimetype")
-            table_lookup = mimetypes_table;
-        else if (table == "actor")
-            table_lookup = actors_table;
-        else
-            return;
-
-        table_lookup.remove((int) rowid);
-    }
-
 }
 
 }

=== modified file 'src/extension-store.vala'
--- src/extension-store.vala	2012-01-26 10:08:09 +0000
+++ src/extension-store.vala	2012-02-02 18:57:35 +0000
@@ -25,7 +25,7 @@
     public class ExtensionStore : Object
     {
 
-        private Zeitgeist.SQLite.ZeitgeistDatabase database;
+        private Zeitgeist.SQLite.Database database;
         private unowned Sqlite.Database db;
         private Sqlite.Statement storage_stmt;
         private Sqlite.Statement retrieval_stmt;

=== modified file 'src/remote.vala'
--- src/remote.vala	2011-11-29 16:04:59 +0000
+++ src/remote.vala	2012-02-05 14:49:10 +0000
@@ -114,12 +114,13 @@
     [DBus (name = "org.gnome.zeitgeist.Index")]
     public interface RemoteSimpleIndexer : Object
     {
-        [DBus (signature = "a(asaasay)u")]
-        public abstract async Variant search (
+        public abstract async void search (
             string query_string,
             [DBus (signature = "(xx)")] Variant time_range,
             [DBus (signature = "a(asaasay)")] Variant filter_templates,
-            uint offset, uint count, uint result_type) throws Error;
+            uint offset, uint count, uint result_type,
+            [DBus (signature = "a(asaasay)")] out Variant events,
+            out uint matches) throws Error;
     }
     
     /* FIXME: Remove this! Only here because of a bug in Vala (see ext-fts) */

=== modified file 'src/sql-schema.vala'
--- src/sql-schema.vala	2012-01-30 18:14:03 +0000
+++ src/sql-schema.vala	2012-02-02 18:57:35 +0000
@@ -70,7 +70,7 @@
             }
         }
 
-        private static int get_schema_version (Sqlite.Database database)
+        public static int get_schema_version (Sqlite.Database database)
         {
           var sql = "SELECT version FROM schema_version WHERE schema='core'";
           int schema_version = -1;

=== modified file 'src/sql.vala'
--- src/sql.vala	2012-01-25 17:37:55 +0000
+++ src/sql.vala	2012-02-06 10:52:13 +0000
@@ -51,8 +51,10 @@
 
     public delegate void DeletionCallback (string table, int64 rowid);
 
-    public class ZeitgeistDatabase : Object
+    public class Database : Object
     {
+        private const int DEFAULT_OPEN_FLAGS =
+            Sqlite.OPEN_READWRITE | Sqlite.OPEN_CREATE;
 
         public Sqlite.Statement event_insertion_stmt;
         public Sqlite.Statement id_retrieval_stmt;
@@ -64,12 +66,28 @@
         public Sqlite.Database database;
 
         private DeletionCallback? deletion_callback = null;
+        private bool is_read_only = false;
 
-        public ZeitgeistDatabase () throws EngineError
+        public Database () throws EngineError
         {
             open_database (true);
 
-            prepare_queries ();
+            prepare_read_queries ();
+            prepare_modification_queries ();
+
+            // Register a data change notification callback to look for
+            // deletions, so we can keep the TableLookups up to date.
+            database.update_hook (update_callback);
+        }
+
+        public Database.read_only () throws EngineError
+        {
+            is_read_only = true;
+            open_database (false);
+
+            prepare_read_queries ();
+            // not initializing the modification queries will let us find
+            // issues more easily
 
             // Register a data change notification callback to look for
             // deletions, so we can keep the TableLookups up to date.
@@ -79,9 +97,10 @@
         private void open_database (bool retry)
             throws EngineError
         {
+            int flags = is_read_only ? Sqlite.OPEN_READONLY : DEFAULT_OPEN_FLAGS;
             int rc = Sqlite.Database.open_v2 (
                 Utils.get_database_file_path (),
-                out database);
+                out database, flags);
             
             if (rc == Sqlite.OK)
             {
@@ -89,7 +108,19 @@
                 {
                     // Error (like a malformed database) may not be exposed
                     // until we try to operate on the database.
-                    DatabaseSchema.ensure_schema (database);
+                    if (is_read_only)
+                    {
+                        int ver = DatabaseSchema.get_schema_version (database);
+                        if (ver != DatabaseSchema.CORE_SCHEMA_VERSION)
+                        {
+                            throw new EngineError.DATABASE_CANTOPEN (
+                                "Unable to open database");
+                        }
+                    }
+                    else
+                    {
+                        DatabaseSchema.ensure_schema (database);
+                    }
                 }
                 catch (EngineError err)
                 {
@@ -296,7 +327,22 @@
             }
         }
 
-        private void prepare_queries () throws EngineError
+        private void prepare_read_queries () throws EngineError
+        {
+            int rc;
+            string sql;
+
+            // Event ID retrieval statement
+            sql = """
+                SELECT id FROM event
+                WHERE timestamp=? AND interpretation=? AND
+                    manifestation=? AND actor=?
+                """;
+            rc = database.prepare_v2 (sql, -1, out id_retrieval_stmt);
+            assert_query_success (rc, "Event ID retrieval query error");
+        }
+
+        private void prepare_modification_queries () throws EngineError
         {
             int rc;
             string sql;
@@ -324,15 +370,6 @@
             rc = database.prepare_v2 (sql, -1, out event_insertion_stmt);
             assert_query_success (rc, "Insertion query error");
 
-            // Event ID retrieval statement
-            sql = """
-                SELECT id FROM event
-                WHERE timestamp=? AND interpretation=? AND
-                    manifestation=? AND actor=?
-                """;
-            rc = database.prepare_v2 (sql, -1, out id_retrieval_stmt);
-            assert_query_success (rc, "Event ID retrieval query error");
-
             // Move handling statment
             sql = """
             UPDATE event

=== modified file 'src/table-lookup.vala'
--- src/table-lookup.vala	2012-01-26 10:08:09 +0000
+++ src/table-lookup.vala	2012-02-02 18:57:35 +0000
@@ -34,7 +34,7 @@
         private HashTable<string, int> value_to_id;
         private Sqlite.Statement insertion_stmt;
 
-        public TableLookup (ZeitgeistDatabase database, string table_name)
+        public TableLookup (Database database, string table_name)
         {
             db = database.database;
             table = table_name;

=== modified file 'src/utils.vala'
--- src/utils.vala	2011-12-31 15:57:15 +0000
+++ src/utils.vala	2012-02-05 14:48:50 +0000
@@ -127,6 +127,59 @@
             File dbfile = File.new_for_path (get_database_file_path ());
             dbfile.set_display_name (get_database_file_retire_name ());
         }
+
+        /**
+         * Check if the value starts with the negation operator. If it does,
+         * remove the operator from the value and return true. Otherwise,
+         * return false.
+         */
+        public static bool parse_negation (ref string val)
+        {
+            if (!val.has_prefix ("!"))
+                return false;
+            val = val.substring (1);
+            return true;
+        }
+
+        /**
+         * Check if the value starts with the noexpand operator. If it does,
+         * remove the operator from the value and return true. Otherwise,
+         * return false.
+         *
+         * Check for the negation operator before calling this function.
+         */
+        public static bool parse_noexpand (ref string val)
+        {
+            if (!val.has_prefix ("+"))
+                return false;
+            val = val.substring (1);
+            return true;
+        }
+
+
+        /**
+         * Check if the value ends with the wildcard character. If it does,
+         * remove the wildcard character from the value and return true.
+         * Otherwise, return false.
+         */
+        public static bool parse_wildcard (ref string val)
+        {
+            if (!val.has_suffix ("*"))
+                return false;
+            unowned uint8[] val_data = val.data;
+            val_data[val_data.length-1] = '\0';
+            return true;
+        }
+
+        /**
+         * Return true if a string is empty (null or containing just a null
+         * byte).
+         */
+        public static bool is_empty_string (string? s)
+        {
+            return s == null || s == "";
+        }
+
     }
 }
 

=== modified file 'src/zeitgeist-daemon.vala'
--- src/zeitgeist-daemon.vala	2012-01-26 10:08:09 +0000
+++ src/zeitgeist-daemon.vala	2012-02-02 18:57:35 +0000
@@ -458,7 +458,7 @@
                     var lm = LogLevelFlags.LEVEL_MESSAGE;
                     var lw = LogLevelFlags.LEVEL_WARNING;
                     var lc = LogLevelFlags.LEVEL_CRITICAL;
-                    switch (log_level)
+                    switch (log_level.up ())
                     {
                         case "DEBUG":
                             discarded = 0;

=== modified file 'test/direct/Makefile.am'
--- test/direct/Makefile.am	2012-02-02 16:24:22 +0000
+++ test/direct/Makefile.am	2012-02-05 14:50:20 +0000
@@ -17,6 +17,7 @@
 	$(NULL)
 
 SRC_FILES = \
+	$(top_srcdir)/src/db-reader.vala \
 	$(top_srcdir)/src/engine.vala \
 	$(top_srcdir)/src/utils.vala \
 	$(top_srcdir)/src/errors.vala \

=== modified file 'test/direct/query-operators-test.vala'
--- test/direct/query-operators-test.vala	2012-01-26 10:08:09 +0000
+++ test/direct/query-operators-test.vala	2012-02-05 14:50:20 +0000
@@ -18,6 +18,8 @@
  *
  */
 
+using Zeitgeist;
+
 int main (string[] args)
 {
 
@@ -45,33 +47,18 @@
 
 private class PublicEngine : Zeitgeist.Engine
 {
-    public bool PUBLIC_parse_negation (ref string val)
-    {
-        return parse_negation (ref val);
-    }
-
 	public void PUBLIC_assert_no_negation (string field, string val)
 		throws Zeitgeist.EngineError
 	{
 		assert_no_negation (field, val);
 	}
 
-    public bool PUBLIC_parse_noexpand (ref string val)
-    {
-        return parse_noexpand (ref val);
-    }
-
 	public void PUBLIC_assert_no_noexpand (string field, string val)
 		throws Zeitgeist.EngineError
 	{
 		assert_no_noexpand (field, val);
 	}
 
-    public bool PUBLIC_parse_wildcard (ref string val)
-    {
-        return parse_wildcard (ref val);
-    }
-
 	public void PUBLIC_assert_no_wildcard (string field, string val)
 		throws Zeitgeist.EngineError
 	{
@@ -82,22 +69,21 @@
 
 public void parse_negation_test ()
 {
-    PublicEngine engine = new PublicEngine ();
     string val;
 
     // Test string without a negation
     val = "no negation";
-    assert (engine.PUBLIC_parse_negation (ref val) == false);
+    assert (Utils.parse_negation (ref val) == false);
     assert (val == "no negation");
 
     // Test string with a valid negation
     val = "!negation";
-    assert (engine.PUBLIC_parse_negation (ref val) == true);
+    assert (Utils.parse_negation (ref val) == true);
     assert (val == "negation");
 
     // Test negation character in a meaningless position
     val = "some ! chars";
-    assert (engine.PUBLIC_parse_negation (ref val) == false);
+    assert (Utils.parse_negation (ref val) == false);
     assert (val == "some ! chars");
 }
 
@@ -121,22 +107,21 @@
 
 public void parse_noexpand_test ()
 {
-    PublicEngine engine = new PublicEngine ();
     string val;
 
     // Test string without a negation
     val = "no expand";
-    assert (engine.PUBLIC_parse_noexpand (ref val) == false);
+    assert (Utils.parse_noexpand (ref val) == false);
     assert (val == "no expand");
 
     // Test string with a valid noexpand
     val = "+noexpand";
-    assert (engine.PUBLIC_parse_noexpand (ref val) == true);
+    assert (Utils.parse_noexpand (ref val) == true);
     assert (val == "noexpand");
 
     // Test negation character in a meaningless position
     val = "some + chars++";
-    assert (engine.PUBLIC_parse_noexpand (ref val) == false);
+    assert (Utils.parse_noexpand (ref val) == false);
     assert (val == "some + chars++");
 }
 
@@ -160,22 +145,21 @@
 
 public void parse_wildcard_test ()
 {
-    PublicEngine engine = new PublicEngine ();
     string val;
 
     // Test string without a wildcard
     val = "no wildcard";
-    assert (engine.PUBLIC_parse_wildcard (ref val) == false);
+    assert (Utils.parse_wildcard (ref val) == false);
     assert (val == "no wildcard");
 
     // Test string with a valid wildcard
     val = "yes wildcar*";
-    assert (engine.PUBLIC_parse_wildcard (ref val) == true);
+    assert (Utils.parse_wildcard (ref val) == true);
     assert (val == "yes wildcar");
 
     // Test wildcard character in a meaningless position
     val = "some * chars";
-    assert (engine.PUBLIC_parse_wildcard ( ref val) == false);
+    assert (Utils.parse_wildcard ( ref val) == false);
     assert (val == "some * chars");
 }
 

=== modified file 'test/direct/table-lookup-test.vala'
--- test/direct/table-lookup-test.vala	2011-12-31 00:31:17 +0000
+++ test/direct/table-lookup-test.vala	2012-02-05 14:50:20 +0000
@@ -52,7 +52,7 @@
 
 public void basic_test ()
 {
-    ZeitgeistDatabase database = new Zeitgeist.SQLite.ZeitgeistDatabase ();
+    Database database = new Zeitgeist.SQLite.Database ();
     unowned Sqlite.Database db = database.database;
     TableLookup table_lookup = new TableLookup (database, "actor");
 
@@ -71,7 +71,7 @@
 public void engine_test ()
 {
     PublicEngine engine = new PublicEngine ();
-    ZeitgeistDatabase database = engine.database;
+    Database database = engine.database;
     unowned Sqlite.Database db = database.database;
     TableLookup table_lookup = engine.get_actors_table_lookup();