def groupByUID(self, groupUID, create=True): """ Return or create a record for the group UID. @type groupUID: C{unicode} @return: Deferred firing with tuple of group ID C{str}, group name C{unicode}, membership hash C{str}, modified timestamp, and extant C{boolean} """ results = yield GroupsRecord.query(self, GroupsRecord.groupUID == groupUID.encode("utf-8")) if results: returnValue(results[0]) elif create: savepoint = SavepointAction("groupByUID") yield savepoint.acquire(self) try: group = yield self.addGroup(groupUID, u"", "") if group is None: # The record does not actually exist within the directory yield savepoint.release(self) returnValue(None) except Exception: yield savepoint.rollback(self) results = yield GroupsRecord.query(self, GroupsRecord.groupUID == groupUID.encode("utf-8")) returnValue(results[0] if results else None) else: yield savepoint.release(self) returnValue(group) else: returnValue(None)
def trylock(self, where=None): """ Try to lock with a select for update no wait. If it fails, rollback to a savepoint and return L{False}, else return L{True}. @param where: SQL expression used to match the rows to lock, by default this is just an expression that matches the primary key of this L{Record}, but it can be used to lock multiple L{Records} matching the expression in one go. If it is an L{str}, then all rows will be matched. @type where: L{SQLExpression} or L{None} @return: a L{Deferred} that fires when the updates have been sent to the database. """ if where is None: where = self._primaryKeyComparison(self._primaryKeyValue()) elif isinstance(where, str): where = None savepoint = SavepointAction("Record_trylock_{}".format(self.__class__.__name__)) yield savepoint.acquire(self.transaction) try: yield Select( list(self.table), From=self.table, Where=where, ForUpdate=True, NoWait=True, ).on(self.transaction) except: yield savepoint.rollback(self.transaction) returnValue(False) else: yield savepoint.release(self.transaction) returnValue(True)
def groupByUID(self, groupUID, create=True): """ Return or create a record for the group UID. @type groupUID: C{unicode} @return: Deferred firing with tuple of group ID C{str}, group name C{unicode}, membership hash C{str}, modified timestamp, and extant C{boolean} """ results = yield GroupsRecord.query( self, GroupsRecord.groupUID == groupUID.encode("utf-8")) if results: returnValue(results[0]) elif create: savepoint = SavepointAction("groupByUID") yield savepoint.acquire(self) try: group = yield self.addGroup(groupUID, u"", "") if group is None: # The record does not actually exist within the directory yield savepoint.release(self) returnValue(None) except Exception: yield savepoint.rollback(self) results = yield GroupsRecord.query( self, GroupsRecord.groupUID == groupUID.encode("utf-8")) returnValue(results[0] if results else None) else: yield savepoint.release(self) returnValue(group) else: returnValue(None)
def notificationsWith(cls, txn, rid, uid, status=None, create=False): """ @param uid: I'm going to assume uid is utf-8 encoded bytes """ if rid is not None: query = cls._homeSchema.RESOURCE_ID == rid elif uid is not None: query = cls._homeSchema.OWNER_UID == uid if status is not None: query = query.And(cls._homeSchema.STATUS == status) else: statusSet = ( _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL, ) if txn._allowDisabled: statusSet += (_HOME_STATUS_DISABLED, ) query = query.And(cls._homeSchema.STATUS.In(statusSet)) else: raise AssertionError("One of rid or uid must be set") results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=query, ).on(txn) if len(results) > 1: # Pick the best one in order: normal, disabled and external byStatus = dict([ (result[cls.homeColumns().index(cls._homeSchema.STATUS)], result) for result in results ]) result = byStatus.get(_HOME_STATUS_NORMAL) if result is None: result = byStatus.get(_HOME_STATUS_DISABLED) if result is None: result = byStatus.get(_HOME_STATUS_EXTERNAL) elif results: result = results[0] else: result = None if result: # Return object that already exists in the store homeObject = yield cls.makeClass(txn, result) returnValue(homeObject) else: # Can only create when uid is specified if not create or uid is None: returnValue(None) # Determine if the user is local or external record = yield txn.directoryService().recordWithUID( uid.decode("utf-8")) if record is None: raise DirectoryRecordNotFoundError( "Cannot create home for UID since no directory record exists: {}" .format(uid)) if status is None: createStatus = _HOME_STATUS_NORMAL if record.thisServer( ) else _HOME_STATUS_EXTERNAL elif status == _HOME_STATUS_MIGRATING: if record.thisServer(): raise RecordNotAllowedError( "Cannot migrate a user data for a user already hosted on this server" ) createStatus = status elif status in ( _HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL, ): createStatus = status else: raise RecordNotAllowedError( "Cannot create home with status {}: {}".format( status, uid)) # 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) try: resourceid = (yield Insert( { cls._homeSchema.OWNER_UID: uid, cls._homeSchema.STATUS: createStatus, }, Return=cls._homeSchema.RESOURCE_ID).on(txn))[0][0] 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 results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=query, ).on(txn) if results: homeObject = yield cls.makeClass(txn, results[0]) returnValue(homeObject) else: raise else: yield savepoint.release(txn) # Note that we must not cache the owner_uid->resource_id # mapping in the query cacher when creating as we don't want that to appear # until AFTER the commit results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=cls._homeSchema.RESOURCE_ID == resourceid, ).on(txn) homeObject = yield cls.makeClass(txn, results[0]) if homeObject.normal(): yield homeObject._initSyncToken() yield homeObject.notifyChanged() returnValue(homeObject)
def notificationsWith(cls, txn, rid, uid, status=None, create=False): """ @param uid: I'm going to assume uid is utf-8 encoded bytes """ if rid is not None: query = cls._homeSchema.RESOURCE_ID == rid elif uid is not None: query = cls._homeSchema.OWNER_UID == uid if status is not None: query = query.And(cls._homeSchema.STATUS == status) else: statusSet = (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,) if txn._allowDisabled: statusSet += (_HOME_STATUS_DISABLED,) query = query.And(cls._homeSchema.STATUS.In(statusSet)) else: raise AssertionError("One of rid or uid must be set") results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=query, ).on(txn) if len(results) > 1: # Pick the best one in order: normal, disabled and external byStatus = dict([(result[cls.homeColumns().index(cls._homeSchema.STATUS)], result) for result in results]) result = byStatus.get(_HOME_STATUS_NORMAL) if result is None: result = byStatus.get(_HOME_STATUS_DISABLED) if result is None: result = byStatus.get(_HOME_STATUS_EXTERNAL) elif results: result = results[0] else: result = None if result: # Return object that already exists in the store homeObject = yield cls.makeClass(txn, result) returnValue(homeObject) else: # Can only create when uid is specified if not create or uid is None: returnValue(None) # Determine if the user is local or external record = yield txn.directoryService().recordWithUID(uid.decode("utf-8")) if record is None: raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid)) if status is None: createStatus = _HOME_STATUS_NORMAL if record.thisServer() else _HOME_STATUS_EXTERNAL elif status == _HOME_STATUS_MIGRATING: if record.thisServer(): raise RecordNotAllowedError("Cannot migrate a user data for a user already hosted on this server") createStatus = status elif status in (_HOME_STATUS_NORMAL, _HOME_STATUS_EXTERNAL,): createStatus = status else: raise RecordNotAllowedError("Cannot create home with status {}: {}".format(status, uid)) # 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) try: resourceid = (yield Insert( { cls._homeSchema.OWNER_UID: uid, cls._homeSchema.STATUS: createStatus, }, Return=cls._homeSchema.RESOURCE_ID ).on(txn))[0][0] 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 results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=query, ).on(txn) if results: homeObject = yield cls.makeClass(txn, results[0]) returnValue(homeObject) else: raise else: yield savepoint.release(txn) # Note that we must not cache the owner_uid->resource_id # mapping in the query cacher when creating as we don't want that to appear # until AFTER the commit results = yield Select( cls.homeColumns(), From=cls._homeSchema, Where=cls._homeSchema.RESOURCE_ID == resourceid, ).on(txn) homeObject = yield cls.makeClass(txn, results[0]) if homeObject.normal(): yield homeObject._initSyncToken() yield homeObject.notifyChanged() returnValue(homeObject)