Uh oh!
There was an error while loading. Please reload this page.
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
- Notifications
You must be signed in to change notification settings - Fork 103
feat: Implementation for Begin and Rollback clientside statements#1041
Merged
Uh oh!
There was an error while loading. Please reload this page.
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
75bf199
fix: Refactoring tests to use fixtures properly
ankiaga 5fb5610
Not using autouse fixtures for few tests where not needed
ankiaga 3e80473
feat: Implementation for Begin and Rollback clientside statements
ankiaga 6318ce9
Incorporating comments
ankiaga df707d7
Formatting
ankiaga ba1e60d
Merge branch 'main' into client_begin
ankiaga 792c0dc
Comments incorporated
ankiaga 6d5f419
Fixing tests
ankiaga c59e468
Small fix
ankiaga 70ac65e
Test fix as emulator was going OOM
ankiaga 56ecb16
Merge branch 'main' into client_begin
ankiaga 2bd98be
Merge branch 'main' into client_begin
ankiaga d758f02
Merge branch 'main' into client_begin
ankiaga File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
13 changes: 12 additions & 1 deletion
13 google/cloud/spanner_dbapi/client_side_statement_executor.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10 google/cloud/spanner_dbapi/client_side_statement_parser.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -34,7 +34,9 @@ | ||
| from google.rpc.code_pb2 import ABORTED | ||
| AUTOCOMMIT_MODE_WARNING = "This method is non-operational in autocommit mode" | ||
| CLIENT_TRANSACTION_NOT_STARTED_WARNING = ( | ||
| "This method is non-operational as transaction has not started" | ||
| ) | ||
| MAX_INTERNAL_RETRIES = 50 | ||
| @@ -104,6 +106,7 @@ def __init__(self, instance, database=None, read_only=False): | ||
| self._read_only = read_only | ||
| self._staleness = None | ||
| self.request_priority = None | ||
| self._transaction_begin_marked = False | ||
| @property | ||
| def autocommit(self): | ||
| @@ -122,7 +125,7 @@ def autocommit(self, value): | ||
| :type value: bool | ||
| :param value: New autocommit mode state. | ||
| """ | ||
| if value and not self._autocommit and self.inside_transaction: | ||
| if value and not self._autocommit and self._spanner_transaction_started: | ||
| self.commit() | ||
| self._autocommit = value | ||
| @@ -137,17 +140,35 @@ def database(self): | ||
| return self._database | ||
| @property | ||
| def inside_transaction(self): | ||
ankiaga marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| """Flag: transaction is started. | ||
| def _spanner_transaction_started(self): | ||
| """Flag: whether transaction started at Spanner. This means that we had | ||
| made atleast one call to Spanner. Property client_transaction_started | ||
| would always be true if this is true as transaction has to start first | ||
| at clientside than at Spanner | ||
| Returns: | ||
| bool: True if transaction begun, False otherwise. | ||
| bool: True if Spanner transaction started, False otherwise. | ||
| """ | ||
| return ( | ||
| self._transaction | ||
| and not self._transaction.committed | ||
| and not self._transaction.rolled_back | ||
| ) | ||
| ) or (self._snapshot is not None) | ||
| @property | ||
| def inside_transaction(self): | ||
| """Deprecated property which won't be supported in future versions. | ||
| Please use spanner_transaction_started property instead.""" | ||
| return self._spanner_transaction_started | ||
| @property | ||
ankiaga marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| def _client_transaction_started(self): | ||
| """Flag: whether transaction started at client side. | ||
| Returns: | ||
| bool: True if transaction started, False otherwise. | ||
| """ | ||
| return (not self._autocommit) or self._transaction_begin_marked | ||
olavloite marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| @property | ||
| def instance(self): | ||
| @@ -175,7 +196,7 @@ def read_only(self, value): | ||
| Args: | ||
| value (bool): True for ReadOnly mode, False for ReadWrite. | ||
| """ | ||
| if self.inside_transaction: | ||
| if self._spanner_transaction_started: | ||
| raise ValueError( | ||
| "Connection read/write mode can't be changed while a transaction is in progress. " | ||
| "Commit or rollback the current transaction and try again." | ||
| @@ -213,7 +234,7 @@ def staleness(self, value): | ||
| Args: | ||
| value (dict): Staleness type and value. | ||
| """ | ||
| if self.inside_transaction: | ||
| if self._spanner_transaction_started: | ||
| raise ValueError( | ||
| "`staleness` option can't be changed while a transaction is in progress. " | ||
| "Commit or rollback the current transaction and try again." | ||
| @@ -331,15 +352,16 @@ def transaction_checkout(self): | ||
| """Get a Cloud Spanner transaction. | ||
| Begin a new transaction, if there is no transaction in | ||
| this connection yet. Return the begun one otherwise. | ||
| this connection yet. Return the started one otherwise. | ||
| The method is non operational in autocommit mode. | ||
ankiaga marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| This method is a no-op if the connection is in autocommit mode and no | ||
| explicit transaction has been started | ||
| :rtype: :class:`google.cloud.spanner_v1.transaction.Transaction` | ||
| :returns: A Cloud Spanner transaction object, ready to use. | ||
| """ | ||
| if not self.autocommit: | ||
| if not self.inside_transaction: | ||
| if not self.read_only and self._client_transaction_started: | ||
| if not self._spanner_transaction_started: | ||
| self._transaction = self._session_checkout().transaction() | ||
| self._transaction.begin() | ||
| @@ -354,7 +376,7 @@ def snapshot_checkout(self): | ||
| :rtype: :class:`google.cloud.spanner_v1.snapshot.Snapshot` | ||
| :returns: A Cloud Spanner snapshot object, ready to use. | ||
| """ | ||
| if self.read_only and not self.autocommit: | ||
| if self.read_only and self._client_transaction_started: | ||
| if not self._snapshot: | ||
| self._snapshot = Snapshot( | ||
| self._session_checkout(), multi_use=True, **self.staleness | ||
| @@ -369,55 +391,80 @@ def close(self): | ||
| The connection will be unusable from this point forward. If the | ||
| connection has an active transaction, it will be rolled back. | ||
| """ | ||
| if self.inside_transaction: | ||
| if self._spanner_transaction_started and not self.read_only: | ||
| self._transaction.rollback() | ||
ankiaga marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| if self._own_pool and self.database: | ||
| self.database._pool.clear() | ||
| self.is_closed = True | ||
| @check_not_closed | ||
| def begin(self): | ||
| """ | ||
| Marks the transaction as started. | ||
olavloite marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| :raises: :class:`InterfaceError`: if this connection is closed. | ||
| :raises: :class:`OperationalError`: if there is an existing transaction that has begin or is running | ||
| """ | ||
| if self._transaction_begin_marked: | ||
| raise OperationalError("A transaction has already started") | ||
| if self._spanner_transaction_started: | ||
| raise OperationalError( | ||
| "Beginning a new transaction is not allowed when a transaction is already running" | ||
| ) | ||
| self._transaction_begin_marked = True | ||
| def commit(self): | ||
| """Commits any pending transaction to the database. | ||
| This method is non-operational in autocommit mode. | ||
| This is a no-op if there is no active client transaction. | ||
| """ | ||
| if self.database is None: | ||
| raise ValueError("Database needs to be passed for this operation") | ||
| self._snapshot = None | ||
| if self._autocommit: | ||
| warnings.warn(AUTOCOMMIT_MODE_WARNING, UserWarning, stacklevel=2) | ||
| if not self._client_transaction_started: | ||
| warnings.warn( | ||
| CLIENT_TRANSACTION_NOT_STARTED_WARNING, UserWarning, stacklevel=2 | ||
| ) | ||
| return | ||
| self.run_prior_DDL_statements() | ||
| if self.inside_transaction: | ||
| if self._spanner_transaction_started: | ||
| try: | ||
| if not self.read_only: | ||
| if self.read_only: | ||
| self._snapshot = None | ||
| else: | ||
| self._transaction.commit() | ||
| self._release_session() | ||
| self._statements = [] | ||
| self._transaction_begin_marked = False | ||
| except Aborted: | ||
| self.retry_transaction() | ||
| self.commit() | ||
| def rollback(self): | ||
| """Rolls back any pending transaction. | ||
| This is a no-op if there is no active transaction or if the connection | ||
| is in autocommit mode. | ||
| This is a no-op if there is no active client transaction. | ||
| """ | ||
| self._snapshot = None | ||
| if self._autocommit: | ||
| warnings.warn(AUTOCOMMIT_MODE_WARNING, UserWarning, stacklevel=2) | ||
| elif self._transaction: | ||
| if not self.read_only: | ||
| if not self._client_transaction_started: | ||
| warnings.warn( | ||
| CLIENT_TRANSACTION_NOT_STARTED_WARNING, UserWarning, stacklevel=2 | ||
| ) | ||
| return | ||
| if self._spanner_transaction_started: | ||
| if self.read_only: | ||
| self._snapshot = None | ||
| else: | ||
| self._transaction.rollback() | ||
| self._release_session() | ||
| self._statements = [] | ||
| self._transaction_begin_marked = False | ||
| @check_not_closed | ||
| def cursor(self): | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.