diff --git a/axiom/item.py b/axiom/item.py index 60401842..69980fbc 100644 --- a/axiom/item.py +++ b/axiom/item.py @@ -761,7 +761,8 @@ def checkpoint(self): # don't issue duplicate SQL and crap; we were created, then # destroyed immediately. return - self.store.executeSQL(self._baseDeleteSQL(self.store), [self.storeID]) + self.store.executeSQL(self._baseDeleteSQL(self.store), + [self.storeID]) # re-using OIDs plays havoc with the cache, and with other things # as well. We need to make sure that we leave a placeholder row at # the end of the table. @@ -770,16 +771,17 @@ def checkpoint(self): self.store.executeSchemaSQL(_schema.CHANGE_TYPE, [-1, self.storeID]) - # Can't do this any more: - # self.store.executeSchemaSQL(_schema.DELETE_OBJECT, [self.storeID]) + # I want to do this to reclaim the space, but I can't because + # the ID allocation algorithm depends on the fact that old + # objects aren't recycled: + + # self.store.executeSchemaSQL(_schema.DELETE_OBJECT, + # [self.storeID]) # TODO: need to measure the performance impact of this, then do # it to make sure things are in fact deleted: # self.store.executeSchemaSQL(_schema.APP_VACUUM) - else: - assert self.__legacy__ - # we're done... if self.store.autocommit: self.committed() diff --git a/axiom/store.py b/axiom/store.py index 63c66a53..1746f865 100644 --- a/axiom/store.py +++ b/axiom/store.py @@ -2302,6 +2302,17 @@ def getItemByID(self, storeID, default=_noItem, autoUpgrade=True): # for the moment we're going to assume no inheritance attrs = self.querySQL(T._baseSelectSQL(self), [storeID]) if len(attrs) == 0: + # there was an oid allocation row with a type set to a valid + # type, but no accompanying data row. + + # there are cases where items will be deleted but their oid + # allocation rows won't be. normally this should set the type + # to -1 but upgraders or old bugs may have left the database + # in a state where it's still set. + + # do cleanup if we're not in a read-only transaction (haha + # there is no such thing as a read-only transaction) + self.executeSchemaSQL(_schema.CHANGE_TYPE, [-1, storeID]) if default is _noItem: raise errors.ItemNotFound( 'No results for known-to-be-good object') diff --git a/axiom/test/test_item.py b/axiom/test/test_item.py index 98ad1003..273adec4 100644 --- a/axiom/test/test_item.py +++ b/axiom/test/test_item.py @@ -656,3 +656,25 @@ class TI3(Item): [(TestInterface2, 20), (TestInterface, 0)]) self.assertTrue(TestInterface.implementedBy(TI3)) + + + def test_fixDangling(self): + st = store.Store() + i = itemtest.PlainItem(store=st) + oldStoreID = i.storeID + self.assertEquals(st.getItemByID(oldStoreID, default=1234), + i) + + # this magic keyword argument actually simulates an old bug, but rather + # than carry around a legacy database, we'll just call an internal API, + # because this is actually used by upgraders as well (e.g. a subtly + # buggy upgrader could leave the DB in the same state as this old bug) + + i.deleteFromStore(deleteObject=False) + + self.assertEquals(st.getItemByID(oldStoreID+100, default=1234), + 1234) + + self.assertEquals(st.getItemByID(oldStoreID, default=1234), + 1234) +