diff --git a/ftw/catalogdoctor/surgery.py b/ftw/catalogdoctor/surgery.py index 6714561..8a76b58 100644 --- a/ftw/catalogdoctor/surgery.py +++ b/ftw/catalogdoctor/surgery.py @@ -91,6 +91,23 @@ def _decrease_length(self): pass +class RemoveFromBooleanIndex(IndexSurgery): + """Remove rid from a `BooleanIndex`. + + Lazily skips inverting the boolean index which is deferred to the next + reindex operation by plone. + + """ + def perform(self): + if self.rid in self.index._unindex: + del self.index._unindex[self.rid] + self.index._length.change(-1) + + if self.rid in self.index._index: + self.index._index.remove(self.rid) + self.index._index_length.change(-1) + + class UnindexObject(IndexSurgery): """Remove a rid via the official `unindex_object` API.""" @@ -102,6 +119,7 @@ class Surgery(object): """Surgery can fix a concrete set of symptoms.""" removal = { + BooleanIndex: RemoveFromBooleanIndex, DateIndex: RemoveFromUnIndex, DateRangeIndex: RemoveFromDateRangeIndex, DateRecurringIndex: RemoveFromUnIndex, @@ -126,13 +144,6 @@ def unindex_rid_from_all_catalog_indexes(self, rid): surgery(idx, rid).perform() continue - if isinstance(idx, (BooleanIndex,)): - # These are more complex index types, that we don't handle - # on a low level. We have to hope .unindex_object is able - # to uncatalog the object and doesn't raise a KeyError. - idx.unindex_object(rid) - continue - if not isinstance(idx, (ExtendedPathIndex, UUIDIndex)): raise CantPerformSurgery( 'Unhandled index type: {0!r}'.format(idx)) diff --git a/ftw/catalogdoctor/tests/test_surgery_remove_from_boolean_index.py b/ftw/catalogdoctor/tests/test_surgery_remove_from_boolean_index.py new file mode 100644 index 0000000..db15cb7 --- /dev/null +++ b/ftw/catalogdoctor/tests/test_surgery_remove_from_boolean_index.py @@ -0,0 +1,95 @@ +from ftw.builder import Builder +from ftw.builder import create +from ftw.catalogdoctor.surgery import RemoveFromBooleanIndex +from ftw.catalogdoctor.tests import FunctionalTestCase + + +class TestRemoveFromBooleanIndex(FunctionalTestCase): + + def setUp(self): + super(TestRemoveFromBooleanIndex, self).setUp() + + self.grant('Contributor') + self.folder = create(Builder('folder').titled(u'Foo')) + self.folder.isPrincipiaFolderish = False + self.folder.reindexObject() + + def test_remove_from_boolean_index(self): + rid = self.get_rid(self.folder) + index = self.catalog.indexes['is_folderish'] + + self.assertIn(rid, index._index) + self.assertIn(rid, index._unindex) + self.assertEqual(1, len(index._index)) + self.assertEqual(1, index._index_length.value) + self.assertEqual(1, len(index._unindex)) + # off by one. not sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(2, index._length.value) + + surgery = RemoveFromBooleanIndex(index, rid) + surgery.perform() + + self.assertNotIn(rid, index._index) + self.assertNotIn(rid, index._unindex) + self.assertEqual(0, len(index._index)) + self.assertEqual(0, index._index_length.value) + self.assertEqual(0, len(index._unindex)) + # off by one. not entirely sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(1, index._length.value) + + def test_remove_from_forward_index_only(self): + rid = self.get_rid(self.folder) + index = self.catalog.indexes['is_folderish'] + + # remove entry from reverse index + del index._unindex[rid] + index._length.change(-1) + + self.assertIn(rid, index._index) + self.assertEqual(1, len(index._index)) + self.assertEqual(1, index._index_length.value) + self.assertEqual(0, len(index._unindex)) + # off by one. not sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(1, index._length.value) + + surgery = RemoveFromBooleanIndex(index, rid) + surgery.perform() + + self.assertNotIn(rid, index._index) + self.assertNotIn(rid, index._unindex) + self.assertEqual(0, len(index._index)) + self.assertEqual(0, index._index_length.value) + self.assertEqual(0, len(index._unindex)) + # off by one. not entirely sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(1, index._length.value) + + def test_remove_from_reverse_index_only(self): + rid = self.get_rid(self.folder) + index = self.catalog.indexes['is_folderish'] + + # remove entry from forward index + index._index.remove(rid) + index._index_length.change(-1) + + self.assertEqual(0, len(index._index)) + self.assertEqual(0, index._index_length.value) + self.assertEqual(1, len(index._unindex)) + # off by one. not sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(2, index._length.value) + + surgery = RemoveFromBooleanIndex(index, rid) + surgery.perform() + + self.assertNotIn(rid, index._index) + self.assertNotIn(rid, index._unindex) + self.assertEqual(0, len(index._index)) + self.assertEqual(0, index._index_length.value) + self.assertEqual(0, len(index._unindex)) + # off by one. not entirely sure what is happening here. not observed + # in production. maybe test-setup related? + self.assertEqual(1, index._length.value)