Merge lp:~mhr3/zeitgeist/refactoring into lp:~zeitgeist/zeitgeist/bluebird

Proposed by Michal Hruby
Status: Merged
Merged at revision: 382
Proposed branch: lp:~mhr3/zeitgeist/refactoring
Merge into: lp:~zeitgeist/zeitgeist/bluebird
Diff against target: 2390 lines (+1055/-968)
16 files modified
extensions/fts.vala (+14/-30)
extensions/storage-monitor.vala (+1/-1)
src/Makefile.am (+1/-0)
src/datamodel.vala (+2/-2)
src/db-reader.vala (+894/-0)
src/engine.vala (+16/-884)
src/extension-store.vala (+1/-1)
src/remote.vala (+4/-3)
src/sql-schema.vala (+1/-1)
src/sql.vala (+52/-15)
src/table-lookup.vala (+1/-1)
src/utils.vala (+53/-0)
src/zeitgeist-daemon.vala (+1/-1)
test/direct/Makefile.am (+1/-0)
test/direct/query-operators-test.vala (+11/-27)
test/direct/table-lookup-test.vala (+2/-2)
To merge this branch: bzr merge lp:~mhr3/zeitgeist/refactoring
Reviewer Review Type Date Requested Status
Siegfried Gevatter Approve
Review via email: mp+91334@code.launchpad.net

Description of the change

Refactor Engine class, so it's easier to do read-only DB access.

To post a comment you must log in.
lp:~mhr3/zeitgeist/refactoring updated
382. By Michal Hruby

Keep the deletion notification in DbReader only

383. By Michal Hruby

Handle null strings properly

384. By Michal Hruby

A small fix to interface definition

385. By Michal Hruby

Fix tests

386. By Michal Hruby

Get rid of the Indexer proxy workaround

Revision history for this message
Siegfried Gevatter (rainct) wrote :

Good work.

You aren't setting `is_read_only' to false anywhere. Other than that, looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'extensions/fts.vala'
--- extensions/fts.vala 2012-01-02 19:30:04 +0000
+++ extensions/fts.vala 2012-02-05 18:08:19 +0000
@@ -24,13 +24,13 @@
24 [DBus (name = "org.gnome.zeitgeist.Index")]24 [DBus (name = "org.gnome.zeitgeist.Index")]
25 public interface RemoteSearchEngine: Object25 public interface RemoteSearchEngine: Object
26 {26 {
27 [DBus (signature = "a(asaasay)u")]27 public abstract async void search (
28 public abstract async Variant search (
29 string query_string,28 string query_string,
30 [DBus (signature = "(xx)")] Variant time_range,29 [DBus (signature = "(xx)")] Variant time_range,
31 [DBus (signature = "a(asaasay)")] Variant filter_templates,30 [DBus (signature = "a(asaasay)")] Variant filter_templates,
32 uint offset, uint count, uint result_type,31 uint offset, uint count, uint result_type,
33 [DBus (signature = "a(asaasay)")] out Variant events) throws Error;32 [DBus (signature = "a(asaasay)")] out Variant events,
33 out uint matches) throws Error;
34 }34 }
3535
36 /* Because of a Vala bug we have to define the proxy interface outside of36 /* Because of a Vala bug we have to define the proxy interface outside of
@@ -39,12 +39,13 @@
39 [DBus (name = "org.gnome.zeitgeist.SimpleIndexer")]39 [DBus (name = "org.gnome.zeitgeist.SimpleIndexer")]
40 public interface RemoteSimpleIndexer : Object40 public interface RemoteSimpleIndexer : Object
41 {41 {
42 [DBus (signature = "a(asaasay)u")]42 public abstract async void search (
43 public abstract async Variant search (
44 string query_string,43 string query_string,
45 [DBus (signature = "(xx)")] Variant time_range,44 [DBus (signature = "(xx)")] Variant time_range,
46 [DBus (signature = "a(asaasay)")] Variant filter_templates,45 [DBus (signature = "a(asaasay)")] Variant filter_templates,
47 uint offset, uint count, uint result_type) throws Error;46 uint offset, uint count, uint result_type,
47 [DBus (signature = "a(asaasay)")] out Variant events,
48 out uint matches) throws Error;
48 }49 }
49 */50 */
5051
@@ -103,11 +104,9 @@
103 }104 }
104 }105 }
105106
106 /* This whole method is one huge workaround for an issue with Vala107 public async void search (string query_string, Variant time_range,
107 * enclosing all out/return parameters in a TUPLE variant */
108 public async Variant search (string query_string, Variant time_range,
109 Variant filter_templates, uint offset, uint count, uint result_type,108 Variant filter_templates, uint offset, uint count, uint result_type,
110 out Variant events) throws Error109 out Variant events, out uint matches) throws Error
111 {110 {
112 if (siin == null || !(siin is DBusProxy))111 if (siin == null || !(siin is DBusProxy))
113 {112 {
@@ -116,26 +115,11 @@
116 "Not connected to SimpleIndexer");115 "Not connected to SimpleIndexer");
117 }116 }
118 var timer = new Timer ();117 var timer = new Timer ();
119 DBusProxy proxy = (DBusProxy) siin;118 yield siin.search (query_string, time_range, filter_templates,
120 var b = new VariantBuilder (new VariantType ("(s(xx)a(asaasay)uuu)"));119 offset, count, result_type,
121 b.add ("s", query_string);120 out events, out matches);
122 b.add_value (time_range);121 debug ("Got %u[/%u] results from indexer (in %f seconds)",
123 b.add_value (filter_templates);122 (uint) events.n_children (), matches, timer.elapsed ());
124 b.add ("u", offset);
125 b.add ("u", count);
126 b.add ("u", result_type);
127 var result = yield proxy.call ("Search", b.end (), 0, -1, null);
128 events = result.get_child_value (0);
129 /* FIXME: this somehow doesn't work :(
130 * but it's fixable in a similar way as this method's signature
131 * is done */
132 /*
133 var result = yield siin.search (query_string, time_range,
134 filter_templates, offset, count, result_type);
135 */
136 debug ("Got %u results from indexer (in %f seconds)",
137 (uint) events.n_children (), timer.elapsed ());
138 return result.get_child_value (1);
139 }123 }
140124
141 }125 }
142126
=== modified file 'extensions/storage-monitor.vala'
--- extensions/storage-monitor.vala 2012-01-27 13:34:18 +0000
+++ extensions/storage-monitor.vala 2012-02-05 18:08:19 +0000
@@ -106,7 +106,7 @@
106 "dav", "davs", "ftp", "http", "https", "mailto",106 "dav", "davs", "ftp", "http", "https", "mailto",
107 "sftp", "smb", "ssh" };107 "sftp", "smb", "ssh" };
108108
109 private Zeitgeist.SQLite.ZeitgeistDatabase database;109 private Zeitgeist.SQLite.Database database;
110 private unowned Sqlite.Database db;110 private unowned Sqlite.Database db;
111 private uint registration_id;111 private uint registration_id;
112112
113113
=== modified file 'src/Makefile.am'
--- src/Makefile.am 2011-12-31 18:15:06 +0000
+++ src/Makefile.am 2012-02-05 18:08:19 +0000
@@ -31,6 +31,7 @@
31zeitgeist_daemon_VALASOURCES = \31zeitgeist_daemon_VALASOURCES = \
32 zeitgeist-daemon.vala \32 zeitgeist-daemon.vala \
33 datamodel.vala \33 datamodel.vala \
34 db-reader.vala \
34 engine.vala \35 engine.vala \
35 remote.vala \36 remote.vala \
36 extension.vala \37 extension.vala \
3738
=== modified file 'src/datamodel.vala'
--- src/datamodel.vala 2012-01-25 17:37:55 +0000
+++ src/datamodel.vala 2012-02-05 18:08:19 +0000
@@ -268,7 +268,7 @@
268 {268 {
269 var matches = false;269 var matches = false;
270 var parsed = template_property;270 var parsed = template_property;
271 var is_negated = Engine.parse_negation (ref parsed);271 var is_negated = Utils.parse_negation (ref parsed);
272272
273 if (parsed == "")273 if (parsed == "")
274 {274 {
@@ -283,7 +283,7 @@
283 {283 {
284 matches = true;284 matches = true;
285 }285 }
286 else if (can_wildcard && Engine.parse_wildcard (ref parsed))286 else if (can_wildcard && Utils.parse_wildcard (ref parsed))
287 {287 {
288 if (property.has_prefix (parsed)) matches = true;288 if (property.has_prefix (parsed)) matches = true;
289 }289 }
290290
=== added file 'src/db-reader.vala'
--- src/db-reader.vala 1970-01-01 00:00:00 +0000
+++ src/db-reader.vala 2012-02-05 18:08:19 +0000
@@ -0,0 +1,894 @@
1/* db-reader.vala
2 *
3 * Copyright © 2011 Collabora Ltd.
4 * By Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
5 * By Seif Lotfy <seif@lotfy.com>
6 * Copyright © 2011 Canonical Ltd.
7 * By Michal Hruby <michal.hruby@canonical.com>
8 *
9 * Based upon a Python implementation (2009-2011) by:
10 * Markus Korn <thekorn@gmx.net>
11 * Mikkel Kamstrup Erlandsen <mikkel.kamstrup@gmail.com>
12 * Seif Lotfy <seif@lotfy.com>
13 * Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com>
14 *
15 * This program is free software: you can redistribute it and/or modify
16 * it under the terms of the GNU Lesser General Public License as published by
17 * the Free Software Foundation, either version 2.1 of the License, or
18 * (at your option) any later version.
19 *
20 * This program is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * You should have received a copy of the GNU Lesser General Public License
26 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27 *
28 */
29
30using Zeitgeist;
31using Zeitgeist.SQLite;
32using Zeitgeist.Utils;
33
34namespace Zeitgeist
35{
36
37public class DbReader : Object
38{
39
40 public Zeitgeist.SQLite.Database database { get; construct; }
41 protected unowned Sqlite.Database db;
42
43 protected TableLookup interpretations_table;
44 protected TableLookup manifestations_table;
45 protected TableLookup mimetypes_table;
46 protected TableLookup actors_table;
47
48 public DbReader () throws EngineError
49 {
50 Object (database: new Zeitgeist.SQLite.Database.read_only ());
51 }
52
53 construct
54 {
55 database.set_deletion_callback (delete_from_cache);
56 db = database.database;
57
58 interpretations_table = new TableLookup (database, "interpretation");
59 manifestations_table = new TableLookup (database, "manifestation");
60 mimetypes_table = new TableLookup (database, "mimetype");
61 actors_table = new TableLookup (database, "actor");
62 }
63
64 protected Event get_event_from_row (Sqlite.Statement stmt, uint32 event_id)
65 {
66 Event event = new Event ();
67 event.id = event_id;
68 event.timestamp = stmt.column_int64 (EventViewRows.TIMESTAMP);
69 event.interpretation = interpretations_table.get_value (
70 stmt.column_int (EventViewRows.INTERPRETATION));
71 event.manifestation = manifestations_table.get_value (
72 stmt.column_int (EventViewRows.MANIFESTATION));
73 event.actor = actors_table.get_value (
74 stmt.column_int (EventViewRows.ACTOR));
75 event.origin = stmt.column_text (
76 EventViewRows.EVENT_ORIGIN_URI);
77
78 // Load payload
79 unowned uint8[] data = (uint8[]) stmt.column_blob(
80 EventViewRows.PAYLOAD);
81 data.length = stmt.column_bytes(EventViewRows.PAYLOAD);
82 if (data != null)
83 {
84 event.payload = new ByteArray();
85 event.payload.append(data);
86 }
87 return event;
88 }
89
90 protected Subject get_subject_from_row (Sqlite.Statement stmt)
91 {
92 Subject subject = new Subject ();
93 subject.uri = stmt.column_text (EventViewRows.SUBJECT_URI);
94 subject.text = stmt.column_text (EventViewRows.SUBJECT_TEXT);
95 subject.storage = stmt.column_text (EventViewRows.SUBJECT_STORAGE);
96 subject.origin = stmt.column_text (EventViewRows.SUBJECT_ORIGIN_URI);
97 subject.current_uri = stmt.column_text (
98 EventViewRows.SUBJECT_CURRENT_URI);
99 subject.interpretation = interpretations_table.get_value (
100 stmt.column_int (EventViewRows.SUBJECT_INTERPRETATION));
101 subject.manifestation = manifestations_table.get_value (
102 stmt.column_int (EventViewRows.SUBJECT_MANIFESTATION));
103 subject.mimetype = mimetypes_table.get_value (
104 stmt.column_int (EventViewRows.SUBJECT_MIMETYPE));
105 return subject;
106 }
107
108 public GenericArray<Event?> get_events(uint32[] event_ids,
109 BusName? sender=null) throws EngineError
110 {
111 // TODO: Consider if we still want the cache. This should be done
112 // once everything is working, since it adds unneeded complexity.
113 // It'd also benchmark it again first, we may have better options
114 // to enhance the performance of SQLite now, and event processing
115 // will be faster now being C.
116
117 if (event_ids.length == 0)
118 return new GenericArray<Event?> ();
119
120 var sql_event_ids = database.get_sql_string_from_event_ids (event_ids);
121 string sql = """
122 SELECT * FROM event_view
123 WHERE id IN (%s)
124 """.printf (sql_event_ids);
125
126 Sqlite.Statement stmt;
127 int rc = db.prepare_v2 (sql, -1, out stmt);
128 database.assert_query_success (rc, "SQL error");
129
130 var events = new HashTable<uint32, Event?> (direct_hash, direct_equal);
131
132 // Create Events and Subjects from rows
133 while ((rc = stmt.step ()) == Sqlite.ROW)
134 {
135 uint32 event_id = (uint32) stmt.column_int64 (EventViewRows.ID);
136 Event? event = events.lookup (event_id);
137 if (event == null)
138 {
139 event = get_event_from_row(stmt, event_id);
140 events.insert (event_id, event);
141 }
142 Subject subject = get_subject_from_row(stmt);
143 event.add_subject(subject);
144 }
145 if (rc != Sqlite.DONE)
146 {
147 throw new EngineError.DATABASE_ERROR ("Error: %d, %s\n",
148 rc, db.errmsg ());
149 }
150
151 // Sort events according to the sequence of event_ids
152 var results = new GenericArray<Event?> ();
153 results.length = event_ids.length;
154 int i = 0;
155 foreach (var id in event_ids)
156 {
157 results.set(i++, events.lookup (id));
158 }
159
160 return results;
161 }
162
163 public uint32[] find_event_ids (TimeRange time_range,
164 GenericArray<Event> event_templates,
165 uint storage_state, uint max_events, uint result_type,
166 BusName? sender=null) throws EngineError
167 {
168
169 WhereClause where = new WhereClause (WhereClause.Type.AND);
170
171 /**
172 * We are using the unary operator here to tell SQLite to not use
173 * the index on the timestamp column at the first place. This is a
174 * "fix" for (LP: #672965) based on some benchmarks, which suggest
175 * a performance win, but we might not oversee all implications.
176 * (See http://www.sqlite.org/optoverview.html, section 6.0).
177 * -- Markus Korn, 29/11/2010
178 */
179 if (time_range.start != 0)
180 where.add (("+timestamp >= %" + int64.FORMAT).printf(
181 time_range.start));
182 if (time_range.end != 0)
183 where.add (("+timestamp <= %" + int64.FORMAT).printf(
184 time_range.end));
185
186 if (storage_state == StorageState.AVAILABLE ||
187 storage_state == StorageState.NOT_AVAILABLE)
188 {
189 where.add ("(subj_storage_state=? OR subj_storage_state IS NULL)",
190 storage_state.to_string ());
191 }
192 else if (storage_state != StorageState.ANY)
193 {
194 throw new EngineError.INVALID_ARGUMENT(
195 "Unknown storage state '%u'".printf(storage_state));
196 }
197
198 WhereClause tpl_conditions = get_where_clause_from_event_templates (
199 event_templates);
200 where.extend (tpl_conditions);
201 //if (!where.may_have_results ())
202 // return new uint32[0];
203
204 string sql = "SELECT id FROM event_view ";
205 string where_sql = "";
206 if (!where.is_empty ())
207 {
208 where_sql = "WHERE " + where.get_sql_conditions ();
209 }
210
211 switch (result_type)
212 {
213 case ResultType.MOST_RECENT_EVENTS:
214 sql += where_sql + " ORDER BY timestamp DESC";
215 break;
216 case ResultType.LEAST_RECENT_EVENTS:
217 sql += where_sql + " ORDER BY timestamp ASC";
218 break;
219 case ResultType.MOST_RECENT_EVENT_ORIGIN:
220 sql += group_and_sort ("origin", where_sql, false);
221 break;
222 case ResultType.LEAST_RECENT_EVENT_ORIGIN:
223 sql += group_and_sort ("origin", where_sql, true);
224 break;
225 case ResultType.MOST_POPULAR_EVENT_ORIGIN:
226 sql += group_and_sort ("origin", where_sql, false, false);
227 break;
228 case ResultType.LEAST_POPULAR_EVENT_ORIGIN:
229 sql += group_and_sort ("origin", where_sql, true, true);
230 break;
231 case ResultType.MOST_RECENT_SUBJECTS:
232 sql += group_and_sort ("subj_id", where_sql, false);
233 break;
234 case ResultType.LEAST_RECENT_SUBJECTS:
235 sql += group_and_sort ("subj_id", where_sql, true);
236 break;
237 case ResultType.MOST_POPULAR_SUBJECTS:
238 sql += group_and_sort ("subj_id", where_sql, false, false);
239 break;
240 case ResultType.LEAST_POPULAR_SUBJECTS:
241 sql += group_and_sort ("subj_id", where_sql, true, true);
242 break;
243 case ResultType.MOST_RECENT_CURRENT_URI:
244 sql += group_and_sort ("subj_id_current", where_sql, false);
245 break;
246 case ResultType.LEAST_RECENT_CURRENT_URI:
247 sql += group_and_sort ("subj_id_current", where_sql, true);
248 break;
249 case ResultType.MOST_POPULAR_CURRENT_URI:
250 sql += group_and_sort ("subj_id_current", where_sql,
251 false, false);
252 break;
253 case ResultType.LEAST_POPULAR_CURRENT_URI:
254 sql += group_and_sort ("subj_id_current", where_sql,
255 true, true);
256 break;
257 case ResultType.MOST_RECENT_ACTOR:
258 sql += group_and_sort ("actor", where_sql, false);
259 break;
260 case ResultType.LEAST_RECENT_ACTOR:
261 sql += group_and_sort ("actor", where_sql, true);
262 break;
263 case ResultType.MOST_POPULAR_ACTOR:
264 sql += group_and_sort ("actor", where_sql, false, false);
265 break;
266 case ResultType.LEAST_POPULAR_ACTOR:
267 sql += group_and_sort ("actor", where_sql, true, true);
268 break;
269 case ResultType.OLDEST_ACTOR:
270 sql += group_and_sort ("actor", where_sql, true, null, "min");
271 break;
272 case ResultType.MOST_RECENT_ORIGIN:
273 sql += group_and_sort ("subj_origin", where_sql, false);
274 break;
275 case ResultType.LEAST_RECENT_ORIGIN:
276 sql += group_and_sort ("subj_origin", where_sql, true);
277 break;
278 case ResultType.MOST_POPULAR_ORIGIN:
279 sql += group_and_sort ("subj_origin", where_sql, false, false);
280 break;
281 case ResultType.LEAST_POPULAR_ORIGIN:
282 sql += group_and_sort ("subj_origin", where_sql, true, true);
283 break;
284 case ResultType.MOST_RECENT_SUBJECT_INTERPRETATION:
285 sql += group_and_sort ("subj_interpretation", where_sql, false);
286 break;
287 case ResultType.LEAST_RECENT_SUBJECT_INTERPRETATION:
288 sql += group_and_sort ("subj_interpretation", where_sql, true);
289 break;
290 case ResultType.MOST_POPULAR_SUBJECT_INTERPRETATION:
291 sql += group_and_sort ("subj_interpretation", where_sql,
292 false, false);
293 break;
294 case ResultType.LEAST_POPULAR_SUBJECT_INTERPRETATION:
295 sql += group_and_sort ("subj_interpretation", where_sql,
296 true, true);
297 break;
298 case ResultType.MOST_RECENT_MIMETYPE:
299 sql += group_and_sort ("subj_mimetype", where_sql, false);
300 break;
301 case ResultType.LEAST_RECENT_MIMETYPE:
302 sql += group_and_sort ("subj_mimetype", where_sql, true);
303 break;
304 case ResultType.MOST_POPULAR_MIMETYPE:
305 sql += group_and_sort ("subj_mimetype", where_sql,
306 false, false);
307 break;
308 case ResultType.LEAST_POPULAR_MIMETYPE:
309 sql += group_and_sort ("subj_mimetype", where_sql,
310 true, true);
311 break;
312 default:
313 string error_message = "Invalid ResultType.";
314 warning (error_message);
315 throw new EngineError.INVALID_ARGUMENT (error_message);
316 }
317
318 int rc;
319 Sqlite.Statement stmt;
320
321 rc = db.prepare_v2 (sql, -1, out stmt);
322 database.assert_query_success(rc, "SQL error");
323
324 var arguments = where.get_bind_arguments ();
325 for (int i = 0; i < arguments.length; ++i)
326 stmt.bind_text (i + 1, arguments[i]);
327
328#if EXPLAIN_QUERIES
329 database.explain_query (stmt);
330#endif
331
332 uint32[] event_ids = {};
333
334 while ((rc = stmt.step()) == Sqlite.ROW)
335 {
336 var event_id = (uint32) uint64.parse(
337 stmt.column_text (EventViewRows.ID));
338 // Events are supposed to be contiguous in the database
339 if (event_ids.length == 0 || event_ids[event_ids.length-1] != event_id) {
340 event_ids += event_id;
341 if (event_ids.length == max_events) break;
342 }
343 }
344 if (rc != Sqlite.DONE && rc != Sqlite.ROW)
345 {
346 string error_message = "Error in find_event_ids: %d, %s".printf (
347 rc, db.errmsg ());
348 warning (error_message);
349 throw new EngineError.DATABASE_ERROR (error_message);
350 }
351
352 return event_ids;
353 }
354
355 public GenericArray<Event?> find_events (TimeRange time_range,
356 GenericArray<Event> event_templates,
357 uint storage_state, uint max_events, uint result_type,
358 BusName? sender=null) throws EngineError
359 {
360 return get_events (find_event_ids (time_range, event_templates,
361 storage_state, max_events, result_type));
362 }
363
364 private struct RelatedUri {
365 public uint32 id;
366 public int64 timestamp;
367 public string uri;
368 public int32 counter;
369 }
370
371 public string[] find_related_uris (TimeRange time_range,
372 GenericArray<Event> event_templates,
373 GenericArray<Event> result_event_templates,
374 uint storage_state, uint max_results, uint result_type,
375 BusName? sender=null) throws EngineError
376 {
377 /**
378 * Return a list of subject URIs commonly used together with events
379 * matching the given template, considering data from within the
380 * indicated timerange.
381 * Only URIs for subjects matching the indicated `result_event_templates`
382 * and `result_storage_state` are returned.
383 */
384 if (result_type == ResultType.MOST_RECENT_EVENTS ||
385 result_type == ResultType.LEAST_RECENT_EVENTS)
386 {
387
388 // We pick out the ids for relational event so we can set them as
389 // roots the ids are taken from the events that match the
390 // events_templates
391 uint32[] ids = find_event_ids (time_range, event_templates,
392 storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
393
394 if (event_templates.length > 0 && ids.length == 0)
395 {
396 throw new EngineError.INVALID_ARGUMENT (
397 "No results found for the event_templates");
398 }
399
400 // Pick out the result_ids for the filtered results we would like to
401 // take into account the ids are taken from the events that match
402 // the result_event_templates if no result_event_templates are set we
403 // consider all results as allowed
404 uint32[] result_ids;
405 result_ids = find_event_ids (time_range, result_event_templates,
406 storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
407
408 // From here we create several graphs with the maximum depth of 2
409 // and push all the nodes and vertices (events) in one pot together
410
411 uint32[] pot = new uint32[ids.length + result_ids.length];
412
413 for (uint32 i=0; i < ids.length; i++)
414 pot[i] = ids[i];
415 for (uint32 i=0; i < result_ids.length; i++)
416 pot[ids.length + i] = result_ids[ids.length + i];
417
418 Sqlite.Statement stmt;
419
420 var sql_event_ids = database.get_sql_string_from_event_ids (pot);
421 string sql = """
422 SELECT id, timestamp, subj_uri FROM event_view
423 WHERE id IN (%s) ORDER BY timestamp ASC
424 """.printf (sql_event_ids);
425
426 int rc = db.prepare_v2 (sql, -1, out stmt);
427
428 database.assert_query_success(rc, "SQL error");
429
430 // FIXME: fix this ugly code
431 var temp_related_uris = new GenericArray<RelatedUri?>();
432
433 while ((rc = stmt.step()) == Sqlite.ROW)
434 {
435 RelatedUri ruri = RelatedUri(){
436 id = (uint32) uint64.parse(stmt.column_text (0)),
437 timestamp = stmt.column_int64 (1),
438 uri = stmt.column_text (2),
439 counter = 0
440 };
441 temp_related_uris.add (ruri);
442 }
443
444 // RelatedUri[] related_uris = new RelatedUri[temp_related_uris.length];
445 // for (int i=0; i<related_uris.length; i++)
446 // related_uris[i] = temp_related_uris[i];
447
448 if (rc != Sqlite.DONE)
449 {
450 string error_message =
451 "Error in find_related_uris: %d, %s".printf (
452 rc, db.errmsg ());
453 warning (error_message);
454 throw new EngineError.DATABASE_ERROR (error_message);
455 }
456
457 var uri_counter = new HashTable<string, RelatedUri?>(
458 str_hash, str_equal);
459
460 for (int i = 0; i < temp_related_uris.length; i++)
461 {
462 var window = new GenericArray<unowned RelatedUri?>();
463
464 bool count_in_window = false;
465 for (int j = int.max (0, i - 5);
466 j < int.min (i, temp_related_uris.length);
467 j++)
468 {
469 window.add(temp_related_uris[j]);
470 if (temp_related_uris[j].id in ids)
471 count_in_window = true;
472 }
473
474 if (count_in_window)
475 {
476 for (int j = 0; j < window.length; j++)
477 {
478 if (uri_counter.lookup (window[j].uri) == null)
479 {
480 RelatedUri ruri = RelatedUri ()
481 {
482 id = window[j].id,
483 timestamp = window[j].timestamp,
484 uri = window[j].uri,
485 counter = 0
486 };
487 uri_counter.insert (window[j].uri, ruri);
488 }
489 uri_counter.lookup (window[j].uri).counter++;
490 if (uri_counter.lookup (window[j].uri).timestamp
491 < window[j].timestamp)
492 {
493 uri_counter.lookup (window[j].uri).timestamp =
494 window[j].timestamp;
495 }
496 }
497 }
498 }
499
500
501 // We have the big hashtable with the structs, now we sort them by
502 // most used and limit the result then sort again
503 List<RelatedUri?> temp_ruris = new List<RelatedUri?>();
504 List<RelatedUri?> values = new List<RelatedUri?>();
505
506 foreach (var uri in uri_counter.get_values())
507 values.append(uri);
508
509 values.sort ((a, b) => a.counter - b.counter);
510 values.sort ((a, b) => {
511 int64 delta = a.timestamp - b.timestamp;
512 if (delta < 0) return 1;
513 else if (delta > 0) return -1;
514 else return 0;
515 });
516
517 foreach (RelatedUri ruri in values)
518 {
519 if (temp_ruris.length() < max_results)
520 temp_ruris.append(ruri);
521 else
522 break;
523 }
524
525 // Sort by recency
526 if (result_type == 1)
527 temp_ruris.sort ((a, b) => {
528 int64 delta = a.timestamp - b.timestamp;
529 if (delta < 0) return 1;
530 else if (delta > 0) return -1;
531 else return 0;});
532
533 string[] results = new string[temp_ruris.length()];
534
535 int i = 0;
536 foreach (var uri in temp_ruris)
537 {
538 results[i] = uri.uri;
539 stdout.printf("%i %lld %s\n", uri.counter,
540 uri.timestamp,
541 uri.uri);
542 i++;
543 }
544
545 return results;
546 }
547 else
548 {
549 throw new EngineError.DATABASE_ERROR ("Unsupported ResultType.");
550 }
551 }
552
553 /**
554 * Clear all resources Engine is using (close database connection, etc.).
555 *
556 * After executing this method on an instance, no other function
557 * may be called.
558 */
559 public virtual void close ()
560 {
561 database.close ();
562 }
563
564 // Used by find_event_ids
565 private string group_and_sort (string field, string where_sql,
566 bool time_asc=false, bool? count_asc=null,
567 string aggregation_type="max")
568 {
569 string time_sorting = (time_asc) ? "ASC" : "DESC";
570 string aggregation_sql = "";
571 string order_sql = "";
572
573 if (count_asc != null)
574 {
575 aggregation_sql = ", COUNT(%s) AS num_events".printf (field);
576 order_sql = "num_events %s,".printf ((count_asc) ? "ASC" : "DESC");
577 }
578
579 return """
580 NATURAL JOIN (
581 SELECT %s,
582 %s(timestamp) AS timestamp
583 %s
584 FROM event_view %s
585 GROUP BY %s)
586 GROUP BY %s
587 ORDER BY %s timestamp %s
588 """.printf (
589 field,
590 aggregation_type,
591 aggregation_sql,
592 where_sql,
593 field,
594 field,
595 order_sql, time_sorting);
596 }
597
598 // Used by find_event_ids
599 protected WhereClause get_where_clause_from_event_templates (
600 GenericArray<Event> templates) throws EngineError
601 {
602 WhereClause where = new WhereClause (WhereClause.Type.OR);
603 for (int i = 0; i < templates.length; ++i)
604 {
605 Event event_template = templates[i];
606 where.extend (
607 get_where_clause_from_event_template (event_template));
608 }
609 return where;
610 }
611
612 // Used by get_where_clause_from_event_templates
613 private WhereClause get_where_clause_from_event_template (Event template)
614 throws EngineError
615 {
616 WhereClause where = new WhereClause (WhereClause.Type.AND);
617
618 // Event ID
619 if (template.id != 0)
620 where.add ("id=?", template.id.to_string());
621
622 // Interpretation
623 if (!is_empty_string (template.interpretation))
624 {
625 assert_no_wildcard ("interpretation", template.interpretation);
626 WhereClause subwhere = get_where_clause_for_symbol (
627 "interpretation", template.interpretation,
628 interpretations_table);
629 if (!subwhere.is_empty ())
630 where.extend (subwhere);
631 }
632
633 // Manifestation
634 if (!is_empty_string (template.manifestation))
635 {
636 assert_no_wildcard ("manifestation", template.interpretation);
637 WhereClause subwhere = get_where_clause_for_symbol (
638 "manifestation", template.manifestation,
639 manifestations_table);
640 if (!subwhere.is_empty ())
641 where.extend (subwhere);
642 }
643
644 // Actor
645 if (!is_empty_string (template.actor))
646 {
647 string val = template.actor;
648 bool like = parse_wildcard (ref val);
649 bool negated = parse_negation (ref val);
650
651 if (like)
652 where.add_wildcard_condition ("actor", val, negated);
653 else
654 where.add_match_condition ("actor",
655 actors_table.get_id (val), negated);
656 }
657
658 // Origin
659 if (!is_empty_string (template.origin))
660 {
661 string val = template.origin;
662 bool like = parse_wildcard (ref val);
663 bool negated = parse_negation (ref val);
664 assert_no_noexpand (val, "origin");
665
666 if (like)
667 where.add_wildcard_condition ("origin", val, negated);
668 else
669 where.add_text_condition_subquery ("origin", val, negated);
670 }
671
672 // Subject templates within the same event template are AND'd
673 // See LP bug #592599.
674 for (int i = 0; i < template.num_subjects(); ++i)
675 {
676 Subject subject_template = template.subjects[i];
677
678 // Subject interpretation
679 if (!is_empty_string (subject_template.interpretation))
680 {
681 assert_no_wildcard ("subject interpretation",
682 template.interpretation);
683 WhereClause subwhere = get_where_clause_for_symbol (
684 "subj_interpretation", subject_template.interpretation,
685 interpretations_table);
686 if (!subwhere.is_empty ())
687 where.extend (subwhere);
688 }
689
690 // Subject manifestation
691 if (!is_empty_string (subject_template.manifestation))
692 {
693 assert_no_wildcard ("subject manifestation",
694 subject_template.manifestation);
695 WhereClause subwhere = get_where_clause_for_symbol (
696 "subj_manifestation", subject_template.manifestation,
697 manifestations_table);
698 if (!subwhere.is_empty ())
699 where.extend (subwhere);
700 }
701
702 // Mime-Type
703 if (!is_empty_string (subject_template.mimetype))
704 {
705 string val = subject_template.mimetype;
706 bool like = parse_wildcard (ref val);
707 bool negated = parse_negation (ref val);
708 assert_no_noexpand (val, "mime-type");
709
710 if (like)
711 where.add_wildcard_condition (
712 "subj_mimetype", val, negated);
713 else
714 where.add_match_condition ("subj_mimetype",
715 mimetypes_table.get_id (val), negated);
716 }
717
718 // URI
719 if (!is_empty_string (subject_template.uri))
720 {
721 string val = subject_template.uri;
722 bool like = parse_wildcard (ref val);
723 bool negated = parse_negation (ref val);
724 assert_no_noexpand (val, "uri");
725
726 if (like)
727 where.add_wildcard_condition ("subj_id", val, negated);
728 else
729 where.add_text_condition_subquery ("subj_id", val, negated);
730 }
731
732 // Origin
733 if (!is_empty_string (subject_template.origin))
734 {
735 string val = subject_template.origin;
736 bool like = parse_wildcard (ref val);
737 bool negated = parse_negation (ref val);
738 assert_no_noexpand (val, "subject origin");
739
740 if (like)
741 where.add_wildcard_condition (
742 "subj_origin", val, negated);
743 else
744 where.add_text_condition_subquery (
745 "subj_origin", val, negated);
746 }
747
748 // Text
749 if (!is_empty_string (subject_template.text))
750 {
751 // Negation, noexpand and prefix search aren't supported
752 // for subject texts, but "!", "+" and "*" are valid as
753 // plain text characters.
754 where.add_text_condition_subquery ("subj_text_id",
755 subject_template.text, false);
756 }
757
758 // Current URI
759 if (!is_empty_string (subject_template.current_uri))
760 {
761 string val = subject_template.current_uri;
762 bool like = parse_wildcard (ref val);
763 bool negated = parse_negation (ref val);
764 assert_no_noexpand (val, "current_uri");
765
766 if (like)
767 where.add_wildcard_condition (
768 "subj_id_current", val, negated);
769 else
770 where.add_text_condition_subquery (
771 "subj_id_current", val, negated);
772 }
773
774 // Subject storage
775 if (!is_empty_string (subject_template.storage))
776 {
777 string val = subject_template.storage;
778 assert_no_negation ("subject storage", val);
779 assert_no_wildcard ("subject storage", val);
780 assert_no_noexpand (val, "subject storage");
781 where.add_text_condition_subquery ("subj_storage_id", val);
782 }
783 }
784
785 return where;
786 }
787
788 // Used by get_where_clause_from_event_templates
789 /**
790 * If the value starts with the negation operator, throw an
791 * error.
792 */
793 protected void assert_no_negation (string field, string val)
794 throws EngineError
795 {
796 if (!val.has_prefix ("!"))
797 return;
798 string error_message =
799 "Field '%s' doesn't support negation".printf (field);
800 warning (error_message);
801 throw new EngineError.INVALID_ARGUMENT (error_message);
802 }
803
804 // Used by get_where_clause_from_event_templates
805 /**
806 * If the value starts with the negation operator, throw an
807 * error.
808 */
809 protected void assert_no_noexpand (string field, string val)
810 throws EngineError
811 {
812 if (!val.has_prefix ("+"))
813 return;
814 string error_message =
815 "Field '%s' doesn't support the no-expand operator".printf (field);
816 warning (error_message);
817 throw new EngineError.INVALID_ARGUMENT (error_message);
818 }
819
820 // Used by get_where_clause_from_event_templates
821 /**
822 * If the value ends with the wildcard character, throw an error.
823 */
824 protected void assert_no_wildcard (string field, string val)
825 throws EngineError
826 {
827 if (!val.has_suffix ("*"))
828 return;
829 string error_message =
830 "Field '%s' doesn't support prefix search".printf (field);
831 warning (error_message);
832 throw new EngineError.INVALID_ARGUMENT (error_message);
833 }
834
835 protected WhereClause get_where_clause_for_symbol (string table_name,
836 string symbol, TableLookup lookup_table) throws EngineError
837 {
838 string _symbol = symbol;
839 bool negated = parse_negation (ref _symbol);
840 bool noexpand = parse_noexpand (ref _symbol);
841 List<unowned string> symbols;
842 if (noexpand)
843 symbols = new List<unowned string> ();
844 else
845 symbols = Symbol.get_all_children (_symbol);
846 symbols.prepend (_symbol);
847
848 WhereClause subwhere = new WhereClause(
849 WhereClause.Type.OR, negated);
850
851 if (symbols.length () == 1)
852 {
853 subwhere.add_match_condition (table_name,
854 lookup_table.get_id (_symbol));
855 }
856 else
857 {
858 var sb = new StringBuilder ();
859 foreach (unowned string uri in symbols)
860 {
861 sb.append_printf ("%d,", lookup_table.get_id (uri));
862 }
863 sb.truncate (sb.len - 1);
864
865 string sql = "%s IN (%s)".printf(table_name, sb.str);
866 subwhere.add(sql);
867 }
868
869 return subwhere;
870 }
871
872 private void delete_from_cache (string table, int64 rowid)
873 {
874 TableLookup table_lookup;
875
876 if (table == "interpretation")
877 table_lookup = interpretations_table;
878 else if (table == "manifestation")
879 table_lookup = manifestations_table;
880 else if (table == "mimetype")
881 table_lookup = mimetypes_table;
882 else if (table == "actor")
883 table_lookup = actors_table;
884 else
885 return;
886
887 table_lookup.remove((int) rowid);
888 }
889
890}
891
892}
893
894// vim:expandtab:ts=4:sw=4
0895
=== modified file 'src/engine.vala'
--- src/engine.vala 2012-01-25 17:37:55 +0000
+++ src/engine.vala 2012-02-05 18:08:19 +0000
@@ -29,37 +29,28 @@
29using Zeitgeist.SQLite;29using Zeitgeist.SQLite;
3030
31namespace Zeitgeist31namespace Zeitgeist
32{ // FIXME: increase indentation once we're ok with breaking 'bzr diff'32{
3333
34public class Engine : Object34public class Engine : DbReader
35{35{
3636
37 public Zeitgeist.SQLite.ZeitgeistDatabase database { get; private set; }
38 public ExtensionStore extension_store;37 public ExtensionStore extension_store;
39 private ExtensionCollection extension_collection;38 private ExtensionCollection extension_collection;
40 private unowned Sqlite.Database db;
41
42 protected TableLookup interpretations_table;
43 protected TableLookup manifestations_table;
44 protected TableLookup mimetypes_table;
45 protected TableLookup actors_table;
4639
47 private uint32 last_id;40 private uint32 last_id;
4841
49 public Engine () throws EngineError42 public Engine () throws EngineError
50 {43 {
51 database = new Zeitgeist.SQLite.ZeitgeistDatabase ();44 Object (database: new Zeitgeist.SQLite.Database ());
52 database.set_deletion_callback (delete_from_cache);45
53 db = database.database;46 // TODO: take care of this if we decide to subclass Engine
54 last_id = database.get_last_id ();47 last_id = database.get_last_id ();
5548 extension_collection = new ExtensionCollection (this);
56 interpretations_table = new TableLookup (database, "interpretation");49 }
57 manifestations_table = new TableLookup (database, "manifestation");50
58 mimetypes_table = new TableLookup (database, "mimetype");51 construct
59 actors_table = new TableLookup (database, "actor");52 {
60
61 extension_store = new ExtensionStore (this);53 extension_store = new ExtensionStore (this);
62 extension_collection = new ExtensionCollection (this);
63 }54 }
6455
65 public string[] get_extension_names ()56 public string[] get_extension_names ()
@@ -67,495 +58,6 @@
67 return extension_collection.get_extension_names ();58 return extension_collection.get_extension_names ();
68 }59 }
6960
70 private Event get_event_from_row (Sqlite.Statement stmt, uint32 event_id)
71 {
72 Event event = new Event ();
73 event.id = event_id;
74 event.timestamp = stmt.column_int64 (EventViewRows.TIMESTAMP);
75 event.interpretation = interpretations_table.get_value (
76 stmt.column_int (EventViewRows.INTERPRETATION));
77 event.manifestation = manifestations_table.get_value (
78 stmt.column_int (EventViewRows.MANIFESTATION));
79 event.actor = actors_table.get_value (
80 stmt.column_int (EventViewRows.ACTOR));
81 event.origin = stmt.column_text (
82 EventViewRows.EVENT_ORIGIN_URI);
83
84 // Load payload
85 unowned uint8[] data = (uint8[]) stmt.column_blob(
86 EventViewRows.PAYLOAD);
87 data.length = stmt.column_bytes(EventViewRows.PAYLOAD);
88 if (data != null)
89 {
90 event.payload = new ByteArray();
91 event.payload.append(data);
92 }
93 return event;
94 }
95
96 private Subject get_subject_from_row (Sqlite.Statement stmt)
97 {
98 Subject subject = new Subject ();
99 subject.uri = stmt.column_text (EventViewRows.SUBJECT_URI);
100 subject.text = stmt.column_text (EventViewRows.SUBJECT_TEXT);
101 subject.storage = stmt.column_text (EventViewRows.SUBJECT_STORAGE);
102 subject.origin = stmt.column_text (EventViewRows.SUBJECT_ORIGIN_URI);
103 subject.current_uri = stmt.column_text (
104 EventViewRows.SUBJECT_CURRENT_URI);
105 subject.interpretation = interpretations_table.get_value (
106 stmt.column_int (EventViewRows.SUBJECT_INTERPRETATION));
107 subject.manifestation = manifestations_table.get_value (
108 stmt.column_int (EventViewRows.SUBJECT_MANIFESTATION));
109 subject.mimetype = mimetypes_table.get_value (
110 stmt.column_int (EventViewRows.SUBJECT_MIMETYPE));
111 return subject;
112 }
113
114 public GenericArray<Event?> get_events(uint32[] event_ids,
115 BusName? sender=null) throws EngineError
116 {
117 // TODO: Consider if we still want the cache. This should be done
118 // once everything is working, since it adds unneeded complexity.
119 // It'd also benchmark it again first, we may have better options
120 // to enhance the performance of SQLite now, and event processing
121 // will be faster now being C.
122
123 if (event_ids.length == 0)
124 return new GenericArray<Event?> ();
125
126 var sql_event_ids = database.get_sql_string_from_event_ids (event_ids);
127 string sql = """
128 SELECT * FROM event_view
129 WHERE id IN (%s)
130 """.printf (sql_event_ids);
131
132 Sqlite.Statement stmt;
133 int rc = db.prepare_v2 (sql, -1, out stmt);
134 database.assert_query_success (rc, "SQL error");
135
136 var events = new HashTable<uint32, Event?> (direct_hash, direct_equal);
137
138 // Create Events and Subjects from rows
139 while ((rc = stmt.step ()) == Sqlite.ROW)
140 {
141 uint32 event_id = (uint32) stmt.column_int64 (EventViewRows.ID);
142 Event? event = events.lookup (event_id);
143 if (event == null)
144 {
145 event = get_event_from_row(stmt, event_id);
146 events.insert (event_id, event);
147 }
148 Subject subject = get_subject_from_row(stmt);
149 event.add_subject(subject);
150 }
151 if (rc != Sqlite.DONE)
152 {
153 throw new EngineError.DATABASE_ERROR ("Error: %d, %s\n",
154 rc, db.errmsg ());
155 }
156
157 // Sort events according to the sequence of event_ids
158 var results = new GenericArray<Event?> ();
159 results.length = event_ids.length;
160 int i = 0;
161 foreach (var id in event_ids)
162 {
163 results.set(i++, events.lookup (id));
164 }
165
166 return results;
167 }
168
169 public uint32[] find_event_ids (TimeRange time_range,
170 GenericArray<Event> event_templates,
171 uint storage_state, uint max_events, uint result_type,
172 BusName? sender=null) throws EngineError
173 {
174
175 WhereClause where = new WhereClause (WhereClause.Type.AND);
176
177 /**
178 * We are using the unary operator here to tell SQLite to not use
179 * the index on the timestamp column at the first place. This is a
180 * "fix" for (LP: #672965) based on some benchmarks, which suggest
181 * a performance win, but we might not oversee all implications.
182 * (See http://www.sqlite.org/optoverview.html, section 6.0).
183 * -- Markus Korn, 29/11/2010
184 */
185 if (time_range.start != 0)
186 where.add (("+timestamp >= %" + int64.FORMAT).printf(
187 time_range.start));
188 if (time_range.end != 0)
189 where.add (("+timestamp <= %" + int64.FORMAT).printf(
190 time_range.end));
191
192 if (storage_state == StorageState.AVAILABLE ||
193 storage_state == StorageState.NOT_AVAILABLE)
194 {
195 where.add ("(subj_storage_state=? OR subj_storage_state IS NULL)",
196 storage_state.to_string ());
197 }
198 else if (storage_state != StorageState.ANY)
199 {
200 throw new EngineError.INVALID_ARGUMENT(
201 "Unknown storage state '%u'".printf(storage_state));
202 }
203
204 WhereClause tpl_conditions = get_where_clause_from_event_templates (
205 event_templates);
206 where.extend (tpl_conditions);
207 //if (!where.may_have_results ())
208 // return new uint32[0];
209
210 string sql = "SELECT id FROM event_view ";
211 string where_sql = "";
212 if (!where.is_empty ())
213 {
214 where_sql = "WHERE " + where.get_sql_conditions ();
215 }
216
217 switch (result_type)
218 {
219 case ResultType.MOST_RECENT_EVENTS:
220 sql += where_sql + " ORDER BY timestamp DESC";
221 break;
222 case ResultType.LEAST_RECENT_EVENTS:
223 sql += where_sql + " ORDER BY timestamp ASC";
224 break;
225 case ResultType.MOST_RECENT_EVENT_ORIGIN:
226 sql += group_and_sort ("origin", where_sql, false);
227 break;
228 case ResultType.LEAST_RECENT_EVENT_ORIGIN:
229 sql += group_and_sort ("origin", where_sql, true);
230 break;
231 case ResultType.MOST_POPULAR_EVENT_ORIGIN:
232 sql += group_and_sort ("origin", where_sql, false, false);
233 break;
234 case ResultType.LEAST_POPULAR_EVENT_ORIGIN:
235 sql += group_and_sort ("origin", where_sql, true, true);
236 break;
237 case ResultType.MOST_RECENT_SUBJECTS:
238 sql += group_and_sort ("subj_id", where_sql, false);
239 break;
240 case ResultType.LEAST_RECENT_SUBJECTS:
241 sql += group_and_sort ("subj_id", where_sql, true);
242 break;
243 case ResultType.MOST_POPULAR_SUBJECTS:
244 sql += group_and_sort ("subj_id", where_sql, false, false);
245 break;
246 case ResultType.LEAST_POPULAR_SUBJECTS:
247 sql += group_and_sort ("subj_id", where_sql, true, true);
248 break;
249 case ResultType.MOST_RECENT_CURRENT_URI:
250 sql += group_and_sort ("subj_id_current", where_sql, false);
251 break;
252 case ResultType.LEAST_RECENT_CURRENT_URI:
253 sql += group_and_sort ("subj_id_current", where_sql, true);
254 break;
255 case ResultType.MOST_POPULAR_CURRENT_URI:
256 sql += group_and_sort ("subj_id_current", where_sql,
257 false, false);
258 break;
259 case ResultType.LEAST_POPULAR_CURRENT_URI:
260 sql += group_and_sort ("subj_id_current", where_sql,
261 true, true);
262 break;
263 case ResultType.MOST_RECENT_ACTOR:
264 sql += group_and_sort ("actor", where_sql, false);
265 break;
266 case ResultType.LEAST_RECENT_ACTOR:
267 sql += group_and_sort ("actor", where_sql, true);
268 break;
269 case ResultType.MOST_POPULAR_ACTOR:
270 sql += group_and_sort ("actor", where_sql, false, false);
271 break;
272 case ResultType.LEAST_POPULAR_ACTOR:
273 sql += group_and_sort ("actor", where_sql, true, true);
274 break;
275 case ResultType.OLDEST_ACTOR:
276 sql += group_and_sort ("actor", where_sql, true, null, "min");
277 break;
278 case ResultType.MOST_RECENT_ORIGIN:
279 sql += group_and_sort ("subj_origin", where_sql, false);
280 break;
281 case ResultType.LEAST_RECENT_ORIGIN:
282 sql += group_and_sort ("subj_origin", where_sql, true);
283 break;
284 case ResultType.MOST_POPULAR_ORIGIN:
285 sql += group_and_sort ("subj_origin", where_sql, false, false);
286 break;
287 case ResultType.LEAST_POPULAR_ORIGIN:
288 sql += group_and_sort ("subj_origin", where_sql, true, true);
289 break;
290 case ResultType.MOST_RECENT_SUBJECT_INTERPRETATION:
291 sql += group_and_sort ("subj_interpretation", where_sql, false);
292 break;
293 case ResultType.LEAST_RECENT_SUBJECT_INTERPRETATION:
294 sql += group_and_sort ("subj_interpretation", where_sql, true);
295 break;
296 case ResultType.MOST_POPULAR_SUBJECT_INTERPRETATION:
297 sql += group_and_sort ("subj_interpretation", where_sql,
298 false, false);
299 break;
300 case ResultType.LEAST_POPULAR_SUBJECT_INTERPRETATION:
301 sql += group_and_sort ("subj_interpretation", where_sql,
302 true, true);
303 break;
304 case ResultType.MOST_RECENT_MIMETYPE:
305 sql += group_and_sort ("subj_mimetype", where_sql, false);
306 break;
307 case ResultType.LEAST_RECENT_MIMETYPE:
308 sql += group_and_sort ("subj_mimetype", where_sql, true);
309 break;
310 case ResultType.MOST_POPULAR_MIMETYPE:
311 sql += group_and_sort ("subj_mimetype", where_sql,
312 false, false);
313 break;
314 case ResultType.LEAST_POPULAR_MIMETYPE:
315 sql += group_and_sort ("subj_mimetype", where_sql,
316 true, true);
317 break;
318 default:
319 string error_message = "Invalid ResultType.";
320 warning (error_message);
321 throw new EngineError.INVALID_ARGUMENT (error_message);
322 }
323
324 int rc;
325 Sqlite.Statement stmt;
326
327 rc = db.prepare_v2 (sql, -1, out stmt);
328 database.assert_query_success(rc, "SQL error");
329
330 var arguments = where.get_bind_arguments ();
331 for (int i = 0; i < arguments.length; ++i)
332 stmt.bind_text (i + 1, arguments[i]);
333
334#if EXPLAIN_QUERIES
335 database.explain_query (stmt);
336#endif
337
338 uint32[] event_ids = {};
339
340 while ((rc = stmt.step()) == Sqlite.ROW)
341 {
342 var event_id = (uint32) uint64.parse(
343 stmt.column_text (EventViewRows.ID));
344 // Events are supposed to be contiguous in the database
345 if (event_ids.length == 0 || event_ids[event_ids.length-1] != event_id) {
346 event_ids += event_id;
347 if (event_ids.length == max_events) break;
348 }
349 }
350 if (rc != Sqlite.DONE && rc != Sqlite.ROW)
351 {
352 string error_message = "Error in find_event_ids: %d, %s".printf (
353 rc, db.errmsg ());
354 warning (error_message);
355 throw new EngineError.DATABASE_ERROR (error_message);
356 }
357
358 return event_ids;
359 }
360
361 public GenericArray<Event?> find_events (TimeRange time_range,
362 GenericArray<Event> event_templates,
363 uint storage_state, uint max_events, uint result_type,
364 BusName? sender=null) throws EngineError
365 {
366 return get_events (find_event_ids (time_range, event_templates,
367 storage_state, max_events, result_type));
368 }
369
370 private struct RelatedUri {
371 public uint32 id;
372 public int64 timestamp;
373 public string uri;
374 public int32 counter;
375 }
376
377 public string[] find_related_uris (TimeRange time_range,
378 GenericArray<Event> event_templates,
379 GenericArray<Event> result_event_templates,
380 uint storage_state, uint max_results, uint result_type,
381 BusName? sender=null) throws EngineError
382 {
383 /**
384 * Return a list of subject URIs commonly used together with events
385 * matching the given template, considering data from within the
386 * indicated timerange.
387 * Only URIs for subjects matching the indicated `result_event_templates`
388 * and `result_storage_state` are returned.
389 */
390 if (result_type == ResultType.MOST_RECENT_EVENTS ||
391 result_type == ResultType.LEAST_RECENT_EVENTS)
392 {
393
394 // We pick out the ids for relational event so we can set them as
395 // roots the ids are taken from the events that match the
396 // events_templates
397 uint32[] ids = find_event_ids (time_range, event_templates,
398 storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
399
400 if (event_templates.length > 0 && ids.length == 0)
401 {
402 throw new EngineError.INVALID_ARGUMENT (
403 "No results found for the event_templates");
404 }
405
406 // Pick out the result_ids for the filtered results we would like to
407 // take into account the ids are taken from the events that match
408 // the result_event_templates if no result_event_templates are set we
409 // consider all results as allowed
410 uint32[] result_ids;
411 result_ids = find_event_ids (time_range, result_event_templates,
412 storage_state, 0, ResultType.LEAST_RECENT_EVENTS);
413
414 // From here we create several graphs with the maximum depth of 2
415 // and push all the nodes and vertices (events) in one pot together
416
417 uint32[] pot = new uint32[ids.length + result_ids.length];
418
419 for (uint32 i=0; i < ids.length; i++)
420 pot[i] = ids[i];
421 for (uint32 i=0; i < result_ids.length; i++)
422 pot[ids.length + i] = result_ids[ids.length + i];
423
424 Sqlite.Statement stmt;
425
426 var sql_event_ids = database.get_sql_string_from_event_ids (pot);
427 string sql = """
428 SELECT id, timestamp, subj_uri FROM event_view
429 WHERE id IN (%s) ORDER BY timestamp ASC
430 """.printf (sql_event_ids);
431
432 int rc = db.prepare_v2 (sql, -1, out stmt);
433
434 database.assert_query_success(rc, "SQL error");
435
436 // FIXME: fix this ugly code
437 var temp_related_uris = new GenericArray<RelatedUri?>();
438
439 while ((rc = stmt.step()) == Sqlite.ROW)
440 {
441 RelatedUri ruri = RelatedUri(){
442 id = (uint32) uint64.parse(stmt.column_text (0)),
443 timestamp = stmt.column_int64 (1),
444 uri = stmt.column_text (2),
445 counter = 0
446 };
447 temp_related_uris.add (ruri);
448 }
449
450 // RelatedUri[] related_uris = new RelatedUri[temp_related_uris.length];
451 // for (int i=0; i<related_uris.length; i++)
452 // related_uris[i] = temp_related_uris[i];
453
454 if (rc != Sqlite.DONE)
455 {
456 string error_message =
457 "Error in find_related_uris: %d, %s".printf (
458 rc, db.errmsg ());
459 warning (error_message);
460 throw new EngineError.DATABASE_ERROR (error_message);
461 }
462
463 var uri_counter = new HashTable<string, RelatedUri?>(
464 str_hash, str_equal);
465
466 for (int i = 0; i < temp_related_uris.length; i++)
467 {
468 var window = new GenericArray<unowned RelatedUri?>();
469
470 bool count_in_window = false;
471 for (int j = int.max (0, i - 5);
472 j < int.min (i, temp_related_uris.length);
473 j++)
474 {
475 window.add(temp_related_uris[j]);
476 if (temp_related_uris[j].id in ids)
477 count_in_window = true;
478 }
479
480 if (count_in_window)
481 {
482 for (int j = 0; j < window.length; j++)
483 {
484 if (uri_counter.lookup (window[j].uri) == null)
485 {
486 RelatedUri ruri = RelatedUri ()
487 {
488 id = window[j].id,
489 timestamp = window[j].timestamp,
490 uri = window[j].uri,
491 counter = 0
492 };
493 uri_counter.insert (window[j].uri, ruri);
494 }
495 uri_counter.lookup (window[j].uri).counter++;
496 if (uri_counter.lookup (window[j].uri).timestamp
497 < window[j].timestamp)
498 {
499 uri_counter.lookup (window[j].uri).timestamp =
500 window[j].timestamp;
501 }
502 }
503 }
504 }
505
506
507 // We have the big hashtable with the structs, now we sort them by
508 // most used and limit the result then sort again
509 List<RelatedUri?> temp_ruris = new List<RelatedUri?>();
510 List<RelatedUri?> values = new List<RelatedUri?>();
511
512 foreach (var uri in uri_counter.get_values())
513 values.append(uri);
514
515 values.sort ((a, b) => a.counter - b.counter);
516 values.sort ((a, b) => {
517 int64 delta = a.timestamp - b.timestamp;
518 if (delta < 0) return 1;
519 else if (delta > 0) return -1;
520 else return 0;
521 });
522
523 foreach (RelatedUri ruri in values)
524 {
525 if (temp_ruris.length() < max_results)
526 temp_ruris.append(ruri);
527 else
528 break;
529 }
530
531 // Sort by recency
532 if (result_type == 1)
533 temp_ruris.sort ((a, b) => {
534 int64 delta = a.timestamp - b.timestamp;
535 if (delta < 0) return 1;
536 else if (delta > 0) return -1;
537 else return 0;});
538
539 string[] results = new string[temp_ruris.length()];
540
541 int i = 0;
542 foreach (var uri in temp_ruris)
543 {
544 results[i] = uri.uri;
545 stdout.printf("%i %lld %s\n", uri.counter,
546 uri.timestamp,
547 uri.uri);
548 i++;
549 }
550
551 return results;
552 }
553 else
554 {
555 throw new EngineError.DATABASE_ERROR ("Unsupported ResultType.");
556 }
557 }
558
559 public uint32[] insert_events (GenericArray<Event> events,61 public uint32[] insert_events (GenericArray<Event> events,
560 BusName? sender=null) throws EngineError62 BusName? sender=null) throws EngineError
561 {63 {
@@ -786,366 +288,14 @@
786 * After executing this method on an Engine instance, no other function288 * After executing this method on an Engine instance, no other function
787 * of said instance may be called.289 * of said instance may be called.
788 */290 */
789 public void close ()291 public override void close ()
790 {292 {
791 // We delete the ExtensionCollection here so that it unloads293 // We delete the ExtensionCollection here so that it unloads
792 // all extensions and they get a chance to access the database294 // all extensions and they get a chance to access the database
793 // (including through ExtensionStore) before it's closed.295 // (including through ExtensionStore) before it's closed.
794 extension_collection = null;296 extension_collection = null;
795 database.close ();297
796 }298 base.close ();
797
798 // Used by find_event_ids
799 private string group_and_sort (string field, string where_sql,
800 bool time_asc=false, bool? count_asc=null,
801 string aggregation_type="max")
802 {
803 string time_sorting = (time_asc) ? "ASC" : "DESC";
804 string aggregation_sql = "";
805 string order_sql = "";
806
807 if (count_asc != null)
808 {
809 aggregation_sql = ", COUNT(%s) AS num_events".printf (field);
810 order_sql = "num_events %s,".printf ((count_asc) ? "ASC" : "DESC");
811 }
812
813 return """
814 NATURAL JOIN (
815 SELECT %s,
816 %s(timestamp) AS timestamp
817 %s
818 FROM event_view %s
819 GROUP BY %s)
820 GROUP BY %s
821 ORDER BY %s timestamp %s
822 """.printf (
823 field,
824 aggregation_type,
825 aggregation_sql,
826 where_sql,
827 field,
828 field,
829 order_sql, time_sorting);
830 }
831
832 // Used by find_event_ids
833 private WhereClause get_where_clause_from_event_templates (
834 GenericArray<Event> templates) throws EngineError
835 {
836 WhereClause where = new WhereClause (WhereClause.Type.OR);
837 for (int i = 0; i < templates.length; ++i)
838 {
839 Event event_template = templates[i];
840 where.extend (
841 get_where_clause_from_event_template (event_template));
842 }
843 return where;
844 }
845
846 // Used by get_where_clause_from_event_templates
847 private WhereClause get_where_clause_from_event_template (Event template)
848 throws EngineError
849 {
850 WhereClause where = new WhereClause (WhereClause.Type.AND);
851
852 // Event ID
853 if (template.id != 0)
854 where.add ("id=?", template.id.to_string());
855
856 // Interpretation
857 if (template.interpretation != "")
858 {
859 assert_no_wildcard ("interpretation", template.interpretation);
860 WhereClause subwhere = get_where_clause_for_symbol (
861 "interpretation", template.interpretation,
862 interpretations_table);
863 if (!subwhere.is_empty ())
864 where.extend (subwhere);
865 }
866
867 // Manifestation
868 if (template.manifestation != "")
869 {
870 assert_no_wildcard ("manifestation", template.interpretation);
871 WhereClause subwhere = get_where_clause_for_symbol (
872 "manifestation", template.manifestation,
873 manifestations_table);
874 if (!subwhere.is_empty ())
875 where.extend (subwhere);
876 }
877
878 // Actor
879 if (template.actor != "")
880 {
881 string val = template.actor;
882 bool like = parse_wildcard (ref val);
883 bool negated = parse_negation (ref val);
884
885 if (like)
886 where.add_wildcard_condition ("actor", val, negated);
887 else
888 where.add_match_condition ("actor",
889 actors_table.get_id (val), negated);
890 }
891
892 // Origin
893 if (template.origin != "")
894 {
895 string val = template.origin;
896 bool like = parse_wildcard (ref val);
897 bool negated = parse_negation (ref val);
898 assert_no_noexpand (val, "origin");
899
900 if (like)
901 where.add_wildcard_condition ("origin", val, negated);
902 else
903 where.add_text_condition_subquery ("origin", val, negated);
904 }
905
906 // Subject templates within the same event template are AND'd
907 // See LP bug #592599.
908 for (int i = 0; i < template.num_subjects(); ++i)
909 {
910 Subject subject_template = template.subjects[i];
911
912 // Subject interpretation
913 if (subject_template.interpretation != "")
914 {
915 assert_no_wildcard ("subject interpretation",
916 template.interpretation);
917 WhereClause subwhere = get_where_clause_for_symbol (
918 "subj_interpretation", subject_template.interpretation,
919 interpretations_table);
920 if (!subwhere.is_empty ())
921 where.extend (subwhere);
922 }
923
924 // Subject manifestation
925 if (subject_template.manifestation != "")
926 {
927 assert_no_wildcard ("subject manifestation",
928 subject_template.manifestation);
929 WhereClause subwhere = get_where_clause_for_symbol (
930 "subj_manifestation", subject_template.manifestation,
931 manifestations_table);
932 if (!subwhere.is_empty ())
933 where.extend (subwhere);
934 }
935
936 // Mime-Type
937 if (subject_template.mimetype != "")
938 {
939 string val = subject_template.mimetype;
940 bool like = parse_wildcard (ref val);
941 bool negated = parse_negation (ref val);
942 assert_no_noexpand (val, "mime-type");
943
944 if (like)
945 where.add_wildcard_condition (
946 "subj_mimetype", val, negated);
947 else
948 where.add_match_condition ("subj_mimetype",
949 mimetypes_table.get_id (val), negated);
950 }
951
952 // URI
953 if (subject_template.uri != "")
954 {
955 string val = subject_template.uri;
956 bool like = parse_wildcard (ref val);
957 bool negated = parse_negation (ref val);
958 assert_no_noexpand (val, "uri");
959
960 if (like)
961 where.add_wildcard_condition ("subj_id", val, negated);
962 else
963 where.add_text_condition_subquery ("subj_id", val, negated);
964 }
965
966 // Origin
967 if (subject_template.origin != "")
968 {
969 string val = subject_template.origin;
970 bool like = parse_wildcard (ref val);
971 bool negated = parse_negation (ref val);
972 assert_no_noexpand (val, "subject origin");
973
974 if (like)
975 where.add_wildcard_condition (
976 "subj_origin", val, negated);
977 else
978 where.add_text_condition_subquery (
979 "subj_origin", val, negated);
980 }
981
982 // Text
983 if (subject_template.text != "")
984 {
985 // Negation, noexpand and prefix search aren't supported
986 // for subject texts, but "!", "+" and "*" are valid as
987 // plain text characters.
988 where.add_text_condition_subquery ("subj_text_id",
989 subject_template.text, false);
990 }
991
992 // Current URI
993 if (subject_template.current_uri != "")
994 {
995 string val = subject_template.current_uri;
996 bool like = parse_wildcard (ref val);
997 bool negated = parse_negation (ref val);
998 assert_no_noexpand (val, "current_uri");
999
1000 if (like)
1001 where.add_wildcard_condition (
1002 "subj_id_current", val, negated);
1003 else
1004 where.add_text_condition_subquery (
1005 "subj_id_current", val, negated);
1006 }
1007
1008 // Subject storage
1009 if (subject_template.storage != "")
1010 {
1011 string val = subject_template.storage;
1012 assert_no_negation ("subject storage", val);
1013 assert_no_wildcard ("subject storage", val);
1014 assert_no_noexpand (val, "subject storage");
1015 where.add_text_condition_subquery ("subj_storage_id", val);
1016 }
1017 }
1018
1019 return where;
1020 }
1021
1022 // Used by get_where_clause_from_event_templates
1023 /**
1024 * Check if the value starts with the negation operator. If it does,
1025 * remove the operator from the value and return true. Otherwise,
1026 * return false.
1027 */
1028 public static bool parse_negation (ref string val)
1029 {
1030 if (!val.has_prefix ("!"))
1031 return false;
1032 val = val.substring (1);
1033 return true;
1034 }
1035
1036 // Used by get_where_clause_from_event_templates
1037 /**
1038 * If the value starts with the negation operator, throw an
1039 * error.
1040 */
1041 protected void assert_no_negation (string field, string val)
1042 throws EngineError
1043 {
1044 if (!val.has_prefix ("!"))
1045 return;
1046 string error_message =
1047 "Field '%s' doesn't support negation".printf (field);
1048 warning (error_message);
1049 throw new EngineError.INVALID_ARGUMENT (error_message);
1050 }
1051
1052 // Used by get_where_clause_from_event_templates
1053 /**
1054 * Check if the value starts with the noexpand operator. If it does,
1055 * remove the operator from the value and return true. Otherwise,
1056 * return false.
1057 *
1058 * Check for the negation operator before calling this function.
1059 */
1060 public static bool parse_noexpand (ref string val)
1061 {
1062 if (!val.has_prefix ("+"))
1063 return false;
1064 val = val.substring (1);
1065 return true;
1066 }
1067
1068 // Used by get_where_clause_from_event_templates
1069 /**
1070 * If the value starts with the negation operator, throw an
1071 * error.
1072 */
1073 protected void assert_no_noexpand (string field, string val)
1074 throws EngineError
1075 {
1076 if (!val.has_prefix ("+"))
1077 return;
1078 string error_message =
1079 "Field '%s' doesn't support the no-expand operator".printf (field);
1080 warning (error_message);
1081 throw new EngineError.INVALID_ARGUMENT (error_message);
1082 }
1083
1084 // Used by get_where_clause_from_event_templates
1085 /**
1086 * Check if the value ends with the wildcard character. If it does,
1087 * remove the wildcard character from the value and return true.
1088 * Otherwise, return false.
1089 */
1090 public static bool parse_wildcard (ref string val)
1091 {
1092 if (!val.has_suffix ("*"))
1093 return false;
1094 unowned uint8[] val_data = val.data;
1095 val_data[val_data.length-1] = '\0';
1096 return true;
1097 }
1098
1099 // Used by get_where_clause_from_event_templates
1100 /**
1101 * If the value ends with the wildcard character, throw an error.
1102 */
1103 protected void assert_no_wildcard (string field, string val)
1104 throws EngineError
1105 {
1106 if (!val.has_suffix ("*"))
1107 return;
1108 string error_message =
1109 "Field '%s' doesn't support prefix search".printf (field);
1110 warning (error_message);
1111 throw new EngineError.INVALID_ARGUMENT (error_message);
1112 }
1113
1114 protected WhereClause get_where_clause_for_symbol (string table_name,
1115 string symbol, TableLookup lookup_table) throws EngineError
1116 {
1117 string _symbol = symbol;
1118 bool negated = parse_negation (ref _symbol);
1119 bool noexpand = parse_noexpand (ref _symbol);
1120 List<unowned string> symbols;
1121 if (noexpand)
1122 symbols = new List<unowned string> ();
1123 else
1124 symbols = Symbol.get_all_children (_symbol);
1125 symbols.prepend (_symbol);
1126
1127 WhereClause subwhere = new WhereClause(
1128 WhereClause.Type.OR, negated);
1129
1130 if (symbols.length () == 1)
1131 {
1132 subwhere.add_match_condition (table_name,
1133 lookup_table.get_id (_symbol));
1134 }
1135 else
1136 {
1137 var sb = new StringBuilder ();
1138 foreach (unowned string uri in symbols)
1139 {
1140 sb.append_printf ("%d,", lookup_table.get_id (uri));
1141 }
1142 sb.truncate (sb.len - 1);
1143
1144 string sql = "%s IN (%s)".printf(table_name, sb.str);
1145 subwhere.add(sql);
1146 }
1147
1148 return subwhere;
1149 }299 }
1150300
1151 private void handle_move_event (Event event)301 private void handle_move_event (Event event)
@@ -1194,24 +344,6 @@
1194 return 0;344 return 0;
1195 }345 }
1196346
1197 private void delete_from_cache (string table, int64 rowid)
1198 {
1199 TableLookup table_lookup;
1200
1201 if (table == "interpretation")
1202 table_lookup = interpretations_table;
1203 else if (table == "manifestation")
1204 table_lookup = manifestations_table;
1205 else if (table == "mimetype")
1206 table_lookup = mimetypes_table;
1207 else if (table == "actor")
1208 table_lookup = actors_table;
1209 else
1210 return;
1211
1212 table_lookup.remove((int) rowid);
1213 }
1214
1215}347}
1216348
1217}349}
1218350
=== modified file 'src/extension-store.vala'
--- src/extension-store.vala 2012-01-26 10:08:09 +0000
+++ src/extension-store.vala 2012-02-05 18:08:19 +0000
@@ -25,7 +25,7 @@
25 public class ExtensionStore : Object25 public class ExtensionStore : Object
26 {26 {
2727
28 private Zeitgeist.SQLite.ZeitgeistDatabase database;28 private Zeitgeist.SQLite.Database database;
29 private unowned Sqlite.Database db;29 private unowned Sqlite.Database db;
30 private Sqlite.Statement storage_stmt;30 private Sqlite.Statement storage_stmt;
31 private Sqlite.Statement retrieval_stmt;31 private Sqlite.Statement retrieval_stmt;
3232
=== modified file 'src/remote.vala'
--- src/remote.vala 2011-11-29 16:04:59 +0000
+++ src/remote.vala 2012-02-05 18:08:19 +0000
@@ -114,12 +114,13 @@
114 [DBus (name = "org.gnome.zeitgeist.Index")]114 [DBus (name = "org.gnome.zeitgeist.Index")]
115 public interface RemoteSimpleIndexer : Object115 public interface RemoteSimpleIndexer : Object
116 {116 {
117 [DBus (signature = "a(asaasay)u")]117 public abstract async void search (
118 public abstract async Variant search (
119 string query_string,118 string query_string,
120 [DBus (signature = "(xx)")] Variant time_range,119 [DBus (signature = "(xx)")] Variant time_range,
121 [DBus (signature = "a(asaasay)")] Variant filter_templates,120 [DBus (signature = "a(asaasay)")] Variant filter_templates,
122 uint offset, uint count, uint result_type) throws Error;121 uint offset, uint count, uint result_type,
122 [DBus (signature = "a(asaasay)")] out Variant events,
123 out uint matches) throws Error;
123 }124 }
124 125
125 /* FIXME: Remove this! Only here because of a bug in Vala (see ext-fts) */126 /* FIXME: Remove this! Only here because of a bug in Vala (see ext-fts) */
126127
=== modified file 'src/sql-schema.vala'
--- src/sql-schema.vala 2012-01-30 18:14:03 +0000
+++ src/sql-schema.vala 2012-02-05 18:08:19 +0000
@@ -70,7 +70,7 @@
70 }70 }
71 }71 }
7272
73 private static int get_schema_version (Sqlite.Database database)73 public static int get_schema_version (Sqlite.Database database)
74 {74 {
75 var sql = "SELECT version FROM schema_version WHERE schema='core'";75 var sql = "SELECT version FROM schema_version WHERE schema='core'";
76 int schema_version = -1;76 int schema_version = -1;
7777
=== modified file 'src/sql.vala'
--- src/sql.vala 2012-01-25 17:37:55 +0000
+++ src/sql.vala 2012-02-05 18:08:19 +0000
@@ -51,8 +51,10 @@
5151
52 public delegate void DeletionCallback (string table, int64 rowid);52 public delegate void DeletionCallback (string table, int64 rowid);
5353
54 public class ZeitgeistDatabase : Object54 public class Database : Object
55 {55 {
56 private const int DEFAULT_OPEN_FLAGS =
57 Sqlite.OPEN_READWRITE | Sqlite.OPEN_CREATE;
5658
57 public Sqlite.Statement event_insertion_stmt;59 public Sqlite.Statement event_insertion_stmt;
58 public Sqlite.Statement id_retrieval_stmt;60 public Sqlite.Statement id_retrieval_stmt;
@@ -64,12 +66,28 @@
64 public Sqlite.Database database;66 public Sqlite.Database database;
6567
66 private DeletionCallback? deletion_callback = null;68 private DeletionCallback? deletion_callback = null;
69 private bool is_read_only;
6770
68 public ZeitgeistDatabase () throws EngineError71 public Database () throws EngineError
69 {72 {
70 open_database (true);73 open_database (true);
7174
72 prepare_queries ();75 prepare_read_queries ();
76 prepare_modification_queries ();
77
78 // Register a data change notification callback to look for
79 // deletions, so we can keep the TableLookups up to date.
80 database.update_hook (update_callback);
81 }
82
83 public Database.read_only () throws EngineError
84 {
85 is_read_only = true;
86 open_database (false);
87
88 prepare_read_queries ();
89 // not initializing the modification queries will let us find
90 // issues more easily
7391
74 // Register a data change notification callback to look for92 // Register a data change notification callback to look for
75 // deletions, so we can keep the TableLookups up to date.93 // deletions, so we can keep the TableLookups up to date.
@@ -79,9 +97,10 @@
79 private void open_database (bool retry)97 private void open_database (bool retry)
80 throws EngineError98 throws EngineError
81 {99 {
100 int flags = is_read_only ? Sqlite.OPEN_READONLY : DEFAULT_OPEN_FLAGS;
82 int rc = Sqlite.Database.open_v2 (101 int rc = Sqlite.Database.open_v2 (
83 Utils.get_database_file_path (),102 Utils.get_database_file_path (),
84 out database);103 out database, flags);
85 104
86 if (rc == Sqlite.OK)105 if (rc == Sqlite.OK)
87 {106 {
@@ -89,7 +108,19 @@
89 {108 {
90 // Error (like a malformed database) may not be exposed109 // Error (like a malformed database) may not be exposed
91 // until we try to operate on the database.110 // until we try to operate on the database.
92 DatabaseSchema.ensure_schema (database);111 if (is_read_only)
112 {
113 int ver = DatabaseSchema.get_schema_version (database);
114 if (ver != DatabaseSchema.CORE_SCHEMA_VERSION)
115 {
116 throw new EngineError.DATABASE_CANTOPEN (
117 "Unable to open database");
118 }
119 }
120 else
121 {
122 DatabaseSchema.ensure_schema (database);
123 }
93 }124 }
94 catch (EngineError err)125 catch (EngineError err)
95 {126 {
@@ -296,7 +327,22 @@
296 }327 }
297 }328 }
298329
299 private void prepare_queries () throws EngineError330 private void prepare_read_queries () throws EngineError
331 {
332 int rc;
333 string sql;
334
335 // Event ID retrieval statement
336 sql = """
337 SELECT id FROM event
338 WHERE timestamp=? AND interpretation=? AND
339 manifestation=? AND actor=?
340 """;
341 rc = database.prepare_v2 (sql, -1, out id_retrieval_stmt);
342 assert_query_success (rc, "Event ID retrieval query error");
343 }
344
345 private void prepare_modification_queries () throws EngineError
300 {346 {
301 int rc;347 int rc;
302 string sql;348 string sql;
@@ -324,15 +370,6 @@
324 rc = database.prepare_v2 (sql, -1, out event_insertion_stmt);370 rc = database.prepare_v2 (sql, -1, out event_insertion_stmt);
325 assert_query_success (rc, "Insertion query error");371 assert_query_success (rc, "Insertion query error");
326372
327 // Event ID retrieval statement
328 sql = """
329 SELECT id FROM event
330 WHERE timestamp=? AND interpretation=? AND
331 manifestation=? AND actor=?
332 """;
333 rc = database.prepare_v2 (sql, -1, out id_retrieval_stmt);
334 assert_query_success (rc, "Event ID retrieval query error");
335
336 // Move handling statment373 // Move handling statment
337 sql = """374 sql = """
338 UPDATE event375 UPDATE event
339376
=== modified file 'src/table-lookup.vala'
--- src/table-lookup.vala 2012-01-26 10:08:09 +0000
+++ src/table-lookup.vala 2012-02-05 18:08:19 +0000
@@ -34,7 +34,7 @@
34 private HashTable<string, int> value_to_id;34 private HashTable<string, int> value_to_id;
35 private Sqlite.Statement insertion_stmt;35 private Sqlite.Statement insertion_stmt;
3636
37 public TableLookup (ZeitgeistDatabase database, string table_name)37 public TableLookup (Database database, string table_name)
38 {38 {
39 db = database.database;39 db = database.database;
40 table = table_name;40 table = table_name;
4141
=== modified file 'src/utils.vala'
--- src/utils.vala 2011-12-31 15:57:15 +0000
+++ src/utils.vala 2012-02-05 18:08:19 +0000
@@ -127,6 +127,59 @@
127 File dbfile = File.new_for_path (get_database_file_path ());127 File dbfile = File.new_for_path (get_database_file_path ());
128 dbfile.set_display_name (get_database_file_retire_name ());128 dbfile.set_display_name (get_database_file_retire_name ());
129 }129 }
130
131 /**
132 * Check if the value starts with the negation operator. If it does,
133 * remove the operator from the value and return true. Otherwise,
134 * return false.
135 */
136 public static bool parse_negation (ref string val)
137 {
138 if (!val.has_prefix ("!"))
139 return false;
140 val = val.substring (1);
141 return true;
142 }
143
144 /**
145 * Check if the value starts with the noexpand operator. If it does,
146 * remove the operator from the value and return true. Otherwise,
147 * return false.
148 *
149 * Check for the negation operator before calling this function.
150 */
151 public static bool parse_noexpand (ref string val)
152 {
153 if (!val.has_prefix ("+"))
154 return false;
155 val = val.substring (1);
156 return true;
157 }
158
159
160 /**
161 * Check if the value ends with the wildcard character. If it does,
162 * remove the wildcard character from the value and return true.
163 * Otherwise, return false.
164 */
165 public static bool parse_wildcard (ref string val)
166 {
167 if (!val.has_suffix ("*"))
168 return false;
169 unowned uint8[] val_data = val.data;
170 val_data[val_data.length-1] = '\0';
171 return true;
172 }
173
174 /**
175 * Return true if a string is empty (null or containing just a null
176 * byte).
177 */
178 public static bool is_empty_string (string? s)
179 {
180 return s == null || s == "";
181 }
182
130 }183 }
131}184}
132185
133186
=== modified file 'src/zeitgeist-daemon.vala'
--- src/zeitgeist-daemon.vala 2012-01-26 10:08:09 +0000
+++ src/zeitgeist-daemon.vala 2012-02-05 18:08:19 +0000
@@ -458,7 +458,7 @@
458 var lm = LogLevelFlags.LEVEL_MESSAGE;458 var lm = LogLevelFlags.LEVEL_MESSAGE;
459 var lw = LogLevelFlags.LEVEL_WARNING;459 var lw = LogLevelFlags.LEVEL_WARNING;
460 var lc = LogLevelFlags.LEVEL_CRITICAL;460 var lc = LogLevelFlags.LEVEL_CRITICAL;
461 switch (log_level)461 switch (log_level.up ())
462 {462 {
463 case "DEBUG":463 case "DEBUG":
464 discarded = 0;464 discarded = 0;
465465
=== modified file 'test/direct/Makefile.am'
--- test/direct/Makefile.am 2012-02-02 16:24:22 +0000
+++ test/direct/Makefile.am 2012-02-05 18:08:19 +0000
@@ -17,6 +17,7 @@
17 $(NULL)17 $(NULL)
1818
19SRC_FILES = \19SRC_FILES = \
20 $(top_srcdir)/src/db-reader.vala \
20 $(top_srcdir)/src/engine.vala \21 $(top_srcdir)/src/engine.vala \
21 $(top_srcdir)/src/utils.vala \22 $(top_srcdir)/src/utils.vala \
22 $(top_srcdir)/src/errors.vala \23 $(top_srcdir)/src/errors.vala \
2324
=== 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 18:08:19 +0000
@@ -18,6 +18,8 @@
18 *18 *
19 */19 */
2020
21using Zeitgeist;
22
21int main (string[] args)23int main (string[] args)
22{24{
2325
@@ -45,33 +47,18 @@
4547
46private class PublicEngine : Zeitgeist.Engine48private class PublicEngine : Zeitgeist.Engine
47{49{
48 public bool PUBLIC_parse_negation (ref string val)
49 {
50 return parse_negation (ref val);
51 }
52
53 public void PUBLIC_assert_no_negation (string field, string val)50 public void PUBLIC_assert_no_negation (string field, string val)
54 throws Zeitgeist.EngineError51 throws Zeitgeist.EngineError
55 {52 {
56 assert_no_negation (field, val);53 assert_no_negation (field, val);
57 }54 }
5855
59 public bool PUBLIC_parse_noexpand (ref string val)
60 {
61 return parse_noexpand (ref val);
62 }
63
64 public void PUBLIC_assert_no_noexpand (string field, string val)56 public void PUBLIC_assert_no_noexpand (string field, string val)
65 throws Zeitgeist.EngineError57 throws Zeitgeist.EngineError
66 {58 {
67 assert_no_noexpand (field, val);59 assert_no_noexpand (field, val);
68 }60 }
6961
70 public bool PUBLIC_parse_wildcard (ref string val)
71 {
72 return parse_wildcard (ref val);
73 }
74
75 public void PUBLIC_assert_no_wildcard (string field, string val)62 public void PUBLIC_assert_no_wildcard (string field, string val)
76 throws Zeitgeist.EngineError63 throws Zeitgeist.EngineError
77 {64 {
@@ -82,22 +69,21 @@
8269
83public void parse_negation_test ()70public void parse_negation_test ()
84{71{
85 PublicEngine engine = new PublicEngine ();
86 string val;72 string val;
8773
88 // Test string without a negation74 // Test string without a negation
89 val = "no negation";75 val = "no negation";
90 assert (engine.PUBLIC_parse_negation (ref val) == false);76 assert (Utils.parse_negation (ref val) == false);
91 assert (val == "no negation");77 assert (val == "no negation");
9278
93 // Test string with a valid negation79 // Test string with a valid negation
94 val = "!negation";80 val = "!negation";
95 assert (engine.PUBLIC_parse_negation (ref val) == true);81 assert (Utils.parse_negation (ref val) == true);
96 assert (val == "negation");82 assert (val == "negation");
9783
98 // Test negation character in a meaningless position84 // Test negation character in a meaningless position
99 val = "some ! chars";85 val = "some ! chars";
100 assert (engine.PUBLIC_parse_negation (ref val) == false);86 assert (Utils.parse_negation (ref val) == false);
101 assert (val == "some ! chars");87 assert (val == "some ! chars");
102}88}
10389
@@ -121,22 +107,21 @@
121107
122public void parse_noexpand_test ()108public void parse_noexpand_test ()
123{109{
124 PublicEngine engine = new PublicEngine ();
125 string val;110 string val;
126111
127 // Test string without a negation112 // Test string without a negation
128 val = "no expand";113 val = "no expand";
129 assert (engine.PUBLIC_parse_noexpand (ref val) == false);114 assert (Utils.parse_noexpand (ref val) == false);
130 assert (val == "no expand");115 assert (val == "no expand");
131116
132 // Test string with a valid noexpand117 // Test string with a valid noexpand
133 val = "+noexpand";118 val = "+noexpand";
134 assert (engine.PUBLIC_parse_noexpand (ref val) == true);119 assert (Utils.parse_noexpand (ref val) == true);
135 assert (val == "noexpand");120 assert (val == "noexpand");
136121
137 // Test negation character in a meaningless position122 // Test negation character in a meaningless position
138 val = "some + chars++";123 val = "some + chars++";
139 assert (engine.PUBLIC_parse_noexpand (ref val) == false);124 assert (Utils.parse_noexpand (ref val) == false);
140 assert (val == "some + chars++");125 assert (val == "some + chars++");
141}126}
142127
@@ -160,22 +145,21 @@
160145
161public void parse_wildcard_test ()146public void parse_wildcard_test ()
162{147{
163 PublicEngine engine = new PublicEngine ();
164 string val;148 string val;
165149
166 // Test string without a wildcard150 // Test string without a wildcard
167 val = "no wildcard";151 val = "no wildcard";
168 assert (engine.PUBLIC_parse_wildcard (ref val) == false);152 assert (Utils.parse_wildcard (ref val) == false);
169 assert (val == "no wildcard");153 assert (val == "no wildcard");
170154
171 // Test string with a valid wildcard155 // Test string with a valid wildcard
172 val = "yes wildcar*";156 val = "yes wildcar*";
173 assert (engine.PUBLIC_parse_wildcard (ref val) == true);157 assert (Utils.parse_wildcard (ref val) == true);
174 assert (val == "yes wildcar");158 assert (val == "yes wildcar");
175159
176 // Test wildcard character in a meaningless position160 // Test wildcard character in a meaningless position
177 val = "some * chars";161 val = "some * chars";
178 assert (engine.PUBLIC_parse_wildcard ( ref val) == false);162 assert (Utils.parse_wildcard ( ref val) == false);
179 assert (val == "some * chars");163 assert (val == "some * chars");
180}164}
181165
182166
=== 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 18:08:19 +0000
@@ -52,7 +52,7 @@
5252
53public void basic_test ()53public void basic_test ()
54{54{
55 ZeitgeistDatabase database = new Zeitgeist.SQLite.ZeitgeistDatabase ();55 Database database = new Zeitgeist.SQLite.Database ();
56 unowned Sqlite.Database db = database.database;56 unowned Sqlite.Database db = database.database;
57 TableLookup table_lookup = new TableLookup (database, "actor");57 TableLookup table_lookup = new TableLookup (database, "actor");
5858
@@ -71,7 +71,7 @@
71public void engine_test ()71public void engine_test ()
72{72{
73 PublicEngine engine = new PublicEngine ();73 PublicEngine engine = new PublicEngine ();
74 ZeitgeistDatabase database = engine.database;74 Database database = engine.database;
75 unowned Sqlite.Database db = database.database;75 unowned Sqlite.Database db = database.database;
76 TableLookup table_lookup = engine.get_actors_table_lookup();76 TableLookup table_lookup = engine.get_actors_table_lookup();
7777

Subscribers

People subscribed via source and target branches