Ejemplo n.º 1
0
    def begin(self):
        """Begin a transaction on the database.

        :rtype: bytes
        :returns: the ID for the newly-begun transaction.
        :raises ValueError:
            if the transaction is already begun, committed, or rolled back.
        """
        if self._transaction_id is not None:
            raise ValueError("Transaction already begun")

        if self.committed is not None:
            raise ValueError("Transaction already committed")

        if self.rolled_back:
            raise ValueError("Transaction is already rolled back")

        database = self._session._database
        api = database.spanner_api
        metadata = _metadata_with_prefix(database.name)
        txn_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        with trace_call("CloudSpanner.BeginTransaction", self._session):
            response = api.begin_transaction(session=self._session.name,
                                             options=txn_options,
                                             metadata=metadata)
        self._transaction_id = response.id
        return self._transaction_id
Ejemplo n.º 2
0
    def commit(self, return_commit_stats=False):
        """Commit mutations to the database.

        :type return_commit_stats: bool
        :param return_commit_stats:
          If true, the response will return commit stats which can be accessed though commit_stats.

        :rtype: datetime
        :returns: timestamp of the committed changes.
        """
        self._check_state()
        database = self._session._database
        api = database.spanner_api
        metadata = _metadata_with_prefix(database.name)
        txn_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        trace_attributes = {"num_mutations": len(self._mutations)}
        request = CommitRequest(
            session=self._session.name,
            mutations=self._mutations,
            single_use_transaction=txn_options,
            return_commit_stats=return_commit_stats,
        )
        with trace_call("CloudSpanner.Commit", self._session,
                        trace_attributes):
            response = api.commit(
                request=request,
                metadata=metadata,
            )
        self.committed = response.commit_timestamp
        self.commit_stats = response.commit_stats
        return self.committed
Ejemplo n.º 3
0
    def test_run_in_transaction_callback_raises_non_gax_error(self):
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud.spanner_v1.transaction import Transaction

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.rollback.return_value = None
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        class Testing(Exception):
            pass

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)
            raise Testing()

        with self.assertRaises(Testing):
            session.run_in_transaction(unit_of_work)

        self.assertIsNone(session._transaction)
        self.assertEqual(len(called_with), 1)
        txn, args, kw = called_with[0]
        self.assertIsInstance(txn, Transaction)
        self.assertIsNone(txn.committed)
        self.assertTrue(txn.rolled_back)
        self.assertEqual(args, ())
        self.assertEqual(kw, {})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        gax_api.begin_transaction.assert_called_once_with(
            session=self.SESSION_NAME,
            options=expected_options,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )
        gax_api.rollback.assert_called_once_with(
            session=self.SESSION_NAME,
            transaction_id=TRANSACTION_ID,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )
Ejemplo n.º 4
0
    def commit(self, return_commit_stats=False, request_options=None):
        """Commit mutations to the database.

        :type return_commit_stats: bool
        :param return_commit_stats:
          If true, the response will return commit stats which can be accessed though commit_stats.

        :type request_options:
            :class:`google.cloud.spanner_v1.types.RequestOptions`
        :param request_options:
                (Optional) Common options for this request.
                If a dict is provided, it must be of the same form as the protobuf
                message :class:`~google.cloud.spanner_v1.types.RequestOptions`.

        :rtype: datetime
        :returns: timestamp of the committed changes.
        """
        self._check_state()
        database = self._session._database
        api = database.spanner_api
        metadata = _metadata_with_prefix(database.name)
        txn_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        trace_attributes = {"num_mutations": len(self._mutations)}

        if request_options is None:
            request_options = RequestOptions()
        elif type(request_options) == dict:
            request_options = RequestOptions(request_options)
        request_options.transaction_tag = self.transaction_tag

        # Request tags are not supported for commit requests.
        request_options.request_tag = None

        request = CommitRequest(
            session=self._session.name,
            mutations=self._mutations,
            single_use_transaction=txn_options,
            return_commit_stats=return_commit_stats,
            request_options=request_options,
        )
        with trace_call("CloudSpanner.Commit", self._session,
                        trace_attributes):
            response = api.commit(
                request=request,
                metadata=metadata,
            )
        self.committed = response.commit_timestamp
        self.commit_stats = response.commit_stats
        return self.committed
Ejemplo n.º 5
0
    def commit(self):
        """Commit mutations to the database.

        :rtype: datetime
        :returns: timestamp of the committed changes.
        """
        self._check_state()
        database = self._session._database
        api = database.spanner_api
        metadata = _metadata_with_prefix(database.name)
        txn_options = TransactionOptions(read_write=TransactionOptions.ReadWrite())
        trace_attributes = {"num_mutations": len(self._mutations)}
        with trace_call("CloudSpanner.Commit", self._session, trace_attributes):
            response = api.commit(
                session=self._session.name,
                mutations=self._mutations,
                single_use_transaction=txn_options,
                metadata=metadata,
            )
        self.committed = response.commit_timestamp
        return self.committed
Ejemplo n.º 6
0
    def test_run_in_transaction_w_abort_w_retry_metadata(self):
        import datetime
        from google.api_core.exceptions import Aborted
        from google.protobuf.duration_pb2 import Duration
        from google.rpc.error_details_pb2 import RetryInfo
        from google.cloud.spanner_v1 import CommitResponse
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud._helpers import UTC
        from google.cloud._helpers import _datetime_to_pb_timestamp
        from google.cloud.spanner_v1.transaction import Transaction

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        RETRY_SECONDS = 12
        RETRY_NANOS = 3456
        retry_info = RetryInfo(
            retry_delay=Duration(seconds=RETRY_SECONDS, nanos=RETRY_NANOS))
        trailing_metadata = [("google.rpc.retryinfo-bin",
                              retry_info.SerializeToString())]
        aborted = _make_rpc_error(Aborted, trailing_metadata=trailing_metadata)
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        now = datetime.datetime.utcnow().replace(tzinfo=UTC)
        now_pb = _datetime_to_pb_timestamp(now)
        response = CommitResponse(commit_timestamp=now_pb)
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.commit.side_effect = [aborted, response]
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)

        with mock.patch("time.sleep") as sleep_mock:
            session.run_in_transaction(unit_of_work, "abc", some_arg="def")

        sleep_mock.assert_called_once_with(RETRY_SECONDS + RETRY_NANOS / 1.0e9)
        self.assertEqual(len(called_with), 2)

        for index, (txn, args, kw) in enumerate(called_with):
            self.assertIsInstance(txn, Transaction)
            if index == 1:
                self.assertEqual(txn.committed, now)
            else:
                self.assertIsNone(txn.committed)
            self.assertEqual(args, ("abc", ))
            self.assertEqual(kw, {"some_arg": "def"})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        self.assertEqual(
            gax_api.begin_transaction.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    options=expected_options,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 2,
        )
        self.assertEqual(
            gax_api.commit.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    mutations=txn._mutations,
                    transaction_id=TRANSACTION_ID,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 2,
        )
Ejemplo n.º 7
0
    def test_run_in_transaction_w_abort_no_retry_metadata(self):
        import datetime
        from google.api_core.exceptions import Aborted
        from google.cloud.spanner_v1 import CommitResponse
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud._helpers import UTC
        from google.cloud._helpers import _datetime_to_pb_timestamp
        from google.cloud.spanner_v1.transaction import Transaction

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        now = datetime.datetime.utcnow().replace(tzinfo=UTC)
        now_pb = _datetime_to_pb_timestamp(now)
        aborted = _make_rpc_error(Aborted, trailing_metadata=[])
        response = CommitResponse(commit_timestamp=now_pb)
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.commit.side_effect = [aborted, response]
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)
            return "answer"

        return_value = session.run_in_transaction(unit_of_work,
                                                  "abc",
                                                  some_arg="def")

        self.assertEqual(len(called_with), 2)
        for index, (txn, args, kw) in enumerate(called_with):
            self.assertIsInstance(txn, Transaction)
            self.assertEqual(return_value, "answer")
            self.assertEqual(args, ("abc", ))
            self.assertEqual(kw, {"some_arg": "def"})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        self.assertEqual(
            gax_api.begin_transaction.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    options=expected_options,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 2,
        )
        self.assertEqual(
            gax_api.commit.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    mutations=txn._mutations,
                    transaction_id=TRANSACTION_ID,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 2,
        )
Ejemplo n.º 8
0
    def test_run_in_transaction_w_args_w_kwargs_wo_abort(self):
        import datetime
        from google.cloud.spanner_v1 import CommitResponse
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud._helpers import UTC
        from google.cloud._helpers import _datetime_to_pb_timestamp
        from google.cloud.spanner_v1.transaction import Transaction

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        now = datetime.datetime.utcnow().replace(tzinfo=UTC)
        now_pb = _datetime_to_pb_timestamp(now)
        response = CommitResponse(commit_timestamp=now_pb)
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.commit.return_value = response
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)
            return 42

        return_value = session.run_in_transaction(unit_of_work,
                                                  "abc",
                                                  some_arg="def")

        self.assertIsNone(session._transaction)
        self.assertEqual(len(called_with), 1)
        txn, args, kw = called_with[0]
        self.assertIsInstance(txn, Transaction)
        self.assertEqual(return_value, 42)
        self.assertEqual(args, ("abc", ))
        self.assertEqual(kw, {"some_arg": "def"})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        gax_api.begin_transaction.assert_called_once_with(
            session=self.SESSION_NAME,
            options=expected_options,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )
        gax_api.commit.assert_called_once_with(
            session=self.SESSION_NAME,
            mutations=txn._mutations,
            transaction_id=TRANSACTION_ID,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )
Ejemplo n.º 9
0
    def test_run_in_transaction_w_timeout(self):
        from google.api_core.exceptions import Aborted
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud.spanner_v1.transaction import Transaction

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        aborted = _make_rpc_error(Aborted, trailing_metadata=[])
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.commit.side_effect = aborted
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)

        # retry several times to check backoff
        def _time(_results=[1, 2, 4, 8]):
            return _results.pop(0)

        with mock.patch("time.time", _time):
            if HAS_OPENTELEMETRY_INSTALLED:
                with mock.patch("opentelemetry.util.time", _ConstantTime()):
                    with mock.patch("time.sleep") as sleep_mock:
                        with self.assertRaises(Aborted):
                            session.run_in_transaction(unit_of_work,
                                                       timeout_secs=8)
            else:
                with mock.patch("time.sleep") as sleep_mock:
                    with self.assertRaises(Aborted):
                        session.run_in_transaction(unit_of_work,
                                                   timeout_secs=8)

        # unpacking call args into list
        call_args = [call_[0][0] for call_ in sleep_mock.call_args_list]
        call_args = list(map(int, call_args))
        assert call_args == [2, 4]
        assert sleep_mock.call_count == 2

        self.assertEqual(len(called_with), 3)
        for txn, args, kw in called_with:
            self.assertIsInstance(txn, Transaction)
            self.assertIsNone(txn.committed)
            self.assertEqual(args, ())
            self.assertEqual(kw, {})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        self.assertEqual(
            gax_api.begin_transaction.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    options=expected_options,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 3,
        )
        self.assertEqual(
            gax_api.commit.call_args_list,
            [
                mock.call(
                    session=self.SESSION_NAME,
                    mutations=txn._mutations,
                    transaction_id=TRANSACTION_ID,
                    metadata=[("google-cloud-resource-prefix", database.name)],
                )
            ] * 3,
        )
Ejemplo n.º 10
0
    def test_run_in_transaction_w_abort_w_retry_metadata_deadline(self):
        import datetime
        from google.api_core.exceptions import Aborted
        from google.protobuf.duration_pb2 import Duration
        from google.rpc.error_details_pb2 import RetryInfo
        from google.cloud.spanner_v1 import CommitResponse
        from google.cloud.spanner_v1 import (
            Transaction as TransactionPB,
            TransactionOptions,
        )
        from google.cloud.spanner_v1.transaction import Transaction
        from google.cloud._helpers import UTC
        from google.cloud._helpers import _datetime_to_pb_timestamp

        TABLE_NAME = "citizens"
        COLUMNS = ["email", "first_name", "last_name", "age"]
        VALUES = [
            ["*****@*****.**", "Phred", "Phlyntstone", 32],
            ["*****@*****.**", "Bharney", "Rhubble", 31],
        ]
        TRANSACTION_ID = b"FACEDACE"
        RETRY_SECONDS = 1
        RETRY_NANOS = 3456
        transaction_pb = TransactionPB(id=TRANSACTION_ID)
        now = datetime.datetime.utcnow().replace(tzinfo=UTC)
        now_pb = _datetime_to_pb_timestamp(now)
        response = CommitResponse(commit_timestamp=now_pb)
        retry_info = RetryInfo(
            retry_delay=Duration(seconds=RETRY_SECONDS, nanos=RETRY_NANOS))
        trailing_metadata = [("google.rpc.retryinfo-bin",
                              retry_info.SerializeToString())]
        aborted = _make_rpc_error(Aborted, trailing_metadata=trailing_metadata)
        gax_api = self._make_spanner_api()
        gax_api.begin_transaction.return_value = transaction_pb
        gax_api.commit.side_effect = [aborted, response]
        database = self._make_database()
        database.spanner_api = gax_api
        session = self._make_one(database)
        session._session_id = self.SESSION_ID

        called_with = []

        def unit_of_work(txn, *args, **kw):
            called_with.append((txn, args, kw))
            txn.insert(TABLE_NAME, COLUMNS, VALUES)

        # retry once w/ timeout_secs=1
        def _time(_results=[1, 1.5]):
            return _results.pop(0)

        with mock.patch("time.time", _time):
            if HAS_OPENTELEMETRY_INSTALLED:
                with mock.patch("opentelemetry.util.time", _ConstantTime()):
                    with mock.patch("time.sleep") as sleep_mock:
                        with self.assertRaises(Aborted):
                            session.run_in_transaction(unit_of_work,
                                                       "abc",
                                                       timeout_secs=1)
            else:
                with mock.patch("time.sleep") as sleep_mock:
                    with self.assertRaises(Aborted):
                        session.run_in_transaction(unit_of_work,
                                                   "abc",
                                                   timeout_secs=1)

        sleep_mock.assert_not_called()

        self.assertEqual(len(called_with), 1)
        txn, args, kw = called_with[0]
        self.assertIsInstance(txn, Transaction)
        self.assertIsNone(txn.committed)
        self.assertEqual(args, ("abc", ))
        self.assertEqual(kw, {})

        expected_options = TransactionOptions(
            read_write=TransactionOptions.ReadWrite())
        gax_api.begin_transaction.assert_called_once_with(
            session=self.SESSION_NAME,
            options=expected_options,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )
        gax_api.commit.assert_called_once_with(
            session=self.SESSION_NAME,
            mutations=txn._mutations,
            transaction_id=TRANSACTION_ID,
            metadata=[("google-cloud-resource-prefix", database.name)],
        )