# Buggy code
def _background_insert_retention(self, progress, batch_size):
    """Retrieves a list of all rooms within a range and inserts an entry for each of
    them into the room_retention table.
    NULLs the property's columns if missing from the retention event in the room's
    state (or NULLs all of them if there's no retention event in the room's state),
    so that we fall back to the server's retention policy.
    """

    last_room = progress.get("room_id", "")

    def _background_insert_retention_txn(txn):
        txn.execute(
            """
                SELECT state.room_id, state.event_id, events.json
                FROM current_state_events as state
                LEFT JOIN event_json AS events ON (state.event_id = events.event_id)
                WHERE state.room_id > ? AND state.type = '%s'
                ORDER BY state.room_id ASC
                LIMIT ?;
                """
            % EventTypes.Retention,
            (last_room, batch_size),
        )

        rows = self.db.cursor_to_dict(txn)

        if not rows:
            return True

        for row in rows:
            if not row["json"]:
                retention_policy = {}
            else:
                ev = json.loads(row["json"])
                retention_policy = json.dumps(ev["content"])

            self.db.simple_insert_txn(
                txn=txn,
                table="room_retention",
                values={
                    "room_id": row["room_id"],
                    "event_id": row["event_id"],
                    "min_lifetime": retention_policy.get("min_lifetime"),
                    "max_lifetime": retention_policy.get("max_lifetime"),
                },
            )

        logger.info("Inserted %d rows into room_retention", len(rows))

        self.db.updates._background_update_progress_txn(
            txn, "insert_room_retention", {"room_id": rows[-1]["room_id"]}
        )

        if batch_size > len(rows):
            return True
        else:
            return False

    end = yield self.db.runInteraction(
        "insert_room_retention",
        _background_insert_retention_txn,
    )

    if end:
        yield self.db.updates._end_background_update("insert_room_retention")

    defer.returnValue(batch_size)


# Traceback (AttributeError)
2020-07-05 10:59:39,226 - synapse.storage.background_updates - 227 - INFO - background_updates-0 - Starting update batch on background update 'insert_room_retention'
2020-07-05 10:59:39,232 - synapse.storage.background_updates - 114 - ERROR - background_updates-0 - Error doing update
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/synapse/storage/background_updates.py", line 111, in run_background_updates
self.BACKGROUND_UPDATE_DURATION_MS
File "/usr/local/lib/python3.7/site-packages/synapse/storage/background_updates.py", line 222, in do_next_background_update
await self._do_background_update(desired_duration_ms)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/background_updates.py", line 255, in _do_background_update
items_updated = await update_handler(progress, batch_size)
File "/usr/local/lib/python3.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/usr/local/lib/python3.7/site-packages/twisted/python/failure.py", line 512, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/data_stores/main/room.py", line 938, in _background_insert_retention
"insert_room_retention", _background_insert_retention_txn,
File "/usr/local/lib/python3.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/usr/local/lib/python3.7/site-packages/twisted/python/failure.py", line 512, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/database.py", line 527, in runInteraction
**kwargs
File "/usr/local/lib/python3.7/site-packages/twisted/internet/defer.py", line 1416, in _inlineCallbacks
result = result.throwExceptionIntoGenerator(g)
File "/usr/local/lib/python3.7/site-packages/twisted/python/failure.py", line 512, in throwExceptionIntoGenerator
return g.throw(self.type, self.value, self.tb)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/database.py", line 575, in runWithConnection
self._db_pool.runWithConnection(inner_func, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/twisted/python/threadpool.py", line 250, in inContext
result = inContext.theWork()
File "/usr/local/lib/python3.7/site-packages/twisted/python/threadpool.py", line 266, in <lambda>
inContext.theWork = lambda: context.call(ctx, func, *args, **kw)
File "/usr/local/lib/python3.7/site-packages/twisted/python/context.py", line 122, in callWithContext
return self.currentContext().callWithContext(ctx, func, *args, **kw)
File "/usr/local/lib/python3.7/site-packages/twisted/python/context.py", line 85, in callWithContext
return func(*args,**kw)
File "/usr/local/lib/python3.7/site-packages/twisted/enterprise/adbapi.py", line 306, in _runWithConnection
compat.reraise(excValue, excTraceback)
File "/usr/local/lib/python3.7/site-packages/twisted/python/compat.py", line 464, in reraise
raise exception.with_traceback(traceback)
File "/usr/local/lib/python3.7/site-packages/twisted/enterprise/adbapi.py", line 297, in _runWithConnection
result = func(conn, *args, **kw)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/database.py", line 572, in inner_func
return func(conn, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/database.py", line 418, in new_transaction
r = func(cursor, *args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/synapse/storage/data_stores/main/room.py", line 921, in _background_insert_retention_txn
"min_lifetime": retention_policy.get("min_lifetime"),
AttributeError: 'str' object has no attribute 'get'

# Fixed code
def _background_insert_retention(self, progress, batch_size):
    """Retrieves a list of all rooms within a range and inserts an entry for each of
    them into the room_retention table.
    NULLs the property's columns if missing from the retention event in the room's
    state (or NULLs all of them if there's no retention event in the room's state),
    so that we fall back to the server's retention policy.
    """

    last_room = progress.get("room_id", "")

    def _background_insert_retention_txn(txn):
        txn.execute(
            """
                SELECT state.room_id, state.event_id, events.json
                FROM current_state_events as state
                LEFT JOIN event_json AS events ON (state.event_id = events.event_id)
                WHERE state.room_id > ? AND state.type = '%s'
                ORDER BY state.room_id ASC
                LIMIT ?;
                """
            % EventTypes.Retention,
            (last_room, batch_size),
        )

        rows = self.db.cursor_to_dict(txn)

        if not rows:
            return True

        for row in rows:
            if not row["json"]:
                retention_policy = {}
            else:
                ev = json.loads(row["json"])
                retention_policy = ev["content"]

            self.db.simple_insert_txn(
                txn=txn,
                table="room_retention",
                values={
                    "room_id": row["room_id"],
                    "event_id": row["event_id"],
                    "min_lifetime": retention_policy.get("min_lifetime"),
                    "max_lifetime": retention_policy.get("max_lifetime"),
                },
            )

        logger.info("Inserted %d rows into room_retention", len(rows))

        self.db.updates._background_update_progress_txn(
            txn, "insert_room_retention", {"room_id": rows[-1]["room_id"]}
        )

        if batch_size > len(rows):
            return True
        else:
            return False

    end = yield self.db.runInteraction(
        "insert_room_retention",
        _background_insert_retention_txn,
    )

    if end:
        yield self.db.updates._end_background_update("insert_room_retention")

    defer.returnValue(batch_size)