2222from google .cloud ._helpers import UTC
2323
2424from google .cloud .spanner_dbapi .connection import Connection , connect
25- from google .cloud .spanner_dbapi .exceptions import ProgrammingError
25+ from google .cloud .spanner_dbapi .exceptions import ProgrammingError , OperationalError
2626from google .cloud .spanner_v1 import JsonObject
2727from google .cloud .spanner_v1 import gapic_version as package_version
2828from . import _helpers
@@ -80,42 +80,43 @@ def init_connection(self, request, shared_instance, dbapi_database):
8080 self ._cursor .close ()
8181 self ._conn .close ()
8282
83- @pytest .fixture
84- def execute_common_statements (self ):
83+ def _execute_common_statements (self , cursor ):
8584 # execute several DML statements within one transaction
86- self . _cursor .execute (
85+ cursor .execute (
8786 """
8887 INSERT INTO contacts (contact_id, first_name, last_name, email)
8988 VALUES (1, 'first-name', 'last-name', 'test.email@domen.ru')
9089 """
9190 )
92- self . _cursor .execute (
91+ cursor .execute (
9392 """
9493 UPDATE contacts
9594 SET first_name = 'updated-first-name'
9695 WHERE first_name = 'first-name'
9796 """
9897 )
99- self . _cursor .execute (
98+ cursor .execute (
10099 """
101100 UPDATE contacts
102101 SET email = 'test.email_updated@domen.ru'
103102 WHERE email = 'test.email@domen.ru'
104103 """
105104 )
106-
107- @pytest .fixture
108- def updated_row (self , execute_common_statements ):
109105 return (
110106 1 ,
111107 "updated-first-name" ,
112108 "last-name" ,
113109 "test.email_updated@domen.ru" ,
114110 )
115111
116- def test_commit (self , updated_row ):
112+ @pytest .mark .parametrize ("client_side" , [False , True ])
113+ def test_commit (self , client_side ):
117114 """Test committing a transaction with several statements."""
118- self ._conn .commit ()
115+ updated_row = self ._execute_common_statements (self ._cursor )
116+ if client_side :
117+ self ._cursor .execute ("""COMMIT""" )
118+ else :
119+ self ._conn .commit ()
119120
120121 # read the resulting data from the database
121122 self ._cursor .execute ("SELECT * FROM contacts" )
@@ -124,18 +125,80 @@ def test_commit(self, updated_row):
124125
125126 assert got_rows == [updated_row ]
126127
127- def test_commit_client_side (self , updated_row ):
128- """Test committing a transaction with several statements."""
129- self ._cursor .execute ("""COMMIT""" )
128+ @pytest .mark .noautofixt
129+ def test_begin_client_side (self , shared_instance , dbapi_database ):
130+ """Test beginning a transaction using client side statement,
131+ where connection is in autocommit mode."""
132+
133+ conn1 = Connection (shared_instance , dbapi_database )
134+ conn1 .autocommit = True
135+ cursor1 = conn1 .cursor ()
136+ cursor1 .execute ("begin transaction" )
137+ updated_row = self ._execute_common_statements (cursor1 )
138+
139+ # As the connection conn1 is not committed a new connection wont see its results
140+ conn2 = Connection (shared_instance , dbapi_database )
141+ cursor2 = conn2 .cursor ()
142+ cursor2 .execute ("SELECT * FROM contacts" )
143+ conn2 .commit ()
144+ got_rows = cursor2 .fetchall ()
145+ assert got_rows != [updated_row ]
146+
147+ assert conn1 ._transaction_begin_marked is True
148+ conn1 .commit ()
149+ assert conn1 ._transaction_begin_marked is False
150+
151+ # As the connection conn1 is committed a new connection should see its results
152+ conn3 = Connection (shared_instance , dbapi_database )
153+ cursor3 = conn3 .cursor ()
154+ cursor3 .execute ("SELECT * FROM contacts" )
155+ conn3 .commit ()
156+ got_rows = cursor3 .fetchall ()
157+ assert got_rows == [updated_row ]
130158
131- # read the resulting data from the database
159+ conn1 .close ()
160+ conn2 .close ()
161+ conn3 .close ()
162+ cursor1 .close ()
163+ cursor2 .close ()
164+ cursor3 .close ()
165+
166+ def test_begin_success_post_commit (self ):
167+ """Test beginning a new transaction post commiting an existing transaction
168+ is possible on a connection, when connection is in autocommit mode."""
169+ want_row = (2 , "first-name" , "last-name" , "test.email@domen.ru" )
170+ self ._conn .autocommit = True
171+ self ._cursor .execute ("begin transaction" )
172+ self ._cursor .execute (
173+ """
174+ INSERT INTO contacts (contact_id, first_name, last_name, email)
175+ VALUES (2, 'first-name', 'last-name', 'test.email@domen.ru')
176+ """
177+ )
178+ self ._conn .commit ()
179+
180+ self ._cursor .execute ("begin transaction" )
132181 self ._cursor .execute ("SELECT * FROM contacts" )
133182 got_rows = self ._cursor .fetchall ()
134183 self ._conn .commit ()
184+ assert got_rows == [want_row ]
135185
136- assert got_rows == [updated_row ]
186+ def test_begin_error_before_commit (self ):
187+ """Test beginning a new transaction before commiting an existing transaction is not possible on a connection, when connection is in autocommit mode."""
188+ self ._conn .autocommit = True
189+ self ._cursor .execute ("begin transaction" )
190+ self ._cursor .execute (
191+ """
192+ INSERT INTO contacts (contact_id, first_name, last_name, email)
193+ VALUES (2, 'first-name', 'last-name', 'test.email@domen.ru')
194+ """
195+ )
196+
197+ with pytest .raises (OperationalError ):
198+ self ._cursor .execute ("begin transaction" )
137199
138- def test_rollback (self ):
200+ @pytest .mark .parametrize ("client_side" , [False , True ])
201+ def test_rollback (self , client_side ):
139202 """Test rollbacking a transaction with several statements."""
140203 want_row = (2 , "first-name" , "last-name" , "test.email@domen.ru" )
141204
@@ -162,7 +225,11 @@ def test_rollback(self):
162225 WHERE email = 'test.email@domen.ru'
163226 """
164227 )
165- self ._conn .rollback ()
228+
229+ if client_side :
230+ self ._cursor .execute ("ROLLBACK" )
231+ else :
232+ self ._conn .rollback ()
166233
167234 # read the resulting data from the database
168235 self ._cursor .execute ("SELECT * FROM contacts" )
0 commit comments