summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam@afuera.me.uk>2021-08-12 15:02:23 +0000
committerSam Thursfield <sam@afuera.me.uk>2021-08-12 15:02:23 +0000
commiteea3220f3787b934a628f897f0fd41cb59e4bfe2 (patch)
tree92312226568c9ab4aa37dde781af5980f97379bb
parentfc797a925f2dd680425ac64766d2a2000ad27caf (diff)
parent08e55cc1f2f59a0b9d8488f6b5d6ef10c255411f (diff)
downloadtracker-eea3220f3787b934a628f897f0fd41cb59e4bfe2.tar.gz
Merge branch 'prevent_saving_half_completed_db' into 'master'
Ignore saving half completed db See merge request GNOME/tracker!457
-rw-r--r--src/libtracker-data/tracker-data-manager.c161
-rw-r--r--src/libtracker-data/tracker-db-manager.c55
-rw-r--r--src/libtracker-data/tracker-db-manager.h6
-rw-r--r--src/tracker/tracker-export.c3
-rw-r--r--tests/functional-tests/meson.build1
-rw-r--r--tests/functional-tests/ontology-changes.py29
-rw-r--r--tests/functional-tests/ontology-rollback.py328
-rw-r--r--tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology33
-rw-r--r--tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology17
-rw-r--r--tests/functional-tests/test-ontologies/simple-updated/91-test.ontology33
-rw-r--r--tests/functional-tests/test-ontologies/simple-updated/92-test.ontology13
-rw-r--r--tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology40
-rw-r--r--tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology16
-rw-r--r--tests/functional-tests/test-ontologies/simple/91-test.ontology35
-rw-r--r--tests/functional-tests/test-ontologies/simple/92-test.ontology13
15 files changed, 664 insertions, 119 deletions
diff --git a/src/libtracker-data/tracker-data-manager.c b/src/libtracker-data/tracker-data-manager.c
index fce5b44cc..89deafc42 100644
--- a/src/libtracker-data/tracker-data-manager.c
+++ b/src/libtracker-data/tracker-data-manager.c
@@ -1819,19 +1819,21 @@ load_ontology_file (TrackerDataManager *manager,
static TrackerOntology*
get_ontology_from_file (TrackerDataManager *manager,
- GFile *file)
+ GFile *file,
+ GError **error)
{
const gchar *subject, *predicate, *object;
TrackerTurtleReader *reader;
- GError *error = NULL;
+ GError *internal_error = NULL;
GHashTable *ontology_uris;
TrackerOntology *ret = NULL;
- reader = tracker_turtle_reader_new_for_file (file, &error);
+ reader = tracker_turtle_reader_new_for_file (file, &internal_error);
- if (error) {
- g_critical ("Turtle parse error: %s", error->message);
- g_error_free (error);
+ if (internal_error) {
+ g_propagate_prefixed_error (error, internal_error,
+ "Turtle parse error: %s",
+ internal_error->message);
return NULL;
}
@@ -1842,7 +1844,7 @@ get_ontology_from_file (TrackerDataManager *manager,
while (tracker_turtle_reader_next (reader,
&subject, &predicate, &object,
- NULL, NULL, &error)) {
+ NULL, NULL, &internal_error)) {
if (g_strcmp0 (predicate, RDF_TYPE) == 0) {
if (g_strcmp0 (object, TRACKER_PREFIX_NRL "Ontology") == 0) {
TrackerOntology *ontology;
@@ -1859,21 +1861,21 @@ get_ontology_from_file (TrackerDataManager *manager,
} else if (g_strcmp0 (predicate, NRL_LAST_MODIFIED) == 0) {
TrackerOntology *ontology;
GDateTime *datetime;
- GError *error = NULL;
+ GError *parsing_error;
ontology = g_hash_table_lookup (ontology_uris, subject);
if (ontology == NULL) {
gchar *uri = g_file_get_uri (file);
g_critical ("%s: Unknown ontology %s", uri, subject);
g_free (uri);
- return NULL;
+ continue;
}
- datetime = tracker_date_new_from_iso8601 (object, NULL);
+ datetime = tracker_date_new_from_iso8601 (object, &parsing_error);
if (!datetime) {
- g_critical ("%s: error parsing nrl:lastModified: %s",
- subject, error->message);
- g_error_free (error);
+ g_propagate_prefixed_error (error, parsing_error,
+ "%s: error parsing nrl:lastModified: %s",
+ subject, parsing_error->message);
return NULL;
}
@@ -1891,15 +1893,19 @@ get_ontology_from_file (TrackerDataManager *manager,
g_hash_table_unref (ontology_uris);
g_object_unref (reader);
- if (error) {
- g_critical ("Turtle parse error: %s", error->message);
- g_error_free (error);
+ if (internal_error) {
+ g_propagate_prefixed_error (error, internal_error,
+ "Turtle parse error: %s",
+ internal_error->message);
+ return NULL;
}
if (ret == NULL) {
gchar *uri = g_file_get_uri (file);
- g_critical ("Ontology file has no nrl:lastModified header: %s", uri);
+ g_propagate_prefixed_error (error, internal_error,
+ "Ontology file has no nrl:lastModified header: %s", uri);
g_free (uri);
+ return NULL;
}
return ret;
@@ -3810,7 +3816,6 @@ tracker_data_manager_initable_init (GInitable *initable,
manager->db_manager = tracker_db_manager_new (manager->flags,
manager->cache_location,
- &is_create,
FALSE,
manager->select_cache_size,
manager->update_cache_size,
@@ -3823,6 +3828,8 @@ tracker_data_manager_initable_init (GInitable *initable,
return FALSE;
}
+ is_create = tracker_db_manager_is_first_time (manager->db_manager);
+
g_signal_connect (manager->db_manager, "setup-interface",
G_CALLBACK (setup_interface_cb), manager);
g_signal_connect (manager->db_manager, "update-interface",
@@ -3887,12 +3894,12 @@ tracker_data_manager_initable_init (GInitable *initable,
NULL,
NULL,
&ontology_error);
+ g_free (uri);
+
if (ontology_error) {
g_propagate_error (error, ontology_error);
- return FALSE;
+ goto rollback_newly_created_db;
}
-
- g_free (uri);
}
tracker_data_ontology_setup_db (manager, iface, "main", FALSE,
@@ -3906,18 +3913,18 @@ tracker_data_manager_initable_init (GInitable *initable,
if (internal_error) {
g_propagate_error (error, internal_error);
- return FALSE;
+ goto rollback_newly_created_db;
}
if (!tracker_data_manager_init_fts (manager, iface, "main", TRUE, &internal_error)) {
g_propagate_error (error, internal_error);
- return FALSE;
+ goto rollback_newly_created_db;
}
tracker_data_manager_initialize_iface (manager, iface, &internal_error);
if (internal_error) {
g_propagate_error (error, internal_error);
- return FALSE;
+ goto rollback_newly_created_db;
}
/* store ontology in database */
@@ -3925,13 +3932,6 @@ tracker_data_manager_initable_init (GInitable *initable,
import_ontology_file (manager, l->data, FALSE);
}
- tracker_data_commit_transaction (manager->data_update, &internal_error);
-
- if (internal_error) {
- g_propagate_error (error, internal_error);
- return FALSE;
- }
-
write_ontologies_gvdb (manager, TRUE /* overwrite */, NULL);
ontologies = tracker_ontologies_get_ontologies (manager->ontologies, &n_ontologies);
@@ -3947,6 +3947,13 @@ tracker_data_manager_initable_init (GInitable *initable,
}
}
+ tracker_data_commit_transaction (manager->data_update, &internal_error);
+
+ if (internal_error) {
+ g_propagate_error (error, internal_error);
+ goto rollback_newly_created_db;
+ }
+
g_list_free_full (sorted, g_object_unref);
sorted = NULL;
} else {
@@ -4059,16 +4066,14 @@ tracker_data_manager_initable_init (GInitable *initable,
gint last_mod;
/* Parse a TrackerOntology from ontology_file */
- ontology = get_ontology_from_file (manager, ontology_file);
+ ontology = get_ontology_from_file (manager, ontology_file, &internal_error);
- if (!ontology) {
- /* TODO: cope with full custom .ontology files: deal with this
- * error gracefully. App devs might install wrong ontology files
- * and we shouldn't critical() due to this. */
+ if (internal_error) {
gchar *uri = g_file_get_uri (ontology_file);
- g_critical ("Can't get ontology from file: %s", uri);
+ g_propagate_prefixed_error (error, internal_error,
+ "Can't get ontology from file: %s", uri);
g_free (uri);
- continue;
+ return FALSE;
}
ontology_uri = tracker_ontology_get_uri (ontology);
@@ -4127,34 +4132,17 @@ tracker_data_manager_initable_init (GInitable *initable,
seen_properties,
&ontology_error);
- if (g_error_matches (ontology_error,
- TRACKER_DATA_ONTOLOGY_ERROR,
- TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) {
- g_warning ("%s", ontology_error->message);
- g_error_free (ontology_error);
+ if (ontology_error) {
+ g_propagate_error (error, ontology_error);
g_clear_pointer (&seen_classes, g_ptr_array_unref);
g_clear_pointer (&seen_properties, g_ptr_array_unref);
- tracker_data_ontology_import_finished (manager);
-
- /* as we're processing an ontology change,
- transaction is guaranteed to be started */
- tracker_data_rollback_transaction (manager->data_update);
- if (ontos_table) {
- g_hash_table_unref (ontos_table);
- }
if (ontos) {
g_list_free_full (ontos, g_object_unref);
}
- g_object_unref (manager->ontology_location);
-
- goto skip_ontology_check;
- }
-
- if (ontology_error) {
- g_critical ("Fatal error dealing with ontology changes: %s", ontology_error->message);
- g_error_free (ontology_error);
+
+ goto rollback_db_changes;
}
to_reload = g_list_prepend (to_reload, l->data);
@@ -4184,28 +4172,17 @@ tracker_data_manager_initable_init (GInitable *initable,
seen_properties,
&ontology_error);
- if (g_error_matches (ontology_error,
- TRACKER_DATA_ONTOLOGY_ERROR,
- TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) {
- g_warning ("%s", ontology_error->message);
- g_error_free (ontology_error);
+ if (ontology_error) {
+ g_propagate_error (error, ontology_error);
g_clear_pointer (&seen_classes, g_ptr_array_unref);
g_clear_pointer (&seen_properties, g_ptr_array_unref);
- tracker_data_ontology_import_finished (manager);
-
- /* as we're processing an ontology change,
- transaction is guaranteed to be started */
- tracker_data_rollback_transaction (manager->data_update);
- if (ontos_table) {
- g_hash_table_unref (ontos_table);
- }
if (ontos) {
g_list_free_full (ontos, g_object_unref);
}
-
- goto skip_ontology_check;
+
+ goto rollback_db_changes;
}
if (ontology_error) {
@@ -4302,28 +4279,17 @@ tracker_data_manager_initable_init (GInitable *initable,
}
}
- if (g_error_matches (ontology_error,
- TRACKER_DATA_ONTOLOGY_ERROR,
- TRACKER_DATA_UNSUPPORTED_ONTOLOGY_CHANGE)) {
- g_warning ("%s", ontology_error->message);
- g_error_free (ontology_error);
+ if (ontology_error) {
+ g_propagate_error (error, ontology_error);
g_clear_pointer (&seen_classes, g_ptr_array_unref);
g_clear_pointer (&seen_properties, g_ptr_array_unref);
- tracker_data_ontology_import_finished (manager);
- /* as we're processing an ontology change,
- transaction is guaranteed to be started */
- tracker_data_rollback_transaction (manager->data_update);
-
- if (ontos_table) {
- g_hash_table_unref (ontos_table);
- }
if (ontos) {
g_list_free_full (ontos, g_object_unref);
}
-
- goto skip_ontology_check;
+
+ goto rollback_db_changes;
}
if (ontology_error) {
@@ -4362,8 +4328,6 @@ tracker_data_manager_initable_init (GInitable *initable,
}
-
-skip_ontology_check:
if (!read_only && is_create) {
tracker_db_manager_set_current_locale (manager->db_manager);
tracker_db_manager_tokenizer_update (manager->db_manager);
@@ -4398,6 +4362,21 @@ skip_ontology_check:
tracker_data_manager_update_status (manager, "Idle");
return TRUE;
+
+rollback_db_changes:
+ tracker_data_ontology_import_finished (manager);
+ tracker_data_rollback_transaction (manager->data_update);
+
+ if (ontos_table) {
+ g_hash_table_unref (ontos_table);
+ }
+
+ return FALSE;
+
+rollback_newly_created_db:
+ tracker_data_rollback_transaction (manager->data_update);
+ tracker_db_manager_rollback_db_creation (manager->db_manager, NULL);
+ return FALSE;
}
static gboolean
diff --git a/src/libtracker-data/tracker-db-manager.c b/src/libtracker-data/tracker-db-manager.c
index 8485af62e..63a7989b0 100644
--- a/src/libtracker-data/tracker-db-manager.c
+++ b/src/libtracker-data/tracker-db-manager.c
@@ -128,6 +128,7 @@ struct _TrackerDBManager {
TrackerDBManagerFlags flags;
guint s_cache_size;
guint u_cache_size;
+ gboolean first_time;
gpointer vtab_data;
@@ -152,6 +153,12 @@ static TrackerDBInterface *tracker_db_manager_create_db_interface (TrackerDBMa
static TrackerDBInterface * init_writable_db_interface (TrackerDBManager *db_manager);
+gboolean
+tracker_db_manager_is_first_time (TrackerDBManager *db_manager)
+{
+ return db_manager->first_time;
+}
+
TrackerDBManagerFlags
tracker_db_manager_get_flags (TrackerDBManager *db_manager,
guint *select_cache_size,
@@ -456,6 +463,29 @@ tracker_db_manager_db_exists (GFile *cache_location)
return db_exists;
}
+int
+tracker_db_manager_rollback_db_creation (TrackerDBManager *db_manager,
+ GError **error)
+{
+ gchar *dir;
+ gchar *filename;
+ int ret;
+
+ g_return_val_if_fail (db_manager->first_time, -1);
+
+ if ((db_manager->flags & TRACKER_DB_MANAGER_IN_MEMORY) != 0)
+ return 0;
+
+ dir = g_file_get_path (db_manager->cache_location);
+ filename = g_build_filename (dir, db_base.file, NULL);
+
+ ret = g_unlink (filename);
+
+ g_free (dir);
+ g_free (filename);
+ return ret;
+}
+
static gboolean
db_check_integrity (TrackerDBManager *db_manager)
{
@@ -513,16 +543,15 @@ db_check_integrity (TrackerDBManager *db_manager)
TrackerDBManager *
tracker_db_manager_new (TrackerDBManagerFlags flags,
- GFile *cache_location,
- gboolean *first_time,
- gboolean shared_cache,
- guint select_cache_size,
- guint update_cache_size,
- TrackerBusyCallback busy_callback,
- gpointer busy_user_data,
+ GFile *cache_location,
+ gboolean shared_cache,
+ guint select_cache_size,
+ guint update_cache_size,
+ TrackerBusyCallback busy_callback,
+ gpointer busy_user_data,
GObject *iface_data,
gpointer vtab_data,
- GError **error)
+ GError **error)
{
TrackerDBManager *db_manager;
TrackerDBVersion version;
@@ -534,10 +563,8 @@ tracker_db_manager_new (TrackerDBManagerFlags flags,
db_manager = g_object_new (TRACKER_TYPE_DB_MANAGER, NULL);
db_manager->vtab_data = vtab_data;
- /* First set defaults for return values */
- if (first_time) {
- *first_time = FALSE;
- }
+ /* Set default value for first_time */
+ db_manager->first_time = FALSE;
/* Set up locations */
db_manager->flags = flags;
@@ -618,9 +645,7 @@ tracker_db_manager_new (TrackerDBManagerFlags flags,
}
if (need_to_create) {
- if (first_time) {
- *first_time = TRUE;
- }
+ db_manager->first_time = TRUE;
if ((db_manager->flags & TRACKER_DB_MANAGER_IN_MEMORY) == 0 &&
!tracker_file_system_has_enough_space (db_manager->data_dir, TRACKER_DB_MIN_REQUIRED_SPACE, TRUE)) {
diff --git a/src/libtracker-data/tracker-db-manager.h b/src/libtracker-data/tracker-db-manager.h
index b8fd04aa4..62bf3b944 100644
--- a/src/libtracker-data/tracker-db-manager.h
+++ b/src/libtracker-data/tracker-db-manager.h
@@ -50,11 +50,13 @@ typedef enum {
TRACKER_DB_MANAGER_SKIP_VERSION_CHECK = 1 << 9,
} TrackerDBManagerFlags;
+int tracker_db_manager_rollback_db_creation (TrackerDBManager *db_manager,
+ GError **error);
+
gboolean tracker_db_manager_db_exists (GFile *cache_location);
TrackerDBManager *tracker_db_manager_new (TrackerDBManagerFlags flags,
GFile *cache_location,
- gboolean *first_time,
gboolean shared_cache,
guint select_cache_size,
guint update_cache_size,
@@ -69,6 +71,8 @@ TrackerDBInterface *tracker_db_manager_get_writable_db_interface (TrackerDBManag
gboolean tracker_db_manager_has_enough_space (TrackerDBManager *db_manager);
+gboolean tracker_db_manager_is_first_time (TrackerDBManager *db_manager);
+
TrackerDBManagerFlags
tracker_db_manager_get_flags (TrackerDBManager *db_manager,
guint *select_cache_size,
diff --git a/src/tracker/tracker-export.c b/src/tracker/tracker-export.c
index ead5de036..38c05f941 100644
--- a/src/tracker/tracker-export.c
+++ b/src/tracker/tracker-export.c
@@ -390,8 +390,7 @@ export_2to3_with_query (const gchar *query,
db_manager = tracker_db_manager_new (TRACKER_DB_MANAGER_READONLY |
TRACKER_DB_MANAGER_SKIP_VERSION_CHECK,
- store,
- NULL, FALSE,
+ store, FALSE,
1, 1, NULL, NULL, NULL, NULL, &inner_error);
if (inner_error) {
diff --git a/tests/functional-tests/meson.build b/tests/functional-tests/meson.build
index 934a59833..35c98c0db 100644
--- a/tests/functional-tests/meson.build
+++ b/tests/functional-tests/meson.build
@@ -34,6 +34,7 @@ functional_tests = [
'notifier',
'collation',
'ontology-changes',
+ 'ontology-rollback',
'cli',
'portal',
]
diff --git a/tests/functional-tests/ontology-changes.py b/tests/functional-tests/ontology-changes.py
index 22cf767ac..f1021a805 100644
--- a/tests/functional-tests/ontology-changes.py
+++ b/tests/functional-tests/ontology-changes.py
@@ -181,7 +181,8 @@ class PropertyRangeStringToDate (OntologyChangeTestTemplate):
Change the range of a property from string to date. There shouldn't be any data loss.
"""
- @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for http://example.org/ns#a_string: can't change rdfs:range (old=http://www.w3.org/2001/XMLSchema#dateTime, attempted new=http://www.w3.org/2001/XMLSchema#string)")
+ # Conversion from string to dateTime is not allowed
+ @ut.expectedFailure
def test_property_range_string_to_date(self):
self.template_test_ontology_change()
@@ -211,7 +212,8 @@ class PropertyRangeDateToString (OntologyChangeTestTemplate):
Change the range of a property from date to string. There shouldn't be any data loss.
"""
- @ut.skip("fails with: basic-future/91-test.ontology: Unsupported ontology change for http://example.org/ns#a_string: can't change rdfs:range (old=http://www.w3.org/2001/XMLSchema#dateTime, attempted new=http://www.w3.org/2001/XMLSchema#string)")
+ # Conversion from dateTime to string is not supported yet
+ @ut.expectedFailure
def test_property_range_date_to_string(self):
self.template_test_ontology_change()
@@ -240,7 +242,7 @@ class PropertyRangeIntToString (OntologyChangeTestTemplate):
"""
Change the range of a property from int to string. There shouldn't be any data loss.
"""
- @ut.skip("Fails with: Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'This property is integer in basic here is string', new value: 'Property to test the changes string/int')")
+
def test_property_range_int_to_str(self):
self.template_test_ontology_change()
@@ -269,7 +271,6 @@ class PropertyRangeStringToInt (OntologyChangeTestTemplate):
Change the range of a property from string to int. There shouldn't be any data loss.
"""
- @ut.skip("Fails with: Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'Property to test the changes string/int', new value: 'This property is integer in basic here is string')")
def test_property_range_str_to_int(self):
self.template_test_ontology_change()
@@ -573,7 +574,7 @@ class OntologyAddPropertyTest (OntologyChangeTestTemplate):
"""
Add new properties in the ontology, with/without super prop and different ranges and cardinalities
"""
- @ut.skip("Fails with:Unable to insert multiple values for subject `http://example.org/ns#a_int' and single valued property `rdfs:comment' (old_value: 'This property is integer in basic here is string', new value: 'Property to test the changes string/int')")
+
def test_ontology_add_property(self):
self.template_test_ontology_change()
@@ -668,7 +669,7 @@ class DomainIndexAddTest (OntologyChangeTestTemplate):
"""
Add nrl:domainIndex to a class and check there is no data loss.
"""
- @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:b_property: can't change rdfs:domain (old=test:A, attempted new=test:B) ")
+
def test_domain_index_add(self):
self.template_test_ontology_change()
@@ -758,7 +759,9 @@ class SuperclassRemovalTest (OntologyChangeTestTemplate):
"""
Remove the superclass relation between two classes
"""
- @ut.skip("Fails with: Unsupported ontology change for http://example.org/ns#B: can't change rdfs:subClassOf (old=-, attempted new=-)")
+
+ # Changes to rdfs:subClassOf are not allowed
+ @ut.expectedFailure
def test_superclass_removal(self):
self.template_test_ontology_change()
@@ -801,7 +804,9 @@ class SuperclassAdditionTest (OntologyChangeTestTemplate):
"""
Add a superclass to a class with no superclass previously
"""
- @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:B: can't change rdfs:subClassOf (old=-, attempted new=test:A)")
+
+ # Changes to rdfs:subClassOf are not allowed
+ @ut.expectedFailure
def test_superclass_addition(self):
self.template_test_ontology_change()
@@ -844,7 +849,9 @@ class PropertyPromotionTest (OntologyChangeTestTemplate):
"""
Move a property to the superclass
"""
- @ut.skip("Fails with: basic-future/91-test.ontology: Unsupported ontology change for test:b_property: can't change rdfs:domain (old=test:A, attempted new=test:B)")
+
+ # Changes to rdfs:domain are not allowed
+ @ut.expectedFailure
def test_property_promotion(self):
self.template_test_ontology_change()
@@ -880,7 +887,9 @@ class PropertyRelegationTest (OntologyChangeTestTemplate):
"""
Move a property to the subclass
"""
- @ut.skip("Fails")
+
+ # Changes to rdfs:domain are not allowed
+ @ut.expectedFailure
def test_property_relegation(self):
self.template_test_ontology_change()
diff --git a/tests/functional-tests/ontology-rollback.py b/tests/functional-tests/ontology-rollback.py
new file mode 100644
index 000000000..cb3e8307d
--- /dev/null
+++ b/tests/functional-tests/ontology-rollback.py
@@ -0,0 +1,328 @@
+# Copyright (C) 2021, Abanoub Ghadban <abanoub.gdb@gmail.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+#
+
+"""
+Test how the database is kept in consistent state when errors occur.
+"""
+
+import gi
+gi.require_version('Tracker', '3.0')
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import Tracker
+
+import os
+import pathlib
+import shutil
+import re
+import tempfile
+import time
+import glob
+import unittest as ut
+
+import trackertestutils.dconf
+import trackertestutils.helpers
+
+import configuration as cfg
+import fixtures
+
+
+RDFS_RANGE = "http://www.w3.org/2000/01/rdf-schema#range"
+XSD_DATETIME = "http://www.w3.org/2001/XMLSchema#dateTime"
+XSD_STRING = "http://www.w3.org/2001/XMLSchema#string"
+XSD_INTEGER = "http://www.w3.org/2001/XMLSchema#integer"
+
+TEST_PREFIX = "http://example.org/ns#"
+TEST2_PREFIX = "http://example2.org/ns#"
+
+
+class OntologyRollbackTestTemplate (ut.TestCase):
+ """
+ Template class for the ontology rollback tests. It ensures that the db
+ is left in a consistent state when building or updating the ontology fails.
+ The tests are subclasses of this, implementing these methods:
+
+ * set_ontology_dirs
+ * insert_data_into_first_ontology
+ * validate_first_ontology_status
+ * insert_data_into_second_ontology
+ * validate_second_ontology_status
+
+ and adding a method 'test_x_y_z' to be invoked by unittest.
+
+ Check doc in those methods for the specific details.
+ """
+
+ def setUp(self):
+ self.tmpdir = tempfile.mkdtemp(prefix='tracker-test-')
+
+ def tearDown(self):
+ shutil.rmtree(self.tmpdir, ignore_errors=True)
+
+ def ontology_path(self, param):
+ return pathlib.Path(__file__).parent.joinpath('test-ontologies', param)
+
+ def template_test_ontology_rollback(self):
+ self.set_ontology_dirs()
+
+ self.__assert_different_ontology_dates(self.FIRST_ONTOLOGY_DIR, self.SECOND_ONTOLOGY_DIR)
+ self.__assert_same_ontology_dates_if_exist(self.FIRST_ONTOLOGY_DIR, self.FIRST_MALFORMED_ONTOLOGY_DIR)
+ self.__assert_same_ontology_dates_if_exist(self.SECOND_ONTOLOGY_DIR, self.SECOND_MALFORMED_ONTOLOGY_DIR)
+
+ # Make sure that the connection fails when the malformed ontology is used
+ with self.assertRaises(GLib.GError):
+ Tracker.SparqlConnection.new(
+ Tracker.SparqlConnectionFlags.NONE,
+ Gio.File.new_for_path(self.tmpdir),
+ Gio.File.new_for_path(str(self.ontology_path(self.FIRST_MALFORMED_ONTOLOGY_DIR))),
+ None)
+
+ # Use the error-free first ontology. It should work now
+ conn1 = Tracker.SparqlConnection.new(
+ Tracker.SparqlConnectionFlags.NONE,
+ Gio.File.new_for_path(self.tmpdir),
+ Gio.File.new_for_path(str(self.ontology_path(self.FIRST_ONTOLOGY_DIR))),
+ None)
+
+ self.tracker = trackertestutils.helpers.StoreHelper(conn1)
+ self.insert_data_into_first_ontology()
+ self.validate_first_ontology_status()
+
+ conn1.close()
+
+ # Reopen the local store with the second malformed set of ontologies.
+ # The connection should fail
+ with self.assertRaises(GLib.GError):
+ Tracker.SparqlConnection.new(
+ Tracker.SparqlConnectionFlags.NONE,
+ Gio.File.new_for_path(self.tmpdir),
+ Gio.File.new_for_path(str(self.ontology_path(self.SECOND_MALFORMED_ONTOLOGY_DIR))),
+ None)
+
+ conn2 = Tracker.SparqlConnection.new(
+ Tracker.SparqlConnectionFlags.NONE,
+ Gio.File.new_for_path(self.tmpdir),
+ Gio.File.new_for_path(str(self.ontology_path(self.SECOND_ONTOLOGY_DIR))),
+ None)
+ self.tracker = trackertestutils.helpers.StoreHelper(conn2)
+
+ self.insert_data_into_second_ontology()
+ self.validate_second_ontology_status()
+
+ conn2.close()
+
+ def set_ontology_dirs(self):
+ """
+ Implement this method in the subclass setting values for:
+ self.FIRST_MALFORMED_ONTOLOGY_DIR,
+ self.FIRST_ONTOLOGY_DIR,
+ self.SECOND_MALFORMED_ONTOLOGY_DIR and
+ self.SECOND_ONTOLOGY_DIR
+ """
+ raise Exception("Subclasses must implement 'set_ontology_dir'")
+
+ def insert_data_into_first_ontology(self):
+ """
+ Store some data with the FIRST ontology
+ Make sure that it can't insert data into properties and classes that
+ exist in the malformed ontology only
+ """
+ raise Exception("Subclasses must implement 'insert_data_into_first_ontology'")
+
+ def validate_first_ontology_status(self):
+ """
+ This is called after inserting the data into the first ontology
+ Check that the data is inserted successfully and the database schema
+ matches that of the correct ontology and the schema of the malformed
+ ontology is completely rolled back
+ """
+ raise Exception("Subclasses must implement 'validate_first_ontology_status'")
+
+ def insert_data_into_second_ontology(slef):
+ """
+ Store some data with the SECOND ontology
+ Make sure that it can't insert data into properties and classes that
+ exist in the malformed second ontology only
+ """
+ raise Exception("Subclasses must implement 'insert_data_into_second_ontology'")
+
+ def validate_second_ontology_status(self):
+ """
+ This is called after inserting the data into the second ontology
+ Check that the data is inserted successfully and the database schema
+ matches that of the correct second ontology and the schema of the malformed
+ second ontology is completely rolled back
+ """
+ raise Exception("Subclasses must implement 'validate_second_ontology_status'")
+
+ def assertInDbusResult(self, member, dbus_result, column=0):
+ """
+ Convenience assertion used in these tests
+ """
+ for row in dbus_result:
+ if member == row[column]:
+ return
+ # This is going to fail with pretty printing
+ self.assertIn(member, dbus_result)
+
+ def assertNotInDbusResult(self, member, dbus_result, column=0):
+ """
+ Convenience assertion used in these tests
+ """
+ for row in dbus_result:
+ if member == str(row[column]):
+ # This is going to fail with pretty printing
+ self.fail("'%s' wasn't supposed to be in '%s'" %
+ (member, dbus_result))
+ return
+
+ def __get_ontology_date(self, ontology_path):
+ """
+ Returns the value of nrl:lastModified in the ontology file
+ """
+ ISO9601_REGEX = "(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)"
+
+ with open(ontology_path, 'r') as f:
+ for line in f:
+ if "nrl:lastModified" in line:
+ getmodtime = re.compile(
+ 'nrl:lastModified\ \"' + ISO9601_REGEX + '\"')
+ modtime_match = getmodtime.search(line)
+
+ if (modtime_match):
+ nao_date = modtime_match.group(1)
+ return time.strptime(nao_date, "%Y-%m-%dT%H:%M:%SZ")
+ else:
+ return None
+
+ def __assert_all_ontology_dates(self, first_dir, second_dir, assertion_fn):
+ """
+ Compare between the nrl:lastModified of all ontology files in first_dir and second_dir
+ assertion_fn is used to compare between the two dates and make sure they are valid
+ """
+ ontology_files = glob.glob(str(self.ontology_path(first_dir).joinpath("*.ontology")))
+
+ for first_ontology in ontology_files:
+ ontology_fname = os.path.basename(first_ontology)
+ second_ontology = self.ontology_path(second_dir).joinpath(ontology_fname)
+
+ first_date = self.__get_ontology_date(first_ontology)
+ second_date = self.__get_ontology_date(second_ontology)
+
+ try:
+ assertion_fn(first_date, second_date)
+ except AssertionError as e:
+ self.fail("%s: %s" % (first_ontology, e.msg))
+
+ def __assert_different_ontology_dates(self, first_dir, second_dir):
+ """
+ Asserts that nrl:lastModified of ontologies in second_dir are more recent
+ than that in first_dir
+ """
+ def assert_different_dates(first_date, second_date):
+ if first_date >= second_date:
+ self.fail("nrl:lastModified is not more recent in the second ontology")
+
+ self.__assert_all_ontology_dates(first_dir, second_dir, assert_different_dates)
+
+ def __assert_same_ontology_dates_if_exist(self, first_dir, second_dir):
+ """
+ Asserts that nrl:lastModified of ontologies in first_dir are the same as
+ that in second_dir if they exist and are valid
+ """
+ def assert_different_dates(first_date, second_date):
+ if second_date is not None and first_date != second_date:
+ self.fail("nrl:lastModified is not the same as in the second ontology")
+
+ self.__assert_all_ontology_dates(first_dir, second_dir, assert_different_dates)
+
+class SimpleOntologyRollback (OntologyRollbackTestTemplate):
+ def test_simple_ontology_rollback(self):
+ self.template_test_ontology_rollback()
+
+ def set_ontology_dirs(self):
+ self.FIRST_MALFORMED_ONTOLOGY_DIR = "simple-with-errors"
+ self.FIRST_ONTOLOGY_DIR = "simple"
+ self.SECOND_MALFORMED_ONTOLOGY_DIR = "simple-updated-with-errors"
+ self.SECOND_ONTOLOGY_DIR = "simple-updated"
+
+ def insert_data_into_first_ontology(self):
+ # test:a_tmp_prop only appeared in the malformed ontology
+ with self.assertRaises(GLib.GError):
+ self.tracker.update(
+ "INSERT { <t1.1> a test:A ; test:a_tmp_prop 5. }")
+
+ # The domain of test:b_a_domain should be test:A no test:B
+ with self.assertRaises(GLib.GError):
+ self.tracker.update(
+ "INSERT { <t1.2> a test:B ; test:b_a_domain 5. }")
+
+ # The domain should be test:B and range be test:A
+ with self.assertRaises(GLib.GError):
+ self.tracker.update(
+ "INSERT { <t1.3> a test:B . <t1.4> a test:A ; test:a_b_domain_range <t1.3>. }")
+
+ # test2:C should be subclass of test:B not test:A
+ with self.assertRaises(GLib.GError):
+ self.tracker.update(
+ "INSERT { <t1.5> a test2:C ; test:b_a_domain 5. }")
+
+ self.tracker.update(
+ "INSERT { <t1.6> a test:A ; test:b_a_domain 5. }")
+
+ self.tracker.update(
+ "INSERT { <t1.7> a test:B ; test:a_b_domain_range <t1.6>. }")
+
+ self.tracker.update(
+ "INSERT { <t1.8> a test2:C ; test:b_range_boolean_string \"String\". }")
+
+ def validate_first_ontology_status(self):
+ result = self.tracker.query(
+ "SELECT ?p WHERE { ?p a rdf:Property. }")
+ self.assertNotInDbusResult(TEST_PREFIX + "a_tmp_prop", result)
+
+ result = self.tracker.query(
+ "SELECT ?d ?r WHERE { test:a_b_domain_range rdfs:domain ?d ; rdfs:range ?r }")
+ self.assertEqual(result[0][0], TEST_PREFIX + "B")
+ self.assertEqual(result[0][1], TEST_PREFIX + "A")
+
+ self.assertFalse(self.tracker.ask("ASK { <%s> a rdfs:Class}" % (
+ TEST2_PREFIX + "D")), "test2:D class is not rolled back on failure")
+
+ def insert_data_into_second_ontology(self):
+ # Domain was test:B in the malformed ontology
+ # and became test:A in the error-free ontology
+ with self.assertRaises(GLib.GError):
+ self.tracker.update(
+ "INSERT { <t2.1> a test:B ; test:a_b_domain 5. }")
+
+ self.tracker.update(
+ "INSERT { <t2.2> a test:A ; test:a_b_domain 5. }")
+
+ def validate_second_ontology_status(self):
+ result = self.tracker.query(
+ "SELECT ?d ?r WHERE { test:a_b_domain rdfs:domain ?d ; rdfs:range ?r }")
+ self.assertEqual(result[0][0], TEST_PREFIX + "A")
+ self.assertEqual(result[0][1], XSD_INTEGER)
+
+ result = self.tracker.query(
+ "SELECT ?v WHERE { <t2.2> test:a_b_domain ?v }")
+ self.assertEqual(result[0][0], "5")
+
+if __name__ == "__main__":
+ fixtures.tracker_test_main()
diff --git a/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology b/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology
new file mode 100644
index 000000000..e62f6981b
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-updated-with-errors/91-test.ontology
@@ -0,0 +1,33 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2011-03-24T11:00:04Z" ;
+ nrl:prefix "test" .
+
+test:A a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:B a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:b_range_boolean_string a rdf:Property ;
+ rdfs:range xsd:string ;
+ rdfs:domain test:B .
+
+test:b_a_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:A .
+
+test:a_b_domain_range a rdf:Property ;
+ rdfs:range test:A ;
+ rdfs:domain test:B .
+
+# ******* UPDATES *******
+test:a_b_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:B . # Will be changed to test:A
+# ***********************
diff --git a/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology b/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology
new file mode 100644
index 000000000..436a2cdf8
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-updated-with-errors/92-test.ontology
@@ -0,0 +1,17 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix test2: <http://example2.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test2: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2011-03-24T11:00:04Z" ;
+ nrl:prefix "test2" .
+
+test2:C a rdfs:Class ;
+ rdfs:subClassOf test:B . # Was test:A
+
+# ******* MALFORMED PREFIX *******
+malformed:x a rdfs:Class .
+# ********************************
diff --git a/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology b/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology
new file mode 100644
index 000000000..c63cf232d
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-updated/91-test.ontology
@@ -0,0 +1,33 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2011-03-24T11:00:04Z" ;
+ nrl:prefix "test" .
+
+test:A a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:B a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:b_range_boolean_string a rdf:Property ;
+ rdfs:range xsd:string ;
+ rdfs:domain test:B .
+
+test:b_a_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:A .
+
+test:a_b_domain_range a rdf:Property ;
+ rdfs:range test:A ;
+ rdfs:domain test:B .
+
+# ******* UPDATES *******
+test:a_b_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:A . # Was test:B
+# ***********************
diff --git a/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology b/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology
new file mode 100644
index 000000000..c1bfb549e
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-updated/92-test.ontology
@@ -0,0 +1,13 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix test2: <http://example2.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test2: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2011-03-24T11:00:04Z" ;
+ nrl:prefix "test2" .
+
+test2:C a rdfs:Class ;
+ rdfs:subClassOf test:B .
diff --git a/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology b/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology
new file mode 100644
index 000000000..daabc6a6b
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-with-errors/91-test.ontology
@@ -0,0 +1,40 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2010-03-24T11:00:04Z" ;
+ nrl:prefix "test" .
+
+test:A a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:B a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource ;
+ rdfs:comment "Class B" . # This will be removed
+
+# The property will be removed in the error-free ontology
+test:a_tmp_prop a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:A .
+
+# The range will be changed
+test:b_range_boolean_string a rdf:Property ;
+ rdfs:range xsd:boolean ;
+ rdfs:domain test:B .
+
+# The domain will be changed
+test:b_a_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:B .
+
+# ***** MALFORMED STATEMENT ******
+test:x a rdf:Property rdfs:Class ;
+# ********************************
+
+# The domain and range will be changed
+test:a_b_domain_range a rdf:Property ;
+ rdfs:range test:B ;
+ rdfs:domain test:A .
diff --git a/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology b/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology
new file mode 100644
index 000000000..942436435
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple-with-errors/92-test.ontology
@@ -0,0 +1,16 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix test2: <http://example2.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test2: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2010-03-24T11:00:04Z" ;
+ nrl:prefix "test2" .
+
+test2:C a rdfs:Class ;
+ rdfs:subClassOf test:A . # Will be changed to test:B
+
+# Will be removed
+test2:D a rdfs:Class ;
diff --git a/tests/functional-tests/test-ontologies/simple/91-test.ontology b/tests/functional-tests/test-ontologies/simple/91-test.ontology
new file mode 100644
index 000000000..30b045ad6
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple/91-test.ontology
@@ -0,0 +1,35 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2010-03-24T11:00:04Z" ;
+ nrl:prefix "test" .
+
+test:A a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+test:B a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+# The property is removed
+# test:a_tmp_prop a rdf:Property ;
+# rdfs:range xsd:integer ;
+# rdfs:domain test:A .
+
+# The range changed from xsd:boolean to xsd:string
+test:b_range_boolean_string a rdf:Property ;
+ rdfs:range xsd:string ;
+ rdfs:domain test:B .
+
+# The domain is changed from test:B to test:A
+test:b_a_domain a rdf:Property ;
+ rdfs:range xsd:integer ;
+ rdfs:domain test:A .
+
+# The domain and range are exchanged
+test:a_b_domain_range a rdf:Property ;
+ rdfs:range test:A ;
+ rdfs:domain test:B .
diff --git a/tests/functional-tests/test-ontologies/simple/92-test.ontology b/tests/functional-tests/test-ontologies/simple/92-test.ontology
new file mode 100644
index 000000000..b554009af
--- /dev/null
+++ b/tests/functional-tests/test-ontologies/simple/92-test.ontology
@@ -0,0 +1,13 @@
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix test: <http://example.org/ns#> .
+@prefix test2: <http://example2.org/ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+
+test2: a nrl:Namespace, nrl:Ontology ;
+ nrl:lastModified "2010-03-24T11:00:04Z" ;
+ nrl:prefix "test2" .
+
+test2:C a rdfs:Class ;
+ rdfs:subClassOf test:B . # Was test:A