Revision: 9320 http://trac.macosforge.org/projects/calendarserver/changeset/9320 Author: glyph@apple.com Date: 2012-05-31 15:50:07 -0700 (Thu, 31 May 2012) Log Message: ----------- Fix and test for denormalized notification home UUIDs. Test for denormalized addressbook UUIDs, just in case. Modified Paths: -------------- CalendarServer/trunk/txdav/common/datastore/sql.py CalendarServer/trunk/txdav/common/datastore/test/test_sql.py Property Changed: ---------------- CalendarServer/trunk/ Modified: CalendarServer/trunk/txdav/common/datastore/sql.py =================================================================== --- CalendarServer/trunk/txdav/common/datastore/sql.py 2012-05-31 21:04:04 UTC (rev 9319) +++ CalendarServer/trunk/txdav/common/datastore/sql.py 2012-05-31 22:50:07 UTC (rev 9320) @@ -438,11 +438,11 @@ @memoizedKey("uid", "_notificationHomes") - def notificationsWithUID(self, uid): + def notificationsWithUID(self, uid, create=True): """ Implement notificationsWithUID. """ - return NotificationCollection.notificationsWithUID(self, uid) + return NotificationCollection.notificationsWithUID(self, uid, create) @classproperty def _insertAPNSubscriptionQuery(cls): #@NoSelf @@ -3443,15 +3443,15 @@ @classmethod @inlineCallbacks - def notificationsWithUID(cls, txn, uid): + def notificationsWithUID(cls, txn, uid, create): rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid) if rows: resourceID = rows[0][0] created = False - else: - # Use savepoint so we can do a partial rollback if there is a race condition - # where this row has already been inserted + elif create: + # Use savepoint so we can do a partial rollback if there is a race + # condition where this row has already been inserted savepoint = SavepointAction("notificationsWithUID") yield savepoint.acquire(txn) @@ -3459,9 +3459,11 @@ resourceID = str(( yield cls._provisionNewNotificationsQuery.on(txn, uid=uid) )[0][0]) - except Exception: # FIXME: Really want to trap the pg.DatabaseError but in a non-DB specific manner + except Exception: + # FIXME: Really want to trap the pg.DatabaseError but in a non- + # DB specific manner yield savepoint.rollback(txn) - + # Retry the query - row may exist now, if not re-raise rows = yield cls._resourceIDFromUIDQuery.on(txn, uid=uid) if rows: @@ -3472,7 +3474,8 @@ else: created = True yield savepoint.release(txn) - + else: + returnValue(None) collection = cls(txn, uid, resourceID) yield collection._loadPropertyStore() if created: @@ -4137,10 +4140,7 @@ homeType=homeTypeName ) other = None - if homeType == ENOTIFICATIONTYPE: - this = yield t.notificationsWithUID(UID) - else: - this = yield t.homeWithUID(homeType, UID) + this = yield _getHome(t, homeType, UID) if homeType == ECALENDARTYPE: fixedThisHome = yield fixOneCalendarHome(this) else: @@ -4159,7 +4159,7 @@ log_msg(format="Detected case variance: %(uid)s %(newuid)s" "[%(homeType)s]", uid=UID, newuid=newname, homeType=homeTypeName) - other = yield t.homeWithUID(homeType, newname) + other = yield _getHome(t, homeType, newname) if other is None: # No duplicate: just fix the name. yield _renameHome(t, homeTable, UID, newname) @@ -4181,6 +4181,30 @@ +def _getHome(txn, homeType, uid): + """ + Like L{CommonHome.homeWithUID} but also honoring ENOTIFICATIONTYPE which + isn't I{really} a type of home. + + @param txn: the transaction to retrieve the home from + @type txn: L{CommonStoreTransaction} + + @param homeType: L{ENOTIFICATIONTYPE}, L{ECALENDARTYPE}, or + L{EADDRESSBOOKTYPE}. + + @param uid: the UID of the home to retrieve. + @type uid: L{str} + + @return: a L{Deferred} that fires with the L{CommonHome} or + L{NotificationHome} when it has been retrieved. + """ + if homeType == ENOTIFICATIONTYPE: + return txn.notificationsWithUID(uid, create=False) + else: + return txn.homeWithUID(homeType, uid) + + + @inlineCallbacks def _normalizeColumnUUIDs(txn, column): """ @@ -4241,7 +4265,8 @@ # a lower-case OWNER_UID. If there are none, then we can early-out and # avoid the tedious and potentially expensive inspection of oodles of # calendar data. - for x in [schema.CALENDAR_HOME, schema.ADDRESSBOOK_HOME]: + for x in [schema.CALENDAR_HOME, schema.ADDRESSBOOK_HOME, + schema.NOTIFICATION_HOME]: slct = Select([x.OWNER_UID], From=x, Where=x.OWNER_UID != Upper(x.OWNER_UID)) rows = yield slct.on(t) Modified: CalendarServer/trunk/txdav/common/datastore/test/test_sql.py =================================================================== --- CalendarServer/trunk/txdav/common/datastore/test/test_sql.py 2012-05-31 21:04:04 UTC (rev 9319) +++ CalendarServer/trunk/txdav/common/datastore/test/test_sql.py 2012-05-31 22:50:07 UTC (rev 9320) @@ -339,13 +339,12 @@ @inlineCallbacks - def allHomeUIDs(self): + def allHomeUIDs(self, table=schema.CALENDAR_HOME): """ Get a listing of all UIDs in the current store. """ - results = yield Select( - [schema.CALENDAR_HOME.OWNER_UID], - From=schema.CALENDAR_HOME).on(self.transactionUnderTest()) + results = yield (Select([table.OWNER_UID], From=table) + .on(self.transactionUnderTest())) yield self.commit() returnValue(results) @@ -364,6 +363,37 @@ self.assertEqual((yield self.allHomeUIDs()), [[normalizedUID]]) + @inlineCallbacks + def test_fixUUIDNormalization_lowerToUpper_notification(self): + """ + L{fixUUIDNormalization} will fix the normalization of UUIDs. If a home + is found with the wrong case but no duplicate, it will simply be + upper-cased. + """ + t1 = self.transactionUnderTest() + yield t1.notificationsWithUID(denormalizedUID, create=True) + yield self.commit() + yield fixUUIDNormalization(self.storeUnderTest()) + self.assertEqual((yield self.allHomeUIDs(schema.NOTIFICATION_HOME)), + [[normalizedUID]]) + + + @inlineCallbacks + def test_fixUUIDNormalization_lowerToUpper_addressbook(self): + """ + L{fixUUIDNormalization} will fix the normalization of UUIDs. If a home + is found with the wrong case but no duplicate, it will simply be + upper-cased. + """ + t1 = self.transactionUnderTest() + yield t1.addressbookHomeWithUID(denormalizedUID, create=True) + yield self.commit() + yield fixUUIDNormalization(self.storeUnderTest()) + self.assertEqual((yield self.allHomeUIDs(schema.ADDRESSBOOK_HOME)), + [[normalizedUID]]) + + + from uuid import UUID exampleUID = UUID("a"*32) denormalizedUID = str(exampleUID)