# Buggy code
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


# 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_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