diff options
author | Seif Lotfy <seif@lotfy.com> | 2013-01-18 23:58:45 +0100 |
---|---|---|
committer | Seif Lotfy <seif@lotfy.com> | 2013-01-18 23:58:45 +0100 |
commit | 403487224559779581fe279514b8ea1de8afc229 (patch) | |
tree | f959356c84888f1d42e3a72f7176d5a3534ae7d5 | |
parent | 218f6601991d779f40497538795ba98c123352b4 (diff) | |
download | zeitgeist-403487224559779581fe279514b8ea1de8afc229.tar.gz |
restructuring
-rw-r--r-- | extensions/fts++/Makefile.am | 6 | ||||
-rw-r--r-- | libzeitgeist/Makefile.am | 6 | ||||
-rw-r--r-- | libzeitgeist/log.vala | 13 | ||||
-rw-r--r-- | libzeitgeist/utils.vala | 4 | ||||
-rw-r--r-- | src/Makefile.am | 6 | ||||
-rw-r--r-- | src/db-reader.vala | 953 | ||||
-rw-r--r-- | src/errors.vala | 53 | ||||
-rw-r--r-- | src/sql-schema.vala | 674 | ||||
-rw-r--r-- | src/sql.vala | 478 | ||||
-rw-r--r-- | src/table-lookup.vala | 155 | ||||
-rw-r--r-- | src/where-clause.vala | 288 |
11 files changed, 23 insertions, 2613 deletions
diff --git a/extensions/fts++/Makefile.am b/extensions/fts++/Makefile.am index d590e7bb..273788bc 100644 --- a/extensions/fts++/Makefile.am +++ b/extensions/fts++/Makefile.am @@ -30,13 +30,7 @@ AM_VALAFLAGS = \ $(NULL) libzeitgeist_internal_la_VALASOURCES = \ - db-reader.vala \ engine.vala \ - sql.vala \ - errors.vala \ - table-lookup.vala \ - sql-schema.vala \ - where-clause.vala \ ext-dummies.vala \ $(NULL) diff --git a/libzeitgeist/Makefile.am b/libzeitgeist/Makefile.am index 69af9efe..ee5444a5 100644 --- a/libzeitgeist/Makefile.am +++ b/libzeitgeist/Makefile.am @@ -28,8 +28,10 @@ AM_VALAFLAGS = \ libzeitgeist_2_0_la_SOURCES = \ data-source.vala \ data-source-registry.vala \ + db-reader.vala \ mimetype.vala \ enumerations.vala \ + errors.vala \ event.vala \ subject.vala \ timerange.vala \ @@ -43,7 +45,11 @@ libzeitgeist_2_0_la_SOURCES = \ remote.vala \ result-set.vala \ simple-result-set.vala \ + sql.vala \ + sql-schema.vala \ + table-lookup.vala \ utils.vala \ + where-clause.vala \ $(NULL) libzeitgeist_2_0_la_LIBADD = $(ZEITGEIST_LIBS) diff --git a/libzeitgeist/log.vala b/libzeitgeist/log.vala index a0db8712..6b7c14f3 100644 --- a/libzeitgeist/log.vala +++ b/libzeitgeist/log.vala @@ -38,6 +38,9 @@ * Zeitgeist also comes with a blacklist extension to make sure the user * always stays in control of what information is logged. */ + +using Zeitgeist.Utils; + namespace Zeitgeist { @@ -59,6 +62,8 @@ public class Log : QueuedProxyWrapper private static Log default_instance; private RemoteLog proxy; + private bool is_direct; + private string datapath; private Variant? engine_version; private HashTable<Monitor, uint> monitors; @@ -72,6 +77,14 @@ public class Log : QueuedProxyWrapper { proxy = Bus.get_proxy.end (res); proxy_acquired (proxy); + datapath = proxy.datapath; + if (!FileUtils.test(datapath, GLib.FileTest.EXISTS) || + datapath == ":memory:") + is_direct = false; + else { + is_direct = true; + set_database_file_path(datapath); + } } catch (IOError err) { diff --git a/libzeitgeist/utils.vala b/libzeitgeist/utils.vala index 31141982..9bbf8c6c 100644 --- a/libzeitgeist/utils.vala +++ b/libzeitgeist/utils.vala @@ -206,6 +206,10 @@ namespace Zeitgeist throw new DataModelError.INVALID_SIGNATURE (error_message); } + public void set_database_file_path(string path) { + DATABASE_FILE_PATH = path; + } + } } diff --git a/src/Makefile.am b/src/Makefile.am index a686045b..bcc18c17 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -38,18 +38,12 @@ extensions_VALASOURCES = \ zeitgeist_daemon_VALASOURCES = \ zeitgeist-daemon.vala \ - db-reader.vala \ engine.vala \ extension.vala \ extension-collection.vala \ extension-store.vala \ logging.vala \ notify.vala \ - sql.vala \ - errors.vala \ - table-lookup.vala \ - sql-schema.vala \ - where-clause.vala \ $(NULL) zeitgeist_daemon_SOURCES = \ diff --git a/src/db-reader.vala b/src/db-reader.vala deleted file mode 100644 index 9e67cd0e..00000000 --- a/src/db-reader.vala +++ /dev/null @@ -1,953 +0,0 @@ -/* db-reader.vala - * - * Copyright © 2011-2012 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * By Seif Lotfy <seif@lotfy.com> - * Copyright © 2011 Canonical Ltd. - * By Michal Hruby <michal.hruby@canonical.com> - * - * Based upon a Python implementation (2009-2011) by: - * Markus Korn <thekorn@gmx.net> - * Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com> - * Seif Lotfy <seif@lotfy.com> - * Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * 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; - - try - { - interpretations_table = new TableLookup (database, "interpretation"); - manifestations_table = new TableLookup (database, "manifestation"); - mimetypes_table = new TableLookup (database, "mimetype"); - actors_table = new TableLookup (database, "actor"); - } - catch (EngineError err) - { - // FIXME: propagate this properly? - critical ("TableLookup initialization failed: %s", err.message); - } - } - - protected Event get_event_from_row (Sqlite.Statement stmt, uint32 event_id) - throws EngineError - { - 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) - throws EngineError - { - 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.current_origin = stmt.column_text ( - EventViewRows.SUBJECT_CURRENT_ORIGIN_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); - } - database.assert_query_success (rc, "Error", Sqlite.DONE); - - // 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_for_clause (WhereClause where, - uint max_events, uint result_type) throws EngineError - { - 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 "; - break; - case ResultType.LEAST_RECENT_EVENTS: - sql += where_sql + " ORDER BY "; - break; - case ResultType.MOST_RECENT_EVENT_ORIGIN: - sql = group_and_sort ("origin", where); - break; - case ResultType.LEAST_RECENT_EVENT_ORIGIN: - sql = group_and_sort ("origin", where); - break; - case ResultType.MOST_POPULAR_EVENT_ORIGIN: - sql = group_and_sort ("origin", where, false); - break; - case ResultType.LEAST_POPULAR_EVENT_ORIGIN: - sql = group_and_sort ("origin", where, true); - break; - case ResultType.MOST_RECENT_SUBJECTS: - sql = group_and_sort ("subj_id", where); - break; - case ResultType.LEAST_RECENT_SUBJECTS: - sql = group_and_sort ("subj_id", where); - break; - case ResultType.MOST_POPULAR_SUBJECTS: - sql = group_and_sort ("subj_id", where, false); - break; - case ResultType.LEAST_POPULAR_SUBJECTS: - sql = group_and_sort ("subj_id", where, true); - break; - case ResultType.MOST_RECENT_CURRENT_URI: - sql = group_and_sort ("subj_id_current", where); - break; - case ResultType.LEAST_RECENT_CURRENT_URI: - sql = group_and_sort ("subj_id_current", where); - break; - case ResultType.MOST_POPULAR_CURRENT_URI: - sql = group_and_sort ("subj_id_current", where, false); - break; - case ResultType.LEAST_POPULAR_CURRENT_URI: - sql = group_and_sort ("subj_id_current", where, true); - break; - case ResultType.MOST_RECENT_ACTOR: - sql = group_and_sort ("actor", where); - break; - case ResultType.LEAST_RECENT_ACTOR: - sql = group_and_sort ("actor", where); - break; - case ResultType.MOST_POPULAR_ACTOR: - sql = group_and_sort ("actor", where, false); - break; - case ResultType.LEAST_POPULAR_ACTOR: - sql = group_and_sort ("actor", where, true); - break; - case ResultType.OLDEST_ACTOR: - sql = group_and_sort ("actor", where, null, "min"); - break; - case ResultType.MOST_RECENT_ORIGIN: - sql = group_and_sort ("subj_origin", where); - break; - case ResultType.LEAST_RECENT_ORIGIN: - sql = group_and_sort ("subj_origin", where); - break; - case ResultType.MOST_POPULAR_ORIGIN: - sql = group_and_sort ("subj_origin", where, false); - break; - case ResultType.LEAST_POPULAR_ORIGIN: - sql = group_and_sort ("subj_origin", where, true); - break; - case ResultType.MOST_RECENT_CURRENT_ORIGIN: - sql = group_and_sort ("subj_origin_current", where); - break; - case ResultType.LEAST_RECENT_CURRENT_ORIGIN: - sql = group_and_sort ("subj_origin_current", where); - break; - case ResultType.MOST_POPULAR_CURRENT_ORIGIN: - sql = group_and_sort ("subj_origin_current", where, false); - break; - case ResultType.LEAST_POPULAR_CURRENT_ORIGIN: - sql = group_and_sort ("subj_origin_current", where, true); - break; - case ResultType.MOST_RECENT_SUBJECT_INTERPRETATION: - sql = group_and_sort ("subj_interpretation", where); - break; - case ResultType.LEAST_RECENT_SUBJECT_INTERPRETATION: - sql = group_and_sort ("subj_interpretation", where); - break; - case ResultType.MOST_POPULAR_SUBJECT_INTERPRETATION: - sql = group_and_sort ("subj_interpretation", where, false); - break; - case ResultType.LEAST_POPULAR_SUBJECT_INTERPRETATION: - sql = group_and_sort ("subj_interpretation", where, true); - break; - case ResultType.MOST_RECENT_MIMETYPE: - sql = group_and_sort ("subj_mimetype", where); - break; - case ResultType.LEAST_RECENT_MIMETYPE: - sql = group_and_sort ("subj_mimetype", where); - break; - case ResultType.MOST_POPULAR_MIMETYPE: - sql = group_and_sort ("subj_mimetype", where, false); - break; - case ResultType.LEAST_POPULAR_MIMETYPE: - sql = group_and_sort ("subj_mimetype", where, true); - break; - default: - string error_message = "Invalid ResultType."; - warning (error_message); - throw new EngineError.INVALID_ARGUMENT (error_message); - } - - // complete the sort rule - bool time_asc = ResultType.is_sort_order_asc ((ResultType) result_type); - sql += " timestamp %s".printf ((time_asc) ? "ASC" : "DESC"); - - if (where.get_is_simple ()) - sql = sql.replace ("FROM event_view", "FROM event"); - - 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); - database.assert_not_corrupt (rc); - throw new EngineError.DATABASE_ERROR (error_message); - } - - return event_ids; - } - - 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 = get_where_clause_for_query (time_range, - event_templates, storage_state); - - //if (!where.may_have_results ()) - // return new uint32[0]; - - return find_event_ids_for_clause (where, max_events, result_type); - } - - 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)); - } - - public WhereClause get_where_clause_for_query (TimeRange time_range, - GenericArray<Event> event_templates, uint storage_state) throws EngineError - { - WhereClause where = new WhereClause (WhereClause.Type.AND); - - 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 ()); - where.set_is_simple (false); - } - 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); - - return where; - } - - 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 == RelevantResultType.RECENT || - result_type == RelevantResultType.RELATED) - { - // 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]; - - database.assert_query_success (rc, "Error in find_related_uris", - Sqlite.DONE); - - 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; - break; - } - } - - if (count_in_window) - { - for (int j = 0; j < window.length; j++) - { - if (window[j].id in result_ids) - { - 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 == RelevantResultType.RECENT) - 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 RelevantResultType"); - } - } - - /** - * 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, WhereClause where, - bool? count_asc=null, string aggregation_type="max") - { - string aggregation_sql = ""; - string order_sql = ""; - string where_sql = where.get_sql_conditions (); - if (count_asc != null) - { - aggregation_sql = ", COUNT(%s) AS num_events".printf (field); - order_sql = "num_events %s,".printf ((count_asc) ? "ASC" : "DESC"); - } - if (count_asc != null || !where.has_non_timestamp_condition()) - { - return """ - SELECT id FROM event - NATURAL JOIN ( - SELECT %s, - %s(timestamp) AS timestamp - %s - FROM event_view WHERE %s - GROUP BY %s) - GROUP BY %s - ORDER BY %s - """.printf ( - field, - aggregation_type, - aggregation_sql, - where_sql, - field, - field, - order_sql); - } - return """ - SELECT id, %s(timestamp) AS timestamp - FROM event_view WHERE %s AND %s IS NOT NULL - GROUP BY %s - ORDER BY - """.printf ( - aggregation_type, - where_sql, - field, - field); - } - - // Used by find_event_ids - public 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.id_try_string (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.id_try_string (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); - } - - // Current Origin - if (!is_empty_string (subject_template.current_origin)) - { - string val = subject_template.current_origin; - bool like = parse_wildcard (ref val); - bool negated = parse_negation (ref val); - assert_no_noexpand (val, "current_origin"); - - if (like) - where.add_wildcard_condition ( - "subj_origin_current", val, negated); - else - where.add_text_condition_subquery ( - "subj_origin_current", val, negated); - } - - // Subject storage - if (!is_empty_string (subject_template.storage)) - { - // FIXME: do we want to support negation? - 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 == null || !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 == null || !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 == null || !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.id_try_string (_symbol)); - } - else - { - string sql = "(%s)"; - string sub_sql = ""; - bool first = true; - foreach (unowned string uri in symbols) - { - if (!first) - sub_sql = sub_sql + " OR "; - sub_sql = sub_sql + "%s = %i ".printf (table_name, lookup_table.id_try_string (uri)); - first = false; - } - sql = sql.printf (sub_sql); - 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 diff --git a/src/errors.vala b/src/errors.vala deleted file mode 100644 index 538264b6..00000000 --- a/src/errors.vala +++ /dev/null @@ -1,53 +0,0 @@ -/* zeitgeist-daemon.vala - * - * Copyright © 2011 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * 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/>. - * - */ - -namespace Zeitgeist -{ - [DBus (name = "org.gnome.zeitgeist.EngineError")] - public errordomain EngineError - { - BACKUP_FAILED, - DATABASE_BUSY, - DATABASE_CANTOPEN, - DATABASE_CORRUPT, - DATABASE_ERROR, - DATABASE_RETIRE_FAILED, - EXISTING_INSTANCE, - INVALID_ARGUMENT, - INVALID_EVENT, - INVALID_KEY, - } - - // vala doesn't include proper headers, this fixes it - private static void vala_bug_workaround () - { - try - { - Bus.get_sync (BusType.SESSION, null); - } - catch (Error err) - { - // kill "unused method" warning - vala_bug_workaround (); - } - } -} - -// vim:expandtab:ts=4:sw=4 diff --git a/src/sql-schema.vala b/src/sql-schema.vala deleted file mode 100644 index 17e6ff7a..00000000 --- a/src/sql-schema.vala +++ /dev/null @@ -1,674 +0,0 @@ -/* sql-schema.vala - * - * Copyright © 2011-2012 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * Copyright © 2011-2012 Canonical Ltd. - * By Michal Hruby <michal.hruby@canonical.com> - * By Siegfried-A. Gevatter <siegfried.gevatter@collabora.co.uk> - * - * Based upon a Python implementation (2009-2011) by: - * Markus Korn <thekorn@gmx.net> - * Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com> - * Seif Lotfy <seif@lotfy.com> - * Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * 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; - -namespace Zeitgeist.SQLite -{ - - public class DatabaseSchema : Object - { - - public const string CORE_SCHEMA = "core"; - public const int CORE_SCHEMA_VERSION = 9; - - private const string DATABASE_CREATION = "database_creation"; - - public static void ensure_schema (Sqlite.Database database) - throws EngineError - { - int schema_version = Utils.using_in_memory_database () ? - -1 : get_schema_version (database); - - if (schema_version == -1) - { - // most likely a new DB - setup_database (database); - create_schema (database); - create_event_indices (database); - - // set database creation date - var schema_sql = ("INSERT INTO schema_version VALUES ('%s', %" + - int64.FORMAT + ")").printf (DATABASE_CREATION, - Timestamp.from_now ()); - exec_query (database, schema_sql); - } - else if (schema_version >= 3 && schema_version <= 8) - { - backup_database (); - - // Fix file permissions, update PRAGMAs, etc. - setup_database (database); - - // SQLite supports transactional DDL. This way we won't leave a - // messed up schema if something goes wrong. - exec_query (database, "BEGIN"); - - if (schema_version == 3) - { - // Add missing columns to storage table - exec_query (database, - "ALTER TABLE storage ADD COLUMN icon VARCHAR"); - exec_query (database, - "ALTER TABLE storage ADD COLUMN display_name VARCHAR"); - - // Set subjects that don't have a storage to "unknown", so - // they'll always be marked as available. - // FIXME: Do we want to separate unknown/local/online? - exec_query (database, """ - INSERT OR IGNORE INTO storage (value, state) - VALUES ('unknown', 1) - """); - exec_query (database, """ - UPDATE event SET subj_storage = - (SELECT id FROM storage WHERE value='unknown') - WHERE subj_storage IS NULL - """); - } - - // The events table is missing several columns: - // - new in version 4: (event) origin and subj_id_current - // - new in version 8: subj_origin_current - exec_query (database, - "ALTER TABLE event RENAME TO event_old"); - - string[] tables = { "interpretation", "manifestation", - "mimetype", "actor" }; - - // Rename old tables that need to be replaced - foreach (unowned string table in tables) - { - exec_query (database, - "ALTER TABLE %s RENAME TO %s_old".printf (table, table)); - } - - // Create any missing tables and indices - create_schema (database); - drop_event_indices (database); - drop_basic_indices (database); - - // Migrate data to the new tables and delete the old ones - foreach (unowned string table in tables) - { - exec_query (database, - "INSERT INTO %s SELECT id, value FROM %s_old".printf ( - table, table)); - - exec_query (database, "DROP TABLE %s_old".printf (table)); - } - - - // Migrate events from the old table - // - We initialize subj_origin_current to subj_origin as an - // approximation. - // FIXME: consider replaying MOVE_EVENTs to fix this - exec_query (database, """ - INSERT INTO event - SELECT - id, timestamp, interpretation, manifestation, - actor, payload, subj_id, subj_interpretation, - subj_manifestation, subj_origin, subj_mimetype, - subj_text, subj_storage, NULL as origin, - subj_id AS subj_id_current, - subj_origin AS subj_origin_current - FROM event_old WHERE id IS NOT NULL - AND timestamp IS NOT NULL - AND interpretation IS NOT NULL - AND manifestation IS NOT NULL - AND actor IS NOT NULL - and subj_id IS NOT NULL - """); - - // This will also drop any triggers the `events' table had - exec_query (database, "DROP TABLE event_old"); - - // Ontology update - exec_query (database, - "INSERT OR IGNORE INTO manifestation (value) VALUES ('%s')" - .printf (NFO.WEB_DATA_OBJECT)); - exec_query (database, """ - UPDATE event - SET subj_manifestation=( - SELECT id FROM manifestation WHERE value='""" + - NFO.WEB_DATA_OBJECT + """') - WHERE - subj_manifestation=( - SELECT id FROM manifestation WHERE value='""" + - NFO.REMOTE_DATA_OBJECT + """') - AND subj_id IN ( - SELECT id FROM uri - WHERE - value LIKE "http://%" - OR value LIKE "https://%" - ) - """); - - exec_query (database, "COMMIT"); - create_event_indices (database); - exec_query (database, "ANALYZE"); - message ("Upgraded database to schema version %d.", - CORE_SCHEMA_VERSION); - } - else if (schema_version < CORE_SCHEMA_VERSION) - { - throw new EngineError.DATABASE_ERROR ( - "Unable to upgrade from schema version %d".printf ( - schema_version)); - } - } - - private static void backup_database () throws EngineError - { - try - { - Utils.backup_database (); - } - catch (Error backup_error) - { - var msg = "Database backup failed: " + backup_error.message; - throw new EngineError.BACKUP_FAILED (msg); - } - } - - public static int get_schema_version (Sqlite.Database database) - throws EngineError - { - int schema_version = (int) get_schema_metadata (database, CORE_SCHEMA); - debug ("schema_version is %d", schema_version); - - if (schema_version < -1) - { - throw new EngineError.DATABASE_CORRUPT ( - "Database corruption flag is set."); - } - return schema_version; - } - - public static int64 get_creation_date (Sqlite.Database database) - { - return get_schema_metadata (database, DATABASE_CREATION); - } - - private static int64 get_schema_metadata (Sqlite.Database database, - string key) - { - var sql = "SELECT version FROM schema_version " + - "WHERE schema='%s'".printf (key); - - int64 schema_version = -1; - - database.exec (sql, - (n_cols, values, column_names) => - { - if (values[0] != null) - { - schema_version = int64.parse (values[0]); - } - return 0; - }, null); - - // we don't really care about the return value of exec, the result - // will be -1 if something went wrong anyway - - return schema_version; - } - - public static void set_corruption_flag (Sqlite.Database database) - throws EngineError - { - // A schema_version value smaller than -1 indicates that - // database corruption has been detected. - int version = get_schema_version (database); - if (version > 0) - version = -version; - set_schema_version (database, version); - } - - private static void set_schema_version (Sqlite.Database database, - int schema_version) throws EngineError - { - /* The 'ON CONFLICT REPLACE' on the PK converts INSERT to UPDATE - * when appriopriate */ - var schema_sql = "INSERT INTO schema_version VALUES ('%s', %d)" - .printf (CORE_SCHEMA, schema_version); - exec_query (database, schema_sql); - } - - public static void setup_database (Sqlite.Database database) - throws EngineError - { - if (!Utils.using_in_memory_database ()) - FileUtils.chmod (Utils.get_database_file_path (), 0600); - if (Utils.get_data_path () == Utils.get_default_data_path ()) - FileUtils.chmod (Utils.get_data_path (), 0700); - - exec_query (database, "PRAGMA journal_mode = WAL"); - exec_query (database, "PRAGMA synchronous = NORMAL"); - exec_query (database, "PRAGMA locking_mode = NORMAL"); - } - - public static void create_schema (Sqlite.Database database) - throws EngineError - { - // URI - exec_query (database, """ - CREATE TABLE IF NOT EXISTS uri ( - id INTEGER PRIMARY KEY, - value VARCHAR UNIQUE - ) - """); - - // Interpretation - exec_query (database, """ - CREATE TABLE IF NOT EXISTS interpretation ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - value VARCHAR UNIQUE - ) - """); - - // Manifestation - exec_query (database, """ - CREATE TABLE IF NOT EXISTS manifestation ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - value VARCHAR UNIQUE - ) - """); - - // Mime-Type - exec_query (database, """ - CREATE TABLE IF NOT EXISTS mimetype ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - value VARCHAR UNIQUE - ) - """); - - // Actor - exec_query (database, """ - CREATE TABLE IF NOT EXISTS actor ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - value VARCHAR UNIQUE - ) - """); - - // Text - exec_query (database, """ - CREATE TABLE IF NOT EXISTS text ( - id INTEGER PRIMARY KEY, - value VARCHAR UNIQUE - ) - """); - - // Payload - // (There's no value index for payloads, they can only be fetched - // by ID). - exec_query (database, """ - CREATE TABLE IF NOT EXISTS payload - (id INTEGER PRIMARY KEY, value BLOB) - """); - - // Storage - exec_query (database, """ - CREATE TABLE IF NOT EXISTS storage ( - id INTEGER PRIMARY KEY, - value VARCHAR UNIQUE, - state INTEGER, - icon VARCHAR, - display_name VARCHAR - ) - """); - - // Event - // This is the primary table for log statements. Note that: - // - event.id is NOT unique, each subject has a separate row; - // - timestamps are integers; - // - (event_)origin and subj_id_current are at the end of the - // table, for backwards-compatibility reasons. - exec_query (database, """ - CREATE TABLE IF NOT EXISTS event ( - id INTEGER NOT NULL, - timestamp INTEGER NOT NULL, - interpretation INTEGER NOT NULL, - manifestation INTEGER NOT NULL, - actor INTEGER NOT NULL, - payload INTEGER, - subj_id INTEGER NOT NULL, - subj_interpretation INTEGER, - subj_manifestation INTEGER, - subj_origin INTEGER, - subj_mimetype INTEGER, - subj_text INTEGER, - subj_storage INTEGER, - origin INTEGER, - subj_id_current INTEGER, - subj_origin_current INTEGER, - CONSTRAINT interpretation_fk - FOREIGN KEY(interpretation) - REFERENCES interpretation(id) - ON DELETE CASCADE, - CONSTRAINT manifestation_fk - FOREIGN KEY(manifestation) - REFERENCES manifestation(id) - ON DELETE CASCADE, - CONSTRAINT actor_fk - FOREIGN KEY(actor) - REFERENCES actor(id) - ON DELETE CASCADE, - CONSTRAINT origin_fk - FOREIGN KEY(origin) - REFERENCES uri(id) - ON DELETE CASCADE, - CONSTRAINT payload_fk - FOREIGN KEY(payload) - REFERENCES payload(id) - ON DELETE CASCADE, - CONSTRAINT subj_id_fk - FOREIGN KEY(subj_id) - REFERENCES uri(id) - ON DELETE CASCADE, - CONSTRAINT subj_id_current_fk - FOREIGN KEY(subj_id_current) - REFERENCES uri(id) - ON DELETE CASCADE, - CONSTRAINT subj_interpretation_fk - FOREIGN KEY(subj_interpretation) - REFERENCES interpretation(id) - ON DELETE CASCADE, - CONSTRAINT subj_manifestation_fk - FOREIGN KEY(subj_manifestation) - REFERENCES manifestation(id) - ON DELETE CASCADE, - CONSTRAINT subj_origin_fk - FOREIGN KEY(subj_origin) - REFERENCES uri(id) - ON DELETE CASCADE, - CONSTRAINT subj_origin_current_fk - FOREIGN KEY(subj_origin_current) - REFERENCES uri(id) - ON DELETE CASCADE, - CONSTRAINT subj_mimetype_fk - FOREIGN KEY(subj_mimetype) - REFERENCES mimetype(id) - ON DELETE CASCADE, - CONSTRAINT subj_text_fk - FOREIGN KEY(subj_text) - REFERENCES text(id) - ON DELETE CASCADE, - CONSTRAINT subj_storage_fk - FOREIGN KEY(subj_storage) - REFERENCES storage(id) - ON DELETE CASCADE, - PRIMARY KEY (timestamp, interpretation, - manifestation, actor, subj_id) - ) - """); - - // Extensions - exec_query (database, """ - CREATE TABLE IF NOT EXISTS extensions_conf ( - extension VARCHAR, - key VARCHAR, - value BLOB, - CONSTRAINT unique_extension UNIQUE (extension, key) - ) - """); - - // Performance note: the subqueries here are provided for lookup - // only. For querying, use explicit "WHERE x IN (SELECT id ...)" - // subqueries. - exec_query (database, "DROP VIEW IF EXISTS event_view"); - exec_query (database, """ - CREATE VIEW IF NOT EXISTS event_view AS - SELECT event.id, - event.timestamp, - event.interpretation, - event.manifestation, - event.actor, - (SELECT value FROM payload - WHERE payload.id=event.payload) - AS payload, - (SELECT value FROM uri - WHERE uri.id=event.subj_id) - AS subj_uri, - event.subj_id, --//this points to an id in the uri table - event.subj_interpretation, - event.subj_manifestation, - event.subj_origin, - (SELECT value FROM uri - WHERE uri.id=event.subj_origin) - AS subj_origin_uri, - event.subj_mimetype, - (SELECT value FROM text - WHERE text.id = event.subj_text) - AS subj_text, - (SELECT value FROM storage - WHERE storage.id=event.subj_storage) - AS subj_storage, - (SELECT state FROM storage - WHERE storage.id=event.subj_storage) - AS subj_storage_state, - event.origin, - (SELECT value FROM uri - WHERE uri.id=event.origin) - AS event_origin_uri, - (SELECT value FROM uri - WHERE uri.id=event.subj_id_current) - AS subj_current_uri, - event.subj_id_current, - event.subj_text AS subj_text_id, - event.subj_storage AS subj_storage_id, - (SELECT value FROM actor - WHERE actor.id=event.actor) - AS actor_uri, - event.subj_origin_current, - (SELECT value FROM uri - WHERE uri.id=event.subj_origin_current) - AS subj_origin_current_uri - FROM event - """); - - // Set schema version - exec_query (database, """ - CREATE TABLE IF NOT EXISTS schema_version ( - schema VARCHAR PRIMARY KEY ON CONFLICT REPLACE, - version INT - ) - """); - set_schema_version (database, CORE_SCHEMA_VERSION); - } - - /* - * Creates indices for all auxiliary tables. - */ - public static void create_basic_indices (Sqlite.Database database) - throws EngineError - { - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS uri_value ON uri(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS interpretation_value - ON interpretation(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS manifestation_value - ON manifestation(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS mimetype_value - ON mimetype(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS actor_value - ON actor(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS text_value - ON text(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS storage_value - ON storage(value) - """); - exec_query (database, """ - CREATE UNIQUE INDEX IF NOT EXISTS extensions_conf_key - ON extensions_conf (extension, key) - """); - } - - public static void create_event_indices (Sqlite.Database database) - throws EngineError - { - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_id - ON event(id, timestamp) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_timestamp - ON event(timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_interpretation - ON event(interpretation, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_manifestation - ON event(manifestation, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_actor - ON event(actor, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_origin - ON event(origin, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_id - ON event(subj_id, timestamp, subj_interpretation, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_id_current - ON event(subj_id_current, timestamp, subj_interpretation, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_interpretation - ON event(subj_interpretation, timestamp, subj_id, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_manifestation - ON event(subj_manifestation, timestamp, subj_id, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_origin - ON event(subj_origin, timestamp, subj_interpretation, subj_id, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_origin_current - ON event(subj_origin_current, timestamp, subj_interpretation, subj_id, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_mimetype - ON event(subj_mimetype, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_text - ON event(subj_text, timestamp, id) - """); - exec_query (database, """ - CREATE INDEX IF NOT EXISTS event_subj_storage - ON event(subj_storage, timestamp, id) - """); - } - - /* - * Drop indices for all auxiliary tables. - */ - public static void drop_basic_indices (Sqlite.Database database) - throws EngineError - { - exec_query (database, "DROP INDEX IF EXISTS uri_value"); - exec_query (database, "DROP INDEX IF EXISTS interpretation_value"); - exec_query (database, "DROP INDEX IF EXISTS manifestation_value"); - exec_query (database, "DROP INDEX IF EXISTS mimetype_value"); - exec_query (database, "DROP INDEX IF EXISTS actor_value"); - exec_query (database, "DROP INDEX IF EXISTS text_value"); - exec_query (database, "DROP INDEX IF EXISTS storage_value"); - exec_query (database, "DROP INDEX IF EXISTS extensions_conf_key"); - } - - public static void drop_event_indices (Sqlite.Database database) - throws EngineError - { - exec_query (database, "DROP INDEX IF EXISTS event_id"); - exec_query (database, "DROP INDEX IF EXISTS event_timestamp"); - exec_query (database, "DROP INDEX IF EXISTS event_interpretation"); - exec_query (database, "DROP INDEX IF EXISTS event_manifestation"); - exec_query (database, "DROP INDEX IF EXISTS event_actor"); - exec_query (database, "DROP INDEX IF EXISTS event_origin"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_id"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_id_current"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_interpretation"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_manifestation"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_origin"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_current"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_mimetype"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_text"); - exec_query (database, "DROP INDEX IF EXISTS event_subj_storage"); - } - - /** - * Execute the given SQL. If the query doesn't succeed, throw - * an error. - * - * @param database the database on which to run the query - * @param sql the SQL query to run - */ - private static void exec_query (Sqlite.Database database, - string sql) throws EngineError - { - int rc = database.exec (sql); - if (rc != Sqlite.OK) - { - if (rc == Sqlite.CORRUPT) - { - throw new EngineError.DATABASE_CORRUPT (database.errmsg ()); - } - else - { - const string fmt_str = "Can't create database: %d, %s\n\n" + - "Unable to execute SQL:\n%s"; - var err_msg = fmt_str.printf (rc, database.errmsg (), sql); - throw new EngineError.DATABASE_ERROR (err_msg); - } - } - } - - } - -} diff --git a/src/sql.vala b/src/sql.vala deleted file mode 100644 index 1bb24685..00000000 --- a/src/sql.vala +++ /dev/null @@ -1,478 +0,0 @@ -/* sql.vala - * - * Copyright © 2011-2012 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * By Seif Lotfy <seif@lotfy.com> - * Copyright © 2011 Manish Sinha <manishsinha@ubuntu.com> - * Copyright © 2012 Canonical Ltd. - * By Siegfried-A. Gevatter <siegfried.gevatter@collabora.co.uk> - * - * 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; - -namespace Zeitgeist.SQLite -{ - - public enum EventViewRows - { - ID, - TIMESTAMP, - INTERPRETATION, - MANIFESTATION, - ACTOR, - PAYLOAD, - SUBJECT_URI, - SUBJECT_ID, - SUBJECT_INTERPRETATION, - SUBJECT_MANIFESTATION, - SUBJECT_ORIGIN, - SUBJECT_ORIGIN_URI, - SUBJECT_MIMETYPE, - SUBJECT_TEXT, - SUBJECT_STORAGE, - SUBJECT_STORAGE_STATE, - EVENT_ORIGIN, - EVENT_ORIGIN_URI, - SUBJECT_CURRENT_URI, - SUBJECT_ID_CURRENT, - SUBJECT_TEXT_ID, - SUBJECT_STORAGE_ID, - ACTOR_URI, - SUBJECT_CURRENT_ORIGIN, - SUBJECT_CURRENT_ORIGIN_URI - } - - public delegate void DeletionCallback (string table, int64 rowid); - - 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; - public Sqlite.Statement move_handling_stmt; - public Sqlite.Statement payload_insertion_stmt; - - // The DB should be accessible from engine for statement preperations - // as well as allowing extensions to add tables to it. - public Sqlite.Database database; - - private DeletionCallback? deletion_callback = null; - private bool is_read_only = false; - - public Database () throws EngineError - { - open_database (true); - - 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. - database.update_hook (update_callback); - } - - 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, flags); - - if (rc == Sqlite.OK) - { - try - { - // Errors (like a malformed database) may not be exposed - // until we try to operate on the 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: old schema version"); - } - } - else - { - DatabaseSchema.ensure_schema (database); - } - } - catch (EngineError err) - { - if (err is EngineError.DATABASE_CORRUPT && retry) - rc = Sqlite.CORRUPT; - else if (err is EngineError.DATABASE_CANTOPEN) - rc = Sqlite.CANTOPEN; - else if (err is EngineError.DATABASE_BUSY) - rc = Sqlite.BUSY; - else - throw err; - } - } - - if (rc != Sqlite.OK) - { - if (rc == Sqlite.CORRUPT && retry) - { - // The database disk image is malformed - warning ("It looks like your database is corrupt. " + - "It will be renamed and a new one will be created."); - retire_database (); - open_database (false); - } - else if (rc == Sqlite.PERM || rc == Sqlite.CANTOPEN) - { - // Access permission denied / Unable to open database file - throw new EngineError.DATABASE_CANTOPEN ( - database.errmsg ()); - } - else if (rc == Sqlite.BUSY) - { - // The database file is locked - throw new EngineError.DATABASE_BUSY (database.errmsg ()); - } - else - { - string message = "Can't open database: %d, %s".printf (rc, - database.errmsg ()); - throw new EngineError.DATABASE_ERROR (message); - } - } - } - - private static void retire_database () throws EngineError - { - try - { - File dbfile = File.new_for_path ( - Utils.get_database_file_path ()); - dbfile.set_display_name ( - Utils.get_database_file_retire_name ()); - } - catch (Error err) - { - string message = "Could not rename database: %s".printf ( - err.message); - throw new EngineError.DATABASE_RETIRE_FAILED (message); - } - } - - public uint32 get_last_id () throws EngineError - { - int last_id = -1; - int rc = database.exec ("SELECT MAX(id) FROM event", - (n_columns, values, column_names) => - { - if (values[0] == null) - last_id = 0; - else - last_id = int.parse (values[0]); - return 0; - }, null); - assert_query_success (rc, "Can't query database"); - assert (last_id != -1); - return last_id; - } - - public void set_deletion_callback (owned DeletionCallback? callback) - { - deletion_callback = (owned) callback; - } - - /** - * Join all given event_ids into a comma-separated string suitable - * for use in a SQL query like "WHERE id IN (...)". - */ - public string get_sql_string_from_event_ids (uint32[] event_ids) - requires (event_ids.length > 0) - { - var sql_condition = new StringBuilder (); - sql_condition.append_printf ("%u", event_ids[0]); - for (int i = 1; i < event_ids.length; ++i) { - sql_condition.append_printf (", %u", event_ids[i]); - } - return sql_condition.str; - } - - public TimeRange? get_time_range_for_event_ids (uint32[] event_ids) - throws EngineError - { - if (event_ids.length == 0) - return null; - - string sql = """ - SELECT MIN(timestamp), MAX(timestamp) - FROM event - WHERE id IN (%s) - """.printf (get_sql_string_from_event_ids (event_ids)); - - TimeRange? time_range = null; - int rc = database.exec (sql, - (n_columns, values, column_names) => - { - if (values[0] != null) - { - int64 start = int64.parse (values[0]); - int64 end = int64.parse (values[1]); - time_range = new TimeRange (start, end); - } - return 0; - }, null); - assert_query_success (rc, "SQL Error"); - - return time_range; - } - - public void insert_or_ignore_into_table (string table_name, - GenericArray<string> values) throws EngineError - { - if (values.length == 0) - return; - - int rc; - - var sql = new StringBuilder (); - sql.append ("INSERT OR IGNORE INTO "); - sql.append (table_name); - sql.append (" (value) SELECT ?"); - for (int i = 1; i < values.length; ++i) - sql.append (" UNION SELECT ?"); - - Sqlite.Statement stmt; - rc = database.prepare_v2 (sql.str, -1, out stmt); - assert_query_success (rc, "SQL error"); - - for (int i = 0; i < values.length; ++i) - stmt.bind_text (i+1, values[i]); - - rc = stmt.step (); - assert_query_success (rc, "SQL error", Sqlite.DONE); - } - - public void begin_transaction () throws EngineError - { - int rc = database.exec ("BEGIN"); - assert_query_success (rc, "Can't start transaction"); - } - - public void end_transaction () throws EngineError - { - int rc = database.exec ("COMMIT"); - assert_query_success (rc, "Can't commit transaction"); - } - - public void abort_transaction () throws EngineError - { - int rc = database.exec ("ROLLBACK"); - assert_query_success (rc, "Can't rollback transaction"); - } - - public void close () - { - // SQLite connection is implicitly closed upon destruction - database = null; - } - -#if EXPLAIN_QUERIES - public void explain_query (Sqlite.Statement prepared_stmt) - throws EngineError - { - int rc; - Sqlite.Statement stmt; - - var explain_sql = "EXPLAIN QUERY PLAN %s".printf (prepared_stmt.sql ()); - - rc = prepared_stmt.db_handle ().prepare_v2 (explain_sql, -1, out stmt); - assert_query_success(rc, "SQL error"); - - print ("%s\n", explain_sql); - - while ((rc = stmt.step()) == Sqlite.ROW) - { - int select_id = stmt.column_int (0); - int order = stmt.column_int (1); - int from = stmt.column_int (2); - unowned string detail = stmt.column_text (3); - - print ("%d %d %d %s\n", select_id, order, from, detail); - } - } -#endif - - /** - * Ensure `rc' is SQLITE_OK. If it isn't, print an error message - * and throw an error. - * - * @param rc error code returned by a SQLite call - * @param msg message to print if `rc' indicates an error - * @throws EngineError - */ - [Diagnostics] - public void assert_query_success (int rc, string msg, - int success_code=Sqlite.OK) throws EngineError - { - if (unlikely (rc != success_code)) - { - string error_message = "%s: %d, %s".printf ( - msg, rc, database.errmsg ()); - warning ("%s\n", error_message); - assert_not_corrupt (rc); - throw new EngineError.DATABASE_ERROR (error_message); - } - } - - /** - * Ensure `rc' isn't SQLITE_CORRUPT. If it is, schedule a database - * retire and Zeitgeist restart so a new database can be created, - * unless in read-only mode, in which case EngineError.DATABASE_ERROR - * will be thrown. - * - * This function should be called whenever assert_query_success isn't - * used. - * - * @param rc error code returned by a SQLite call - */ - public void assert_not_corrupt (int rc) - throws EngineError - { - if (unlikely (rc == Sqlite.CORRUPT)) - { - warning ("It looks like your database is corrupt: %s".printf ( - database.errmsg ())); - if (!is_read_only) - { - // Sets a flag in the database indicating that it is - // corrupt. This will trigger a database retire and - // re-creation on the next startup. - DatabaseSchema.set_corruption_flag (database); - } - throw new EngineError.DATABASE_CORRUPT (database.errmsg ()); - } - } - - 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; - - // Event insertion statement - sql = """ - INSERT INTO event ( - id, timestamp, interpretation, manifestation, actor, - origin, payload, subj_id, subj_id_current, - subj_interpretation, subj_manifestation, subj_origin, - subj_origin_current, subj_mimetype, subj_text, subj_storage - ) VALUES ( - ?, ?, ?, ?, ?, - (SELECT id FROM uri WHERE value=?), - ?, - (SELECT id FROM uri WHERE value=?), - (SELECT id FROM uri WHERE value=?), - ?, ?, - (SELECT id FROM uri WHERE value=?), - (SELECT id FROM uri WHERE value=?), - ?, - (SELECT id FROM text WHERE value=?), - (SELECT id FROM storage WHERE value=?) - )"""; - - rc = database.prepare_v2 (sql, -1, out event_insertion_stmt); - assert_query_success (rc, "Insertion query error"); - - // Move handling statment - sql = """ - UPDATE event - SET subj_id_current=(SELECT id FROM uri WHERE value=?) - , subj_origin_current=(SELECT id FROM uri WHERE value=?) - WHERE subj_id_current=(SELECT id FROM uri WHERE value=?) - AND interpretation!=? AND timestamp<? - """; - rc = database.prepare_v2 (sql, -1, out move_handling_stmt); - assert_query_success (rc, "Move handling error"); - - // Payload insertion statment - sql = """ - INSERT INTO payload (value) VALUES (?) - """; - rc = database.prepare_v2 (sql, -1, out payload_insertion_stmt); - assert_query_success (rc, "Payload insertion query error"); - } - - public bool analyze() throws EngineError - { - int rc = database.exec("ANALYZE"); - assert_query_success (rc, "Event ID retrieval query error"); - return false; - } - - protected void update_callback (Sqlite.Action action, - string dbname, string table, int64 rowid) - { - if (action != Sqlite.Action.DELETE) - return; - if (deletion_callback != null) - deletion_callback (table, rowid); - //interpretations_table - // manifestations_ - //mimetypes_table - mimetype table - // actors_ . actor table - // FIXME! - /* - stdout.printf ("%s", dbname); // = main - stdout.printf ("%s", table); - stdout.printf ("%li", (long) rowid); - */ - } - - } - -} - -// vim:expandtab:ts=4:sw=4 diff --git a/src/table-lookup.vala b/src/table-lookup.vala deleted file mode 100644 index b582b143..00000000 --- a/src/table-lookup.vala +++ /dev/null @@ -1,155 +0,0 @@ -/* table-lookup.vala - * - * Copyright © 2011 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * Based upon a Python implementation (2009-2011) by: - * Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * 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/>. - * - */ - -namespace Zeitgeist.SQLite -{ - - public class TableLookup : Object - { - unowned Zeitgeist.SQLite.Database database; - unowned Sqlite.Database db; - - private string table; - private HashTable<int, string> id_to_value; - private HashTable<string, int> value_to_id; - private Sqlite.Statement insertion_stmt; - private Sqlite.Statement retrieval_stmt; - - public TableLookup (Database database, string table_name) - throws EngineError - { - this.database = database; - db = database.database; - table = table_name; - id_to_value = new HashTable<int, string>(direct_hash, direct_equal); - value_to_id = new HashTable<string, int>(str_hash, str_equal); - - int rc; - string sql; - - rc = db.exec ("SELECT id, value FROM " + table, - (n_columns, values, column_names) => - { - id_to_value.insert (int.parse(values[0]), values[1]); - value_to_id.insert (values[1], int.parse(values[0])); - return 0; - }, null); - database.assert_query_success (rc, - "Can't init %s table".printf (table)); - - sql = "INSERT INTO " + table + " (value) VALUES (?)"; - rc = db.prepare_v2 (sql, -1, out insertion_stmt); - database.assert_query_success (rc, "Error creating insertion_stmt"); - - sql = "SELECT value FROM " + table + " WHERE id=?"; - rc = db.prepare_v2 (sql, -1, out retrieval_stmt); - database.assert_query_success (rc, "Error creating retrieval_stmt"); - } - - /** - * Searches the table for the given ID, returns -1 if not found. - * - * @see id_for_string - */ - public int id_try_string (string name) - { - int id = value_to_id.lookup (name); - if (id == 0) - return -1; - return id; - } - - /** - * Searches the table for the given ID, inserts a new one if not found. - * - * @see id_try_string - * - */ - public int id_for_string (string name) throws EngineError - { - int id = value_to_id.lookup (name); - if (id == 0) - { - int rc; - insertion_stmt.reset (); - insertion_stmt.bind_text (1, name); - rc = insertion_stmt.step (); - database.assert_query_success (rc, "Error in id_for_string", - Sqlite.DONE); - - id = (int) db.last_insert_rowid (); - - id_to_value.insert (id, name); - value_to_id.insert (name, id); - } - return id; - } - - public unowned string? get_value (int id) throws EngineError - { - // When we fetch an event, it either was already in the database - // at the time Zeitgeist started or it was inserted later -using - // Zeitgeist-, so here we always have the data in memory already. - if (id == 0) - return null; - unowned string val = id_to_value.lookup (id); - if (val != null) return val; - - // Unless this is a standalone reader in a separate process, in - // which case the values won't be kept updated, so we need to - // query the DB if we don't find it. - int rc; - string? text = null; - - retrieval_stmt.reset (); - retrieval_stmt.bind_int64 (1, id); - if ((rc = retrieval_stmt.step()) == Sqlite.ROW) - { - text = retrieval_stmt.column_text (0); - id_to_value.insert (id, text); - value_to_id.insert (text, id); - rc = retrieval_stmt.step (); - } - database.assert_query_success (rc, "Error in get_value", - Sqlite.DONE); - if (text == null) - { - critical ("Error getting data from table: %d, %s\n", - rc, db.errmsg ()); - } - - return id_to_value.lookup (id); - } - - public void remove (int id) - { - string name = id_to_value.lookup (id); - id_to_value.remove (id); - value_to_id.remove (name); - } - - } - -} - -// vim:expandtab:ts=4:sw=4 diff --git a/src/where-clause.vala b/src/where-clause.vala deleted file mode 100644 index a8305995..00000000 --- a/src/where-clause.vala +++ /dev/null @@ -1,288 +0,0 @@ -/* where-clause.vala - * - * Copyright © 2011-2012 Collabora Ltd. - * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * Based upon a Python implementation (2009-2011) by: - * Markus Korn <thekorn@gmx.net> - * Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com> - * Seif Lotfy <seif@lotfy.com> - * Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> - * - * 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; - -namespace Zeitgeist -{ - - /** - * This class provides a convenient representation a SQL `WHERE' clause, - * composed of a set of conditions joined together. - * - * The relation between conditions can be either of type *AND* or *OR*, but - * not both. To create more complex clauses, use several `WhereClause` - * instances and joining them together using `extend`. - * - * Instances of this class can then be used to obtain a line of SQL code and - * a list of arguments, for use with SQLite 3. - */ - public class WhereClause : Object - { - - public enum Type - { - AND, - OR - } - - private static string[] RELATION_SIGNS = { " AND ", " OR " }; - - private WhereClause.Type clause_type; - private bool negated; - private GenericArray<string> conditions; - private GenericArray<string> arguments; - private bool is_simple; - - public WhereClause (WhereClause.Type type, bool negate=false) - { - clause_type = type; - negated = negate; - is_simple = true; - conditions = new GenericArray<string> (); - arguments = new GenericArray<string> (); - } - - public int get_conditions_length() - { - return conditions.length; - } - - public bool has_non_timestamp_condition() { - for (int i=0; i<conditions.length; i++) { - if (!conditions[i].has_prefix("timestamp")) - return true; - } - return false; - } - - public void add (string condition, string? argument=null) - { - conditions.add (condition); - if (argument != null) - arguments.add (argument); - } - - public void add_with_array (string condition, - GenericArray<string> args) - { - conditions.add (condition); - for (int i = 0; i < args.length; ++i) - arguments.add (args[i]); - } - - private static string get_search_table_for_column (string column) - { - string search_table; - switch (column) - { - // For use in add_text_condition_subquery and - // add_wildcard_condition: - case "origin": - case "subj_origin": - case "subj_origin_current": - case "subj_id": - case "subj_id_current": - search_table = "uri"; - break; - case "subj_mimetype": - search_table = "mimetype"; - break; - - // For use only in add_text_condition_subquery: - case "subj_text_id": - search_table = "text"; - break; - case "subj_storage_id": - search_table = "storage"; - break; - - // -- - default: - search_table = column; - break; - } - return search_table; - } - - public void add_match_condition (string column, int val, - bool negation=false) - { - string sql = "%s %s= %d".printf (column, (negation) ? "!" : "", val); - add (sql); - } - - public void add_text_condition_subquery (string column, string val, - bool negation=false) - { - string search_table = get_search_table_for_column (column); - string sql = "%s %s= (SELECT id FROM %s WHERE value = ?)".printf ( - column, (negation) ? "!" : "", search_table); - add (sql, val); - is_simple = false; - } - - public void add_text_condition (string column, string val, - bool negation=false) - { - string sql = "%s %s= ?".printf (column, (negation) ? "!" : ""); - add (sql, val); - } - - public void add_wildcard_condition (string column, string needle, - bool negation=false) - { - string search_table = get_search_table_for_column (column); - - var values = new GenericArray<string> (); - values.add(needle); - string optimized_glob = optimize_glob ( - "id", search_table, ref values); - - string sql; - if (!negation) - sql = "%s IN (%s)".printf (column, optimized_glob); - else - sql = "(%s NOT IN (%s) OR %s is NULL)".printf (column, - optimized_glob, column); - add_with_array (sql, values); - is_simple = false; - } - - public void extend (WhereClause clause) - { - if (clause.is_empty ()) - return; - string sql = clause.get_sql_conditions (); - add_with_array (sql, clause.arguments); - is_simple = clause.get_is_simple (); - /*if not where.may_have_results(): - if self._relation == self.AND: - self.clear() - self.register_no_result() - */ - } - - public bool is_empty () - { - return conditions.length == 0; - } - - public bool may_have_results () - { - return conditions.length > 0; // or not self._no_result_member - } - - public bool get_is_simple () - { - return is_simple; - } - - public void set_is_simple (bool simple) - { - is_simple = simple; - } - - /** - * This is dangerous. Only use it if you're made full of awesome. - */ - private T[] generic_array_to_unowned_array<T> (GenericArray<T> gptrarr) - { - long[] pointers = new long[gptrarr.length + 1]; - Memory.copy(pointers, ((PtrArray *) gptrarr)->pdata, - (gptrarr.length) * sizeof (void *)); - return (T[]) pointers; - } - - public string get_sql_conditions () - { - assert (conditions.length > 0); - string negation_sign = (negated) ? "NOT " : ""; - string relation_sign = RELATION_SIGNS[clause_type]; - - if (conditions.length == 1) - return "%s%s".printf (negation_sign, conditions[0]); - string conditions_string = string.joinv (relation_sign, - generic_array_to_unowned_array<string> (conditions)); - return "%s(%s)".printf (negation_sign, conditions_string); - } - - public unowned GenericArray<string> get_bind_arguments () - { - return arguments; - } - - /** - * Return an optimized version of the GLOB statement as described in - * http://www.sqlite.org/optoverview.html "4.0 The LIKE optimization". - */ - private static string optimize_glob (string column, string table, - ref GenericArray<string> args) - requires (args.length == 1) - { - string sql; - string prefix = args[0]; - if (prefix == "") { - sql = "SELECT %s FROM %s".printf (column, table); - } - else if (false) // ... - { - // FIXME: check for all(i == unichr(0x10ffff)...) - } - else - { - sql = "SELECT %s FROM %s WHERE (value >= ? AND value < ?)" - .printf (column, table); - args.add (get_right_boundary (prefix)); - } - return sql; - } - - /** - * Return the smallest string which is greater than the given `text`. - */ - protected static string get_right_boundary (string text) - { - if (text == "") - return new StringBuilder ().append_unichar(0x10ffff).str; - int len = text.char_count () - 1; - unichar charpoint = text.get_char (text.index_of_nth_char (len)); - string head = text.substring (0, text.index_of_nth_char (len)); - if (charpoint == 0x10ffff) - { - // If the last char is the biggest possible char we need to - // look at the second last. - return get_right_boundary (head); - } - return head + - new StringBuilder ().append_unichar(charpoint + 1).str; - } - - } - -} - -// vim:expandtab:ts=4:sw=4 |