def test_inner_and_outer_changes_persisted_on_successful_exit(cxn, other_cxn): with Transaction(cxn): insert_row(cxn, 'outer-before') with Transaction(cxn): insert_row(cxn, 'inner') insert_row(cxn, 'outer-after') assert_rows(cxn, {'outer-before', 'inner', 'outer-after'}) assert_rows(other_cxn, {'outer-before', 'inner', 'outer-after'})
def test_inner_and_outer_changes_discarded_on_outer_exception(cxn, other_cxn): with pytest.raises(ExpectedException): with Transaction(cxn): insert_row(cxn, 'outer') with Transaction(cxn): insert_row(cxn, 'inner') raise ExpectedException() assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_explicit_rollback_inner_discards_only_inner_changes(cxn, other_cxn): with Transaction(cxn): insert_row(cxn, 'outer-before') with Transaction(cxn) as inner: insert_row(cxn, 'inner') inner.rollback() insert_row(cxn, 'outer-after') assert_rows(cxn, {'outer-before', 'outer-after'}) assert_rows(other_cxn, {'outer-before', 'outer-after'})
def test_explicit_rollback_outer_discards_inner_and_outer_changes( cxn, other_cxn): with Transaction(cxn) as outer: insert_row(cxn, 'outer') with Transaction(cxn): insert_row(cxn, 'inner') outer.rollback() assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_explicit_rollback_of_outer_transaction_while_inner_transaction_is_active_not_allowed( cxn): with Transaction(cxn) as outer: with Transaction(cxn): with pytest.raises(Exception, match=re.escape( 'Cannot rollback outer transaction from ' 'nested transaction context.')): outer.rollback()
def test_transactions_on_multiple_connections_are_independent(cxn, other_cxn): with Transaction(cxn) as outer_txn: insert_row(cxn, 'outer') with Transaction(other_cxn): insert_row(other_cxn, 'inner') outer_txn.rollback() assert_rows(other_cxn, {'inner'}, still_in_transaction=True) assert_rows(cxn, {'inner'})
def test_inner_changes_discarded_on_exception_but_outer_changes_persisted_on_successful_exit( cxn, other_cxn): with Transaction(cxn): insert_row(cxn, 'outer-before') with pytest.raises(ExpectedException): with Transaction(cxn): insert_row(cxn, 'inner') raise ExpectedException() insert_row(cxn, 'outer-after') assert_rows(cxn, {'outer-before', 'outer-after'}) assert_rows(other_cxn, {'outer-before', 'outer-after'})
def test_no_open_transaction_on_exception(cxn): assert_not_in_transaction(cxn) with pytest.raises(ExpectedException): with Transaction(cxn): assert_in_transaction(cxn) raise ExpectedException('This rolls back the transaction') assert_not_in_transaction(cxn)
def test_manual_transaction_management_inside_context_explicit_commit( cxn, other_cxn): def classic_method(cxn): assert cxn.autocommit is False insert_row(cxn, 'inner') cxn.commit() txn = Transaction(cxn).__enter__() insert_row(cxn, 'outer') classic_method(cxn) # All changes are committed and visible immediately :-( assert_rows(other_cxn, {'inner', 'outer'}) # Context exit fails :-(( with pytest.raises(psycopg2.InternalError, match='no such savepoint'): txn.__exit__(None, None, None)
def test_autocommit_off_successful_exit(cxn): cxn.autocommit = False assert_not_in_transaction(cxn) with Transaction(cxn): assert_in_transaction(cxn) assert_not_in_transaction(cxn) assert cxn.autocommit is False
def test_autocommit_on_successful_exit(cxn): assert cxn.autocommit is True, 'Pre-condition' assert_not_in_transaction(cxn) with Transaction(cxn): assert_in_transaction(cxn) assert_not_in_transaction(cxn) assert cxn.autocommit is True
def test_transaction_stack_dict_does_not_leak(cxn): assert len( Transaction._Transaction__transaction_stack) == 0, 'Pre-condition' with Transaction(cxn): assert len(Transaction._Transaction__transaction_stack) == 1 assert len( Transaction._Transaction__transaction_stack) == 0, 'Post-condition'
def test_changes_discarded_on_exception(cxn, other_cxn): with pytest.raises(ExpectedException): with Transaction(cxn): insert_row(cxn, 'value') raise ExpectedException('This discards the insert') assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_manual_transaction_management_with_connection_subclass_commit_rollback_raises( python_cxn, other_cxn): def classic_method(python_cxn, commit): if commit: python_cxn.commit() else: python_cxn.rollback() with Transaction(python_cxn): with pytest.raises( Exception, match=re.escape( 'Explicit commit() forbidden within a Transaction ' 'context. (Transaction will be automatically committed ' 'on successful exit from context.)')): classic_method(python_cxn, commit=True) with pytest.raises( Exception, match=re.escape( 'Explicit rollback() forbidden within a Transaction ' 'context. (Either call Transaction.rollback() or allow ' 'an exception to propogate out of the context.)')): classic_method(python_cxn, commit=False) assert_rows(python_cxn, set()) assert_rows(other_cxn, set())
def test_forced_discard_changes_discarded_on_exception(cxn, other_cxn): with pytest.raises(ExpectedException): with Transaction(cxn, force_discard=True): insert_row(cxn, 'value') raise ExpectedException() assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_forced_discard_explicit_rollback_followed_by_successful_exit( cxn, other_cxn): with Transaction(cxn, force_discard=True) as txn: insert_row(cxn, 'value') txn.rollback() assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_explicit_rollback_repeated_raises(cxn): with Transaction(cxn) as txn: insert_row(cxn, 'value') txn.rollback() with pytest.raises( Exception, match=re.escape('Transaction already rolled back.')): txn.rollback()
def test_explicit_rollback_followed_by_exception_inside_context( cxn, other_cxn): with pytest.raises(ExpectedException): with Transaction(cxn) as txn: insert_row(cxn, 'value') txn.rollback() raise ExpectedException() assert_rows(cxn, set()) assert_rows(other_cxn, set())
def test_autocommit_off_exception(cxn): cxn.autocommit = False assert_not_in_transaction(cxn) with pytest.raises(ExpectedException): with Transaction(cxn): assert_in_transaction(cxn) raise ExpectedException('This rolls back the transaction') assert_not_in_transaction(cxn) assert cxn.autocommit is False
def test_context_manager_is_reusable(cxn): txn = Transaction(cxn) assert_not_in_transaction(cxn) with txn: assert_in_transaction(cxn) assert_not_in_transaction(cxn) with txn: assert_in_transaction(cxn) assert_not_in_transaction(cxn)
def test_autocommit_on_exception(cxn): assert cxn.autocommit is True, 'Pre-condition' assert_not_in_transaction(cxn) with pytest.raises(ExpectedException): with Transaction(cxn): assert_in_transaction(cxn) raise ExpectedException('This rolls back the transaction') assert_not_in_transaction(cxn) assert cxn.autocommit is True
def check(cxn): original_commit, original_rollback = cxn.commit, cxn.rollback original_dict_keys = set(cxn.__dict__.keys()) with Transaction(cxn): pass assert (original_commit, original_rollback) == (cxn.commit, cxn.rollback) assert original_dict_keys == set( cxn.__dict__.keys()), 'Instance dict has changed'
def test_autocommit_off_but_no_transaction_started_successful_exit_commits_changes( cxn, other_cxn): cxn.autocommit = False assert_not_in_transaction(cxn) with Transaction(cxn): insert_row(cxn, 'new') assert_not_in_transaction(cxn) assert cxn.autocommit is False assert_rows(cxn, {'new'}) assert_rows(other_cxn, {'new'}) # Changes committed.
def test_manual_transaction_management_inside_context_explicit_rollback( cxn, other_cxn): def classic_method(cxn): assert cxn.autocommit is False insert_row(cxn, 'inner') cxn.rollback() txn = Transaction(cxn).__enter__() insert_row(cxn, 'outer') assert_in_transaction(cxn) classic_method(cxn) assert_not_in_transaction(cxn) # Context exit fails :-( with pytest.raises(psycopg2.InternalError, match='no such savepoint'): txn.__exit__(None, None, None) # All changes are discarded :-(( assert_rows(other_cxn, set())
def test_context_manager_is_not_reentrant(cxn): # As the context manager stores state on self, calling __enter__() a second time overwrites it txn = Transaction(cxn) assert_not_in_transaction(cxn) with txn: assert_in_transaction(cxn) with txn: # Don't do this! assert_in_transaction(cxn) assert_in_transaction(cxn) assert_not_in_transaction(cxn)
def test_manual_transaction_management_inside_context_implicit_rollback( cxn, other_cxn): def method(cxn): # This method implicitly rolls back the current transaction :-( # See: https://github.com/psycopg/psycopg2/issues/950 cxn.set_client_encoding('LATIN1') txn = Transaction(cxn).__enter__() insert_row(cxn, 'outer') assert_in_transaction(cxn) method(cxn) assert_not_in_transaction(cxn) # Context exit fails :-( with pytest.raises(psycopg2.InternalError, match='no such savepoint'): txn.__exit__(None, None, None) # All changes are discarded :-(( assert_rows(other_cxn, set())
def test_manual_enter_and_exit_out_of_order_exit_raises_assertion(cxn): t1, t2 = Transaction(cxn), Transaction(cxn) t1.__enter__() t2.__enter__() with pytest.raises( AssertionError, match=re.escape( 'Out-of-order Transaction context exits. Are you calling ' '__exit__() manually and getting it wrong?')): t1.__exit__(None, None, None)
def test_autocommit_off_transaction_in_progress_successful_exit_leaves_transaction_running( cxn, other_cxn): cxn.autocommit = False insert_row(cxn, 'prior') assert_in_transaction(cxn) with Transaction(cxn): insert_row(cxn, 'new') assert_in_transaction(cxn) assert cxn.autocommit is False assert_rows(cxn, {'prior', 'new'}, still_in_transaction=True) assert_rows( other_cxn, set()) # Nothing committed; changes not visible on another connection
def test_autocommit_off_but_no_transaction_started_exception_discards_changes( cxn, other_cxn): cxn.autocommit = False assert_not_in_transaction(cxn) with pytest.raises(ExpectedException): with Transaction(cxn): insert_row(cxn, 'new') raise ExpectedException( 'This rolls back just the inner transaction') assert_not_in_transaction(cxn) assert cxn.autocommit is False assert_rows(cxn, set()) assert_rows(other_cxn, set()) # No changes committed.
def test_manual_transaction_management_inside_context_autocommit_raises( cxn, python_cxn): def classic_method(cxn, autocommit): cxn.autocommit = autocommit # Setting autocommit always raises insert_row(cxn, 'inner') cxn.commit() for i, cxn in enumerate((cxn, python_cxn)): for autocommit in (True, False): with Transaction(cxn): with pytest.raises( psycopg2.ProgrammingError, match='set_session cannot be used inside a transaction' ): classic_method(cxn, autocommit)