@@ -823,7 +823,7 @@ async def test_transaction_rollback(
823823async def test_transaction_cursor_isolation (
824824 connection : Connection , create_drop_test_table_setup_teardown_async : Callable
825825) -> None :
826- """Test that one cursor can't see another's data until it commits ."""
826+ """Test that cursors share the same transaction state - no isolation between cursors ."""
827827 table_name = create_drop_test_table_setup_teardown_async
828828 cursor1 = connection .cursor ()
829829 cursor2 = connection .cursor ()
@@ -832,25 +832,158 @@ async def test_transaction_cursor_isolation(
832832 result = await cursor1 .execute ("BEGIN TRANSACTION" )
833833 assert result == 0 , "BEGIN TRANSACTION should return 0 rows"
834834
835- await cursor1 .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'isolated_data ')" )
835+ await cursor1 .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'shared_data ')" )
836836
837837 # Verify cursor1 can see its own uncommitted data
838838 await cursor1 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
839839 data1 = await cursor1 .fetchall ()
840840 assert len (data1 ) == 1 , "Cursor1 should see its own uncommitted data"
841- assert data1 [0 ] == [1 , "isolated_data " ], "Cursor1 data should match inserted values"
841+ assert data1 [0 ] == [1 , "shared_data " ], "Cursor1 data should match inserted values"
842842
843- # Verify cursor2 cannot see cursor1's uncommitted data
843+ # Verify cursor2 CAN see cursor1's uncommitted data (no isolation between cursors)
844844 await cursor2 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
845845 data2 = await cursor2 .fetchall ()
846- assert len (data2 ) == 0 , "Cursor2 should not see cursor1's uncommitted data"
846+ assert (
847+ len (data2 ) == 1
848+ ), "Cursor2 should see cursor1's uncommitted data (no isolation)"
849+ assert data2 [0 ] == [1 , "shared_data" ], "Cursor2 should see the same data as cursor1"
847850
848- # Commit the transaction in cursor1
849- result = await cursor1 .execute ("COMMIT TRANSACTION" )
851+ # Commit the transaction in cursor2 (affects both cursors)
852+ result = await cursor2 .execute ("COMMIT TRANSACTION" )
850853 assert result == 0 , "COMMIT TRANSACTION should return 0 rows"
851854
852- # Now cursor2 should be able to see the committed data
855+ # Both cursors should still see the committed data
856+ await cursor1 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
857+ data1_after = await cursor1 .fetchall ()
858+ assert len (data1_after ) == 1 , "Cursor1 should see committed data"
859+ assert data1_after [0 ] == [1 , "shared_data" ], "Cursor1 should see the committed data"
860+
861+
862+ @mark .parametrize ("autocommit_mode" , ["implicit" , "explicit" ])
863+ async def test_autocommit_immediate_visibility (
864+ connection : Connection ,
865+ autocommit_mode : str ,
866+ create_drop_test_table_setup_teardown_async : Callable ,
867+ ) -> None :
868+ """Test that statements are visible immediately with autocommit enabled (uses existing connection fixture)."""
869+ table_name = create_drop_test_table_setup_teardown_async
870+ cursor1 = connection .cursor ()
871+ cursor2 = connection .cursor ()
872+
873+ # Insert data with cursor1
874+ await cursor1 .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'autocommit_data')" )
875+
876+ # Immediately verify cursor2 can see the data (autocommit makes it visible)
853877 await cursor2 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
854- data2 = await cursor2 .fetchall ()
855- assert len (data2 ) == 1 , "Cursor2 should see committed data after commit"
856- assert data2 [0 ] == [1 , "isolated_data" ], "Cursor2 should see the committed data"
878+ data = await cursor2 .fetchall ()
879+ assert (
880+ len (data ) == 1
881+ ), f"Data should be immediately visible with { autocommit_mode } autocommit"
882+ assert data [0 ] == [1 , "autocommit_data" ], "Data should match inserted values"
883+
884+ # Insert more data with cursor2
885+ await cursor2 .execute (f"INSERT INTO \" { table_name } \" VALUES (2, 'more_data')" )
886+
887+ # Verify cursor1 can immediately see cursor2's data
888+ await cursor1 .execute (f'SELECT * FROM "{ table_name } " ORDER BY id' )
889+ all_data = await cursor1 .fetchall ()
890+ assert len (all_data ) == 2 , "All data should be immediately visible"
891+ assert all_data [0 ] == [1 , "autocommit_data" ], "First row should match"
892+ assert all_data [1 ] == [2 , "more_data" ], "Second row should match"
893+
894+
895+ # Not compatible with core
896+ @mark .parametrize ("connection" , ["remote" ], indirect = True )
897+ async def test_begin_with_autocommit_on (
898+ connection : Connection , create_drop_test_table_setup_teardown_async : Callable
899+ ) -> None :
900+ """Test that BEGIN does not start a transaction when autocommit is enabled."""
901+ table_name = create_drop_test_table_setup_teardown_async
902+
903+ cursor = connection .cursor ()
904+ # Test that data is immediately visible without explicit transaction (autocommit)
905+ await cursor .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'autocommit_test')" )
906+
907+ # Create a second cursor to verify data is visible immediately
908+ cursor2 = connection .cursor ()
909+ await cursor2 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
910+ data = await cursor2 .fetchall ()
911+ assert len (data ) == 1 , "Data should be visible immediately with autocommit"
912+ assert data [0 ] == [1 , "autocommit_test" ], "Data should match inserted values"
913+
914+ # Now test with explicit BEGIN - this should be a no-op when autocommit is enabled
915+ result = await cursor .execute ("BEGIN TRANSACTION" )
916+ assert result == 0 , "BEGIN TRANSACTION should return 0 rows"
917+ assert (
918+ not connection .in_transaction
919+ ), "Transaction should not be started when autocommit is enabled"
920+
921+ await cursor .execute (
922+ f"INSERT INTO \" { table_name } \" VALUES (2, 'no_transaction_test')"
923+ )
924+
925+ # ROLLBACK should fail since no transaction was started
926+ with raises (Exception ):
927+ await cursor .execute ("ROLLBACK" )
928+
929+ # The second insert should not be rolled back since it was committed immediately
930+ await cursor .execute (f'SELECT * FROM "{ table_name } " WHERE id = 2' )
931+ data = await cursor .fetchall ()
932+ assert (
933+ len (data ) == 1
934+ ), "Data should remain committed since no transaction was started"
935+ assert data [0 ] == [2 , "no_transaction_test" ], "Data should match inserted values"
936+
937+ # Verify data is visible from another cursor (confirming it was committed)
938+ cursor2 = connection .cursor ()
939+ await cursor2 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 2' )
940+ data = await cursor2 .fetchall ()
941+ assert len (data ) == 1 , "Data should be visible from other cursors"
942+ assert data [0 ] == [2 , "no_transaction_test" ], "Data should match inserted values"
943+
944+
945+ async def test_connection_commit (
946+ connection : Connection , create_drop_test_table_setup_teardown_async : Callable
947+ ) -> None :
948+ """Test that connection.commit() works correctly."""
949+ table_name = create_drop_test_table_setup_teardown_async
950+
951+ cursor = connection .cursor ()
952+ # Start a transaction
953+ await cursor .execute ("BEGIN TRANSACTION" )
954+ await cursor .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'commit_test')" )
955+
956+ # Call commit on connection level
957+ await connection .commit ()
958+
959+ # Verify data is now visible in a new cursor
960+ cursor2 = connection .cursor ()
961+ await cursor2 .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
962+ data = await cursor2 .fetchall ()
963+ assert len (data ) == 1 , "Data should be visible after connection.commit()"
964+ assert data [0 ] == [1 , "commit_test" ], "Data should match inserted values"
965+
966+
967+ async def test_connection_rollback (
968+ connection : Connection , create_drop_test_table_setup_teardown_async : Callable
969+ ) -> None :
970+ """Test that connection.rollback() works correctly."""
971+ table_name = create_drop_test_table_setup_teardown_async
972+
973+ cursor = connection .cursor ()
974+ # Start a transaction
975+ await cursor .execute ("BEGIN TRANSACTION" )
976+ await cursor .execute (f"INSERT INTO \" { table_name } \" VALUES (1, 'rollback_test')" )
977+
978+ # Verify data is visible within the transaction
979+ await cursor .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
980+ data = await cursor .fetchall ()
981+ assert len (data ) == 1 , "Data should be visible within transaction"
982+
983+ # Call rollback on connection level
984+ await connection .rollback ()
985+
986+ # Verify data is no longer visible
987+ await cursor .execute (f'SELECT * FROM "{ table_name } " WHERE id = 1' )
988+ data = await cursor .fetchall ()
989+ assert len (data ) == 0 , "Data should be rolled back after connection.rollback()"
0 commit comments