1 /*
2 * Copyright (C) 2006, Jamie McCracken <jamiemcc@gnome.org>
3 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #include "config.h"
22
23 #include <string.h>
24 #include <stdlib.h>
25 #include <math.h>
26 #include <time.h>
27
28 #include <libtracker-common/tracker-date-time.h>
29 #include <libtracker-common/tracker-file-utils.h>
30 #include <libtracker-common/tracker-ontologies.h>
31
32 #include <libtracker-miner/tracker-miner-common.h>
33
34 #include "tracker-class.h"
35 #include "tracker-data-manager.h"
36 #include "tracker-data-update.h"
37 #include "tracker-data-query.h"
38 #include "tracker-db-interface-sqlite.h"
39 #include "tracker-db-manager.h"
40 #include "tracker-db-journal.h"
41 #include "tracker-ontologies.h"
42 #include "tracker-property.h"
43 #include "tracker-sparql-query.h"
44
45 #define RDF_PREFIX TRACKER_RDF_PREFIX
46 #define RDFS_PREFIX TRACKER_RDFS_PREFIX
47 #define TRACKER_PREFIX TRACKER_TRACKER_PREFIX
48 #define NAO_PREFIX TRACKER_NAO_PREFIX
49 #define NAO_LAST_MODIFIED NAO_PREFIX "lastModified"
50 #define RDF_PREFIX TRACKER_RDF_PREFIX
51 #define RDF_PROPERTY RDF_PREFIX "Property"
52 #define RDF_TYPE RDF_PREFIX "type"
53
54 typedef struct _TrackerDataUpdateBuffer TrackerDataUpdateBuffer;
55 typedef struct _TrackerDataUpdateBufferResource TrackerDataUpdateBufferResource;
56 typedef struct _TrackerDataUpdateBufferPredicate TrackerDataUpdateBufferPredicate;
57 typedef struct _TrackerDataUpdateBufferProperty TrackerDataUpdateBufferProperty;
58 typedef struct _TrackerDataUpdateBufferTable TrackerDataUpdateBufferTable;
59 typedef struct _TrackerDataBlankBuffer TrackerDataBlankBuffer;
60 typedef struct _TrackerStatementDelegate TrackerStatementDelegate;
61 typedef struct _TrackerCommitDelegate TrackerCommitDelegate;
62
63 struct _TrackerDataUpdateBuffer {
64 /* string -> integer */
65 GHashTable *resource_cache;
66 /* string -> TrackerDataUpdateBufferResource */
67 GHashTable *resources;
68 /* integer -> TrackerDataUpdateBufferResource */
69 GHashTable *resources_by_id;
70
71 /* the following two fields are valid per sqlite transaction, not just for same subject */
72 /* TrackerClass -> integer */
73 GHashTable *class_counts;
74
75 #if HAVE_TRACKER_FTS
76 gboolean fts_ever_updated;
77 #endif
78 };
79
80 struct _TrackerDataUpdateBufferResource {
81 const gchar *subject;
82 gint id;
83 gboolean create;
84 gboolean modified;
85 /* TrackerProperty -> GValueArray */
86 GHashTable *predicates;
87 /* string -> TrackerDataUpdateBufferTable */
88 GHashTable *tables;
89 /* TrackerClass */
90 GPtrArray *types;
91
92 #if HAVE_TRACKER_FTS
93 gboolean fts_updated;
94 #endif
95 };
96
97 struct _TrackerDataUpdateBufferProperty {
98 const gchar *name;
99 GValue value;
100 gint graph;
101 gboolean date_time : 1;
102
103 #if HAVE_TRACKER_FTS
104 gboolean fts : 1;
105 #endif
106 };
107
108 struct _TrackerDataUpdateBufferTable {
109 gboolean insert;
110 gboolean delete_row;
111 gboolean delete_value;
112 gboolean multiple_values;
113 TrackerClass *class;
114 /* TrackerDataUpdateBufferProperty */
115 GArray *properties;
116 };
117
118 /* buffer for anonymous blank nodes
119 * that are not yet in the database */
120 struct _TrackerDataBlankBuffer {
121 GHashTable *table;
122 gchar *subject;
123 /* string */
124 GArray *predicates;
125 /* string */
126 GArray *objects;
127 /* string */
128 GArray *graphs;
129 };
130
131 struct _TrackerStatementDelegate {
132 TrackerStatementCallback callback;
133 gpointer user_data;
134 };
135
136 struct _TrackerCommitDelegate {
137 TrackerCommitCallback callback;
138 gpointer user_data;
139 };
140
141 typedef struct {
142 gchar *graph;
143 gchar *subject;
144 gchar *predicate;
145 gchar *object;
146 gboolean is_uri;
147 } QueuedStatement;
148
149 static gboolean in_transaction = FALSE;
150 static gboolean in_ontology_transaction = FALSE;
151 static gboolean in_journal_replay = FALSE;
152 static TrackerDataUpdateBuffer update_buffer;
153 /* current resource */
154 static TrackerDataUpdateBufferResource *resource_buffer;
155 static TrackerDataBlankBuffer blank_buffer;
156 static time_t resource_time = 0;
157 static gint transaction_modseq = 0;
158 static gboolean has_persistent = TRUE;
159
160 static GPtrArray *insert_callbacks = NULL;
161 static GPtrArray *delete_callbacks = NULL;
162 static GPtrArray *commit_callbacks = NULL;
163 static GPtrArray *rollback_callbacks = NULL;
164 static gint max_service_id = 0;
165 static gint max_ontology_id = 0;
166
167 static gint ensure_resource_id (const gchar *uri,
168 gboolean *create);
169 static void cache_insert_value (const gchar *table_name,
170 const gchar *field_name,
171 gboolean transient,
172 GValue *value,
173 gint graph,
174 gboolean multiple_values,
175 gboolean fts,
176 gboolean date_time);
177 static GValueArray *get_old_property_values (TrackerProperty *property,
178 GError **error);
179 static gchar* gvalue_to_string (TrackerPropertyType type,
180 GValue *gvalue);
181 static gboolean delete_metadata_decomposed (TrackerProperty *property,
182 const gchar *value,
183 gint value_id,
184 GError **error);
185 static void resource_buffer_switch (const gchar *graph,
186 gint graph_id,
187 const gchar *subject,
188 gint subject_id);
189
190 void
191 tracker_data_add_commit_statement_callback (TrackerCommitCallback callback,
192 gpointer user_data)
193 {
194 TrackerCommitDelegate *delegate = g_new0 (TrackerCommitDelegate, 1);
195
196 if (!commit_callbacks) {
197 commit_callbacks = g_ptr_array_new ();
198 }
199
200 delegate->callback = callback;
201 delegate->user_data = user_data;
202
203 g_ptr_array_add (commit_callbacks, delegate);
204 }
205
206 void
207 tracker_data_remove_commit_statement_callback (TrackerCommitCallback callback,
208 gpointer user_data)
209 {
210 TrackerCommitDelegate *delegate;
211 guint i;
212 gboolean found = FALSE;
213
214 if (!commit_callbacks) {
215 return;
216 }
217
218 for (i = 0; i < commit_callbacks->len; i++) {
219 delegate = g_ptr_array_index (commit_callbacks, i);
220 if (delegate->callback == callback && delegate->user_data == user_data) {
221 found = TRUE;
222 break;
223 }
224 }
225
226 if (found) {
227 g_free (delegate);
228 g_ptr_array_remove_index (commit_callbacks, i);
229 }
230 }
231
232 void
233 tracker_data_add_rollback_statement_callback (TrackerCommitCallback callback,
234 gpointer user_data)
235 {
236 TrackerCommitDelegate *delegate = g_new0 (TrackerCommitDelegate, 1);
237
238 if (!rollback_callbacks) {
239 rollback_callbacks = g_ptr_array_new ();
240 }
241
242 delegate->callback = callback;
243 delegate->user_data = user_data;
244
245 g_ptr_array_add (rollback_callbacks, delegate);
246 }
247
248
249 void
250 tracker_data_remove_rollback_statement_callback (TrackerCommitCallback callback,
251 gpointer user_data)
252 {
253 TrackerCommitDelegate *delegate;
254 guint i;
255 gboolean found = FALSE;
256
257 if (!rollback_callbacks) {
258 return;
259 }
260
261 for (i = 0; i < rollback_callbacks->len; i++) {
262 delegate = g_ptr_array_index (rollback_callbacks, i);
263 if (delegate->callback == callback && delegate->user_data == user_data) {
264 found = TRUE;
265 break;
266 }
267 }
268
269 if (found) {
270 g_free (delegate);
271 g_ptr_array_remove_index (rollback_callbacks, i);
272 }
273 }
274
275 void
276 tracker_data_add_insert_statement_callback (TrackerStatementCallback callback,
277 gpointer user_data)
278 {
279 TrackerStatementDelegate *delegate = g_new0 (TrackerStatementDelegate, 1);
280
281 if (!insert_callbacks) {
282 insert_callbacks = g_ptr_array_new ();
283 }
284
285 delegate->callback = callback;
286 delegate->user_data = user_data;
287
288 g_ptr_array_add (insert_callbacks, delegate);
289 }
290
291 void
292 tracker_data_remove_insert_statement_callback (TrackerStatementCallback callback,
293 gpointer user_data)
294 {
295 TrackerStatementDelegate *delegate;
296 guint i;
297 gboolean found = FALSE;
298
299 if (!insert_callbacks) {
300 return;
301 }
302
303 for (i = 0; i < insert_callbacks->len; i++) {
304 delegate = g_ptr_array_index (insert_callbacks, i);
305 if (delegate->callback == callback && delegate->user_data == user_data) {
306 found = TRUE;
307 break;
308 }
309 }
310
311 if (found) {
312 g_free (delegate);
313 g_ptr_array_remove_index (insert_callbacks, i);
314 }
315 }
316
317 void
318 tracker_data_add_delete_statement_callback (TrackerStatementCallback callback,
319 gpointer user_data)
320 {
321 TrackerStatementDelegate *delegate = g_new0 (TrackerStatementDelegate, 1);
322
323 if (!delete_callbacks) {
324 delete_callbacks = g_ptr_array_new ();
325 }
326
327 delegate->callback = callback;
328 delegate->user_data = user_data;
329
330 g_ptr_array_add (delete_callbacks, delegate);
331 }
332
333 void
334 tracker_data_remove_delete_statement_callback (TrackerStatementCallback callback,
335 gpointer user_data)
336 {
337 TrackerStatementDelegate *delegate;
338 guint i;
339 gboolean found = FALSE;
340
341 if (!delete_callbacks) {
342 return;
343 }
344
345 for (i = 0; i < delete_callbacks->len; i++) {
346 delegate = g_ptr_array_index (delete_callbacks, i);
347 if (delegate->callback == callback && delegate->user_data == user_data) {
348 found = TRUE;
349 break;
350 }
351 }
352
353 if (found) {
354 g_free (delegate);
355 g_ptr_array_remove_index (delete_callbacks, i);
356 }
357 }
358
359 static gint
360 tracker_data_update_get_new_service_id (void)
361 {
362 TrackerDBCursor *cursor = NULL;
363 TrackerDBInterface *iface;
364 TrackerDBStatement *stmt;
365 GError *error = NULL;
366
367 if (in_ontology_transaction) {
368 if (G_LIKELY (max_ontology_id != 0)) {
369 return ++max_ontology_id;
370 }
371
372 iface = tracker_db_manager_get_db_interface ();
373
374 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
375 "SELECT MAX(ID) AS A FROM Resource WHERE ID <= %d", TRACKER_ONTOLOGIES_MAX_ID);
376
377 if (stmt) {
378 cursor = tracker_db_statement_start_cursor (stmt, &error);
379 g_object_unref (stmt);
380 }
381
382 if (cursor) {
383 if (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
384 max_ontology_id = MAX (tracker_db_cursor_get_int (cursor, 0), max_ontology_id);
385 }
386
387 g_object_unref (cursor);
388 }
389
390 if (G_UNLIKELY (error)) {
391 g_warning ("Could not get new resource ID for ontology transaction: %s\n", error->message);
392 g_error_free (error);
393 }
394
395 return ++max_ontology_id;
396 } else {
397 if (G_LIKELY (max_service_id != 0)) {
398 return ++max_service_id;
399 }
400
401 max_service_id = TRACKER_ONTOLOGIES_MAX_ID;
402
403 iface = tracker_db_manager_get_db_interface ();
404
405 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
406 "SELECT MAX(ID) AS A FROM Resource");
407
408 if (stmt) {
409 cursor = tracker_db_statement_start_cursor (stmt, &error);
410 g_object_unref (stmt);
411 }
412
413 if (cursor) {
414 if (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
415 max_service_id = MAX (tracker_db_cursor_get_int (cursor, 0), max_service_id);
416 }
417
418 g_object_unref (cursor);
419 }
420
421 if (G_UNLIKELY (error)) {
422 g_warning ("Could not get new resource ID: %s\n", error->message);
423 g_error_free (error);
424 }
425
426 return ++max_service_id;
427 }
428 }
429
430 static gint
431 tracker_data_update_get_next_modseq (void)
432 {
433 TrackerDBCursor *cursor = NULL;
434 TrackerDBInterface *temp_iface;
435 TrackerDBStatement *stmt;
436 GError *error = NULL;
437 gint max_modseq = 0;
438
439 temp_iface = tracker_db_manager_get_db_interface ();
440
441 stmt = tracker_db_interface_create_statement (temp_iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
442 "SELECT MAX(\"tracker:modified\") AS A FROM \"rdfs:Resource\"");
443
444 if (stmt) {
445 cursor = tracker_db_statement_start_cursor (stmt, &error);
446 g_object_unref (stmt);
447 }
448
449 if (cursor) {
450 if (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
451 max_modseq = MAX (tracker_db_cursor_get_int (cursor, 0), max_modseq);
452 }
453
454 g_object_unref (cursor);
455 }
456
457 if (G_UNLIKELY (error)) {
458 g_warning ("Could not get new resource ID: %s\n", error->message);
459 g_error_free (error);
460 }
461
462 return ++max_modseq;
463 }
464
465 void
466 tracker_data_update_shutdown (void)
467 {
468 max_service_id = 0;
469 max_ontology_id = 0;
470 transaction_modseq = 0;
471 }
472
473 static gint
474 get_transaction_modseq (void)
475 {
476 if (G_UNLIKELY (transaction_modseq == 0)) {
477 transaction_modseq = tracker_data_update_get_next_modseq ();
478 }
479
480 /* Always use 1 for ontology transactions */
481 if (in_ontology_transaction) {
482 return 1;
483 }
484
485 return transaction_modseq;
486 }
487
488 static TrackerDataUpdateBufferTable *
489 cache_table_new (gboolean multiple_values)
490 {
491 TrackerDataUpdateBufferTable *table;
492
493 table = g_slice_new0 (TrackerDataUpdateBufferTable);
494 table->multiple_values = multiple_values;
495 table->properties = g_array_sized_new (FALSE, FALSE, sizeof (TrackerDataUpdateBufferProperty), 4);
496
497 return table;
498 }
499
500 static void
501 cache_table_free (TrackerDataUpdateBufferTable *table)
502 {
503 TrackerDataUpdateBufferProperty *property;
504 gint i;
505
506 for (i = 0; i < table->properties->len; i++) {
507 property = &g_array_index (table->properties, TrackerDataUpdateBufferProperty, i);
508 g_value_unset (&property->value);
509 }
510
511 g_array_free (table->properties, TRUE);
512 g_slice_free (TrackerDataUpdateBufferTable, table);
513 }
514
515 static TrackerDataUpdateBufferTable *
516 cache_ensure_table (const gchar *table_name,
517 gboolean multiple_values,
518 gboolean transient)
519 {
520 TrackerDataUpdateBufferTable *table;
521
522 if (!resource_buffer->modified && !transient) {
523 /* first modification of this particular resource, update tracker:modified */
524
525 GValue gvalue = { 0 };
526
527 resource_buffer->modified = TRUE;
528
529 g_value_init (&gvalue, G_TYPE_INT64);
530 g_value_set_int64 (&gvalue, get_transaction_modseq ());
531 cache_insert_value ("rdfs:Resource", "tracker:modified", TRUE, &gvalue,
532 0,
533 FALSE, FALSE, FALSE);
534 }
535
536 table = g_hash_table_lookup (resource_buffer->tables, table_name);
537 if (table == NULL) {
538 table = cache_table_new (multiple_values);
539 g_hash_table_insert (resource_buffer->tables, g_strdup (table_name), table);
540 table->insert = multiple_values;
541 }
542
543 return table;
544 }
545
546 static void
547 cache_insert_row (TrackerClass *class)
548 {
549 TrackerDataUpdateBufferTable *table;
550
551 table = cache_ensure_table (tracker_class_get_name (class), FALSE, FALSE);
552 table->class = class;
553 table->insert = TRUE;
554 }
555
556 static void
557 cache_insert_value (const gchar *table_name,
558 const gchar *field_name,
559 gboolean transient,
560 GValue *value,
561 gint graph,
562 gboolean multiple_values,
563 gboolean fts,
564 gboolean date_time)
565 {
566 TrackerDataUpdateBufferTable *table;
567 TrackerDataUpdateBufferProperty property;
568
569 /* No need to strdup here, the incoming string is either always static, or
570 * long-standing as tracker_property_get_name return value. */
571 property.name = field_name;
572
573 property.value = *value;
574 property.graph = graph;
575 #if HAVE_TRACKER_FTS
576 property.fts = fts;
577 #endif
578 property.date_time = date_time;
579
580 table = cache_ensure_table (table_name, multiple_values, transient);
581 g_array_append_val (table->properties, property);
582 }
583
584 static void
585 cache_delete_row (TrackerClass *class)
586 {
587 TrackerDataUpdateBufferTable *table;
588
589 table = cache_ensure_table (tracker_class_get_name (class), FALSE, FALSE);
590 table->class = class;
591 table->delete_row = TRUE;
592 }
593
594 static void
595 cache_delete_value (const gchar *table_name,
596 const gchar *field_name,
597 gboolean transient,
598 GValue *value,
599 gboolean multiple_values,
600 gboolean fts,
601 gboolean date_time)
602 {
603 TrackerDataUpdateBufferTable *table;
604 TrackerDataUpdateBufferProperty property;
605
606 property.name = field_name;
607 property.value = *value;
608 property.graph = 0;
609 #if HAVE_TRACKER_FTS
610 property.fts = fts;
611 #endif
612 property.date_time = date_time;
613
614 table = cache_ensure_table (table_name, multiple_values, transient);
615 table->delete_value = TRUE;
616 g_array_append_val (table->properties, property);
617 }
618
619 static gint
620 query_resource_id (const gchar *uri)
621 {
622 gint id;
623
624 id = GPOINTER_TO_INT (g_hash_table_lookup (update_buffer.resource_cache, uri));
625
626 if (id == 0) {
627 id = tracker_data_query_resource_id (uri);
628
629 if (id) {
630 g_hash_table_insert (update_buffer.resource_cache, g_strdup (uri), GINT_TO_POINTER (id));
631 }
632 }
633
634 return id;
635 }
636
637 static gint
638 ensure_resource_id (const gchar *uri,
639 gboolean *create)
640 {
641 TrackerDBInterface *iface;
642 TrackerDBStatement *stmt;
643 GError *error = NULL;
644 gint id;
645
646 id = query_resource_id (uri);
647
648 if (create) {
649 *create = (id == 0);
650 }
651
652 if (id == 0) {
653 iface = tracker_db_manager_get_db_interface ();
654
655 id = tracker_data_update_get_new_service_id ();
656 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &error,
657 "INSERT INTO Resource (ID, Uri) VALUES (?, ?)");
658
659 if (stmt) {
660 tracker_db_statement_bind_int (stmt, 0, id);
661 tracker_db_statement_bind_text (stmt, 1, uri);
662 tracker_db_statement_execute (stmt, &error);
663 g_object_unref (stmt);
664 }
665
666 if (error) {
667 g_critical ("Could not ensure resource existence: %s", error->message);
668 g_error_free (error);
669 }
670
671 #ifndef DISABLE_JOURNAL
672 if (!in_journal_replay) {
673 tracker_db_journal_append_resource (id, uri);
674 }
675 #endif /* DISABLE_JOURNAL */
676
677 g_hash_table_insert (update_buffer.resource_cache, g_strdup (uri), GINT_TO_POINTER (id));
678 }
679
680 return id;
681 }
682
683 static void
684 statement_bind_gvalue (TrackerDBStatement *stmt,
685 gint *idx,
686 const GValue *value)
687 {
688 GType type;
689
690 type = G_VALUE_TYPE (value);
691 switch (type) {
692 case G_TYPE_STRING:
693 tracker_db_statement_bind_text (stmt, (*idx)++, g_value_get_string (value));
694 break;
695 case G_TYPE_INT64:
696 tracker_db_statement_bind_int (stmt, (*idx)++, g_value_get_int64 (value));
697 break;
698 case G_TYPE_DOUBLE:
699 tracker_db_statement_bind_double (stmt, (*idx)++, g_value_get_double (value));
700 break;
701 default:
702 if (type == TRACKER_TYPE_DATE_TIME) {
703 tracker_db_statement_bind_double (stmt, (*idx)++, tracker_date_time_get_time (value));
704 tracker_db_statement_bind_int (stmt, (*idx)++, tracker_date_time_get_local_date (value));
705 tracker_db_statement_bind_int (stmt, (*idx)++, tracker_date_time_get_local_time (value));
706 } else {
707 g_warning ("Unknown type for binding: %s\n", G_VALUE_TYPE_NAME (value));
708 }
709 break;
710 }
711 }
712
713 static void
714 add_class_count (TrackerClass *class,
715 gint count)
716 {
717 gint old_count_entry;
718
719 tracker_class_set_count (class, tracker_class_get_count (class) + count);
720
721 /* update class_counts table so that the count change can be reverted in case of rollback */
722 if (!update_buffer.class_counts) {
723 update_buffer.class_counts = g_hash_table_new (g_direct_hash, g_direct_equal);
724 }
725
726 old_count_entry = GPOINTER_TO_INT (g_hash_table_lookup (update_buffer.class_counts, class));
727 g_hash_table_insert (update_buffer.class_counts, class,
728 GINT_TO_POINTER (old_count_entry + count));
729 }
730
731 static void
732 tracker_data_resource_buffer_flush (GError **error)
733 {
734 TrackerDBInterface *iface;
735 TrackerDBStatement *stmt;
736 TrackerDataUpdateBufferTable *table;
737 TrackerDataUpdateBufferProperty *property;
738 GHashTableIter iter;
739 const gchar *table_name;
740 gint i, param;
741 GError *actual_error = NULL;
742
743 iface = tracker_db_manager_get_db_interface ();
744
745 g_hash_table_iter_init (&iter, resource_buffer->tables);
746 while (g_hash_table_iter_next (&iter, (gpointer*) &table_name, (gpointer*) &table)) {
747 if (table->multiple_values) {
748 for (i = 0; i < table->properties->len; i++) {
749 property = &g_array_index (table->properties, TrackerDataUpdateBufferProperty, i);
750
751 if (table->delete_value) {
752 /* delete rows for multiple value properties */
753 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
754 "DELETE FROM \"%s\" WHERE ID = ? AND \"%s\" = ?",
755 table_name,
756 property->name);
757 } else if (property->date_time) {
758 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
759 "INSERT OR IGNORE INTO \"%s\" (ID, \"%s\", \"%s:localDate\", \"%s:localTime\", \"%s:graph\") VALUES (?, ?, ?, ?, ?)",
760 table_name,
761 property->name,
762 property->name,
763 property->name,
764 property->name);
765 } else {
766 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
767 "INSERT OR IGNORE INTO \"%s\" (ID, \"%s\", \"%s:graph\") VALUES (?, ?, ?)",
768 table_name,
769 property->name,
770 property->name);
771 }
772
773 if (actual_error) {
774 g_propagate_error (error, actual_error);
775 return;
776 }
777
778 param = 0;
779
780 tracker_db_statement_bind_int (stmt, param++, resource_buffer->id);
781 statement_bind_gvalue (stmt, ¶m, &property->value);
782
783 if (property->graph != 0) {
784 tracker_db_statement_bind_int (stmt, param++, property->graph);
785 } else {
786 tracker_db_statement_bind_null (stmt, param++);
787 }
788
789 tracker_db_statement_execute (stmt, &actual_error);
790 g_object_unref (stmt);
791
792 if (actual_error) {
793 g_propagate_error (error, actual_error);
794 return;
795 }
796 }
797 } else {
798 GString *sql, *values_sql;
799
800 if (table->delete_row) {
801 /* remove entry from rdf:type table */
802 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
803 "DELETE FROM \"rdfs:Resource_rdf:type\" WHERE ID = ? AND \"rdf:type\" = ?");
804
805 if (stmt) {
806 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
807 tracker_db_statement_bind_int (stmt, 1, ensure_resource_id (tracker_class_get_uri (table->class), NULL));
808 tracker_db_statement_execute (stmt, &actual_error);
809 g_object_unref (stmt);
810 }
811
812 if (actual_error) {
813 g_propagate_error (error, actual_error);
814 return;
815 }
816
817 if (table->class) {
818 add_class_count (table->class, -1);
819 }
820
821 /* remove row from class table */
822 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
823 "DELETE FROM \"%s\" WHERE ID = ?", table_name);
824
825 if (stmt) {
826 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
827 tracker_db_statement_execute (stmt, &actual_error);
828 g_object_unref (stmt);
829 }
830
831 if (actual_error) {
832 g_propagate_error (error, actual_error);
833 return;
834 }
835
836 continue;
837 }
838
839 if (table->insert) {
840 sql = g_string_new ("INSERT INTO \"");
841 values_sql = g_string_new ("VALUES (?");
842 } else {
843 sql = g_string_new ("UPDATE \"");
844 values_sql = NULL;
845 }
846
847 g_string_append (sql, table_name);
848
849 if (table->insert) {
850 g_string_append (sql, "\" (ID");
851
852 if (strcmp (table_name, "rdfs:Resource") == 0) {
853 g_string_append (sql, ", \"tracker:added\", \"tracker:modified\", Available");
854 g_string_append (values_sql, ", ?, ?, 1");
855 } else {
856 }
857 } else {
858 g_string_append (sql, "\" SET ");
859 }
860
861 for (i = 0; i < table->properties->len; i++) {
862 property = &g_array_index (table->properties, TrackerDataUpdateBufferProperty, i);
863 if (table->insert) {
864 g_string_append_printf (sql, ", \"%s\"", property->name);
865 g_string_append (values_sql, ", ?");
866
867 if (property->date_time) {
868 g_string_append_printf (sql, ", \"%s:localDate\"", property->name);
869 g_string_append_printf (sql, ", \"%s:localTime\"", property->name);
870 g_string_append (values_sql, ", ?, ?");
871 }
872
873 g_string_append_printf (sql, ", \"%s:graph\"", property->name);
874 g_string_append (values_sql, ", ?");
875 } else {
876 if (i > 0) {
877 g_string_append (sql, ", ");
878 }
879 g_string_append_printf (sql, "\"%s\" = ?", property->name);
880
881 if (property->date_time) {
882 g_string_append_printf (sql, ", \"%s:localDate\" = ?", property->name);
883 g_string_append_printf (sql, ", \"%s:localTime\" = ?", property->name);
884 }
885
886 g_string_append_printf (sql, ", \"%s:graph\" = ?", property->name);
887 }
888 }
889
890 if (table->insert) {
891 g_string_append (sql, ")");
892 g_string_append (values_sql, ")");
893
894 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
895 "%s %s", sql->str, values_sql->str);
896 g_string_free (sql, TRUE);
897 g_string_free (values_sql, TRUE);
898 } else {
899 g_string_append (sql, " WHERE ID = ?");
900
901 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &actual_error,
902 "%s", sql->str);
903 g_string_free (sql, TRUE);
904 }
905
906 if (actual_error) {
907 g_propagate_error (error, actual_error);
908 return;
909 }
910
911 if (table->insert) {
912 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
913
914 if (strcmp (table_name, "rdfs:Resource") == 0) {
915 g_warn_if_fail (resource_time != 0);
916 tracker_db_statement_bind_int (stmt, 1, (gint64) resource_time);
917 tracker_db_statement_bind_int (stmt, 2, get_transaction_modseq ());
918 param = 3;
919 } else {
920 param = 1;
921 }
922 } else {
923 param = 0;
924 }
925
926 for (i = 0; i < table->properties->len; i++) {
927 property = &g_array_index (table->properties, TrackerDataUpdateBufferProperty, i);
928 if (table->delete_value) {
929 /* just set value to NULL for single value properties */
930 tracker_db_statement_bind_null (stmt, param++);
931 if (property->date_time) {
932 /* also set localDate and localTime to NULL */
933 tracker_db_statement_bind_null (stmt, param++);
934 tracker_db_statement_bind_null (stmt, param++);
935 }
936 } else {
937 statement_bind_gvalue (stmt, ¶m, &property->value);
938 }
939 if (property->graph != 0) {
940 tracker_db_statement_bind_int (stmt, param++, property->graph);
941 } else {
942 tracker_db_statement_bind_null (stmt, param++);
943 }
944 }
945
946 if (!table->insert) {
947 tracker_db_statement_bind_int (stmt, param++, resource_buffer->id);
948 }
949
950 tracker_db_statement_execute (stmt, &actual_error);
951 g_object_unref (stmt);
952
953 if (actual_error) {
954 g_propagate_error (error, actual_error);
955 return;
956 }
957 }
958 }
959
960 #if HAVE_TRACKER_FTS
961 if (resource_buffer->fts_updated) {
962 TrackerProperty *prop;
963 GValueArray *values;
964 gboolean create = resource_buffer->create;
965 GPtrArray *properties, *text;
966
967 properties = text = NULL;
968 g_hash_table_iter_init (&iter, resource_buffer->predicates);
969 while (g_hash_table_iter_next (&iter, (gpointer*) &prop, (gpointer*) &values)) {
970 if (tracker_property_get_fulltext_indexed (prop)) {
971 GString *fts;
972
973 fts = g_string_new ("");
974 for (i = 0; i < values->n_values; i++) {
975 g_string_append (fts, g_value_get_string (g_value_array_get_nth (values, i)));
(emitted by gcc) 976 g_string_append_c (fts, ' ');
977 }
978
979 if (!properties && !text) {
980 properties = g_ptr_array_new ();
981 text = g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
982 }
983
984 g_ptr_array_add (properties, (gpointer) tracker_property_get_name (prop));
985 g_ptr_array_add (text, g_string_free (fts, FALSE));
986 }
987 }
988
989 if (properties && text) {
990 g_ptr_array_add (properties, NULL);
991 g_ptr_array_add (text, NULL);
992
993 tracker_db_interface_sqlite_fts_update_text (iface,
994 resource_buffer->id,
995 (gchar **) properties->pdata,
996 (gchar **) text->pdata,
997 create);
998 update_buffer.fts_ever_updated = TRUE;
999 g_ptr_array_free (properties, TRUE);
1000 g_ptr_array_free (text, TRUE);
1001 }
1002 }
1003 #endif
1004 }
1005
1006 static void resource_buffer_free (TrackerDataUpdateBufferResource *resource)
1007 {
1008 g_hash_table_unref (resource->predicates);
1009 g_hash_table_unref (resource->tables);
1010 resource->subject = NULL;
1011
1012 g_ptr_array_free (resource->types, TRUE);
1013 resource->types = NULL;
1014
1015 g_slice_free (TrackerDataUpdateBufferResource, resource);
1016 }
1017
1018 void
1019 tracker_data_update_buffer_flush (GError **error)
1020 {
1021 GHashTableIter iter;
1022 GError *actual_error = NULL;
1023
1024 if (in_journal_replay) {
1025 g_hash_table_iter_init (&iter, update_buffer.resources_by_id);
1026 while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &resource_buffer)) {
1027 tracker_data_resource_buffer_flush (&actual_error);
1028 if (actual_error) {
1029 g_propagate_error (error, actual_error);
1030 break;
1031 }
1032 }
1033
1034 g_hash_table_remove_all (update_buffer.resources_by_id);
1035 } else {
1036 g_hash_table_iter_init (&iter, update_buffer.resources);
1037 while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &resource_buffer)) {
1038 tracker_data_resource_buffer_flush (&actual_error);
1039 if (actual_error) {
1040 g_propagate_error (error, actual_error);
1041 break;
1042 }
1043 }
1044
1045 g_hash_table_remove_all (update_buffer.resources);
1046 }
1047 resource_buffer = NULL;
1048 }
1049
1050 void
1051 tracker_data_update_buffer_might_flush (GError **error)
1052 {
1053 /* avoid high memory usage by update buffer */
1054 if (g_hash_table_size (update_buffer.resources) +
1055 g_hash_table_size (update_buffer.resources_by_id) >= 1000) {
1056 tracker_data_update_buffer_flush (error);
1057 }
1058 }
1059
1060 static void
1061 tracker_data_update_buffer_clear (void)
1062 {
1063 g_hash_table_remove_all (update_buffer.resources);
1064 g_hash_table_remove_all (update_buffer.resources_by_id);
1065 g_hash_table_remove_all (update_buffer.resource_cache);
1066 resource_buffer = NULL;
1067
1068 #if HAVE_TRACKER_FTS
1069 update_buffer.fts_ever_updated = FALSE;
1070 #endif
1071
1072 if (update_buffer.class_counts) {
1073 /* revert class count changes */
1074
1075 GHashTableIter iter;
1076 TrackerClass *class;
1077 gpointer count_ptr;
1078
1079 g_hash_table_iter_init (&iter, update_buffer.class_counts);
1080 while (g_hash_table_iter_next (&iter, (gpointer*) &class, &count_ptr)) {
1081 gint count;
1082
1083 count = GPOINTER_TO_INT (count_ptr);
1084 tracker_class_set_count (class, tracker_class_get_count (class) - count);
1085 }
1086
1087 g_hash_table_remove_all (update_buffer.class_counts);
1088 }
1089 }
1090
1091 static void
1092 tracker_data_blank_buffer_flush (GError **error)
1093 {
1094 /* end of blank node */
1095 gint i;
1096 gint id;
1097 gchar *subject;
1098 gchar *blank_uri;
1099 const gchar *sha1;
1100 GChecksum *checksum;
1101 GError *actual_error = NULL;
1102
1103 subject = blank_buffer.subject;
1104 blank_buffer.subject = NULL;
1105
1106 /* we share anonymous blank nodes with identical properties
1107 to avoid blowing up the database with duplicates */
1108
1109 checksum = g_checksum_new (G_CHECKSUM_SHA1);
1110
1111 /* generate hash uri from data to find resource
1112 assumes no collisions due to generally little contents of anonymous nodes */
1113 for (i = 0; i < blank_buffer.predicates->len; i++) {
1114 if (g_array_index (blank_buffer.graphs, guchar *, i) != NULL) {
1115 g_checksum_update (checksum, g_array_index (blank_buffer.graphs, guchar *, i), -1);
1116 }
1117
1118 g_checksum_update (checksum, g_array_index (blank_buffer.predicates, guchar *, i), -1);
1119 g_checksum_update (checksum, g_array_index (blank_buffer.objects, guchar *, i), -1);
1120 }
1121
1122 sha1 = g_checksum_get_string (checksum);
1123
1124 /* generate name based uuid */
1125 blank_uri = g_strdup_printf ("urn:uuid:%.8s-%.4s-%.4s-%.4s-%.12s",
1126 sha1, sha1 + 8, sha1 + 12, sha1 + 16, sha1 + 20);
1127
1128 id = tracker_data_query_resource_id (blank_uri);
1129
1130 if (id == 0) {
1131 /* uri not found
1132 replay piled up statements to create resource */
1133 for (i = 0; i < blank_buffer.predicates->len; i++) {
1134 tracker_data_insert_statement (g_array_index (blank_buffer.graphs, gchar *, i),
1135 blank_uri,
1136 g_array_index (blank_buffer.predicates, gchar *, i),
1137 g_array_index (blank_buffer.objects, gchar *, i),
1138 &actual_error);
1139 if (actual_error) {
1140 break;
1141 }
1142 }
1143 }
1144
1145 /* free piled up statements */
1146 for (i = 0; i < blank_buffer.predicates->len; i++) {
1147 g_free (g_array_index (blank_buffer.graphs, gchar *, i));
1148 g_free (g_array_index (blank_buffer.predicates, gchar *, i));
1149 g_free (g_array_index (blank_buffer.objects, gchar *, i));
1150 }
1151 g_array_remove_range (blank_buffer.graphs, 0, blank_buffer.graphs->len);
1152 g_array_remove_range (blank_buffer.predicates, 0, blank_buffer.predicates->len);
1153 g_array_remove_range (blank_buffer.objects, 0, blank_buffer.objects->len);
1154
1155 g_hash_table_insert (blank_buffer.table, subject, blank_uri);
1156 g_checksum_free (checksum);
1157
1158 if (actual_error) {
1159 g_propagate_error (error, actual_error);
1160 }
1161 }
1162
1163 static void
1164 cache_create_service_decomposed (TrackerClass *cl,
1165 const gchar *graph,
1166 gint graph_id)
1167 {
1168 TrackerClass **super_classes;
1169 TrackerProperty **domain_indexes;
1170 GValue gvalue = { 0 };
1171 gint i, final_graph_id, class_id;
1172
1173 /* also create instance of all super classes */
1174 super_classes = tracker_class_get_super_classes (cl);
1175 while (*super_classes) {
1176 cache_create_service_decomposed (*super_classes, graph, graph_id);
1177 super_classes++;
1178 }
1179
1180 for (i = 0; i < resource_buffer->types->len; i++) {
1181 if (g_ptr_array_index (resource_buffer->types, i) == cl) {
1182 /* ignore duplicate statement */
1183 return;
1184 }
1185 }
1186
1187 g_ptr_array_add (resource_buffer->types, cl);
1188
1189 g_value_init (&gvalue, G_TYPE_INT64);
1190
1191 cache_insert_row (cl);
1192
1193 final_graph_id = (graph != NULL ? ensure_resource_id (graph, NULL) : graph_id);
1194
1195 /* This is the original, no idea why tracker_class_get_id wasn't used here:
1196 * class_id = ensure_resource_id (tracker_class_get_uri (cl), NULL); */
1197
1198 class_id = tracker_class_get_id (cl);
1199
1200 g_value_set_int64 (&gvalue, class_id);
1201 cache_insert_value ("rdfs:Resource_rdf:type", "rdf:type", FALSE, &gvalue,
1202 final_graph_id,
1203 TRUE, FALSE, FALSE);
1204
1205 add_class_count (cl, 1);
1206
1207 if (!in_journal_replay && insert_callbacks) {
1208 guint n;
1209
1210 for (n = 0; n < insert_callbacks->len; n++) {
1211 TrackerStatementDelegate *delegate;
1212
1213 delegate = g_ptr_array_index (insert_callbacks, n);
1214 delegate->callback (final_graph_id, graph, resource_buffer->id, resource_buffer->subject,
1215 tracker_property_get_id (tracker_ontologies_get_rdf_type ()),
1216 class_id,
1217 tracker_class_get_uri (cl),
1218 resource_buffer->types,
1219 delegate->user_data);
1220 }
1221 }
1222
1223 /* When a new class created, make sure we propagate to the domain indexes
1224 * the property values already set, if any. */
1225 domain_indexes = tracker_class_get_domain_indexes (cl);
1226 if (!domain_indexes) {
1227 /* Nothing else to do, return */
1228 return;
1229 }
1230
1231 while (*domain_indexes) {
1232 GError *error = NULL;
1233 GValueArray *old_values;
1234
1235 /* read existing property values */
1236 old_values = get_old_property_values (*domain_indexes, &error);
1237 if (error) {
1238 g_critical ("Couldn't get old values for property '%s': '%s'",
1239 tracker_property_get_name (*domain_indexes),
1240 error->message);
1241 g_clear_error (&error);
1242 domain_indexes++;
1243 continue;
1244 }
1245
1246 if (old_values &&
1247 old_values->n_values > 0) {
1248 GValue gvalue_copy = { 0 };
1249
1250 /* Don't expect several values for property which is a domain index */
1251 g_assert_cmpint (old_values->n_values, ==, 1);
1252
1253 g_debug ("Propagating '%s' property value from '%s' to domain index in '%s'",
1254 tracker_property_get_name (*domain_indexes),
1255 tracker_property_get_table_name (*domain_indexes),
1256 tracker_class_get_name (cl));
1257
1258 g_value_init (&gvalue_copy, G_VALUE_TYPE (old_values->values));
1259 g_value_copy (old_values->values, &gvalue_copy);
1260
1261 cache_insert_value (tracker_class_get_name (cl),
1262 tracker_property_get_name (*domain_indexes),
1263 tracker_property_get_transient (*domain_indexes),
1264 &gvalue_copy,
1265 graph != NULL ? ensure_resource_id (graph, NULL) : graph_id,
1266 tracker_property_get_multiple_values (*domain_indexes),
1267 tracker_property_get_fulltext_indexed (*domain_indexes),
1268 tracker_property_get_data_type (*domain_indexes) == TRACKER_PROPERTY_TYPE_DATETIME);
1269 }
1270
1271 domain_indexes++;
1272 }
1273 }
1274
1275 static gboolean
1276 value_equal (GValue *value1,
1277 GValue *value2)
1278 {
1279 GType type = G_VALUE_TYPE (value1);
1280
1281 if (type != G_VALUE_TYPE (value2)) {
1282 return FALSE;
1283 }
1284
1285 switch (type) {
1286 case G_TYPE_STRING:
1287 return (strcmp (g_value_get_string (value1), g_value_get_string (value2)) == 0);
1288 case G_TYPE_INT64:
1289 return g_value_get_int64 (value1) == g_value_get_int64 (value2);
1290 case G_TYPE_DOUBLE:
1291 /* does RDF define equality for floating point values? */
1292 return g_value_get_double (value1) == g_value_get_double (value2);
1293 default:
1294 if (type == TRACKER_TYPE_DATE_TIME) {
1295 /* ignore UTC offset for comparison, irrelevant for comparison according to xsd:dateTime spec
1296 * http://www.w3.org/TR/xmlschema-2/#dateTime
1297 * also ignore sub-millisecond as this is a floating point comparison
1298 */
1299 return fabs (tracker_date_time_get_time (value1) - tracker_date_time_get_time (value2)) < 0.001;
1300 }
1301 g_assert_not_reached ();
1302 }
1303 }
1304
1305 static gboolean
1306 value_set_add_value (GValueArray *value_set,
1307 GValue *value)
1308 {
1309 gint i;
1310
1311 g_return_val_if_fail (G_VALUE_TYPE (value), FALSE);
1312
1313 for (i = 0; i < value_set->n_values; i++) {
1314 if (value_equal (g_value_array_get_nth (value_set, i), value)) {
(emitted by gcc) 1315 /* no change, value already in set */
1316 return FALSE;
1317 }
1318 }
1319
1320 g_value_array_append (value_set, value);
1321
1322 return TRUE;
1323 }
1324
1325 static gboolean
1326 value_set_remove_value (GValueArray *value_set,
1327 GValue *value)
1328 {
1329 gint i;
1330
1331 g_return_val_if_fail (G_VALUE_TYPE (value), FALSE);
1332
1333 for (i = 0; i < value_set->n_values; i++) {
1334 if (value_equal (g_value_array_get_nth (value_set, i), value)) {
(emitted by gcc) 1335 /* value found, remove from set */
1336
1337 g_value_array_remove (value_set, i);
1338
1339 return TRUE;
1340 }
1341 }
1342
1343 /* no change, value not found */
1344 return FALSE;
1345 }
1346
1347 static gboolean
1348 check_property_domain (TrackerProperty *property)
1349 {
1350 gint type_index;
1351
1352 for (type_index = 0; type_index < resource_buffer->types->len; type_index++) {
1353 if (tracker_property_get_domain (property) == g_ptr_array_index (resource_buffer->types, type_index)) {
1354 return TRUE;
1355 }
1356 }
1357 return FALSE;
1358 }
1359
1360 static GValueArray *
1361 get_property_values (TrackerProperty *property)
1362 {
1363 gboolean multiple_values;
1364 GValueArray *old_values;
1365
1366 multiple_values = tracker_property_get_multiple_values (property);
1367
1368 old_values = g_value_array_new (multiple_values ? 4 : 1);
(emitted by gcc) 1369 g_hash_table_insert (resource_buffer->predicates, g_object_ref (property), old_values);
1370
1371 if (!resource_buffer->create) {
1372 TrackerDBInterface *iface;
1373 TrackerDBStatement *stmt;
1374 TrackerDBCursor *cursor = NULL;
1375 const gchar *table_name;
1376 const gchar *field_name;
1377 GError *error = NULL;
1378
1379 table_name = tracker_property_get_table_name (property);
1380 field_name = tracker_property_get_name (property);
1381
1382 iface = tracker_db_manager_get_db_interface ();
1383
1384 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
1385 "SELECT \"%s\" FROM \"%s\" WHERE ID = ?",
1386 field_name, table_name);
1387
1388 if (stmt) {
1389 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
1390 cursor = tracker_db_statement_start_cursor (stmt, &error);
1391 g_object_unref (stmt);
1392 }
1393
1394 if (error) {
1395 g_warning ("Could not get property values: %s\n", error->message);
1396 g_error_free (error);
1397 }
1398
1399 if (cursor) {
1400 while (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
1401 GValue gvalue = { 0 };
1402 tracker_db_cursor_get_value (cursor, 0, &gvalue);
1403 if (G_VALUE_TYPE (&gvalue)) {
1404 if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME) {
1405 gdouble time;
1406
1407 if (G_VALUE_TYPE (&gvalue) == G_TYPE_INT64) {
1408 time = g_value_get_int64 (&gvalue);
1409 } else {
1410 time = g_value_get_double (&gvalue);
1411 }
1412 g_value_unset (&gvalue);
1413 g_value_init (&gvalue, TRACKER_TYPE_DATE_TIME);
1414 /* UTC offset is irrelevant for comparison */
1415 tracker_date_time_set (&gvalue, time, 0);
1416 }
1417 g_value_array_append (old_values, &gvalue);
1418 g_value_unset (&gvalue);
1419 }
1420 }
1421 g_object_unref (cursor);
1422 }
1423 }
1424
1425 return old_values;
1426 }
1427
1428 static GValueArray *
1429 get_old_property_values (TrackerProperty *property,
1430 GError **error)
1431 {
1432 GValueArray *old_values;
1433
1434 /* read existing property values */
1435 old_values = g_hash_table_lookup (resource_buffer->predicates, property);
1436 if (old_values == NULL) {
1437 if (!check_property_domain (property)) {
1438 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_CONSTRAINT,
1439 "Subject `%s' is not in domain `%s' of property `%s'",
1440 resource_buffer->subject,
1441 tracker_class_get_name (tracker_property_get_domain (property)),
1442 tracker_property_get_name (property));
1443 return NULL;
1444 }
1445
1446 #if HAVE_TRACKER_FTS
1447 if (tracker_property_get_fulltext_indexed (property)) {
1448 TrackerDBInterface *iface;
1449
1450 iface = tracker_db_manager_get_db_interface ();
1451
1452 if (!resource_buffer->fts_updated && !resource_buffer->create) {
1453 guint i, n_props;
1454 TrackerProperty **properties, *prop;
1455
1456 /* first fulltext indexed property to be modified
1457 * retrieve values of all fulltext indexed properties
1458 */
1459 properties = tracker_ontologies_get_properties (&n_props);
1460
1461 for (i = 0; i < n_props; i++) {
1462 prop = properties[i];
1463
1464 if (tracker_property_get_fulltext_indexed (prop)
1465 && check_property_domain (prop)) {
1466 const gchar *property_name;
1467 gint i;
1468
1469 old_values = get_property_values (prop);
1470 property_name = tracker_property_get_name (prop);
1471
1472 /* delete old fts entries */
1473 for (i = 0; i < old_values->n_values; i++) {
1474 tracker_db_interface_sqlite_fts_delete_text (iface,
1475 resource_buffer->id,
1476 property_name);
1477 }
1478 }
1479 }
1480
1481 update_buffer.fts_ever_updated = TRUE;
1482
1483 old_values = g_hash_table_lookup (resource_buffer->predicates, property);
1484 } else {
1485 old_values = get_property_values (property);
1486 }
1487
1488 resource_buffer->fts_updated = TRUE;
1489 } else {
1490 old_values = get_property_values (property);
1491 }
1492 #else
1493 old_values = get_property_values (property);
1494 #endif
1495 }
1496
1497 return old_values;
1498 }
1499
1500 static void
1501 string_to_gvalue (const gchar *value,
1502 TrackerPropertyType type,
1503 GValue *gvalue,
1504 GError **error)
1505 {
1506 gint object_id;
1507 gchar *datetime;
1508
1509 switch (type) {
1510 case TRACKER_PROPERTY_TYPE_STRING:
1511 g_value_init (gvalue, G_TYPE_STRING);
1512 g_value_set_string (gvalue, value);
1513 break;
1514 case TRACKER_PROPERTY_TYPE_INTEGER:
1515 g_value_init (gvalue, G_TYPE_INT64);
1516 g_value_set_int64 (gvalue, atoll (value));
1517 break;
1518 case TRACKER_PROPERTY_TYPE_BOOLEAN:
1519 /* use G_TYPE_INT64 to be compatible with value stored in DB
1520 (important for value_equal function) */
1521 g_value_init (gvalue, G_TYPE_INT64);
1522 g_value_set_int64 (gvalue, strcmp (value, "true") == 0);
1523 break;
1524 case TRACKER_PROPERTY_TYPE_DOUBLE:
1525 g_value_init (gvalue, G_TYPE_DOUBLE);
1526 g_value_set_double (gvalue, atof (value));
1527 break;
1528 case TRACKER_PROPERTY_TYPE_DATE:
1529 g_value_init (gvalue, G_TYPE_INT64);
1530 datetime = g_strdup_printf ("%sT00:00:00Z", value);
1531 g_value_set_int64 (gvalue, tracker_string_to_date (datetime, NULL, error));
1532 g_free (datetime);
1533 break;
1534 case TRACKER_PROPERTY_TYPE_DATETIME:
1535 g_value_init (gvalue, TRACKER_TYPE_DATE_TIME);
1536 tracker_date_time_set_from_string (gvalue, value, error);
1537 break;
1538 case TRACKER_PROPERTY_TYPE_RESOURCE:
1539 object_id = ensure_resource_id (value, NULL);
1540 g_value_init (gvalue, G_TYPE_INT64);
1541 g_value_set_int64 (gvalue, object_id);
1542 break;
1543 default:
1544 g_warn_if_reached ();
1545 break;
1546 }
1547 }
1548
1549 static gchar*
1550 gvalue_to_string (TrackerPropertyType type,
1551 GValue *gvalue)
1552 {
1553 gchar *retval = NULL;
1554 gint64 datet;
1555
1556 switch (type) {
1557 case TRACKER_PROPERTY_TYPE_STRING:
1558 retval = g_value_dup_string (gvalue);
1559 break;
1560 case TRACKER_PROPERTY_TYPE_INTEGER:
1561 retval = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (gvalue));
1562 break;
1563 case TRACKER_PROPERTY_TYPE_BOOLEAN:
1564 retval = g_value_get_int64 (gvalue) == 0 ? g_strdup ("false") : g_strdup ("true");
1565 break;
1566 case TRACKER_PROPERTY_TYPE_DOUBLE:
1567 retval = g_strdup_printf ("%f", g_value_get_double (gvalue));
1568 break;
1569 case TRACKER_PROPERTY_TYPE_DATE:
1570 datet = g_value_get_int64 (gvalue);
1571 retval = tracker_date_to_string (datet);
1572 /* it's a date-only, cut off the time */
1573 retval[10] = '\0';
1574 break;
1575 case TRACKER_PROPERTY_TYPE_DATETIME:
1576 datet = tracker_date_time_get_time (gvalue);
1577 retval = tracker_date_to_string (datet);
1578 break;
1579 case TRACKER_PROPERTY_TYPE_RESOURCE:
1580 default:
1581 g_warn_if_reached ();
1582 break;
1583 }
1584
1585 return retval;
1586 }
1587
1588 static gboolean
1589 resource_in_domain_index_class (TrackerClass *domain_index_class)
1590 {
1591 guint i;
1592 for (i = 0; i < resource_buffer->types->len; i++) {
1593 if (g_ptr_array_index (resource_buffer->types, i) == domain_index_class) {
1594 return TRUE;
1595 }
1596 }
1597 return FALSE;
1598 }
1599
1600 static void
1601 process_domain_indexes (TrackerProperty *property,
1602 GValue *gvalue,
1603 const gchar *field_name,
1604 const gchar *graph,
1605 gint graph_id)
1606 {
1607 TrackerClass **domain_index_classes;
1608
1609 domain_index_classes = tracker_property_get_domain_indexes (property);
1610 while (*domain_index_classes) {
1611 if (resource_in_domain_index_class (*domain_index_classes)) {
1612 GValue gvalue_copy = { 0 };
1613
1614 g_value_init (&gvalue_copy, G_VALUE_TYPE (gvalue));
1615 g_value_copy (gvalue, &gvalue_copy);
1616
1617 cache_insert_value (tracker_class_get_name (*domain_index_classes),
1618 field_name,
1619 tracker_property_get_transient (property),
1620 &gvalue_copy,
1621 graph != NULL ? ensure_resource_id (graph, NULL) : graph_id,
1622 FALSE,
1623 tracker_property_get_fulltext_indexed (property),
1624 tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME);
1625 }
1626 domain_index_classes++;
1627 }
1628 }
1629
1630 static gboolean
1631 cache_insert_metadata_decomposed (TrackerProperty *property,
1632 const gchar *value,
1633 gint value_id,
1634 const gchar *graph,
1635 gint graph_id,
1636 GError **error)
1637 {
1638 gboolean multiple_values;
1639 const gchar *table_name;
1640 const gchar *field_name;
1641 TrackerProperty **super_properties;
1642 GValue gvalue = { 0 };
1643 GValueArray *old_values;
1644 GError *new_error = NULL;
1645 gboolean change = FALSE;
1646
1647 /* also insert super property values */
1648 super_properties = tracker_property_get_super_properties (property);
1649 while (*super_properties) {
1650 change |= cache_insert_metadata_decomposed (*super_properties, value, value_id,
1651 graph, graph_id, &new_error);
1652 if (new_error) {
1653 g_propagate_error (error, new_error);
1654 return FALSE;
1655 }
1656 super_properties++;
1657 }
1658
1659 multiple_values = tracker_property_get_multiple_values (property);
1660 table_name = tracker_property_get_table_name (property);
1661 field_name = tracker_property_get_name (property);
1662
1663 /* read existing property values */
1664 old_values = get_old_property_values (property, &new_error);
1665 if (new_error) {
1666 g_propagate_error (error, new_error);
1667 return FALSE;
1668 }
1669
1670 if (value) {
1671 string_to_gvalue (value, tracker_property_get_data_type (property), &gvalue, &new_error);
1672 if (new_error) {
1673 g_propagate_error (error, new_error);
1674 return FALSE;
1675 }
1676 } else {
1677 g_value_init (&gvalue, G_TYPE_INT64);
1678 g_value_set_int64 (&gvalue, value_id);
1679 }
1680
1681 if (!value_set_add_value (old_values, &gvalue)) {
1682 /* value already inserted */
1683 g_value_unset (&gvalue);
1684 } else if (!multiple_values && old_values->n_values > 1) {
1685 /* trying to add second value to single valued property */
1686 GValue old_value = { 0 };
1687 GValue new_value = { 0 };
1688 const gchar *old_value_str = NULL;
1689 const gchar *new_value_str = NULL;
1690
1691 g_value_init (&old_value, G_TYPE_STRING);
1692 g_value_init (&new_value, G_TYPE_STRING);
1693
1694 /* Get both old and new values as strings letting glib do
1695 * whatever transformation needed */
1696 if (g_value_transform (g_value_array_get_nth (old_values, 0), &old_value)) {
(emitted by gcc) 1697 old_value_str = g_value_get_string (&old_value);
1698 }
1699 if (g_value_transform (g_value_array_get_nth (old_values, 1), &new_value)) {
1700 new_value_str = g_value_get_string (&new_value);
1701 }
1702
1703 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_CONSTRAINT,
1704 "Unable to insert multiple values for subject `%s' and single valued property `%s' "
1705 "(old_value: '%s', new value: '%s')",
1706 resource_buffer->subject,
1707 field_name,
1708 old_value_str ? old_value_str : "<untransformable>",
1709 new_value_str ? new_value_str : "<untransformable>");
1710
1711 g_value_unset (&old_value);
1712 g_value_unset (&new_value);
1713 g_value_unset (&gvalue);
1714
1715 } else {
1716 cache_insert_value (table_name, field_name,
1717 tracker_property_get_transient (property),
1718 &gvalue,
1719 graph != NULL ? ensure_resource_id (graph, NULL) : graph_id,
1720 multiple_values,
1721 tracker_property_get_fulltext_indexed (property),
1722 tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME);
1723
1724 if (!multiple_values) {
1725 process_domain_indexes (property, &gvalue, field_name, graph, graph_id);
1726 }
1727
1728 change = TRUE;
1729 }
1730
1731 return change;
1732 }
1733
1734 static gboolean
1735 delete_first_object (TrackerProperty *field,
1736 GValueArray *old_values,
1737 const gchar *graph,
1738 GError **error)
1739 {
1740 gint pred_id = 0, graph_id = 0;
1741 gint object_id = 0;
1742 gboolean change = FALSE;
1743
1744 if (old_values->n_values == 0) {
1745 return change;
1746 }
1747
1748 pred_id = tracker_property_get_id (field);
1749 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
1750
1751 if (tracker_property_get_data_type (field) == TRACKER_PROPERTY_TYPE_RESOURCE) {
1752 GError *new_error = NULL;
1753
1754 object_id = (gint) g_value_get_int64 (g_value_array_get_nth (old_values, 0));
(emitted by gcc) 1755
1756 /* This influences old_values, which is a reference, not a copy */
1757 change = delete_metadata_decomposed (field, NULL, object_id, &new_error);
1758
1759 if (new_error) {
1760 g_propagate_error (error, new_error);
1761 return change;
1762 }
1763
1764 #ifndef DISABLE_JOURNAL
1765 if (!in_journal_replay && change && !tracker_property_get_transient (field)) {
1766 tracker_db_journal_append_delete_statement_id (graph_id,
1767 resource_buffer->id,
1768 pred_id,
1769 object_id);
1770 }
1771 #endif /* DISABLE_JOURNAL */
1772 } else {
1773 GError *new_error = NULL;
1774 gchar *object_str = NULL;
1775
1776 object_id = 0;
1777 object_str = gvalue_to_string (tracker_property_get_data_type (field),
1778 g_value_array_get_nth (old_values, 0));
1779
1780 /* This influences old_values, which is a reference, not a copy */
1781 change = delete_metadata_decomposed (field, object_str, 0, &new_error);
1782
1783 if (new_error) {
1784 g_propagate_error (error, new_error);
1785 return change;
1786 }
1787
1788 #ifndef DISABLE_JOURNAL
1789 if (!in_journal_replay && change && !tracker_property_get_transient (field)) {
1790 if (!tracker_property_get_force_journal (field) &&
1791 g_strcmp0 (graph, TRACKER_MINER_FS_GRAPH_URN) == 0) {
1792 /* do not journal this statement extracted from filesystem */
1793 TrackerProperty *damaged;
1794
1795 damaged = tracker_ontologies_get_property_by_uri (TRACKER_TRACKER_PREFIX "damaged");
1796
1797 tracker_db_journal_append_insert_statement (graph_id,
1798 resource_buffer->id,
1799 tracker_property_get_id (damaged),
1800 "true");
1801 } else {
1802 tracker_db_journal_append_delete_statement (graph_id,
1803 resource_buffer->id,
1804 pred_id,
1805 object_str);
1806 }
1807 }
1808
1809 #endif /* DISABLE_JOURNAL */
1810
1811 if (delete_callbacks && change) {
1812 guint n;
1813 for (n = 0; n < delete_callbacks->len; n++) {
1814 TrackerStatementDelegate *delegate;
1815
1816 delegate = g_ptr_array_index (delete_callbacks, n);
1817 delegate->callback (graph_id, graph,
1818 resource_buffer->id,
1819 resource_buffer->subject,
1820 pred_id, object_id,
1821 object_str,
1822 resource_buffer->types,
1823 delegate->user_data);
1824 }
1825 }
1826
1827 g_free (object_str);
1828 }
1829
1830 return change;
1831 }
1832
1833 static gboolean
1834 cache_update_metadata_decomposed (TrackerProperty *property,
1835 const gchar *value,
1836 gint value_id,
1837 const gchar *graph,
1838 gint graph_id,
1839 GError **error)
1840 {
1841 gboolean multiple_values;
1842 const gchar *table_name;
1843 const gchar *field_name;
1844 TrackerProperty **super_properties;
1845 GValue gvalue = { 0 };
1846 GError *new_error = NULL;
1847 gboolean change = FALSE;
1848
1849 multiple_values = tracker_property_get_multiple_values (property);
1850
1851 /* also insert super property values */
1852 super_properties = tracker_property_get_super_properties (property);
1853 while (*super_properties) {
1854 gboolean super_is_multi;
1855 super_is_multi = tracker_property_get_multiple_values (*super_properties);
1856
1857 if (!multiple_values && super_is_multi) {
1858 gint subject_id;
1859 gchar *subject;
1860
1861 GValueArray *old_values;
1862
1863 /* read existing property values */
1864 old_values = get_old_property_values (property, &new_error);
1865 if (new_error) {
1866 g_propagate_error (error, new_error);
1867 return FALSE;
1868 }
1869
1870 /* Delete old values from super */
1871 change |= delete_first_object (*super_properties,
1872 old_values,
1873 graph,
1874 &new_error);
1875
1876 if (new_error) {
1877 g_propagate_error (error, new_error);
1878 return FALSE;
1879 }
1880
1881 subject_id = resource_buffer->id;
1882 subject = g_strdup (resource_buffer->subject);
1883
1884 /* We need to flush to apply the delete */
1885 tracker_data_update_buffer_flush (&new_error);
1886 if (new_error) {
1887 g_propagate_error (error, new_error);
1888 g_free (subject);
1889 return FALSE;
1890 }
1891
1892 /* After flush we need to switch the resource_buffer */
1893 resource_buffer_switch (graph, graph_id, subject, subject_id);
1894
1895 g_free (subject);
1896 }
1897
1898 change |= cache_update_metadata_decomposed (*super_properties, value, value_id,
1899 graph, graph_id, &new_error);
1900 if (new_error) {
1901 g_propagate_error (error, new_error);
1902 return FALSE;
1903 }
1904 super_properties++;
1905 }
1906
1907 table_name = tracker_property_get_table_name (property);
1908 field_name = tracker_property_get_name (property);
1909
1910 if (value) {
1911 string_to_gvalue (value, tracker_property_get_data_type (property), &gvalue, &new_error);
1912 if (new_error) {
1913 g_propagate_error (error, new_error);
1914 return FALSE;
1915 }
1916 } else {
1917 g_value_init (&gvalue, G_TYPE_INT64);
1918 g_value_set_int64 (&gvalue, value_id);
1919 }
1920
1921 cache_insert_value (table_name, field_name,
1922 tracker_property_get_transient (property),
1923 &gvalue,
1924 graph != NULL ? ensure_resource_id (graph, NULL) : graph_id,
1925 multiple_values,
1926 tracker_property_get_fulltext_indexed (property),
1927 tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME);
1928
1929 if (!multiple_values) {
1930 process_domain_indexes (property, &gvalue, field_name, graph, graph_id);
1931 }
1932
1933 return TRUE;
1934 }
1935
1936 static gboolean
1937 delete_metadata_decomposed (TrackerProperty *property,
1938 const gchar *value,
1939 gint value_id,
1940 GError **error)
1941 {
1942 gboolean multiple_values;
1943 const gchar *table_name;
1944 const gchar *field_name;
1945 TrackerProperty **super_properties;
1946 GValue gvalue = { 0 };
1947 GValueArray *old_values;
1948 GError *new_error = NULL;
1949 gboolean change = FALSE;
1950
1951 multiple_values = tracker_property_get_multiple_values (property);
1952 table_name = tracker_property_get_table_name (property);
1953 field_name = tracker_property_get_name (property);
1954
1955 /* read existing property values */
1956 old_values = get_old_property_values (property, &new_error);
1957 if (new_error) {
1958 /* no need to error out if statement does not exist for any reason */
1959 g_clear_error (&new_error);
1960 return FALSE;
1961 }
1962
1963 if (value) {
1964 string_to_gvalue (value, tracker_property_get_data_type (property), &gvalue, &new_error);
1965 if (new_error) {
1966 g_propagate_error (error, new_error);
1967 return FALSE;
1968 }
1969 } else {
1970 g_value_init (&gvalue, G_TYPE_INT64);
1971 g_value_set_int64 (&gvalue, value_id);
1972 }
1973
1974 if (!value_set_remove_value (old_values, &gvalue)) {
1975 /* value not found */
1976 g_value_unset (&gvalue);
1977 } else {
1978 cache_delete_value (table_name, field_name,
1979 tracker_property_get_transient (property),
1980 &gvalue, multiple_values,
1981 tracker_property_get_fulltext_indexed (property),
1982 tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME);
1983
1984 if (!multiple_values) {
1985 TrackerClass **domain_index_classes;
1986
1987 domain_index_classes = tracker_property_get_domain_indexes (property);
1988
1989 while (*domain_index_classes) {
1990 if (resource_in_domain_index_class (*domain_index_classes)) {
1991 GValue gvalue_copy = { 0 };
1992 g_value_init (&gvalue_copy, G_VALUE_TYPE (&gvalue));
1993 g_value_copy (&gvalue, &gvalue_copy);
1994 cache_delete_value (tracker_class_get_name (*domain_index_classes),
1995 field_name,
1996 tracker_property_get_transient (property),
1997 &gvalue_copy, multiple_values,
1998 tracker_property_get_fulltext_indexed (property),
1999 tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME);
2000 }
2001 domain_index_classes++;
2002 }
2003 }
2004
2005 change = TRUE;
2006 }
2007
2008 /* also delete super property values */
2009 super_properties = tracker_property_get_super_properties (property);
2010 while (*super_properties) {
2011 change |= delete_metadata_decomposed (*super_properties, value, value_id, error);
2012 super_properties++;
2013 }
2014
2015 return change;
2016 }
2017
2018 static void
2019 db_delete_row (TrackerDBInterface *iface,
2020 const gchar *table_name,
2021 gint id)
2022 {
2023 TrackerDBStatement *stmt;
2024 GError *error = NULL;
2025
2026 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &error,
2027 "DELETE FROM \"%s\" WHERE ID = ?",
2028 table_name);
2029
2030 if (stmt) {
2031 tracker_db_statement_bind_int (stmt, 0, id);
2032 tracker_db_statement_execute (stmt, &error);
2033 g_object_unref (stmt);
2034 }
2035
2036 if (error) {
2037 g_warning ("%s", error->message);
2038 g_error_free (error);
2039 error = NULL;
2040 }
2041 }
2042
2043 static void
2044 cache_delete_resource_type_full (TrackerClass *class,
2045 const gchar *graph,
2046 gint graph_id,
2047 gboolean single_type)
2048 {
2049 TrackerDBInterface *iface;
2050 TrackerDBStatement *stmt;
2051 TrackerDBCursor *cursor = NULL;
2052 TrackerProperty **properties, *prop;
2053 gboolean found, direct_delete;
2054 gint i;
2055 guint p, n_props;
2056 GError *error = NULL;
2057
2058 iface = tracker_db_manager_get_db_interface ();
2059
2060 if (!single_type) {
2061 if (!HAVE_TRACKER_FTS &&
2062 strcmp (tracker_class_get_uri (class), RDFS_PREFIX "Resource") == 0 &&
2063 g_hash_table_size (resource_buffer->tables) == 0) {
2064 /* skip subclass query when deleting whole resource
2065 to improve performance */
2066
2067 while (resource_buffer->types->len > 0) {
2068 TrackerClass *type;
2069
2070 type = g_ptr_array_index (resource_buffer->types,
2071 resource_buffer->types->len - 1);
2072 cache_delete_resource_type_full (type,
2073 graph,
2074 graph_id,
2075 TRUE);
2076 }
2077
2078 return;
2079 }
2080
2081 found = FALSE;
2082 for (i = 0; i < resource_buffer->types->len; i++) {
2083 if (g_ptr_array_index (resource_buffer->types, i) == class) {
2084 found = TRUE;
2085 break;
2086 }
2087 }
2088
2089 if (!found) {
2090 /* type not found, nothing to do */
2091 return;
2092 }
2093
2094 /* retrieve all subclasses we need to remove from the subject
2095 * before we can remove the class specified as object of the statement */
2096 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
2097 "SELECT (SELECT Uri FROM Resource WHERE ID = \"rdfs:Class_rdfs:subClassOf\".ID) "
2098 "FROM \"rdfs:Resource_rdf:type\" INNER JOIN \"rdfs:Class_rdfs:subClassOf\" ON (\"rdf:type\" = \"rdfs:Class_rdfs:subClassOf\".ID) "
2099 "WHERE \"rdfs:Resource_rdf:type\".ID = ? AND \"rdfs:subClassOf\" = (SELECT ID FROM Resource WHERE Uri = ?)");
2100
2101 if (stmt) {
2102 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
2103 tracker_db_statement_bind_text (stmt, 1, tracker_class_get_uri (class));
2104 cursor = tracker_db_statement_start_cursor (stmt, &error);
2105 g_object_unref (stmt);
2106 }
2107
2108 if (cursor) {
2109 while (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
2110 const gchar *class_uri;
2111
2112 class_uri = tracker_db_cursor_get_string (cursor, 0, NULL);
2113 cache_delete_resource_type_full (tracker_ontologies_get_class_by_uri (class_uri),
2114 graph, graph_id, FALSE);
2115 }
2116
2117 g_object_unref (cursor);
2118 }
2119
2120 if (error) {
2121 g_warning ("Could not delete cache resource (selecting subclasses): %s", error->message);
2122 g_error_free (error);
2123 error = NULL;
2124 }
2125 }
2126
2127 /* bypass buffer if possible
2128 we need old property values with FTS */
2129 direct_delete = (!HAVE_TRACKER_FTS && g_hash_table_size (resource_buffer->tables) == 0);
2130
2131 /* delete all property values */
2132
2133 properties = tracker_ontologies_get_properties (&n_props);
2134
2135 for (p = 0; p < n_props; p++) {
2136 gboolean multiple_values;
2137 const gchar *table_name;
2138 const gchar *field_name;
2139 GValueArray *old_values;
2140 gint y;
2141
2142 prop = properties[p];
2143
2144 if (tracker_property_get_domain (prop) != class) {
2145 continue;
2146 }
2147
2148 multiple_values = tracker_property_get_multiple_values (prop);
2149 table_name = tracker_property_get_table_name (prop);
2150 field_name = tracker_property_get_name (prop);
2151
2152 if (direct_delete) {
2153 if (multiple_values) {
2154 db_delete_row (iface, table_name, resource_buffer->id);
2155 }
2156 /* single-valued property values are deleted right after the loop by deleting the row in the class table */
2157 continue;
2158 }
2159
2160 old_values = get_old_property_values (prop, NULL);
2161
2162 for (y = old_values->n_values - 1; y >= 0 ; y--) {
2163 GValue *old_gvalue;
2164 GValue gvalue = { 0 };
2165
2166 old_gvalue = g_value_array_get_nth (old_values, y);
(emitted by gcc) 2167 g_value_init (&gvalue, G_VALUE_TYPE (old_gvalue));
2168 g_value_copy (old_gvalue, &gvalue);
2169
2170 value_set_remove_value (old_values, &gvalue);
2171 cache_delete_value (table_name, field_name,
2172 tracker_property_get_transient (prop),
2173 &gvalue, multiple_values,
2174 tracker_property_get_fulltext_indexed (prop),
2175 tracker_property_get_data_type (prop) == TRACKER_PROPERTY_TYPE_DATETIME);
2176
2177
2178 if (!multiple_values) {
2179 TrackerClass **domain_index_classes;
2180
2181 domain_index_classes = tracker_property_get_domain_indexes (prop);
2182 while (*domain_index_classes) {
2183 if (resource_in_domain_index_class (*domain_index_classes)) {
2184 GValue gvalue_copy = { 0 };
2185 g_value_init (&gvalue_copy, G_VALUE_TYPE (&gvalue));
2186 g_value_copy (&gvalue, &gvalue_copy);
2187 cache_delete_value (tracker_class_get_name (*domain_index_classes),
2188 field_name,
2189 tracker_property_get_transient (prop),
2190 &gvalue_copy, multiple_values,
2191 tracker_property_get_fulltext_indexed (prop),
2192 tracker_property_get_data_type (prop) == TRACKER_PROPERTY_TYPE_DATETIME);
2193 }
2194 domain_index_classes++;
2195 }
2196 }
2197
2198 }
2199 }
2200
2201 if (direct_delete) {
2202 /* delete row from class table */
2203 db_delete_row (iface, tracker_class_get_name (class), resource_buffer->id);
2204
2205 if (!single_type) {
2206 /* delete row from rdfs:Resource_rdf:type table */
2207 /* this is not necessary when deleting the whole resource
2208 as all property values are deleted implicitly */
2209 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &error,
2210 "DELETE FROM \"rdfs:Resource_rdf:type\" WHERE ID = ? AND \"rdf:type\" = ?");
2211
2212 if (stmt) {
2213 tracker_db_statement_bind_int (stmt, 0, resource_buffer->id);
2214 tracker_db_statement_bind_int (stmt, 1, tracker_class_get_id (class));
2215 tracker_db_statement_execute (stmt, &error);
2216 g_object_unref (stmt);
2217 }
2218
2219 if (error) {
2220 g_warning ("Could not delete cache resource: %s", error->message);
2221 g_error_free (error);
2222 error = NULL;
2223 }
2224 }
2225
2226 add_class_count (class, -1);
2227 } else {
2228 cache_delete_row (class);
2229 }
2230
2231 if (!in_journal_replay && delete_callbacks) {
2232 guint n;
2233 gint final_graph_id;
2234
2235 final_graph_id = (graph != NULL ? ensure_resource_id (graph, NULL) : graph_id);
2236
2237 for (n = 0; n < delete_callbacks->len; n++) {
2238 TrackerStatementDelegate *delegate;
2239
2240 delegate = g_ptr_array_index (delete_callbacks, n);
2241 delegate->callback (final_graph_id, graph, resource_buffer->id, resource_buffer->subject,
2242 tracker_property_get_id (tracker_ontologies_get_rdf_type ()),
2243 tracker_class_get_id (class),
2244 tracker_class_get_uri (class),
2245 resource_buffer->types,
2246 delegate->user_data);
2247 }
2248 }
2249
2250 g_ptr_array_remove (resource_buffer->types, class);
2251 }
2252
2253 static void
2254 cache_delete_resource_type (TrackerClass *class,
2255 const gchar *graph,
2256 gint graph_id)
2257 {
2258 cache_delete_resource_type_full (class, graph, graph_id, FALSE);
2259 }
2260
2261 static void
2262 resource_buffer_switch (const gchar *graph,
2263 gint graph_id,
2264 const gchar *subject,
2265 gint subject_id)
2266 {
2267 if (in_journal_replay) {
2268 /* journal replay only provides subject id
2269 resource_buffer->subject is only used in error messages and callbacks
2270 both should never occur when in journal replay */
2271 if (resource_buffer == NULL || resource_buffer->id != subject_id) {
2272 /* switch subject */
2273 resource_buffer = g_hash_table_lookup (update_buffer.resources_by_id, GINT_TO_POINTER (subject_id));
2274 }
2275 } else {
2276 if (resource_buffer == NULL || strcmp (resource_buffer->subject, subject) != 0) {
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2277 /* switch subject */
2278 resource_buffer = g_hash_table_lookup (update_buffer.resources, subject);
2279 }
2280 }
2281
2282 if (resource_buffer == NULL) {
2283 gchar *subject_dup = NULL;
2284
2285 /* large INSERTs with thousands of resources could lead to
2286 high peak memory usage due to the update buffer
2287 flush the buffer if it already contains 1000 resources */
2288 tracker_data_update_buffer_might_flush (NULL);
2289
2290 /* subject not yet in cache, retrieve or create ID */
2291 resource_buffer = g_slice_new0 (TrackerDataUpdateBufferResource);
2292 if (subject != NULL) {
2293 subject_dup = g_strdup (subject);
2294 resource_buffer->subject = subject_dup;
2295 }
2296 if (subject_id > 0) {
2297 resource_buffer->id = subject_id;
2298 } else {
2299 resource_buffer->id = ensure_resource_id (resource_buffer->subject, &resource_buffer->create);
2300 }
2301 #if HAVE_TRACKER_FTS
2302 resource_buffer->fts_updated = FALSE;
2303 #endif
2304 if (resource_buffer->create) {
2305 resource_buffer->types = g_ptr_array_new ();
2306 } else {
2307 resource_buffer->types = tracker_data_query_rdf_type (resource_buffer->id);
2308 }
2309 resource_buffer->predicates = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, (GDestroyNotify) g_value_array_free);
(emitted by gcc) 2310 resource_buffer->tables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) cache_table_free);
2311
2312 if (in_journal_replay) {
2313 g_hash_table_insert (update_buffer.resources_by_id, GINT_TO_POINTER (subject_id), resource_buffer);
2314 } else {
2315 g_hash_table_insert (update_buffer.resources, subject_dup, resource_buffer);
2316
2317 if (graph != NULL) {
2318 graph_id = ensure_resource_id (graph, NULL);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2319 }
2320 }
2321 }
2322 }
2323
2324 void
2325 tracker_data_delete_statement (const gchar *graph,
2326 const gchar *subject,
2327 const gchar *predicate,
2328 const gchar *object,
2329 GError **error)
2330 {
2331 TrackerClass *class;
2332 gint subject_id = 0;
2333 gboolean change = FALSE;
2334
2335 g_return_if_fail (subject != NULL);
2336 g_return_if_fail (predicate != NULL);
2337 g_return_if_fail (object != NULL);
2338 g_return_if_fail (in_transaction);
2339
2340 subject_id = query_resource_id (subject);
2341
2342 if (subject_id == 0) {
2343 /* subject not in database */
2344 return;
2345 }
2346
2347 resource_buffer_switch (graph, 0, subject, subject_id);
2348
2349 if (object && g_strcmp0 (predicate, RDF_PREFIX "type") == 0) {
2350 class = tracker_ontologies_get_class_by_uri (object);
2351 if (class != NULL) {
2352 has_persistent = TRUE;
2353
2354 #ifndef DISABLE_JOURNAL
2355 if (!in_journal_replay) {
2356 tracker_db_journal_append_delete_statement_id (
2357 (graph != NULL ? query_resource_id (graph) : 0),
2358 resource_buffer->id,
2359 tracker_data_query_resource_id (predicate),
2360 tracker_class_get_id (class));
2361 }
2362 #endif /* DISABLE_JOURNAL */
2363
2364 cache_delete_resource_type (class, graph, 0);
2365 } else {
2366 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
2367 "Class '%s' not found in the ontology", object);
2368 }
2369 } else {
2370 gint pred_id = 0, graph_id = 0, object_id = 0;
2371 gboolean tried = FALSE;
2372 TrackerProperty *field;
2373
2374 field = tracker_ontologies_get_property_by_uri (predicate);
2375 if (field != NULL) {
2376 if (!tracker_property_get_transient (field)) {
2377 has_persistent = TRUE;
2378 }
2379
2380 change = delete_metadata_decomposed (field, object, 0, error);
2381 if (!in_journal_replay && change && !tracker_property_get_transient (field)) {
2382 if (tracker_property_get_data_type (field) == TRACKER_PROPERTY_TYPE_RESOURCE) {
2383
2384 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2385 pred_id = tracker_property_get_id (field);
2386 object_id = query_resource_id (object);
2387 tried = TRUE;
2388
2389 #ifndef DISABLE_JOURNAL
2390 tracker_db_journal_append_delete_statement_id (graph_id,
2391 resource_buffer->id,
2392 pred_id,
2393 object_id);
2394 #endif /* DISABLE_JOURNAL */
2395 } else {
2396 pred_id = tracker_property_get_id (field);
2397 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2398 object_id = 0;
2399 tried = TRUE;
2400
2401 #ifndef DISABLE_JOURNAL
2402 if (!tracker_property_get_force_journal (field) &&
2403 g_strcmp0 (graph, TRACKER_MINER_FS_GRAPH_URN) == 0) {
2404 /* do not journal this statement extracted from filesystem */
2405 TrackerProperty *damaged;
2406
2407 damaged = tracker_ontologies_get_property_by_uri (TRACKER_TRACKER_PREFIX "damaged");
2408
2409 tracker_db_journal_append_insert_statement (graph_id,
2410 resource_buffer->id,
2411 tracker_property_get_id (damaged),
2412 "true");
2413 } else {
2414 tracker_db_journal_append_delete_statement (graph_id,
2415 resource_buffer->id,
2416 pred_id,
2417 object);
2418 }
2419 #endif /* DISABLE_JOURNAL */
2420 }
2421 }
2422 } else {
2423 /* I wonder why in case of error the delete_callbacks are still executed */
2424 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2425 "Property '%s' not found in the ontology", predicate);
2426 }
2427
2428 if (!tried) {
2429 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2430 if (field == NULL) {
2431 pred_id = tracker_data_query_resource_id (predicate);
2432 } else {
2433 pred_id = tracker_property_get_id (field);
2434 }
2435 }
2436
2437 if (delete_callbacks && change) {
2438 guint n;
2439 for (n = 0; n < delete_callbacks->len; n++) {
2440 TrackerStatementDelegate *delegate;
2441
2442 delegate = g_ptr_array_index (delete_callbacks, n);
2443 delegate->callback (graph_id, graph, subject_id, subject,
2444 pred_id, object_id,
2445 object,
2446 resource_buffer->types,
2447 delegate->user_data);
2448 }
2449 }
2450 }
2451 }
2452
2453 static void
2454 delete_all_objects (const gchar *graph,
2455 const gchar *subject,
2456 const gchar *predicate,
2457 GError **error)
2458 {
2459 gint subject_id = 0;
2460 gboolean change = FALSE;
2461 GError *new_error = NULL;
2462 TrackerProperty *field;
2463
2464 g_return_if_fail (subject != NULL);
2465 g_return_if_fail (predicate != NULL);
2466 g_return_if_fail (in_transaction);
2467
2468 subject_id = query_resource_id (subject);
2469
2470 if (subject_id == 0) {
2471 /* subject not in database */
2472 return;
2473 }
2474
2475 resource_buffer_switch (graph, 0, subject, subject_id);
2476
2477 field = tracker_ontologies_get_property_by_uri (predicate);
2478 if (field != NULL) {
2479 GValueArray *old_values;
2480
2481 if (!tracker_property_get_transient (field)) {
2482 has_persistent = TRUE;
2483 }
2484
2485 old_values = get_old_property_values (field, &new_error);
2486 if (new_error) {
2487 g_propagate_error (error, new_error);
2488 return;
2489 }
2490
2491 while (old_values->n_values > 0) {
2492 GError *new_error = NULL;
2493
2494 change |= delete_first_object (field, old_values, graph, &new_error);
2495
2496 if (new_error) {
2497 g_propagate_error (error, new_error);
2498 return;
2499 }
2500 }
2501 } else {
2502 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2503 "Property '%s' not found in the ontology", predicate);
2504 }
2505 }
2506
2507 static gboolean
2508 tracker_data_insert_statement_common (const gchar *graph,
2509 const gchar *subject,
2510 const gchar *predicate,
2511 const gchar *object,
2512 GError **error)
2513 {
2514 if (g_str_has_prefix (subject, ":")) {
2515 /* blank node definition
2516 pile up statements until the end of the blank node */
2517 gchar *value;
2518 GError *actual_error = NULL;
2519
2520 if (blank_buffer.subject != NULL) {
2521 /* active subject in buffer */
2522 if (strcmp (blank_buffer.subject, subject) != 0) {
2523 /* subject changed, need to flush buffer */
2524 tracker_data_blank_buffer_flush (&actual_error);
2525
2526 if (actual_error) {
2527 g_propagate_error (error, actual_error);
2528 return FALSE;
2529 }
2530 }
2531 }
2532
2533 if (blank_buffer.subject == NULL) {
2534 blank_buffer.subject = g_strdup (subject);
2535 if (blank_buffer.graphs == NULL) {
2536 blank_buffer.graphs = g_array_sized_new (FALSE, FALSE, sizeof (char*), 4);
2537 blank_buffer.predicates = g_array_sized_new (FALSE, FALSE, sizeof (char*), 4);
2538 blank_buffer.objects = g_array_sized_new (FALSE, FALSE, sizeof (char*), 4);
2539 }
2540 }
2541
2542 value = g_strdup (graph);
2543 g_array_append_val (blank_buffer.graphs, value);
2544 value = g_strdup (predicate);
2545 g_array_append_val (blank_buffer.predicates, value);
2546 value = g_strdup (object);
2547 g_array_append_val (blank_buffer.objects, value);
2548
2549 return FALSE;
2550 }
2551
2552 resource_buffer_switch (graph, 0, subject, 0);
2553
2554 return TRUE;
2555 }
2556
2557 void
2558 tracker_data_insert_statement (const gchar *graph,
2559 const gchar *subject,
2560 const gchar *predicate,
2561 const gchar *object,
2562 GError **error)
2563 {
2564 TrackerProperty *property;
2565
2566 g_return_if_fail (subject != NULL);
2567 g_return_if_fail (predicate != NULL);
2568 g_return_if_fail (object != NULL);
2569 g_return_if_fail (in_transaction);
2570
2571 property = tracker_ontologies_get_property_by_uri (predicate);
2572 if (property != NULL) {
2573 if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_RESOURCE) {
2574 tracker_data_insert_statement_with_uri (graph, subject, predicate, object, error);
2575 } else {
2576 tracker_data_insert_statement_with_string (graph, subject, predicate, object, error);
2577 }
2578 } else {
2579 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2580 "Property '%s' not found in the ontology", predicate);
2581 }
2582 }
2583
2584
2585 static gboolean
2586 handle_blank_node (const gchar *subject,
2587 const gchar *predicate,
2588 const gchar *object,
2589 const gchar *graph,
2590 gboolean update,
2591 GError **error)
2592 {
2593 GError *actual_error = NULL;
2594 /* anonymous blank node used as object in a statement */
2595 const gchar *blank_uri;
2596
2597 if (blank_buffer.subject != NULL) {
2598 if (strcmp (blank_buffer.subject, object) == 0) {
2599 /* object still in blank buffer, need to flush buffer */
2600 tracker_data_blank_buffer_flush (&actual_error);
2601
2602 if (actual_error) {
2603 g_propagate_error (error, actual_error);
2604 return FALSE;
2605 }
2606 }
2607 }
2608
2609 blank_uri = g_hash_table_lookup (blank_buffer.table, object);
2610
2611 if (blank_uri != NULL) {
2612 /* now insert statement referring to blank node */
2613 if (update) {
2614 tracker_data_update_statement (graph, subject, predicate, blank_uri, &actual_error);
2615 } else {
2616 tracker_data_insert_statement (graph, subject, predicate, blank_uri, &actual_error);
2617 }
2618
2619 g_hash_table_remove (blank_buffer.table, object);
2620
2621 if (actual_error) {
2622 g_propagate_error (error, actual_error);
2623 return FALSE;
2624 }
2625
2626 return TRUE;
2627 } else {
2628 g_critical ("Blank node '%s' not found", object);
2629
2630 return FALSE;
2631 }
2632 }
2633
2634 void
2635 tracker_data_insert_statement_with_uri (const gchar *graph,
2636 const gchar *subject,
2637 const gchar *predicate,
2638 const gchar *object,
2639 GError **error)
2640 {
2641 GError *actual_error = NULL;
2642 TrackerClass *class;
2643 TrackerProperty *property;
2644 gint prop_id = 0, graph_id = 0;
2645 gint final_prop_id = 0, object_id = 0;
2646 gboolean change = FALSE;
2647
2648 g_return_if_fail (subject != NULL);
2649 g_return_if_fail (predicate != NULL);
2650 g_return_if_fail (object != NULL);
2651 g_return_if_fail (in_transaction);
2652
2653 property = tracker_ontologies_get_property_by_uri (predicate);
2654 if (property == NULL) {
2655 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2656 "Property '%s' not found in the ontology", predicate);
2657 return;
2658 } else {
2659 if (tracker_property_get_data_type (property) != TRACKER_PROPERTY_TYPE_RESOURCE) {
2660 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_TYPE,
2661 "Property '%s' does not accept URIs", predicate);
2662 return;
2663 }
2664 prop_id = tracker_property_get_id (property);
2665 }
2666
2667 if (!tracker_property_get_transient (property)) {
2668 has_persistent = TRUE;
2669 }
2670
2671 /* subjects and objects starting with `:' are anonymous blank nodes */
2672 if (g_str_has_prefix (object, ":")) {
2673 if (handle_blank_node (subject, predicate, object, graph, FALSE, &actual_error)) {
2674 return;
2675 }
2676
2677 if (actual_error) {
2678 g_propagate_error (error, actual_error);
2679 return;
2680 }
2681 }
2682
2683 if (!tracker_data_insert_statement_common (graph, subject, predicate, object, &actual_error)) {
2684 if (actual_error) {
2685 g_propagate_error (error, actual_error);
2686 return;
2687 }
2688
2689 return;
2690 }
2691
2692 if (property == tracker_ontologies_get_rdf_type ()) {
2693 /* handle rdf:type statements specially to
2694 cope with inference and insert blank rows */
2695 class = tracker_ontologies_get_class_by_uri (object);
2696 if (class != NULL) {
2697 cache_create_service_decomposed (class, graph, 0);
2698 } else {
2699 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
2700 "Class '%s' not found in the ontology", object);
2701 return;
2702 }
2703
2704 if (!in_journal_replay && !tracker_property_get_transient (property)) {
2705 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
Possibly related backtrace: 28f3eb595700555806698b89b41102de32d0216f MatchResult(frame_number=6, dist=1)
Possibly related backtrace: 61de754ac00793bc81d77cc302ac006884baff15 MatchResult(frame_number=5, dist=1)
Possibly related backtrace: bc7e9a0f3e93e18aaaef59e12735383f9a24d4b2 MatchResult(frame_number=3, dist=1)
Possibly related backtrace: cbce0118de9f79ab47e1e9e910d7873d222cefd6 MatchResult(frame_number=4, dist=1)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2706 final_prop_id = (prop_id != 0) ? prop_id : tracker_data_query_resource_id (predicate);
2707 object_id = query_resource_id (object);
2708 }
2709
2710 change = TRUE;
2711 } else {
2712 /* add value to metadata database */
2713 change = cache_insert_metadata_decomposed (property, object, 0, graph, 0, &actual_error);
2714 if (actual_error) {
2715 g_propagate_error (error, actual_error);
2716 return;
2717 }
2718
2719 if (change) {
2720 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2721 final_prop_id = (prop_id != 0) ? prop_id : tracker_data_query_resource_id (predicate);
2722 object_id = query_resource_id (object);
2723
2724 if (insert_callbacks) {
2725 guint n;
2726 for (n = 0; n < insert_callbacks->len; n++) {
2727 TrackerStatementDelegate *delegate;
2728
2729 delegate = g_ptr_array_index (insert_callbacks, n);
2730 delegate->callback (graph_id, graph, resource_buffer->id, subject,
2731 final_prop_id, object_id,
2732 object,
2733 resource_buffer->types,
2734 delegate->user_data);
2735 }
2736 }
2737 }
2738 }
2739
2740 #ifndef DISABLE_JOURNAL
2741 if (!in_journal_replay && change && !tracker_property_get_transient (property)) {
2742 tracker_db_journal_append_insert_statement_id (
2743 (graph != NULL ? query_resource_id (graph) : 0),
2744 resource_buffer->id,
2745 final_prop_id,
2746 object_id);
2747 }
2748 #endif /* DISABLE_JOURNAL */
2749
2750 }
2751
2752 void
2753 tracker_data_insert_statement_with_string (const gchar *graph,
2754 const gchar *subject,
2755 const gchar *predicate,
2756 const gchar *object,
2757 GError **error)
2758 {
2759 GError *actual_error = NULL;
2760 TrackerProperty *property;
2761 gboolean change;
2762 gint graph_id = 0, pred_id = 0;
2763 #ifndef DISABLE_JOURNAL
2764 gboolean tried = FALSE;
2765 #endif
2766
2767 g_return_if_fail (subject != NULL);
2768 g_return_if_fail (predicate != NULL);
2769 g_return_if_fail (object != NULL);
2770 g_return_if_fail (in_transaction);
2771
2772 property = tracker_ontologies_get_property_by_uri (predicate);
2773 if (property == NULL) {
2774 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2775 "Property '%s' not found in the ontology", predicate);
2776 return;
2777 } else {
2778 if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_RESOURCE) {
2779 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_TYPE,
2780 "Property '%s' only accepts URIs", predicate);
2781 return;
2782 }
2783 pred_id = tracker_property_get_id (property);
2784 }
2785
2786 if (!tracker_property_get_transient (property)) {
2787 has_persistent = TRUE;
2788 }
2789
2790 if (!tracker_data_insert_statement_common (graph, subject, predicate, object, &actual_error)) {
2791 if (actual_error) {
2792 g_propagate_error (error, actual_error);
2793 return;
2794 }
2795
2796 return;
2797 }
2798
2799 /* add value to metadata database */
2800 change = cache_insert_metadata_decomposed (property, object, 0, graph, 0, &actual_error);
2801 if (actual_error) {
2802 g_propagate_error (error, actual_error);
2803 return;
2804 }
2805
2806 if (insert_callbacks && change) {
2807 guint n;
2808
2809 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2810 pred_id = (pred_id != 0) ? pred_id : tracker_data_query_resource_id (predicate);
2811 #ifndef DISABLE_JOURNAL
2812 tried = TRUE;
2813 #endif
2814
2815 for (n = 0; n < insert_callbacks->len; n++) {
2816 TrackerStatementDelegate *delegate;
2817
2818 delegate = g_ptr_array_index (insert_callbacks, n);
2819 delegate->callback (graph_id, graph, resource_buffer->id, subject,
2820 pred_id, 0 /* Always a literal */,
2821 object,
2822 resource_buffer->types,
2823 delegate->user_data);
2824 }
2825 }
2826
2827 #ifndef DISABLE_JOURNAL
2828 if (!in_journal_replay && change && !tracker_property_get_transient (property)) {
2829 if (!tried) {
2830 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
2831 pred_id = (pred_id != 0) ? pred_id : tracker_data_query_resource_id (predicate);
2832 }
2833 if (!tracker_property_get_force_journal (property) &&
2834 g_strcmp0 (graph, TRACKER_MINER_FS_GRAPH_URN) == 0) {
2835 /* do not journal this statement extracted from filesystem */
2836 TrackerProperty *damaged;
2837
2838 damaged = tracker_ontologies_get_property_by_uri (TRACKER_TRACKER_PREFIX "damaged");
2839 tracker_db_journal_append_insert_statement (graph_id,
2840 resource_buffer->id,
2841 tracker_property_get_id (damaged),
2842 "true");
2843 } else {
2844 tracker_db_journal_append_insert_statement (graph_id,
2845 resource_buffer->id,
2846 pred_id,
2847 object);
2848 }
2849 }
2850 #endif /* DISABLE_JOURNAL */
2851 }
2852
2853 static void
2854 tracker_data_update_statement_with_uri (const gchar *graph,
2855 const gchar *subject,
2856 const gchar *predicate,
2857 const gchar *object,
2858 GError **error)
2859 {
2860 GError *actual_error = NULL;
2861 TrackerClass *class;
2862 TrackerProperty *property;
2863 gint prop_id = 0, graph_id = 0;
2864 gint final_prop_id = 0, object_id = 0;
2865 gboolean change = FALSE;
2866
2867 g_return_if_fail (subject != NULL);
2868 g_return_if_fail (predicate != NULL);
2869 g_return_if_fail (in_transaction);
2870
2871 property = tracker_ontologies_get_property_by_uri (predicate);
2872 if (property == NULL) {
2873 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
2874 "Property '%s' not found in the ontology", predicate);
2875 return;
2876 } else {
2877 if (tracker_property_get_data_type (property) != TRACKER_PROPERTY_TYPE_RESOURCE) {
2878 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_TYPE,
2879 "Property '%s' does not accept URIs", predicate);
2880 return;
2881 }
2882 prop_id = tracker_property_get_id (property);
2883 }
2884
2885 if (!tracker_property_get_transient (property)) {
2886 has_persistent = TRUE;
2887 }
2888
2889 /* subjects and objects starting with `:' are anonymous blank nodes */
2890 if (g_str_has_prefix (object, ":")) {
2891 if (handle_blank_node (subject, predicate, object, graph, TRUE, &actual_error)) {
2892 return;
2893 }
2894
2895 if (actual_error) {
2896 g_propagate_error (error, actual_error);
2897 return;
2898 }
2899 }
2900
2901 /* Update and insert share the exact same code here */
2902 if (!tracker_data_insert_statement_common (graph, subject, predicate, object, &actual_error)) {
2903 if (actual_error) {
2904 g_propagate_error (error, actual_error);
2905 return;
2906 }
2907
2908 return;
2909 }
2910
2911 if (property == tracker_ontologies_get_rdf_type ()) {
2912 /* handle rdf:type statements specially to
2913 cope with inference and insert blank rows */
2914 class = tracker_ontologies_get_class_by_uri (object);
2915 if (class != NULL) {
2916 /* Create here is fine for Update too */
2917 cache_create_service_decomposed (class, graph, 0);
2918 } else {
2919 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
2920 "Class '%s' not found in the ontology", object);
2921 return;
2922 }
2923
2924 if (!in_journal_replay && !tracker_property_get_transient (property)) {
2925 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
2926 final_prop_id = (prop_id != 0) ? prop_id : tracker_data_query_resource_id (predicate);
2927 object_id = query_resource_id (object);
2928 }
2929
2930 change = TRUE;
2931 } else {
2932 gint old_object_id = 0;
2933 GValueArray *old_values;
2934 gboolean multiple_values;
2935 GError *new_error = NULL;
2936 gboolean domain_unchecked = TRUE;
2937
2938 multiple_values = tracker_property_get_multiple_values (property);
2939
2940 #if HAVE_TRACKER_FTS
2941 /* This is unavoidable with FTS */
2942 /* This does a check_property_domain too */
2943 old_values = get_old_property_values (property, &new_error);
2944 domain_unchecked = FALSE;
2945 if (!new_error) {
2946 if (old_values->n_values > 0) {
2947 /* evel knievel cast */
2948 old_object_id = (gint) g_value_get_int64 (g_value_array_get_nth (old_values, 0));
(emitted by gcc) 2949 }
2950 } else {
2951 g_propagate_error (error, new_error);
2952 return;
2953 }
2954 #else
2955 /* We can disable correct object-id for deletes array here */
2956 if (!multiple_values) {
2957 guint r;
2958
2959 for (r = 0; r < resource_buffer->types->len; r++) {
2960 TrackerClass *m_class = g_ptr_array_index (resource_buffer->types, r);
2961
2962 /* We only do the old_values for GraphUpdated in tracker-store.
2963 * The subject's known classes are in resource_buffer->types
2964 * since resource_buffer_switch, so we can quickly check if any
2965 * of them has tracker:notify annotated. If so, get the old
2966 * values for the old_object_id */
2967
2968 if (tracker_class_get_notify (m_class)) {
2969 /* This does a check_property_domain too */
2970 old_values = get_old_property_values (property, &new_error);
2971 domain_unchecked = FALSE;
2972 if (!new_error) {
2973 if (old_values->n_values > 0) {
2974 /* evel knievel cast */
2975 old_object_id = (gint) g_value_get_int64 (g_value_array_get_nth (old_values, 0));
2976 }
2977 } else {
2978 g_propagate_error (error, new_error);
2979 return;
2980 }
2981
2982 break;
2983 }
2984 }
2985 }
2986 #endif /* HAVE_TRACKER_FTS */
2987
2988 if (domain_unchecked && !check_property_domain (property)) {
2989 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_CONSTRAINT,
2990 "Subject `%s' is not in domain `%s' of property `%s'",
2991 resource_buffer->subject,
2992 tracker_class_get_name (tracker_property_get_domain (property)),
2993 tracker_property_get_name (property));
2994 return;
2995 }
2996
2997 /* update or add value to metadata database */
2998 change = cache_update_metadata_decomposed (property, object, 0, graph, 0, &actual_error);
2999 if (actual_error) {
3000 g_propagate_error (error, actual_error);
3001 return;
3002 }
3003
3004 if (change) {
3005 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
3006 final_prop_id = (prop_id != 0) ? prop_id : tracker_data_query_resource_id (predicate);
3007 object_id = query_resource_id (object);
3008
3009 if (!multiple_values && delete_callbacks) {
3010 guint n;
3011
3012 for (n = 0; n < delete_callbacks->len; n++) {
3013 TrackerStatementDelegate *delegate;
3014
3015 /* Don't pass object to the delete, it's not correct */
3016 delegate = g_ptr_array_index (delete_callbacks, n);
3017 delegate->callback (graph_id, graph, resource_buffer->id, subject,
3018 final_prop_id, old_object_id,
3019 NULL,
3020 resource_buffer->types,
3021 delegate->user_data);
3022 }
3023 }
3024
3025 if (insert_callbacks) {
3026 guint n;
3027
3028 for (n = 0; n < insert_callbacks->len; n++) {
3029 TrackerStatementDelegate *delegate;
3030
3031 delegate = g_ptr_array_index (insert_callbacks, n);
3032 delegate->callback (graph_id, graph, resource_buffer->id, subject,
3033 final_prop_id, object_id,
3034 object,
3035 resource_buffer->types,
3036 delegate->user_data);
3037 }
3038 }
3039 }
3040 }
3041
3042 #ifndef DISABLE_JOURNAL
3043 if (!in_journal_replay && change && !tracker_property_get_transient (property)) {
3044 tracker_db_journal_append_update_statement_id (
3045 (graph != NULL ? query_resource_id (graph) : 0),
3046 resource_buffer->id,
3047 final_prop_id,
3048 object_id);
3049 }
3050 #endif /* DISABLE_JOURNAL */
3051 }
3052
3053 static void
3054 tracker_data_update_statement_with_string (const gchar *graph,
3055 const gchar *subject,
3056 const gchar *predicate,
3057 const gchar *object,
3058 GError **error)
3059 {
3060 GError *actual_error = NULL;
3061 TrackerProperty *property;
3062 gboolean change;
3063 gint graph_id = 0, pred_id = 0;
3064 gboolean multiple_values;
3065 #ifndef DISABLE_JOURNAL
3066 gboolean tried = FALSE;
3067 #endif
3068 #if HAVE_TRACKER_FTS
3069 GError *new_error = NULL;
3070 #endif /* HAVE_TRACKER_FTS */
3071
3072 g_return_if_fail (subject != NULL);
3073 g_return_if_fail (predicate != NULL);
3074 g_return_if_fail (in_transaction);
3075
3076 property = tracker_ontologies_get_property_by_uri (predicate);
3077 if (property == NULL) {
3078 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
3079 "Property '%s' not found in the ontology", predicate);
3080 return;
3081 } else {
3082 if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_RESOURCE) {
3083 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_TYPE,
3084 "Property '%s' only accepts URIs", predicate);
3085 return;
3086 }
3087 pred_id = tracker_property_get_id (property);
3088 }
3089
3090 multiple_values = tracker_property_get_multiple_values (property);
3091
3092 if (!tracker_property_get_transient (property)) {
3093 has_persistent = TRUE;
3094 }
3095
3096 /* Update and insert share the exact same code here */
3097 if (!tracker_data_insert_statement_common (graph, subject, predicate, object, &actual_error)) {
3098 if (actual_error) {
3099 g_propagate_error (error, actual_error);
3100 return;
3101 }
3102
3103 return;
3104 }
3105
3106 #if HAVE_TRACKER_FTS
3107 /* This is unavoidable with FTS */
3108 get_old_property_values (property, &new_error);
3109 if (new_error) {
3110 g_propagate_error (error, new_error);
3111 return;
3112 }
3113 #else
3114 if (!check_property_domain (property)) {
3115 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_CONSTRAINT,
3116 "Subject `%s' is not in domain `%s' of property `%s'",
3117 resource_buffer->subject,
3118 tracker_class_get_name (tracker_property_get_domain (property)),
3119 tracker_property_get_name (property));
3120 return;
3121 }
3122 #endif /* HAVE_TRACKER_FTS */
3123
3124 /* add or update value to metadata database */
3125 change = cache_update_metadata_decomposed (property, object, 0, graph, 0, &actual_error);
3126 if (actual_error) {
3127 g_propagate_error (error, actual_error);
3128 return;
3129 }
3130
3131 if (((!multiple_values && delete_callbacks) || insert_callbacks) && change) {
3132 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
3133 pred_id = (pred_id != 0) ? pred_id : tracker_data_query_resource_id (predicate);
3134 #ifndef DISABLE_JOURNAL
3135 tried = TRUE;
3136 #endif
3137 }
3138
3139 if ((!multiple_values && delete_callbacks) && change) {
3140 guint n;
3141
3142 for (n = 0; n < delete_callbacks->len; n++) {
3143 TrackerStatementDelegate *delegate;
3144
3145 /* Don't pass object to the delete, it's not correct */
3146 delegate = g_ptr_array_index (delete_callbacks, n);
3147 delegate->callback (graph_id, graph, resource_buffer->id, subject,
3148 pred_id, 0 /* Always a literal */,
3149 NULL,
3150 resource_buffer->types,
3151 delegate->user_data);
3152 }
3153 }
3154
3155 if (insert_callbacks && change) {
3156 guint n;
3157 for (n = 0; n < insert_callbacks->len; n++) {
3158 TrackerStatementDelegate *delegate;
3159
3160 delegate = g_ptr_array_index (insert_callbacks, n);
3161 delegate->callback (graph_id, graph, resource_buffer->id, subject,
3162 pred_id, 0 /* Always a literal */,
3163 object,
3164 resource_buffer->types,
3165 delegate->user_data);
3166 }
3167 }
3168
3169 #ifndef DISABLE_JOURNAL
3170 if (!in_journal_replay && change && !tracker_property_get_transient (property)) {
3171 if (!tried) {
3172 graph_id = (graph != NULL ? query_resource_id (graph) : 0);
3173 pred_id = (pred_id != 0) ? pred_id : tracker_data_query_resource_id (predicate);
3174 }
3175 if (!tracker_property_get_force_journal (property) &&
3176 g_strcmp0 (graph, TRACKER_MINER_FS_GRAPH_URN) == 0) {
3177 /* do not journal this statement extracted from filesystem */
3178 TrackerProperty *damaged;
3179
3180 damaged = tracker_ontologies_get_property_by_uri (TRACKER_TRACKER_PREFIX "damaged");
3181 tracker_db_journal_append_update_statement (graph_id,
3182 resource_buffer->id,
3183 tracker_property_get_id (damaged),
3184 "true");
3185 } else {
3186 tracker_db_journal_append_update_statement (graph_id,
3187 resource_buffer->id,
3188 pred_id,
3189 object);
3190 }
3191 }
3192 #endif /* DISABLE_JOURNAL */
3193 }
3194
3195 void
3196 tracker_data_update_statement (const gchar *graph,
3197 const gchar *subject,
3198 const gchar *predicate,
3199 const gchar *object,
3200 GError **error)
3201 {
3202 TrackerProperty *property;
3203
3204 g_return_if_fail (subject != NULL);
3205 g_return_if_fail (predicate != NULL);
3206 g_return_if_fail (in_transaction);
3207
3208 property = tracker_ontologies_get_property_by_uri (predicate);
3209 if (property != NULL) {
3210 if (object == NULL) {
3211 GError *new_error = NULL;
3212 if (property == tracker_ontologies_get_rdf_type ()) {
3213 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNSUPPORTED,
3214 "Using 'null' with '%s' is not supported", predicate);
3215 return;
3216 }
3217
3218 /* Flush upfront to make a null,x,null,y,z work: When x is set then
3219 * if a null comes, we need to be flushed */
3220
3221 tracker_data_update_buffer_flush (&new_error);
3222 if (new_error) {
3223 g_propagate_error (error, new_error);
3224 return;
3225 }
3226
3227 delete_all_objects (graph, subject, predicate, error);
3228
3229 /* Flush at the end to make null, x work. When x arrives the null
3230 * (delete all values of the multivalue) must be flushed */
3231
3232 tracker_data_update_buffer_flush (&new_error);
3233 if (new_error) {
3234 g_propagate_error (error, new_error);
3235 }
3236 } else {
3237 if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_RESOURCE) {
3238 tracker_data_update_statement_with_uri (graph, subject, predicate, object, error);
3239 } else {
3240 tracker_data_update_statement_with_string (graph, subject, predicate, object, error);
3241 }
3242 }
3243 } else {
3244 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
3245 "Property '%s' not found in the ontology", predicate);
3246 }
3247 }
3248
3249 void
3250 tracker_data_begin_transaction (GError **error)
3251 {
3252 TrackerDBInterface *iface;
3253
3254 g_return_if_fail (!in_transaction);
3255
3256 if (!tracker_db_manager_has_enough_space ()) {
3257 g_set_error (error, TRACKER_SPARQL_ERROR, TRACKER_SPARQL_ERROR_NO_SPACE,
3258 "There is not enough space on the file system for update operations");
3259 return;
3260 }
3261
3262 resource_time = time (NULL);
3263
3264 has_persistent = FALSE;
3265
3266 if (update_buffer.resource_cache == NULL) {
3267 update_buffer.resource_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
3268 /* used for normal transactions */
3269 update_buffer.resources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) resource_buffer_free);
3270 /* used for journal replay */
3271 update_buffer.resources_by_id = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) resource_buffer_free);
3272 }
3273
3274 resource_buffer = NULL;
3275 if (blank_buffer.table == NULL) {
3276 blank_buffer.table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
3277 }
3278
3279 iface = tracker_db_manager_get_db_interface ();
3280
3281 tracker_db_interface_execute_query (iface, NULL, "PRAGMA cache_size = %d", TRACKER_DB_CACHE_SIZE_UPDATE);
3282
3283 tracker_db_interface_start_transaction (iface);
3284
3285 #ifndef DISABLE_JOURNAL
3286 if (!in_journal_replay) {
3287 if (in_ontology_transaction) {
3288 GError *n_error = NULL;
3289 tracker_db_journal_start_ontology_transaction (resource_time, &n_error);
3290
3291 if (n_error) {
3292 /* No need for rollback here */
3293 tracker_db_interface_end_db_transaction (iface, NULL);
3294 g_propagate_error (error, n_error);
3295 return;
3296 }
3297
3298 } else {
3299 tracker_db_journal_start_transaction (resource_time);
3300 }
3301 }
3302 #endif /* DISABLE_JOURNAL */
3303
3304 iface = tracker_db_manager_get_db_interface ();
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3305
3306 in_transaction = TRUE;
3307 }
3308
3309 void
3310 tracker_data_begin_ontology_transaction (GError **error)
3311 {
3312 in_ontology_transaction = TRUE;
3313 tracker_data_begin_transaction (error);
3314 }
3315
3316 void
3317 tracker_data_begin_transaction_for_replay (time_t time, GError **error)
3318 {
3319 in_journal_replay = TRUE;
3320 tracker_data_begin_transaction (error);
3321 resource_time = time;
3322 }
3323
3324 void
3325 tracker_data_commit_transaction (GError **error)
3326 {
3327 TrackerDBInterface *iface;
3328 GError *actual_error = NULL;
3329
3330 g_return_if_fail (in_transaction);
3331
3332 iface = tracker_db_manager_get_db_interface ();
3333
3334 tracker_data_update_buffer_flush (&actual_error);
3335 if (actual_error) {
3336 tracker_data_rollback_transaction ();
3337 g_propagate_error (error, actual_error);
3338 return;
3339 }
3340
3341 tracker_db_interface_end_db_transaction (iface,
3342 &actual_error);
3343
3344 if (actual_error) {
3345 tracker_data_rollback_transaction ();
3346 g_propagate_error (error, actual_error);
3347 return;
3348 }
3349
3350 #ifndef DISABLE_JOURNAL
3351 if (!in_journal_replay) {
3352 if (has_persistent || in_ontology_transaction) {
3353 tracker_db_journal_commit_db_transaction (&actual_error);
3354 } else {
3355 /* If we only had transient properties, then we must not write
3356 * anything to the journal. So we roll it back, but only the
3357 * journal's part. */
3358 tracker_db_journal_rollback_transaction (&actual_error);
3359 }
3360
3361 if (actual_error) {
3362 /* Can't write in journal anymore; quite a serious problem */
3363 g_propagate_error (error, actual_error);
3364 /* Don't return, remainder of the function cleans things up */
3365 }
3366 }
3367 #endif /* DISABLE_JOURNAL */
3368
3369 get_transaction_modseq ();
3370 if (has_persistent && !in_ontology_transaction) {
3371 transaction_modseq++;
3372 }
3373
3374 resource_time = 0;
3375 in_transaction = FALSE;
3376 in_ontology_transaction = FALSE;
3377
3378 if (update_buffer.class_counts) {
3379 /* successful transaction, no need to rollback class counts,
3380 so remove them */
3381 g_hash_table_remove_all (update_buffer.class_counts);
3382 }
3383
3384 #if HAVE_TRACKER_FTS
3385 if (update_buffer.fts_ever_updated) {
3386 update_buffer.fts_ever_updated = FALSE;
3387 }
3388 #endif
3389
3390 tracker_db_interface_execute_query (iface, NULL, "PRAGMA cache_size = %d", TRACKER_DB_CACHE_SIZE_DEFAULT);
3391
3392 g_hash_table_remove_all (update_buffer.resources);
3393 g_hash_table_remove_all (update_buffer.resources_by_id);
3394 g_hash_table_remove_all (update_buffer.resource_cache);
3395
3396 in_journal_replay = FALSE;
3397 }
3398
3399 void
3400 tracker_data_notify_transaction (TrackerDataCommitType commit_type)
3401 {
3402 if (commit_callbacks) {
3403 guint n;
3404 for (n = 0; n < commit_callbacks->len; n++) {
3405 TrackerCommitDelegate *delegate;
3406 delegate = g_ptr_array_index (commit_callbacks, n);
3407 delegate->callback (commit_type, delegate->user_data);
3408 }
3409 }
3410 }
3411
3412 void
3413 tracker_data_rollback_transaction (void)
3414 {
3415 TrackerDBInterface *iface;
3416 GError *ignorable = NULL;
3417
3418 g_return_if_fail (in_transaction);
3419
3420 in_transaction = FALSE;
3421 in_ontology_transaction = FALSE;
3422
3423 iface = tracker_db_manager_get_db_interface ();
3424
3425 tracker_data_update_buffer_clear ();
3426
3427 tracker_db_interface_execute_query (iface, &ignorable, "ROLLBACK");
3428
3429 if (ignorable) {
3430 g_error_free (ignorable);
3431 }
3432
3433 tracker_db_interface_execute_query (iface, NULL, "PRAGMA cache_size = %d", TRACKER_DB_CACHE_SIZE_DEFAULT);
3434
3435 /* Runtime false in case of DISABLE_JOURNAL */
3436 if (!in_journal_replay) {
3437
3438 #ifndef DISABLE_JOURNAL
3439 tracker_db_journal_rollback_transaction (&ignorable);
3440
3441 if (ignorable) {
3442 /* Not sure if this is also ignorable: it's the close() of the
3443 * journal file failing (in case of TRANSACTION_FORMAT_ONTOLOGY) */
3444 g_warning ("Error ignored while rolling back transaction in journal: %s",
3445 ignorable->message ? ignorable->message : "No error given");
3446 g_error_free (ignorable);
3447 }
3448 #endif /* DISABLE_JOURNAL */
3449
3450
3451 if (rollback_callbacks) {
3452 guint n;
3453 for (n = 0; n < rollback_callbacks->len; n++) {
3454 TrackerCommitDelegate *delegate;
3455 delegate = g_ptr_array_index (rollback_callbacks, n);
3456 delegate->callback (TRUE, delegate->user_data);
3457 }
3458 }
3459 }
3460 }
3461
3462 static GVariant *
3463 update_sparql (const gchar *update,
3464 gboolean blank,
3465 GError **error)
3466 {
3467 GError *actual_error = NULL;
3468 TrackerSparqlQuery *sparql_query;
3469 GVariant *blank_nodes;
3470
3471 g_return_val_if_fail (update != NULL, NULL);
3472
3473 tracker_data_begin_transaction (&actual_error);
3474 if (actual_error) {
3475 g_propagate_error (error, actual_error);
3476 return NULL;
3477 }
3478
3479 sparql_query = tracker_sparql_query_new_update (update);
3480 blank_nodes = tracker_sparql_query_execute_update (sparql_query, blank, &actual_error);
3481 g_object_unref (sparql_query);
3482
3483 if (actual_error) {
3484 tracker_data_rollback_transaction ();
3485 g_propagate_error (error, actual_error);
3486 return NULL;
3487 }
3488
3489 tracker_data_commit_transaction (&actual_error);
3490 if (actual_error) {
3491 g_propagate_error (error, actual_error);
3492 return NULL;
3493 }
3494
3495 return blank_nodes;
3496 }
3497
3498 void
3499 tracker_data_update_sparql (const gchar *update,
3500 GError **error)
3501 {
3502 update_sparql (update, FALSE, error);
3503 }
3504
3505 GVariant *
3506 tracker_data_update_sparql_blank (const gchar *update,
3507 GError **error)
3508 {
3509 return update_sparql (update, TRUE, error);
3510 }
3511
3512 void
3513 tracker_data_load_turtle_file (GFile *file,
3514 GError **error)
3515 {
3516 gchar *path;
3517
3518 g_return_if_fail (G_IS_FILE (file) && g_file_is_native (file));
3519
3520 path = g_file_get_path (file);
3521 tracker_turtle_reader_load (path, error);
3522 g_free (path);
3523 }
3524
3525 void
3526 tracker_data_sync (void)
3527 {
3528 #ifndef DISABLE_JOURNAL
3529 tracker_db_journal_fsync ();
3530 #endif
3531 }
3532
3533 #ifndef DISABLE_JOURNAL
3534
3535 void
3536 tracker_data_replay_journal (TrackerBusyCallback busy_callback,
3537 gpointer busy_user_data,
3538 const gchar *busy_status,
3539 GError **error)
3540 {
3541 GError *journal_error = NULL;
3542 TrackerProperty *rdf_type = NULL;
3543 gint last_operation_type = 0;
3544 const gchar *uri;
3545 GError *n_error = NULL;
3546
3547
3548 rdf_type = tracker_ontologies_get_rdf_type ();
3549
3550 tracker_db_journal_reader_init (NULL, &n_error);
3551 if (n_error) {
3552 /* This is fatal (doesn't happen when file doesn't exist, does happen
3553 * when for some other reason the reader can't be created) */
3554 g_propagate_error (error, n_error);
3555 return;
3556 }
3557
3558 while (tracker_db_journal_reader_next (&journal_error)) {
3559 TrackerDBJournalEntryType type;
3560 const gchar *object;
3561 gint graph_id, subject_id, predicate_id, object_id;
3562
3563 type = tracker_db_journal_reader_get_type ();
3564 if (type == TRACKER_DB_JOURNAL_RESOURCE) {
3565 GError *new_error = NULL;
3566 TrackerDBInterface *iface;
3567 TrackerDBStatement *stmt;
3568 gint id;
3569
3570 tracker_db_journal_reader_get_resource (&id, &uri);
3571
3572 iface = tracker_db_manager_get_db_interface ();
3573
3574 stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_UPDATE, &new_error,
3575 "INSERT INTO Resource (ID, Uri) VALUES (?, ?)");
3576
3577 if (stmt) {
3578 tracker_db_statement_bind_int (stmt, 0, id);
3579 tracker_db_statement_bind_text (stmt, 1, uri);
3580 tracker_db_statement_execute (stmt, &new_error);
3581 g_object_unref (stmt);
3582 }
3583
3584 if (new_error) {
3585 g_warning ("Journal replay error: '%s'", new_error->message);
3586 g_error_free (new_error);
3587 }
3588
3589 } else if (type == TRACKER_DB_JOURNAL_START_TRANSACTION) {
3590 tracker_data_begin_transaction_for_replay (tracker_db_journal_reader_get_time (), NULL);
3591 } else if (type == TRACKER_DB_JOURNAL_END_TRANSACTION) {
3592 GError *new_error = NULL;
3593 tracker_data_update_buffer_might_flush (&new_error);
3594
3595 tracker_data_commit_transaction (&new_error);
3596 if (new_error) {
3597 /* Out of disk is an unrecoverable fatal error */
3598 if (g_error_matches (new_error, TRACKER_DB_INTERFACE_ERROR, TRACKER_DB_NO_SPACE)) {
3599 g_propagate_error (error, new_error);
3600 return;
3601 } else {
3602 g_warning ("Journal replay error: '%s'", new_error->message);
3603 g_clear_error (&new_error);
3604 }
3605 }
3606 } else if (type == TRACKER_DB_JOURNAL_INSERT_STATEMENT ||
3607 type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT) {
3608 GError *new_error = NULL;
3609 TrackerProperty *property = NULL;
3610
3611 tracker_db_journal_reader_get_statement (&graph_id, &subject_id, &predicate_id, &object);
3612
3613 if (last_operation_type == -1) {
3614 tracker_data_update_buffer_flush (&new_error);
3615 if (new_error) {
3616 g_warning ("Journal replay error: '%s'", new_error->message);
3617 g_clear_error (&new_error);
3618 }
3619 }
3620 last_operation_type = 1;
3621
3622 uri = tracker_ontologies_get_uri_by_id (predicate_id);
3623 if (uri) {
3624 property = tracker_ontologies_get_property_by_uri (uri);
3625 }
3626
3627 if (property) {
3628 resource_buffer_switch (NULL, graph_id, NULL, subject_id);
3629
3630 if (type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT) {
3631 cache_update_metadata_decomposed (property, object, 0, NULL, graph_id, &new_error);
3632 } else {
3633 cache_insert_metadata_decomposed (property, object, 0, NULL, graph_id, &new_error);
3634 }
3635 if (new_error) {
3636 g_warning ("Journal replay error: '%s'", new_error->message);
3637 g_clear_error (&new_error);
3638 }
3639
3640 } else {
3641 g_warning ("Journal replay error: 'property with ID %d doesn't exist'", predicate_id);
3642 }
3643
3644 } else if (type == TRACKER_DB_JOURNAL_INSERT_STATEMENT_ID ||
3645 type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT_ID) {
3646 GError *new_error = NULL;
3647 TrackerClass *class = NULL;
3648 TrackerProperty *property = NULL;
3649
3650 tracker_db_journal_reader_get_statement_id (&graph_id, &subject_id, &predicate_id, &object_id);
3651
3652 if (last_operation_type == -1) {
3653 tracker_data_update_buffer_flush (&new_error);
3654 if (new_error) {
3655 g_warning ("Journal replay error: '%s'", new_error->message);
3656 g_clear_error (&new_error);
3657 }
3658 }
3659 last_operation_type = 1;
3660
3661 uri = tracker_ontologies_get_uri_by_id (predicate_id);
3662 if (uri) {
3663 property = tracker_ontologies_get_property_by_uri (uri);
3664 }
3665
3666 if (property) {
3667 if (tracker_property_get_data_type (property) != TRACKER_PROPERTY_TYPE_RESOURCE) {
3668 g_warning ("Journal replay error: 'property with ID %d does not account URIs'", predicate_id);
3669 } else {
3670 resource_buffer_switch (NULL, graph_id, NULL, subject_id);
3671
3672 if (property == rdf_type) {
3673 uri = tracker_ontologies_get_uri_by_id (object_id);
3674 if (uri) {
3675 class = tracker_ontologies_get_class_by_uri (uri);
3676 }
3677 if (class) {
3678 cache_create_service_decomposed (class, NULL, graph_id);
3679 } else {
3680 g_warning ("Journal replay error: 'class with ID %d not found in the ontology'", object_id);
3681 }
3682 } else {
3683 GError *new_error = NULL;
3684
3685 /* add value to metadata database */
3686 if (type == TRACKER_DB_JOURNAL_UPDATE_STATEMENT_ID) {
3687 cache_update_metadata_decomposed (property, NULL, object_id, NULL, graph_id, &new_error);
3688 } else {
3689 cache_insert_metadata_decomposed (property, NULL, object_id, NULL, graph_id, &new_error);
3690 }
3691
3692 if (new_error) {
3693 g_warning ("Journal replay error: '%s'", new_error->message);
3694 g_error_free (new_error);
3695 }
3696 }
3697 }
3698 } else {
3699 g_warning ("Journal replay error: 'property with ID %d doesn't exist'", predicate_id);
3700 }
3701
3702 } else if (type == TRACKER_DB_JOURNAL_DELETE_STATEMENT) {
3703 GError *new_error = NULL;
3704 TrackerProperty *property = NULL;
3705
3706 tracker_db_journal_reader_get_statement (&graph_id, &subject_id, &predicate_id, &object);
3707
3708 if (last_operation_type == 1) {
3709 tracker_data_update_buffer_flush (&new_error);
3710 if (new_error) {
3711 g_warning ("Journal replay error: '%s'", new_error->message);
3712 g_clear_error (&new_error);
3713 }
3714 }
3715 last_operation_type = -1;
3716
3717 resource_buffer_switch (NULL, graph_id, NULL, subject_id);
3718
3719 uri = tracker_ontologies_get_uri_by_id (predicate_id);
3720 if (uri) {
3721 property = tracker_ontologies_get_property_by_uri (uri);
3722 }
3723
3724 if (property) {
3725 GError *new_error = NULL;
3726
3727 if (object && rdf_type == property) {
3728 TrackerClass *class = NULL;
3729
3730 uri = tracker_ontologies_get_uri_by_id (object_id);
3731 if (uri) {
3732 class = tracker_ontologies_get_class_by_uri (uri);
3733 }
3734 if (class != NULL) {
3735 cache_delete_resource_type (class, NULL, graph_id);
3736 } else {
3737 g_warning ("Journal replay error: 'class with '%s' not found in the ontology'", object);
3738 }
3739 } else {
3740 delete_metadata_decomposed (property, object, 0, &new_error);
3741 }
3742
3743 if (new_error) {
3744 g_warning ("Journal replay error: '%s'", new_error->message);
3745 g_error_free (new_error);
3746 }
3747
3748 } else {
3749 g_warning ("Journal replay error: 'property with ID %d doesn't exist'", predicate_id);
3750 }
3751
3752 } else if (type == TRACKER_DB_JOURNAL_DELETE_STATEMENT_ID) {
3753 GError *new_error = NULL;
3754 TrackerClass *class = NULL;
3755 TrackerProperty *property = NULL;
3756
3757 tracker_db_journal_reader_get_statement_id (&graph_id, &subject_id, &predicate_id, &object_id);
3758
3759 if (last_operation_type == 1) {
3760 tracker_data_update_buffer_flush (&new_error);
3761 if (new_error) {
3762 g_warning ("Journal replay error: '%s'", new_error->message);
3763 g_clear_error (&new_error);
3764 }
3765 }
3766 last_operation_type = -1;
3767
3768 uri = tracker_ontologies_get_uri_by_id (predicate_id);
3769 if (uri) {
3770 property = tracker_ontologies_get_property_by_uri (uri);
3771 }
3772
3773 if (property) {
3774
3775 resource_buffer_switch (NULL, graph_id, NULL, subject_id);
3776
3777 if (property == rdf_type) {
3778 uri = tracker_ontologies_get_uri_by_id (object_id);
3779 if (uri) {
3780 class = tracker_ontologies_get_class_by_uri (uri);
3781 }
3782 if (class) {
3783 cache_delete_resource_type (class, NULL, graph_id);
3784 } else {
3785 g_warning ("Journal replay error: 'class with ID %d not found in the ontology'", object_id);
3786 }
3787 } else {
3788 GError *new_error = NULL;
3789
3790 delete_metadata_decomposed (property, NULL, object_id, &new_error);
3791
3792 if (new_error) {
3793 g_warning ("Journal replay error: '%s'", new_error->message);
3794 g_error_free (new_error);
3795 }
3796 }
3797 } else {
3798 g_warning ("Journal replay error: 'property with ID %d doesn't exist'", predicate_id);
3799 }
3800 }
3801
3802 if (busy_callback) {
3803 busy_callback (busy_status,
3804 tracker_db_journal_reader_get_progress (),
3805 busy_user_data);
3806 }
3807 }
3808
3809
3810 if (journal_error) {
3811 GError *n_error = NULL;
3812 gsize size;
3813
3814 size = tracker_db_journal_reader_get_size_of_correct ();
3815 tracker_db_journal_reader_shutdown ();
3816
3817 tracker_db_journal_init (NULL, FALSE, &n_error);
3818 if (n_error) {
3819 g_clear_error (&journal_error);
3820 /* This is fatal (journal file not writable, etc) */
3821 g_propagate_error (error, n_error);
3822 return;
3823 }
3824 tracker_db_journal_truncate (size);
3825 tracker_db_journal_shutdown (&n_error);
3826
3827 if (n_error) {
3828 g_clear_error (&journal_error);
3829 /* This is fatal (close of journal file failed after truncate) */
3830 g_propagate_error (error, n_error);
3831 return;
3832 }
3833
3834 g_clear_error (&journal_error);
3835 } else {
3836 tracker_db_journal_reader_shutdown ();
3837 }
3838 }
3839
3840 #else
3841
3842 void
3843 tracker_data_replay_journal (TrackerBusyCallback busy_callback,
3844 gpointer busy_user_data,
3845 const gchar *busy_status,
3846 GError **error)
3847 {
3848 g_critical ("Not good. We disabled the journal and yet replaying it got called");
3849 }
3850
3851 #endif /* DISABLE_JOURNAL */