async def test_explicit_rollback_discards_changes(aconn, svcconn): """ Raising a Rollback exception in the middle of a block exits the block and discards all changes made within that block. You can raise any of the following: - Rollback (type) - Rollback() (instance) - Rollback(tx) (instance initialised with reference to the transaction) All of these are equivalent. """ async def assert_no_rows(): assert not await inserted(aconn) assert not inserted(svcconn) async with aconn.transaction(): await insert_row(aconn, "foo") raise Rollback await assert_no_rows() async with aconn.transaction(): await insert_row(aconn, "foo") raise Rollback() await assert_no_rows() async with aconn.transaction() as tx: await insert_row(aconn, "foo") raise Rollback(tx) await assert_no_rows()
async def test_explicit_rollback_of_outer_transaction(aconn): """ Raising a Rollback exception that references an outer transaction will discard all changes from both inner and outer transaction blocks. """ async with aconn.transaction() as outer_tx: await insert_row(aconn, "outer") async with aconn.transaction(): await insert_row(aconn, "inner") raise Rollback(outer_tx) assert False, "This line of code should be unreachable." assert not await inserted(aconn)
async def test_named_savepoints_with_repeated_names_works(aconn): """ Using the same savepoint name repeatedly works correctly, but bypasses some sanity checks. """ # Works correctly if no inner transactions are rolled back async with aconn.transaction(force_rollback=True): async with aconn.transaction("sp"): await insert_row(aconn, "tx1") async with aconn.transaction("sp"): await insert_row(aconn, "tx2") async with aconn.transaction("sp"): await insert_row(aconn, "tx3") assert await inserted(aconn) == {"tx1", "tx2", "tx3"} # Works correctly if one level of inner transaction is rolled back async with aconn.transaction(force_rollback=True): async with aconn.transaction("s1"): await insert_row(aconn, "tx1") async with aconn.transaction("s1", force_rollback=True): await insert_row(aconn, "tx2") async with aconn.transaction("s1"): await insert_row(aconn, "tx3") assert await inserted(aconn) == {"tx1"} assert await inserted(aconn) == {"tx1"} # Works correctly if multiple inner transactions are rolled back # (This scenario mandates releasing savepoints after rolling back to them.) async with aconn.transaction(force_rollback=True): async with aconn.transaction("s1"): await insert_row(aconn, "tx1") async with aconn.transaction("s1") as tx2: await insert_row(aconn, "tx2") async with aconn.transaction("s1"): await insert_row(aconn, "tx3") raise Rollback(tx2) assert await inserted(aconn) == {"tx1"} assert await inserted(aconn) == {"tx1"} # Will not (always) catch out-of-order exits async with aconn.transaction(force_rollback=True): tx1 = aconn.transaction("s1") tx2 = aconn.transaction("s1") await tx1.__aenter__() await tx2.__aenter__() await tx1.__aexit__(None, None, None) await tx2.__aexit__(None, None, None)
def test_explicit_rollback_of_enclosing_tx_outer_tx_unaffected(conn, svcconn): """ Rolling-back an enclosing transaction does not impact an outer transaction. """ with conn.transaction(): insert_row(conn, "outer-before") with conn.transaction() as tx_enclosing: insert_row(conn, "enclosing") with conn.transaction(): insert_row(conn, "inner") raise Rollback(tx_enclosing) insert_row(conn, "outer-after") assert inserted(conn) == {"outer-before", "outer-after"} assert not inserted(svcconn) # Not yet committed # Changes committed assert inserted(svcconn) == {"outer-before", "outer-after"}